Comunicación IPC entre procesos de programación del sistema Linux.






Prefacio

        Este artículo registra algunas de mis notas sobre la comunicación entre procesos de aprendizaje y registra brevemente las seis formas de comunicación de procesos.
      




1. Comunicación entre procesos

  La comunicación entre procesos (IPC, InterProcess Communication) se refiere a la difusión o intercambio de información entre diferentes procesos. Los métodos IPC generalmente incluyen canalizaciones (incluidas canalizaciones sin nombre y canalizaciones con nombre), colas de mensajes, semáforos, almacenamiento compartido, sockets, flujos, etc. Entre ellos, Socket y Streams admiten dos procesos IPC en diferentes hosts.

        Cada proceso tiene un espacio de direcciones de usuario diferente. Las variables globales de cualquier proceso no se pueden ver en otro proceso. Por lo tanto, el intercambio de datos entre procesos debe pasar por el kernel. Se abre un buffer en el kernel. El proceso A transfiere los datos Copiar del usuario espacio al búfer del kernel y el proceso B lee los datos del búfer del kernel. Este mecanismo proporcionado por el kernel se llama comunicación entre procesos.

          La naturaleza de la comunicación entre diferentes procesos: Se puede ver un recurso común entre procesos, pero la forma o el proveedor de este recurso es diferente, lo que resulta en diferentes métodos de comunicación.

2. Tubería

        Generalmente se refiere a una canalización sin nombre, que es la forma más antigua de IPC en los sistemas UNIX.

1. Características:

  1.  Es semidúplex (es decir, los datos sólo pueden fluir en una dirección), con extremos fijos de lectura y escritura.

  2. Solo se puede utilizar para la comunicación entre procesos que tienen relaciones de afinidad (también entre procesos padre-hijo o procesos hermanos).

  3. Puede considerarse como un archivo especial y también se pueden utilizar funciones ordinarias de lectura, escritura y otras para leerlo y escribirlo. Pero no es un archivo normal, no pertenece a ningún otro sistema de archivos y sólo existe en la memoria.

2. Prototipo: la canalización se crea llamando a la función de canalización.

#include <unistd.h>
int pipe (int fd[2]);
//返回:成功返回0,出错返回-1     




  El parámetro fd devuelve dos descriptores de archivo: fd[0] apunta al final de lectura del proceso principal y fd[1] apunta al final de escritura del proceso principal.

                                                fd[1] apunta al final de escritura del proceso hijo y fd[0] apunta al final de lectura del proceso hijo.

3. Cómo las tuberías implementan la comunicación entre procesos

        Las tuberías dentro de un solo proceso son de poca utilidad. Por lo tanto, normalmente el proceso que llama a la tubería luego llama a la bifurcación, creando así un canal IPC entre el proceso padre y el proceso hijo. Como se muestra abajo:

   (1) El proceso principal crea una tubería y obtiene dos descriptores de archivo que apuntan a ambos extremos de la tubería.

   (2) El proceso padre bifurca el proceso hijo, y el proceso hijo también tiene dos descriptores de archivo que apuntan a la misma tubería.

   (3) El proceso padre cierra fd [0] y el proceso hijo cierra fd [1], es decir, el proceso padre cierra el extremo de lectura de la tubería y el proceso hijo cierra el extremo de escritura de la tubería (porque la tubería solo admite unidireccional). comunicación). El proceso padre puede escribir en la tubería y el proceso hijo puede leer desde la tubería. La tubería se implementa mediante una cola circular y los datos fluyen desde el extremo de escritura y salen desde el extremo de lectura, logrando así la comunicación entre procesos.

#include<stdio.h>
#include<unistd.h>

int main()
{
	int fd[2];  // 两个文件描述符
	pid_t pid;
	char buff[20];

	if(pipe(fd) < 0)  // 创建管道
		printf("Create Pipe Error!\n");

	if((pid = fork()) < 0)  // 创建子进程
		printf("Fork Error!\n");
	else if(pid > 0)  // 父进程
	{
		close(fd[0]); // 关闭读端
		write(fd[1], "hello world\n", 12);
	}
	else
	{
		close(fd[1]); // 关闭写端
		read(fd[0], buff, 20);
		printf("%s", buff);
	}

	return 0;
}

