管道

image.png
管道:
实现原理:内核借助环形队列,使用内核缓冲区(4k)实现
特质

  1. 伪文件 实质伪内核缓冲区 pipe
  2. 管道中的数据只能一次读取
  3. 数据在管道中,只能单向流动

局限性:
自己写 不能自己读

  1. 数据不可以反复读
  2. 半双工通信
  3. 血缘关系进程间可用

伪文件

不用来存储数据 本身不占用任何空间 是文件系统的一部分 并按目录进行组织
目的:提供一种服务 采取和常规文件相同的访问方式进行访问
多数情况下 伪文件用来访问内核提供的服务
类型: 设备文件 命名管道 proc文件
设备文件:也称特殊文件,是物理设备的内部表示,包括计算机和网络中的每个设备都可以当作特殊文件来访问。如键盘,显示器,打印机,磁盘驱动器。
命名管道:管道功能的一个扩展,经一个程序的输出连接到另一个程序的输入上。
proc文件:运行访问内核中的信息

命令管道

命令管道是常规管道的扩展
管道 将一个进程的输出链接到另一个进程的输入
匿名管道 管道没有具体的名称,自动创建,且仅当两个进程正在运行时它才存在
命名管道 必须显式地创建,当两个进程结束时,命令管道不会消失。除非删除它,不让会一直存在

FIFO(first-in,first-out),即数据结构中的队列。
mkfifo 可以创建一个管道。
通常使用它来进行进程间通信。
在一个进程中将数据重定向到命名管道中,另一个进程中从命名管道中读取数据。

管道的基本用法

pipe函数 创建并打开管道
int pipe(int fd[2]);
参数:fd[0] : 读端
fd[1] :写端
返回值 成功 0
失败 -1 error
管道通信原理
image.png

应用

  1. pid = fork();
  2. if (pid > 0) {//父进程写
  3. close(fd[0]); // 关闭读段
  4. //sleep(3);
  5. write(fd[1], str, strlen(str));
  6. close(fd[1]);
  7. }
  8. else if (pid == 0) {//子进程读
  9. close(fd[1]); // 子进程关闭写段
  10. ret = read(fd[0], buf, sizeof(buf));
  11. printf("child read ret = %d\n", ret);
  12. write(STDOUT_FILENO, buf, ret);
  13. close(fd[0]);
  14. }

管道的读写行为:
读管道:

  1. 管道有数据,read 返回实际读到的字节数。
  2. 管道无数据:

1)无写端,read 返回 0 (类似读到文件尾)
2)有写端,read 阻塞等待。

写管道:

  1. 无读端, 异常终止。 (SIGPIPE 导致的)
  2. 有读端:

1) 管道已满, 阻塞等待
2) 管道未满, 返回写出的字节个数

进程间通信的常用方式,特征:
管道:简单
信号:开销小
mmap 映射:非血缘关系进程间
socket(本地套接字):稳定

普通文件,目录,软链接,这三个要占磁盘空间
管道,套接字,字符设备,块设备,不占磁盘空间,伪文件

父子进程 lswc-l

练习:使用管道实现父子进程间通信,完成:ls|wc-l 假定父进程是实现ls 子进程实现wc ls 命令正常会将结果集写到stdout 但现在会写入管道写端。
wc -l命令正常应该从stdin读取数据,但此时会从管道的读端读。
要用到pipe dup2 exec

  1. #include <stdio.h>
  2. #include <stdlib.h>
  3. #include <string.h>
  4. #include <unistd.h>
  5. #include <error.h>
  6. #include <pthread.h>
  7. void sys_err(const char * str)
  8. {
  9. perror(str);
  10. exit(1);
  11. }
  12. int main(int argc,char *argv[])
  13. {
  14. int fd[2];
  15. int ret;
  16. pid_t pid;
  17. ret = pipe(fd);
  18. if(ret == -1)
  19. {
  20. sys_err("pipe error");
  21. }
  22. pid = fork();
  23. if(pid < 0)
  24. {
  25. sys_err("fork_error");
  26. }
  27. else if(pid > 0)//父进程 读 关闭写端fd[1]
  28. {
  29. close(fd[1]);
  30. dup2(fd[0],STDIN_FILENO);//重定向stdin 到管道的 读端
  31. execllp("wc","wc","-l",NULL);
  32. sys_err("exclp wc error");
  33. }
  34. else if(pid == 0)//子进程 写 关闭读端
  35. {
  36. close(fd[0]);
  37. dup2(fd[1],STDOUT_FILENO);//重定向stdout 到管道的 写端
  38. execlp("ls","ls",NULL); //NULL为参数哨兵
  39. }
  40. return 0;
  41. }

