进程间通信IPC

  1. 多个进程之间的用户空间是相互独立的,其栈区、堆区、静态区的数据都是私有的,因此不可以通过用户空间中的区域完成多个进程之间的通信。
  2. 可使用外部文件来完成多个进程之间数据的传递,一个进程向文件中写数据,另外一个进程从文件中读取数据。该方式必须是保证写进程先执行,再执行读进程,保证进程执行的同步性。
  3. 可利用内核空间 来完成数据的通信,本质上,在内核空间中创建一个特殊的区域,一个进程向该区域中存放数据,另外一个进程可以从该区域中读取数据。
  4. 引入原因:用户空间中的数据,不能作为多个进程之间的数据交换容器。

1、进程间通信的基础概念

  • IPC:进程通信
  • 使用内核空间完成多个进程之间相互通信,分为三类通信机制
  • 进程间通信方式的分类:内核提供的通信方式(无名管道、有名管道、信号);system v提供的通信方式(消息队列、共享内存、信号量);套接字通信(socket网络通信(跨主机通信))

2、无名管道

  1. 原理:管道是一种特殊的文件,该文件只能用于进程通信,不能用于存储数据。管道分为有名管道和无名管道。

    bash 复制代码
    文件类型:bcd-lsp
    b:块设备文件
    c:字符设备文件
    d:目录文件,文件夹
    -:普通文件
    l:链接文件
    s:套接字文件(网络编程)
    p:管道文件
  2. 在内核空间创建出一个管道通信,一个进程可以将数据写入管道,经由管道缓冲到另一个进程中读取。

  3. 无名管道:顾名思义就是没有名字的管道,会在内存中创建出该管道,不存在于文件系统,随着进程结束而消失。

  4. 无名管道仅适用于亲缘进程间通信,不适用于非亲缘进程间通信。

  5. 无名管道的API

    c 复制代码
    	#include <unistd.h>
    	int pipe(int fildes[2]);
    	功能:创建一个无名管道,并返回该管道的两个文件描述符
    	参数:是一个整型数组,用于返回打开的管道的两端的文件描述符, fildes[0]表示读端
    	fildes[1]表示写端
    	返回值:成功返回0,失败返回-1并置位错误码
  6. 管道通信的特点

3、有名管道

  1. 顾名思义就是有名字的管道文件,会在文件系统中创建一个真实存在的管道文件

  2. 既可以完成亲缘进程间通信,也可以完成非亲缘进程间通信

  3. 有名管道的API

    c 复制代码
    #include <sys/types.h>
    #include <sys/stat.h>
    int mkfifo(const char *pathname, mode_t mode);
    功能:创建一个管道文件,并存在与文件系统中
    参数1:管道文件的名称
    参数2:管道文件的权限,内容详见open函数的mode参数
    返回值:成功返回0,失败返回-1并置位错误码
    注意:管道文件被创建后,其他进程就可以进行打开读写操作了,但是,必须要保证当前管道文件的
    两端都打开后,才能进行读写操作,否则函数会在open处阻塞

4、信号

  1. 相关概念:信号是软件模拟硬件的中断功能,信号是软件实现的,中断是硬件实现的;信号是linux内核实现的,没有内核就没有信号的概念;用户可以给进程发信号:例如键入ctrl+c,内核可以向进程发送信号:例如SIGPIPE,一个进程可以给另一个进程发送信号,需要通过相关函数来完成;信号通信是属于异步通信工作

  2. 信号的种类及功能

  3. 对应信号的处理方式有三种:捕获、忽略、默认

  4. 对信号的处理函数:signal

    c 复制代码
    #include <signal.h>
    typedef void (*sighandler_t)(int);
    sighandler_t signal(int signum, sighandler_t handler);
    功能:将信号与信号处理方式绑定到一起
    参数1:要处理的信号
    参数2:处理方式
    SIG_IGN:忽略
    SIG_DFL:默认,一般信号的默认操作都是杀死进程
    typedef void (*sighandler_t)(int):用户自定义的函数
    返回值:成功返回处理方式的起始地址,失败返回SIG_ERR并置位错误码
    注意:只要程序与信号绑定一次,后续但凡程序收到该信号,对应的处理方式就会立即响应
  5. 信号发送函数:kill、raise

    c 复制代码
    #include <signal.h>
    int kill(pid_t pid, int sig);
    功能:向指定进程或进程组发送信号
    参数1:进程号或进程组号
    >0:表示向执行进程发送信号
    =0:向当前进程所在的进程组中的所有进程发送信号
    =-1:向所有进程发送信号
    <-1:向指定进程组发送信号,进程组的ID号为给定pid的绝对值
    参数2:要发送的信号
    返回值:成功返回0,失败返回-1并置位错误码
    #include <signal.h>
    int raise(int sig);
    功能:向自己发送信号 等价于:kill(getpid(), sig);
    参数:要发送的信号
    返回值:成功返回0,失败返回非0数组

