Un artículo para resolver los ocho métodos principales de comunicación entre procesos (IPC): canalizaciones, canalizaciones con nombre, señales, semáforos, colas de mensajes, memoria compartida + mapeo de memoria, sockets.

Tabla de contenido

Comunicación entre procesos (IPC)

IPC UNIX

Tubo

Tubería con nombre (FIFO)

Señal

Sistema V IPC

Semáforo

Cola de mensajes

Memoria compartida

Modo adicional IPC

Mapa de memoria

Enchufe


Comunicación entre procesos (IPC)

Referencia/Cita:

El método original de comunicación entre procesos de UNIX (IPC: InterProcess Communication): incluye canalizaciones (PIPE), canalizaciones con nombre (FIFO) y señales (Signal); métodos de comunicación de procesos del Sistema V: incluye semáforo (Semaphore), cola de mensajes (Message Queue ) ), y memoria compartida (Shared Memory). Ambos son los primeros UNIX IPC, así como los sockets y el mapeo de memoria. Linux ha heredado estos ocho métodos básicos.

En resumen: canalizaciones, canalizaciones con nombre, señales, semáforos, colas de mensajes, memoria compartida, mapas de memoria y sockets.

IPC UNIX

Tubo

Una tubería es un método de comunicación semidúplex. Es un buffer en el kernel. Los datos solo pueden fluir en una dirección, escribiendo en un extremo y leyendo en el otro. Solo se puede usar entre procesos que tienen una relación (padre- proceso hijo). Además, la tubería transmite un flujo de bytes sin formato y el tamaño del búfer de la tubería es limitado (el búfer de la tubería existe en la memoria y se asigna un tamaño de página para el búfer cuando se crea la tubería).

Half-duplex (comunicación bidireccional, solo una parte puede enviar y la otra puede recibir al mismo tiempo); se utiliza para la comunicación entre procesos de padre, hijo y hermano (¿Puedes ver el método de denominación en esta sociedad patriarcal? ¿Por qué no? Si nos levantamos y utilizamos procesos "padres e hijos", "procesos pares" (¡llamémoslo!), las tuberías también se denominan tuberías sin nombre o tuberías anónimas; las tuberías utilizadas para la comunicación entre dos procesos cualesquiera se denominan tuberías con nombre.

Utilice la función pipe() para crear un búfer para la comunicación de tuberías. Esta función devolverá dos descriptores de archivo, a saber, "leer descriptor de archivo" y "escribir descriptor de archivo", que apuntan respectivamente al extremo de entrada del búfer. /reading end y final de salida/fin de escritura, y luego dos procesos relacionados usan write () para "escribir el descriptor de archivo" y escribir datos, y el otro usa read () para "leer el descriptor de archivo" Simplemente lea los datos.

#include <unistd.h> 
int pipe(int pipefd[2]); 
/* Parámetro de matriz pipefd, se deben pasar dos descriptores de archivo, pipefd[0] es para lectura, pipefd[1] es para escritura*/

Al llamar a la función de tubería, se abre un búfer en el kernel para la comunicación, que tiene un extremo de lectura y un extremo de escritura. pipefd[0] apunta al extremo de lectura de la tubería y pipefd[1] apunta al extremo de escritura de la tubería. Por lo tanto, la tubería parece un archivo abierto en el programa de usuario. Leer y escribir datos en este archivo mediante lectura (pipefd [0]) o escritura (pipefd [1]) es en realidad leer y escribir en el búfer del núcleo.

Procedimientos de operación

  1. El proceso principal (Nota del autor: ¡¡¡El nombre aquí debe cambiarse a "proceso principal" y el "nodo principal" en la estructura de árbol debe cambiarse a "nodo principal"!!!) llama a la tubería para abrir una tubería y Obtiene dos descriptores de archivo que apuntan a ambos extremos de la canalización.

  2. Si el proceso padre (nota del autor: ahhhh) llama a fork para crear un proceso hijo, entonces el proceso hijo también tiene dos descriptores de archivo que apuntan a la misma tubería.

  3. Tome el proceso principal (nota del autor: no me importará más adelante) como ejemplo de envío de datos al proceso secundario: el proceso principal cierra el extremo de lectura de la tubería y el proceso secundario cierra el extremo de escritura de la tubería. . El proceso padre puede escribir en la tubería y el proceso hijo puede leer desde la tubería. La canalización se implementa mediante una cola en anillo, y los datos fluyen desde el extremo de escritura y salen desde el extremo de lectura, logrando así la comunicación entre procesos.

Ejemplo de comunicación por tubería:

La función pipe() devuelve un descriptor de archivo, por lo que solo se puede acceder a él mediante las llamadas al sistema subyacentes read() y write().

Reglas de lectura y escritura de tuberías

Cuando no hay datos para leer

  • O_NONBLOCK deshabilitar: La llamada de lectura se bloquea, es decir, el proceso suspende la ejecución hasta que lleguen los datos.

  • Habilitación O_NONBLOCK: la llamada de lectura devuelve -1 y el valor de error es EAGAIN.

cuando la tubería está llena

  • O_NONBLOCK desactivar: la llamada de escritura se bloquea hasta que un proceso lee los datos.

  • Habilitación O_NONBLOCK: la llamada devuelve -1 y el valor de error es EAGAIN.

Cuatro situaciones especiales en los oleoductos

  1. El final de escritura está cerrado pero el final de lectura no está cerrado: luego de leer todos los datos restantes en la canalización, leer nuevamente devolverá 0, al igual que leer hasta el final del archivo.

    Si los descriptores de archivo correspondientes a los extremos de escritura de todas las tuberías están cerrados, la lectura devuelve 0.

  2. El extremo de escritura no está cerrado, pero los datos no se escriben y el extremo de lectura no está cerrado: en este momento, después de que se hayan leído todos los datos restantes en la tubería, la lectura se bloqueará nuevamente y los datos no se volverán a publicar. -leer y devolver hasta que haya datos en la tubería para leer.

  3. El extremo de lectura está cerrado, pero el extremo de escritura no está cerrado: en este momento, el proceso recibirá la señal SIGPIPE, lo que generalmente hace que el proceso finalice de manera anormal.

    Si los descriptores de archivo correspondientes a todos los lectores de tuberías están cerrados, la operación de escritura generará la señal SIGPIPE.

  4. El extremo de lectura no está cerrado, pero los datos no se leen y el extremo de escritura no está cerrado: en este momento, cuando el extremo de escritura está lleno, se bloqueará la escritura nuevamente y los datos no se escribirán hasta que haya una posición vacía en la tubería y regresar nuevamente.

Cantidad de datos escritos

  • Cuando la cantidad de datos a escribir no sea mayor que PIPE_BUF (Posix.1 requiere que PIPE_BUF tenga al menos 512 bytes), Linux garantizará la atomicidad de la escritura.

  • Cuando la cantidad de datos a escribir es mayor que PIPE_BUF, Linux ya no garantizará la atomicidad de la escritura.

Desventajas de usar tuberías.

  1. Dos procesos solo pueden lograr una comunicación unidireccional a través de una tubería. Si desea una comunicación bidireccional, debe crear una nueva tubería o usar sockpair para resolver este tipo de problema.

  2. Solo se puede utilizar para la comunicación entre procesos que tienen relaciones de afinidad, como procesos padre-hijo o hermanos.

Tubería con nombre (FIFO)

Las canalizaciones con nombre también proporcionan comunicación semidúplex , pero en comparación con las canalizaciones/canalizaciones sin nombre/canalizaciones anónimas, permiten la comunicación entre procesos no relacionados . Las canalizaciones con nombre también se denominan canalizaciones con nombre o canalizaciones con nombre real (en el contexto chino, hasta donde puedo ver, ahora hay un total de seis nombres para Pipe y FIFO).

Referencia: canalización de comunicación entre procesos de Linux (canalización), canalización con nombre (FIFO) y señal (señal) - as_ - Blog Park (cnblogs.com), comunicación de procesos de Linux: canalización con nombre FIFO resumen_blog-CSDN de Mr_John_Liang .

Referencia detallada para FIFO de tubería con nombre: [Linux] Blog de guía de aprendizaje de programación FIFO de tubería con nombre de comunicación entre procesos-blog CSDN con nombre de tubería FIFO .

FIFO simplemente toma prestado el sistema de archivos (la canalización con nombre es un tipo especial de archivo, porque todo en Linux es un archivo y existe como un nombre de archivo en el sistema de archivos) para nombrar la canalización. Los procesos en modo de escritura escriben en el archivo FIFO, mientras que los procesos en modo de lectura leen del archivo FIFO. Cuando se elimina el archivo FIFO, la conexión de la tubería desaparece. La ventaja de FIFO es que podemos identificar tuberías por la ruta del archivo, lo que permite establecer conexiones entre procesos no relacionados.

#include <sys/types.h> 
#include <sys/stat.h> 
int mkfifo(const char *nombre de archivo, modo mode_t); 
int mknode(const char *nombre de archivo, modo_t | S_IFIFO, (dev_t) 0); 
/ * Donde nombre de ruta es el nombre del archivo que se creará (el archivo no debe existir), modo representa los bits de permiso que se establecerán en el archivo y el tipo de archivo que se creará (indica sus permisos de lectura y escritura), y dev es el archivo especial del dispositivo cuando se crea un valor para usar. Por lo tanto, tiene un valor de 0 para los archivos de primero en entrar, primero en salir. */

Procedimientos de operación

  1. Primero puede usar access() para determinar si el archivo FIFO de canalización con nombre de destino existe. Si existe, puede saltar al paso tres; si no existe, puede saltar al paso dos.

  2. Utilice mkfifo() para crear un archivo FIFO de canalización con nombre, utilizando el parámetro modo 0777. Si el archivo FIFO creado es , puede verlo /tmp/my_fifousando la línea de comando .ls -lF /tmp/my_fifo

  3. Luego use open() (o el paquete avanzado de fopen()) para abrir el archivo FIFO (los indicadores entrantes son O_RDONLY, O_WRONLY y O_NONBLOCK, individualmente o en combinación, los indicadores entrantes se analizarán en detalle más adelante) 关于 FIFO 读写时候的阻塞问题. Debido a que FIFO es un archivo, debe abrirse antes de usarlo.

  4. Luego use lectura/escritura (o fread/fwrite) para leer y escribir.

  5. Finalmente use close() para cerrar el archivo.

Ejemplo de referencia

Escribe el ejemplo wirte_fifo.c

O_WRONLY); 
     if(fd == -1){ perror("error de apertura"); salir(1); }
 char *p = "hola mundo"; 
 int len ​​= escribir(fd, p, strlen(p)+1); 
 cerrar(fd); 
 devolver 0; 
}

