Comunicação IPC entre processos de programação do sistema Linux






Prefácio

        Este artigo registra algumas de minhas notas sobre a comunicação entre processos de aprendizagem e registra brevemente as seis formas de comunicação de processos.
      




1. Comunicação entre processos

  Comunicação entre processos (IPC, Comunicação entre processos) refere-se à disseminação ou troca de informações entre diferentes processos. Os métodos IPC geralmente incluem pipes (incluindo pipes não nomeados e pipes nomeados), filas de mensagens, semáforos, armazenamento compartilhado, soquetes, fluxos, etc. Entre eles, Socket e Streams suportam dois processos IPC em hosts diferentes.

        Cada processo possui um espaço de endereço de usuário diferente. As variáveis ​​globais de qualquer processo não podem ser vistas em outro processo. Portanto, a troca de dados entre processos deve passar pelo kernel. Um buffer é aberto no kernel. O processo A transfere os dados Cópia do usuário espaço para o buffer do kernel, e o processo B lê os dados do buffer do kernel.Este mecanismo fornecido pelo kernel é chamado de comunicação entre processos.

          A natureza da comunicação entre diferentes processos: Um recurso comum pode ser visto entre processos; mas a forma ou fornecedor deste recurso é diferente, resultando em diferentes métodos de comunicação.

2. Gasoduto

        Geralmente se refere a um pipe sem nome, que é a forma mais antiga de IPC em sistemas UNIX.

1. Recursos:

  1.  É half-duplex (ou seja, os dados só podem fluir em uma direção), com extremidades fixas de leitura e gravação.

  2. Ele só pode ser usado para comunicação entre processos que possuem relacionamentos de afinidade (também entre processos pai-filho ou processos irmãos).

  3. Ele pode ser considerado um arquivo especial, e funções comuns de leitura, gravação e outras funções também podem ser usadas para lê-lo e gravá-lo. Mas não é um arquivo comum, não pertence a nenhum outro sistema de arquivos e só existe na memória.

2. Protótipo: O pipeline é criado chamando a função pipe

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




  O parâmetro fd retorna dois descritores de arquivo: fd[0] aponta para o final de leitura do processo pai e fd[1] aponta para o final de gravação do processo pai.

                                                fd[1] aponta para o final de gravação do processo filho e fd[0] aponta para o final de leitura do processo filho.

3. Como os pipes implementam a comunicação entre processos

        Pipes dentro de um único processo são de pouca utilidade. Portanto, normalmente o pipe de chamada do processo chama fork, criando assim um canal IPC entre o processo pai e o processo filho. Como mostrado abaixo:

   (1) O processo pai cria um canal e obtém dois descritores de arquivo apontando para ambas as extremidades do canal.

   (2) O processo pai bifurca o processo filho, e o processo filho também possui dois descritores de arquivo apontando para o mesmo canal.

   (3) O processo pai fecha fd[0] e o processo filho fecha fd[1], ou seja, o processo pai fecha a extremidade de leitura do pipe e o processo filho fecha a extremidade de escrita do pipe (porque o pipe só suporta unidirecional comunicação). O processo pai pode gravar no canal e o processo filho pode ler no canal. O canal é implementado usando uma fila circular e os dados fluem da extremidade de gravação e saem da extremidade de leitura, realizando assim a comunicação entre processos.

#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. Quatro situações em que os pipes leem dados

        1. O final da leitura não lê e o final da escrita continua escrevendo.

        2. O lado da escrita não escreve, mas o lado da leitura continua lendo.

        3. O final de leitura continua lendo e fd[0] permanece aberto, enquanto o final de escrita grava parte dos dados e para de escrever e fecha fd[1].

        4. A extremidade de leitura lê parte dos dados, interrompe a leitura e fecha fd[0], a extremidade de escrita continua escrevendo e f[1] permanece aberta.

  3. FIFO

        FIFO , também conhecido como pipe nomeado, é um tipo de arquivo.

1. Recursos

  1. O FIFO pode trocar dados entre processos não relacionados, diferentemente de pipes não nomeados.

  2. Um FIFO possui um nome de caminho associado e existe no sistema de arquivos como um arquivo de dispositivo especial.

