[003 Sistema Operacional] Quais são os métodos de comunicação entre processos? Quais são os prós e contras?

1. Métodos de comunicação entre processos e suas vantagens e desvantagens

1. Tubo de pipeline anônimo

Princípio: Baseado no buffer fornecido pelo kernel do sistema operacional, ele implementa a comunicação entre processos conectando a saída de um processo à entrada de outro processo. Quando um processo grava dados em um pipe, os dados serão armazenados no buffer do pipe, aguardando que outro processo leia os dados do pipe. O processo de gravação não será bloqueado antes da leitura, a menos que o buffer esteja cheio. Quando outro processo lê dados do pipe, os dados são lidos do buffer e passados ​​​​para o processo e, quando nenhum dado chega, ele bloqueia e espera (portanto, comunicação síncrona). (Resumo: comunicação half-duplex, fluxo de dados unidirecional; só pode ser usado entre processos com parentesco)

Vantagens: simples e conveniente;

Desvantagens: Limitado à comunicação unidirecional; só pode ser criado entre seu processo e seus processos relacionados; área tampão limitada.

2. Tubo nomeado FIFO

Princípio: O princípio dos pipes nomeados é semelhante ao dos pipes anônimos e também se baseia no buffer fornecido pelo kernel do sistema operacional para alcançar a comunicação entre processos. Quando um processo grava dados em um pipe nomeado, os dados serão armazenados no buffer do pipe nomeado, aguardando que outro processo leia os dados do pipe nomeado. O processo de gravação não será bloqueado antes da leitura, a menos que o buffer esteja cheio. Quando outro processo lê dados do pipe nomeado, os dados são lidos do buffer e passados ​​​​para o processo. Quando nenhum dado chega, ele bloqueia e espera (daí a comunicação síncrona).

Vantagens: A comunicação entre processos de qualquer relacionamento pode ser realizada;

Desvantagens: Ele existe no sistema há muito tempo e é propenso a erros se usado incorretamente; o buffer é limitado.

3. Fila de mensagens  fila de mensagens

Princípio: 1. A fila de mensagens é uma estrutura de dados da fila primeiro a entrar, primeiro a sair, que na verdade é uma lista vinculada interna no kernel do sistema. As mensagens são inseridas na fila sequencialmente, com o processo de envio adicionando mensagens ao final da fila e o processo de recebimento lendo mensagens do início da fila.
2. Vários processos podem enviar mensagens para uma fila de mensagens ao mesmo tempo e também podem receber mensagens de uma fila de mensagens ao mesmo tempo. O processo de envio envia a mensagem para o final da fila, o processo de recebimento lê a mensagem do início da fila de mensagens e, uma vez lida, a mensagem é excluída da fila.

Vantagens: A comunicação entre quaisquer processos pode ser realizada e a sincronização entre o envio e o recebimento de mensagens pode ser alcançada por meio de funções de chamada do sistema.Não há necessidade de considerar problemas de sincronização, o que é conveniente;

Desvantagens: A cópia de informações requer tempo adicional de CPU e não é adequada para situações com grandes quantidades de informações ou operações frequentes.

4. Memória compartilhada memória compartilhada

Princípio: A memória compartilhada serve para mapear uma seção de memória que pode ser acessada por outros processos. Essa memória compartilhada é criada por um processo, mas pode ser acessada por vários processos. A memória compartilhada é o método IPC (comunicação entre processos) mais rápido, especialmente projetado para a baixa eficiência de outros métodos de comunicação entre processos.

Vantagens: não há necessidade de copiar, rapidez, grande quantidade de informações.

Desvantagens: A memória compartilhada fornece apenas um mecanismo de compartilhamento de dados, mas não fornece um mecanismo de sincronização e exclusão mútua.

5. Semáforo

Princípio: Um semáforo é geralmente uma variável inteira usada para registrar a quantidade disponível de um recurso compartilhado e pode ser acessada e modificada por vários threads ou processos ao mesmo tempo. (O semáforo é essencialmente um contador)

Vantagens: pode sincronizar processos;

Desvantagens: Não suporta passagem de mensagens, só pode fazer sincronização e exclusão mútua e tem funções limitadas.

6. sinal de sinal

