ERROR USUARIO NO VALIDADO 1

MÓDULO DE PROCESOS

INTRODUCCIÓN

Este módulo incide fundamentalmente en los servicios de procesos suministrados por un sistema operativo tipo UNIX.

Se utilizarán los siguientes programas, que deberá descargarse de la Web y compilar para su ejecución:

10Fork.c Argumentos.c Cada3Segs.c
carga1 Compart_pth.c Compart_prc.c
Concurr_pth.c Concurr_prc.c CPUa000.c
CPUa100.c Crear_Proceso.c FEW_signal.c
Huerfano.c Identificadores.c Invocar_Exec.c
Muerto.c Parado.c Salir.c
Signal.c Times_B.c UnSegundo.c
Utilizando_Entorno.c Ventanilla_prc.c Ventanilla_pth.c
Ventanilla_srl.c


Los mandatos externos utilizados en este módulo son los siguientes:

cat export fg
find free gcc
jobs kill ls
more pstree ps
top stty


Las funciones de biblioteca utilizadas en dichos programas son las siguientes:

FUNCIÓN BIBLIOTECA SINTAXIS Y BREVE DESCRIPCIÓN
fprintf stdio.h * int fprintf(FILE *stream, const char *format, …);
* Escribe en la salida especificada por stream, los parámetros especificados, según el formato especificado en format. Como resultado proporciona el número de caracteres que se han escrito.
free stdlib.h *void free(void *ptr);
*Provoca que el espacio referenciado por ptr, y que ha sido reservado previamente por la función malloc(), sea liberado para posterior uso.
getchar stdio.h *int getchar (void)
*Proporciona el siguiente carácter, que se recibe a través de la entrada estándar.
malloc stdlib.h *void *malloc (size_t size);
*Permite reservar la cantidad de memoria especificada por size en tiempo de ejecución. Como valor de retorno proporciona un puntero a la primera posición de la zona de memoria reservada.
perror stdio.h *void perror(const char *%s);
*Transcribe el valor de la variable global errno a un string t, e imprime el string s, seguido del string t en la salida estándar de error.
printf stdio.h *int printf(const char *format, …);
*Escribe en la salida estándar, los parámetros especificados, según el formato especificado en format. Como resultado proporciona el número de caracteres que se han escrito.
setbuf stdio.h *void setbuf ( FILE * stream , char * buffer );
*Cambia el buffer utilizado para las operaciones de entrada salida con el stream especificado. Si el buffer especificado es NULL deshabilita el buffer de stream
strsignal string.h *char *strsignal(int sig);
*Devuelve una cadena que describe la señal cuyo número corresponde al parámetro sig. La cadena sólo puede ser usada hasta la siguiente llamada a strsignal().
getenv stdlib.h char *getenv(const char *name);
*Consulta en la lista de variables de entorno el valor de la variable que se pasa como argumento, devolviendo un puntero al correspondiente valor.
putenv stdlib.h int putenv(char *string);
*Añade una nueva variable al entorno con el valor asociado o modifica el valor de una ya existente.


Los servicios UNIX utilizados en dichos programas son los siguientes:

alarm execl execvp exit
fork getegid geteuid
getgid getpid getppid getuid
pause pthread_create pthread_detach pthread_join
read sched_yield sigaction
signal sleep times usleep
wait waitpid write

Del directorio proc se manejaran los siguientes subdirectorios:

*/proc/<PID>/status

CONTROL DE PROCESOS

Un proceso es una abstracción que se utiliza en Linux para representar un programa que está en funcionamiento

El kernel recopila información sobre los procesos que están activos en el sistema. Entre los datos que se recopilan se encuentran, por ejemplo:

  • Estado del proceso (inactivo, listo para iniciarse, etc.)
  • Prioridad de ejecución
  • Información sobre los recursos que está utilizando
  • Información sobre los archivos o puertos de comunicaciones que está utilizando.
  • Propietario del proceso
  • La máscara de señales

La mayoría de información sobre un proceso se almacena en su BCP (Bloque de Control de Proceso). Además, como ya se comentó en el módulo anterior, en el directorio /proc podemos encontrar un subdirectorio (/proc/<PID>/) por cada proceso en ejecución. En este subdirectorio, como ya se había comentado, podemos encontrar la información relativa los descriptores de fichero que está manejando el proceso (ls -l /proc/<PID>/fd/), pero además podemos encontrar también información relativa al estado del proceso en los ficheros:

  • /proc/<PID>/stat
  • /proc/<PID>/statm
  • /proc/<PID>/status



El fichero /proc/<PID>/status, proporciona información sobre el proceso en un formato más inteligible. Veamos el siguiente ejemplo:

  alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ cat /proc/self/status
                /* Name of the executable */
  Name:         cat
                /* Current state of the process */
  State:        R (running)
                /* Sleep average */
  SleepAVG:     88%
                /* Thread group ID *
  Tgid:         27888
                /* Process ID */
  Pid:          27888
                /* Parent PID */
  PPid:         25468
                /* Tracer PID */
  TracerPid:    0
                /* User Ids Real, effective, saved, file system */
  Uid:          1000  1000      1000    1000
                /* Group IDs Real, effective, saved, file system */
  Gid:          2001  2001      2001    2001
                /* FD table size */
  FDSize:       256
               /* Process groups it belongs to */
  Groups:       2001
                /* Virtual memory peak */
  VmPeak:       58924 kB
                /* Virtual memory size */
  VmSize:       58924 kB
                /* Number of "locked" pages that cannot be swapped out */
  VmLck:        0 kB
                /* Maximum number of page frames ever owned by the process*/
  VmHWM:        480 kB
                /* Resident system set: physical memory actually used */
  VmRSS:        480 kB
                /* Size of Data segment */
  VmData:       164 kB
                /* Stack size */ 
  VmStk:        84 kB
                /* Executable size */ 
  VmExe:        20 kB
                /* Loaded libraries */ 
  VmLib:        1440 kB
                /* Page table entries */ 
  VmPTE:        36 kB
                /* Initial address of the heap */
  StaBrk:       027ed000 kB
                /* Current final address of the heap */
  Brk:          0280e000 kB
                /* Initial address of User Mode stack */
  StaStk:       7fff51240b30 kB
                /* Number of threads */
  Threads:      1
                /* Signals queue entry. */
  SigQ:         2/139264
                /* Pending signals */
  SigPnd:       0000000000000000
                /* Shared pending signals */ 
  ShdPnd:       0000000000000000
                /* Blocked signals */
  SigBlk:       0000000000000000
                /* Signals to be ignored */
  SigIgn:       0000000000000000
                /* Signals caught */ 
  SigCgt:       0000000000000000
                /* Inherited capabilities */ 
  CapInh:       0000000000000000
                /* Permitted capabilities */ 
  CapPrm:       0000000000000000
                /* Effective capabilities */ 
  CapEff:       0000000000000000
                /* on which CPUs it may be scheduled */
  Cpus_allowed: 00000000,00000000,00000000,00000000,00000000,00000000,00000000,000000ff
                /* on which Memory Nodes it may obtain memory */
  Mems_allowed: 00000000,00000001