Leer ejemplo read_fifo.c

O_RDONLY); 
     if(fd == -1){ perror("error de apertura"); salir(1); } 
 char buf[512];
 int len ​​= leer(fd, buf, tamaño de(buf)); 
 buf[len] = 0; 
 printf("buf = %s\n, len = %d", buf, len); 
 cerrar(fd); 
 devolver 0; 
}

Respecto al problema de bloqueo durante la lectura y escritura FIFO

Referencia detallada:

Si se usa open() para abrir un archivo FIFO de forma bloqueante (es decir, no se pasa el indicador O_NONBLOCK), entonces read() se bloqueará (cuando el FIFO esté vacío u otros procesos estén leyendo, se bloqueará). bloqueado Hasta que se libere el bloqueo), lo mismo ocurre con write().

Los indicadores entrantes para abrir un archivo FIFO son O_RDONLY, O_WRONLY y O_NONBLOCK, individualmente o en combinación, como se detalla a continuación:

Una limitación importante al abrir FIFO es que el programa no puede abrir el archivo FIFO en modo O_RDWR para operaciones de lectura y escritura. Las consecuencias de hacerlo no están claramente definidas. Esta restricción tiene sentido porque usamos FIFO solo para pasar datos en singletons, por lo que no es necesario usar el modo O_RDWR. Si una tubería abre un FIFO para lectura/escritura, el proceso vuelve a leer su propia salida de la tubería. Si realmente necesita pasar datos en ambas direcciones entre programas, es mejor utilizar un par de FIFO, uno en cada dirección.

Cuando un proceso de Linux está bloqueado, no consume recursos de la CPU, este método de sincronización de procesos es muy eficiente para la CPU.

Por lo tanto, se puede ver que, además de leer/escribir, el mayor impacto es el indicador O_NONBLOCK:

  • flags = O_RDONLY: open llamará al bloqueo y esperará hasta que otro proceso abra el mismo FIFO para escribir.

  • flags = O_WRONLY: open llamará al bloqueo y esperará hasta que otro proceso abra el mismo FIFO para lectura.

  • flags = O_RDONLY | O_NONBLOCK: Si ningún otro proceso abre el FIFO para escribir en este momento, la apertura también regresará con éxito. En este momento, el FIFO se abrirá para leer sin devolver un error.

  • flags = O_WRONLY | O_NONBLOCK: Regreso inmediato. Si no se abre ningún otro proceso para lectura en este momento, open no se abrirá. En este momento, el FIFO no se abre y se devolverá -1.

Operaciones de lectura y escritura en archivos FIFO (el impacto de pasar el indicador O_NONBLOCK cuando se abre()):

El indicador de parámetro O_NONBLOCK en la llamada a la función abierta afectará las operaciones de lectura y escritura de FIFO.

Las reglas son las siguientes:

  • Una llamada de lectura a un FIFO de bloqueo vacío esperará hasta que haya datos para leer antes de continuar.

  • Una llamada de lectura a un FIFO vacío y sin bloqueo devuelve inmediatamente 0 bytes.

  • Una llamada de escritura a un FIFO completamente bloqueado esperará hasta que se puedan escribir datos antes de ejecutarse.

Reglas sobre el tamaño de los datos escritos a la vez:

Regulaciones del sistema: si la longitud de los datos escritos es menor o igual a PIPE_BUF bytes, se escribirán todos los bytes o no se escribirá ni uno solo. Tenga en cuenta el efecto de esta restricción:

Cuando se utiliza solo un FIF y se permite que varios programas diferentes envíen solicitudes a un proceso de lector FIFO, esta restricción es importante para garantizar que los bloques de datos de diferentes programas no se intercalen entre sí, es decir, cada operación es atómica. Si todas las solicitudes de escritura se envían a un FIFO de bloqueo y la longitud de los datos de cada solicitud de escritura es menor o igual a PIPE_BUF bytes, el sistema puede garantizar que los datos nunca se entrelazarán. Generalmente es una buena idea limitar la longitud de los datos que pasan a través de FIFO a PIPE_BUF cada vez.

En el caso de una llamada de escritura sin bloqueo, si el FIFO no puede recibir todos los datos escritos, se seguirán las siguientes reglas:

  • La longitud de los datos solicitados para escribir es inferior a PIPE_BUF bytes, la llamada falla y los datos no se pueden escribir.

  • La longitud de los datos solicitados para escribir es mayor que PIPE_BUF bytes. Se escribirán datos parciales y se devolverá el número real de bytes escritos. El valor de retorno también puede ser 0.

en. PIPE_BUF es la longitud del FIFO, que se define en los límites del archivo de encabezado.h. En Linux u otros sistemas similares a UNIX, su valor suele ser de 4096 bytes.

Eliminación de archivos FIFO

Los archivos FIFO deben eliminarse después de su uso para evitar la creación de archivos basura.

#include <unistd.h> 
int unlink(const char *nombre de ruta);

Para obtener detalles sobre la desvinculación, consulte: unlink(2) - Página de manual de Linux .

Señal

Citas/referencias adicionales: Capítulo 10 Signal-as_-Blog Park (cnblogs.com) ,  Linux-Programación de aplicaciones-Resumen de aprendizaje (4): Comunicación entre procesos (Parte 2)_Estudiar el blog-CSDN de Broccoli , Comunicación entre procesos de Linux tuberías (tubería), tuberías con nombre (FIFO) y señales (Señal) - as_ - Blog Park (cnblogs.com) .