4. Cuatro situaciones en las que las tuberías leen datos.

        1. El final de la lectura no lee y el final de la escritura sigue escribiendo.

        2. El que escribe no escribe, pero el que lee sigue leyendo.

        3. El lado de lectura continúa leyendo y fd [0] permanece abierto, mientras que el lado de escritura escribe parte de los datos, deja de escribir y cierra fd [1].

        4. El lado de lectura lee parte de los datos, deja de leer y cierra fd[0], el lado de escritura sigue escribiendo y f[1] permanece abierto.

  3. FIFO

        FIFO , también conocido como canalización con nombre, es un tipo de archivo.

1. Características

  1. FIFO puede intercambiar datos entre procesos no relacionados, a diferencia de las canalizaciones sin nombre.

  2. Un FIFO tiene un nombre de ruta asociado y existe en el sistema de archivos como un archivo de dispositivo especial.

2. Prototipo


#include <sys/stat.h>
//返回值:成功返回0,出错返回-1
int mkfifo(const char *pathname, mode_t mode);

        El parámetro de modo openes el mismo que el modo en la función. Una vez que se crea un FIFO, se puede manipular usando funciones normales de E/S de archivos.

        Al abrir un FIFO, O_NONBLOCKla diferencia es si la bandera de no bloqueo ( ) está configurada:

  • Si no se especifica O_NONBLOCK(el valor predeterminado), una apertura de solo lectura se bloquea hasta que algún otro proceso abra el FIFO para escribir. De manera similar, una apertura de solo escritura se bloquea hasta que algún otro proceso la abre para lectura.

  • Si se especifica O_NONBLOCK, la apertura de solo lectura regresa inmediatamente. Una apertura de solo escritura devolverá -1 en caso de error. Si ningún proceso ha abierto el FIFO para lectura, su error se establece en ENXIO.

3. Ejemplo

        El método de comunicación de FIFO es similar al uso de archivos para transmitir datos en un proceso, excepto que los archivos de tipo FIFO también tienen las características de tuberías. Cuando se leen los datos, los datos se borran de la canalización FIFO al mismo tiempo y son "el primero en entrar, el primero en salir". El siguiente ejemplo demuestra el proceso de uso de FIFO para IPC:

escribir_fifo.c

#include<stdio.h>
#include<stdlib.h>   // exit
#include<fcntl.h>    // O_WRONLY
#include<sys/stat.h>
#include<time.h>     // time

int main()
{
	int fd;
	int n, i;
	char buf[1024];
	time_t tp;

	printf("I am %d process.\n", getpid()); // 说明进程ID
	
	if((fd = open("fifo1", O_WRONLY)) < 0) // 以写打开一个FIFO 
	{
		perror("Open FIFO Failed");
		exit(1);
	}

	for(i=0; i<10; ++i)
	{
		time(&tp);  // 取系统当前时间
		n=sprintf(buf,"Process %d's time is %s",getpid(),ctime(&tp));
		printf("Send message: %s", buf); // 打印
		if(write(fd, buf, n+1) < 0)  // 写入到FIFO中
		{
			perror("Write FIFO Failed");
			close(fd);
			exit(1);
		}
		sleep(1);  // 休眠1秒
	}

	close(fd);  // 关闭FIFO文件
	return 0;
}

leer_fifo.c

#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<fcntl.h>
#include<sys/stat.h>

int main()
{
	int fd;
	int len;
	char buf[1024];

	if(mkfifo("fifo1", 0666) < 0 && errno!=EEXIST) // 创建FIFO管道
		perror("Create FIFO Failed");

	if((fd = open("fifo1", O_RDONLY)) < 0)  // 以读打开FIFO
	{
		perror("Open FIFO Failed");
		exit(1);
	}
	
	while((len = read(fd, buf, 1024)) > 0) // 读取FIFO管道
		printf("Read message: %s", buf);

	close(fd);  // 关闭FIFO文件
	return 0;
}