En nuestro caso, hemos utilizado en lugar del PID del proceso, self, lo cual quiere decir, que estamos accediendo al estado del proceso que está actualmente en ejecución (cat). Además, se han añadido las líneas de comentarios (que no son generadas por el mandato) para explicar cada uno de los campos.

Realice los siguientes pasos:

  • Compile y ejecute en background el programa Cada3Segs.c (Si no lo encuentra en el directorio /ModuloProcesos, cópielo de /ModuloMemoria)
  • Acceda al fichero status del proceso Cada3Segs que está en ejecución


  • Mate el proceso mediante el mandato kill (al cual se le pasará como parámetro el PID del proceso a matar)

ESTADOS DEL PROCESO

Los estados principales de un proceso son tres: ejecución, listo y bloqueado.

El esquema de estados de proceso que tiene Linux incluye algunas alternativas adicionales al esquema clásico (ejecución, listo, bloqueado). En concreto un proceso bloqueado puede ser interrumpible o no interrumpible, y un proceso puede estar parado. El mandato ps puede suministrar el estado de los procesos, de acuerdo a las claves siguientes:

  • D (uninterruptible sleep) Bloqueado no interrumpible. Es igual que el estado S, pero no puede despertarlo una señal (cada vez es menos utilizado).
  • R (running or runnable) Ejecutando o listo para ejecutar (en la cola de ejecutable)
  • S (sleeping) Bloqueado interrumpible (esperando que se complete un evento). Puede despertarlo una señal.
  • T (traced or stopped) Parado por una señal de control de trabajo o porque está siendo monitorizado.
  • Z (zombie) Zombi.

Observe que la clave R indica dos estados: en ejecución y listo para ejecutar.

Para visualizar los procesos en Linux, disponemos de diferentes herramientas, entre las que podemos citar: ps y top.

El mandato ps, que ya hemos visto anteriormente, nos proporciona un resumen puntual del sistema. Por el contrario, el mandato top, proporciona un resumen continuamente actualizado de los procesos y sus recursos. Además se puede configurar tanto la frecuencia de actualización como la información que muestra.

Veamos el siguiente ejemplo:

  1. Compile y ejecute en background el programa CPUa100.c
  2. Compile el programa CPUa000.c. A continuación ejecútelo y pulse [Crl+z] (lo cual solicita la parada del proceso en ejecución).
  3. Ejecute el mandato top. Comprobará que se muestran todos los procesos en ejecución en la maquina. Para acotar la información, pulsaremos la tecla u, y a continuación introduciremos el nombre de nuestro usuario (p.ej. alumno).




Seguidamente salga del mandato top pulsando la tecla q. Ejecute el mandato ps -l y compare la información proporcionada por el mandato ps y el mandato top.

MULTIPROCESO Y JERARQUÍA DE PROCESOS

Un sistema UNIX es multiproceso y establece una jerarquía de procesos a modo de árbol genealógico.

El mandato pstree permite conocer la jerarquía de procesos. Añadiendo como argumento el pid o el nombre de la cuenta se obtienen la parte de la jerarquía que deriva de un proceso o que pertenece a un usuario.

Compile el programa Parado:

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc Parado.c -o Parado_alumno

Observe que como nombre de ejecutable hemos añadido al nombre del programa el identificador del usuario. En su caso, añada su identificador de usuario. Esto se realiza para diferenciar todos los programas Parado que puedan estar ejecutándose simultáneamente en el sistema.

Ejecute el programa Parado_alumno en segundo plano (background).

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Parado_alumno &
[1] 8777

Ejecute el mandato pstree <alumno>. Obtendrá el árbol con los procesos activos de su cuenta.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ pstree -A alumno
sshd---bash---Parado_alumno
            +-pstree

El ejemplo anterior se ha ejecutado desde un terminal ssh y se utiliza el shell bash, por lo que aparece el demonio sshd, el shell bash, el proceso Parado_alumno y el proceso pstree que está generando el árbol.

El resultado obtenido puede variar en función del shell que tenga definido en su sistema, y de que el acceso se haga desde un terminal ssh o desde un terminal local o, incluso, desde una interfaz gráfica. Se recomienda que haga este ejercicio desde un terminal ssh.

Ejecute ahora el mandato pstree sin usuario. Obtendrá todos los procesos del sistema. Busque en el árbol su proceso Parado_alumno. Observe que solamente una parte de estos procesos son suyos, el resto son procesos de otros usuarios.


Cada proceso pertenece a una serie de grupos de procesos.

Cada vez que se inicia una sesión de shell (por ejemplo, al abrir una ventana de terminal), se le asigna un identificador de sesión. En la línea de mandato se puede ordenar un único mandato o en general un grupo de mandatos concatenados (Por ejemplo ls | wc), acabando con la tecla Intro. Se denomina process group o job a los procesos generados por una línea de mandatos. A cada process group se le asigna un identificador, incluso si consta de un único proceso. Así, cada proceso pertenece siempre a un process group y a una sesión.

Nota: Las señales de teclado (^C,^Z…) se envían a todos los procesos del process group en primer plano (foreground).

Utilizando el mandato ps con la opción -j se obtiene el PGID (Process Group ID) y el SID (Session ID) de los procesos, dos grupos a los que pertenece el proceso. Como ya hemos visto, el fichero /proc/<PID>/status, también suministra información de los grupos a los que pertenece un proceso. Ejecute ps -j, obtendrá la lista de los procesos asociados al terminal con su PGID y su SID.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -j
 
PID   PGID  SID   TTY    TIME      CMD  
8527  8527  8527  pts/3  00:00:00  bash  
8777  8777  8527  pts/3  00:00:14  Parado_alumno
8778  8778  8527  pts/3  00:00:00  ps

Ejecute ps -je, obtendrá la lista de todos los procesos con su PGID y su SID.


Pase a primer plano el proceso Parado_alumno mediante el mandato fg y mátelo con [Ctrl+c].

