嵌入式养成计划-27-IO进线程----IPC----进程间通信机制

一个小demo

- 要求实现AB进程对话
1. A进程先发送一句话给B进程,B进程接收后打印
2. B进程再回复一句话给A进程,A进程接收后打印
3. 重复1.2步骤,当收到quit后,要结束AB进程
4. 提示:两根管道

俩小demo

消息队列实现AB进程对话;
共享内存和信号量集完成多进程字符串翻转与输出

六十三、 IPC----进程间通信机制

63.1 什么是IPC机制

  • IPC机制: Inter Process Communication,即进程间通信机制
  • 进程与进程间的用户空间相互独立,内核空间共享。所以如果要实现进程间的通信,需要使用进程间通信机制。

分类:

  1. 传统的进程间通信机制
    1. 无名管道 pipe
    2. 有名管道 fifo
    3. 信号 signal
  2. system v操作系统的IPC对象 (IPC对象只有一下三种)
    1. 消息队列 message queue
    2. 共享内存 shared memory
    3. 信号灯集 semaphore
  3. 可用于跨主机传输的通信
    1. 套接字 socket

63.2 管道

63.2.1 管道的原理

在进程的3G~4G的内核空间中,创建一个管道(特殊的文件),管道中的数据直接保存在内存中。
嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第1张图片

63.2.2 管道的特性

  1. 管道可以看成是一个特殊的文件:一般文件存储在磁盘上,而管道中的内容是直接存储在内核内存中的。
  2. 管道遵循先进先出的原则。(队列的方式实现)
  3. 管道的读操作是一次性的,如果对管道进行读操作,那么被读取的数据会从管道中删除。
  4. 管道是一种半双工通信方式。
    1. 单工:只能A发送数据给B,B不能发数据给A。单向通信。
    2. 半双工:同一个时间,只能单向通信。同一个时间内只能A发数据给B,或者B发数据给A.
    3. 全双工:同一个时间,能够双向通信。
  5. 管道的大小:64K = 64*1024 = 65536bytes;
  6. 当管道的读写端均关闭后,管道对应的内存空间会被释放。
  7. 对于管道的操作只能使用文件IO函数,因为需要直接操作内核空间。例如:open read write close,但是不能使用lseek
  8. 从管道中读取数据
    1. 当管道的读写端均存在
      1. 若管道中没有数据,read函数阻塞。
      2. 若管道中有10个数据,读取5个,实际读取5个
      3. 若管道中有5个数据,读取10个,实际读取5个
    2. 当管道的写端不存在
      1. 若管道有数据的时候,会先将管道中的数据读取出来。
        2 .若管道中没有数据,read函数不会阻塞,且立即返回0;
  9. 向管道中写入数据
    1. 当管道的读写端均存在
      1. 当管道写满,write函数阻塞。
    2. 当管道的读端不存在
      1. 此时只要尝试向管道中写入数据(只要调用了write函数),调用write函数的进程会收到一个管道破裂信号,该信号会导致当前进程退出。
      2. 管道破裂信号:SIGPIPE

63.2.3 无名管道(pipe)

  • 无名管道的特点
    1. 无名管道顾名思义,即没有名字的管道。在文件系统中不可见的管道文件。
    2. 无名管道只能用于具有亲缘关系的进程间通信。(父子进程,兄弟进程,爷孙进程)

为什么无名管道只能用于具有亲缘关系的进程间通信

  1. 无名管道在文件系统中不可见,所以无亲缘关系的进程无法拿到同一根管道的读写端。
  2. 子进程会克隆父进程的文件描述符表,所以可以在父进程中创建一根管道,然后fork一份资源给子进程,此时子进程可以拿到与父进程相同的文件描述符表。即可以拿到同一根无名管道的读写端。
  • pipe
功能:
	创建一个无名管道文件,同时打开管道的读写端。
原型:
       #include 

       int pipe(int pipefd[2]);
参数:
    int pipefd[2]:需要传入一个int类型的数组,且数组的容量为2;
                    用于存储读写端文件描述符;
                    pipefd[0]:读端文件描述符;
                    pipefd[1]:写端文件描述符;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

