Comandos para gestionar procesos en Linux
Comandos para gestionar procesos en Linux
TEMA 1
INTRODUCCIÓN A LOS SISTEMAS MULTITAREA
Ciclo: DAM
1.4. Administración de Procesos en Linux Ubuntu
Linux es un sistema operativo multitarea y multiusuario. Esto significa que múltiples procesos pueden operar
simultáneamente sin interferir entre sí. Cada proceso tiene la "impresión" de que es el único proceso y que tiene
acceso exclusivo a todos los servicios del sistema operativo.
Los programas y procesos son entidades separadas. En un sistema operativo multitarea, varias instancias de un
programa pueden ejecutarse simultáneamente. Cada instancia es un proceso separado. Por ejemplo, si cinco
usuarios de diferentes computadoras ejecutan el mismo programa al mismo tiempo, habría cinco instancias del
mismo programa, es decir, cinco procesos diferentes.
Cada proceso que se inicia se referencia con un número de identificación único conocido como ID de proceso PID,
que siempre es un número entero positivo. Prácticamente todo lo que se ejecuta en el sistema en cualquier
momento es un proceso, incluido el shell.
Así como todo proceso tiene un atributo PID, que es el número de proceso que lo identifica en el sistema, también
existe un atributo llamado PPID. Este número corresponde al número PID del proceso padre.
Todos los procesos deben tener un proceso que figure como padre, pero ¿qué sucede si un padre muere antes que
cualquiera de sus hijos? En ese caso, el proceso init adoptará estos procesos para que no queden huérfanos.
Es una utilidad de gnome que muestra los procesos y permite monitorizar el estado del sistema. Para ejecutar la
aplicación, se debe ir a buscar, introducir monitor y elegir el monitor del sistema:
$ sudo gnome-system-monitor
Desde el monitor del sistema, para cada proceso podemos detenerlo, renovarlo, terminarlo y cambiar su prioridad,
así como ver sus propiedades.
Página 1 de 37
En consola o Terminal
El comando top
Es el equivalente de System Monitor en la línea de comandos. Muestra una lista de procesos que se están
ejecutando en tiempo real. Se pueden ordenar de diferentes maneras.
$ sudo top
Permite realizar diferentes acciones sobre cada uno de ellos, como matarlo o cambiar su prioridad. Para salir,
Ctrl+Z.
Página 2 de 37
En la cabecera muestra datos como: tiempo encendido, usuarios conectados, carga del sistema,
procesos actuales, carga del procesador, uso de memoria RAM y uso de memoria de SWAP1.
Otros datos son: ID del proceso, Usuario que ejecuta el proceso, Prioridad, Uso de CPU, Uso de RAM, Tiempo de
ejecución o Comando ejecutado.
El comando ps
El comando ps (process status) es el mejor modo de mostrar los procesos que se encontraron activos. Veámos
algunos ejemplos:
$ ps
1
o espacio de intercambio es una parte de la memoria virtual del sistema en disco.
Página 3 de 37
Si no añadimos ningún parámetro, ps mostrará los procesos del usuario en el que estemos logueados.
$ ps –u root
Mostrará los procesos activos del usuario que se indica en el modificador –u.
$ ps u U uadmin
Donde:
Página 4 de 37
$ ps aux
$ ps –-forest
El comando pstree
El comando pstree muestra una vista en forma de árbol (jerárquica) de los procesos en ejecución.
$ pstree
Página 5 de 37
$ pstree $$
$ pstree uadmin
$ pstree root
Si mostramos los procesos que ejecuta el root, veremos mucho más, ya que se incluye los procesos
que ejecuta systemd2.
2
SystemD es el administrador de servicios y sistemas en Linux, y la estandarización de la mayoría de distribuciones de Debian y Red Hat.
SystemD fue desarrollado con el objetivo de encargarse de arrancar todo lo que está por debajo del Kernel, permitiendo ejecutar varios procesos de
manera simultánea.
Página 6 de 37
El comando jobs
El comando jobs se usa para enumerar los procesos que se ejecutan en segundo plano. Si no devuelve ningún
tipo de respuesta, significa que no hay procesos presentes. Solo nos muestra los trabajos vinculados a la
terminal desde donde se ejecuta jobs, es decir, no nos mostrará aquellos trabajos que hayan sido lanzados
desde otras terminales aunque se estén ejecutando en ese momento.
Un proceso en segundo plano, es un proceso que se está ejecutando pero que a su vez permite que se ejecuten
otros procesos a la vez en la misma consola, es decir, se está ejecutando por detrás dejando la terminal libre para
que se ejecuten otros programas.
Para lanzar un proceso en segundo plano no hay más que añadir al final del comando el carácter &.
Aunque el proceso está ejecutándose en segundo plano, no permite lanzar otros procesos. El motivo es porque se
está enviando la salida a la misma terminal. Tendremos que pulsar intro, para finalizar el proceso.
Vamos a enviar la salida del comando a un “periférico nulo” el cual referenciaremos con /dev/null (null device).
Donde:
El número sin corchete, en este caso [3], es la numeración que se asigna a los procesos en segundo plano.
El número con corchete, en este caso [3013], es el PID del proceso.
Para probar el comando ‘jobs’, vamos a lanzar un proceso que se está ejecutando indefinidamente, se trata del
comando yes. Este comando muestra infinitas (y). Lo ejecutamos en segundo plano.
Página 7 de 37
¿Cuándo sería adecuado lanzar procesos en primer plano y en segundo plano?. Procesos en primer plano serían
adecuados para los procesos que son rápidos o que necesitan interactuar constantemente con el usuario y
segundo plano para procesos que no necesitan interacción, por ejemplo, procesos gráficos, copias de seguridad,
etc.
El comando jobs, muestra qué procesos están ejecutándose en primer y segundo plano.
Página 8 de 37
Pasar un proceso de primer plano a segundo plano y viceversa
Vamos cambiar el proceso [3] que se esta ejecutando en segundo plano a primer plano. Para ello utilizaremos el
comando fg (foreground).
Para cambiar de segundo plano a primer plano, lo primero es pausar el proceso (CTRL + Z) no pararlo (CTRL + C).
Para ello utilizaremos el comando bg (background).
Finalizar un proceso
Terminar un proceso activo puede provocar la pérdida de información y/o la inestabilidad del sistema si no se
conoce el proceso que se está terminando.
Basta con ir a la pestaña de procesos, seleccionar el proceso a finalizar y presionar el botón: finalizar proceso.
Página 9 de 37
En la consola o Terminal
Para finalizar un proceso mediante la consola, debe conocer el número de identificación del proceso (PID). Para
averiguar el número, enumerar los procesos.
Una vez que se enumeran los procesos, buscar el proceso para finalizar y memorizar el número en la columna
PID.
Una vez que se conoce el PID, ejecutar en una terminal:
$ sudo kill [PID]
Suele pasar que al listar los procesos activos, el que se quiere terminar tiene varias instancias abiertas, es decir
tiene varios PIDs y para terminar se necesitaría usar el comando kill varias veces. El comando killall se usa para
terminar todos los procesos abiertos por un comando. Como se puede imaginar, el disponer de diferentes PID no
es el PID lo que se debe indicar al comando killall, sino el nombre del proceso. Este nombre se da en la lista de
procesos como CMD; entonces:
Página 10 de 37
$ sudo killall [CMD]
Todavía tenemos varias instancias en ejecución del comando yes, si queremos matarlas todos de una vez
utilizaremos el comando killall.
El comando xkill es una utilidad para obligar al servidor X a cerrar conexiones. Este comando se puede usar como
el comando kill:
La ventaja de este comando es que se puede utilizar sin necesidad de un PID, ya que se escribe en el terminal:
$ xkill
El cursor cambiará a una calavera o una cruz y la aplicación matará (terminará) el proceso que controla la ventana
donde se hace clic en la calavera.
Para pausar un proceso, es preciso conocer el número de identificación del proceso (PID). Para averiguar el
número, simplemente listar los procesos.
Una vez que se listar los procesos, buscar el proceso para pausar y memorizar el número en la columna PID.
Una vez que se conoce el PID, simplemente ejecutar en una terminal:
$ sudo kill -STOP [PID]
Página 11 de 37
Cambiar la prioridad de un proceso
Si ejecutamos el comando top, la tercera columna, PR (Priority) nos indica la prioridad asignada a cada proceso.
Cuanto más alto sea el valor, más asignación de memoria y CPU se le asignará al proceso. Por defecto, todos los
procesos se lanzan con prioridad 20, que corresponde a una prioridad media.
El comando para asignar prioridades es nice, donde las sintaxis es: nice --valor comando.
Página 12 de 37
El kernel, mediante su administrador de procesos, retira un proceso del procesador, ¿cómo decide qué proceso
deberá ingresar?
Eso lo decide mediante un cálculo de prioridades. Todos los procesos en linux corren con determinada prioridad,
un número entero, que puede verse en la salida de un comando top.
La columna PRI(ority) hace referencia a esta prioridad, por defecto, 20. Se trata de una prioridad media por
defecto. Las prioridades de los procesos oscilan entre los valores 0 a 39. Donde, cuanto menor es el número, más
alta es la prioridad, el proceso tendrá más posibilidades de ser elegido por el planificador para usa la CPU, por el
contrario, un proceso con una prioridad cercana al 39 tendrá menos opciones de hacerse con la CPU.
La columna NI(ce) hace referencia a una prioridad relativa que por lo general mapea con la prioridad absoluta
PRI, y que depende del estado y carga de procesamiento, pero que va desde -20 a +19, donde -20 es la mayor
prioridad de proceso de usuario (equivalente a un PRI de 0) y +19 es la menor prioridad de usuario (equivalente a
un PRI de 39), siendo él NI de 0 la prioridad media, equivalente a un PRI de 20.
Cuando se inicia un proceso, éste se inicia, por defecto, con prioridad absoluta 20 equivalente a prioridad relativa
0.
Si quisiéramos ejecutar una tarea determinada, por ejemplo, alguna tarea que consuma intensivamente al
procesador, y quisiéramos hacerlo con una prioridad menor de lo habitual de modo que el sistema no se ralentice
demasiado, y nos deje «espacio» para trabajar, podríamos ejecutarla utilizando el comando «nice» de esta
forma:
donde:
nn es un número positivo o negativo de prioridad.
comando es la tarea a ejecutar.
Ejemplo:
1. Vamos a ejecutar el comando «yes > /dev/null &» con una prioridad baja de +10:
Página 13 de 37
3. Ahora bien, si necesitáramos elevar la prioridad del proceso en ejecución, supongamos, a -5 (5 puntos
encima de la normal), podremos hacer uso del comando «renice» y utilizando el PID del proceso, en
nuestro caso, 2267, de esta forma: renice -[+|-]nn identificadordelproceso (PID)
renice -5 2267
Comprobamos…
Si el proceso a pausar es del usuario y no del sistema, no es necesario el uso del comando sudo
La carga promedio mínima de un sistema es de 0 y la máxima es ilimitada, aunque raramente excede de 20, y
hasta más de 10 es poco usual.
Para ver la carga promedio de nuestro sistema sistema podemos usar el comando top:
Página 14 de 37
1.5. Control de Procesos en Linux
Seguro que más de una vez hemos necesitado dentro de un programa ejecutar otro programa que realice alguna
tarea concreta. Linux ofrece varias funciones para realizar esto: system(), fork() y execl().
La función system() se encuentra en la librería estándar <<stdlib.h>> por lo que funciona en cualquier sistema
operativo que tenga un compilador de C/C++ como por ejemplo Linux, Windows, etc. El formato es el siguiente:
La función recibe como parámetro una cadena de caracteres que indica el comando que se desea procesar. Dicha
instrucción es pasada al intérprete de comandos del ambiente en el que se está trabajando y se ejecuta. Devuelve
el valor 0 si no ocurre un error y el estado devuelto por el comando en caso contrario.
La ejecución del siguiente ejemplo en C lista el contenido del directorio actual y lo envía a un fichero, abre el
editor gedit con el fichero generado y ejecuta un comando que no existe en el intérprete de comandos de Linux:
Ejemplo: ejemploSystem.c
#include <stdio.h>
#include <stdlib.h>
void main(){
printf("Ejemplo de uso de system");
printf("\n\t Listado del directorio actual y envio a un fichero");
printf("%d", system("ls > ficsalida"));
printf("\n\tAbrimos con gedit el fichero...");
printf("%d", system("gedit ficsalida"));
printf("\n\tEste comando es erróneo: %d", system("ged"));
printf("\nFin de programa...\n");
}
Página 15 de 37
Lo compilamos y lo ejecutamos desde Linux:
Esta función no se puede usar desde un programa con privilegios de administrador porque pudiera ser que se
emplearán valores extraños para algunas variables de entorno y podrían comprometer la integridad del sistema.
En este caso se utiliza execl().
La función recibe el nombre del fichero que se va a ejecutar y luego los argumentos terminando con un puntero
nulo. Devuelve -1 si ocurre algún error y en la variable global errno se pondrá el código de error adecuado. Por
ejemplo, para ejecutar el comando /bin/ls -l
escribirnos:
execl("/bin/ls", "ls", "-l", (char *)NULL);
A continuación se muestra un ejemplo de uso de la función:
Ejemplo: ejemploExec.c
#include <stdio.h>
#include <unistd.h>
void main(){
printf("Ejemplo de uso de exec\n");
printf("Los archivos en el directorio son:\n");
execl("/bin/ls", "ls", "-l",(char *)NULL);
printf("¡¡¡¡ Esto no se ejecuta !!!\n");
Compilamos y ejecutamos:
Página 16 de 37
Para crear nuevos procesos se dispone de la función fork() sin ningún tipo de parámetros y que se trata en el
siguiente apartado.
Hasta ahora hemos visto funciones que ejecutaban comandos ya sea del intérprete de comandos o de ficheros en
disco, a continuación veremos una función cuya misión es crear un proceso. Se trata de la función fork(). Su
sintaxis es:
#include <unistd.h>
pid_t fork(void);
Al llamar a esta función se crea un nuevo proceso (proceso hijo) que es una copia exacta en código y datos del
proceso que ha realizado la llamada (el proceso padre), salvo el PID y la memoria que ocupa. Las variables del
proceso hijo son una copia de las del padre, por lo que modificar una variable en uno de los procesos no se refleja
en el otro (ya que tienen distintas memorias).
El valor devuelto por fork() es un valor numérico:
Devuelve -1 si se produce algún error en la ejecución.
Devuelve 0 si no se produce ningún error y nos encontramos en el proceso hijo.
Devuelve el PID asignado al proceso hijo si no se produce ningún error y nos encontramos en el
proceso padre.
Antes de hacer un ejemplo con la función fork() vamos a ver cómo obtener el identificador de un proceso o PID.
Para ello usamos 2 funciones que devuelven un tipo pid_t. Las funciones son las siguientes:
pid_t getpid(void);
Devuelve el identificador del proceso que realiza la llamada, es decir, del proceso actual.
pid_t getppid (void);
Devuelve el identificador del proceso padre del proceso actual.
Veamos un simple ejemplo para ver los PID del proceso actual y del proceso padre:
Ejemplo: ejemploPadres.c
#include <stdio.h>
#include <unistd.h>
Página 17 de 37
void main(){
pid_t id_proceso;
pid_t id_padre;
id_proceso = getpid();
id_padre = getppid();
Si ejecutamos el comando ps para ver los procesos que se están ejecutando, podemos ver que el PID del shell de
Ubuntu (6090) coincide con el padre del proceso ejecutado anteriormente:
A continuación vamos a ver un ejemplo donde el proceso actual (proceso padre) crea un proceso (proceso hijo)
con la función fork():
Ejemplo: ejemplo1Fork.c
#include <stdlib.h>
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
void main() {
pid_t pid, Hijo_pid;
pid = fork();
Página 18 de 37
else //Nos encontramos en Proceso padre
{
Hijo_pid = wait(NULL); //espera la finalización del proceso hijo
printf("Soy el proceso padre:\n\t Mi PID es %d, El PID de mi padre es: %d.\n\t Mi hijo: %d terminó.\n",
getpid(), getppid(), pid);
}
exit(0);}
En el código anterior se utiliza la función wait() para que el proceso padre espere la finalización del proceso hijo,
el proceso padre quedara bloqueado hasta que termine el hijo. La sintaxis de la orden es la siguiente:
pid_t wait(int *status);
Devuelve el identificador del proceso hijo cuya ejecución ha finalizado. La sentencia wait (NULL) es la forma más
básica de esperar a que un hijo termine.
Partiendo del ejemplo anterior, creamos un nuevo proceso en el proceso hijo; así tendremos el proceso padre
(ABUELO), el proceso hijo (HIJO) y el proceso hijo del hijo (NIETO):
fork() fork()
Abuelo Abuelo Nieto
Procesos Abuelo-Hijo-Nieto
Ejemplo: ejemplol_2Fork.c
#include <unistd.h>
#include <stdio.h>
#include <sys/wait.h>
//ABUELO-HIJO-NIETO
void main() {
pid_t pid, Hijo_pid,pid2,Hijo2_pid;
Página 19 de 37
switch(pid2)
{
case -1: // error
printf("No se ha podido crear el proceso hijo en el HIJO...");
exit(-1);
break;
case 0: // proceso hijo
printf("\t\tSoy el proceso NIETO %d; Mi padre es = %d \n", getpid(), getppid());
break;
default: // proceso padre
Hijo2_pid = wait(NULL);
printf("\tSoy el proceso HIJO %d, Mi padre es: %d.\n",
getpid(), getppid());
printf("\tMi hijo: %d terminó.\n", Hijo2_pid);
}
Realiza un programa en C que cree un proceso (tendremos 2 procesos uno padre y otro hijo). El programa definirá
una variable entera y le dará el valor 6. El proceso padre incrementara dicho valor en 5 y el hijo restará 5. Se
deben mostrar los valores en pantalla. A continuación se muestra un ejemplo de la ejecución:
gcc actividad_l_2.c -o actividad_l_2
./actividad_1_2
Valor inicial de la variable: 6
Variable en Proceso Hijo: 1
Variable en Proceso Padre: 11
Página 20 de 37
1.5.2. Comunicación entre procesos
Existen varias formas de comunicación entre procesos (Inter-Process Communication o IPC) de Linux: pipes, colas
de mensajes, semáforos y segmentos de memoria compartida. En este tema trataremos los mecanismos más
sencillos, los pipes (tuberías en castellano).
Un pipe es una especie de falso fichero que sirve para conectar dos procesos. Si el proceso A quiere enviar datos
al proceso B, los escribe en el pipe como si este fuera un fichero de salida. El proceso B puede leer los datos sin
más que leer el pipe como si se tratara de un fichero de entrada. Así la comunicación entre procesos es parecida a
la lectura y escritura en ficheros normales.
Cuando un proceso quiere leer del pipe y este está vacío, tendrá que esperar (es decir, se bloqueará) hasta que
algún otro proceso ponga datos en él. Igualmente cuando un proceso intenta escribir en el pipe y está lleno se
bloqueará hasta que se vacíe. El pipe es bidireccional pero, cada proceso lo utiliza en una única dirección, es este
caso, el kernel gestiona la sincronización. Para crear un pipe se realiza una llamada a la función pipe().
Para enviar datos al pipe, se usa la función wr¡te(), y para recuperar datos del pipe, se usa la función read(). La
sintaxis es la siguiente:
read(), intenta leer count bytes del descriptor de fichero definido en fd, para guardarlos en el buffer buf.
Devuelve el número de bytes leídos; si comparamos este valor con la variable count podemos saber si ha
conseguido leer tantos bytes como se pedían.
write(), es muy similar. A buf le damos el valor de lo que queramos escribir, definimos su tamaño en count y
especificamos el fichero en el que escribiremos en fd. Veamos a continuación un sencillo ejemplo que usa
ficheros, pero antes se exponen las funciones que abren y cierran ficheros. Usamos la función open() para
abrirlo y close() para cerrarlo, la sintaxis es la siguiente:
open(), abre el fichero indicado en la cadena fichero según el modo de acceso indicado en el entero modo (0 para
lectura, 1 para escritura, 2 para lectura y escritura, etc). Devuelve -1 si ocurre algún error.
Para cerrar el fichero usamos close() indicando entre paréntesis el descriptor de fichero a cerrar.
Página 21 de 37
Se parte de la existencia de un fichero vacío de nombre texto.txt, el programa abre el fichero para escritura,
escribe un saludo y después cierra el fichero. Posteriormente vuelve a abrir el fichero en modo lectura y hace un
recorrido leyendo los bytes de uno en uno. Al finalizar la lectura se cierra el fichero. El programa es el siguiente:
Ejemplo: ejemWriteRead.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
void main(void)
{
char saludo[] = "Un saludo!!!\n";
char buffer[10];
int fd, bytesleidos;
printf("Escribo el saludo...\n");
write(fd,saludo, strlen(saludo));
close(fd); //cierro el fichero
Una vez que sabemos cómo leer y escribir en ficheros, veamos algunos ejemplos usando pipes. En el primer
ejemplo se crea un proceso hijo con fork(). El proceso hijo envía al proceso padre mediante el uso de pipes el
mensaje “Hola papi” en el descriptor para escritura fd[1], el proceso padre mediante el descriptor fd[0] lee los
datos enviados por el hijo:
Ejemplo: ejemploPipe1.c
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/wait.h>
Página 22 de 37
int main()
{
int fd[2];
char buffer[30];
pid_t pid;
pipe(fd);
pid=fork();
switch(pid) {
case -1 : //ERROR
printf("NO SE HA PODIDO CREAR HIJO...");
exit(-1);
break;
case 0 : //HIJO
printf("El HIJO escribe en el pipe...\n");
write(fd[1], "Hola papi", 10);
break;
default : //PADRE
wait(NULL);
printf("El PADRE lee del pipe...\n");
read(fd[0], buffer, 10);
printf("\tMensaje leido: %s\n",buffer);
break;
}
}
Primero se crea la tubería con pipe() y a continuación el proceso hijo. Recordemos que cuando se crea un proceso
hijo con fork(), recibe una copia de todos los descriptores de ficheros del proceso padre, incluyendo copia de los
descriptores de ficheros del pipe (fd[0] y fd[1]). Esto permite que el proceso hijo mande datos al extremo de
escritura del pipe fd[1], y el padre los reciba del extremo de lectura fd[0], véase en la imagen.
Pipe para un proceso hijo que escribe y otro padre que lee.
Los procesos padre e hijo están unidos por el pipe (imagen inferior), pero la comunicación es en una única
dirección, por tanto se debe decidir en que dirección se envía la información, del padre al hijo o del hijo al padre; y
dado que los descriptores se comparten siempre, debemos estar seguros de cerrar el extremo que no nos
interesa.
Cuando el flujo de información va del padre hacia el hijo:
El padre debe cerrar el descriptor de lectura fd[0].
El hijo debe cerrar el descriptor de escritura fd[1].
Página 23 de 37
Cuando el flujo de información va del hijo hacia padre ocurre lo contrario:
El padre debe cerrar el descriptor de escritura fd[1].
El hijo debe cerrar el descriptor de lectura fd[0].
El siguiente ejemplo crea un pipe en el que el padre envía un mensaje al hijo, el flujo de la información va del
padre al hijo, el padre debe cerrar el descriptor fd[0] y el hijo fd[1]; el padre escribe en fd[1] y el hijo lee de
fd[0]:
Ejemplo: ejemploPipe3.c
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <sys/wait.h>
switch(pid) {
case -1 : //ERROR
printf("NO SE HA PODIDO CREAR HIJO...");
exit(-1);
case 0 : //HIJO RECIBE
close(fd[1]);//cierra el descriptor de entrada
read(fd[0], buffer, sizeof(buffer)); //leo el pipe
printf("\tEl HIJO recibe algo del pipe: %s\n",buffer);
break;
Página 24 de 37
Actividad propuesta 1.3
Siguiendo el ejemplo anterior, realiza un programa en C que cree un pipe en el que el hijo envié un mensaje al
padre, es decir, la información fluya del hijo al padre. La ejecución debe mostrar la siguiente salida:
gcc actividadl_3.c -o actividadl_3
./actividadl_3
El HIJO envia algo al pipe.
El PADRE recibe algo del pipe: Buenos dias padre.
En el siguiente ejemplo vamos a hacer que padres e hijos puedan enviar y recibir información, como la
comunicación es en un único sentido crearemos dos pipes fd1 y fd2. Cada proceso usará un pipe para enviar la
información y otro para recibirla. Partimos de los procesos ABUELO, HIJO y NIETO, la comunicación entre ellos se
muestra en la siguiente imagen:
El ABUELO usará el fd1 para enviar información al HIJO y recibirá la información de este a través del fd2.
El HIJO usará el fd2 para enviar información al NIETO y recibirá la información de este a través del fd1.
El NIETO usará el fd1 para enviar información al HIJO (su padre) y recibirá la información de este a través
del fd2.
Ejemplo: ejemploForkPipe.c
#include <stdlib.h>
#include <unistd.h>
Página 25 de 37
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
//ABUELO-HIJO-NIETO
void main() {
char buffer[80]="";
//NIETO ENVIA
printf("\t\tNIETO ENVIA MENSAJE a su padre...\n");
close(fd1[0]);
write(fd1[1], saludoNieto, strlen(saludoNieto));
break;
//HIJO RECIBE
close(fd1[1]); //cierra el descriptor de entrada
read(fd1[0], buffer, sizeof(buffer)); //leo el pipe
printf("\tHIJO recibe mensaje de ABUELO: %s\n",buffer);
//RECIBE de su hijo
close(fd1[1]);//cierra el descriptor de entrada
read(fd1[0], buffer, sizeof(buffer)); //leo el pipe
printf("\tHIJO RECIBE mensaje de su hijo: %s\n",buffer);
Página 26 de 37
}
//PADRE RECIBE
close(fd2[1]);//cierra el descriptor de entrada
read(fd2[0], buffer, sizeof(buffer)); //leo el pipe
printf("El ABUELO RECIBE MENSAJE del HIJO: %s\n", buffer);
}
exit(0);
}
Los pipes vistos anteriormente establecían un canal de comunicación entre procesos emparentados (padre-
hijo). Los FIFOS permiten comunicar procesos que no tienen que estar emparentados.
Un FIFO es como un fichero con nombre que existe en el sistema de ficheros y que pueden abrir, leer y
escribir múltiples procesos. Los datos escritos se leen como en una cola, primero en entrar (FIRST IN),
primero en salir (FIRST OUT); y una vez leídos, no pueden ser leídos de nuevo. Los FIFOS tienen algunas
diferencias con los ficheros:
Una operación de escritura en un FIFO queda en espera hasta que el proceso pertinente abra el FIFO para
iniciar la lectura.
Solo se permite la escritura de información cuando un proceso vaya a recoger dicha informacion.
Hay varias formas de crear un FIFO: ejecutando el comando mknod desde la línea de comandos de Linux o
desde un programa C usando la función mknod().
Para usar mknod desde la línea de comandos de Linux seguimos el siguiente formato:
finaliza.
o --version: muestra en la salida estándar informacion sobre la versión, y luego finaliza.
Página 27 de 37
El siguiente ejemplo crea un FIFO llamado FIFO1 desde la línea de comandos y luego se muestra la
información del fichero creado. Se puede observar el indicador “p” que aparece en la lista del directorio y el
símbolo de pipe | detrás del nombre:
A continuación veamos cómo funciona el FIFO. Ejecuto desde la línea de comandos la orden cat con el nombre
FIFO1:
Observamos que se queda a la espera. Abro una nueva terminal y ejecuto desde la línea de comandos la
orden l para enviar la información del directorio al FIFO1:
Veremos que el cat que anteriormente estaba a la espera se ejecuta ya que ha recibido la información.
Página 28 de 37
Para crear un FIFO en C, utilizamos la función mknod(). Su formato es el siguiente:
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
Donde:
pathname: es el nombre del dispositivo creado.
modo: especifica tanto los permisos de uso y el tipo de nodo que se creara. Debe ser una combinación
(utilizando OR bit a bit) de uno de los tipos de fichero que se enumeran a continuación y los permisos
para el nuevo nodo. El tipo de nodo debe ser uno de los siguientes:
o S_IFREG o 0: para especificar un fichero normal (que será creado vacio).
o S_IFCHR: para especificar un fichero especial de caracteres.
o S_IFBLK: un fichero especial de bloques.
o S_IFIFO: para crear un FIFO.
Si el tipo de fichero es S_IFCHR o S_IFBLK entonces dev debe especificar los números mayor y menor del
fichero especial de dispositivo creado; en caso contrario, es ignorado. Si pathname ya existe, o es un enlace
simbólico, esta llamada fallaría devolviendo el error EEXIST.
A continuación se muestra un ejemplo de uso de FIFOS. El programa fifocrea.c crea un FIFO de nombre FIFO2
y lee la información del FIFO; mientras no hay información quedará en espera. El programa fifoescribe.c
escribe información en el FIFO. La imagen muestra la ejecución, primero se ejecuta fifocrea desde un terminal
y después ejecutamos varias veces fifoescribe desde otro terminal.
Página 29 de 37
El código es el siguiente:
//fifocrea.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main(void)
{
int fp;
int p, bytesleidos;
char saludo[] = "Un saludo!!!\n", buffer[10];
if (p==-1) {
printf("HA OCURRIDO UN ERROR...\n");
exit(0);
}
while(1) {
fp = open("FIFO2", 0);
bytesleidos= read(fp, buffer, 1);
printf("OBTENIENDO Información...");
while (bytesleidos!=0){
printf("%1c", buffer[0]); //leo un carácter
bytesleidos= read(fp, buffer, 1);//leo otro byte
}
close(fp);
}
return(0);
}
//fifoescribe.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <string.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
int main()
{
int fp;
char saludo[] = "Un saludo!!!\n";
fp = open("FIFO2", 1);
if(fp == -1) {
printf("ERROR AL ABRIR EL FICHERO...");
exit(1);
}
printf("Mandando información al FIFO...\n");
write(fp,saludo, strlen(saludo));
close(fp);
return 0;
Página 30 de 37
}
En el apartado anterior se han tratado los mecanismos más sencillos de comunicación entre procesos. Pero
para que los procesos interactúen unos con otros necesitan cierto nivel de sincronización, es decir, es
necesario que haya un funcionamiento coordinado entre los procesos a la hora de ejecutar alguna tarea.
A continuación se muestran una serie de funciones útiles que utilizaremos para que un proceso padre y otro
hijo se comuniquen de forma síncrona usando señales.
Una señal es como un aviso que un proceso manda a otro proceso. El sistema operativo se encarga de que el
proceso que recibe la señal la trate inmediatamente.
La función signal() es el gestor de señales por excelencia que especifica la acción que debe realizarse cuando
un proceso recibe una señal. Su formato es el siguiente:
#include <signal.h>
void (*signal(int Señal, void (*Func) (int)) (int);
Func: contiene la función a la que queremos que se llame. Esta función es conocida como el manejador de
la señal (signal handler). En el ejemplo que se verá a continuación se definen dos manejadores de señal, uno
para el proceso padre (void gestion_padre( int segnal)) y otro para el hijo (void gestión_hijo( int
segnal)).
La función devuelve un puntero al manejador previamente instalado para esa señal. Un ejemplo de uso de la
función: signal(SIGUSRl, gestion_padre); significa que cuando el proceso (en este caso el proceso padre)
recibe una señal SIGUSR1 se realizara una llamada a la función gestion_padre().
Recibe dos parámetros: el PID del proceso que recibirá la señal y la señal. Por ejemplo y suponiendo que
pid_padre es el PID de un proceso padre: kill(pid_padre, SIGUSR1); envía una señal S1GUSR1 al proceso
padre.
Si la operación se realiza con éxito, la función devuelve 0; en caso contrario, el valor devuelto es -1. Se debe
tener en cuenta que, cuando se envía la señal a varios procesos, la operación tiene éxito cuando al menos uno
Página 31 de 37
de ellos recibe la señal. En este caso, no puede determinarse qué procesos capturaron la señal o si todos lo
hicieron.
Cuando queremos que un proceso espere a que le llegue una señal, usamos la función pause(). Para capturar
esa señal, el proceso debe haber establecido un tratamiento de la misma con la función signal(). Este es su
formato:
int pause (void);
Por último la función sleep() suspende al proceso que realiza la llamada la cantidad de segundos indicada o
hasta que se reciba una señal.
#include <unistd.h>
unsigned int sleep (unsigned int seconds);
En el siguiente ejemplo se crea un proceso hijo y el proceso padre le va a enviar dos señales SIGUSR1. Se
define la función manejador() para gestionar la señal, visualizará un mensaje cuando el proceso hijo la reciba.
En el proceso hijo se realiza la llamada a signal() donde se decide lo que se hará en el caso de recibir una
señal, en este caso pinta un mensaje. Después hacemos un bucle infinito que no hace nada.
En el proceso padre se hacen las llamadas a kill() para enviar las señales. Con la función sleep() hacemos que
los procesos esperen un segundo antes de continuar.
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
/*********************************/
/* Gestión de señales en proceso HIJO */
int main(){
int pid_hijo;
pid_hijo = fork(); //creamos el hijo
switch (pid_hijo){
case -1:
printf("Error al crear el proceso hijo...\n");
exit(-1);
case 0: //HIJO
signal(SIGUSR1, manejador); //MANEJADOR DE SEÑAL EN HIJO
while(1){};
break;
default: //PADRE envía 2 señales
sleep(1);
kill(pid_hijo, SIGUSR1); //ENVÍA SEÑAL AL HIJO
sleep(1);
kill(pid_hijo, SIGUSR1); //ENVÍA SEÑAL AL HIJO
sleep(1);
break;
}
return 0;
}
Página 32 de 37
En el ejemplo que se muestra a continuación un proceso padre y otro hijo se ejecutan de forma síncrona. Se
han definido dos funciones para gestionar la señal uno para el padre y otro para el hijo, con las acciones que
se realizaran cuando los procesos reciban una señal; en este caso se visualizara un mensaje.
En primer lugar el proceso padre crea el proceso hijo. Dentro de cada proceso se realiza una llamada a
signal() donde se decide lo que se hará en el caso de recibir una señal. En el proceso padre tenemos las
siguientes instrucciones donde se observa que entra en bucle infinito esperando a recibir una señal. Cuando
recibe la señal se ejecutaría la función gestion_padre().
Con kill() envía la señal de respuesta al proceso hijo mediante su PID, y el proceso se vuelve a repetir:
signal(SIGUSR1, gestion_padre );
while(1) {
pause();//padre espera hasta recibir una señal del hijo
sleep(1);
kill(pid_hijo, SIGUSR1);//ENVIA SEÑAL AL HIJO
}
En el proceso hijo también tenemos un trozo de código parecido, por la colocación del pause() se puede
deducir que es el proceso hijo el que inicia la comunicación con el padre mediante la llamada kill(). Primero
envía la señal al padre y después espera a que le llegue una señal de respuesta, cuando recibe la señal
ejecutaría la función gestion_hijo():
#include <stdio.h>
#include <signal.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/wait.h>
/*-------------------------------------------*/
Página 33 de 37
printf("Padre recibe senhal..%d\n", segnal);
}
/*-------------------------------------------*/
int main()
{
int pid_padre, pid_hijo;
pid_padre = getpid();
pid_hijo = fork(); //creamos hijo
switch(pid_hijo)
{
case -1:
printf( "Error al crear el proceso hijo...\n");
exit( -1 );
case 0: //HIJO
//tratamiento de la señal en proceso hijo
signal( SIGUSR1, gestion_hijo );
while(1) { //bucle infinito
sleep(1);
kill(pid_padre, SIGUSR1);//ENVIA SEÑAL AL PADRE
pause();//hijo espera hasta que llegue una señal de respuesta
}
break;
default: //PADRE
//tratamiento de la señal en proceso padre
signal( SIGUSR1, gestion_padre );
while(1) {
pause();//padre espera hasta recibir una señal del hijo
sleep(1);
kill(pid_hijo, SIGUSR1);//ENVIA SEÑAL AL HIJO
}
break;
}
return 0;
}
Para detener el proceso podemos pulsar las teclas [CTRL+C] o bien mediante el comando ps podemos ver el
PID de los procesos padre e hijo que se están ejecutando:
Página 34 de 37
Primero eliminaremos el proceso hijo (PID 5208) y después el padre (PID 5209). Al eliminar el hijo, el padre
quedara esperando.
Realiza un programa en C en donde un hijo envíe 3 señales SIGUSR1 a su padre y después envíe una señal
SIGKILL para que el proceso padre termine.
gcc actividad_l_4.c -o actividad_l_4
./actividad_1_4
ÍNDICE
1.4. ADMINISTRACIÓN DE PROCESOS EN LINUX UBUNTU................................................................................................................ 1
MOSTRANDO LOS PROCESOS.......................................................................................................................................................... 1
MEDIANTE INTERFACE GRÁFICA: GNOME-SYSTEM-MONITOR ................................................................................................................ 1
EN CONSOLA O TERMINAL ............................................................................................................................................................. 2
EL COMANDO TOP ....................................................................................................................................................................... 2
EL COMANDO PS.......................................................................................................................................................................... 3
EL COMANDO PSTREE ................................................................................................................................................................... 5
EL COMANDO JOBS ...................................................................................................................................................................... 7
Página 35 de 37
PASAR UN PROCESO DE PRIMER PLANO A SEGUNDO PLANO Y VICEVERSA ................................................................................................. 9
FINALIZAR UN PROCESO ................................................................................................................................................................ 9
UTILIZANDO EL MONITOR DEL SISTEMA. ........................................................................................................................................... 9
EN LA CONSOLA O TERMINAL ....................................................................................................................................................... 10
MEDIANTE EL COMANDO KILL E KILLALL .......................................................................................................................................... 10
MEDIANTE EL COMANDO XKILL ..................................................................................................................................................... 11
PAUSAR Y RENOVAR UN PROCESO ................................................................................................................................................. 11
CAMBIAR LA PRIORIDAD DE UN PROCESO ........................................................................................................................................ 12
1.4. EL CONCEPTO "CARGA PROMEDIO DEL SISTEMA" (LOAD AVERAGE) .......................................................................................... 14
1.5. CONTROL DE PROCESOS EN LINUX ..................................................................................................................................... 15
1.5.1. CREACIÓN Y EJECUCIÓN DE PROCESOS ............................................................................................................................ 17
1.5.2. COMUNICACIÓN ENTRE PROCESOS ................................................................................................................................. 21
PIPES SIN NOMBRE .................................................................................................................................................................... 21
PIPES CON NOMBRE FIFOS (FIRST IN FIRST OUT)............................................................................................................................ 27
1.5.3. SINCRONIZACIÓN ENTRE PROCESOS ................................................................................................................................ 31
Página 36 de 37