SERVIDORES Y DEMONIOS DEL SISTEMA

En el arranque del sistema operativo se ponen en marcha diferentes servidores y demonios.

Un demonio es un proceso que se ejecuta en segundo plano y está encargado de realizar una función específica o tarea del sistema. Muchos demonios se inician durante el arranque del sistema y continúan ejecutándose mientras este se encuentre encendido, mientras que otros solo se activan bajo demanda. En este último sentido, nos encontramos con el demonio inetd, que es el encargado de iniciar otros demonios cuando son necesarios.

Como ya sabemos, utilizando el mandato ps, obtenemos información sobre los procesos del sistema. En este caso utilizaremos las siguientes opciones:

  • S.- muestra, para cada proceso, el tiempo empleado por él y por todos sus hijos.
  • k-cutime.- ordena según valores decrecientes (-) y de acuerdo al tiempo de usuario acumulado (cutime).
  • e y f.- se listan todos los procesos en formato largo.


Para facilitar la visualización de los resultados, se recomienda redirigir la salida del mandato a un fichero (p.ej. salida_ps.txt) de modo que después podamos visualizarlo.

Ejecute el mandato y visualice el fichero, que muestra los procesos con más uso de procesador acumulado.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps S  k-cutime -ef > salida_ps.txt
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ more salida_ps.txt
UID   PID    PPID  C  STIME  TTY  STAT  TIME     CMD  
root  1      0     4  Dec01  ?    S     1336:44  init [2]  
root  8899   1     6  Dec13  ?    S     690:16   /usr/sbin/apache  
root  8967   1     0  Dec01  ?    Ss    195:29   /usr/sbin/cron  
root  8670   1     0  Dec01  ?    Ss    241:47   /usr/sbin/sshd  
root  8234   1     0  Dec01  ?    Ss    190:14   /usr/sbin/inetd  
root  8143   1     0  Dec01  ?    Ss    76:36    /usr/bin/spamd -d -u pfilter  
root  9019   1     0  Dec01  ?    S     19:12    /usr/sbin/apache-ssl  
root  24698  1     0  Dec14  ?    Ss    8:00     /usr/lib/postfix/master  
...

El demonio init, que siempre tendrá el identificador PID 1, es el padre de todos los procesos de usuario y de la mayoría de los procesos de sistema. Como se puede ver, acumula el tiempo consumido por todos los procesos ejecutados desde el arranque del sistema. Además de ocuparse de las tareas de gestión de inicio de sesión, tiene la responsabilidad de eliminar todos los procesos que han dejado de usarse.

Además, podemos observar procesos cuyo dueño es el root, algunos de los cuales son demonios típicos. Por ejemplo encontrará el sshd, demonio que es el servidor del protocolo ssh, (protocolo que está usando para conectarse al sistema). También encontrará el demonio inetd que se utiliza por varios servicios de red como son el ntpdated, daytime o time, entre otros.


ARGUMENTOS DEL MAIN

Como ya hemos visto, todo programa se puede ejecutar escribiendo el nombre del ejecutable correspondiente. A continuación del mismo, y separados por blancos o tabuladores, podemos añadir otros textos, que denominamos argumentos, los cuales estarán disponibles para ser usados por el proceso que se ejecuta (programa en ejecución).

Asumiendo que el programa a ejecutar está escrito en lenguaje C, dichos argumentos podrán ser accedidos a través de los dos primeros parámetros formales de la función principal main(int argc, char*argv[], char*envp[]).

Dado el siguiente fragmento de código del programa Argumentos.c:

A-01.- #define MYNAME  "Argumentos"
A-02.- int main(int argc, char * argv[])
A-03.- {
A-04.-         int num;
A-05.-         for (num=0; num < argc; num++) 
A-06.-                 printf(MYNAME": argv[%d]        =%s\n", num, argv[num]);
A-07.-         return 0;
A-08.- }

y suponiendo que dicho programa se ejecuta en un directorio con el siguiente contenido:

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ls -l
total 24
-rw------- 1 alumno GrupoAlumno 932 Jul 23 13:56 Cada3Segs.c
-rw------- 1 alumno GrupoAlumno 10395 Jul 23 13:57 carga
-rw------- 1 alumno GrupoAlumno 26 Jul 23 13:57 carga1
-rw------- 1 alumno GrupoAlumno 445 Jul 23 13:56 CPUa100.c
-rw------- 1 alumno GrupoAlumno 905 Jul 23 13:57 CPUyES.c
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Argumentos C*



ENTORNO

Para que un proceso pueda acceder al entorno del mismo, podemos utilizar dos mecanismos diferentes. Por una parte, el entorno puede recibirse como parámetro en la función main, main(int argc, char*argv[], char*envp[]), y por otra parte, disponemos de las funciones de biblioteca getenv y putenv que permiten ver y modificar el entorno del proceso que las ejecuta.

La variable envp contienen una lista de las variables de entorno del Shell en el que se ejecuta el proceso, con el siguiente formato:

  • NOMBRE=VALOR.

Analice el siguiente fragmento de código, del programa Utilizando_Entorno.c:

UE-01.- #define MYNAME  "Utilizando_Entorno"
UE-02.- int main(int argc, char *argv[])
UE-03.- {
UE-04.-         char *var;
UE-05.-         if ((var=getenv("SEMILLA")))
UE-06.-                 srand(atoi(var));
UE-07.-         printf("\n\t[%d] - [%d]\n\n",rand()%6+1, rand()%6+1);
UE-08.-         return 0;
UE-09.- }


Supongamos el siguiente escenario donde ejecutamos el mismo programa en dos Shell diferentes: SHELL 1

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$export SEMILLA=666
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$./Utilizando_entorno

y a continuación ejecutamos: SHELL 2

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$./Utilizando_entorno


Supongamos por último que en un proceso se realiza la siguiente asignación:

 static char *variable = "Mi_Variable=mi_variable";
 putenv(variable);


Como ya hemos comentado reiteradamente, el directorio /proc, mantiene información sobre los procesos en ejecución. Si accedemos a un subdirectorio de un proceso concreto, nos encontraremos con un fichero llamada environ que mantiene información sobre las variables de entorno del proceso.

EL SERVICIO FORK()

El servicio fork crea un proceso idéntico al padre. Los procesos difieren en el PID y en el PPID.