兄弟进程间通信

练习题:兄弟进程间通信
兄:ls
弟:wc -l
父:等待回收子进程
要求,使用循环创建 N 个子进程模型创建兄弟进程,使用循环因子 i 标识,注意管道读写行为
测试:
是否允许,一个 pipe 有一个写端多个读端

是否允许,一个 pipe 有多个写端一个读端

多个读写端操作管道和管道缓冲区大小

  1. if (i == 0) {
  2. close(fd[0]);
  3. write(fd[1], "1.hello\n", strlen("1.hello\n"));
  4. } else if(i == 1) {
  5. close(fd[0]);
  6. write(fd[1], "2.world\n", strlen("2.world\n"));
  7. } else {
  8. close(fd[1]); //父进程关闭写端,留读端读取数据
  9. sleep(1);
  10. n = read(fd[0], buf, 1024); //从管道中读数据
  11. write(STDOUT_FILENO, buf, n);
  12. for(i = 0; i < 2; i++) //两个儿子 wait 两次
  13. wait(NULL);
  14. }

命令管道fifo的创建和原理图

管道的优劣:
优点:简单,相比信号,套接字实现进程通信,简单很多
缺点:
1.只能单向通信,双向通信需建立两个管道
2.只能用于有血缘关系的进程间通信。该问题后来使用 fifo 命名管道解决。

image.png

fifo 管道:可以用于无血缘关系的进程间通信。
命名管道: mkfifo
无血缘关系进程间通信:
读端,open fifo O_RDONLY
写端,open fifo O_WRONLY
fifo 操作起来像文件
下面的代码创建一个 fifo:
mkfifo(“文件名”,文件权限)
读写进程对文件读写一致 打开权限读端为 O_RDONLY
打开权限写端为 O_WRONLY
image.png

文件用于进程间通信

文件实现进程间通信:
打开的文件是内核中的一块缓冲区。多个无血缘关系的进程 可以同时访问该文件
缓冲区的一样的
image.png

文件通信这个 有没有血缘关系都行 只是有血缘关系的进程对于同一个文件 使用的同一个文件描述符 没有血缘关系的进程 对同一个文件使用的文件描述符可能不同

mmap函数原型

存储映射 I/O(Memory-mapped I/O) 使一个磁盘文件与存储空间中的一个缓冲区相映射。于是从缓冲区中取数据,就相当于读文件中的相应字节。与此类似,将数据存入缓冲区,则相应的字节就自动写入文件。这样,就可在不使用 read 和 write 函数的情况下,使地址指针完成 I/O 操作。

  1. void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);
  2. 创建共享内存映射
  3. 参数:
  4. addr 指定映射区的首地址。通常传 NULL,表示让系统自动分配
  5. length:共享内存映射区的大小。(<= 文件的实际大小)
  6. prot 共享内存映射区的读写属性。PROT_READPROT_WRITEPROT_READ|PROT_WRITE
  7. flags 标注共享内存的共享属性。MAP_SHAREDMAP_PRIVATE
  8. fd: 用于创建共享内存映射区的那个文件的 文件描述符。
  9. offset:默认 0,表示映射文件全部。偏移位置。需是 4k 的整数倍。
  10. 返回值:
  11. 成功:映射区的首地址。
  12. 失败:MAP_FAILED (void*(-1)), errno

flags 里面的 shared 意思是修改会反映到磁盘上
private 表示修改不反映到磁盘上

int munmap(void *addr, size_t length);
释放映射区。
addr:mmap 的返回值
length:大小

为什么使用mmap

