• 进程间通信几种方式


    管道

    管道是由内核管理的一个缓冲区。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。

     

    int main(void)
    {
       int n;
       int fd[2]; //0读 1写
       pid_t pid;
       char line[MAXLINE];

       if(pipe(fd) == 0){                 /* 先建立管道得到一对文件描述符 */
           exit(0);
      }

       if((pid = fork()) == 0)            /* 父进程把文件描述符复制给子进程 */
           exit(1);
       else if(pid > 0){                /* 父进程写 */
           close(fd[0]);                /* 关闭读描述符 */
           write(fd[1], "\nhello world\n", 14);
      }
       else{                            /* 子进程读 */
           close(fd[1]);                /* 关闭写端 */
           n = read(fd[0], line, MAXLINE);
           write(STDOUT_FILENO, line, n);
      }

       exit(0);
    }

    命名管道

    由于基于fork机制,所以管道只能用于父进程和子进程之间,或者拥有相同祖先的两个子进程之间 (有亲缘关系的进程之间)。为了解决这一问题,Linux提供了FIFO方式连接进程。FIFO又叫做命名管道(named PIPE)。

    FIFO (First in, First out)为一种特殊的文件类型,它在文件系统中有对应的路径。当一个进程以读(r)的方式打开该文件,而另一个进程以写(w)的方式打开该文件,那么内核就会在这两个进程之间建立管道,所以FIFO实际上也由内核管理,不与硬盘打交道。

    #include <sys/types.h>
    #include <sys/stat.h>

    int mkfifo(const char *filename, mode_t mode);
    int mknode(const char *filename, mode_t mode | S_IFIFO, (dev_t) 0 );

    其中filename是被创建的文件名称,mode表示将在该文件上设置的权限位和将被创建的文件类型(在此情况下为S_IFIFO),dev是当创建设备特殊文件时使用的一个值。因此,对于先进先出文件它的值为0。

    #include <stdio.h>  
    #include <stdlib.h>  
    #include <sys/types.h>  
    #include <sys/stat.h>  

    int main()  
    {  
       int res = mkfifo("/tmp/my_fifo", 0777);  
       if (res == 0)  
      {  
           printf("FIFO created/n");  
      }  
        exit(EXIT_SUCCESS);  
    }

    信号量

    信号量是一个特殊的变量,程序对其访问都是原子操作,且只允许对它进行等待(即P(信号变量))和发送(即V(信号变量))信息操作。最简单的信号量是只能取0和1的变量,这也是信号量最常见的一种形式,叫做二进制信号量。而可以取多个正整数的信号量被称为通用信号量。

    • P(sv):如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行

    • V(sv):如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1.

    semget函数

    创建一个新信号量或取得一个原有的信号量。

    int semget(key_t key, int num_sems, int sem_flags); 
    • 第一个参数key是整数值(唯一非零),不相关的进程可以通过它访问一个信号量,它代表程序可能要使用的某个资源,程序对所有信号量的访问都是间接的,程序先通过调用semget函数并提供一个键,再由系统生成一个相应的信号标识符(semget函数的返回值),只有semget函数才直接使用信号量键,所有其他的信号量函数使用由semget函数返回的信号量标识符。如果多个程序使用相同的key值,key将负责协调工作。

    • 第二个参数num_sems指定需要的信号量数目,它的值几乎总是1。

    • 第三个参数sem_flags是一组标志,当想要当信号量不存在时创建一个新的信号量,可以和值IPC_CREAT做按位或操作。设置了IPC_CREAT标志后,即使给出的键是一个已有信号量的键,也不会产生错误。而IPC_CREAT | IPC_EXCL则可以创建一个新的,唯一的信号量,如果信号量已存在,返回一个错误。

    int semop(int semid, struct sembuf semoparray[], size_t numops);  
    int semctl(int semid, int sem_num, int cmd, ...)

    当semget创建新的信号量集合时,必须指定集合中信号量的个数(即num_sems),通常为1; 如果是引用一个现有的集合,则将num_sems指定为 0 。

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

     

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

     

    消息队列

    消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标识。

    • 消息队列是面向记录的,消息具有特定的格式和优先级。

    • 消息队列独立于发送和接收线程。进程结束时,消息队列以及内容不会被删除。

    • 可以实现消息的随机查询,不一定是FIFO,也可以按照消息的类型读取

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

    在以下两种情况下,msgget将创建一个新的消息队列:

    • 没有与键值key对应的消息队列,并且flag中包含了IPC_CREAT标志位

    • key参数为IPC_PRIVATE

    函数msgrcv在读取消息队列时,type参数有下面几种情况:

    • type == 0 返回队列的第一个消息

    • type>0 返回队列中消息类型为type的第一个消息

    • type<0 返回队列中消息类型值小于等于type绝对值的消息,如果有多个,则取类型值最小的消息。

    type值非 0 时用于以非先进先出次序读消息。也可以把 type 看做优先级的权值。

    #include <iostream>
    #include <sys/msg.h>
    #include <unistd.h>

    #define MSG_FILE "/etc/passwd"

    using namespace std;

    struct msg {
    long type;
    char text[256];
    };

    int main()
    {
    int msgid;
    key_t key;
    msg m;

    if ((key = ftok(MSG_FILE, 'z')) < 0) {
    perror("ftok error");
    goto  END;
    }

    cout << "Key : " << key << endl;
    if ((msgid = msgget(key, IPC_CREAT | 0777)) == -1) {
    perror("Msg get error");
    goto END;
    }

    cout << "Msg id : " << msgid << endl;
    cout << "Pid: " << getpid() << endl;

    cout << "Waiting..." << endl;
    while (true)
    {
    msgrcv(msgid, &m, sizeof m, 0, 888);
    cout << "Msg type: " << m.type << " Msg Info:" << m.text << endl;
    m.type = 999;
    msgsnd(msgid, &m, sizeof m.text, 0);
    }

    END:
    getc(stdin);
    return 0;
    }
    #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);

       // 读取类型为777的消息
       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;
    }

    共享内存

    共享内存(Shared Memory),指两个或多个进程共享一个给定的存储区。

    • 共享内存是最快的一种 IPC,因为进程是直接对内存进行存取。

    • 多个线程可以同时操作,所以需要进行同步

    • 信号量+共享内存组合

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

     

  • 相关阅读:
    【C++】深度探索C++对象模型读书笔记--关于对象(Object Lessons)
    【操作系统】调度算法
    【vim】vim常用命令
    【linux】linux的数据流重定向
    以太网帧,IP,TCP,UDP首部结构
    IPv4编址及子网划分
    【计算机网络】计算机网络模型
    【计算机网络】NAT:网络地址转换
    【设计模式】C++中的单例模式
    (转)linux查找技巧: find grep xargs
  • 原文地址:https://www.cnblogs.com/HadesBlog/p/16015663.html
Copyright © 2020-2023  润新知