Cuando un proceso crea otro proceso mediante el servicio fork, ambos continúan ejecutando en la siguiente sentencia al fork, pero existirán dos procesos ejecutando el mismo código en lugar de un único proceso, como sucedía antes de invocar el servicio fork. En el caso del proceso padre (el que ha creado el hijo), el valor devuelto por el servicio fork será el pid del proceso hijo recién creado, mientras que en el caso del proceso hijo, el valor devuelto por el servicio fork será 0. Obviamente, en caso de producirse un error durante la llamada a fork, no se creará el proceso hijo y el valor devuelto por fork será -1. (Se puede obtener más información en man fork).

CP-01.- #define MYNAME  "Crear_Proceso"
CP-02.- int ret;
CP-03.- ret = fork();
CP-04.- switch(ret) 
CP-05.- {
CP-06.-    case -1:
CP-07.-            perror(MYNAME": fork()");
CP-08.-            break;
CP-09.-    case  0:
CP-10.-            printf(MYNAME": \tPID=%d\tPPID=%d\n", getpid(), getppid());
CP-11.-            break;
CP-12.-    default:
CP-13.-            printf(MYNAME": \tPID=%d\tPPID=%d\n", getpid(), getppid());
CP-14.- }
CP-15.- pause();

El programa anterior, muestra el proceso de creación de un proceso hijo mediante el servicio fork. Los servicios getpid() y getppid() suministran, respectivamente, el pid del proceso y el del padre.

Como se puede observar, para que el proceso hijo ejecute un código distinto del código del padre es necesario incluir una sentencia de selección, que deberá incluir el caso en que el fork termine con error.

Supongamos que ejecutamos el programa anterior y que sabemos que el PID del Shell en el que se ejecuta el programa es 3000, y el PID del programa Crear_Proceso es 3100. Además no se crean procesos ajenos a Crear_Proceso, desde que se inicia hasta que finaliza.





En principio, el número de procesos que se puede crear estará limitado por las características o configuración del sistema. Analice el siguiente fragmento de código.

10F-01.- #define MYNAME  "10Fork"
10F-03.- pid_t pid;
10F-04.- int i;
10F-05.- for (i = 0; i < 10; i++) 
10F-06.- {
10F-07.-    pid = fork();
10F-08.-    if (pid == 0)
10F-09.-       break;
10F-10.- }
10F-11.- printf("El padre del proceso %d es %d\n", getpid(), getppid());
10F-12.- sleep(1);



Asumiendo que el PID del programa 10Fork es 3100, el del Shell desde el que se lanza el programa es PID=3000, y que no se crea ningún proceso ajeno al programa 10Fork durante la ejecución del programa:


EL SERVICIO EXIT()

Cuando se completa un proceso, mediante la invocación del servicio exit(int status), se comunica al proceso padre, que el proceso está listo para cerrarse. status es un valor de estado de terminación que se retorna al proceso padre. Puede ser 0, EXIT_SUCCESS, EXIT_FAILURE, u otro valor, aunque se recomienda usar el rango 0..255 pues solo el byte de menor peso de status se retorna al proceso padre, que lo interpreta como byte sin signo.

El siguiente programa, simplemente obtiene el valor de su PID y lo imprime tanto en formato decimal como en formato hexadecimal. Además, dicho valor, se utiliza como valor de retorno en la llamada al servicio exit.

S-01.-#define MYNAME  "Salir"
S-02.-int main(void)
S-03.-{
S-04.-        int pid = getpid();
S-05.-        printf(MYNAME": \tPID=%d=0x%x\n", pid, pid);
S-06.-        exit(pid);              
S-07.-}

Suponiendo que dispusiéramos de un programa, Estado, capaz de capturar el valor devuelto por la llamada exit del programa Salir, y que el PID del proceso Salir es 19816.


EL SERVICIO EXEC()

El servicio exec() cambia la imagen del proceso que lo ejecuta por una nueva imagen de proceso. Esto quiere decir, que se deja de ejecutar el programa que invoca el servicio, y pasa a ejecutarse el programa indicado como parámetro del servicio exec.

Analice los siguientes programas:

IE-01.- #define MYNAME  "Invocar_Exec"
IE-02.- #include <stdio.h>
IE-03.- #include <stdlib.h>
IE-04.- #include <unistd.h>
IE-05.- int main(void)
IE-06.- {
IE-07.-         printf(MYNAME": Antes del exec  \tPID=%d\tPPID=%d\n", getpid(), getppid());
IE-08.-         execl("./Identificadores", "./Identificadores", NULL);
IE-09.-         printf(MYNAME": Después del exec\tPID=%d\tPPID=%d\n", getpid(), getppid());
IE-10.-         return 0;
IE-11.- }
I-01.-#define MYNAME  "Identificadores"
I-02.-#include <stdio.h>
I-03.-#include <unistd.h>
I-04.-int main(void)
I-05.-{
I-06.-        printf(MYNAME":       PID  = %d\n", getpid() );
I-07.-        printf(MYNAME":       PPID = %d\n", getppid());
I-08.-        printf(MYNAME":       UID  = %d\n", getuid() );
I-09.-        printf(MYNAME":       EUID = %d\n", geteuid());
I-10.-        printf(MYNAME":       GID  = %d\n", getgid() );
I-11.-        printf(MYNAME":       EGID = %d\n", getegid());
I-12.-        return 0;
I-13.-}



Dado que los servicios del sistema operativo pueden fallar, devolviendo error, es conveniente tratar siempre el caso de error. Esto es especialmente importante para el servicio exec.

SETUID SETGID

Linux guarda un registro de los usuarios y de los grupos de usuario de forma numérica.

Como hemos visto en el programa Identificadores.c del apartado anterior, los procesos tendrán asociados unos identificadores de usuario (UID→real, EUID→efectivo) y de grupo (GID→real, EGID→efectivo).

Aunque en principio no es posible que un proceso cambie la identidad del usuario al que pertenece, hay una situación especial, en la que se puede cambiar tanto el UID como el GID del proceso. Si un fichero ejecutable tiene activados los bits, SETUID y SETGID, cuando se ejecute, el proceso cambiará su UID o su GID efectivos del usuario que ejecutó el proceso, al usuario o grupo que tenga ese fichero. Esto permite, por ejemplo, aumentar los privilegios de un usuario normal a la categoría de root.

Por ejemplo, el programa passwd, que vimos en el módulo de introducción a Linux, tiene activo el bit setuid. Esto también puede suponer una vulnerabilidad del sistema, si por ejemplo, un usuario no autorizado se introduce en el sistema y crea un shell privado setuid, que le daría privilegios de administrador en la máquina.