Linux通过内存映像机制来提供用户程序对内存直接访问的能力。内存映像的意思是把内核中特定部分的内存空间映射到用户级程序的内存空间去。也就是说,用户空间和内核空间共享一块相同的内存。这样做的直观效果显而易见:内核在这块地址内存储变更的任何数据,用户可以立即发现和使用,根本无须数据拷贝。举个例子理解一下,使用mmap方式获取磁盘上的文件信息,只需要将磁盘上的数据拷贝至那块共享内存中去,用户进程可以直接获取到信息,而相对于传统的write/read IO系统调用, 必须先把数据从磁盘拷贝至到内核缓冲区中(页缓冲),然后再把数据拷贝至用户进程中。两者相比,mmap会少一次拷贝数据,这样带来的性能提升是巨大的。
[

](https://blog.csdn.net/Holy_666/article/details/86532671)

mmap的原理

  1. 进程启动映射过程,并在虚拟地址空间中为映射创建虚拟映射区域
  2. 调用内核空间的系统调用函数mmap(不同于用户空间函数),实现文件物理地址和进程虚拟地址的一一映射关系
  • 内核mmap函数通过虚拟文件系统inode模块定位到文件磁盘物理地址。
  • remap_pfn_range函数建立页表,即实现了文件地址和虚拟地址区域的映射关系。此时,这片虚拟地址并没有任何数据关联到主存中。
  1. 进程发起对这片映射空间的访问,引发缺页异常,实现文件内容到物理内存(主存)的拷贝
  • 前两步没有数据拷贝到主存 真正的文件读取是当进程发起读或写操作时。
  • 进程的读或写操作访问虚拟地址空间这一段映射地址,通过查询页表,发现这一段地址并不在物理页面上。因为目前只建立了地址映射,真正的硬盘数据还没有拷贝到内存中,因此引发缺页异常。
  • 缺页异常进行一系列判断,确定无非法操作后,内核发起请求调页过程。
  • 调页过程先在交换缓存空间(swap cache)中寻找需要访问的内存页,如果没有则调用nopage函数把所缺的页从磁盘装入到主存中
  • 之后进程即可对这片主存进行读或者写的操作,如果写操作改变了其内容,一定时间后系统会自动回写脏页面到对应磁盘地址,也即完成了写入到文件的过程

注:修改过的脏页面并不会立即更新回文件中,而是有一段时间的延迟,可以调用msync()来强制同步, 这样所写的内容就能立即保存到文件里了。

mmap 使用注意事项

  1. 用于创建映射区的文件大小为0 实际指定非0大小创建映射区 出“总线错误”
  2. 用于创建映射区的文件大小为0 实际指定0大小创建映射区 出“无效参数”
  3. 用于创建映射区的文件读写属性为 只读。映射区属性为读写 出 “无效参数”
  4. 创建映射区 需要read权限 当访问权限指定为“共享”MAP_SHARED 时 mmap的读权限 应该<=文件的open权限 只写不行
  5. 文件描述符fd 在mmap创建映射区完成即可关闭。后续访问文件用地址访问返回的首地址
  6. offest 必须时4096的整数倍 (MMU映射的最小单位 4k)
  7. 对申请的映射区内存 不能越界访问
  8. munmap 用于释放的 地址,必须时mmap申请返回的地址
  9. 映射区访问权限为“私有”MAP_PRIVATE 对内存才能所做的所有修改 只在内存有效 不会映射到物理磁盘上
  10. 映射区访问权限为“私有”只需要 open文件时 有读权限 用于创建映射区即可。

mmap 函数的保险调用方式:
1. fd = open(
“文件名”, O_RDWR);
2. mmap(NULL, 有效文件大小, PROT_READ|PROT_WRITE, MAP_SHARED, fd, 0);

MMAP 总结

  1. 创建映射区的过程中,隐含着一次对映射文件的读操作
    2. 当 MAP_SHARED 时,要求:映射区的权限应该<=文件打开的权限(出于对映射区的保护)。而MAP_PRIVATE 则无所谓,因为 mmap 中的权限是对内存的限制
    3. 映射区的释放与文件关闭无关。只要映射建立成功,文件可以立即关闭
    4. 特别注意,当映射文件大小为 0 时,不能创建映射区。所以:用于映射的文件必须要有实际大小!!mmap 使用时常常会出现总线错误,通常是由于共享文件存储空间大小引起的。如,400 字节大小的文件,在简历映射区时,offset4096 字节,则会报出总线错误
    5. munmap 传入的地址一定是 mmap 返回的地址。坚决杜绝指针++操作 。
    6. 文件偏移量必须为 4K 的整数倍
    7. mmap 创建映射区出错概率非常高,一定要检查返回值,确保映射区建立成功再进行后续操作。

mmap 将一个文件或者其它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对映关系。mmap()系统调用使得进程之间通过映射同一个普通文件实现共享内存。普通文件被映射到进程地址空间后,进程可以向访问普通内存一样对文件进行访问,不必再调用read(),write()等操作。得到一个指针 可以通过指针对内存进行操作

mmap父子进程间通信

父子等有血缘关系的进程之间也可以通过mmap建立的映射区来完成数据通信。但相应的要在创建映射区的时候指定对应的标志位参数flags
MAP_PRIVATE:父子进程各自独自映射区
MAP_SHARED:父子进程共享映射区
父进程创建映射区 ,然后fork子进程,子进程修改映射区内容,而后,父进程读取映射区内容
子进程拥有与父进程
读时共享、写时复制。———————— 全局变量。
1. 文件描述符 2. mmap 映射区
结论 父进程共享:1.打开的文件 2.mmap建立的映射区(但必须要使用MAP_SHARED)

无血缘关系进程间mmap通信

两个进程 打开同一个文件 创建映射区
指定flags MAP_SHARDER
一个进程写入 另一个进程退出
【注意】无血缘关系进程通信,mmap:数据可以重复读取
fifo :数据只能一次读取

if(p == MAP_FAILED){ //注意:不是 p == NULL
perror(“mmap error”);
exit(1); }

匿名映射区

使用映射区完成文件读写操作十分方便,父子进程间通信也比较容易。但缺陷是,每次创建映射区一定要依赖一个文件才能实现。通常为了建立映射区要open一个temp文件,创建好了再unlink close掉 比较麻烦。可以直接使用匿名映射来代替,其实Linux系统给我们提供了创建匿名映射区的方法,无需依赖一个文件即可创建映射区,同样需要借助标志位参数flags来指定。
使用MAP_AONYMOUS(或MAP_ANON) 如:
int *p=mmap(NULL,4,PROT_READ|PORT_WRITE,MAP_SHARED|MAP_ANONYMOUS,-1,0)
anonymous

信号

信号的概念和机制

信号共性:
简单 不能携带大量信息 满足条件才发送
信号的特质:
信号是软件层面上的“中断”。一旦信号产生 无论程序执行到什么位置 必须立即停止运行,处理信号 处理结束 再继续执行后续指令

所有信号的产生发送都是由内核完成处理的

信号相关的概念

产生信号:

  1. 按键产生
  2. 系统调用产生
  3. 软件条件产生
  4. 硬件异常产生
  5. 命令产生

概念 :
未决:产生与递达之间状态
递达:产生并且迭达到进程 之间被内核处理掉
信号处理方式:执行默认处理动作 忽略 捕捉(自定义)

  1. 执行默认动作
  2. 忽略
  3. 捕捉(调用户处理器)

    LINUX 内核的进程控制块PCB是一个结构体,task_struct 除了包含进程id,状态,工作目录,用户id,组id,文件描述符表,还包含了信号相关的信息,主要阻塞信号集和未决信号集。

阻塞信号集(信号屏蔽字) :
本质 :位图,用来记录信号的屏蔽状态。一旦被屏蔽的信号,在解除屏蔽前,一直 处于未决态。 相当于一直递达不到相应的进程

未决信号集:
本质:位图,用来记录信号的处理状态。该信号集中的信号,表示,已经产生,但尚未被处理。
信号产生,未决信号集中描述该信号的位立即翻转为1 表信号处于未决状态,当信号被处理对应位翻转为0,这一时刻往往非常短暂。
信号产生后由于某些原因(主要是阻塞)不能抵达。这类信号的集合称之为未决信号集,再屏蔽解除前,信号一直处于未决状态

信号屏蔽字与未决信号集两个位图位于进程PCB控制块中。

位图:
位图,就是用每一位来存放某种状态,适用于大规模数据,但数据状态又不是很多的情况。通常是用来判断某个数据存不存在的。位图其实是用数组实现的,数组的每一个元素的每一个二进制位都可以表示一个数据在或者不在,0表示数据存在,1表示数据不存在。因为比特位只有两种状态,要不是0,要不就是1,所以位图其实就是一种直接定址法的哈希,只不过位图只能表示这个值在或者不在。

信号四要素

kill -l 查看当前系统中常规信号
信号四要素:
信号使用之前 应先确定其四要素
编号 名称 对应事件 默认处理动作

编号 kill -l 查看编号

9)SIGKILL 19)SIGSTOP 信号 不允许忽略和捕捉,只能执行默认动作,甚至不能将其设置为阻塞。
另外需清楚 只有每个信号所对应的事件发生了 该信号才会被递送(但不一定递送)不应乱发信号
终端按键产生信号
ctrl c SIGINT 终止 中断
ctrl z SIGSTOP 暂停/停止
ctrl \ SIGQUIT 退出