Las señales se utilizan para notificar al proceso receptor que ha ocurrido un evento. Se entrega una señal para notificación asincrónica (las señales son asincrónicas y un proceso no tiene que esperar la llegada de la señal a través de ninguna operación. De hecho, el proceso no no sé cuándo llega la señal Señal Es el único mecanismo de comunicación asincrónico entre los mecanismos de comunicación entre procesos y puede considerarse como una notificación asincrónica) y no puede transmitir ningún dato. Las señales son una especie de mecanismo de interrupción a nivel de software y el efecto de simulación es similar al mecanismo de interrupción.

Sin embargo, las señales y las interrupciones son diferentes. La respuesta y el procesamiento de interrupciones ocurren en el espacio del kernel, mientras que la respuesta de las señales ocurre en el espacio del kernel y la ejecución de los manejadores de señales ocurre en el espacio del usuario.

Entonces, ¿cuándo detectas y respondes a una señal? Suele ocurrir en dos situaciones:

  • Después de que el proceso actual ingresa al espacio del kernel debido a una llamada, interrupción o excepción del sistema, y ​​antes de regresar del espacio del kernel al espacio del usuario, es decir, cuando un proceso está a punto de regresar del estado del kernel al estado de usuario (es decir, es decir, la señal de interrupción suave no funciona en el estado del kernel), no se procesará hasta que regrese al modo de usuario).

  • Cuando el proceso actual recién se despierta después de entrar en suspensión en el kernel, regresa temprano al espacio del usuario debido a la detección de la señal, es decir, cuando un proceso está a punto de entrar o salir de un estado de suspensión apropiado de baja prioridad.

Señal de referencia (mecanismo de señal LINUX)_Enciclopedia Baidu (baidu.com) .

En informática, las señales son un método restringido de comunicación entre procesos en Unix, sistemas similares a Unix y otros sistemas operativos compatibles con POSIX. Es un mecanismo de notificación asincrónico que se utiliza para recordar al proceso que ha ocurrido un evento. Cuando se envía una señal a un proceso, el sistema operativo interrumpe el flujo normal de control del proceso, en este momento se interrumpirán todas las operaciones no atómicas. Si el proceso define un controlador de señales, se ejecutará; de lo contrario, se ejecutará el controlador predeterminado.

Los procesos pueden enviarse señales de interrupción suave entre sí a través de la llamada al sistema kill (el comando kill se usa en el shell, la función kill() se usa en la programación de aplicaciones). El kernel también puede enviar señales al proceso debido a eventos internos, notificando al proceso que ha ocurrido un evento. Tenga en cuenta que las señales solo se utilizan para notificar a un proceso qué eventos han ocurrido y no pasan ningún dato al proceso. El shell también puede utilizar señales para pasar comandos de control de trabajos a sus procesos secundarios.

Tipo de señal:

Señales comunes:

nombre de la señal número de señal procesar contenido
FIRMA 2 Cuando Ctrl+C, el sistema operativo envía un mensaje a cada proceso en el grupo de procesos de primer plano.
SIGUE 3 Al ingresar la tecla Salir (CTRL+/), ​​se envía a todos los procesos del grupo de primer plano.
SIGABRT 6 Se llama a la función de aborto y el proceso finaliza de forma anormal.
SIGKILL 9 Abortar un proceso. No se puede ignorar ni capturar.
PLAZO OBJETIVO 15 Solicitudes para finalizar el proceso. El comando Kill se envía de forma predeterminada. La señal de terminación predeterminada del sistema operativo enviada por el comando kill
SIGTSTP 20 Tecla Suspender, normalmente Ctrl+Z. Enviado a todos los procesos del grupo de primer plano
SIGSTOP 19 Abortar el proceso. No se puede ignorar ni capturar.
SEÑAL 18 Cuando el proceso detenido reanuda su operación, se envía automáticamente.
SIGSEGV 11 El sistema operativo emite esta señal cuando se produce un acceso al almacenamiento no válido.
SIGPIPE 13 Involucra tuberías y enchufes. Enviado al escribir en Pipe después de que el lector haya terminado. Ocurre al escribir en una canalización que nadie ha leído.
SIGALRM 14 El tiempo de espera del temporizador establecido por la función de alarma o el tiempo de espera del temporizador de intervalo establecido por la función setitimer
SEÑAL 17 El sistema operativo envía esta señal a su proceso principal cuando el proceso secundario finaliza o se detiene. Cuando el proceso finalice o se detenga, SIGCHLD se enviará a su proceso principal. Por defecto, esta señal será ignorada
SIGPOLL / SIGIO 8 Indica un evento IO asíncrono, mencionado en IO avanzado
SIGUSR1 10 Señales definidas por el usuario, cuyas funciones y significados están definidos por la propia aplicación.
SIGUSR2 12 Señales definidas por el usuario, cuyas funciones y significados están definidos por la propia aplicación.
SIGTTIN 21 El proceso en segundo plano quiere leer
SIGTTOU 22 El proceso en segundo plano quiere escribir.

Ver todas las señales y los números correspondientes en el Shell: kill -l.

Una señal que no se puede ignorar

  • SIGKILL, finalizará el proceso.

  • SIGSTOP, que forma parte del mecanismo de control del trabajo, suspenderá la ejecución del trabajo. No se puede atrapar ni ignorar.

Esto es para proporcionar una forma segura de detener o finalizar un proceso.

señal de control de trabajo

SIGCHILD: El proceso hijo ha sido detenido o finalizado;

SIGCONT: Si el proceso se detiene, dejar que siga ejecutándose;

SIGSTOP: señal de parada (no se puede captar ni ignorar);

SIGTSTP: Señal de parada para interacción;

SIGTTIN: Los miembros del grupo de proceso en segundo plano leen la terminal de control;

SIGTTOU: Los miembros del grupo de proceso en segundo plano escriben en la terminal de control.

Control de trabajos de procesos en Shell: control de tareas de Linux bg, fg, trabajos, matar, esperar, suspender... - Baidu Experience (baidu.com) . Hay bg, fg, trabajos, matar, esperar y suspender.

Transmisión de señal:

  • Entre controladores y aplicaciones: las señales pueden interactuar directamente entre los procesos del espacio del usuario y los procesos del kernel (como los controladores). El proceso del kernel puede usarlo para notificar a los procesos del espacio del usuario qué eventos han ocurrido (por ejemplo, los controladores comúnmente usan señales SIGIO para notificar de forma asíncrona aplicaciones).

  • Entre aplicaciones: también se puede utilizar como una forma de comunicación entre procesos o modificación de comportamiento, enviada explícitamente por un proceso a otro proceso (o a sí mismo). La generación de una señal se llama generación y la recepción de una señal se llama captura.

Situaciones que generan señales:

  • Emitido por usuarios de Shell. Por ejemplo, presionar CTRL+C en un proceso en primer plano genera una señal SIGINT y la envía al proceso en primer plano.

  • API de llamada al sistema en el proceso de modo de usuario (como matar (), elevar (), alarma (), pausa (), etc.).

  • Los controladores envían señales para notificar aplicaciones de forma asincrónica (las comunes como SIGIO) o errores de hardware.

Tres tipos de procesamiento de señales capturadas:

  1. El proceso ignora la señal. La mayoría de las señales pueden ignorarse. Además, si ignoramos las señales generadas por algunas excepciones de hardware, el comportamiento del proceso queda indefinido. De hecho, la acción predeterminada para señales individuales es ignorarlas.

  2. Captar la señal. Después de que el proceso recibe la señal, ejecuta la función configurada por el usuario para llamar al sistema de señal/sigaction (el usuario puede configurar la función de devolución de llamada de señal).

  3. Realice la acción predeterminada. Si no se realiza ningún procesamiento, se realiza la acción predeterminada. El comportamiento predeterminado de la mayoría de las señales es abortar el proceso.

