• [Linux系统编程]_进程的通信(三)


    嵌入式之路,贵在日常点滴

                                                                    ---阿杰在线送代码

    目录

    进程间通信(IPC)介绍 

    一、管道

    二、FIFO

    三、消息队列

    ftok 

    四、 共享内存

    五、信号

    信号概述

    信号处理函数的注册

    信号处理发送函数

    六、信号量 (管理临界资源)

    临界资源

    七、通信方式总结


    进程间通信(IPC)介绍 

    进程间通信(IPC,InterProcess Communication)是指在不同进程之间传播或交换信息。

    IPC的方式通常有管道(包括无名管道和命名管道)、消息队列、信号量、共享存储、Socket、Streams等。其中 Socket和Streams支持不同主机上的两个进程IPC。--》面试可能会问

    以Linux中的C语言编程为例。

    一、管道

    管道,通常指无名管道,是 UNIX 系统IPC最古老的形式。

    1、特点:

    1. 它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端。

    2. 它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间)。

    3. 它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write 等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。

    2、原型: 

    1. #include
    2. int pipe(int fd[2]); // 返回值:若成功返回0,失败返回-1

    当一个管道建立时,它会创建两个文件描述符:fd[0]为读而打开,fd[1]为写而打开。如下图:

    要关闭管道只需将这两个文件描述符关闭即可。 

    3、例子

    单个进程中的管道几乎没有任何用处。所以,通常调用 pipe 的进程接着调用 fork,这样就创建了父进程与子进程之间的 IPC 通道。如下图所示:

    若要数据流从父进程流向子进程,则关闭父进程的读端(fd[0])与子进程的写端(fd[1]);反之,则可以使数据流从子进程流向父进程。

    1. #include "stdio.h"
    2. #include
    3. #include "string.h"
    4. #include
    5. int main()
    6. {
    7. int fd[2];
    8. int pid;
    9. char buf[128];
    10. //int pipe(int pipefd[2]);
    11. if(pipe(fd) == -1){
    12. printf("creat pipe failed\n");
    13. }
    14. pid = fork();//创建子进程
    15. if(pid<0){
    16. printf("creat child failed\n");
    17. }
    18. else if(pid > 0){
    19. printf("this is father\n");
    20. close(fd[0]);
    21. write(fd[1],"hello from father",strlen("hello from father"));
    22. wait(NULL);//不能让父进程先结束,让父进程在这边等着
    23. }
    24. else{
    25. printf("this is child\n");
    26. close(fd[1]);
    27. read(fd[0],buf,128);//把fd[0]端的数据读到buf里面,如果fd[0]里面没数据,read就会阻塞
    28. printf("read from father:%s\n",buf);
    29. exit(0);
    30. }
    31. return 0;
    32. }

    运行结果:

    当程序运行到子进程处,readbuf里面数据为空,这时read堵塞,父进程往fd[1]里面写数据,写入之后,父进程先不结束而是等待子进程运行之后再结束。 如何体现呢,我们把上面的代码进行个微改,在父进程加个睡眠3s

    1. #include "stdio.h"
    2. #include
    3. #include "string.h"
    4. #include
    5. int main()
    6. {
    7. int fd[2];
    8. int pid;
    9. char buf[128];
    10. //int pipe(int pipefd[2]);
    11. if(pipe(fd) == -1){
    12. printf("creat pipe failed\n");
    13. }
    14. pid = fork();
    15. if(pid<0){
    16. printf("creat child failed\n");
    17. }
    18. else if(pid > 0){
    19. sleep(3);
    20. printf("this is father\n");
    21. close(fd[0]);
    22. write(fd[1],"hello from father",strlen("hello from father"));
    23. wait(NULL);
    24. }
    25. else{
    26. printf("this is child\n");
    27. close(fd[1]);
    28. read(fd[0],buf,128);
    29. printf("read from father:%s\n",buf);
    30. exit(0);
    31. }
    32. return 0;
    33. }

    运行结果: 

    二、FIFO

    FIFO,也称为命名管道,它是一种文件类型

    1、特点

    1. FIFO可以在无关的进程之间交换数据,与无名管道不同。

    2. FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。

    2、原型

    1. #include
    2. 返回值:成功返回0,出错返回-1
    3. int mkfifo(const char *pathname, mode_t mode);
    4. char *pathname:管道名字,mode:管道权限,0600可读可写 

    其中的 mode 参数与open函数中的 mode 相同。一旦创建了一个 FIFO,就可以用一般的文件I/O函数操作它。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. int main()
    7. {
    8. if((mkfifo("./file",0600) == -1) && errno != EEXIST)//如果管道创建失败且失败的原因不是已存在
    9. {
    10. printf("make fifo failed\n");
    11. perror("why");
    12. }
    13. return 0;
    14. }

      

    当 open 一个FIFO时,是否设置非阻塞标志(O_NONBLOCK)的区别:

    • 若没有指定O_NONBLOCK(默认),只读 open 要阻塞到某个其他进程为写而打开此 FIFO。类似的,只写 open 要阻塞到某个其他进程为读而打开它。

    • 若指定了O_NONBLOCK,则只读 open 立即返回。而只写 open 将出错返回 -1 如果没有进程已经为读而打开该 FIFO,其errno置ENXIO。

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. int main()
    8. {
    9. char buf[30] = {0};
    10. int nread = 0;
    11. //如果管道创建失败且失败的原因不是已存在
    12. if((mkfifo("./file",0600) == -1) && errno != EEXIST)
    13. {
    14. printf("make fifo failed\n");
    15. perror("why");
    16. }
    17. int fd = open("./file",O_RDONLY);
    18. printf("open success\n");
    19. while(1){
    20. nread = read(fd,buf,30);
    21. printf("read %d byte from fifo,context:%s\n",nread,buf);
    22. }
    23. return 0;
    24. }
    25. 若没有指定(默认),只读open到阻塞到其他进程为写而打开次fifo,
    26. 只写open要阻塞到其他进程为读而打开次fifo。
    27. #include
    28. #include
    29. #include
    30. #include
    31. #include
    32. #include
    33. int main()
    34. {
    35. int cnt = 0;
    36. char *str = "message from fifo";
    37. int fd = open("./file",O_WRONLY);
    38. printf("write open success\n");
    39. while(1){
    40. write(fd,str,strlen(str));
    41. sleep(1);
    42. if(cnt == 5){
    43. break;
    44. }
    45. }
    46. close(fd);
    47. return 0;
    48. }

    两者即时通信才能读写成功,两个进程间通信用fifo实现 

    三、消息队列

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

    1、特点

    1. 消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。

    2. 消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。

    3. 消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。

    2、原型

    1. #include
    2. 创建或打开消息队列:成功返回队列ID,失败返回-1
    3. int msgget(key_t key, int flag);
    4. key 队列索引值,通过索引值找到队列;flag打开队列的方式
    5. 添加消息:成功返回0,失败返回-1
    6. int msgsnd(int msqid, const void *ptr, size_t size, int flag);
    7. msgid:由msgget函数返回的消息队列标识码
    8. *ptr:指针指向准备发送的消息
    9. size:指向的消息的长度(不包括消息类型的long int长整型)
    10. flag:默认为0
    11. 读取消息:成功返回消息数据的长度,失败返回-1
    12. int msgrcv(int msqid, void *ptr, size_t size, long type,int flag);
    13. 控制消息队列:成功返回0,失败返回-1
    14. 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 看做优先级的权值。(其他的参数解释,请自行Google之) 

    3、原理

    • 获取队列
    • 写队列 \ 读队列 

    发送消息: 

    1. #include "stdio.h"
    2. #include
    3. #include
    4. #include
    5. #include "string.h"
    6. //int msgget(key_t key, int msgflg);
    7. //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    8. //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    9. // int msgflg);
    10. struct msgbuf {
    11. long mtype; /* message type, must be > 0 */
    12. char mtext[128]; /* message data */
    13. };
    14. int main()
    15. {
    16. struct msgbuf sendBuf = {888,"this is message from quen"};
    17. struct msgbuf readBuf;
    18. 1、获取
    19. int msgId = msgget(0x1234,IPC_CREAT|0777);
    20. //如果有该队列则直接打开,没有则创建 0777:权限 可读可写可执行
    21. if(msgId == -1){
    22. printf("get que failuer\n");
    23. }
    24. 2、既发送
    25. msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
    26. printf("send over\n");
    27. 3、又接收
    28. msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
    29. printf("reaturn from get:%s\n",readBuf.mtext);
    30. return 0;
    31. }

    读取消息: 

    1. #include "stdio.h"
    2. #include
    3. #include
    4. #include
    5. #include "string.h"
    6. //int msgget(key_t key, int msgflg);
    7. //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    8. //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    9. // int msgflg);
    10. struct msgbuf {
    11. long mtype; /* message type, must be > 0 */
    12. char mtext[128]; /* message data */
    13. };
    14. int main()
    15. {
    16. struct msgbuf readBuf;
    17. 1、获取队列
    18. int msgId = msgget(0x1234,IPC_CREAT|0777);
    19. if(msgId == -1){
    20. printf("get que failuer\n");
    21. }
    22. 2、接收
    23. msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
    24. //没有读到数据时 会一直阻塞在这
    25. printf("read from que:%s\n",readBuf.mtext);
    26. 3、发送
    27. struct msgbuf sendBuf = {988,"thank you for reach"};
    28. msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
    29. return 0;
    30. }

    运行结果:

    ftok 

    系统建立IPC通讯 (消息队列信号量共享内存) 时必须指定一个ID值。通常情况下,该id值通过ftok函数得到。 

    头文件

    #include <sys/types.h>

    #include

    函数原型:

    key_t ftok( const char * fname, int id )

    fname就是你指定的文件名(已经存在的文件名),一般使用当前目录,如:

    key_t key;

    key = ftok(".", 1); 这样就是将fname设为当前目录。

    id是子序号。虽然是int类型,但是只使用8bits(1-255)。

    在一般的UNIX实现中,是将文件的索引节点号取出,前面加上子序号得到key_t的返回值。

    如指定文件的索引节点号为65538,换算成16进制为0x010002,而你指定的ID值为38,换算成16进制为0x26,则最后的key_t返回值为0x26010002。

    查询文件索引节点号的方法是: ls -i

    1. #include "stdio.h"
    2. #include
    3. #include
    4. #include
    5. #include "string.h"
    6. //int msgget(key_t key, int msgflg);
    7. //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    8. //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    9. // int msgflg);
    10. struct msgbuf {
    11. long mtype; /* message type, must be > 0 */
    12. char mtext[128]; /* message data */
    13. };
    14. int main()
    15. struct msgbuf readBuf;
    16. key_t key;
    17. key = ftok(".",'1');
    18. printf("key=%x\n",key);
    19. int msgId = msgget(key,IPC_CREAT|0777);
    20. if(msgId == -1){
    21. printf("get que failuer\n");
    22. }
    23. msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),888,0);
    24. printf("read from que:%s\n",readBuf.mtext);
    25. struct msgbuf sendBuf = {988,"thank you for reach"};
    26. msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
    27. msgctl(msgId,IPC_RMID,NULL);//用完队列把队列去除
    28. return 0;
    29. }
    1. #include "stdio.h"
    2. #include
    3. #include
    4. #include
    5. #include "string.h"
    6. //int msgget(key_t key, int msgflg);
    7. //int msgsnd(int msqid, const void *msgp, size_t msgsz, int msgflg);
    8. //ssize_t msgrcv(int msqid, void *msgp, size_t msgsz, long msgtyp,
    9. // int msgflg);
    10. struct msgbuf {
    11. long mtype; /* message type, must be > 0 */
    12. char mtext[128]; /* message data */
    13. };
    14. int main()
    15. {
    16. struct msgbuf sendBuf = {888,"this is message from quen"};
    17. struct msgbuf readBuf;
    18. key_t key;
    19. key = ftok(".",'1');
    20. printf("key=%x\n",key);
    21. int msgId = msgget(key,IPC_CREAT|0777);
    22. if(msgId == -1){
    23. printf("get que failuer\n");
    24. }
    25. msgsnd(msgId,&sendBuf,strlen(sendBuf.mtext),0);
    26. printf("send over\n");
    27. msgrcv(msgId,&readBuf,sizeof(readBuf.mtext),988,0);
    28. printf("reaturn from get:%s\n",readBuf.mtext);
    29. msgctl(msgId,IPC_RMID,NULL);
    30. return 0;
    31. }

    四、 共享内存

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

    1、特点

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

    2. 因为多个进程可以同时操作,所以需要进行同步。

    3. 信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问。

    2、原型

    1. #include
    2. // 创建或获取一个共享内存:成功返回共享内存ID,失败返回-1
    3. int shmget(key_t key, size_t size, int flag);
    4. (1)生成键值
    5. (2)开辟的大小必须以M为基本单位
    6. (3)共享内存权限
    7. // 连接共享内存到当前进程的地址空间:成功返回指向共享内存的指针,失败返回-1
    8. void *shmat(int shm_id, const void *addr, int flag);
    9. 1)共享内存的ID
    10. 2)一般写0,让系统自动分配共享内存的地址
    11. 3)一般写0,内存可读可写
    12. // 断开与共享内存的连接:成功返回0,失败返回-1
    13. int shmdt(void *addr);
    14. 1)共享内存连接成功后返回的指针
    15. // 控制共享内存的相关信息:成功返回0,失败返回-1
    16. int shmctl(int shm_id, int cmd, struct shmid_ds *buf);
    17. 1)共享内存ID
    18. 2)IPC_RMID,一般写这个
    19. 3)不关心这个写0

    当用shmget函数创建一段共享内存时,必须指定其 size;而如果引用一个已存在的共享内存,则将 size 指定为0 。

    当一段共享内存被创建以后,它并不能被任何进程访问。必须使用shmat函数连接该共享内存到当前进程的地址空间,连接成功后把共享内存区对象映射到调用进程的地址空间,随后可像本地空间一样访问。

    shmdt函数是用来断开shmat建立的连接的。注意,这并不是从系统中删除该共享内存,只是当前进程不能再访问该共享内存而已。

    shmctl函数可以对共享内存执行多种操作,根据参数 cmd 执行相应的操作。常用的是IPC_RMID(从系统中删除该共享内存)。

    3、编程实现步骤

    1. 创建或打开共享内存
    2. 映射:将共享内存映射到要通信的A进程和B进程
    3. 数据交换
    4. 释放共享内存
    5. 删除共享内存

      

    1. #include
    2. #include
    3. #include "stdio.h"
    4. #include "stdlib.h"
    5. #include
    6. #include
    7. //int shmget(key_t key, size_t size, int shmflg);
    8. int main()
    9. {
    10. int shmid;
    11. char *shmaddr;
    12. key_t key;
    13. key = ftok(".",1);
    14. /*共享内存的创建或者打开*/
    15. shmid = shmget(key,1024*4,IPC_CREAT|0666);//共享内存的大小是以兆对齐的
    16. if(shmid == -1){
    17. printf("shmget error\n");
    18. exit(-1);
    19. }
    20. /*将共享内存映射到要通信的进程*/
    21. shmaddr = shmat(shmid,0,0);//0代表默认方式
    22. printf("shamt success\n");
    23. /*往共享内存中写入数据*/
    24. strcpy(shmaddr,"a jie zai xian song dai ma");
    25. /*延迟5s 释放共享内存 然后 删除共享内存*/
    26. sleep(5);//延时5秒为使从共享内存读数据有缓冲时间
    27. shmdt(shmaddr);
    28. shmctl(shmid,IPC_RMID,0);
    29. printf("quit\n");
    30. return 0;
    31. }
    1. #include
    2. #include
    3. #include "stdio.h"
    4. #include "stdlib.h"
    5. #include
    6. #include
    7. //int shmget(key_t key, size_t size, int shmflg);
    8. int main()
    9. {
    10. int shmid;
    11. char *shmaddr;
    12. key_t key;
    13. key = ftok(".",1);
    14. /*获取共享内存*/
    15. shmid = shmget(key,1024*4,0);
    16. if(shmid == -1){
    17. printf("shmget error\n");
    18. exit(-1);
    19. }
    20. /*挂载共享内存*/
    21. shmaddr = shmat(shmid,0,0);
    22. printf("shamt success\n");
    23. /*读取共享内存数据*/
    24. printf("data: %s\n",shmaddr);
    25. /*释放共享内存*/
    26. shmdt(shmaddr);
    27. //因为写已经删除共享内存,所以这边不需要删除了
    28. printf("quit\n");
    29. return 0;
    30. }

    运行结果:

    从共享内存读数据时,必须要在这延时的5秒(有一个延迟)内进行,否则共享内存将被释放,无法与共享内存建立联系。 

    1. ipcs -m 查看共享内存的ID值和大小
    2. ipcrm -m shmid 移除未释放的共享内存

    五、信号

    对于 Linux来说,实际信号是软中断,许多重要的程序都需要处理信号。信号,为 Linux 提供了一种处理异步事件的方法。比如,终端用户输入了 ctrl+c 来中断程序,会通过信号机制停止一个程序。 

    信号概述

    1. 信号的名字和编号:
      每个信号都有一个名字和编号,这些名字都以“SIG”开头,例如“SIGIO ”、“SIGCHLD”等等。
      信号定义在signal.h头文件中,信号名都定义为正整数。
      具体的信号名称可以使用kill -l来查看信号的名字以及序号,信号是从1开始编号的,不存在0号信号。kill对于信号0又特殊的应用。

    2. 信号的处理:信号的处理有三种方法,分别是:忽略、捕捉和默认动作

    • 忽略信号,大多数信号可以使用这个方式来处理,但是有两种信号不能被忽略(分别是 SIGKILLSIGSTOP)。因为他们向内核和超级用户提供了进程终止和停止的可靠方法,如果忽略了,那么这个进程就变成了没人能管理的的进程,显然是内核设计者不希望看到的场景
    • 捕捉信号,需要告诉内核,用户希望如何处理某一种信号,说白了就是写一个信号处理函数,然后将这个函数告诉内核。当该信号产生时,由内核来调用用户自定义的函数,以此来实现某种信号的处理。
    • 系统默认动作,对于每个信号来说,系统都对应由默认的处理动作,当发生了该信号,系统会自动执行。不过,对系统来说,大部分的处理方式都比较粗暴,就是直接杀死该进程。
      具体的信号默认动作可以使用man 7 signal来查看系统的具体定义。在此,我就不详细展开了,需要查看的,可以自行查看。也可以参考 《UNIX 环境高级编程(第三部)》的 P251——P256中间对于每个信号有详细的说明。

    忽略信号:对信号不做任何处理,但是有两个信号不能忽略:即SIGKILL及SIGSTOP。

    捕捉信号:定义信号处理函数,当信号发生时,执行相应的处理函数。

    执行缺省操作:Linux对每种信号都规定了默认操作

    了解了信号的概述,那么,信号是如何来使用呢?

    其实对于常用的 kill 命令就是一个发送信号的工具,kill 9 PID来杀死进程。比如,我在后台运行了一个 top 工具,通过 ps 命令可以查看他的 PID,通过 kill 9 来发送了一个终止进程的信号来结束了 top 进程。如果查看信号编号和名称,可以发现9对应的是 9) SIGKILL,正是杀死该进程的信号。而以下的执行过程实际也就是执行了9号信号的默认动作——杀死进程。


    对于信号来说,最大的意义不是为了杀死信号,而是实现一些异步通讯的手段,那么如何来自定义信号的处理函数呢?

    信号处理函数的注册

    信号处理函数的注册不只一种方法,分为入门版和高级版

    1. 入门版:函数signal
    2. 高级版:函数sigaction

    信号处理发送函数

    信号发送函数也不止一个,同样分为入门版和高级版
    1.入门版:kill
    2.高级版:sigqueue

    sighandler_t signal(int signum, sighandler_t handler)

    第一个参数signum:表示要捕捉哪个信号。  

    第二个参数handler:函数指针,当捕捉到这个信号时,执行信号处理函数

    (1)捕捉信号 

    调用信号:

    1. #include "stdio.h"
    2. #include
    3. //typedef void (*sighandler_t)(int);
    4. //sighandler_t signal(int signum, sighandler_t handler);
    5. void handler(int signum)
    6. {
    7. printf("get signum=%d\n",signum);
    8. printf("never quit\n");
    9. }
    10. int main()
    11. {
    12. signal(SIGINT,handler);
    13. while(1);
    14. return 0;
    15. }

    发送信号; 

    1. #include "stdio.h"
    2. #include
    3. #include
    4. int main(int argc,char **argv)
    5. {
    6. int signum;
    7. int pid;
    8. signum = atoi(argv[1]);
    9. pid = atoi(argv[2]);
    10. printf("signum=%d,pid=%d",signum,pid);
    11. kill(pid,signum);
    12. printf("send signal ok");
    13. return 0;
    14. }

    运行结果 

    (2)忽略信号
    ps -aux|grep -xinhao:查看后缀为xinhao的信号,用kill -9 信号号来杀死信号 

    1. #include "stdio.h"
    2. #include
    3. //typedef void (*sighandler_t)(int);
    4. //sighandler_t signal(int signum, sighandler_t handler);
    5. void handler(int signum)
    6. {
    7. printf("get signum=%d\n",signum);
    8. printf("never quit\n");
    9. }
    10. int main()
    11. {
    12. signal(SIGINT,SIG_IGN);
    13. while(1);
    14. return 0;
    15. }

    (3)高级信号编程 

    在上面的信号编程入门版中,实现了信号的发送。高级版则在信号发送过程中携带了消息。 

    信号如何携带消息
    思路
    发信号
    1.用什么发 sigqueue()
    2.怎么放入消息

    收信号
    1.用什么绑定函数(收到信号如何让处理)sigaction()
    int sigaction(int signum, const struct sigaction *act,struct sigaction *oldact(备份原操作));
    2.如何读出消息

    int sigqueue(pid_t pid, int sig, const union sigval value);

    第一个参数 发给谁

    第二个参数 发的是啥

    第三个参数 要发的消息

    int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
    (1)第一个参数表示接受的信号。
    (2)第二个参数表示收到这个信号想干嘛
    (3)第三个参数表示备份,通常NULL

    1. //接收信号
    2. #include
    3. #include
    4. void handler (int signum, siginfo_t *info, void *context)
    5. {
    6. printf("get signum = %d\n",signum);
    7. if(context != NULL){
    8. printf("get data = %d\n",info->si_int);//这里是指针所以要用->
    9. printf("get data = %d\n",info->si_value.sival_int);
    10. printf("from %d\n",info->si_pid);//发送者的pid
    11. }
    12. }
    13. int main()
    14. {
    15. struct sigaction act;
    16. printf("pid = %d\n",getpid());
    17. act.sa_flags = SA_SIGINFO;//要收信号就把这个参数设置为SA_SIGINFO
    18. act.sa_sigaction = handler;//收到信号处理该函数
    19. sigaction(SIGUSR1,&act,NULL);//接收SIGUSR1信号,SIGUSR1代号是10
    20. while(1);
    21. return 0;
    22. }
    1. //发送信号
    2. #include
    3. #include
    4. // int sigqueue(pid_t pid, int sig, const union sigval value);
    5. int main(int argc,char **argv)
    6. {
    7. int signum;
    8. int pid;
    9. signum = atoi(argv[1]);//信号代号,SIGUSR1代号是10
    10. pid = atoi(argv[2]);//要发送到的进程号
    11. union sigval value;
    12. value.sival_int = 100;//写入要发送的数据
    13. sigqueue(pid,signum,value);
    14. printf("%d done\n",getpid());
    15. return 0;
    16. }

    运行结果:

     

    六、信号量 (管理临界资源)

    临界资源

    多道程序系统中存在许多进程,它们共享各种资源,然而有很多资源一次只能供一个进程使用。一次仅允许一个进程使用的资源称为临界资源。许多物理设备都属于临界资源,如输入机、打印机磁带机等。 

    信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。

    1、特点

    1. 信号量用于进程间同步,若要在进程间传递数据需要结合共享内存

    2. 信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。

    3. 每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。

    4. 支持信号量组。

    2、原型

    最简单的信号量是只能取 0 和 1 的变量,这也是信号量最常见的一种形式,叫做二值信号量(Binary Semaphore)。而可以取多个正整数的信号量被称为通用信号量。

    Linux 下的信号量函数都是在通用的信号量数组上进行操作,而不是在一个单一的二值信号量上进行操作。

    #include
    // 创建或获取一个信号量组:若成功返回信号量集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, ...); 

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

    semop函数中,sembuf结构的定义如下: 

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

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. //联合体,用于semctl初始化
    7. union semun {
    8. int val; /* Value for SETVAL */
    9. struct semid_ds *buf; /* Buffer for IPC_STAT, IPC_SET */
    10. unsigned short *array; /* Array for GETALL, SETALL */
    11. struct seminfo *__buf; /* Buffer for IPC_INFO
    12. (Linux-specific) */
    13. };
    14. /*放锁函数*/
    15. /*释放资源并将信号量值+1*/
    16. /*如果有进程正在挂起等待,则唤醒它们*/
    17. void vreturnKey(int id)
    18. {
    19. struct sembuf set;
    20. set.sem_num = 0;/*序号*/
    21. set.sem_op = 1;/*v操作*/ 临界资源:锁+1
    22. set.sem_flg = SEM_UNDO;
    23. semop(id,&set,1);//1代表只操作了一个
    24. printf("returnkey\n");
    25. }
    26. /*拿锁函数*/
    27. /*若信号量值为1,获取资源并将信号量值-1*/
    28. /*若信号量值为0,进程挂起等待*/
    29. void pGetKey(int id)
    30. {
    31. struct sembuf set;
    32. set.sem_num = 0;/*序号*/
    33. set.sem_op = -1;/*v操作*/ 临界资源:锁-1
    34. set.sem_flg = SEM_UNDO;
    35. semop(id,&set,1);//1代表只操作了一个
    36. printf("getkey\n");
    37. }
    38. int main ()
    39. {
    40. int key;
    41. int semId;
    42. int pid;
    43. key = ftok(".",1);
    44. /*创建或者创建一个信号量组,参数1代表信号量集合中有一个信号量*/
    45. semId = semget(key,1,IPC_CREAT|0666);
    46. union semun initsem;
    47. initsem.val = 0;//锁被子进程拿走(初始值:初值设为0资源被占用)
    48. /*初始化信号量,参数0代表操作第0个信号量,SETVAL设置信号量的值,设置为initsem*/
    49. semctl(semId,0,SETVAL,initsem);
    50. /*创建子进程*/
    51. pid = fork();
    52. if(pid >0)
    53. {
    54. pGetKey(semId);/*拿锁*/
    55. printf("this is father process\n");
    56. vreturnKey(semId);/*锁放回去*/
    57. semctl(semId,0,IPC_RMID);//销毁锁
    58. }
    59. else if(pid == 0)
    60. {
    61. printf("this is child process\n");
    62. vreturnKey(semId);//放回锁
    63. }
    64. else
    65. {
    66. printf("no child process is created!\n");
    67. }
    68. return 0;
    69. }

    运行结果

    七、通信方式总结

    1.管道:速度慢,容量有限,只有父子进程能通讯

    2.FIFO:任何进程间都能通讯,但速度慢

    3.消息队列:容量受到系统限制,且要注意第一次读的时候,要考虑上一次没有读完数据的问题

    4.信号量:不能传递复杂消息,只能用来同步

    5.共享内存区:能够很容易控制容量,速度快,但要保持同步,比如一个进程在写的时候,另一个进程要注意读写的问题,相当于线程中的线程安全,当然,共享内存区同样可以用作线程间通讯,不过没这个必要,线程间本来就已经共享了同一进程内的一块内存
     

  • 相关阅读:
    数据结构入门 — 队列
    前端周刊第三十三期
    特征数组的特征值 leetcode
    004:vue+leaflet 加载单个图片的方法(示例代码)
    想要精通算法和SQL的成长之路 - 加油站
    云数一体机
    makedown语法及相关语法格式
    量化固定投资方法可不可行?
    Spring Boot中@Import三种使用方式!
    弘辽科技:玩转店铺标签,能让你首页流量快速起爆
  • 原文地址:https://blog.csdn.net/weixin_50546241/article/details/126131471