Utilice gcc para compilar y ejecutar los dos archivos anteriores en dos terminales. Puede ver el resultado de la siguiente manera:

 1 [cheesezh@localhost]$ ./write_fifo 
 2 I am 5954 process.
 3 Send message: Process 5954's time is Mon Apr 20 12:37:28 2015
 4 Send message: Process 5954's time is Mon Apr 20 12:37:29 2015
 5 Send message: Process 5954's time is Mon Apr 20 12:37:30 2015
 6 Send message: Process 5954's time is Mon Apr 20 12:37:31 2015
 7 Send message: Process 5954's time is Mon Apr 20 12:37:32 2015
 8 Send message: Process 5954's time is Mon Apr 20 12:37:33 2015
 9 Send message: Process 5954's time is Mon Apr 20 12:37:34 2015
10 Send message: Process 5954's time is Mon Apr 20 12:37:35 2015
11 Send message: Process 5954's time is Mon Apr 20 12:37:36 2015
12 Send message: Process 5954's time is Mon Apr 20 12:37:37 2015
 1 [cheesezh@localhost]$ ./read_fifo 
 2 Read message: Process 5954's time is Mon Apr 20 12:37:28 2015
 3 Read message: Process 5954's time is Mon Apr 20 12:37:29 2015
 4 Read message: Process 5954's time is Mon Apr 20 12:37:30 2015
 5 Read message: Process 5954's time is Mon Apr 20 12:37:31 2015
 6 Read message: Process 5954's time is Mon Apr 20 12:37:32 2015
 7 Read message: Process 5954's time is Mon Apr 20 12:37:33 2015
 8 Read message: Process 5954's time is Mon Apr 20 12:37:34 2015
 9 Read message: Process 5954's time is Mon Apr 20 12:37:35 2015
10 Read message: Process 5954's time is Mon Apr 20 12:37:36 2015
11 Read message: Process 5954's time is Mon Apr 20 12:37:37 2015

        El ejemplo anterior se puede extender a un ejemplo de comunicación de proceso cliente-servidor. write_fifoSu función es similar a la de un cliente. Puede abrir múltiples clientes para enviar información de solicitud a un servidor. read_fifoSimilar a un servidor, monitorea el extremo de lectura. FIFO de manera oportuna. Cuando hay datos, se leen y procesan al mismo tiempo, pero una cuestión clave es que cada cliente debe conocer de antemano la interfaz FIFO proporcionada por el servidor. La siguiente figura muestra esta disposición:

 3. Cola de mensajes

1. Características

  1. Las colas de mensajes están orientadas a registros, donde los mensajes tienen un formato específico y una prioridad específica.

  2. La cola de mensajes es independiente de los procesos de envío y recepción. Cuando finaliza el proceso, la cola de mensajes y su contenido no se eliminan.

  3. La cola de mensajes puede realizar consultas aleatorias de mensajes. Los mensajes no necesitan leerse en orden de primero en entrar, primero en salir, sino que también se pueden leer según el tipo de mensaje.

  4. La cola de mensajes es independiente de los procesos de envío y recepción. Cuando finaliza el proceso, la cola de mensajes y su contenido no se eliminarán.

2. Prototipo

#include <sys/msg.h>
// 创建或打开消息队列:成功返回队列ID,失败返回-1
int msgget(key_t key, int flag);
// 添加消息:成功返回0,失败返回-1
int msgsnd(int msqid, const void *ptr, size_t size, int flag);
// 读取消息:成功返回消息数据的长度,失败返回-1
int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
// 控制消息队列:成功返回0,失败返回-1
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

msggetSe creará una nueva cola de mensajes en las dos situaciones siguientes :

  • Si no hay una cola de mensajes correspondiente a la clave de valor clave y el indicador contiene IPC_CREATel bit de indicador, el ID de la cola se devuelve correctamente y -1 en caso de error.
  • El parámetro clave es IPC_PRIVATE.