El comportamiento predeterminado de algunas señales no solo finaliza el proceso, sino que también genera un volcado de núcleo, es decir, se genera un archivo llamado core, que guarda la imagen de la memoria del proceso al salir, que puede usarse para depurar. En las siguientes situaciones, no se generará el archivo principal:

  • El proceso actual no pertenece al usuario actual.

  • El proceso actual no pertenece al grupo actual.

  • El usuario no tiene permiso de escritura en el directorio actual.

  • El archivo Core ya existe y el usuario no tiene permiso de escritura.

  • El archivo es demasiado grande y supera RLIMIT_CORE.

La esencia es: la señal notifica de forma asincrónica al proceso que recibe la señal que ha ocurrido un evento, y luego el sistema operativo interrumpirá la ejecución del proceso que recibió la señal y en su lugar ejecutará el controlador de señal correspondiente (ejecutar de acuerdo con ignorar, capturar u operaciones por defecto) ).

#include <sys/types.h> 
#include <signal.h> 
#include <unistd.h> 
​void
(*signal(int sig,void (*func)(int)))(int); 
/* vinculante 
El El primer parámetro de la función de devolución de llamada después de recibir una determinada señal es la señal, y el segundo parámetro es el puntero de la función de procesamiento del usuario para la señal. 
El valor de retorno es un puntero al controlador de señal anterior. 
​Ejemplo
: int ret = signal(SIGSTOP, sig_handle); 
*/ 
​/
* Dado que la señal no es lo suficientemente robusta, se recomienda utilizar la función sigaction. La función sigaction vuelve a implementar la función de señal*/ 
int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact); 
/* Con respecto al uso de sigaction, verifique el tiempo de uso */ 
​//
La función kill envía una señal al proceso con el número de proceso pid y el valor de la señal es sig. Cuando pid es 0, la señal sig se envía a todos los procesos del sistema actual. 
int kill(pid_t pid,int sig); 
/* 
    El parámetro pid de kill tiene cuatro situaciones: 
 1).pid > 0, la señal se envía al proceso con el ID de proceso de pid;
 2).pid == 0, la señal se envía a todos los procesos que pertenecen al mismo grupo de procesos que el proceso de envío (el ID del grupo de procesos de estos procesos es igual al ID del grupo de procesos del proceso de envío), y el proceso de envío El proceso tiene el permiso para enviar señales a estos procesos. Tenga en cuenta que el término "todos los procesos" no incluye el conjunto de procesos del sistema definido por la implementación. Para la mayoría de los sistemas UNIX, este conjunto de procesos del sistema incluye el proceso del núcleo e init (pid 1); 
 3).pid < 0, la señal se envía a todos aquellos cuyo ID es igual al valor absoluto de pid y el remitente tiene permiso. para enviar señales de proceso. Como se indicó anteriormente, el conjunto de todos los procesos no incluye los procesos del sistema. 
 4).pid == -1, envía esta señal a todos los procesos del sistema a los que el proceso de envío tiene permiso para enviar preferencias. Como antes, no se incluyen procesos del sistema específicos. 
​Ejemplo
 : finalizar el proceso principal kill(getppid(), SIGKILL); 
*/ 
​//
Arranca una señal al proceso actual, es decir, envía una señal al proceso actual. Equivalente a kill(getpid(),sig); 
int rise(int sig); 
//
alarm() se utiliza para configurar la señal SIGALRM que se enviará al proceso actual después del número de segundos especificado por el parámetro segundos. Si el parámetro de segundos es 0, la alarma previamente configurada se cancelará y se devolverá el tiempo restante. Al utilizar la función de alarma, preste atención a la cobertura de la función de alarma, es decir, si la función de alarma se utiliza una vez en un proceso, la función de alarma anterior del proceso no será válida. 
//Después de unos segundos, envía la señal SIGALRM al proceso mismo. 
alarma int sin signo (segundos int sin signo); 
/*
 Cuando se excede el valor de tiempo establecido, se genera la señal SIGALRM. Si esta señal no se ignora ni se detecta, la acción es finalizar el proceso. 
 Si la hora de la alarma se configuró previamente para el proceso cuando se llama a la alarma y no se agotó el tiempo de espera, el valor restante de la hora de la alarma se devuelve como el valor de esta llamada a la función de alarma. La hora de alarma previamente registrada se reemplaza por el nuevo valor. Si hay una hora de alarma previamente registrada que aún no ha pasado y el valor de los segundos es 0, la hora de alarma anterior se cancela y los valores restantes aún se utilizan como valor de retorno de la función. 
*/ 
​//
Retraso/segundos de suspensión segundos 
unsigned int sleep(unsigned int segundos); 
/* 
 Devuelve 0 o el número de segundos sin suspensión. 
 Esta función hace que el proceso de llamada se suspenda hasta que: 
 (1) haya pasado el tiempo del reloj de pared especificado en segundos; 
 (2) el proceso capture una señal y regrese del controlador de señales. 
 Al igual que la señal de alarma, debido a alguna actividad del sistema, el tiempo de retorno real será posterior al requerido 
*/ 
​//
Hacer que el proceso de llamada (o subproceso) entre en suspensión hasta que se reciba la señal, o termine, o haga que llame a una señal función de captura. 
// La función de pausa hace que el proceso de llamada se suspenda hasta que se capte una señal. 
int pausa(void); 
/* 
 La pausa regresa solo cuando se ejecuta y devuelve un controlador de señal. En este caso, la pausa devuelve -1 y errno se establece en EINTR. 
*/
/
// La función de la función de aborto es hacer que el programa finalice de manera anormal. Esta función envía la señal SIGABRT al proceso de llamada. Los procesos no deberían ignorar esta señal. 
// La función de cancelación nunca regresa. 
aborto nulo (nulo);

Algunos ejemplos de envío y captura de señales:

Sistema V IPC

System V IPC se refiere a las tres herramientas de comunicación entre procesos introducidas por AT&T en la versión System V.2: (1) semáforos, utilizados para administrar el acceso a recursos compartidos (2) memoria compartida, utilizada para implementar procesos de manera eficiente Intercambio de datos entre procesos (3) Cola de mensajes, utilizada para realizar la transferencia de datos entre procesos. Nos referimos a estas tres herramientas colectivamente como objetos IPC del Sistema V, y cada objeto tiene un identificador IPC único. Para garantizar que diferentes procesos puedan obtener el mismo objeto IPC, se debe proporcionar una clave IPC y el núcleo es responsable de convertir la clave IPC en un identificador IPC.

System V IPC tiene una sintaxis similar, en términos de nombres de API: semxxx() para semáforos, shmxxx() para memoria compartida, msgxxx() para colas de mensajes.

System V IPC generalmente funciona de la siguiente manera:

1. Seleccione la palabra clave IPC (parámetro formal key_t key en API). Puede utilizar los siguientes tres métodos:

  • IPC_PRIVADO. El kernel es responsable de seleccionar una palabra clave y luego generar un objeto IPC y pasar el identificador IPC directamente a otro proceso.

  • Simplemente seleccione una palabra clave existente.

  • Utilice la función ftok() para generar una palabra clave.

2. Utilice la función semget()/shmget()/msgget() para crear o acceder a un objeto IPC basado en la clave de palabra clave IPC y una bandera.

Si la clave es IPC_PRIVATE, o la clave no está asociada con un objeto IPC existente y el indicador contiene el indicador IPC_CREAT, se creará un nuevo objeto IPC.

3. Utilice la función semctl()/shmctl()/msgctl() para modificar los atributos del objeto IPC.

4. Utilice la función semctl()/shmctl()/msgctl() y el indicador IPC_RMID para destruir el objeto IPC.

System V IPC establece una estructura ipc_perm para cada objeto IPC y la inicializa al crear el objeto IPC. Esta estructura define los permisos de acceso y los propietarios del objeto IPC:

struct ipc_perm{ 
   uid_t uid; //ID de usuario del propietario 
   gid_t gid; //ID de grupo del propietario 
   uid_t cuid; //ID de usuario del creador 
   gid_t cgid; //ID de grupo del creador 
   mode_t mode; //Modo de acceso 
   … 
};

Las colas de mensajes, los semáforos y la memoria compartida se denominan colectivamente XSI IPC y tienen estructuras IPC similares en el kernel (msgid_ds para colas de mensajes, semid_ds para semáforos y shmid_ds para memoria compartida) y todos están identificados por un número entero no negativo. Se hace referencia al identificador (msg_id de la cola de mensajes, sem_id del semáforo y shm_id de la memoria compartida, obtenidos a través de msgget, semget y shmget respectivamente). El identificador es el nombre interno del objeto IPC. Cada objeto IPC tiene un key (key_t key) Asociación, utilizando esta clave como nombre externo del objeto.

La estructura IPC de XSI IPC funciona en todo el sistema y no utiliza recuento de referencias. Si un proceso crea una cola de mensajes y coloca varios mensajes en la cola de mensajes, una vez finalizado el proceso, la cola de mensajes y su contenido permanecen incluso si ningún programa está utilizando la cola de mensajes. Cuando PIPE finaliza el último proceso que hizo referencia a la tubería, la tubería se elimina por completo. Cuando finalice el último proceso que hace referencia al FIFO, aunque el FIFO todavía esté en el sistema, su contenido será eliminado. A diferencia de PIPE y FIFO, XSI IPC no usa descriptores de archivos, por lo que no puede usar ls para ver objetos IPC, no puede usar el comando rm para eliminarlos y no puede usar el comando chmod para eliminar sus derechos de acceso. Sólo puedes usar ipcs e ipcrm para ver si puedes eliminarlos.

Los comandos para gestionar objetos IPC en el shell son ipcs, ipcmk e ipcrm.

como:

  • ipcs -sVerifique el número de conjuntos de semáforos creados y ipcrm -s <semid>elimine un conjunto de semáforos numerado semid.

  • ipcs -mVerifique la cantidad de memorias compartidas creadas y ipcrm -m shm_idelimine la memoria compartida.

Semáforo

Referencia/cita adicional: blog del semáforo de objetos IPC - blog CSDN semáforo ipc , explicación detallada del semáforo IPC blog de Qiuoooooo - blog CSDN semáforo ipc .

Un semáforo es un contador que se utiliza para registrar el estado de acceso de cada proceso a un determinado recurso y se puede utilizar para controlar el acceso de múltiples procesos a recursos compartidos (por ejemplo, los semáforos se utilizan en la memoria compartida posterior). A menudo se utiliza como mecanismo de bloqueo para evitar que otros procesos accedan a un recurso compartido cuando un proceso accede al recurso. A menudo se utiliza para abordar el problema de sincronización de acceso a recursos críticos (recursos críticos: recursos que solo pueden ser operados por un proceso o subproceso en un momento determinado). Sólo un hilo puede acceder a recursos críticos en cualquier momento.

Flujo de trabajo del semáforo:

(1) Crear un semáforo para controlar un recurso.

(2) Si el valor de este semáforo es positivo, se permite utilizar el recurso. El proceso disminuye el número de avance en 1.

(3) Si el semáforo es 0, el recurso no está disponible actualmente y el proceso entra en estado de suspensión hasta que el valor del semáforo sea mayor que 0, el proceso se despierta y pasa al paso (1).

(4) Cuando el proceso ya no utiliza un recurso controlado por un semáforo, el valor del semáforo aumenta en 1. Si hay un proceso inactivo esperando este semáforo en este momento, despierte este proceso.

Cuando un proceso ya no usa el recurso, el semáforo es +1 (la operación correspondiente se llama operación V), por el contrario, cuando un proceso usa el recurso, el semáforo es -1 (la operación correspondiente es la operación P) . Todas las operaciones de valor en semáforos son operaciones atómicas.

Operación P, prepárese para comenzar a leer y escribir, P(sv): Si el valor de sv es mayor que cero, disminúyalo en 1; si su valor es cero, suspenda la ejecución del proceso y espere la operación.

La operación V se puede liberar después de leer y escribir. V (sv): si otros procesos están suspendidos esperando a sv, déjelo reanudar su ejecución. Si ningún proceso está suspendido esperando a sv, agregue 1.

Una comprensión simple es que P equivale a solicitar recursos y V equivale a liberar recursos.

Por ejemplo, dos procesos comparten el semáforo sv, con un valor inicial de 1. Una vez que uno de los procesos realiza la operación P(sv), obtendrá el semáforo y podrá ingresar a la sección crítica, reduciendo sv en 1. Y el segundo proceso no podrá ingresar a la sección crítica porque cuando intenta ejecutar P(sv), sv es 0, se bloqueará esperando que el primer proceso abandone la sección crítica y ejecute V(sv) para liberar el semáforo, entonces el segundo proceso puede reanudar la ejecución.

Semáforo binario : el valor es 0 o 1. Similar a un bloqueo mutex, el valor es 1 cuando el recurso está disponible y 0 cuando no está disponible; es decir, la operación P equivale a bloquear y la operación V equivale a desbloquear.

Semáforo de conteo : valor entre 0 y n. Al igual que los recursos estadísticos, su valor representa la cantidad de recursos disponibles.

En un sistema Linux, el uso de un semáforo generalmente requiere cuatro operaciones: crear un semáforo, inicializar el semáforo, operación PV del semáforo y eliminar el semáforo.

Crear/obtener una colección de semáforos:

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/sem.h> 
int semget(key_t key, int nsems, int semflg); 
/* 
    clave: número/clave de colección de semáforos El valor 
        se puede obtener usando la función key_t ftok(const char *pathname, int proj_id); 
        diferentes procesos pueden acceder al mismo conjunto de semáforos siempre que el valor de la clave sea el mismo. 
        Hay un valor especial IPC_PRIVATE, que significa crear un privado nsems de semáforo 
    para el proceso actual: este parámetro indica el número de semáforos en el conjunto de semáforos que desea crear. Los semáforos sólo se pueden crear como colecciones. 
        El número de semáforos que se crearán, normalmente 1. Si se crean varios semáforos, se denomina conjunto de semáforos. 
        Si está creando una nueva colección, se debe especificar nsems. 
        Si hace referencia a una colección de semáforos existente, especifique nsems como 0. Si solo está obteniendo el identificador del conjunto de semáforos (en lugar de crear uno nuevo), nsems puede ser 0. 
    semflg: 
        IPC_CREAT|IPC_EXCL significa que si el semáforo correspondiente a la clave no existe, se creará y, si existe, se informará un error. Es decir, se creará una nueva colección de semáforos o se devolverá -1 si ya existe. 
        IPC_CREAT significa que si el semáforo correspondiente a la clave no existe, se creará y, si existe, se devolverá directamente el identificador del semáforo. Devuelve una colección de semáforos nueva o existente.
        Los 8 bits inferiores de la bandera se utilizan como bits de permiso de acceso del semáforo, similar a los permisos de acceso del archivo, y 
            los 8 bits inferiores de la bandera son bits de permiso. Generalmente se usa 0666 (el número binario de 6 es 110, lo que significa legible, escribible y no ejecutable. Los tres 6 corresponden al usuario actual, al usuario del grupo y a otros usuarios respectivamente). Por ejemplo, el indicador puede ser IPC_CREAT |0666. El valor de retorno es: el semáforo se devuelve 
        correctamente 
    . Semid de la colección (entero no negativo), devuelve -1 en caso de error. 
    
    Por ejemplo, si se usa el mismo semáforo entre el proceso A y el proceso B, entonces el diseño A genera/crea el semáforo primero, y luego B hace referencia/vincula el semáforo: por lo tanto, cuando A llama a semget(), se debe pasar el parámetro 
        semflg en IPC_CREAT|IPC_EXCL |0666, y nsems es el número de semáforos que se crearán; 
        y B debe pasar IPC_CREAT|0666 o IPC_CREAT, y nsems es 0. 
*/ 
​key_t
ftok( const char * fname, int id); 
/* Función de conversión de formato del valor de la clave IPC. El sistema debe especificar un valor de ID al establecer la comunicación IPC (cola de mensajes, semáforo y memoria compartida). Normalmente, el valor de id se obtiene mediante la función ftok. 
    fname es el nombre del archivo que especifica (un nombre de archivo existente). Generalmente, la identificación del directorio actual 
    es el número de subserie. Aunque es de tipo int, sólo se utilizan 8 bits (1-255).
    Proceso de cálculo: si el número de nodo de índice del archivo especificado es 65538, convertido a hexadecimal es 0x010002 y el valor de ID que especificó es 38, convertido a hexadecimal es 0x26, entonces el valor de retorno final de key_t es 0x26010002. 
    Se utiliza para garantizar que el mismo programa y dos programas idénticos bajo dos usuarios diferentes obtengan valores de clave IPC que no interfieran entre sí.
    Ejemplo key_t clave = ftok(".", 'a'); 
*/