63.2.4 有名管道(fifo)

  • 有名管道就是名字可见的管道文件,但是数据依然存在于内核内存中,是不可见的。
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第2张图片

  • 有名管道的特点

    1. 有名字的管道文件,在文件系统中可见的管道文件。
    2. 有名管道可以用于任意进程间的通信。
  • 创建有名管道

    1. 用shell指令
    mkfifo 有名管道的路径及名字
    mkfifo ./myfifo
    
    1. mkfifo函数实现
    功能:创建一个有名管道文件;
    原型:
          #include 
          #include 
    
          int mkfifo(const char *pathname, mode_t mode);
    参数:
       char *pathname:指定要创建的有名管道路劲及名字;
       mode_t mode:文件创建时候的权限,真实权限为 (mode & ~umask);
    返回值:
       成功,返回0;
       失败,返回-1,更新errno; 
           文件已经存在导致的错误是合法错误 ,errno == 17
           #define EEXIST      17
    
  • 对有名管道的操作

    1. 与用文件IO函数操作普通文件一样
    2. open read write close
  • 有名管道的open规则

    #include 
    #include 
    #include 
    
    int open(const char *pathname, int flags);
    
    int flags:
    O_RDONLY 只读
    O_WRONLY 只写
    O_RDWR   读写
    ----以上三种必须包含一种,且只能包含一种----
    O_NONBLOCK  非阻塞选项
    
    1. flags == O_RDONLY;
      以只读方式打开FIFO,此时open函数阻塞。当有其他进程以写的方式打开同一根FIFO文件,此时open函数解除阻塞;
    2. flags == O_WRONLY
      以只读方式打开FIFO,此时open函数阻塞。当有其他进程以读的方式打开同一根FIFO文件,此时open函数解除阻塞;
    3. flags == O_RDWR
      以读写方式打开FIFO,此时open函数不阻塞。
    4. flags == O_RDONLY | O_NONBLOCK
      此时open函数不阻塞,open函数运行成功,此时读端打开成功。
    5. flags == O_WRONLY | O_NONBLOCK
      此时open函数运行失败,写端未打开。

操作同一根有名管道,只有读写端均存在此时open函数才能

63.3 信号(signal)

63.3.1 信号的概念

  • 信号的原理
    1. 信号是软件层次上,对中断的一种模拟
    2. 信号是一种异步通信方式。进程与进程互不干扰,各自根据cpu调度运行自己的任务。
      嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第3张图片
  • 进程对信号的处理方式
  1. 执行默认操作(缺省操作)
    1. 每个信号都有自己默认的处理函数,当信号发生的时候,执行默认处理函数。
  2. 忽略信号
    1. 当信号发生的时候,对信号不做处理。
    2. 但是有两个信号不能忽略 9) SIGKILL 19)SIGSTOP
  3. 捕获信号
    1. 自定义信号的处理函数,当信号发生的时候执行自定义处理函数
    2. 但是有两个信号不能捕获 9) SIGKILL 19)SIGSTOP
  • 常见的信号
kill     -l     查看所有信号     62个信号,其中32 33没有

 1) SIGHUP             2) SIGINT             3) SIGQUIT             4) SIGILL                 5) SIGTRAP
 6) SIGABRT             7) SIGBUS             8) SIGFPE             9) SIGKILL                10) SIGUSR1