信号可以完成多个进程间通知作用,但是,不能进行数据传输功能

5、system V提供的进程通信概述

  1. 内核提供的三种通信方式,对于管道而言,只能实现单向的数据通信,对于信号而言,只能完成对进程之间消息的通知,不能起到数据传输的效果。为了解决这个问题,引入了系统V进程间的通信

  2. system V提供的进程间通信的方式分别为:消息队列、共享内存、信号量

  3. 相关指令:

    c 复制代码
    ipcs 可以查看所有的信息(消息队列、共享内存、信号量)
    ipcs -q:可以查看消息队列的信息
    ipcs -m:可以查看共享内存的信息
    ipcs -s:可以查看信号量的信息
    ipcrm -q/m/s ID :可以删除指定ID的IPC对象
  4. 消息队列、共享内存、信号量这三种通信方式,是借助内核空间的相关通信,原理是在内核空间中创建相关的对象容器,在进行进程间的通信时,可以将信息放入到对象中,另外一个进程就可以从容器中取数据了

  5. 与内核提供的管道、信号通信不同:system V的iIPC对象实现了数据传递的容器与程序相分离,也就是说,即使程序以己经结束,但是放入到容器中的数据依然存在,除非将容器手动删除

6、消息队列

  1. 实现原理:System V 消息队列存放于内核空间,各进程运行在隔离的用户空间,依靠系统调用收发带 type 标识的消息;发送时数据从用户态拷贝进内核队列,接收时按 type 匹配取出消息并拷贝回用户空间,消费完毕内核清除消息,队列独立于进程生命周期,可多进程分类消费不同类型消息。

  2. 消息队列实现的API

    c 复制代码
    1、创建key值
           #include <sys/types.h>
           #include <sys/ipc.h>
           key_t ftok(const char *pathname, int proj_id); //ftok("/", 'k');
           功能:通过给定的文件以及给定的一个随机值,创建出一个4字节整数的key值,用于system V 
    IPC对象的创建
           参数1:一个文件路径,要求是已经存在的文件路径,提供了key值3字节的内容,其中,文件的设备
    号占1字节,文件的inode号占2字节
           参数2:一个随机整数,取后8位(1字节)跟前面的文件共同组成key值,必须是非0的数字
           返回值:成功返回key值,失败返回-1并置位错误码
           
    2、通过key值,创建消息队列
     #include <sys/types.h>
           #include <sys/ipc.h>
           #include <sys/msg.h>
           int msgget(key_t key, int msgflg);
           功能:通过给定的key值,创建出一个消息队列的对象,并返回消息队列的句柄ID,后期可以通过该
    ID操作整个消息队列
           参数1:key值,该值可以是IPC_PRIVATE,也可以是ftok创建出来的,前者只用于亲缘进程间的通
    信
           参数2:创建标识
           IPC_CREAT:创建并打开一个消息队列,如果消息队列已经存在,则直接打开
           IPC_EXCL:确保本次创建处理的是一个新的消息队列,如果消息队列已经存在,则报错,错
    误码位EEXIST
           0664:该消息队列的操作权限
           eg: IPC_CREAT|0664  或者  IPC_CREAT|IPC_EXCL|0664
           返回值:成功返回消息队列的ID号,失败返回-1并置位错误码
           3、向消息队列中存放数据
           #include <sys/types.h>
           #include <sys/ipc.h>
           #include <sys/msg.h>
           int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
           功能:向消息队列中存放一个指定格式的消息
           参数1:打开的消息队列的id号
           参数2:要发送的消息的起始地址,消息一般定义为一个结构体类型,由用户手动定义
            struct msgbuf {
                   long mtype;       /* message type, must be > 0 */   消息的类型
                   char mtext[1];    /* message data */    消息正文
                   。。。
               };
            参数3:消息正文的大小
            参数4:是否阻塞的标识
           0:标识阻塞形式向消息队列中存放消息,如果消息队列满了,就在该函数处阻塞
           IPC_NOWAIT:标识非阻塞的形式向消息队列中存放消息,如果消息队列满了,直接返回
            返回值:成功返回0,失败返回-1并置位错误码
           
           
    4、从消息队列中取消息
           ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,int 
    msgflg);
           功能:从消息队列中取数指定类型的消息放入给定的容器中
            参数1:打开的消息队列的id号
           参数2:要接收的消息的起始地址,消息一般定义为一个结构体类型,由用户手动定义
            struct msgbuf {
                   long mtype;       /* message type, must be > 0 */   消息的类型
                   char mtext[1];    /* message data */    消息正文
                   。。。
               };
            参数3:消息正文的大小
            参数4:要接收的消息类型
           0:表示每次都取消息队列中的第一个消息,无论类型
           >0:读取队列中第一个类型为msgtyp的消息
           <0:读取队列中的一个消息,消息为绝对值小于msgtyp的第一个消息
           eg:    10-->8-->3-->6-->5-->20-->2
           -5: 会从队列中绝对值小于5的类型的消息中选取第一个消息,就是3   
            参数5:是否阻塞的标识
           0:标识阻塞形式向消息队列中读取消息,如果消息队列空了,就在该函数处阻塞
           IPC_NOWAIT:标识非阻塞的形式向消息队列中读取消息,如果消息队列空了,直接返回
           返回值:成功返回实际读取的正文大小,失败返回-1并置位错误码
           
    5、销毁消息队列
           #include <sys/types.h>
           #include <sys/ipc.h>
           #include <sys/msg.h>
           int msgctl(int msqid, int cmd, struct msqid_ds *buf);
           功能:对给定的消息队列执行相关的操作,该操作由cmd参数而定
           参数1:消息队列的ID号
           参数2:要执行的操作
           IPC_RMID:删除一个消息队列,当cmd为该值时,第三个参数可以省略填NULL即可
           IPC_STAT:表示获取当前消息队列的属性,此时第三个参数就是存放获取的消息队列属性
    的容器起始地址
           IPC_SET:设置当前消息队列的属性,此时第三个参数就是要设置消息队列的属性数据的起
    始地址       参数3:消息队列数据容器结构体,如果第二个参数为IPC_RMID,则该参数忽略填NULL即可,如果
    是 IPC_STAT、 IPC_SET填如下结构体:
           struct msqid_ds {
                   struct ipc_perm msg_perm;     /* Ownership and permissions */   消息队列的拥有者和权限
                   time_t          msg_stime;    /* Time of last msgsnd(2) */   最后一次发送消息的时间
                   time_t          msg_rtime;    /* Time of last msgrcv(2) */   最后一次接收消息的时间
                   time_t          msg_ctime;    /* Time of last change */      最后一次状态改变的时间
                   unsigned long   __msg_cbytes; /* Current number of bytes in queue(nonstandard) */ 已用字节数
                   msgqnum_t       msg_qnum;     /* Current number of messages in queue */ 消息队列中消息个数
                   msglen_t        msg_qbytes;   /* Maximum number of bytes allowed in queue */最大消息个数
                   pid_t           msg_lspid;    /* PID of last msgsnd(2) */    最后一次发送消息的进程pid
                   pid_t           msg_lrpid;    /* PID of last msgrcv(2) */    最后一次读取消息的进程pid
               };
           该结构体的第一个成员类型如下:
           struct ipc_perm {
                   key_t          __key;       /* Key supplied to msgget(2) */   key值
                   uid_t          uid;         /* Effective UID of owner */   当前进程的uid
                   gid_t          gid;         /* Effective GID of owner */   当前进程的组ID
                   uid_t          cuid;        /* Effective UID of creator */ 消息队列创建者的用户id
                   gid_t          cgid;        /* Effective GID of creator */ 消息队列创建者的组id
                   unsigned short mode;        /* Permissions */              消息队列的权限
                   unsigned short __seq;       /* Sequence number */          队列号
               };
     返回值:成功返回0,失败返回-1并置位错误码

