9.1 信号

信号是一个32位整型数值,代表一个简单信息。每个信号都有一个以SIG开头的名字,实际就是系统定义的宏。
kill -l命令查看当前系统支持的所有信号。最小的信号值为1。

image.png
其中值为9的SIGKILL和值为19的SIGSTOP不可以被阻塞。
image.png

image.png
image.png
注意,只有具有root权限的进程才能向其他任一进程发送信号,非root权限的进程只能向属于同一个组或同一个用户的进程发送信号。
image.png
信号是可以被阻塞的。如果为进程产生一个信号,这个信号已经被进程设置为阻塞,并且对该信号的动作为系统默认动作或捕捉该信号,则该信号将一直处于未决(pending)状态。
当进程接收到信号后,信号会做什么?这要视情况而定,很多信号都会杀死进程,某时刻进程还在运行,下一秒就消亡了,从内存中被删除,相应的所有的文件描述符被关闭,并从进程表中被删除。但进程也有方法保护自己不被杀死。
image.png

9.1.1 signal系统调用

image.png

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. #include <sys/types.h>
  4. #include <signal.h>
  5. void sig_handler(int sig)
  6. {
  7. switch(sig){
  8. case SIGINT:
  9. printf("signal SIGINT is caught\n");
  10. break;
  11. default:
  12. printf("other signal is caught\n");
  13. }
  14. }
  15. int main()
  16. {
  17. int second = 0;
  18. signal(SIGINT,SIG_DFL);
  19. signal(SIGINT,sig_handler);//两个signal函数放在一起,以后面一个为准
  20. return 0;
  21. }

9.1.2 产生信号的两个函数:raise和write

image.png
image.png

9.1.3 设置定时器——alarm

image.png

9.1.4 挂起进程

image.png
进程调用pause会挂起,直到进程捕捉到一个信号。当执行完信号处理函数并返回时,pause才返回。pause始终返回1。

9.1.5 异常终止进程

image.png

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <sys/types.h>
  4. #include <signal.h>
  5. void sig_handler(int sig)
  6. {
  7. switch(sig){
  8. case SIGABRT:
  9. printf("signal SIGABRT is caught\n");
  10. break;
  11. default:
  12. printf("other signal is caught\n");
  13. }
  14. }
  15. int main()
  16. {
  17. signal(SIGABRT,sig_handler);
  18. abort();
  19. printf("after abort()\n");
  20. return 0;
  21. }

在该程序中,abort使得内核向进程传递信号SIGABRT,因此,进程执行信号处理函数。由于abort使得程序异常退出,因此程序不会执行abort后面的语句

9.1.6 添加延时:sleep

sleep的参数为无符号整型值,表示要挂起的秒数。进程调用sleep函数后挂起直到以下两种情况发生:

  1. 经过了seconds指定的秒数,此时sleep返回;
  2. 捕捉到一个信号并从信号处理函数返回,此时sleep返回剩余的秒数。
  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <unistd.h>
  4. #include <sys/types.h>
  5. #include <signal.h>
  6. void sig_handler(int sig)
  7. {
  8. switch(sig){
  9. case SIGQUIT:
  10. printf("SIGQUIT is caught\n");
  11. break;
  12. default:
  13. printf("other signal is caught\n");
  14. }
  15. }
  16. int main()
  17. {
  18. int rtn;
  19. if(signal(SIGQUIT,sig_handler) != SIG_ERR){
  20. printf("please enter Ctrl-\\\n");
  21. rtn = sleep(1000);
  22. printf("sleep returns and %d seconds remain\n",rtn);
  23. rtn = sleep(3);
  24. printf("sleep finishes and returns %d seconds\n",rtn);
  25. }
  26. return 0;
  27. }

image.png

9.2 管道

管道是双向半双工的,即通过管道可以实现两个方向的数据流,但通信时只有一个方向的数据流。
使用管道进行通信的两个进程一定要有相同的祖先进程。(无名管道)
image.png
image.png