Princípio: Os sinais são um método de comunicação relativamente complexo usado para notificar o processo receptor de que um evento ocorreu. Utilizado principalmente como meio de sincronização entre processos e entre diferentes threads do mesmo processo. Para Linux, o sinal real é uma interrupção suave e muitos programas importantes precisam lidar com sinais. Por exemplo, se o usuário final digitar ctrl+c para interromper o programa, um programa será interrompido através do mecanismo de sinal.

  • O processo envia sinais: os sinais são enviados ao processo alvo por meio de kill, raise, alarm e outras funções.

  • O processo recebe sinais: registra funções de processamento de sinais por meio de sinal, sigaction e outras funções.

  • Quando chega um sinal, a função de processamento é chamada e o processamento correspondente é feito de acordo com o sinal na função.

Vantagens: Sinais diferentes podem transmitir informações diferentes. Uma comunicação relativamente flexível pode ser alcançada definindo funções de processamento correspondentes para cada sinal.

Desvantagens: menos informações são transmitidas.

7. soquete

Princípio: O soquete também é um mecanismo de comunicação entre processos e, ao contrário de outros mecanismos de comunicação, pode ser usado para comunicação de processos entre diferentes máquinas.

Vantagens: 1) Os dados de transmissão são de nível de byte, os dados de transmissão podem ser personalizados, o volume de dados é pequeno e a eficiência é alta; 2) O tempo de transmissão de dados é curto e o desempenho é alto; 3) Adequado para tempo real interação de informações entre o cliente e o servidor; 4) Pode ser criptografado e possui forte segurança de dados.
Desvantagens: 1) Os dados transmitidos precisam ser analisados ​​​​e convertidos em dados em nível de aplicativo.

Métodos de comunicação entre processos e suas respectivas vantagens e desvantagens_Análise e resumo das diferenças, vantagens e desvantagens dos quatro métodos de comunicação de processos_Echo_Anna's Blog-CSDN Blog

https://www.cnblogs.com/jiangknownet/p/14516630.html (o resumo é muito bom)


2. Seleção do método de comunicação entre processos

PIPE e FIFO (pipe nomeado) são usados ​​para enviar mensagens muito curtas e de alta frequência entre processos. Esses dois métodos geralmente são adequados para comunicação entre dois processos.

A memória compartilhada é usada para realizar dados muito grandes compartilhados entre processos e com alta frequência de operações de leitura e gravação.

Outros consideram o uso de soquete. Usado principalmente em desenvolvimento distribuído.


3. Quais são os métodos de sincronização entre threads?

  • Operações atômicas
  • bloqueio de rotação
  • bloqueio de rotação de leitura e gravação
  • Bloqueio sequencial (seqlock, incluído apenas no kernel 2.6 e versões posteriores)
  • Quantidade de sinal
  • Ler e escrever semáforos
  • mutex
  • Big Kernel Lock (BKL, Big Kernel Lock, incluído apenas no kernel 2.4, não discutido)
  • Bloqueio de leitor grande (brlock, incluído apenas no kernel 2.4, não discutido)
  • RCU (otimização de bloqueios de leitura e gravação, incluída apenas no kernel 2.6 e versões posteriores)

【005 Conhecimento básico】 Mecanismo de sincronização Linux? _Blog de Kashine-Blog CSDN


4. Explicação detalhada dos métodos de comunicação entre processos

1. Gasoduto

  • Chame a função fork() para criar um novo processo filho. A função fork() retornará duas vezes, uma vez no processo pai para retornar o ID do processo filho, e uma vez no processo filho para retornar 0.
  • No processo pai, chame a função sleep(3) para permitir que o processo filho seja executado primeiro, gere uma mensagem de prompt, feche a extremidade de leitura do canal e, em seguida, chame write(fd[1], "hello fromfather", strlen ("olá do pai")) grava dados na extremidade de gravação do canal.
#include <stdio.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
 
/*使用匿名管道实现进程间通信*/
int main()
{
        int fd[2];//fd[0]为读端 fd[1]为写端
        pid_t pid;
        char buf[128];
        //int pipe(int pipefd[2]);
        if(pipe(fd) == -1)//创建管道
        {
                printf("管道创建失败\n");
                perror("why"); // 打印错误信息
        }
 
        pid = fork(); // 创建子进程
 
        if(pid < 0 )
        {
                printf("子进程开辟失败\n");
                perror("why"); // 打印错误信息
        }else if(pid > 0){
 
                sleep(3);//让子进程先执行
                printf("这是一个父进程\n");//父进程完成写操作
                close(fd[0]); // 关闭读端
                write(fd[1],"hello from father",strlen("hello from father")); // 往写端写入数据
        }else{
 
                printf("这是一个子进程\n");//子进程完成读操作
                close(fd[1]); // 关闭写端
                read(fd[0],buf,sizeof(buf)); // 从管道读取数据,并存储到buf数组中
                printf("buf = %s\n",buf); // 输出读取的数据
        }
 
        return 0;
}