2. Protótipo


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

        O parâmetro mode opené igual ao modo na função. Depois que um FIFO é criado, ele pode ser manipulado usando funções normais de E/S de arquivo.

        Ao abrir um FIFO, O_NONBLOCKa diferença é se o sinalizador de não bloqueio ( ) está definido:

  • Se não for especificado O_NONBLOCK(o padrão), um open somente leitura será bloqueado até que algum outro processo abra o FIFO para gravação. Da mesma forma, um open somente gravação é bloqueado até que algum outro processo o abra para leitura.

  • Se especificado O_NONBLOCK, a abertura somente leitura retornará imediatamente. Uma abertura somente gravação retornará -1 em caso de erro. Se nenhum processo abriu o FIFO para leitura, seu errno será definido como ENXIO.

3. Exemplo

        O método de comunicação FIFO é semelhante ao uso de arquivos para transmitir dados em um processo, exceto que os arquivos do tipo FIFO também possuem características de pipes. Quando os dados são lidos, os dados são apagados do pipeline FIFO ao mesmo tempo e são "primeiro a entrar, primeiro a sair". O exemplo a seguir demonstra o processo de utilização do FIFO para IPC:

write_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;
}

read_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;
}

Use gcc para compilar e executar os dois arquivos acima em dois terminais. Você pode ver a saída da seguinte forma:

 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

        O exemplo acima pode ser estendido para um exemplo de comunicação processo cliente-servidor. write_fifoSua função é semelhante à de um cliente. Ele pode abrir vários clientes para enviar informações de solicitação a um servidor. read_fifoSemelhante a um servidor, ele monitora o final da leitura do FIFO em tempo hábil. Quando há dados Ao mesmo tempo, eles são lidos e processados, mas uma questão importante é que cada cliente deve conhecer antecipadamente a interface FIFO fornecida pelo servidor. A figura a seguir mostra esse arranjo:

 3. Fila de mensagens

1. Recursos

  1. As filas de mensagens são orientadas a registros, onde as mensagens têm um formato específico e uma prioridade específica.

  2. A fila de mensagens é independente dos processos de envio e recebimento. Quando o processo termina, a fila de mensagens e seu conteúdo não são excluídos.

  3. A fila de mensagens pode realizar consultas aleatórias de mensagens. As mensagens não precisam ser lidas na ordem de primeiro a entrar, primeiro a sair, mas também podem ser lidas de acordo com o tipo de mensagem.

  4. A fila de mensagens é independente dos processos de envio e recebimento. Quando o processo for encerrado, a fila de mensagens e seu conteúdo não serão excluídos.

2. Protótipo

#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);

msggetUma nova fila de mensagens será criada nas duas situações a seguir :

  • Se não houver fila de mensagens correspondente à chave-valor e o sinalizador contiver IPC_CREATo bit do sinalizador, o ID da fila será retornado com êxito e -1 será retornado em caso de falha.
  • O parâmetro chave é IPC_PRIVATE.

Quando a função msgrcvlê a fila de mensagens, o parâmetro type apresenta as seguintes situações:

  • type == 0, retorna a primeira mensagem da fila;
  • type > 0, retorna a primeira mensagem na fila com o tipo de mensagem type;
  • type < 0, retorna as mensagens na fila cujo valor do tipo de mensagem é menor ou igual ao valor absoluto do tipo. Se houver várias mensagens, a mensagem com o menor valor de tipo será obtida.

        Pode-se observar que quando o valor do tipo é diferente de 0, ele é usado para ler mensagens na ordem não primeiro a entrar, primeiro a sair. O tipo também pode ser considerado como o peso da prioridade.

3. Exemplo

        Um exemplo simples de uso da fila de mensagens para IPC está escrito abaixo: O programa servidor está aguardando um tipo específico de mensagem. Após receber esse tipo de mensagem, ele envia outro tipo específico de mensagem como feedback e o cliente lê a mensagem. Dê feedback e imprima.

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

        O semáforo é diferente da estrutura IPC já introduzida: é um contador. Os semáforos são usados ​​para implementar exclusão mútua e sincronização entre processos, em vez de armazenar dados de comunicação entre processos.