11) SIGSEGV            12) SIGUSR2            13) SIGPIPE            14) SIGALRM                15) SIGTERM
16) SIGSTKFLT        17) SIGCHLD            18) SIGCONT            19) SIGSTOP                20) SIGTSTP
21) SIGTTIN            22) SIGTTOU            23) SIGURG            24) SIGXCPU                25) SIGXFSZ
26) SIGVTALRM        27) SIGPROF            28) SIGWINCH        29) SIGIO                30) SIGPWR
31) SIGSYS            34) SIGRTMIN        35) SIGRTMIN+1        36) SIGRTMIN+2            37) SIGRTMIN+3
38) SIGRTMIN+4        39) SIGRTMIN+5        40) SIGRTMIN+6        41) SIGRTMIN+7            42) SIGRTMIN+8
43) SIGRTMIN+9        44) SIGRTMIN+10        45) SIGRTMIN+11        46) SIGRTMIN+12            47) SIGRTMIN+13
48) SIGRTMIN+14        49) SIGRTMIN+15        50) SIGRTMAX-14        51) SIGRTMAX-13            52) SIGRTMAX-12
53) SIGRTMAX-11        54) SIGRTMAX-10        55) SIGRTMAX-9        56) SIGRTMAX-8            57) SIGRTMAX-7
58) SIGRTMAX-6        59) SIGRTMAX-5        60) SIGRTMAX-4        61) SIGRTMAX-3            62) SIGRTMAX-2
63) SIGRTMAX-1        64) SIGRTMAX    
  1. 硬件能够按出来信号
    2) SIGINT 默认处理函数:退出进程 ctrl + c
    3) SIGQUIT 默认处理函数:退出进程 ctrl +
    20) SIGTSTP 默认处理函数:挂起进程 ctrl + z (挂起进程,进程是没有结束的)
  2. 无法被忽略,捕获的信号
    9) SIGKILL 默认处理函数:退出进程 kill -9 pid 发送的就是9号信号
    19) SIGSTOP 默认处理函数:退出进程
  3. 常见的信号
    11) SIGSEGV 段错误信号,默认处理函数:退出进程
    13) SIGPIPE 管道破裂信号,默认处理函数:退出进程
    14) SIGALRM 时钟信号,默认处理函数:退出进程
    17) SIGCHLD 子进程退出后,父进程会收到该信号。

63.3.2 信号相关的处理函数

63.3.2.1 signal

功能:
	捕获信号,为信号注册新的处理函数;
原型:
       #include 

       typedef void (*sighandler_t)(int);

       sighandler_t signal(int signum, sighandler_t handler);  //sighandler_t handler ==> void (*handler)(int);
参数:
    int signum:指定要捕获的信号,可以填对应的编号2 或者宏SIGINT;
    sighandler_t handler:
        1. SIG_IGN,     忽略信号。919)无法忽略
        2. SIG_DFL,     执行默认操作,缺省操作
        3. 给信号注册新的处理函数。函数指针变量,回调函数,该指针可以指向返回值是void类型,参数列表是int类型的函数。
            该函数代表新注册的处理函数。
返回值:
     成功,返回注册后,上一个处理函数的首地址。
     失败,返回SIG_ERR,更新errno; 

嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第4张图片

验证任务

  1. 若在2号信号处理函数中不退出,再次触发2号信号,此时2号信号的处理函数会重新被载入吗。
    1.不会被重新载入
    . 若在2号信号处理函数中不退出,触发3号信号,此时3号信号的处理函数会被载入吗。
    1. 3号信号处理函数会被载入

注意:
当在某个信号A的处理函数内部时,若再次收到信号A,则此时A信号的信号处理函数不会被重新载入,
此时第二个信号A被屏蔽。

63.3.2.2 kill

功能:
	给指定进程或者进程组发送一个信号;
原型:
       #include 
       #include 

       int kill(pid_t pid, int sig);

63.3.2.3 raise

63.3.2.4 alarm

63.3.3 用信号的方式回收僵尸进程(重点!!)

  1. 子进程退出后,父进程会收到17)SIGCHLD信号。
  2. 父进程中捕获17)SIGCHLD信号,给该信号注册新的处理函数。在该新的处理函数中执行waitpid函数,回收僵尸进程。
  3. 当在信号A的处理函数内部时,再次触发A信号,会导致信号屏蔽,会造成多个子进程短时间内同时退出,父进程只会处理一个17号信号,导致僵尸进程收不干净的问题。
  4. 解决方式:当成功回收到僵尸进程后,再回收一次。直到没有僵尸进程,结束循环。即判断waitpid(-1, NULL, WNOHANG);的返回值
    1. = 0,有子进程,但是没有僵尸进程
    2. =-1,没有子进程,也没有僵尸进程

嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第5张图片

63.4 消息队列(message queue)

63.4.1 消息队列的概念

  • 消息队列的原理
    消息队列是在内核中创建一个容器(队列),进程需要将数据打包成结点,添加到队尾。或者从队列中读取结点,实现进程间通信。
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第6张图片
  • 消息队列的特点
  1. 消息队列是面向记录的,其中消息有特定的格式以及类型。
  2. 消息队列总体上是按照先进先出的原则。与管道对比,管道只能按照先进先出,但是消息队列可以根据消息类型选择进行先进先出。
  3. 消息队列独立于进程,等进程结束后,消息队列以及其中的内容不消失,除非手动删除或者操作系统重启。
  • 查看消息队列
    查看消息队列: ipcs
                  ipcs -q
                  
    删除消息队列:ipcrm -q  msqid
    

63.4.2 消息队列的函数

63.4.2.1 ftok

功能:
	需要使用到pathname提供的id号 和proj_id提供的8bits非0 参数,用于去计算键值,给msgget shmget semget函数使用。  
	简单来说,用户就是通过键值找IPC对象,只要键值不变,则找到的IPC对象就是同一个。
原型:
       #include 
       #include 

       key_t ftok(const char *pathname, int proj_id);
参数:
    char *pathname:路径及名字; (文件必须存在,且可访问即可)
    int proj_id:非0参数即可;
返回值:
    成功,返回key值;
    失败,返回-1,更新errno;

63.4.2.2 msgget

功能:
	通过key值找对应的消息队列,若不存在则需要创建,若存在则返回消息队列id号;
原型:
       #include 
       #include 
       #include 

       int msgget(key_t key, int msgflg);
参数:
    key_t key:指定要通过那个key值找消息队列;
    int msgflg:
            IPC_CREAT:若消息队列不存在则创建消息队列,若存在则忽略该选项;
                IPC_CREAT | 八进制权限: IPC_CREAT| 0664
            IPC_CREAT | IPC_EXCL :若消息队列不存在则创建消息队列,若存在则报错;
返回值:
    成功,返回非负整数,即消息队列id号,我们一般成为msqid;
    失败,返回-1,更新errno;

63.4.2.3 msgsnd

功能:
	向指定的消息队列中发送数据;
原型:
       #include 
       #include 
       #include 

       int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
参数:
    int msqid:消息队列的id号;
    void *msgp:指定要发送的消息包;消息包的通用格式如下;
           struct msgbuf {
               long mtype;       /* message type, must be > 0 */     消息类型,必须大于0
               char mtext[1];    /* message data */ 消息内容,可以根据需求改变成其他结构,但是大小由第三个参数msgsz指定
           };
    size_t msgsz:消息内容的大小,sizeof(真实的消息内容)int msgflg:     
        0:阻塞方式,当消息队列满了,该函数阻塞;
        IPC_NOWAIT:非阻塞方式,当消息队列满了,该函数不阻塞,立即返回失败情况,且errno == EAGAIN;
返回值:
    成功,返回0;
    失败,返回-1,更新errno;

63.4.2.4 msgrcv

功能:
	从指定的消息队列中读取数据;
原型:
       #include 
       #include 
       #include 

       ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
 int msgflg);
参数:
    int msqid:指定要从哪个消息队列中读取数据,填对应的消息队列id号;
    void *msgp:存储从消息队列中读取到的消息;消息包的通用格式如下;
           struct msgbuf {
               long mtype;       /* message type, must be > 0 */     消息类型,必须大于0
               char mtext[1];    /* message data */ 消息内容,可以根据需求改变成其他结构,但是大小由第三个参数msgsz指定
           };
    size_t msgsz:指定要读取的消息内容的大小,sizeof(真实的消息内容)long msgtyp:指定要读取的消息类型;
        msgtyp == 0, 读取消息队列中第一条消息; 即按照先进先出的原则读取
        msgtyp >0,   读取消息队列中第一条消息类型 == msgtyp参数的消息;
                     若在msgflg中指定了MSG_EXCEPT,则会读取消息队列中第一条消息类型 != msgtyp参数的消息;
                             vi -t MSG_EXCPET  ==>  #define MSG_EXCEPT  020000
        msgtyp <0,   读取消息队列中第一条最小的消息,且该消息类型<= msgtyp参数的绝对值                            


    int msgflg:
        0:阻塞方式读取,当消息队列中没有要读取的数据的时候,该函数阻塞;
        IPC_NOWAIT:非阻塞,当消息队列中没有要读取的数据的时候,该函数不阻塞,函数运行失败,errno == ENOMSG;
        MSG_EXCEPT:
返回值:
    成功,返回成功读取到的字节数;
    失败,返回-1,更新errno; 

代码示例:

若消息队列中有消息:
    mtype 100   101  99   100  101
    mtext aaa   bbb  ccc  ddd  eee
  1. msgtyp == 0
    while(1)                                                                   
    {
        //阻塞方式读取消息队列中第一条消息,先进先出的原则
        //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, 0);
        
        //非阻塞方式读取消息队列中第一条消息,先进先出的原则
        res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 0, IPC_NOWAIT);
        if(res < 0)
        {
            perror("msgrcv");
            return -1;
        }
        printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
    }
    ​
    输出顺序:
     100 aaa   101 bbb   99 ccc   100 ddd    101 eee
    
  2. msgtyp > 0
        while(1)
        {
            //1.阻塞方式读取消息队列中第一条消息类型 == 101 的消息
            //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, 0);//2.非阻塞方式读取消息队列中第一条消息 == 101 的消息
            //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT);
                                                                                      
            //3.非阻塞方式读取消息队列中第一条消息类型 != 101的消息 
            res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), 101, IPC_NOWAIT|020000);
            if(res < 0)
            {
                perror("msgrcv");
                return -1;
            }
            printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
        }
    注释1,2的现象:
        101 bbb     101 eee
    第3个的现象:
        100 aaa    99 ccc   100 ddd    
    
  3. msgtyp <0
        while(1)
        {
            //阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
            res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, 0);
           
            //非阻塞方式读取消息队列中<=msgtyp参数指定的消息类型中最小的那条消息;
            //res = msgrcv(msqid, &rcv, sizeof(rcv.mtext), -100, IPC_NOWAIT);if(res < 0)
            {
                perror("msgrcv");
                return -1;
            }
            printf("res=%ld : %ld %s\n", res, rcv.mtype, rcv.mtext);
        }
    ​
    现象:
    res=128 : 99 ccc
    res=128 : 100 aaa
    res=128 : 100 ddd
    

63.4.2.5 msgctl

功能:
	控制消息队列,常用于删除消息队列;
原型:
       #include 
       #include 
       #include 

       int msgctl(int msqid, int cmd, struct msqid_ds *buf);
参数:
    int msqid:消息队列的id号;
    int cmd:
        IPC_RMID:删除消息队列; 第三个参数填NULL;
        IPC_STAT:获取消息队列属性; 存储到第三个参数指向的内存空间中
    struct msqid_ds *buf: 
返回值:
    成功,返回0;
    失败,返回-1,更新errno;        

63.5 共享内存(shared memory)

63.5.1 共享内存的概念

  • 共享内存的原理
    在内核内存中创建一个共享内存,共享内存可以被分别映射到不同的进程用户空间中,每个进程在各自的用户空间中就可以操作同一个共享内存,从而可以操作同一个物理地址空间。
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第7张图片
  • 共享内存的特点
    1. 共享内存是 最高效的 进程间通信方式。
      1. 进程可以直接读写共享内存中的数据,不需要任何数据拷贝。
    2. 共享内存是创建在内核空间中的,但是可以被映射到多个不同进程的用户空间中。
    3. 多个进程可以同时访问共享内存,此时共享内存相当于是一个临界资源,因此使用共享内存的时候一般要结合进程间的同步互斥机制(信号灯集,信号灯)
    4. 共享内存独立于进程,等进程结束后,共享内存以及其中的内容不消失,除非手动删除或者操作系统重启。
    5. 共享内存中的数据即使被读取后,依然存在,不会被删除。
  • 查看共享内存