Configuración del semáforo (valor de inicialización o destrucción):

/* Estructura definida en el kernel*/ 
union semun{ 
    int val; // El valor utilizado por SETVAL 
    struct semid_ds *buf; // El área de búfer utilizada por IPC_STAT e IPC_SET 
    unsigned short *array; // El área de búfer utilizada por GETALL y SETALL, ALL, todos los semáforos de un determinado conjunto de semáforos 
    struct seminfo *__buf; // IPC_INFO (específico de Linux) usa el área de caché 
}; 
​/
* El núcleo mantiene una estructura semid_ds para cada conjunto de semáforos */ 
struct semid_ds{ 
    struct ipc_perm sem_perm; 
    unsigned short sem_nsems; 
    time_t sem_otime; 
    time_t sem_ctime; 
    ... 
} 
​int
semctl(int semid, int semnum, int cmd, union semun arg); 
/* 
    semid: complete el número cmd del semáforo configurado para ser operado  
    : Para operar el semáforo semnum-ésimo en el conjunto de semáforos semid (rango: 0 ~ nsems-1)
    : Se definen una variedad de operaciones diferentes en el archivo de encabezado sem.h: los siguientes ejemplos 
        IPC_STAT: obtiene la estructura semid_ds de una determinada colección de semáforos y la almacena en la dirección señalada por el parámetro buf de la unión semun. 
        IPC_SET: establece el valor del miembro ipc_perm de la estructura semid_ds de un determinado conjunto de semáforos. El valor se toma del parámetro buf de la unión semun. 
        IPC_RMID: el kernel elimina la colección de semáforos. 
        GETVAL: Devuelve el valor de un semáforo de la colección. 
        SETVAL: establece el valor de un único semáforo en la colección al valor del miembro val de la unión. 
        
        Los dos valores de cmd comúnmente utilizados son: 
            SETVAL: inicializa el valor del semáforo semnum-ésimo en arg.val; 
            IPC_RMID: elimina el semáforo. 
            Generalmente, significa establecer el valor inicial y eliminar el semáforo. El cmd anterior define muchas operaciones diferentes. Puede verificarlo cuando lo use (busque en línea o use man para ver el manual del kernel). Valor de retorno: Éxito: IPC_STAT, IPC_SETVAL o operación IPC_RMID: 0 
    , operación IPC_GETVAL: devuelve el valor del semáforo actual; falla: devuelve -1. 
*/

Operación de semáforo (operación P/V, cambiando el valor del semáforo):

/* Una estructura mantenida en el kernel, utilizada para describir qué tipo de operación se realiza en un determinado semáforo */ 
struct sembuf 
{ 
    unsigned short sem_num; /* número de semáforo */ 
    short sem_op; /* operación de semáforo */ 
    short sem_flg; / * indicadores de operación */ 
} 
/* 
    sem_num: qué semáforo en el conjunto de semáforos, comenzando desde 0 
    sem_op: indica la operación del semáforo (operación P o operación V). Si su valor es positivo, el valor se suma al contenido de la señal existente. Generalmente se usa para liberar el derecho a usar el recurso controlado; si el valor de sem_op es un número negativo y su valor absoluto es mayor que el valor actual de la señal, la operación se bloqueará hasta que el valor de la señal sea mayor o igual al valor absoluto de sem_op. Generalmente se utiliza para obtener el derecho a utilizar recursos. 
        El valor -1 es la operación P y el valor 1 es la operación V. 
    sem_flg: indicador de operación de señal, que tiene dos valores: IPC_NOWAIT y SEM_UNDO: IPC_NOWAIT 
        : cuando no se puede satisfacer la operación del semáforo, semop() no se bloqueará, pero regresará inmediatamente y establecerá el mensaje de error; 
        SEM_UNDO: programa Cuando finalice (ya sea normal o anormal), se garantiza que se establecerá el valor de la señal; al mismo tiempo, si el recurso no se libera cuando finaliza el proceso, el sistema lo liberará automáticamente, generalmente SEM_UNDO, lo que significa que cuando el proceso que llamó al semáforo sale, se restaurará la respuesta correspondiente.El 
            valor de conteo del semáforo, 
            . Sume 1; cuando el proceso no sale, el semáforo se convierte en 21; cuando el proceso sale, el valor del semáforo vuelve a 20
*/ 
​int
semop(int semid, struct sembuf *sops, unsigned nsops); 
int semtimedop(int semid, struct sembuf *sops, unsigned nsops,struct timespec *timeout); 
/* 
    semid: número de colección de semáforos 
    sops: por realizar Para operar, primero complete la estructura struct sembuf y luego pase su dirección, puede pasar una matriz (parámetro formal struct sembuf sops []) 
    nsops: indica el número de semáforos a operar. El parámetro sops puede pasar una matriz y nsops representa el número de sops. Un sops corresponde a la operación de un semáforo, 
        por lo que se pueden operar varios semáforos en una colección al mismo tiempo. 
    Valor de retorno: se devuelve 0 en caso de éxito, - 1 en caso de fracaso. 
*/ 
​/
* Ejemplo, operar en un semáforo V, es decir, semáforo +1 */ 
struct sembuf sops_v = {0, +1, SEM_UNDO}; // Agrega un semop al semáforo con valor de índice 0 
(semid, &sops , 1); // La función anterior se ejecuta una vez

manual:

Proceso 1 (sem):

①Llame a semget para crear un semáforo;

②Llame a SETVAL de semctl para establecer un valor inicial para el semáforo;

③Llame a semop para realizar operaciones P y V

Proceso 2 (sem2):

①Llame a semget para obtener el identificador semi del semáforo existente;

②Llame a semop para realizar la operación P y la operación V.

(Nota: si otro proceso también usa el semáforo y realiza una operación P para hacer que el valor del semáforo sea -1, cuando este proceso realice la operación P, se bloqueará y esperará hasta que otro proceso ingrese a la ciudad para realizar la operación V. +1 liberar recursos)

Ejemplo: consulte el ejemplo dado en el blog de Qiuooooooo: blog CSDN semáforo ipc , una explicación detallada de los semáforos IPC .

Cola de mensajes

Una cola de mensajes es una lista vinculada de mensajes, almacenada en el kernel e identificada mediante un identificador de cola de mensajes. Las colas de mensajes superan las deficiencias de una menor transmisión de información de señales, las canalizaciones solo pueden transportar flujos de bytes sin formato y tamaños de búfer limitados. La lista de mensajes se almacena en el kernel y cada cola de mensajes se identifica mediante un identificador de cola de mensajes; a diferencia de las canalizaciones, las colas de mensajes se almacenan en el kernel y una cola de mensajes solo se puede eliminar cuando se reinicia el kernel; el tamaño del La cola de mensajes es limitada.

Las colas de mensajes tienen un formato específico y una prioridad específica. Un proceso con permisos de escritura puede agregar nuevos mensajes a la cola de mensajes; un proceso con permisos de lectura puede leer mensajes de la cola de mensajes.