1. Recursos

  1. O semáforo é usado para sincronização entre processos. Para transferir dados entre processos, ele precisa ser combinado com memória compartilhada .

  2. O semáforo é baseado na operação PV do sistema operacional, e as operações do programa no semáforo são todas operações atômicas.

  3. Cada operação PV no semáforo não se limita a adicionar 1 ou subtrair 1 ao valor do semáforo, mas também pode adicionar ou subtrair qualquer número inteiro positivo.

  4. Suporta grupos de semáforos.

2. Protótipo

        O semáforo mais simples é uma variável que só pode receber 0 e 1. Essa também é a forma mais comum de semáforo, chamada de semáforo binário (Binary Semaphore) . Um semáforo que pode receber vários números inteiros positivos é chamado de semáforo universal.

        Todas as funções de semáforo no Linux operam em uma matriz de semáforo geral, em vez de em um único semáforo binário.

#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, ...);

        Ao semgetcriar uma nova coleção de semáforos, o número de semáforos na coleção (ou seja, num_sems) deve ser especificado, geralmente 1; se uma coleção existente for referenciada, num_sems0 será especificado.

Na semopfunção, sembufa estrutura é definida da seguinte forma:

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

onde sem_op é a quantidade de alteração do semáforo em uma operação:

  • Se sem_op > 0, significa que o processo libera a quantidade correspondente de recursos e o valor de sem_op é adicionado ao valor do semáforo. Se houver processos dormindo esperando por esse semáforo, envolva-os.

  • Se sim sem_op < 0, solicite o recurso com o valor absoluto de sem_op.

    • Se o número correspondente de recursos puder satisfazer a solicitação, o valor absoluto de sem_op será subtraído do valor do semáforo e a função retornará com sucesso.
    • Esta operação é relevante quando o número correspondente de recursos não consegue satisfazer a solicitação sem_flg.
      • Se sem_flg for especificado IPC_NOWAIT, a função semop retornará um erro EAGAIN.
      • Se sem_flg não for especificado IPC_NOWAIT, o valor semncnt do semáforo será aumentado em 1 e o processo será interrompido até que ocorra o seguinte:
        • Quando o número correspondente de recursos pode satisfazer a solicitação, o valor semântico deste semáforo é reduzido em 1 e o valor do semáforo é reduzido pelo valor absoluto de sem_op. Retorno com sucesso;
        • O semáforo é excluído e a função smeop retorna EIDRM quando ocorre um erro;
        • O processo captura o sinal e retorna da função de processamento de sinal, neste caso o valor semncnt do semáforo é reduzido em 1, e a função semop retorna EINTR quando ocorre um erro.
  • Se sem_op == 0, o processo é bloqueado até que o valor correspondente do semáforo seja 0:
    • Quando o semáforo já é 0, a função retorna imediatamente.
    • Se o valor do semáforo não for 0, sem_flga ação da função é determinada de acordo com:
      • Se sem_flg for especificado IPC_NOWAIT, um erro será retornado EAGAIN.
      • Se sem_flg não for especificado IPC_NOWAIT, o valor semncnt do semáforo será aumentado em 1 e o processo será interrompido até que ocorra o seguinte:
        • O valor do semáforo é 0, diminua o valor semzcnt do semáforo em 1 e a função semop retorna com sucesso;
        • O semáforo é excluído e a função smeop retorna EIDRM quando ocorre um erro;
        • O processo captura o sinal e retorna da função de processamento de sinal, neste caso o valor semncnt do semáforo é reduzido em 1, e a função semop retorna EINTR quando ocorre um erro.
  • Existem muitos tipos de comandos em semctlfunções. Aqui estão dois dos mais usados:
    • SETVAL: Usado para inicializar o semáforo com um valor conhecido. O valor necessário é passado como o membro val do sindicato semun. O semáforo precisa ser configurado antes de ser usado pela primeira vez.
    • IPC_RMID: exclua uma coleção de semáforos. Se você não excluir o semáforo, ele continuará a existir no sistema mesmo que o programa tenha sido encerrado, o que pode causar problemas na próxima vez que você executar o programa, e o semáforo é um recurso limitado.