查看共享内存: ipcs
              ipcs -m
              
删除共享内存:ipcrm -m  shmid

63.5.2 共享内存的函数

63.5.2.1 ftok

功能:
	需要使用到pathname提供的id号 和proj_id提供的8bits非0 参数,用于去计算键值,给msgget shmget semget函数使用。  
	简单来说,用户就是通过键值找IPC对象,只要键值不变,则找到的IPC对象就是同一个。
原型:
       #include 
       #include 

       key_t ftok(const char *pathname, int proj_id);
参数:
    char *pathname:路径及名字; (文件必须存在,且可访问即可)
    int proj_id:非0参数即可;
返回值:
    成功,返回key值;
    失败,返回-1,更新errno;

63.5.2.2 shmget

功能:
	通过key值找对应的共享内存,若不存在则需要创建,若存在则返回共享内存id号;
原型:
       #include 
       #include 

       int shmget(key_t key, size_t size, int shmflg);

参数:
    key_t key:指定要通过那个key值找共享内存;
    size_t size:指定创建多大的共享内存;
    int shmflg:
            IPC_CREAT:若共享内存不存在则创建共享内存,若存在则忽略该选项;
                IPC_CREAT | 八进制权限: IPC_CREAT| 0664
            IPC_CREAT | IPC_EXCL :若共享内存不存在则创建共享内存,若存在则报错;
返回值:
    成功,返回非负整数,即共享内存id号,我们一般成为shmid;
    失败,返回-1,更新errno;

63.5.2.3 shmat

功能:
	将共享内存映射到用户空间中;
原型:
       #include 
       #include 

       void *shmat(int shmid, const void *shmaddr, int shmflg);
参数:
    int shmid:指定要映射的共享内存的id号;
    void *shmaddr:指定要将共享内存映射到用户空间的什么位置,例如想要映射到0x80000000该地址上,就需要填(void*)0x80000000NULL,代表让编译器自动映射;
    int shmflg:
            0 ,代表映射的进程对共享内存可读可写;
            SHM_RDONLY:只读;
返回值:
    成功,返回共享内存映射到用户空间的地址;
    失败,返回-1,更新errno;   
                                                                             
注意:获取到的映射空间的首地址的指向不允许修改,若修改后会导致首地址找不到,导致内存泄漏,与堆空间首地址不能改变的概念一致

嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第8张图片

63.5.2.4 shmdt

功能:
	断开共享内存的映射
原型:
       #include 
       #include 
       

       int shmdt(const void *shmaddr);
参数:
    void *shmaddr:将哪一块用户空间与共享内存断开映射;
返回值:
    成功,返回0;
    失败,返回-1,更新errno

63.5.2.5 shmctl

功能:控制共享内存,常用于删除共享内存;
原型:
       #include 
       #include 

       int shmctl(int shmid, int cmd, struct shmid_ds *buf);
参数:
    int msqid:共享内存的id号;
    int cmd:
        IPC_RMID:删除共享内存; 第三个参数填NULL;
        IPC_STAT:获取共享内存属性; 存储到第三个参数指向的内存空间中
    struct shmid_ds *buf: 
返回值:
    成功,返回0;
    失败,返回-1,更新errno;        

63.6 信号灯集(semaphore)(信号量数组)(信号量集)

63.6.1 原理

  1. 信号灯:又称之为信号量,用于线程之间的同步互斥机制
  2. 信号灯集:又称之为信号量数组,集合中有一个或多个信号灯。信号灯集中灯的编号从0开始,其实就是数组下标。
  3. 核心操作:PV操作
    1. P操作:申请信号量的操作,减操作,当信号量的值不够减阻塞。
    2. V操作:释放信号量的操作,加操作
    3. wait for zero:阻塞等待信号量的值为0,解除阻塞。当信号量的值不为0阻塞,当为0时解除阻塞。
  • 信号灯的查看
ipcs
ipcs -s 

ipcrm -s semid

63.5.2 信号灯集的函数

63.5.2.1 ftok