发送端的实现

c 复制代码
#include<myhead.h>
//消息类型的定义
struct msgBuf{
               long mtype;       // 消息的类型
               char mtext[1024];    //消息正文
           };
#define MSGSZ (sizeof(struct msgBuf)-sizeof(long))      //正文的大小
int main(int argc, const char *argv[])
{
    //1、创建key值,用于创建出一个消息队列
    key_t key = ftok("/", 'k');                //参数1:已经存在的路径
    //参数2:是一个随机值
    if(key == -1)
   {
        perror("ftok error");
        return -1;
   }
    printf("key = %#x\n", key);      //输出键值
    //2、通过key值创建出一个消息队列,并返回该消息队列的id
    int msqid = -1;
    if((msqid = msgget(key, IPC_CREAT|0664)) == -1)
   {
        perror("msgget error");
        return -1;
   }
    printf("msgqid = %d\n", msqid);        //输出id号
    //3、向消息队列中存放消息
    //组建一个消息
    struct msgBuf buf;
    while(1)
   {
        printf("请输入消息的类型:");
        scanf("%ld", &buf.mtype);
        getchar();                //吸收回车
        printf("请输入消息正文:");
        fgets(buf.mtext, MSGSZ, stdin);      //从终端输入数据
        buf.mtext[strlen(buf.mtext)-1] = '\0';     //将换行更换成'\0'
        //将上述组装的消息放入消息队列中, 以阻塞的方式将其放入消息队列
        msgsnd(msqid, &buf, MSGSZ, 0);
        printf("消息存入成功\n");
        //判断退出条件
        if(strcmp(buf.mtext, "quit") == 0)
       {
            break;
       }
   }
    return 0;
}