Cuando la función msgrcvlee la cola de mensajes, el parámetro de tipo tiene las siguientes situaciones:

  • type == 0, devuelve el primer mensaje de la cola;
  • type > 0, devuelve el primer mensaje de la cola con tipo de mensaje;
  • type < 0, devuelve los mensajes en la cola cuyo valor de tipo de mensaje es menor o igual que el valor absoluto del tipo. Si hay varios mensajes, se toma el mensaje con el valor de tipo más pequeño.

        Se puede ver que cuando el valor del tipo no es 0, se usa para leer mensajes en orden que no sea primero en entrar, primero en salir. El tipo también puede considerarse como el peso de la prioridad.

3. Ejemplo

        A continuación se describe un ejemplo simple del uso de la cola de mensajes para IPC. El programa del servidor ha estado esperando un tipo específico de mensaje. Después de recibir este tipo de mensaje, envía otro tipo específico de mensaje como respuesta y el cliente lee el mensaje. Dé su opinión e imprímala.

msg_server.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
	long mtype;
	char mtext[256];
};

int main()
{
	int msqid;
	key_t key;
	struct msg_form msg;
	
	// 获取key值
	if((key = ftok(MSG_FILE,'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 打印key值
	printf("Message Queue - Server key is: %d.\n", key);

	// 创建消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 打印消息队列ID及进程ID
	printf("My msqid is: %d.\n", msqid);
	printf("My pid is: %d.\n", getpid());

	// 循环读取消息
	for(;;) 
	{
		msgrcv(msqid, &msg, 256, 888, 0);// 返回类型为888的第一个消息
		printf("Server: receive msg.mtext is: %s.\n", msg.mtext);
		printf("Server: receive msg.mtype is: %d.\n", msg.mtype);

		msg.mtype = 999; // 客户端接收的消息类型
		sprintf(msg.mtext, "hello, I'm server %d", getpid());
		msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
	}
	return 0;
}

msg_client.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/msg.h>

// 用于创建一个唯一的key
#define MSG_FILE "/etc/passwd"

// 消息结构
struct msg_form {
	long mtype;
	char mtext[256];
};

int main()
{
	int msqid;
	key_t key;
	struct msg_form msg;

	// 获取key值
	if ((key = ftok(MSG_FILE, 'z')) < 0) 
	{
		perror("ftok error");
		exit(1);
	}

	// 打印key值
	printf("Message Queue - Client key is: %d.\n", key);

	// 打开消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1) 
	{
		perror("msgget error");
		exit(1);
	}

	// 打印消息队列ID及进程ID
    printf("My msqid is: %d.\n", msqid);
	printf("My pid is: %d.\n", getpid());

	// 添加消息,类型为888
	msg.mtype = 888;
	sprintf(msg.mtext, "hello, I'm client %d", getpid());
	msgsnd(msqid, &msg, sizeof(msg.mtext), 0);

	// 读取类型为999的消息
	msgrcv(msqid, &msg, 256, 999, 0);
	printf("Client: receive msg.mtext is: %s.\n", msg.mtext);
	printf("Client: receive msg.mtype is: %d.\n", msg.mtype);
	return 0;
}

4. Semáforo

        El semáforo es diferente de la estructura IPC ya introducida: es un contador. Los semáforos se utilizan para implementar la exclusión mutua y la sincronización entre procesos, en lugar de almacenar datos de comunicación entre procesos.

1. Características

  1. El semáforo se utiliza para la sincronización entre procesos y para transferir datos entre procesos, es necesario combinarlo con memoria compartida .

  2. El semáforo se basa en la operación PV del sistema operativo, y las operaciones del programa en el semáforo son todas operaciones atómicas.

  3. Cada operación PV en el semáforo no se limita a sumar 1 o restar 1 al valor del semáforo, sino que también puede sumar o restar cualquier número entero positivo.

  4. Admite grupos de semáforos.

2. Prototipo

        El semáforo más simple es una variable que solo puede tomar 0 y 1. Esta es también la forma más común de semáforo, llamada semáforo binario (Binary Semaphore) . Un semáforo que puede tomar múltiples números enteros positivos se llama semáforo universal.

        Todas las funciones de semáforo en Linux operan en una matriz de semáforo general, en lugar de un único semáforo binario.

#include <sys/sem.h>
// 创建或获取一个信号量组:若成功返回信号量集ID,失败返回-1
int semget(key_t key, int num_sems, int sem_flags);
// 对信号量组进行操作,改变信号量的值:成功返回0,失败返回-1
int semop(int semid, struct sembuf semoparray[], size_t numops);  
// 控制信号量的相关信息
int semctl(int semid, int sem_num, int cmd, ...);

        Al crear una nueva colección de semáforos, se debe especificar semgetel número de semáforos en la colección (es decir ), generalmente 1; si se hace referencia a una colección existente, se especificará 0.num_semsnum_sems

En semopla función, sembufla estructura se define de la siguiente manera:

struct sembuf 
{
    short sem_num; // 信号量组中对应的序号,0~sem_nums-1
    short sem_op;  // 信号量值在一次操作中的改变量
    short sem_flg; // IPC_NOWAIT, SEM_UNDO
}

donde sem_op es la cantidad de cambio del semáforo en una operación:

  • Si sem_op > 0, significa que el proceso libera la cantidad correspondiente de recursos y el valor de sem_op se suma al valor del semáforo. Si hay procesos inactivos esperando este semáforo, envuélvalos.

  • Si es así sem_op < 0, solicite el recurso con el valor absoluto de sem_op.

    • Si la cantidad correspondiente de recursos puede satisfacer la solicitud, el valor absoluto de sem_op se resta del valor del semáforo y la función regresa exitosamente.
    • Esta operación es relevante cuando el número correspondiente de recursos no puede satisfacer la solicitud sem_flg.
      • Si se especifica sem_flg IPC_NOWAIT, la función semop devuelve un error EAGAIN.
      • Si no se especifica sem_flg IPC_NOWAIT, el valor semncnt del semáforo aumenta en 1 y luego el proceso se bloquea hasta que ocurre lo siguiente:
        • Cuando la cantidad correspondiente de recursos puede satisfacer la solicitud, el valor semncnt de este semáforo se reduce en 1 y el valor del semáforo se reduce en el valor absoluto de sem_op. Regrese con éxito;
        • El semáforo se elimina y la función smeop devuelve EIDRM cuando ocurre un error;
        • El proceso captura la señal y regresa de la función de procesamiento de señal. En este caso, el valor semncnt del semáforo se reduce en 1 y la función semop devuelve EINTR cuando ocurre un error.
  • Si sem_op == 0, el proceso se bloquea hasta que el valor correspondiente del semáforo sea 0:
    • Cuando el semáforo ya es 0, la función regresa inmediatamente.
    • Si el valor del semáforo no es 0, sem_flgla acción de la función se determina según:
      • Si se especifica sem_flg IPC_NOWAIT, se devolverá un error EAGAIN.
      • Si no se especifica sem_flg IPC_NOWAIT, el valor semncnt del semáforo aumenta en 1 y luego el proceso se bloquea hasta que ocurre lo siguiente:
        • El valor del semáforo es 0, disminuya el valor semzcnt del semáforo en 1 y la función semop regresa correctamente;
        • El semáforo se elimina y la función smeop devuelve EIDRM cuando ocurre un error;
        • El proceso captura la señal y regresa de la función de procesamiento de señal. En este caso, el valor semncnt del semáforo se reduce en 1 y la función semop devuelve EINTR cuando ocurre un error.
  • Hay muchos tipos de comandos en semctllas funciones, aquí hay dos de uso común:
    • SETVAL: Se utiliza para inicializar el semáforo a un valor conocido. El valor requerido se pasa como miembro val de la unión semun. El semáforo debe configurarse antes de utilizarlo por primera vez.
    • IPC_RMID: elimina una colección de semáforos. Si no elimina el semáforo, seguirá existiendo en el sistema incluso si el programa se cierra, lo que puede causar problemas la próxima vez que ejecute el programa, y ​​el semáforo es un recurso limitado.

3. Ejemplo

#include<stdio.h>
#include<stdlib.h>
#include<sys/sem.h>

// 联合体,用于semctl初始化
union semun
{
	int              val; /*for SETVAL*/
	struct semid_ds *buf;
	unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
	union semun tmp;
	tmp.val = value;
	if(semctl(sem_id, 0, SETVAL, tmp) == -1)
	{
		perror("Init Semaphore Error");
		return -1;
	}
	return 0;
}

// P操作:
//	若信号量值为1,获取资源并将信号量值-1 
//	若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0; /*序号*/
	sbuf.sem_op = -1; /*P操作*/
	sbuf.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sbuf, 1) == -1)
	{
		perror("P operation Error");
		return -1;
	}
	return 0;
}