Para controlar los archivos que tienen activo el bit setuid en el sistema, podemos utilizar el mandato find.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ find /usr -perm /4000 -ls

Ejecute dicho mandato. Verá que se trata de programas como el passwd que puede ejecutar cualquier usuario, pero que ha de ejecutar con privilegios de sistema.


Es posible que no tenga privilegios para recorrer algunos subdirectorios. Para evitar que salgan por pantalla los correspondientes mensajes de error se puede redirigir la salida de error al /dev/null, como se indica a continuación.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ find /usr -perm /4000 -ls 2>/dev/null

PROCESO HUERFANO

Cuando un proceso muere antes del hijo que haya creado, este último es “adoptado”, como ya habíamos comentado, por el proceso init.

Analice siguiente fragmento de código del programa Huerfano.c.

H-01.- ret = fork();
H-02.- switch(ret) {
H-03.- case -1:
H-04.-         perror(" fork()");
H-05.-         break;
H-06.- case  0:
H-07.-         printf("\n:\tPID=%d\tPPID=%d\n", getpid(), getppid());
H-08.-         sleep(50);      
H-09.-         printf("\n:\tPID=%d\tPPID=%d\n", getpid(), getppid());
H-10.-         break;
H-11.- default:
H-12.-         printf(": \tPID=%d\tPPID=%d\n", getpid(), getppid());
H-13.-         sleep(20);
H-14.- }

Suponiendo que, desde un Shell (con PID=7000) ejecutamos el programa anterior (con PID=8000), que no se produce ningún error en la ejecución del mismo, y que el proceso hijo generado recibe el PID=9000, conteste a las siguientes preguntas:





PROCESO ZOMBI

Cuando un proceso termina sin que su padre esté ejecutando un servicio wait, el proceso queda en estado zombi.

M-01.- #define MYNAME  "Muerto"
M-02.- #include <stdio.h>
M-03.- #include <stdlib.h>
M-04.- #include <unistd.h>
M-05.- int main(void)
M-06.- {
M-07.-         int ret;
M-08.-         ret = fork();
M-09.-         switch(ret) {
M-10.-         case -1:
M-11.-                 perror(MYNAME": fork()");
M-12.-                 break;
M-13.-         case  0:
M-14.-                 printf(MYNAME": \tPID=%d\tPPID=%d\n", getpid(), getppid());
M-15.-                 exit(0);
M-16.--                break;
M-17.-         default:
M-18.-                 printf(MYNAME": \tPID=%d\tPPID=%d\n", getpid(), getppid());
M-19.-         }
M-20.-         pause();
M-21.-         return 0;
M-22.- }


Compile y ejecute el programa anterior en un Shell, en segundo plano (background).

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc Muerto.c -o Muerto
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Muerto &



A continuación liste los procesos asociados a su usuario.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -l -u alumno
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
5 S  1000 21137 21135  0  75   0 - 26202 stext  ?        00:00:00 sshd
0 S  1000 21138 21137  0  75   0 - 16275 wait   pts/1    00:00:00 bash
0 S  1000 22007 21138  0  76   0 -   911 pause  pts/1    00:00:00 Muerto
1 Z  1000 22008 22007  0  76   0 -     0 exit   pts/1    00:00:00 Muerto <defunct>
0 R  1000 22010 21138  0  77   0 - 16156 -      pts/1    00:00:00 ps

Observe que el proceso hijo (PID = 22008) está en estado Z, es decir, en estado zombi.

Por último, mediante el mandato fg, traiga el proceso Muerto a primer plano y mátelo mediante [Ctrl+C]

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ fg
./Muerto
[Ctrl+C]
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -l -u alumno
F S   UID   PID  PPID  C PRI  NI ADDR SZ WCHAN  TTY          TIME CMD
5 S  1000 21137 21135  0  75   0 - 26202 stext  ?        00:00:00 sshd
0 S  1000 21138 21137  0  75   0 - 16275 wait   pts/1    00:00:00 bash
0 R  1000 22037 21138  0  77   0 - 16156 -      pts/1    00:00:00 ps


SEÑALES

General

Las señales son interrupciones al proceso.

Las señales se pueden utilizar con distintos fines, por ejemplo:

  • Para la comunicación entre los procesos
  • Enviadas mediante combinación de teclas, a través del teclado, para interrumpir, detener o suspender procesos.
  • Las puede enviar el kernel cuando un proceso comete una infracción (p.ej. división por cero), o para comunicar un evento en el sistema (p.ej. finalización de un proceso hijo).

Cuando un proceso recibe una señal, pueden suceder dos cosas:

  • Si el proceso tiene asignada una rutina para el tratamiento de dicha señal, se pasará a ejecutar en el momento que se recibe la señal. Cuando la rutina finalice el tratamiento de la señal, el proceso continuará en el punto en el que se recibió la señal.
  • Si el proceso no tiene asignada una rutina para el tratamiento de la señal, entonces se realizará la acción por defecto. Que en muchos casos consiste en cerrar el proceso.

Como hemos comentado, a través del teclado, podemos enviar al proceso activo en primer plano ciertas señales, como por ejemplo:

  • La combinación [Ctrl+c], llamada “intr” → Envía señal SIGINT
  • La combinación [Ctrl+\], llamada “quit” → Envía señal SIGQUIT. Es posible que en su teclado español necesite utilizar la combinación [Ctrl+<] en vez de [Ctrl+\].
  • La combinación [Ctrl+z], llamada “susp” → Envía señal SIGTSTP

Las combinaciones de teclado son programables. Para ver los valores que tienen se puede utilizar el mandato stty. Dicho mandato también permite cambiar esos valores.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ stty -a
speed 38400 baud; rows 47; columns 158; line = 0;
intr = ^C; quit = ^\; erase = ^?; kill = ^U; eof = ^D; eol = <undef>; eol2 = <undef>; swtch = <undef>; 
start = ^Q; stop = ^S; susp = ^Z; rprnt = ^R; werase = ^W; lnext = ^V; flush = ^O; min = 1; time = 0;
-parenb -parodd cs8 -hupcl -cstopb cread -clocal -crtscts
-ignbrk -brkint -ignpar -parmrk -inpck -istrip -inlcr -igncr icrnl ixon -ixoff -iuclc -ixany -imaxbel -iutf8
opost -olcuc -ocrnl onlcr -onocr -onlret -ofill -ofdel nl0 cr0 tab0 bs0 vt0 ff0
isig icanon iexten echo echoe echok -echonl -noflsh -xcase -tostop -echoprt echoctl echoke