接收端的实现

c 复制代码
#include<myhead.h>
//消息类型的定义
struct msgBuf{
               long mtype;       // 消息的类型
               char mtext[1024];    //消息正文
           };
#define MSGSZ (sizeof(struct msgBuf)-sizeof(long))      //正文的大小
int main(int argc, const char *argv[])
{    //1、创建key值,用于创建出一个消息队列
    key_t key = ftok("/", 'k');            
    //参数1:已经存在的路径
    //参数2:是一个随机值
    if(key == -1)
   {
        perror("ftok error");
        return -1;
   }
    printf("key = %#x\n", key);      //输出键值
    //2、通过key值创建出一个消息队列,并返回该消息队列的id
    int msqid = -1;
    if((msqid = msgget(key, IPC_CREAT|0664)) == -1)
   {
        perror("msgget error");
        return -1;
   }
    printf("msgqid = %d\n", msqid);        //输出id号
    //3、从消息队列中取消息
    //组建一个消息
    struct msgBuf buf;
    while(1)
   {
       //清空容器
       bzero(&buf, sizeof(buf));
       //读取消息
       msgrcv(msqid, &buf, MSGSZ, 1, 0);
       //参数4:表示读取的消息类型
       //参数5:表示是否阻塞读取
       printf("读取到的消息为:%s\n", buf.mtext);
       if(strcmp(buf.mtext,"quit") == 0)
       {
            break;
       }
   }
    //4、删除消息队列
    if(msgctl(msqid, IPC_RMID, NULL) == -1)
   {
        perror("msgctl error");
        return -1;
   }
    return 0;
}

注意事项

  • 对于消息而言,由两部分组成:消息的类型和消息正文,消息结构体由用户自定义
  • 对于消息队列而言,任意一个进程都可以向消息队列中发送消息,也可以从消息队列中取消息
  • 多个进程,使用相同的key值打开的是同一个消息队列
  • 对消息队列中的消息读取操作是一次性的,被读取后,消息队列中不存在该消息了
  • 消息队列的大小:16KB