硬件异常产生信号
除以0操作 8)SIGFPE
非法访问内存 11)SIGSEGV(段错误)
总线错误 7)SIGBU

kill函数和kill命令

  1. int kill(pid_t pid,int signum)
  2. 参数:
  3. pid
  4. > 0 :发送信号给指定进程
  5. = 0:发送信号给跟调用kill函数的那个进程处于同一进程组的进程。
  6. < -1:取绝对值,发送信号给该绝对值所对应的进程组的所有组员
  7. = -1:发送信号给,由权限的所有进程
  8. signum:待发送的信号。
  9. 返回值:
  10. 成功 0
  11. 失败 :-1 error

例子:
image.png
子进程发送信号kill父进程
getppid() 为父进程大于0 则发送SIGKILL信号给父进程·

alarm函数

设置定时器闹钟 , 在指定seconds后,内核会给当前进程发送14)SIGALRM信号 进程收到该信号后,默认动作终止。
定时 与进程状态无关(自然定时法)就绪 运行 挂起(堵塞 暂停)终止 僵尸 无论处于那种状态 alarm都计时
例子 alarm(5) —> 3sec —>alarm(4) —-> 5sec —>alarm(5)
返回 5 2 0
只有前一个闹钟完成时才能继续设置下一个闹钟 因为一个进程只有一个闹钟可以用
alarm函数 使用自然计时法
定时发送SIGALRM给当前进程
每个进程都有且只有唯一一个定时器

  1. unsigned int alarm(unsigned int seconds)
  2. seconds : 定时秒数
  3. 返回值 :上次定时剩余秒数
  4. 无错误现象
  5. alarm(0) 取消闹钟