2. Tubo nomeado FIFO

Primeiro crie um pipe nomeado:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        return 0;
}

Leia o tubo em read.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <fcntl.h>
#include <unistd.h>
 
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = {'\0'};
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_RDONLY);//以只读的形式打开管道,程序阻塞在这,直到有另一个进程对其执行写操作
        if(fd < 0)
        {
                printf("read open failed\n");
        }else
        {
                printf("read open successn\n");
        }
 
        while(1)
        {
                nread = read(fd,buf,sizeof(buf));
                printf("read %d byte,context is:%s\n",nread,buf);
        }
 
        close(fd);
 
        return 0;
}

Escreva no pipe em write.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
//       int mkfifo(const char *pathname, mode_t mode);
 
int main()
{
        int nread;
        char buf[30] = "message from myfifo";
 
        if(mkfifo("myfifo",0600) == -1 && errno != EEXIST)//创建命名管道
        {
                printf("mkfifo failed\n");
                perror("why");
        }
 
        int fd = open("./myfifo",O_WRONLY);//打开管道,程序阻塞在这,直到其他进程为读而打开它
        if(fd < 0)
        {
                printf("write open failed\n");
        }
        else
        {
                printf("write open success\n");
        }
 
        while(1)
        {
                sleep(1);
                write(fd,buf,strlen(buf));
        }
        close(fd);
 
        return 0;
}
  1. A função mkfifo() é usada para criar um pipe nomeado e os parâmetros são o caminho do pipe e as permissões. Se o canal já existir, retorne -1 e defina errno como EEXIST.

  2. O processo de leitura abre o canal somente leitura (O_RDONLY) e o processo de gravação abre o canal somente gravação (O_WRONLY).

  3. Quando o pipe é aberto, o processo de leitura será bloqueado na chamada de leitura e o processo de escrita será bloqueado na chamada de gravação.

  4. Depois que ambos os processos abrirem o pipe, a leitura do processo de leitura retornará os dados gravados e a gravação do processo de escrita também retornará com sucesso.

3. Fila de mensagens:

A fila de mensagens é uma lista vinculada de mensagens armazenadas no kernel. Uma fila de mensagens é identificada por um identificador (ID da fila).

Os processos do usuário podem adicionar mensagens à fila de mensagens e ler mensagens da fila de mensagens.

// Cria ou abre uma fila de mensagens: retorna o ID da fila em caso de sucesso, -1 em caso de falha
int msgget(key_t key, int flag);
// Adiciona uma mensagem: retorna 0 em caso de sucesso, -1 em caso de falha
int msgsnd(int msqid , const void *ptr , size_t size, int flag);
// Ler a mensagem: Retorna o comprimento dos dados da mensagem com sucesso, ou -1 se falhar
int msgrcv(int msqid, void *ptr, size_t size, long type, int flag); //
Controla a fila de mensagens: Retorna 0 em caso de sucesso, -1 em caso de falha
int msgctl(int msqid, int cmd, struct msqid_ds *buf);

arquivo msgSend.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
 
int main()
{
        struct msgbuf sendbuf={888,"message from send"};
        struct msgbuf readbuf;
 
        key_t key;
 
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
        }
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
        printf("send over\n");
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),999,0);
        printf("read from get is:%s\n",readbuf.mtext);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

arquivo msgGet.c:

#include <stdio.h>
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/msg.h>
#include <string.h>
//       int msgget(key_t key, int msgflg);
// int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
 
//       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int msgflg);
struct msgbuf{
        long mtype;       /* message type, must be > 0 */
        char mtext[128];    /* message data */
};
 
int main()
{
        struct msgbuf readbuf;
        memset(readbuf.mtext, '\0', sizeof(readbuf.mtext));
        struct msgbuf sendbuf={999,"thank for your reach"};
 
        key_t key;
 
        //获取key值
        if((key = ftok(".",'z')) < 0){
                printf("ftok error\n");
        }
 
        int msgId = msgget(key,IPC_CREAT|0777);
 
        if(msgId == -1){
                printf("get quen failed\n");
                perror("why");
        }
 
        msgrcv(msgId,&readbuf,sizeof(readbuf.mtext),888,0);
        printf("read from send is:%s\n",readbuf.mtext);
 
        msgsnd(msgId,&sendbuf,strlen(sendbuf.mtext),0);
 
        msgctl(msgId,IPC_RMID,NULL);
 
        return 0;
}