7、共享内存

  1. 实现原理:共享内存是效率最高的进程间通信方式,内核开辟一块物理内存区域映射到多个进程各自的用户虚拟地址空间,多个进程可直接读写同一块内存数据,省去用户态与内核态之间的数据拷贝,仅需配合信号量、消息队列等同步机制防止多进程同时读写引发数据错乱,内存段生命周期独立于进程,销毁前数据会一直留存。

  2. 共享内存的API

    c 复制代码
    1、创建key值
           #include <sys/types.h>
           #include <sys/ipc.h>
           key_t ftok(const char *pathname, int proj_id); //ftok("/", 'k');
           功能:通过给定的文件以及给定的一个随机值,创建出一个4字节整数的key值,用于system V 
    IPC对象的创建
           参数1:一个文件路径,要求是已经存在的文件路径,提供了key值3字节的内容,其中,文件的设备
    号占1字节,文件的inode号占2字节
           参数2:一个随机整数,取后8位(1字节)跟前面的文件共同组成key值,必须是非0的数字
           返回值:成功返回key值,失败返回-1并置位错误码
          
    2、通过key值创建共享内存段
     #include <sys/ipc.h>
           #include <sys/shm.h>
           int shmget(key_t key, size_t size, int shmflg);       
           功能:申请指定大小的物理内存,映射到内核空间,创建出共享内存段
           参数1:key值,可以是IPC_PRIVATE,也可以是ftok创建出来的key值
           参数2:申请的大小,是一页(4096字节)的整数倍,并且向上取整
           参数3:创建标识
           IPC_CREAT:创建并打开一个共享内存,如果共享内存已经存在,则直接打开
           IPC_EXCL:确保本次创建处理的是一个新的共享内存,如果共享内存已经存在,则报错,错
    误码位EEXIST
           0664:该共享内存的操作权限
           eg: IPC_CREAT|0664  或者  IPC_CREAT|IPC_EXCL|0664
           返回值:成功返回共享内存段的id,失败返回-1并置位错误码
           
    3、将共享内存段的地址映射到用户空间
           #include <sys/types.h>
           #include <sys/shm.h>
           void *shmat(int shmid, const void *shmaddr, int shmflg);
           功能:将共享内存段映射到用户空间
           参数1:共享内存的id号
           参数2:物理内存的起始地址,一般填NULL,由系统自动选择一个合适的对齐页
           参数3:对共享内存段的操作
           0:表示读写操作
           SHM_RDONLY:只读
           返回值:成功返回用于操作共享内存的指针,失败返回(void*)-1并置位错误码
    4、释放共享内存的映射关系
           int shmdt(const void *shmaddr);
           功能:将进程与共享内存的映射取消
           参数:共享内存的指针
           返回值:成功返回0,失败返回-1并置位错误码
           
    5、共享内存的控制函数
           #include <sys/ipc.h>
           #include <sys/shm.h>
           int shmctl(int shmid, int cmd, struct shmid_ds *buf);
           功能:根据给定的不同的cmd执行不同的操作
           参数1:共享内存的ID
           参数2:要操作的指令
           IPC_RMID:删除共享内存段,第三个参数可以省略
           IPC_STAT:获取当前共享内存的属性
           IPC_SET:设置当前共享内存的属性
           参数3:如果参数2为IPC_RMID,则参数3可以省略填NULL,如果参数2为另外两个,参数3填如下
    结构体变量
           struct shmid_ds {
                   struct ipc_perm shm_perm;    /* Ownership and permissions */
                   size_t          shm_segsz;   /* Size of segment (bytes) */
                   time_t          shm_atime;   /* Last attach time */
                   time_t          shm_dtime;   /* Last detach time */
                   time_t          shm_ctime;   /* Last change time */
                   pid_t           shm_cpid;    /* PID of creator */
                   pid_t           shm_lpid;    /* PID of last shmat(2)/shmdt(2) */
                   shmatt_t        shm_nattch;  /* No. of current attaches */
                   ...
               };
              该结构体的第一个成员结构体:
              struct ipc_perm {
                   key_t          __key;    /* Key supplied to shmget(2) */
                   uid_t          uid;      /* Effective UID of owner */
                   gid_t          gid;      /* Effective GID of owner */               
                   uid_t          cuid;     /* Effective UID of creator */
                   gid_t          cgid;     /* Effective GID of creator */
                   unsigned short mode;     /* Permissions + SHM_DEST and
                                               SHM_LOCKED flags */
                   unsigned short __seq;    /* Sequence number */
               };
            返回值:成功返回0,失败返回-1并置位错误码

发送端:

c 复制代码
#include<myhead.h>
#define PAGE_SIZE 4096          //一页的大小
int main(int argc, const char *argv[])
{
    //1、创建key值
    key_t key = ftok("/", 'k');
    if(key == -1)
   {
        perror("ftok error");
        return -1;
   }
    printf("key = %#x\n", key);      //输出key值
    //2、通过key值创建共享内存段
    int shmid = -1;
    if((shmid= shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1)
   {
        perror("shmget error");
        return -1;
   }
    printf("shmid = %d\n", shmid);
    //3、将共享内存段映射到用户空间
    char *addr = (char *)shmat(shmid, NULL, 0);
    //NULL表示让系统自动寻找对齐页
    //0表示对该共享内存段的操作是读写操作打开
    if(addr == (void*)-1)
   {
        perror("shmat error");
        return -1;
   }
    printf("addr = %p\n", addr);        //输出共享内存段映射的地址
    //4、对共享内存进行操作
    while(1)
   {
        printf("请输入>>>");
        fgets(addr, PAGE_SIZE, stdin);       //从终端输入数据放入共享内存中
        addr[strlen(addr)-1] = 0;
        
        if(strcmp(addr, "quit") == 0)
       {
            break;
       }   }
    sleep(5);       //休眠5秒
    printf("结束吧\n");
    //5、取消映射
    if(shmdt(addr) == -1)
   {
        perror("取消映射\n");
        return -1;
   }
    return 0;
}

接收端:

c 复制代码
#include<myhead.h>
#define PAGE_SIZE 4096          //一页的大小
int main(int argc, const char *argv[])
{
    //1、创建key值
    key_t key = ftok("/", 'k');
    if(key == -1)
   {
        perror("ftok error");
        return -1;
   }
    printf("key = %#x\n", key);      //输出key值
    //2、通过key值创建共享内存段
    int shmid = -1;
    if((shmid= shmget(key, PAGE_SIZE, IPC_CREAT|0664)) == -1)
   {
        perror("shmget error");
        return -1;
   }
    printf("shmid = %d\n", shmid);
    //3、将共享内存段映射到用户空间
    char *addr = (char *)shmat(shmid, NULL, 0);
    //NULL表示让系统自动寻找对齐页
    //0表示对该共享内存段的操作是读写操作打开
    if(addr == (void*)-1)
   {
        perror("shmat error");
        return -1;
   }
    printf("addr = %p\n", addr);        //输出共享内存段映射的地址
    //4、对共享内存进行操作    while(1)
   {
        sleep(2);
        printf("读取到消息为:%s\n", addr);    //通过地址访问共享内存中的数据
        
        if(strcmp(addr, "quit") == 0)
       {
            break;
       }
   }
    //5、取消映射
    if(shmdt(addr) == -1)
   {
        perror("取消映射\n");
        return -1;
   }
    //6、删除共享内存段
    if(shmctl(shmid, IPC_RMID, NULL) == -1)
   {
        perror("shmctl error");
        return -1;
   }
    return 0;
}

注意

  • 共享内存是多个进程共享同一个内存空间,使用时可能会产生竞态,为了解决这个问题,共享
    内存一般会跟信号量一起使用,完成进程的同步功能
  • 共享内存VS消息队列:消息队列能够保证数据的不丢失性,而共享内存能够保证数据的时效性
  • 使用共享内存,跟正常使用指针是一样的,使用时,无需再进行用户空间与内核空间的切换
    了,所以说,共享内存是所有进程间通信方式中效率最高的一种通信方式。

8、信号量

  1. 原理图
  2. 信号量相关API
c 复制代码
1、创建key值
       #include <sys/types.h>
       #include <sys/ipc.h>
       key_t ftok(const char *pathname, int proj_id); //ftok("/", 'k');
       功能:通过给定的文件以及给定的一个随机值,创建出一个4字节整数的key值,用于system V 
IPC对象的创建
       参数1:一个文件路径,要求是已经存在的文件路径,提供了key值3字节的内容,其中,文件的设备
号占1字节,文件的inode号占2字节
       参数2:一个随机整数,取后8位(1字节)跟前面的文件共同组成key值,必须是非0的数字
       返回值:成功返回key值,失败返回-1并置位错误码
       
2、通过key值创建信号量集
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       int semget(key_t key, int nsems, int semflg);
       功能:通过给定的key值创建一个信号量集
       参数1::key值,该值可以是IPC_PRIVATE,也可以是ftok创建出来的,前者只用于亲缘进程间的
通信
       参数2:信号量数组中信号量的个数
       参数3:创建标识
       IPC_CREAT:创建并打开一个信号量集,如果信号量集已经存在,则直接打开
       IPC_EXCL:确保本次创建处理的是一个新的信号量集,如果信号量集已经存在,则报错,错
误码位EEXIST
       0664:该信号量集的操作权限
       eg: IPC_CREAT|0664  或者  IPC_CREAT|IPC_EXCL|0664
       返回值:成功返回信号量集的id,失败返回-1并置位错误码
       
       
3、关于信号量集的操作:P(申请资源)V(释放资源)   
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>       int semop(int semid, struct sembuf *sops, size_t nsops);
       功能:完成对信号量数组的操作
       参数1:信号量数据ID号
       参数2:有关信号量操作的结构体变量起始地址,该结构体中包含了操作的信号量编号和申请还是释
放的操作
       struct sembuf
       {
         unsigned short sem_num;  /* semaphore number */    要操作的信号量的编号
           short          sem_op;   /* semaphore operation */  要进行的操作,大于0
表示释放资源,小于0表示申请资源
           short          sem_flg;  /* operation flags */   操作标识位,0标识阻塞方
式,IPC_NOWAIT表示非阻塞
       }
       参数3:本次操作的信号量的个数
       返回值:成功返回0,失败返回-1并置位错误码
       
4、关于信号量集的控制函数
       #include <sys/types.h>
       #include <sys/ipc.h>
       #include <sys/sem.h>
       int semctl(int semid, int semnum, int cmd, ...);
       功能:执行有关信号量集的控制函数,具体控制内容取决于cmd
       参数1:信号量集的ID
       参数2:要操作的信号量的编号,编号是从0开始
       参数3:要执行的操作
       IPC_RMID:表示删除信号量集,cmd为该值时,参数2可以忽略,参数4可以不填
       SETVAL:表示对参数2对应的信号量进行设置操作(初始值)
       GETVAL:表示对参数2对应的信号量进行获取值操作
       SETALL:设置信号量集中所有信号量的值
       GETALL:获取信号量集中的所有信号量的值
       IPC_STAT:表示获取当前信号量集的属性
       IPC_SET:表示设置当前信号量集的属性、
       参数4:根据不同的cmd值,填写不同的参数值,所以该处是一个共用体变量
       union semun {
               int              val;    /* Value for SETVAL */      设置信号量的值
               struct semid_ds *buf;    /* Buffer for IPC_STAT, IPC_SET */   关于信号量集属性的操作
               unsigned short  *array;  /* Array for GETALL, SETALL */    对于信号量集中所有信号量的操作
               struct seminfo  *__buf;  /* Buffer for IPC_INFO
                                           (Linux-specific) */
           };
        返回值:成功时:SETVAL、IPC_RMID返回0,GETVAL返回当前信号量的值,失败返回-1并置位
错误码
        
        例如:
        1) 给0号信号量设置初始值为1
        union semun us;           //定义一个共用体变量
        us.val = 1;               //对该共用体变量赋值
        semctl(semid, 0, SETVAL, us);    //该函数就完成了对0号信号量设置初始值为1的操作
        
        2) 删除信号量集
        semctl(semid, 0, IPC_RMID);