time命令:查看程序执行时间。实际时间 = 用户时间+内核时间+等待时间

例子自写 使用alarm函数计时 打印变量i的值。

setitimer函数

  1. int setitimer(int which,const struct itimerval *new_value,struct itimerval * old_value)
  2. 参数:
  3. which
  4. ITIMER_REAL:采用自然计时-->SIGALRM
  5. ITIMER_VIRTUAL:采用用户空间计时-->SIGTALRM 只计算进程占用CPU的事件
  6. ITIMER_PROF:采用内核+用户空间计时 -->SIGPROF 计算占用cpu及系统调用的时间
  7. new_value:定时秒数
  8. 类型 struct itimerval
  9. {
  10. struct timeval
  11. {
  12. time_t tv_sec;//second
  13. suseconds_t tv_usec;//microseconds
  14. }it_interval;--->周期定时秒数
  15. struct timeval
  16. {
  17. time_t tv_sec;//second
  18. suseconds_t tv_usec;//microseconds
  19. }it_value;--->第一次定时秒数
  20. }
  21. old_value:传出参数 上次定时剩余时间
  22. e.g.
  23. struct itimerval new_t;
  24. struct itimerval old_t;
  25. new_t.it_interval.tv_sec = 0;
  26. new_t.it_interval.tv_usec = 0;
  27. new_t.it_value.tv_sec = 1;
  28. new_t.it_value.tv_usec = 0;
  29. int ret = setitimer(&new_t, &old_t); 定时 1
  30. 返回值:
  31. 成功: 0
  32. 失败: -1 errno