// V操作:
//	释放资源并将信号量值+1
//	如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
	struct sembuf sbuf;
	sbuf.sem_num = 0; /*序号*/
	sbuf.sem_op = 1;  /*V操作*/
	sbuf.sem_flg = SEM_UNDO;

	if(semop(sem_id, &sbuf, 1) == -1)
	{
		perror("V operation Error");
		return -1;
	}
	return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
	union semun tmp;
	if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
	{
		perror("Delete Semaphore Error");
		return -1;
	}
	return 0;
}


int main()
{
	int sem_id;  // 信号量集ID
	key_t key;  
	pid_t pid;

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 创建信号量集,其中只有一个信号量
	if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
	{
		perror("semget error");
		exit(1);
	}

	// 初始化:初值设为0资源被占用
	init_sem(sem_id, 0);

	if((pid = fork()) == -1)
		perror("Fork Error");
	else if(pid == 0) /*子进程*/ 
	{
		sleep(2);
		printf("Process child: pid=%d\n", getpid());
		sem_v(sem_id);  /*释放资源*/
	}
	else  /*父进程*/
	{
		sem_p(sem_id);   /*等待资源*/
		printf("Process father: pid=%d\n", getpid());
		sem_v(sem_id);   /*释放资源*/
		del_sem(sem_id); /*删除信号量集*/
	}
	return 0;
}

        Si el ejemplo anterior no agrega un semáforo, el proceso principal completará la ejecución primero. Aquí se agrega un semáforo para permitir que el proceso principal espere a que el proceso secundario termine de ejecutarse antes de ejecutarlo.