系统调用pipe接收一个具有两个元素的整型数组fds作为参数,pipe成功后返回0,并向fds返回两个文件描述符。fds[0]对应管道读出端,fds[1]对应管道写入端。每个管道都有一个管道缓冲区,大小为4096个字节。可在命令行输入“ulimit -p”查看系统当前管道缓冲区大小,以512字节为单位。(16个缓冲区)
int pipe(int fds[2]);
管道也是文件,是内存文件,没有inode节点,不会存放在磁盘中。

使用管道进行通信的父子进程:

  1. 父进程调用pipe创建管道,并返回两个文件描述符fds[0]和fds[1]
  2. 调用fork创建子进程,子进程继承fds[0]和fds[1]
  3. 如果子进程向父进程传输数据,则子进程关闭fds[0],父进程关闭fds[1];如果父进程向子进程传输数据,则子进程关闭fds[1],父进程关闭fds[0]

    image.png

  1. //pipe的典型用法1
  2. #include <stdio.h>
  3. #include <stdlib.h>
  4. #include <unistd.h>
  5. #include <sys/types.h>
  6. #include <sys/stat.h>
  7. #include <fcntl.h>
  8. #include <string.h>
  9. int main()
  10. {
  11. pid_t pid;
  12. char data[32] = "hello";
  13. char buf[32] = {0};
  14. int fds[2];
  15. if(pipe(fds) == 0){
  16. pid = fork();
  17. if(pid == 0){
  18. close(fds[0]);
  19. write(fds[1],data,strlen(data));
  20. exit(0);
  21. }else if(pid > 0){
  22. wait(NULL);
  23. close(fds[1]);
  24. read(fds[0],buf,sizeof(buf));
  25. printf("%s\n",buf);
  26. }
  27. }
  28. return 0;
  29. }
  1. //pipe的典型用法2。
  2. #include<stdio.h>
  3. #include<stdlib.h>
  4. #include<unistd.h>
  5. #include<sys/types.h>
  6. int main()
  7. {
  8. int fds[2];
  9. int pid1,pid2;
  10. int sibling,self;
  11. if(pipe(fds) == 0)
  12. {
  13. if((pid1 = fork()) == 0)
  14. {
  15. close(fds[0]);
  16. self = getpid();
  17. write(fds[1],&self,sizeof(int));
  18. exit(1);
  19. }
  20. if((pid2 = fork()) == 0)
  21. {
  22. close(fds[1]);
  23. read(fds[0],&sibling,sizeof(int));
  24. printf("sibling pid = %d\n",sibling);
  25. exit(2);
  26. }
  27. }
  28. return 0;
  29. }
  1. pipefork2中父进程创建了两个子进程,第一个子进程把自己的pid写入管道,第二个子进程从管道中读出第一个子进程的pid。<br />因为一个管道的缓冲区容量有限,所以,写进程向已满的管道写数据时,写进程将被阻塞,直至读进程把数据从管道中读出。与之类似,若读进程从空管道中读数据,则读进程也会被阻塞,直至写进程向管道中写数据。如果管道的读出端进程不存在,则写进程调用write时,内核向此进程发送信号SIGPIPE,系统默认动作为终止进程。<br />当fork创建子进程,子进程会继承父进程的文件描述符等。通过struct file就能找到文件缓冲区。<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1054933/1638105556402-d19b130b-ba54-46e8-b569-a1c042291eac.png#clientId=u3ca9c15c-7a40-4&from=paste&height=453&id=uad3cf893&margin=%5Bobject%20Object%5D&name=image.png&originHeight=604&originWidth=1215&originalType=binary&ratio=1&size=104062&status=done&style=none&taskId=uf1e94a07-ec42-4ebc-8b01-23ac999fbaa&width=911)<br />![image.png](https://cdn.nlark.com/yuque/0/2021/png/1054933/1637487050441-4a8f9862-9e19-4895-84af-73b3883d10de.png#clientId=ubd9804ff-76d5-4&from=paste&height=208&id=u926f4e7e&margin=%5Bobject%20Object%5D&name=image.png&originHeight=416&originWidth=986&originalType=binary&ratio=1&size=66579&status=done&style=none&taskId=u6dfea755-e0fa-4223-891b-ad12daa1693&width=493)

image.png
image.png