Observe que la nomenclatura utilizada es ^C en vez de [Ctrl+c] y que las teclas se refieren al teclado inglés, por lo que, en su teclado, la tecla «\» puede que corresponda a la «<» y la tecla «?» a la «-».

Analice el programa Parado, programa que nunca termina.

P-01.- #define MYNAME  "Parado"
P-02.- #include <unistd.h>
P-03.- int main(void)
P-04.- {
P-05.-         pause();
P-06.-         return 0;
P-07.- }

Compile y ejecute el programa Parado en background.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc Parado.c -o Parado
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Parado &

Observe que el proceso Parado está en estado S, pero aparece como pause (columna WCHAN).

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -l -u alumno
F  S  UID    PID    PPID   C  PRI  NI  ADDR  SZ    WCHAN   TTY    TIME      CMD  
5  S  27182  20783  20777  0  76   0   -     1810  -       ?      00:00:00  sshd  
0  S  27182  20784  20783  0  76   0   -     814   wait    pts/3  00:00:00  bash  
0  S  27182  21556  20783  0  76   0   -     816   -       pts/0  00:00:00  bash  
0  S  27182  22503  21556  0  76   0   -     361   pause   pts/0  00:00:00  Parado  
0  R  27182  22523  20784  0  77   0   -     651   -       pts/3  00:00:00  ps

A continuación devuelva el proceso a primer plano y mátelo con ^C.

Ejecute otra vez el programa y mátelo con ^\ o ^< según sea su teclado. Verá que ahora el proceso termina con un Quit.

Ejecute nuevamente el programa Parado y pulse ^Z. La acción por defecto de la señal SIGTSTP es detener el proceso. Sin embargo, esto no implica que el proceso haya finalizado. Si ejecuta el mandato jobs, comprobará que el proceso continúa en segundo plano, pero está parado.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ jobs
[1]+  Stopped                 ./Parado

Observe que el proceso Parado está en estado T.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -l -u alumno
F  S  UID    PID    PPID   C  PRI  NI  ADDR  SZ    WCHAN   TTY    TIME      CMD  
5  S  27182  20783  20777  0  76   0   -     1810  -       ?      00:00:00  sshd  
0  S  27182  20784  20783  0  76   0   -     814   wait    pts/3  00:00:00  bash  
0  S  27182  21556  20783  0  76   0   -     816   -       pts/0  00:00:00  bash  
0  T  27182  22503  21556  0  76   0   -     361   finish  pts/0  00:00:00  Parado  
0  R  27182  22523  20784  0  77   0   -     651   -       pts/3  00:00:00  ps

Traiga a primer plano el proceso Parado mediante el mandato fg 1 y mátelo.

Mediante el mandato kill, también podemos enviar señales a los procesos en ejecución desde un terminal.


Suponga que ha iniciado la ejecución del programa Parado.



Como hemos comentado anteriormente, podemos definir una rutina de tratamiento, para las señales, utilizando el servicio sigaction (anteriormente se usaba el servicio signal, ahora desaconsejado por no ser portable). En el siguiente programa, se presenta un ejemplo para programar este tipo de rutinas.

#define MYNAME	"Signal_C"
 
#include <stdio.h>
#include <signal.h>
#include <unistd.h>
 
void Handler(int num_segnal)
{
	char * strsignal(int num_segnal);
 
	printf(MYNAME": [%d] Recibida la señal \"%s\"(%d)\n",
		getpid(), strsignal(num_segnal), num_segnal);
}
 
int main(void)
{
	struct sigaction a1;
	a1.sa_flags = 0; 
 
	a1.sa_handler = SIG_IGN;
	sigaction(SIGINT, &a1, NULL); /* Ordena ignorar señal INT */
 
	a1.sa_handler = Handler;
	sigaction(SIGQUIT, &a1, NULL); /* Arma señal QUIT con Handler */
 
	a1.sa_handler = SIG_DFL;
	sigaction(SIGTERM, &a1, NULL); /* Ordena acción por defecto para señal TERM */
 
	while (1)
		pause();
	return 0;
}

Observe que en el main la señal SIGINT (Ctrl+C) es ignorada, la señal SIGQUIT pasa a ser manejada con la función Handler y que la señal SIGTERM tiene asignada la acción por defecto. Observe que la función Handler recibe como argumento el tipo de señal y que, imprime una línea con su pid y el número de la señal recibida.
Nota: La señal SIGQUIT se puede enviar desde el mismo shell pulsando (en este orden) AltGr + Ctrl + \, o bien desde otro shell mediante el mandato kill -SIGQUIT pid.

Compile el programa Signal.c y ejecútelo.



Si pulsa la combinación de teclas ^Z, verá que el proceso se para.

[1]+  Stopped	./Signal</p>

Mate el proceso parado nº 1 mediante el mandato kill %1.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ kill %1

Señales con fork y exec

Con el servicio fork, el hijo hereda el armado de señales del padre, las señales ignoradas y la máscara de señales. Sin embargo, la alarma se cancela en el hijo y las señales pendientes no son heredadas.

Con el servicio exec el armado desaparece pasándose a la acción por defecto. Las señales ignoradas se mantienen, al igual que la máscara de señales y la alarma. Las señales pendientes siguen pendientes.

#define MYNAME	"FEW_signal"
 
#include <sys/wait.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
 
void Handler(int num_segnal)
{
	char * strsignal(int num_segnal);
 
	printf(MYNAME": [%d] Recibida la señal \"%s\"(%d)\n",
		getpid(), strsignal(num_segnal), num_segnal);
}
 