3. Exemplo

#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;
}

        Se o exemplo acima não adicionar um semáforo, o processo pai concluirá a execução primeiro. Um semáforo é adicionado aqui para permitir que o processo pai espere que o processo filho termine de ser executado antes de executá-lo.

5. Memória compartilhada

Memória Compartilhada refere-se a dois ou mais processos que compartilham uma determinada área de armazenamento.

Siga aproximadamente estas etapas: criar/abrir memória, mapear memória (conexão), dados, liberar memória (desconectar), limpar compartilhamento

1. Recursos

  1. A memória compartilhada é o tipo mais rápido de IPC porque o processo acessa a memória diretamente.

  2. Como vários processos podem operar simultaneamente, a sincronização é necessária.

  3. Semáforos + memória compartilhada geralmente são usados ​​juntos. Os semáforos são usados ​​para sincronizar o acesso à memória compartilhada.

2. Protótipo

#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);

Ao usar shmgetuma função para criar uma seção de memória compartilhada, seu tamanho deve ser especificado; e se uma memória compartilhada existente for referenciada, o tamanho deve ser especificado como 0.

Ao usar shmgetuma função para criar uma seção de memória compartilhada, seu tamanho deve ser especificado; e se uma memória compartilhada existente for referenciada, o tamanho deve ser especificado como 0.

Quando uma seção de memória compartilhada é criada, ela não pode ser acessada por nenhum processo. Você deve usar shmatuma função para conectar a memória compartilhada ao espaço de endereço do processo atual. Após a conexão ser bem-sucedida, o objeto da área de memória compartilhada é mapeado para o espaço de endereço do processo de chamada e pode então ser acessado como espaço local.

shmdtA função é usada para desconectar shmata conexão estabelecida. Observe que isso não exclui a memória compartilhada do sistema, apenas o processo atual não pode mais acessar a memória compartilhada.

shmctlA função pode realizar várias operações na memória compartilhada e executar as operações correspondentes de acordo com o parâmetro cmd. Comumente usado é IPC_RMID(remover a memória compartilhada do sistema).

3. Exemplo

Neste exemplo, uma combinação de [memória compartilhada + semáforo + fila de mensagens] é usada para implementar a comunicação entre o processo servidor e o processo cliente.

  • A memória compartilhada é usada para transferir dados;
  • Semáforos são usados ​​para sincronização;
  • A fila de mensagens é usada para notificar o servidor para ler depois que o cliente modifica a memória compartilhada.

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: Quando scanf()caracteres ou strings são inseridos, eles são deixados no buffer \n, portanto, o buffer de entrada padrão precisa ser limpo após cada operação de entrada. Mas como o compilador gcc não suporta isso fflush(stdin)(é apenas uma extensão do C padrão), usamos uma alternativa:

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




Resumir

Resumo dos cinco métodos de comunicação:

1. Pipeline: capacidade lenta e limitada, apenas processos pai e filho podem se comunicar.    

2.FIFO: Qualquer processo pode se comunicar, mas a velocidade é lenta.    

3. Fila de mensagens: A capacidade é limitada pelo sistema, e deve-se prestar atenção ao problema de os dados não terem sido lidos da última vez na primeira leitura.    

4. Semáforo: Não pode transmitir mensagens complexas e só pode ser usado para sincronização.    

5. Área de memória compartilhada: A capacidade pode ser facilmente controlada e a velocidade é rápida, mas a sincronização deve ser mantida.Por exemplo, quando um processo está gravando, outro processo deve prestar atenção aos problemas de leitura e gravação, o que equivale à segurança do thread em threads. É claro que áreas de memória compartilhada também podem ser usadas para comunicação entre threads, mas isso não é necessário. Threads já compartilham um pedaço de memória no mesmo processo.

Referência: https://blog.csdn.net/skyroben/article/details/71513385

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

Acho que você gosta

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