注意:

  • 信号量集是完成多个进程间同步问题的,一般不进行信息的通信
  • 信号量集的使用,本质上是对多个value值进行管控,每个信号量控制一个进程,在进程执行
    前,申请一个信号量的资源,执行后,释放另一个信号量的资源
  • 如果当前进程申请的信号量值为0,则当前进程在申请处阻塞,直到其他进程将该信号量中的
    资源增加到大于0
相关推荐
小小晓.1 小时前
零基础C++小白突破
开发语言·c++
阿i索1 小时前
【C++学习笔记】【基础】4.string类(2)——模拟实现
c++·笔记·学习
我不是懒洋洋1 小时前
从零实现一个消息队列:生产消费与持久化
c++
玖玥拾2 小时前
C/C++ 数据结构(五)链表的应用、对象池
c语言·数据结构·c++·链表·对象池·双向链表
John_ToDebug2 小时前
Windows客户端热修复技术:从原理到工程实践
c++·经验分享·hook
凡人叶枫2 小时前
Effective C++ 条款37:绝不重新定义继承而来的缺省参数值
linux·c++·windows
王老师青少年编程2 小时前
2022年CSP-X复赛真题及题解(T4:摧毁)
c++·真题·csp·信奥赛·复赛·csp-x·摧毁
梓䈑2 小时前
C++大模型统一接入引擎(第三篇):模型管理、会话持久化与SDK门面封装的完整实现
数据库·c++
王燕龙(大卫)2 小时前
使用实时调度策略和无锁队列踩坑记录
c++