int main(int argc, char * argv[])
{
	int pid, ret, status;
	char buff[80];
 
	if (argc == 1) {
		printf("USO: %s mandato [argumentos...]\n", MYNAME);
	} else {
		struct sigaction a1;
		a1.sa_flags = 0; 
 
		a1.sa_handler = SIG_IGN;
		sigaction(SIGINT, &a1, NULL); /* Ordena ignorar señal INT */
 
		a1.sa_handler = Handler;
		sigaction(SIGQUIT, &a1, NULL); /* Arma señal QUIT con Handler */
 
		a1.sa_handler = SIG_DFL;
		sigaction(SIGTERM, &a1, NULL); /* Ordena acción por defecto para señal TERM */
 
		switch(pid = fork()) {
		case -1: /* Error */
			perror(MYNAME);
			break;
		case  0: /* Hijo  */
			ret = read(0, buff, 80);
			fprintf(stderr, MYNAME": %d bytes leidos\n", ret);
			if (ret < 0) perror(MYNAME": En la llamada read()");
			execvp(argv[1], &argv[1]);
			perror(MYNAME);
			exit(1);
		default:  /* Padre */
			do { /* Esperar a que termine este hijo. */
				ret = wait(&status);
				if (ret < 0) perror(MYNAME": En la llamada wait()");
			} while (ret != pid);
			printf(MYNAME": »»» [%d]%s: status=%d\n", ret, argv[1], status);
 
			/* Aquí status = Varios códigos 'empaquetados' en n bytes.
			   Para interpretarla hay que usar las macros de wait "WIF..."*/
			if(WIFEXITED(status)){
				printf(MYNAME": Hijo terminado voluntariamente. Código retornado = %d.\n", WEXITSTATUS(status));
			}else if(WIFSIGNALED(status)){
				printf(MYNAME": Hijo terminado involuntariamente por segnal #%d.\n", WTERMSIG(status));
			}
		}
	}
	return 0;
}

Analice el programa FEW_signal. Observará que se ignora SIGINT, se arma SIGQUIT y se da la acción por defecto a SIGTERM. A continuación se crea un proceso hijo.


El proceso hijo, realiza una lectura de la entrada estándar 0, por lo que se quedará esperando hasta que introduzcamos una línea a través de teclado (basta con pulsar Intro). Si durante este tiempo de espera llega una señal al proceso, el servicio de lectura termina devolviendo error. Esta eventualidad se trata imprimiendo un mensaje de error. Seguidamente el hijo realiza un exec. Si el exec fracasa, retornará como una función normal, y el hijo continuará su ejecución hasta exit(1). Por su lado, el padre simplemente se queda esperando al hijo.

Mientras el proceso hijo no ejecute el exec tendrá armadas e ignoradas las mismas señales que el padre. Una vez ejecutado el exec, las señales ignoradas se mantendrán, pero las armadas pasan al tratamiento por defecto.

Abra dos shell. En el primero, ejecute FEW_signal con el argumento ./Parado.

Shell 1

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./FEW_signal ./Parado

En el segundo shell, podemos comprobar, como se ha creado un proceso hijo (cuyo pid será normalmente consecutivo al de su padre):

Shell 2

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -u alumno
PID  TTY  TIME  CMD  
20579  ?  00:00:00  sshd  
20580  pts/0  00:00:00  bash  
28449  pts/4  00:00:00  bash  
29567  pts/0  00:00:00  FEW_signal  
29568  pts/0  00:00:00  FEW_signal  
29571  pts/4  00:00:00  ps


alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ kill –SIGQUIT 29567 
(tenga en cuenta que el pid de su proceso será otro)


alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ kill –SIGQUIT 29568 
(tenga en cuenta que el pid de su proceso será otro)


Shell 2
Si mandamos la señal SIGINT al hijo, vemos que no ocurre nada. Es ignorada.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ kill -SIGINT 29568
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ps -u alumno

Mate al proceso hijo. El padre terminará por sí mismo.

TEMPORIZADOR

El SO mantiene uno o varios temporizadores por proceso. Al vencer el temporizador se produce una señal.

#define MYNAME	"UnSegundo"
 
#include <signal.h>
#include <stdio.h>
#include <unistd.h>
 
volatile int cnt = 0;
int secs = 0;
long long vueltas = 0;	/* 64 bits */
 
void Handler(int num_segnal)
{
	vueltas += cnt;
	secs++;
	printf(MYNAME": En %d segundos %Ld vueltas. Son %d vueltas/segundo.\n",
		secs, vueltas, (int)(vueltas/secs));
	alarm(1);
	cnt = 1; /* A */
}
 
int main(void)
{
	struct sigaction a1;
	a1.sa_flags = 0; 
 
	a1.sa_handler = Handler;
	sigaction(SIGALRM, &a1, NULL);
 
	alarm(1);
	cnt = 1;
	while (cnt > 0)
		cnt++;
 
	return 0;
}

Analice el programa UnSegundo. Como puede comprobar, en primer lugar arma la señal ALARM y a continuación establece un temporizador de 1 segundo, tras el cual el sistema operativo enviará la señal ALARM al proceso. Cada vez que se recibe tal señal, la función de armado o tratamiento (handler) vuelve a pedir un temporizador de 1 segundo.

Compile y ejecute el programa UnSegundo El programa nunca termina por lo que es necesario matarlo.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc UnSegundo.c -o UnSegundo
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./UnSegundo
UnSegundo: En 1 segundos 373076780 vueltas. Son 373076780 vueltas/segundo.
UnSegundo: En 2 segundos 734883602 vueltas. Son 367441801 vueltas/segundo.
UnSegundo: En 3 segundos 1108195359 vueltas. Son 369398453 vueltas/segundo.
UnSegundo: En 4 segundos 1482007607 vueltas. Son 370501901 vueltas/segundo.
UnSegundo: En 5 segundos 1855694339 vueltas. Son 371138867 vueltas/segundo.
[Ctrl+C]




HILOS

Los hilos permiten dividir un programa en trozos que ejecutan concurrentemente y que comparten variables y por tanto mapa de memoria.

Para analizar el comportamiento de los hilos o procesos ligeros, frente a los procesos creados mediante el servicio fork, utilizaremos los programas Compart_prc.c, Compart_pth.c, Concurr_prc.c y Concurr_pth.c. Comenzaremos analizando el comportamiento del programa Compart_prc.c.

CPRC-01.- #define MYNAME  "Compart_prc"
CPRC-02.- #include <sched.h>
CPRC-03.- #include <stdio.h>
CPRC-04.- #include <unistd.h>
CPRC-05.- unsigned acumulador = 0;
CPRC-06.- void * echo(void * dummy)
CPRC-07.- {
CPRC-08.-    setbuf(stdout, NULL);
CPRC-09.-    while(1) 
CPRC-10.-    {
CPRC-11.-       printf(MYNAME": Acumulador = %u \r", acumulador);
CPRC-12.-       sched_yield();
CPRC-13.-    }
CPRC-14.-    return NULL;
CPRC-15.- }
CPRC-16.- int main(void)
CPRC-17.- {
CPRC-18.-    switch(fork()) 
CPRC-19.-    {
CPRC-20.-         case -1: 
CPRC-21.-                 return 1;
CPRC-22.-         case  0: 
CPRC-23.-                 echo(NULL);
CPRC-24.-         default:
CPRC-25.-                while(1) 
CPRC-26.-                {
CPRC-27.-                   acumulador++;
CPRC-28.-                   sched_yield();
CPRC-29.-                }
CPRC-30.-    }
CPRC-31.-    return 0;
CPRC-32.-}