例子 使用setitimer定时 向屏幕打印信息。
image.png
可以理解为第一个为定时器 什么时候触发 第二个是间隔时间

信号操作函数

  1. sigset_t set; 自定义信号集
  2. sigemptyset(sigset_t *set);清空信号集
  3. sigfillset(sigset_t *set); 全部置 1
  4. sigaddset(sigset_t *set, int signum); 将一个信号添加到集合中
  5. sigdelset(sigset_t *set, int signum); 将一个信号从集合中移除
  6. sigismemberconst sigset_t *setint signum); 判断一个信号是否在集合中。
  7. 在的话返回1 不在返回0
  8. 设置信号屏蔽字和解除屏蔽:
  9. int sigprocmask(int how,const sigset_t *set,sigset_t *oldset)
  10. how:
  11. SIG_BLOCK 设置阻塞
  12. SIG_UNBLOCK 取消阻塞
  13. SIG_SETMASK 用自定义set替换mask
  14. set:自定义set
  15. oldset:旧有的mask
  16. 查看未决信号集:
  17. int sigpending(sigset_t &set)
  18. set:产出的未决信号集

信号捕捉:signal()

image.png

sig 标志希望修改处置的信号
handle 为标识信号抵达时所调用的函数的地址 (函数指针)
void handler(int sig)
{

}

信号类型

  1. /* Signals. */
  2. #define SIGHUP 1 /* Hangup (POSIX). 终端连接断开信号*/
  3. #define SIGINT 2 /* Interrupt (ANSI). 中断信号,终端中输入ctrl+c,可中断前台进程*/
  4. #define SIGQUIT 3 /* Quit (POSIX). 退出信号,终端中输入ctrl+\,可退出前台进程,同时产生core文件*/
  5. #define SIGILL 4 /* Illegal instruction (ANSI). 非法指令信号,4.3BSD的abort函数产生该信号 */
  6. #define SIGTRAP 5 /* Trace trap (POSIX). 调试信号,当在程序中设置断点后,该信号使得调试程序获得控制权*/
  7. #define SIGABRT 6 /* Abort (ANSI). 程序异常终止信号,abort函数产生该信号 */
  8. #define SIGIOT 6 /* IOT trap (4.2 BSD). 功能同SIGABRT*/
  9. #define SIGBUS 7 /* BUS error (4.2 BSD). 程序访问不存在的内存区域时,产生该信号*/
  10. #define SIGFPE 8 /* Floating-point exception (ANSI). 算术异常,如除以0*/
  11. #define SIGKILL 9 /* Kill, unblockable (POSIX). 不能被忽略,非阻塞,可杀死任意一个运行中的进程*/
  12. #define SIGUSR1 10 /* User-defined signal 1 (POSIX). 用户自定义1*/
  13. #define SIGSEGV 11 /* Segmentation violation (ANSI). 当程序访问没有访问权限的内存区域,或者访问非可读的内存区域时,产生该信号,如数组越界 */
  14. #define SIGUSR2 12 /* User-defined signal 2 (POSIX). 用户自定义2 */
  15. #define SIGPIPE 13 /* Broken pipe (POSIX). 当管道读端已关闭,继续往管道中写,产生该信号 */
  16. #define SIGALRM 14 /* Alarm clock (POSIX). alarm函数超时时产生该信号,默认动作是程序终止 */
  17. #define SIGTERM 15 /* Termination (ANSI). 终止程序信号,命令kill默认使用该参数*/
  18. #define SIGSTKFLT 16 /* Stack fault. */
  19. #define SIGCLD SIGCHLD /* Same as SIGCHLD (System V). */
  20. #define SIGCHLD 17 /* Child status has changed (POSIX). 子进程终止或停止时,产生该信号,默认被忽略*/
  21. #define SIGCONT 18 /* Continue (POSIX). */
  22. #define SIGSTOP 19 /* Stop, unblockable (POSIX). 停止一个作业控制进程*/
  23. #define SIGTSTP 20 /* Keyboard stop (POSIX). ctrl+z产生该信号,改信号使得前台进程挂起*/
  24. #define SIGTTIN 21 /* Background read from tty (POSIX). */
  25. #define SIGTTOU 22 /* Background write to tty (POSIX). */
  26. #define SIGURG 23 /* Urgent condition on socket (4.2 BSD). */
  27. #define SIGXCPU 24 /* CPU limit exceeded (4.2 BSD). 进程超过了CPU软限制产生该信号*/
  28. #define SIGXFSZ 25 /* File size limit exceeded (4.2 BSD). 进程超过了文件大小软限制产生该信号*/
  29. #define SIGVTALRM 26 /* Virtual alarm clock (4.2 BSD). */
  30. #define SIGPROF 27 /* Profiling alarm clock (4.2 BSD). */
  31. #define SIGWINCH 28 /* Window size change (4.3 BSD, Sun). */
  32. #define SIGPOLL SIGIO /* Pollable event occurred (System V). */
  33. #define SIGIO 29 /* I/O now possible (4.2 BSD). */
  34. #define SIGPWR 30 /* Power failure restart (System V). */
  35. #define SIGSYS 31 /* Bad system call. */
  36. #define SIGUNUSED 31
  37. #define _NSIG 65 /* Biggest signal number + 1
  38. (including real-time signals). */
  39. #define SIGRTMIN (__libc_current_sigrtmin ())
  40. #define SIGRTMAX (__libc_current_sigrtmax ())