功能:
	通过pathname参数给定文件的id号 和 proj_id给定的非0参数,计算key值(键值),提供给msgget shmget semget函数使用;
	只要key值不变,则通过key值找到的IPC对象就是同一个。   
原型:
       #include 
       #include 

       key_t ftok(const char *pathname, int proj_id);
参数:
    char *pathname:文件路径以及名字; 文件必须存在且可以访问即可;
    int proj_id:非0参数,用户自定义;
返回值:
    成功,返回key值;
    失败,返回-1,更新errno;  

63.5.2.2 semget

功能:
	通过key值找对应的信号灯集,若不存在则需要创建,若存在则返回信号灯集id号; 创建成功后,信号灯的值默认为0;
原型:
       #include 
       #include 
       #include 

       int semget(key_t key, int nsems, int semflg);
参数:
    key_t key:ftok计算出来的键值;
    int nsems:信号灯集中有几个信号灯;
    int semflg:
        IPC_CREAT:如果信号灯集不存在,则创建,如果存在则忽略该选项;
            IPC_CREAT | 0664
        IPC_CREAT|IPC_EXCL:如果信号灯集存在,则该函数运行失败,errno == EEXIST;
返回值:
    成功,返回非负数,即信号灯集id , semid;
    失败,返回-1,更新errno;

63.5.2.3 semop

功能:
	P操作(阻塞,不够减阻塞) V操作  wait for zero操作(不为0阻塞)
原型:
       #include 
       #include 
       #include 

       int semop(int semid, struct sembuf *sops, size_t nsops);
参数:
    int semid:指定要操作的信号灯集:
    struct sembuf *sops:
        struct membuf{
           unsigned short sem_num;  /* semaphore number */     指定要控制登记中信号灯的编号,从0开始
           short          sem_op;   /* semaphore operation */  填正整数,V操作,例如+2,代表在信号灯原来的值上+2;
                                                         填负整数,P操作,例如-2,代表在信号灯原来的值上-2,若不够减阻塞。
                                                         0,wait-for-zero操作,信号灯的值为0不阻塞,不为0阻塞。
           short          sem_flg;  /* operation flags */ 0,代表阻塞方式。该阻塞的时候阻塞。P操作, wait for zero操作该阻塞时阻塞。
                                                          IPC_NOWAIT:非阻塞方式,该阻塞的时候不阻塞。此时函数运行失败.
        }
    size_t nsops:指定要控制的信号灯集中灯的个数;
返回值:
    成功,返回非负数,即信号灯集id , semid;
    失败,返回-1,更新errno;

63.5.2.4 示例代码

  • P操作
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第9张图片
  • V操作
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第10张图片
  • wait for zero
    嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第11张图片

63.5.2.5 semctl

功能:
	控制信号灯集;
原型:
       #include 
       #include 
       #include 

       int semctl(int semid, int semnum, int cmd, ...);
参数:
    int semid:指定要控制哪个信号灯集;
    int semnum:指定要控制灯集中的哪个灯,填对应的编号;
    int cmd:
        IPC_STAT:获取信号灯集的属性,存储到最后一个参数中,最后一个参数的类型:struct semid_ds *buf
        IPC_RMID:删除信号灯集,第二个参数无意义,最后一个参数不用填;
        GETALL:获取信号灯集中,所有灯的值。第二个参数无意义,最后一个参数的类型:unsigned short  *array
        GETVAL:获取信号灯集中,指定灯的值,最后一个参数不用填; 获取到的数据从返回值返回;
        SETALL:设置信号灯集中,所有灯的值。第二个参数无意义,最后一个参数的类型:unsigned short  *array
        SETVAL:获取信号灯集中,指定灯的值,最后参数类型:int val
        
    ... :
            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) */
           };
           
            
if(semctl(semid, 0, IPC_RMID) < 0)
{
    perror("semctl");                   
    return -1;
}

63.5.2.6 示例代码

嵌入式养成计划-27-IO进线程----IPC----进程间通信机制_第12张图片

你可能感兴趣的:(服务器,linux,网络,c++)