Analice el programa Compart_prc.c. Observe la variable acumulador se ha definido como global y que el programa crea un hijo durante su ejecución.


Padre e hijo incluyen la llamada a sched_yield() para garantizar que van alternando su ejecución.

Compile el programa Compart_prc.c y ejecútelo.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc Compart_prc.c -o Compart_prc
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Compart_prc
Compart_prc: Acumulador = 0


Abra otro shell, y ejecute ps -u alumno.


Mate el/los proceso/s Compart_prc.

Veamos ahora que sucede en el caso de utilizar procesos ligeros. Para ello utilizaremos el programa Compart_pth.c

CPTH-01.- #define MYNAME  "Compart_pth"
CPTH-02.- #include <pthread.h>
CPTH-03.- #include <stdio.h>
CPTH-04.- unsigned acumulador = 0;
CPTH-05.- void * echo(void * dummy)
CPTH-06.- {
CPTH-07.-    setbuf(stdout, NULL);
CPTH-08.-    while(1) 
CPTH-09.-    {
CPTH-00.-       printf(MYNAME": Acumulador = %u \r", acumulador);
CPTH-11.-       sched_yield();
CPTH-12.-    }
CPTH-13.-    return NULL;
CPTH-14.- }
CPTH-15.- int main(void)
CPTH-16.- {
CPTH-17.-    pthread_t tid;
CPTH-18.-    pthread_create(&tid, NULL, echo, NULL);
CPTH-19.-    while(1) 
CPTH-20.-    {
CPTH-21.-       acumulador++;
CPTH-22.-       sched_yield();
CPTH-23.-    }
CPTH-24.-    return 0;
CPTH-25.- }

Observe, que en la línea CPTH-18, se procede a crear un nuevo hilo de ejecución que se encargará de ejecutar la función echo. Para compilar en Linux un programa con hilos hay que indicar al compilador las bibliotecas a utilizar, por tanto, ejecutaremos:

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc -Wall  -pthread  Compart_pth.c -o Compart_pth

Al ejecutar el programa Compart_pth verá que se reescribe la línea Compart_pth: Acumulador = 0, pero en este caso el valor impreso va aumentado, de acuerdo a la ejecución del hilo primario.


Observe, en un segundo shell, el número de procesos Compart_pth que aparecen.


Mate el/los proceso/s Compart_pth.

Analicemos ahora los programas Concurr_p*.

CURC-01.- #define MYNAME  "Concurr_prc"
CURC-02.- #include <sys/wait.h>
CURC-03.- #include <stdio.h>
CURC-04.- #include <stdlib.h>
CURC-05.- #include <unistd.h>
CURC-06.- void * echo(void * str)
CURC-07.- {
CURC-08.-    int cnt;
CURC-09.-    int consumo;
CURC-10.-    for (cnt=0; cnt<10000; cnt++) 
CURC-11.-    {
CURC-12.-       for (consumo=0; consumo<10000; consumo++)
CURC-13.-          continue;
CURC-14.-       fprintf(stderr, "%s", (char *)str);
CURC-15.-    }
CURC-16.-    return NULL;
CURC-17.- }
CURC-18.- 
CURC-19.- int main(int argc, char * argv[])
CURC-20.- {
CURC-21.-    int i;
CURC-22.-    int pid[argc];
CURC-23.-    setbuf(stdout, NULL);
CURC-24.-    for (i=1; i<argc; i++)
CURC-25.-       if ((pid[i] = fork()) == 0) { echo(argv[i]); exit(0); }
CURC-26.-       for (i=1; i<argc; i++)
CURC-27.-          waitpid(pid[i], NULL, 0);
CURC-28.-    return 0;
CURC-29.- }

Analice el programa Concurr_prc.


Compile y ejecute el programa Concurr_prc.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc Concurr_prc.c -o Concurr_prc
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Concurr_prc a b c d

Observará que se van imprimiendo, de forma alternada, los argumentos a, b, c y d. Esto significa que el uso del procesador va pasando, cada cierto tiempo, de un proceso a otro.

En otro shell observe los procesos mientras está Concurr_prc en ejecución.


Analicemos ahora el programa Concurr_pth.

CUTH-01.- #define MYNAME  "Concurr_pth"
CUTH-02.- #include <pthread.h>
CUTH-03.- #include <stdio.h>
CUTH-04.- void * echo(void * str)
CUTH-05.- {
CUTH-06.-    int cnt;
CUTH-07.-    int consumo;
CUTH-08.-    for (cnt=0; cnt<10000; cnt++) 
CUTH-09.-    {
CUTH-10.-       for (consumo=0; consumo<10000; consumo++)
CUTH-11.-          continue;
CUTH-12.-       fprintf(stderr, "%s", (char *)str);
CUTH-13.-    }
CUTH-14.-    return NULL;
CUTH-15.- }
CUTH-16.- 
CUTH-17.- int main(int argc, char * argv[])
CUTH-18.- {
CUTH-19.-    int i;
CUTH-20.-    pthread_t tid[argc];
CUTH-21.-    setbuf(stdout, NULL);
CUTH-22.-    for (i=1; i<argc; i++)
CUTH-23.-       pthread_create(&tid[i], NULL, echo, argv[i]);
CUTH-24.-    for (i=1; i<argc; i++)
CUTH-25.-       pthread_join(tid[i], NULL);
CUTH-26.-    return 0;
CUTH-27.- }

Observe que se crean tantos hilos como argumentos se incluyan en la llamada al programa. Cada hilo consume un cierto número de ciclos de CPU e imprime el valor de su argumento.

Compile y ejecute el programa Concurr_pth.

alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ gcc -pthread Concurr_pth.c -o Concurr_pth
alumno@maquinaLinux:~/PracticasAnalisis/ModuloProcesos$ ./Concurr_pth a b c d

Observará que se van imprimiendo, de forma alternada, los argumentos a, b , c y d. Esto significa que el uso del procesador va pasando, cada cierto tiempo, de un hilo a otro. En otro shell observe los procesos mientras está Concurr_pth en ejecución.


 
docencia/asignaturas/sox/prv/practicas/analisis_so6/modulo_pp.txt · Última modificación: 2023/09/01 17:51 (editor externo)
 
Recent changes RSS feed Powered by PHP Valid XHTML 1.0 Valid CSS Driven by DokuWiki