msgsnd() é usado para enviar mensagens para a fila de mensagens especificada. Ele contém os seguintes parâmetros:

  • msgId: o ID da fila de mensagens a ser enviada.

  • &sendbuf: Ponteiro para o buffer de envio de mensagens, a estrutura da mensagem é representada por mtext.

  • strlen(sendbuf.mtext): O comprimento dos dados a serem enviados.

  • 0: Enviar sinalizador, geralmente 0.

msgrcv() é usado para receber mensagens da fila de mensagens especificada. Ele contém os seguintes parâmetros:

  • msgId: ID da fila para receber mensagens.

  • &readbuf: Ponteiro para o buffer de mensagem de recebimento, a estrutura da mensagem é representada por mtext.

  • sizeof(readbuf.mtext): Especifica o comprimento máximo da mensagem recebida desta vez.

  • 999: Tipo de mensagem recebida, 999 significa receber qualquer tipo de mensagem.

  • 0: Sinalizador de recebimento, geralmente 0.

4. Memória compartilhada

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

// Cria ou obtém uma memória compartilhada: retorna o ID da memória compartilhada com sucesso, retorna -1 em caso de falha
int shmget(key_t key, size_t size, int flag);
// Conecta a memória compartilhada ao espaço de endereço do processo atual: return apontando para a memória compartilhada com sucesso Ponteiro, retorna -1 em caso de falha
void *shmat(int shm_id, const void *addr, int flag);
// Desconecta da memória compartilhada: retorna 0 em caso de sucesso, -1 em caso de falha
int shmdt(void * addr); 
// Informações relacionadas ao controle da memória compartilhada: 0 é retornado em caso de sucesso, -1 em caso de falha
int shmctl(int shm_id, int cmd, struct shmid_ds *buf);

Para obter detalhes, consulte: https://www.cnblogs.com/52php/p/5861372.html

arquivo shmwrite.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, IPC_CREAT|0666);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        strcpy(shmaddr,"I am so cool");
 
        sleep(5);//等待5秒,让别的进程去读
 
        shmdt(shmaddr);
        shmctl(shmId, IPC_RMID, 0);//写0表示不关心
        printf("quit\n");
 
        return 0;
}

arquivo shmread.c:

#include <stdio.h>
#include <string.h>
#include <stdlib.h>
#include <sys/ipc.h>
#include <sys/shm.h>
 
//       int shmget(key_t key, size_t size, int shmflg);
// void *shmat(int shmid, const void *shmaddr, int shmflg);
 
//       int shmdt(const void *shmaddr);
 
int main()
{
        int shmId;
        key_t key;
        char *shmaddr;
 
        if((key = ftok(".",1)) < 0){
                printf("ftok error\n");
        }
 
        shmId = shmget(key, 1024*4, 0);//内存大小必须得是MB的整数倍
 
        if(shmId == -1){
                printf("shmget error\n");
                exit(-1);
        }
 
        /*第二个参数一般写0,让linux内核自动分配空间,第三个参数也一般写0,表示可读可写*/
        shmaddr = shmat(shmId, 0, 0);
        printf("shmat OK\n");
        printf("data : %s\n",shmaddr);
 
        shmdt(shmaddr);
 
        return 0;
}
if((key = ftok(".",1)) < 0){
    printf("ftok error\n");
}

Como você pode ver, o número 1 é usado como parâmetro em ambos os programas ., o que garante que os valores-chave que eles geram sejam os mesmos. Isso garante que a mesma memória esteja sendo operada.


5. Conteúdo de referência

Seis métodos comuns de comunicação entre processos_Os métodos mais comuns de comunicação entre processos_A Quiet Corner Blog-Blog CSDN (referência principal)

Métodos de comunicação entre processos e suas respectivas vantagens e desvantagens_Análise e resumo das diferenças, vantagens e desvantagens dos quatro métodos de comunicação de processos_Echo_Anna's Blog-CSDN Blog

Acho que você gosta

Origin blog.csdn.net/qq_41709234/article/details/132007328
Recomendado
Clasificación