5. Memoria compartida

La memoria compartida se refiere a dos o más procesos que comparten un área de almacenamiento determinada.

Siga aproximadamente estos pasos: crear/abrir memoria, asignar memoria (conexión), datos, liberar memoria (desconectar), borrar compartir

1. Características

  1. La memoria compartida es el tipo más rápido de IPC porque el proceso accede a la memoria directamente.

  2. Debido a que varios procesos pueden operar simultáneamente, se requiere sincronización.

  3. Los semáforos + memoria compartida generalmente se usan juntos y los semáforos se usan para sincronizar el acceso a la memoria compartida.

2. Prototipo

#include <sys/shm.h>
// 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
int shmget(key_t key, size_t size, int flag);
// 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
void *shmat(int shm_id, const void *addr, int flag);
// 断开与共享内存的连接:成功返回0,失败返回-1
int shmdt(void *addr); 
// 控制共享内存的相关信息:成功返回0,失败返回-1
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

Cuando se utiliza shmgetuna función para crear una sección de memoria compartida, se debe especificar su tamaño; y si se hace referencia a una memoria compartida existente, el tamaño se debe especificar como 0.

Cuando se utiliza shmgetuna función para crear una sección de memoria compartida, se debe especificar su tamaño; y si se hace referencia a una memoria compartida existente, el tamaño se debe especificar como 0.

Cuando se crea una sección de memoria compartida, ningún proceso puede acceder a ella. Debe utilizar shmatuna función para conectar la memoria compartida al espacio de direcciones del proceso actual. Una vez que la conexión es exitosa, el objeto del área de memoria compartida se asigna al espacio de direcciones del proceso de llamada y luego se puede acceder a él como un espacio local.