1) SIGHUP
本信号在用户终端连接(正常或非正常)结束时发出, 通常是在终端的控制进程结束时, 通知同一session内的各个作业, 这时它们与控制终端不再关联。

登录Linux时,系统会分配给登录用户一个终端(Session)。在这个终端运行的所有程序,包括前台进程组和后台进程组,一般都 属于这个 Session。当用户退出Linux登录时,前台进程组和后台有对终端输出的进程将会收到SIGHUP信号。这个信号的默认操作为终止进程,因此前台进 程组和后台有终端输出的进程就会中止。不过可以捕获这个信号,比如wget能捕获SIGHUP信号,并忽略它,这样就算退出了Linux登录,wget也 能继续下载。

此外,对于与终端脱离关系的守护进程,这个信号用于通知它重新读取配置文件。

2) SIGINT
程序终止(interrupt)信号, 在用户键入INTR字符(通常是Ctrl-C)时发出,用于通知前台进程组终止进程。

3) SIGQUIT
和SIGINT类似, 但由QUIT字符(通常是Ctrl-)来控制. 进程在因收到SIGQUIT退出时会产生core文件, 在这个意义上类似于一个程序错误信号。

4) SIGILL
执行了非法指令. 通常是因为可执行文件本身出现错误, 或者试图执行数据段. 堆栈溢出时也有可能产生这个信号。

5) SIGTRAP
由断点指令或其它trap指令产生. 由debugger使用。

6) SIGABRT
调用abort函数生成的信号。

7) SIGBUS
非法地址, 包括内存地址对齐(alignment)出错。比如访问一个四个字长的整数, 但其地址不是4的倍数。它与SIGSEGV的区别在于后者是由于对合法存储地址的非法访问触发的(如访问不属于自己存储空间或只读存储空间)。

8) SIGFPE
在发生致命的算术运算错误时发出. 不仅包括浮点运算错误, 还包括溢出及除数为0等其它所有的算术的错误。

9) SIGKILL
用来立即结束程序的运行. 本信号不能被阻塞、处理和忽略。如果管理员发现某个进程终止不了,可尝试发送这个信号。

10) SIGUSR1
留给用户使用

11) SIGSEGV
试图访问未分配给自己的内存, 或试图往没有写权限的内存地址写数据.

信号 11,即表示程序中可能存在特定条件下的非法内存访问。

12) SIGUSR2
留给用户使用

13) SIGPIPE
管道破裂。这个信号通常在进程间通信产生,比如采用FIFO(管道)通信的两个进程,读管道没打开或者意外终止就往管道写,写进程会收到SIGPIPE信号。此外用Socket通信的两个进程,写进程在写Socket的时候,读进程已经终止。

14) SIGALRM
时钟定时信号, 计算的是实际的时间或时钟时间. alarm函数使用该信号.

15) SIGTERM
程序结束(terminate)信号, 与SIGKILL不同的是该信号可以被阻塞和处理。通常用来要求程序自己正常退出,shell命令kill缺省产生这个信号。如果进程终止不了,我们才会尝试SIGKILL。