La cola de mensajes se utiliza para la comunicación entre procesos que se ejecutan en la misma máquina. Es muy similar a una tubería. De hecho, es un método de comunicación que se está eliminando gradualmente. Podemos reemplazarlo con una tubería de flujo o un socket .

No mencionaré el uso aquí, solo compruébalo cuando lo uses.

Memoria compartida

Posiblemente el método más útil de comunicación entre procesos y la forma más rápida de IPC. La memoria compartida es el método IPC más rápido y está especialmente diseñada para la ineficiencia de otros métodos de comunicación entre procesos.

Por lo general, un proceso crea un área de memoria compartida y otros procesos leen y escriben en esta área de memoria. La memoria compartida consiste en mapear una sección de la memoria a la que pueden acceder otros procesos. Esta memoria compartida es creada por un proceso, pero a la que pueden acceder varios procesos (siempre que las tablas de páginas de los dos procesos se modifiquen para que su virtual direcciones asignadas a la misma página física se pueden compartir). El proceso Proc A escribe datos en la memoria y el proceso Proc B lee los datos de la memoria. Durante este período, se producen un total de dos copias: Proc A a la memoria compartida y memoria compartida al Proc B. Debido a que opera directamente en la memoria, se comparte y la velocidad de la memoria también aumenta. La forma más común es utilizar la familia de funciones shmXXX para utilizar la memoria compartida para el almacenamiento.

Cuando dos procesos asignan direcciones virtuales a direcciones físicas a través de tablas de páginas, existe un área de memoria común en la dirección física, es decir, memoria compartida, que ambos procesos pueden ver al mismo tiempo. De esta manera, cuando un proceso realiza una operación de escritura y otro proceso realiza una operación de lectura, se puede lograr la comunicación entre procesos.

Sin embargo, debido a que la memoria compartida no proporciona un mecanismo de exclusión mutua correspondiente, la memoria compartida generalmente se usa junto con semáforos, lo que requiere el control del programador. La memoria compartida se utiliza a menudo junto con otros mecanismos de comunicación, como los semáforos, para lograr la sincronización y la comunicación entre procesos. Queremos asegurarnos de que un proceso no se pueda leer mientras se escribe, por lo que utilizamos semáforos para lograr la sincronización y la exclusión mutua.

Crear memoria compartida:

#include <sys/types.h> 
#include <sys/ipc.h> 
#include <sys/shm.h> 
int shmget(key_t key, size_t size, int shmflg); 
/* 
    clave: y el semáforo presentado anteriormente La clave de parámetro de la función semget es la misma, que se usa principalmente para distinguir procesos. Puede usar la función key_t ftok(const char *pathname, int proj_id); para obtener el tamaño: indica el tamaño de la memoria compartida que se 
    aplicará , que generalmente es un múltiplo entero de 4k (es decir, 4096xn bytes). 
    banderas: 
        IPC_CREAT e IPC_EXCL se usan juntas para crear una nueva memoria compartida; de lo contrario, se devuelve -1. para crear otros nuevos. 
        IPC_CREAT devuelve una memoria compartida cuando se usa sola. Si existe, se devolverá directamente. Si no, se creará. Se utiliza para hacer referencia/vincular una clave existente. 
        Igual que semflag de semget(). 
        Por ejemplo: IPC_CREAT|IPC_EXCL|0666 
    Valor de retorno: el ID de la memoria compartida, es decir, shmid, se devuelve correctamente y -1 se devuelve en caso de error. 
*/

Establecer propiedades de memoria compartida:

/* estructura shmid_ds, envía los parámetros del comando de configuración a la memoria compartida */ 
strcut shmid_ds{ 
    struct ipc_perm shm_perm; 
    size_t shm_segsz; 
    time_t shm_atime; 
    time_t shm_dtime; 
    ... 
} 
int
shmctl(int shmid,int cmd,const void* addr); 
/* 
    Los valores comúnmente utilizados de cmd son: 
        (1) IPC_STAT obtiene la estructura shmid_ds de la memoria compartida actual y la guarda en buf 
        (2) IPC_SET usa el valor en buf para configurar la estructura shmid_ds de la memoria compartida actual memoria 
        (3) IPC_RMID Elimina la memoria compartida actual. 
    Cuando cmd es IPC_RMID, se puede usar para eliminar una parte de la memoria compartida, por ejemplo: shmctl(shm_id, IPC_RMID, NULL); 
*/

El punto clave a tener en cuenta al utilizar el almacenamiento compartido para implementar la comunicación entre procesos es la sincronización del acceso a los datos. Debe asegurarse de que cuando un proceso lea datos, los datos que desea ya se hayan escrito. Por lo general, los semáforos se utilizan para sincronizar el acceso a los datos del almacenamiento compartido. Además, esto se puede lograr utilizando la función shmctl para establecer ciertos bits de bandera de la memoria de almacenamiento compartido, como SHM_LOCK, SHM_UNLOCK, etc.

Funciones de gancho/desenganche:

void *shmat(int shm_id,const void *shmaddr,int shmflg); 
/* 
    La función shmat conecta la memoria compartida al espacio de direcciones del proceso a través de shm_id 
    y monta la memoria compartida aplicada en la tabla de páginas del proceso. una Memoria virtual corresponde a una memoria física. 
    shmid: Complete la identificación de la memoria compartida. 
    El usuario puede especificar el parámetro shmaddr para asignar la dirección de la memoria compartida al espacio de proceso. Si shm_addr es 0, el núcleo intenta encontrar un área no asignada. 
        shmaddr suele ser NULL y el sistema selecciona la dirección de la memoria compartida. memoria que se adjuntará. 
    shmflg se puede 
    devolver para SHM_RDONLY Valor: Devuelve la dirección virtual de esta memoria, que el proceso puede leer y escribir. 
    
    Ejemplo de uso: 
        char* mem = (char*)shmat(shm_id, NULL, 0); 
* / 
​int
shmdt(const void *shmaddr); 
/* 
    La función de shmdt es separar la memoria compartida de la tabla de páginas y eliminar la relación de mapeo entre las dos. 
    shmaddr: representa la dirección virtual de esta memoria física. 
    Valor de retorno: -1 en caso de error. 
    
    Uso de ejemplo: shmdt(mem); 
*/

manual:

  1. Utilice ftok() para obtener la clave key_t.

  2. Utilice shmget() para crear/hacer referencia a una parte de la memoria compartida. Luego use shmat() para devolver la dirección de la memoria compartida correspondiente al número shmid.

  3. Simplemente lea y escriba la dirección de la memoria compartida directamente.

  4. Cuando ya no esté en uso, use shmdt() para cancelar el enlace; luego use shmctl() para destruir la memoria compartida.

Para el acceso mutuamente exclusivo a la memoria compartida, puede utilizar un semáforo o un mecanismo de bloqueo. Un método para bloquear el acceso mutuamente exclusivo a la memoria compartida para la comunicación entre procesos: Linux | Cómo bloquear la comunicación entre procesos - Zhihu (zhihu.com ) .

Modo adicional IPC

Mapa de memoria

Es decir, asigne un archivo a una parte de la memoria mediante la función mmap () y luego realice operaciones de lectura y escritura. Después de que ambos procesos asignan el mismo archivo, pueden leer y escribir por separado sin llamar a la API de E/S pero operando directamente la memoria.

Cada proceso que utiliza este mecanismo logra la comunicación entre múltiples procesos asignando el mismo archivo compartido a su propio espacio de direcciones de proceso (esto es similar a la memoria compartida, siempre que un proceso opere en la memoria del archivo asignado, también se pueden ver otros procesos). inmediatamente).