shmdtLa función se utiliza para desconectar shmatla conexión establecida. Tenga en cuenta que esto no elimina la memoria compartida del sistema, simplemente el proceso actual ya no puede acceder a la memoria compartida.

shmctlLa función puede realizar varias operaciones en la memoria compartida y realizar las operaciones correspondientes según el parámetro cmd. El uso común es IPC_RMID(eliminar la memoria compartida del sistema).

3. Ejemplo

En este ejemplo, se utiliza una combinación de [memoria compartida + semáforo + cola de mensajes] para implementar la comunicación entre el proceso del servidor y el proceso del cliente.

  • La memoria compartida se utiliza para transferir datos;
  • Los semáforos se utilizan para la sincronización;
  • La cola de mensajes se utiliza para notificar al servidor que lea después de que el cliente modifica la memoria compartida.

Servidor.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// 初始化信号量
int init_sem(int sem_id, int value)
{
    union semun tmp;
    tmp.val = value;
    if(semctl(sem_id, 0, SETVAL, tmp) == -1)
    {
        perror("Init Semaphore Error");
        return -1;
    }
    return 0;
}

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}

// 删除信号量集
int del_sem(int sem_id)
{
    union semun tmp;
    if(semctl(sem_id, 0, IPC_RMID, tmp) == -1)
    {
        perror("Delete Semaphore Error");
        return -1;
    }
    return 0;
}

// 创建一个信号量集
int creat_sem(key_t key)
{
	int sem_id;
	if((sem_id = semget(key, 1, IPC_CREAT|0666)) == -1)
	{
		perror("semget error");
		exit(-1);
	}
	init_sem(sem_id, 1);  /*初值设为1资源未占用*/
	return sem_id;
}