17) SIGCHLD
子进程结束时, 父进程会收到这个信号。

如果父进程没有处理这个信号,也没有等待(wait)子进程,子进程虽然终止,但是还会在内核进程表中占有表项,这时的子进程称为僵尸 进程。这种情 况我们应该避免(父进程或者忽略SIGCHILD信号,或者捕捉它,或者wait它派生的子进程,或者父进程先终止,这时子进程的终止自动由init进程 来接管)。

18) SIGCONT
让一个停止(stopped)的进程继续执行. 本信号不能被阻塞. 可以用一个handler来让程序在由stopped状态变为继续执行时完成特定的工作. 例如, 重新显示提示符

19) SIGSTOP
停止(stopped)进程的执行. 注意它和terminate以及interrupt的区别:该进程还未结束, 只是暂停执行. 本信号不能被阻塞, 处理或忽略.

20) SIGTSTP
停止进程的运行, 但该信号可以被处理和忽略. 用户键入SUSP字符时(通常是Ctrl-Z)发出这个信号

21) SIGTTIN
当后台作业要从用户终端读数据时, 该作业中的所有进程会收到SIGTTIN信号. 缺省时这些进程会停止执行.

22) SIGTTOU
类似于SIGTTIN, 但在写终端(或修改终端模式)时收到.

23) SIGURG
有”紧急”数据或out-of-band数据到达socket时产生.

24) SIGXCPU
超过CPU时间资源限制. 这个限制可以由getrlimit/setrlimit来读取/改变。

25) SIGXFSZ
当进程企图扩大文件以至于超过文件大小资源限制。

26) SIGVTALRM
虚拟时钟信号. 类似于SIGALRM, 但是计算的是该进程占用的CPU时间.

27) SIGPROF
类似于SIGALRM/SIGVTALRM, 但包括该进程用的CPU时间以及系统调用的时间.

28) SIGWINCH
窗口大小改变时发出.

29) SIGIO
文件描述符准备就绪, 可以开始进行输入/输出操作.

30) SIGPWR
Power failure

31) SIGSYS
非法的系统调用。

image.png

信号操作函数使用原理分析

image.png

ret = sigprocmask(SIG_BLOCK, &set, &oldset);
设置信号屏蔽集 解除屏蔽 其本质就是读取或者修改进程中的信号屏蔽集
注意 : 屏蔽信号只是将信号处理延后执行(延至解除屏蔽)而忽略表示将信号丢掉
设置信号屏蔽字和解除屏蔽:
int sigprocmask(int how,const sigset_t set,sigset_t oldset)
how:
SIG_BLOCK 设置阻塞 (屏蔽) 位或
SIG_UNBLOCK 取消阻塞(解除) 取反再位与
SIG_SETMASK 用自定义set替换mask //不推荐用
set:自定义set 哪个为1 就屏蔽哪个
oldset:旧有的mask
查看未决信号集:
int sigpending(sigset_t &set)传出参数
set:产出的未决信号集

sigaction实现信号捕捉

image.png
一般sa_mask 传全部为0
一般flags 传0

信号捕捉特性:
1. 捕捉函数执行期间,信号屏蔽字 由 mask —> sa_mask , 捕捉函数执行结束。 恢复回 mask
2. 捕捉函数执行期间,本信号自动被屏蔽(sa_flgs == 0).
3. 捕捉函数执行期间,被屏蔽信号多次发送,解除屏蔽后只处理一次!

SIGCHID信号

SIGCHLD的产生条件

子进程终止时
子进程接收到SIFSTOP信号停止时
子进程处在停止态,接收到SIGCON后唤醒时

借助SIGCHILD信号回收子进程

子进程结束运行,其父进程会收到SIGCHLD信号,该信号的默认处理动作是忽略,可以捕捉该信号,在捕捉函数中完成子进程状态的回收

中断系统调用

系统调用可分为两类 :慢速系统调用和其他系统调用
1.慢速系统调用,可能会使进程永远堵塞的一类,如果在阻塞期间收到一个信号,该系统调用就被中断,不再继续执行(早期),也可以设定系统调用是否重启,如read pause wait。。
2.其他系统调用: getpid getppid fork