#include <sys/mman.h> 
#include <unistd.h> 
void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset); 
int munmap(void *inicio, tamaño_t longitud);
void *mmap(void*start,size_t length,int prot,int flags,int fd,off_t offset); 
//La función Themmap asigna un archivo u otro objeto a la memoria. El primer parámetro es la dirección inicial del área de mapeo. Establecerlo en 0 significa que el sistema determina la dirección inicial del área de mapeo. El segundo parámetro es la longitud del mapeo. El tercer parámetro es el indicador de protección de memoria esperado. El cuarto parámetro especifica el tipo de objeto mapeado, el quinto parámetro es el descriptor del archivo (indica el archivo que se va a mapear) y el sexto parámetro es el punto de partida del contenido del objeto mapeado. El puntero al área asignada se devuelve correctamente y MAP_FAILED [su valor es (void *)-1] se devuelve en caso de error. 
​int
munmap(void* start,size_t length); 
// La función munmap se utiliza para cancelar la dirección inicial de la memoria asignada apuntada por el parámetro start, y la longitud del parámetro es el tamaño de la memoria que se cancelará. Si la desasignación se realiza correctamente, se devuelve 0; de lo contrario, se devuelve -1 y la causa del error se almacena en el código de error EINVAL en errno. 
​int
msync(void *addr,size_t len,int flags); 
// la función msync realiza la coherencia del contenido del archivo del disco y el contenido de acceso a la memoria compartida, es decir, la sincronización. El primer parámetro es la dirección del archivo asignado al espacio de proceso, el segundo parámetro es el tamaño del espacio asignado y el tercer parámetro es la configuración del parámetro de actualización.

La explicación detallada de la API se puede encontrar en el documento: 【Linux 应用开发】\0-用到的API-收集积累\文件IO、字符流收发和字符串处理相关的API收集积累.c. Se puede encontrar más en línea.

mmap() Explicación detallada de la administración de memoria de Linux - Explicación detallada del principio de mmap - Zhihu (zhihu.com) .

Ejemplo: el ejemplo aquí es asignar un archivo a una parte de la memoria a través de mmap () y luego leer esta memoria. Hay otro proceso que asigna el mismo archivo a través de mmap() y se puede modificar.

#incluir <stdio.h> 
#incluir <unistd.h> 
#incluir <stdlib.h> 
#incluir <sys/types.h> 
#incluir <sys/stat.h> 
#incluir <string.h> 
#incluir <sys /mman.h> 
#include <fcntl.h> 
​int
main(int argc, const char* argv[]) 
{ 
 int fd = open("english.txt", O_RDWR); 
     if(fd == -1){ perror("error de apertura"); salir(1); } 
 // obtiene la longitud del archivo 
 int len ​​= lseek(fd, 0, SEEK_END); 
 vacío * ptr = mmap(NULL, len, PROT_READ | PROT_WRITE, MAP_SHARED, fd,
 // Libera el área de mapeo de memoria 
 int ret = munmap(ptr, len); 
     if(ret == -1){ perror("munmap error"); exit(1); } 
 return 0; 
}

La diferencia entre memoria compartida y archivos mapeados en memoria :

Los archivos mapeados en memoria usan memoria virtual para asignar archivos al espacio de direcciones del proceso. Después de eso, el proceso opera el archivo tal como opera la dirección en el espacio del proceso, como usar funciones de operación de memoria como memcpy en el lenguaje C. Este método se puede aplicar bien en situaciones en las que es necesario procesar un archivo o un archivo grande con frecuencia. Este método de procesamiento de IO es más eficiente que el IO normal.

La memoria compartida es un caso especial de archivos mapeados en memoria, que mapean un bloque de memoria en lugar de un archivo en el disco. El tema de la memoria compartida es el proceso. El sistema operativo asignará un espacio de memoria a cada proceso de forma predeterminada. Cada proceso solo puede acceder a la memoria que le asigna el sistema operativo y no puede acceder a otros procesos. A veces necesitamos acceder a la misma memoria entre diferentes procesos, ¿qué debemos hacer? El sistema operativo proporciona API para crear y acceder a la memoria compartida. Los procesos que necesitan compartir memoria pueden usar este conjunto de API definidas para acceder a la memoria compartida entre múltiples procesos. Acceder a esta memoria por cada proceso es como acceder a una memoria en un disco duro. Los archivos son los mismos.

La diferencia y conexión entre archivos mapeados en memoria y memoria virtual :

Los archivos mapeados en memoria y la memoria virtual son partes importantes de la administración de la memoria del sistema operativo y tienen similitudes y diferencias.

Conexión: la memoria virtual y el mapeo de memoria son mecanismos para cargar parte del contenido en la memoria y colocar otra parte en el disco. Todos son transparentes para los usuarios.

Diferencia: la memoria virtual es parte del disco duro y es el área de intercambio de datos entre la memoria y el disco duro. Durante la ejecución de muchos programas, los datos del programa no utilizados temporalmente se colocan en esta memoria virtual para ahorrar recursos de memoria. El mapeo de memoria es un mapeo de un archivo a un bloque de memoria, de modo que el programa pueda acceder al archivo a través del puntero de memoria.

La base del hardware de la memoria virtual es el mecanismo de paginación. Otra base es el principio de localidad (localidad temporal y localidad espacial), de modo que parte del programa se puede cargar en la memoria y el resto permanece en la memoria externa. Cuando la información a la que se accede no existe, los datos requeridos se pueden almacenar. transferido a la memoria. El archivo mapeado en memoria no es local, pero permite que todo o parte del contenido del disco Silver Snake en un área determinada del espacio de direcciones virtuales acceda al archivo del disco mapeado a través de esta área sin la necesidad de E/S de archivos y no es necesario. El contenido del archivo se almacena en el búfer.

Enchufe

Al crear un Socket, seleccione el ámbito de uso dentro del sistema (y seleccione Ethernet para la comunicación TCP/UDP/IP entre diferentes máquinas).

Un socket tiene tres atributos: dominio, tipo y protocolo, correspondientes a diferentes dominios, y el socket también tiene una dirección como nombre.

El dominio especifica la familia de protocolos utilizada para la comunicación por socket. El dominio más utilizado es AF_INET, que representa sockets de red. El protocolo subyacente es el protocolo IP. Para los sockets de red, dado que el servidor puede proporcionar múltiples servicios, el cliente debe usar el número de puerto IP para especificar un servicio específico. AF_UNIX significa socket local, implementado utilizando el sistema de archivos Unix/Linux.

El protocolo IP proporciona dos métodos de comunicación: flujos y datagramas (correspondientes al protocolo TCP y al protocolo UDP respectivamente), y los tipos de sockets correspondientes son sockets de flujo y sockets de datagramas, respectivamente. Los sockets de transmisión (SOCK_STREAM) se utilizan para proporcionar servicios de transmisión de datos confiables y orientados a la conexión. Este servicio garantiza que los datos se envíen sin errores, sin duplicados y se reciban en orden. Los sockets de transmisión utilizan el protocolo TCP. Los sockets de datagramas (SOCK_DGRAM) proporcionan un servicio sin conexión. Este servicio no garantiza la confiabilidad de la transmisión de datos. Los datos pueden perderse o duplicarse durante la transmisión y no se puede garantizar que los datos se reciban en secuencia. Los sockets de datagramas utilizan el protocolo UDP.

#include <sys/types.h> 
#include <sys/socket.h> 
​int
socket(dominio it,tipo int,protocal int); 
int bind(int socket,const struct sockaddr *dirección,size_t dirección_len); 
int escucha(int socket,int backlog); 
int aceptar(int socket,struct sockaddr *dirección,size_t *dirección_len); 
int connect(int socket,const struct sockaddr *addrsss,size_t address_len); 
​ssize_t
send(int sockfd, const void *buf, size_t len, int flags); 
ssize_t recv(int sockfd, void *buf, size_t len, int flags); 
ssize_t recvfrom(int sockfd, void *buf, size_t len, int flags, struct sockaddr *src_addr, socklen_t *addrlen);
ssize_t sendto(int sockfd, const void *buf, size_t len, int flags, const struct sockaddr *dest_addr, socklen_t addrlen);

Consulte 【Linux 应用开发】\3-Socket编程\el contenido del interior. Se puede encontrar más en línea.

Supongo que te gusta

Origin blog.csdn.net/Staokgo/article/details/132630719
Recomendado
Clasificación