int main()
{
	key_t key;
	int shmid, semid, msqid;
	char *shm;
	char data[] = "this is server";
	struct shmid_ds buf1;  /*用于删除共享内存*/
	struct msqid_ds buf2;  /*用于删除消息队列*/
	struct msg_form msg;  /*消息队列用于通知对方更新了共享内存*/

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 创建共享内存
	if((shmid = shmget(key, 1024, IPC_CREAT|0666)) == -1)
	{
		perror("Create Shared Memory Error");
		exit(1);
	}

	// 连接共享内存
	shm = (char*)shmat(shmid, 0, 0);
	if((int)shm == -1)
	{
		perror("Attach Shared Memory Error");
		exit(1);
	}


	// 创建消息队列
	if ((msqid = msgget(key, IPC_CREAT|0777)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 创建信号量
	semid = creat_sem(key);
	
	// 读数据
	while(1)
	{
		msgrcv(msqid, &msg, 1, 888, 0); /*读取类型为888的消息*/
		if(msg.mtext == 'q')  /*quit - 跳出循环*/ 
			break;
		if(msg.mtext == 'r')  /*read - 读共享内存*/
		{
			sem_p(semid);
			printf("%s\n",shm);
			sem_v(semid);
		}
	}

	// 断开连接
	shmdt(shm);

    /*删除共享内存、消息队列、信号量*/
	shmctl(shmid, IPC_RMID, &buf1);
	msgctl(msqid, IPC_RMID, &buf2);
	del_sem(semid);
	return 0;
}

Cliente.c

#include<stdio.h>
#include<stdlib.h>
#include<sys/shm.h>  // shared memory
#include<sys/sem.h>  // semaphore
#include<sys/msg.h>  // message queue
#include<string.h>   // memcpy

// 消息队列结构
struct msg_form {
    long mtype;
    char mtext;
};

// 联合体,用于semctl初始化
union semun
{
    int              val; /*for SETVAL*/
    struct semid_ds *buf;
    unsigned short  *array;
};

// P操作:
//  若信号量值为1,获取资源并将信号量值-1 
//  若信号量值为0,进程挂起等待
int sem_p(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = -1; /*P操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("P operation Error");
        return -1;
    }
    return 0;
}

// V操作:
//  释放资源并将信号量值+1
//  如果有进程正在挂起等待,则唤醒它们
int sem_v(int sem_id)
{
    struct sembuf sbuf;
    sbuf.sem_num = 0; /*序号*/
    sbuf.sem_op = 1;  /*V操作*/
    sbuf.sem_flg = SEM_UNDO;

    if(semop(sem_id, &sbuf, 1) == -1)
    {
        perror("V operation Error");
        return -1;
    }
    return 0;
}


int main()
{
	key_t key;
	int shmid, semid, msqid;
	char *shm;
	struct msg_form msg;
	int flag = 1; /*while循环条件*/

	// 获取key值
	if((key = ftok(".", 'z')) < 0)
	{
		perror("ftok error");
		exit(1);
	}

	// 获取共享内存
	if((shmid = shmget(key, 1024, 0)) == -1)
	{
		perror("shmget error");
		exit(1);
	}

	// 连接共享内存
	shm = (char*)shmat(shmid, 0, 0);
	if((int)shm == -1)
	{
		perror("Attach Shared Memory Error");
		exit(1);
	}

	// 创建消息队列
	if ((msqid = msgget(key, 0)) == -1)
	{
		perror("msgget error");
		exit(1);
	}

	// 获取信号量
	if((semid = semget(key, 0, 0)) == -1)
	{
		perror("semget error");
		exit(1);
	}
	
	// 写数据
	printf("***************************************\n");
	printf("*                 IPC                 *\n");
	printf("*    Input r to send data to server.  *\n");
	printf("*    Input q to quit.                 *\n");
	printf("***************************************\n");
	
	while(flag)
	{
		char c;
		printf("Please input command: ");
		scanf("%c", &c);
		switch(c)
		{
			case 'r':
				printf("Data to send: ");
				sem_p(semid);  /*访问资源*/
				scanf("%s", shm);
				sem_v(semid);  /*释放资源*/
				/*清空标准输入缓冲区*/
				while((c=getchar())!='\n' && c!=EOF);
				msg.mtype = 888;  
				msg.mtext = 'r';  /*发送消息通知服务器读数据*/
				msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
				break;
			case 'q':
				msg.mtype = 888;
				msg.mtext = 'q';
				msgsnd(msqid, &msg, sizeof(msg.mtext), 0);
				flag = 0;
				break;
			default:
				printf("Wrong input!\n");
				/*清空标准输入缓冲区*/
				while((c=getchar())!='\n' && c!=EOF);
		}
	}

	// 断开连接
	shmdt(shm);

	return 0;
}

Nota: Cuando scanf()se ingresan caracteres o cadenas, se dejan en el búfer \n, por lo que el búfer de entrada estándar debe borrarse después de cada operación de entrada. Pero como el compilador gcc no lo admite fflush(stdin)(es solo una extensión del estándar C), utilizamos una alternativa:

while((c=getchar())!='\n' && c!=EOF);




Resumir

Resumen de los cinco métodos de comunicación:

1. Tubería: capacidad lenta y limitada, solo los procesos padre e hijo pueden comunicarse.    

2.FIFO: cualquier proceso puede comunicarse, pero la velocidad es lenta.    

3. Cola de mensajes: la capacidad está limitada por el sistema y se debe prestar atención al problema de que los datos no se leyeron la última vez cuando se leyeron por primera vez.    

4. Semáforo: no puede transmitir mensajes complejos y solo puede usarse para sincronización.    

5. Área de memoria compartida: la capacidad se puede controlar fácilmente y la velocidad es rápida, pero se debe mantener la sincronización. Por ejemplo, cuando un proceso está escribiendo, otro proceso debe prestar atención a los problemas de lectura y escritura, lo que equivale a la seguridad de los subprocesos. en subprocesos. Por supuesto, las áreas de memoria compartida también se pueden utilizar para la comunicación entre subprocesos, pero esto no es necesario. Los subprocesos ya comparten una parte de la memoria en el mismo proceso.

Referencia: https://blog.csdn.net/skyroben/article/details/71513385

        http://songlee24.github.io/2015/04/21/linux-IPC/

Supongo que te gusta

Origin blog.csdn.net/qq_44848795/article/details/122016633
Recomendado
Clasificación