• Linux C进程间通信(IPC)


    概述

    每个进程有独立的进程空间:

    好处————安全

    缺点:开销大(独立的地址空间);进程的通信更加困难(对其他进程的操作开销也大)

    广义上的进程间通信:

    A进程写给文件/数据库,B进程从文件/数据库里读取

     狭义上的真正的“进程间通信”

    1. 管道
    2. 信号
    3. 消息队列
    4. 共享内存
    5. 信号量
    6. 套接字
       

    进程间通信的原理

    尽管进程空间是各自独立的,相互之间没有任何可以共享的空间,但至少还有一个共享的,那就是OS,因为甭管运行有多少个进程,但是它们共用OS只有一个
    既然大家共用的是同一个OS,那么显然,所有的进程可以通过大家都共享第三方OS来实现数据的转发。
    因此进程间通信的原理就是,OS作为所有进程共享的第三万,会提供相应的机制,以实现进程间数据的转发,达到数据共享的目的

    信号

    信号是一种向进程发送通知,告诉其某件事情发生了的一种简单通信机制

    古老,应用广泛;        仅做通知,不做数据传输;        本质上是整数值(SIG开头);

    信号列表

    信号的产生

    另一个进程发生信号;内核发送信号;底层硬件发送信号

    信号发送

    ps命令:查看进程的信息

    终端

    kill命令:kill -s 《signal》 《pid》

    程序中给一个进程发信号

    给当前进程发信号

    raise
     alarm

    abort
    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. // kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)
    8. // raise(SIGINT); //给当前进程发出终止信号
    9. alarm(5); //定时器到期,操作系统将发送 SIGALRM 信号给进程。
    10. //默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行
    11. //while(1);
    12. pause(); //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源)
    13. abort(); //错误地退出
    14. return 0;
    15. }

    信号的处理方式

    1.默认处理;                2.忽略;                3.执行用户需要执行的操作(捕获)

    信号处理API

    signal

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main()
    6. {
    7. // kill(-1, SIGINT);//给所有进程发出信号(SIGINT是终止信号)
    8. // raise(SIGINT); //给当前进程发出终止信号
    9. alarm(5); //定时器到期,操作系统将发送 SIGALRM 信号给进程。
    10. //默认情况下,如果进程没有对 SIGALRM 信号进行处理,它将终止进程的执行
    11. //while(1);
    12. pause(); //挂起当前进程(相比于while(1)这种cpu消耗性更节约资源);直到有一个信号来
    13. return 0;
    14. }
    1. #include
    2. #include
    3. #include
    4. #include
    5. void handler(int sig) // 信号处理函数
    6. {
    7. if (sig == SIGALRM) // 可以判断是哪个信号调用的处理函数
    8. {
    9. printf("handler with alarm\n");
    10. }
    11. else if (sig == SIGINT)
    12. {
    13. printf("handler with ctrl+c\n");
    14. }
    15. }
    16. int main()
    17. {
    18. // signal(SIGALRM,SIG_IGN);/sigalrm信号被忽略 则pause不会接收到信号,一直挂起
    19. // signal(SIGALRM,SIG_DFL);//sigalrm信号变为默认 则五秒后,printf输出;
    20. signal(SIGALRM, handler); // sigalrm信号转向 ”处理函数“————handler;
    21. signal(SIGINT, handler);
    22. alarm(5);
    23. pause(); // 当处理了一个信号处理函数,会唤醒pause
    24. printf("main is over\n");
    25. return 0;
    26. }
    sigaction


    异步IO的实现

    fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收

    signal(SIGIO, handler); // 设置信号处理函数

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. void handler(int sig) // 定义的信号处理函数
    11. {
    12. char buffer[1024];
    13. memset(buffer, 0, sizeof(buffer));
    14. int ret = read(0, buffer, sizeof(buffer) - 1);
    15. buffer[ret] = '\0';
    16. printf("%s\n", buffer);
    17. }
    18. int main(int argc, char **argv)
    19. {
    20. int fd;
    21. fd = open("/dev/input/mouse0", O_RDWR); // 打开一个文件描述符
    22. if (fd == -1)
    23. {
    24. perror("fd open error\n");
    25. exit(-1);
    26. }
    27. int flags = fcntl(0, F_GETFL);
    28. flags = flags | O_ASYNC; // 获取fd的flags,并加上0_ASYNC(异步读取)
    29. fcntl(0, F_SETFL, flags);
    30. fcntl(0, __F_SETOWN, getpid()); // 将sigio信号设置成由当前的进程接收
    31. signal(SIGIO, handler); // 设置信号线处理函数
    32. while (1) // 主函数的操作不受影响
    33. {
    34. int cor = 0;
    35. read(fd, &cor, sizeof(int));
    36. printf("handler is going: cor =%d\n", cor);
    37. }
    38. return 0;
    39. }

    优化进程等待

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. void handler(int sig)
    12. {
    13. wait(NULL);
    14. printf(("handler & wait\n"));
    15. }
    16. int main()
    17. {
    18. signal(SIGCHLD, handler);
    19. pid_t pid = fork();
    20. if (pid > 0)
    21. {
    22. while (1)
    23. {
    24. printf("father is going\n");
    25. sleep(1);
    26. }
    27. }
    28. if (pid == 0)
    29. {
    30. printf("child is going\n");
    31. }
    32. return 0;
    33. }

    信号屏蔽字

    作用:屏蔽信号

    sigset_t数据类型

    1. #include
    2. #include
    3. #include
    4. #include
    5. int main(int argc, char **argv)
    6. {
    7. sigset_t set; // 定义一个信号字
    8. sigemptyset(&set); // 清空:全置为0
    9. sigaddset(&set, SIGINT); // 将sigint信号加入该信号集——也就是将对应位置为1
    10. sigprocmask(SIG_SETMASK, &set, NULL); // 设置信号罩
    11. pid_t pid = fork();
    12. if (pid > 0)
    13. {
    14. while (1)
    15. {
    16. printf("father \n");
    17. sleep(1);
    18. }
    19. }
    20. if (pid == 0)
    21. {
    22. while (1)
    23. {
    24. printf("child \n");
    25. sleep(1);
    26. }
    27. }
    28. return 0;
    29. }

    未决(处理)信号集

    也是六十四位的int,记录了未处理的信号

    pause() 函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数,pause() 函数会被信号中断并返回 -1,同时将 errno 设置为 EINTR。 

    管道

    无格式,读取后数据会删除

    无名管道

    内核会开辟一个“管道”,通信的进程通过共享这个管道从而实现通信

    int pipe(int pipefd【2】);

    特点:

    1.只允许具有血管关系的进程间通信,如父子进程间的通信

    2.管道只允许单向通信

    3.读管道时,没有数据就会堵塞;写数据,写满缓冲区会休眠

    4.数据被读出后,数据就会被管道删除

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. int main(int agrc, char **argv)
    9. {
    10. int fd[2];
    11. pipe(fd);
    12. pid_t pid = fork();
    13. if (pid > 0)
    14. {
    15. close(fd[0]);
    16. char buffer[1024];
    17. while (1)
    18. {
    19. memset(buffer, 0, sizeof(buffer));
    20. scanf("%s", buffer);
    21. write(fd[1], buffer, sizeof(buffer));
    22. }
    23. }
    24. else if (pid == 0)
    25. {
    26. int flags =fcntl(fd[0],F_GETFL);
    27. flags=flags|O_NONBLOCK;
    28. fcntl(fd[0],F_SETFL,flags);
    29. close(fd[1]);
    30. char buffer[1024];
    31. while (1)
    32. {
    33. memset(buffer, 0, sizeof(buffer));
    34. read(fd[0], buffer, sizeof(buffer));
    35. printf("buffer is %s\n", buffer);
    36. sleep(1);
    37. }
    38. }
    39. return 0;
    40. }

    注意事项

    SIGPIPE信号:

    1.写管道时,如果管道的读端被close了话,向管道"写"数据的进程会被内核发送一个SIGPIPE号,发这个信号的目的就是想通知你,管道所有的"读"都被关闭了。

    2.由于这个信号的默认动作是终止,所以收到这个信号的进程会被终止,如果你不想被终止的
    话,你可以忽略、捕获、或者屏蔽这个信号。
    3.只有当管道所有的读端都被关闭时,才会产生这个信号,只有还有一个读端开着,就不会产生

     

    signal (SIGPIPE, SIG_IGN)来忽略sigpipe这个信号

    无名管道结合异步IO 

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. int fd[2];
    10. void handle(int sig)
    11. {
    12. if (sig == SIGIO)
    13. {
    14. char buffer[1024];
    15. memset(buffer, 0, sizeof(buffer));
    16. read(fd[0], buffer, sizeof(buffer) - 1);
    17. printf("%s\n", buffer);
    18. }
    19. }
    20. int main(int agrc, char **argv)
    21. {
    22. pipe(fd);
    23. pid_t pid = fork();
    24. if (pid > 0) // 父进程:写
    25. {
    26. close(fd[0]);
    27. char buffer[1024];
    28. while (1)
    29. {
    30. memset(buffer, 0, sizeof(buffer));
    31. scanf("%s", buffer);
    32. write(fd[1], buffer, strlen(buffer));
    33. }
    34. }
    35. else if (pid == 0) // 子进程:读
    36. {
    37. // close(fd[0]);
    38. close(fd[1]);
    39. int flags = fcntl(fd[0], F_GETFL);
    40. flags = flags | O_ASYNC;
    41. fcntl(fd[0], F_SETFL, flags);
    42. fcntl(fd[0], __F_SETOWN, getpid());
    43. signal(SIGIO, handle);
    44. pause();
    45. }
    46. return 0;
    47. }

    有名管道

    管道应用的一个重大限制是它没有名字,只适合具有亲缘性质的进程之间通信。命名管道克服了这种限制,FIFO不同于管道之处在于它提供一个路径名与之关联,以FIFO的文件形式存在于文件系统中。这样,即使与FIFO的创建进程不存在亲缘关系的进程,只要可以访问该路径,就能够彼此通过FIFO相互通信(能够访问该路径的进程以及FIFO的创建进程之间),因此,通过FIFO不相关的进程也能交换数据。 

    例子:

    1. FILE1:
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define FILE_PATH "./pipe"
    11. void rm_pipe(int sig)
    12. {
    13. if(sig==SIGINT)
    14. {
    15. remove(FILE_PATH);
    16. }
    17. }
    18. int main()
    19. {
    20. if(mkfifo(FILE_PATH,0777)<0)
    21. {
    22. perror("mkfifo error");
    23. exit(-1);
    24. }
    25. int fd=open(FILE_PATH,O_WRONLY);
    26. if(fd==-1)
    27. {
    28. perror("fd open error");
    29. exit(-1);
    30. }
    31. signal(SIGINT,rm_pipe);
    32. while(1)
    33. {
    34. char buffer[1024];
    35. memset(buffer,0,sizeof(buffer));
    36. scanf("%s",buffer);
    37. write(fd,buffer,strlen(buffer));
    38. }
    39. return 0;
    40. }
    41. FILE2:
    42. #include
    43. #include
    44. #include
    45. #include
    46. #include
    47. #include
    48. #include
    49. #include
    50. #define FILE_PATH "./pipe"
    51. int main()
    52. {
    53. int fd=open(FILE_PATH,O_RDONLY);
    54. if(fd==-1)
    55. {
    56. perror("fd open error");
    57. exit(-1);
    58. }
    59. while(1)
    60. {
    61. char buffer[1024];
    62. memset(buffer,0,sizeof(buffer));
    63. int ret=read(fd,buffer,sizeof(buffer)-1);
    64. buffer[ret]='\0';
    65. printf("%s\n",buffer);
    66. }
    67. return 0;
    68. }

    注意事项:

    “"有名管道"这种特殊文件,只能使用mkfifo函数来创建
    为了保证有名管道一定被创建,最好是两个进程都包含创建管道的代码,谁先运行就谁先创建,后运行的发现管道经创建好了,那就直接open打开使用。

    不能以O_RDWR模式打开命名管道FIFO文件,否则其行为是未定义的,管道是单向的,不能同时读写
     


    System V IPC

    特点:

    与管道不同,他完全使用了不同的实现机制,与文件没有任何关系,也就是说内核不再以文件形式

    System V IPC不在以文件形式存在,所以没有文件描述符这个东西,但有类似的标识符

    任何进程间通信时,都可以使用System V IPC来通信

    优点:减少进程间通信的开销(文件的开销大于链表、内存、整形);Linux和Unix都通用

    消息队列

    消息队列的本质就是由内核创建的用于存放消息的链表,由于是存放消息的,所以我们就把这个链表称为消息队列


    分类

    System V的消息队列
    Posix消息队列团


    消息的组成(结构体)

    1.消息编号:识别消息;

    2.消息正文:真正的信息内容

    消息队列API

    创建

    key值:

    1.指定为IPC_PRIVATE宏,指定这个宏后,每次调用msgget时都会创建一个新的消息
    队列。如果你每次使用的必须是新消息队列的话,就可以指定这个,不过这个用的很少。因为一般来说,只要有一个消息队列可以用来通信就可以了﹐并不需要每次都创建一个全新的消息队列。
    2.自己指定一个整数型,但容易重复指定。本来我想创建一个新的消息队列,结果我所指定的这个整形数﹐之前就已经被用于创建某个消息队列了,当我的指定重复时msgget就不会创建新消息队列,而是使用的是别人之前就创建好的消息队列。所以我们也不会使用这种方式来指定key值

    3.key_t ftok(const char *pathname, int proj_id);
    ftok通过指定路径名和一个整形数,就可以计算并返回一个唯一对应的key值,只要路径名和整形数不变,所对应的key值就唯一不变的。
    不过由于ftok只会使用整形数《 proj_id》的低8位,因此我们往往会指定为一个ASCII码值,因为ASCII码值刚好是8位的整形数。

    msgflag
    指定创建时的原始权限,比如0664
    创建一个新的消息队列时,除了原始权限,还需要指定IPC_CREAT选项。
    msgid = msgget(key, 0664 | IPC_CREAT);
     

    查看消息队列命令

    ipcs -a是默认的输出信息:打印出当前系统中所有的进程间通信方式的信息

    ipcs -m打印出使用共享内存进行进程间通信的信息

    ipcs -q打印出使用消息队列进行进程间通信的信息

    ipcs -s打印出使用信号量进行进程间通信的信息
     

    获取属性及删除

    进程结束后,system v ipc不会自动删除,进程结束后,使用ipc依然能够查看到

    1.重启OS

    2.使用ipcrm命令删除:

    ipcrm -Q msgkey移除用msqkey创建的消息队列
    ipcrm -q msqid移除用msqid标识的消息队列


    3.int msgctl(int msqid, int cmd, struct msqid_ds *buf);//也可以获取消息队列的属性

    cmd

    IPC_STAT:将msqid消息队列的属性信息,读到第三个参数所指定的缓存。

    IPC_SET:IPC_SET:使用第三个参数中的新设置去依改消息队列的属性
            定一个struct msqid_ds buf
            将新的属性信息设置到buf中
            cmd指定为IPC_SET后,msgctl函数就会使用buf中的新属性去修改消息队列原有的属性

    IPC_RMID:删除消息队列,第三个参数置为空

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #define FILE "./msg_file"
    7. int main()
    8. {
    9. key_t key;
    10. key = ftok(FILE, 'F'); // 定义key值
    11. int msgid = msgget(key, 0777 | IPC_CREAT); // 创建消息队列
    12. if (msgid < 0)
    13. {
    14. perror("msgget error");
    15. }
    16. printf("%x\n", key);
    17. printf("%d\n", msgid);
    18. // msgctl(msgid,IPC_RMID,NULL); 删除队列
    19. return 0;
    20. }
    发送

    接收

     删除

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define FILE_PATH "./path"
    12. int msgpid;
    13. void handler(int sig)
    14. {
    15. if (sig == SIGINT)
    16. {
    17. msgctl(msgpid, IPC_RMID, NULL);
    18. }
    19. }
    20. struct msgbuf
    21. {
    22. long mstype;
    23. char mstext[1024];
    24. };
    25. int main(int argc, char **argv)
    26. {
    27. signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列
    28. key_t key = ftok(FILE_PATH, 'K');
    29. msgpid = msgget(key, 0777 | IPC_CREAT);
    30. if (msgpid < 0)
    31. {
    32. perror("msgget error");
    33. }
    34. pid_t pid = fork();
    35. if (pid < 0)
    36. {
    37. perror("fork error");
    38. }
    39. else if (pid == 0) // 发消息到队列
    40. {
    41. while (1)
    42. {
    43. struct msgbuf m1;
    44. memset(&m1, 0, sizeof(struct msgbuf));
    45. printf("input type:");
    46. scanf("%ld", &m1.mstype);
    47. printf("input text:");
    48. scanf("%s", m1.mstext);
    49. if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0)
    50. {
    51. perror("msgsnd error");
    52. _exit(-1);
    53. }
    54. }
    55. }
    56. else // 从队列读消息
    57. {
    58. while (1)
    59. {
    60. struct msgbuf m2;
    61. memset(&m2, 0, sizeof(struct msgbuf));
    62. if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, MSG_NOERROR) < 0)
    63. {
    64. perror("msgrcv error");
    65. exit(-1);
    66. }
    67. else
    68. {
    69. printf("msg rcv:%s\n", m2.mstext);
    70. }
    71. sleep(1);
    72. }
    73. }
    74. return 0;
    75. }

    消息队列的使用步骤

     
    创建        收发        删除


    代码实例


    消息队列的特点

    传送有格式的消息流

    多进程网状交叉通信,消息队列是上上之选

    能实现大规模(进程规模多,不是说数据量大)数据的通信

    通过共同参数的ftok函数,生成的信号队列,可以实现两个无血缘关系进程的读写:

    1. msg_write.c:
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #define FILE_PATH "./path"
    13. int msgpid;
    14. void handler(int sig)
    15. {
    16. if (sig == SIGINT)
    17. {
    18. msgctl(msgpid, IPC_RMID, NULL);
    19. }
    20. }
    21. struct msgbuf
    22. {
    23. long mstype;
    24. char mstext[1024];
    25. };
    26. int main(int argc, char **argv)
    27. {
    28. signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列
    29. key_t key = ftok(FILE_PATH, 'K');
    30. msgpid = msgget(key, 0777 | IPC_CREAT);
    31. if (msgpid < 0)
    32. {
    33. perror("msgget error");
    34. }
    35. while (1)
    36. {
    37. struct msgbuf m1;
    38. memset(&m1, 0, sizeof(struct msgbuf));
    39. printf("input type:");
    40. scanf("%ld", &m1.mstype);
    41. printf("input text:");
    42. scanf("%s", m1.mstext);
    43. if (msgsnd(msgpid, &m1, sizeof(m1.mstext), IPC_NOWAIT) < 0)
    44. {
    45. perror("msgsnd error");
    46. _exit(-1);
    47. }
    48. }
    49. return 0;
    50. }
    51. msg_read.c :
    52. #include
    53. #include
    54. #include
    55. #include
    56. #include
    57. #include
    58. #include
    59. #include
    60. #include
    61. #include
    62. #define FILE_PATH "./path"
    63. int msgpid;
    64. void handler(int sig)
    65. {
    66. if (sig == SIGINT)
    67. {
    68. msgctl(msgpid, IPC_RMID, NULL);
    69. }
    70. }
    71. struct msgbuf
    72. {
    73. long mstype;
    74. char mstext[1024];
    75. };
    76. int main(int argc, char **argv)
    77. {
    78. signal(SIGINT, handler); // 后续循环只能用ctrl+c来退出,只能通过信号处理函数来删除信号队列
    79. key_t key = ftok(FILE_PATH, 'K');
    80. msgpid = msgget(key, 0777 | IPC_CREAT);
    81. if (msgpid < 0)
    82. {
    83. perror("msgget error");
    84. }
    85. while (1)
    86. {
    87. struct msgbuf m2;
    88. memset(&m2, 0, sizeof(struct msgbuf));
    89. if (msgrcv(msgpid, &m2, sizeof(m2.mstext), 3, 0) < 0) //阻塞的读
    90. {
    91. perror("msgrcv error");
    92. exit(-1);
    93. }
    94. else
    95. {
    96. printf("msg rcv:%s\n", m2.mstext);
    97. }
    98. sleep(1);
    99. }
    100. return 0;
    101. }

    共享内存

    让同一块物理内存被映射到进程A、B各自的进程地址空间。进程A可以即时看到进程B对共享内存中数据的更新

    API:

    创建:

    删除:

    1.重启OS

    2.使用ipcrm命令删除:

    ipcrm -M shmkey移除用shmkey创建的共享内存段
    ipcrm -m shmid移除用shmid标识的共享内存段

    3.int shmctl(int shmid, int cmd, struct shmid_ds *buf);

    映射:

    1. char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)
    2. // null代表系统分配内存地址;
    3. // 0代表可读可写;shm_rdonly代表只读

    取消映射:

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #define SIZE_T 4096
    9. #define FILE_PATh ".demo1"
    10. int shmid1;
    11. void handler(int sig)
    12. {
    13. shmctl(shmid1, IPC_RMID, NULL);
    14. printf("delete done\n");
    15. exit(1);
    16. }
    17. int main(int argc, char **argv)
    18. {
    19. key_t key1 = ftok(FILE_PATh, 'F');
    20. shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    21. if (shmid1 == -1)
    22. {
    23. perror("shmget error");
    24. exit(-1);
    25. }
    26. signal(SIGINT, handler);
    27. char *shm_c = shmat(shmid1, NULL, 0); // shmat结果是void类型的指针(强制类型转换)
    28. // null代表系统分配内存地址;
    29. // 0代表可读可写;shm_rdonly代表只读
    30. pid_t pid = fork();
    31. if (pid < 0)
    32. {
    33. perror("fork error");
    34. exit(-1);
    35. }
    36. else if (pid > 0) // 父进程 用来向内存写
    37. {
    38. while (1)
    39. {
    40. char buffer[1024];
    41. memset(buffer, 0, sizeof(buffer));
    42. scanf("%s", buffer);
    43. strcpy(shm_c, buffer);
    44. }
    45. }
    46. else // 子进程 用来从内存读
    47. {
    48. while (1)
    49. {
    50. char buffer[1024];
    51. memset(buffer, 0, sizeof(buffer));
    52. memcpy(buffer, shm_c, sizeof(buffer));
    53. memset(shm_c, 0, sizeof(buffer));
    54. printf("receive buffer = %s\n", buffer);
    55. sleep(1);
    56. }
    57. }
    58. return 0;
    59. }

    改进为阻塞读取(以节省CPU资源):

    1.信号:

    pause() 函数会一直等待直到收到一个信号。当进程接收到一个信号时,如果该信号没有被忽略并且没有注册对应的信号处理函数pause() 函数会被信号中断并返回 -1,同时将 errno 设置为 EINTR

    2.信号量

    特点

    开销最小,减少进入内核次数;

    直接使用地址来读写,效率更高,适用于大数据量的通信

    作业:实现任意进程间的阻塞读取

    1. 读数据:
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #define FILE_PATH "./pipe_file"
    14. #define SIZE_T 4096
    15. void handler(int sig)
    16. {
    17. if (sig == SIGINT)
    18. {
    19. remove(FILE_PATH);
    20. exit(-1);
    21. }
    22. if (sig == SIGUSR1)//空处理,可以唤醒pause
    23. {
    24. }
    25. }
    26. int main(int argc, char **argv) // 读
    27. {
    28. signal(SIGINT, handler);
    29. signal(SIGUSR1, handler);
    30. if (mkfifo(FILE_PATH, 0777) < 0) // 创建有名管道
    31. {
    32. perror("mkfifo error");
    33. exit(-1);
    34. }
    35. int fd = open(FILE_PATH, O_WRONLY); /// 只写打开有名管道文件
    36. if (fd < 0)
    37. {
    38. perror("open error");
    39. exit(-1);
    40. }
    41. pid_t pid1 = getpid(); // 获取当前进程pid号,并通过有名管道传给 写 进程
    42. printf("%d\n", pid1);
    43. if (write(fd, &pid1, sizeof(pid_t)) < 0)
    44. {
    45. perror("write pid1 error");
    46. exit(-1);
    47. }
    48. key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存
    49. int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    50. if (shmid1 < 0)
    51. {
    52. perror("shmid1 error");
    53. exit(-1);
    54. }
    55. char *shm = (char *)shmat(shmid1, NULL, 0); // 共享内存
    56. if (shm == NULL)
    57. {
    58. perror("shm error");
    59. exit(-1);
    60. }
    61. while (1) // 取出共享内存内的数据
    62. {
    63. printf("please wait output\n");
    64. pause();
    65. char buffer[1024];
    66. memset(buffer, 0, sizeof(buffer));
    67. strcpy(buffer, shm);
    68. printf("receive buffer :%s\n", buffer);
    69. memset(shm, 0, SIZE_T);
    70. }
    71. return 0;
    72. }
    73. 写数据:
    74. #include
    75. #include
    76. #include
    77. #include
    78. #include
    79. #include
    80. #include
    81. #include
    82. #include
    83. #include
    84. #include
    85. #define FILE_PATH "./pipe_file"
    86. #define SIZE_T 4096
    87. int main(int argc, char **argv) // 写
    88. {
    89. int fd = open(FILE_PATH, O_RDONLY); // 只读打开有名管道
    90. if (fd < 0)
    91. {
    92. perror("open error");
    93. exit(-1);
    94. }
    95. pid_t pid1;
    96. read(fd, &pid1, sizeof(pid_t)); // 把 读 进程pid号通过管道读出来
    97. printf("%d\n",pid1);
    98. key_t key1 = ftok(FILE_PATH, 'F'); // 配置共享内存
    99. int shmid1 = shmget(key1, SIZE_T, 0777 | IPC_CREAT);
    100. if (shmid1 < 0)
    101. {
    102. perror("shmid1 error");
    103. exit(-1);
    104. }
    105. char *shm = (char*)shmat(shmid1, NULL, 0); // 共享内存
    106. if (shm == NULL)
    107. {
    108. perror("shm error");
    109. exit(-1);
    110. }
    111. while (1) // 写数据,写完就传个信号给 读 进程
    112. {
    113. char buffer[1024];
    114. printf("please input\n");
    115. scanf("%s", buffer);
    116. strcpy(shm, buffer);
    117. kill(pid1,SIGUSR1);
    118. }
    119. return 0;
    120. }

    信号量(信号锁、信号灯

    当多个进程/线程进行共享访问的时候,用于资源保护,以防止资源出现干扰的情况

    进程同步:进程按照一定的顺序执行(不是指先后顺序,而是指互斥)

    进程竞态:

    互斥:对于互斥操作来说,多进程共享操作时,多个进程间不关心谁先操作、谁后操作的先后顺序问题,它们只关心,自己操作时候,别人不能操作

    同步:所谓同步就是,多个共享操作时,进程必须要有统
    操作的步调,按照一定的顺序来操作

    解决方法:加锁

    信号量(信号锁):信号量其实是一个OS创建的,供相关进程共享的int变量,只不过我们在调用相关API创建信号量时,我们创建的都是一个信号量集合,所谓集合就是可能会包含好多个信号量。
    用于互斥时,集合中只包含一个信号量。
    用于同步时,集合中会包含多个信号量,至于多少个,需要看情况

    API:

    1.配置信号队列:int semget (key_t key,int nsems,int semflg)

    参数:
    key用ftok获取key值
    nsems指定集合中信号量的个数        【用于互斥时,数量都指定为1,因为只需要一个信号量】
    semfig:权限:―般都设置为0664 | IPC_CREAT        【设置同消息队列和共享内存】        

    2.控制信号队列:int semctl(int semid,int semnum,int cmd,...)

    参数:

    semnum:集合中某个信号量的编号(集合中某个信号量的编号:信号量的编号为非负整数,而且是自动从0开始)

    cmd:IPC_STAT;        IPC_SET;         IPC_RMID

    可变参数:......

    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #define FILE_PATH "./sem_demo"
    11. int semid;
    12. void delete_sem(int, int);
    13. void handler(int sig)
    14. {
    15. delete_sem(semid, 0);
    16. }
    17. void creat_sem(int nsems) // 创建
    18. {
    19. key_t key = ftok(FILE_PATH, 'f');
    20. int semid = semget(key, nsems, 0777 | IPC_CREAT);
    21. if (semid < 0)
    22. {
    23. perror("semget error");
    24. exit(-1);
    25. }
    26. }
    27. void init_sem(int semid, int semnum, int val) // 初始化制定信号量的值
    28. {
    29. semctl(semid, semnum, SETVAL, val);
    30. }
    31. void delete_sem(int semid, int semnum) // 删除制定信号量的值
    32. {
    33. if (semctl(semid, semnum, IPC_RMID) < 0)
    34. {
    35. perror("semctl delete error");
    36. }
    37. }
    38. int main(int argc, char **argv)
    39. {
    40. key_t key = ftok(FILE_PATH, 'F');
    41. semid = semget(key, 3, 0777 | IPC_CREAT);
    42. if (semid < 0)
    43. {
    44. perror("semget error");
    45. exit(-1);
    46. }
    47. printf("%d\n", semid);
    48. pid_t pid = fork();
    49. if (pid > 0)
    50. {
    51. while (1)
    52. {
    53. sleep(1);
    54. }
    55. }
    56. if (pid == 0)
    57. {
    58. signal(SIGINT, handler);//只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错
    59. while (1)
    60. {
    61. sleep(1);
    62. }
    63. }
    64. // printf("%x\n", key);
    65. // semctl(semid, 0, IPC_RMID);
    66. return 0;
    67. }
    1. #include
    2. #include
    3. #include
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #define FILE_PATH "./sem_demo"
    12. int semid;
    13. pid_t pid;
    14. void delete_sem(int);
    15. void handler(int sig)
    16. {
    17. printf("handler is used\n");
    18. delete_sem(0);
    19. semctl(semid, 0, IPC_RMID);
    20. exit(-1);
    21. }
    22. void lock(int semid1, int semnum1) // 封装 上锁 函数
    23. {
    24. struct sembuf sembuffer1[1];
    25. sembuffer1[0].sem_num = semnum1;
    26. sembuffer1[0].sem_op = -1;
    27. sembuffer1[0].sem_flg = SEM_UNDO;
    28. semop(semid1, sembuffer1, 1);
    29. }
    30. void unlock(int semid1, int semnum1) // 封装 解锁 函数
    31. {
    32. struct sembuf sembuffer1[1];
    33. sembuffer1[0].sem_num = semnum1;
    34. sembuffer1[0].sem_op = 1;
    35. sembuffer1[0].sem_flg = SEM_UNDO;
    36. semop(semid1, sembuffer1, 1);
    37. }
    38. void creat_sem(int nsems) // 创建
    39. {
    40. key_t key = ftok(FILE_PATH, 'f');
    41. semid = semget(key, nsems, 0777 | IPC_CREAT);
    42. if (semid < 0)
    43. {
    44. perror("semget error");
    45. exit(-1);
    46. }
    47. }
    48. void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
    49. {
    50. semctl(semid, semnum, SETVAL, val);
    51. }
    52. void delete_sem(int semnum) // 删除制定信号量的值
    53. {
    54. if (semctl(semid, semnum, IPC_RMID) < 0)
    55. {
    56. perror("semctl delete error");
    57. }
    58. printf("delete done\n");
    59. }
    60. int main(int argc, char **argv)
    61. {
    62. int fd = open("a.txt", O_WRONLY | O_APPEND | O_CREAT, 0655);
    63. if (fd == -1)
    64. {
    65. perror("fd error");
    66. exit(-1);
    67. }
    68. creat_sem(1);
    69. init_sem(semid, 0, 1);
    70. printf("%d\n", semid);
    71. pid = fork();
    72. if (pid > 0)
    73. {
    74. while (1)
    75. {
    76. lock(semid, 0);
    77. write(fd, "helloworld", 10);
    78. write(fd, "helloworld", 10);
    79. write(fd, "\n", 1);
    80. unlock(semid, 0);
    81. sleep(1);
    82. }
    83. }
    84. if (pid == 0)
    85. {
    86. signal(SIGINT, handler); // 只能在子进程中注册,在main函数内注册的话,当contrl+c时候,,父子进程都要调用一次handler,也就是删除的函数,则会报错
    87. while (1)
    88. {
    89. lock(semid, 0);
    90. write(fd, "hhhhhwwwww", 10);
    91. write(fd, "hhhhhwwwww", 10);
    92. write(fd, "\n", 1);
    93. unlock(semid, 0);
    94. sleep(1);
    95. }
    96. }
    97. return 0;
    98. }

    作业:

    答: 

    1. sem.h:
    2. #ifndef _MYSEM_H_
    3. #define _MYSEM_H_
    4. #include
    5. #include
    6. #include
    7. #include
    8. #include
    9. #include
    10. #include
    11. #include
    12. #include
    13. #include
    14. #define FILE_PATH "./sem_demo"
    15. void creat_sem(int *semid, int nsems); // 创建
    16. void init_sem(int semid, int semnum, int val); // 初始化指定信号量的值
    17. void lock(int semid1, int semnum1); // 封装 上锁 函数
    18. void unlock(int semid1, int semnum1); // 封装 解锁 函数
    19. void delete_sem(int semid, int semnum); // 删除制定信号量的值
    20. #endif
    21. sem.c:
    22. #include "sem.h"
    23. void delete_sem(int semid, int semnum) // 删除制定信号量的值
    24. {
    25. if (semctl(semid, semnum, IPC_RMID) < 0)
    26. {
    27. perror("semctl delete error");
    28. }
    29. printf("delete done\n");
    30. }
    31. void lock(int semid1, int semnum1) // 封装 上锁 函数
    32. {
    33. struct sembuf sembuffer1[1];
    34. sembuffer1[0].sem_num = semnum1;
    35. sembuffer1[0].sem_op = -1;
    36. sembuffer1[0].sem_flg = SEM_UNDO;
    37. semop(semid1, sembuffer1, 1);
    38. }
    39. void unlock(int semid1, int semnum1) // 封装 解锁 函数
    40. {
    41. struct sembuf sembuffer1[1];
    42. sembuffer1[0].sem_num = semnum1;
    43. sembuffer1[0].sem_op = 1;
    44. sembuffer1[0].sem_flg = SEM_UNDO;
    45. semop(semid1, sembuffer1, 1);
    46. }
    47. void creat_sem(int *semid,int nsems) // 创建
    48. {
    49. key_t key = ftok(FILE_PATH, 'f');
    50. *semid = semget(key, nsems, 0777 | IPC_CREAT);
    51. if (semid < 0)
    52. {
    53. perror("semget error");
    54. exit(-1);
    55. }
    56. }
    57. void init_sem(int semid, int semnum, int val) // 初始化指定信号量的值
    58. {
    59. semctl(semid, semnum, SETVAL, val);
    60. }
    61. sem_abcd.c:
    62. #include "sem.h"
    63. int semid;
    64. int main(int argc, char **argv)
    65. {
    66. creat_sem(&semid, 4);
    67. init_sem(semid, 0, 1);
    68. for (int i = 1; i < 4; ++i)
    69. {
    70. init_sem(semid, i, 0);
    71. }
    72. pid_t pid1 = fork();
    73. if (pid1 > 0)
    74. {
    75. pid_t pid2 = fork();
    76. if (pid2 > 0)
    77. {
    78. while (1)
    79. {
    80. lock(semid, 0);
    81. printf("A\n");
    82. sleep(1);
    83. unlock(semid, 1);
    84. }
    85. }
    86. if (pid2 == 0)
    87. {
    88. while (1)
    89. {
    90. lock(semid, 1);
    91. printf("B\n");
    92. sleep(1);
    93. unlock(semid, 2);
    94. }
    95. }
    96. }
    97. else if (pid1 == 0)
    98. {
    99. pid_t pid3 = fork();
    100. if (pid3 > 0)
    101. {
    102. while (1)
    103. {
    104. lock(semid, 2);
    105. printf("C\n");
    106. sleep(1);
    107. unlock(semid, 3);
    108. }
    109. }
    110. if (pid3 == 0)
    111. {
    112. while (1)
    113. {
    114. lock(semid, 3);
    115. printf("D\n");
    116. sleep(1);
    117. unlock(semid, 0);
    118. }
    119. }
    120. }
    121. return 0;
    122. }

    google  笔试题:

    Google多线程面试题: 4个线程向4个文件里写入数据, 每个线程只能写一个值

  • 相关阅读:
    晨控CK-GW06系列网关与汇川可编程控制器MOSBUSTCP通讯手册
    LINUX安装openssl
    Selenium环境+元素定位大法
    Java版本电子招标采购系统源码:营造全面规范安全的电子招投标环境,促进招投标市场健康可持续发展
    Java核心编程(15)
    介绍一个中后台管理系统
    c语言---指针进阶(1)
    达人评测i7 12700kf和i9 12900kf选哪个好
    python 学习:各种符号的意思\n \t等
    使用dockerfile自定义Tomcat镜像
  • 原文地址:https://blog.csdn.net/qq_58170320/article/details/132483247