1. 概念--什么是信号
信号是事件发生时对进程的通知机制,有时也称之为软件中断。
在linux的操作系统中<signal.h> 以SIGxxxx的形式指代一种信号,他们实际上都是小整数的宏。
2. 信号的分类
1>用于内核向应用程序通知事件
2> 由实时信号构成
具体差异还得之后再改笔记
3. 什么情况下会引发信号
1> 硬件检测到错误并通知内核
2>用户输入了某种终端字符
3>发生软件时间 某一些定义的情况发生时会引发的信号
4. 对于信号的处理
引言:信号的传送
当当信号送达的时候,如果目标进程正在运行则可以立即接受信号并处理,当想要一段代码不会被信号中断,那么可以临时将信号加入到进程的信号掩码中(也就是将此信号阻塞了),等到希望处理该信号的时候再将信号从信号掩码中取出(解除阻塞)。这些操作均可使用系统调用完成。
1>程序默认操作
- 忽略信号:也就是说进程将信号丢弃
- 终止(杀死进程): 有时指的是异常终止进程而不是调用exit();函数的正常退出
- 产生核心转储文件同时进程终止 比如abort(); 函数引发的SIGABRT信号
- 停止进程:也就是暂停进程
- 恢复暂停的进程
2> 程序对于信号的设置的处理
- 采取默认行为,看上述
- 忽略信号 主要作用是让那些使进程终止的信号被屏蔽掉
- 执行信号处理器函数
5. 具体信号类型,及他们的默认行为
引言:linux中的标准信号共1~31 一共31个 还有其他的好多主要是为了兼容性 通过signal(7) 手册可查到所有的信号
- SIGABRT 当进程调用abort();函数时系统会向进程发送该信号默认情况下会生成核心转储文件并结束进程
- SIGALRM 经调用alarm(); 或者setitimer(); 设置的实时定时器一旦到期 内核将产生该信号
- SIGBUG 内存访问错误的时候 如mmap()创建后访问超过结尾
- SIGCHLD 父进程的某一个子进程终止的时候会向父进程发送此信号 当子进程收到信号停止或恢复的时候也有可能发送该信号
- SIGCLD 与SIGCHLD 意义一样
- SIGCONT 发送给停止的进程使它恢复,但是对于没有停止的进程,该进程将忽略这个信号
- SIGEMT 一个硬件错误,知道就好
- SIGFPE 由算术运算的的错 误而产生比如除以0
- SIGHUP 当终端断开的时候 将发送这个信号给终端控制程序,当大多数的守护进程收到该信号的时候会重新读取配置文件也就是说会重启
- SIGILL 执行格式不正确的(非法的)机器语言指令则给进程发送这个信号
- SIGINFO 用于获取前台进程组的信息 跟信号SIGPWR是一个意思
- SIGINT ctrl+c 默认行为是终止进程
- SIGIO 对于特定类型的文件描述符 进行I/O操作时,会发送这个信号给进程 跟SIGPOLL信号一样
- SIGIOT linux中与SIGABRT同义 但是在一些其他的Unix实现中 表示定义产生的硬件错误
- SIGKILL 这个信号太逗了 被解释成必杀信号,进程无法将其阻塞忽略或者捕捉,从而起到一击必杀的效果
- SIGLOST linux中存在但是没使用
- SIGPIPE 服务器程序中常常将这个信号屏蔽,因为当client 关闭链接后,再次向对端关闭的套接字中写数据(因为此时对面没有阅读进程)则引发该信号,而该信号的默认行为便是终止进程
- SIGPROF 由setitimer();函数调用设置的性能分析定时器一旦到时会触发该信号
- SIGVTALRM 由setitimer();函数调用设置的虚拟定时器一到期内核就会产生该信号
- SIGPWR 监控电源,当快没电的时候该信号会发送给init进程 并被用来有序关闭系统
- SIGQUIT 终止进程并生成核心转储文件,当程序陷入无限循环的时候可以用 之后用gdb进行调试
- SIGSEGV 段错误,也就是访问无效内存的时候产生
- SIGSTOP 必停信号 跟必杀挺像的就是作用是停止
- SIGSYS 系统调用错误 默认终止
- SIGTERM 用来终止进程的标准信号,也是kill killall程序的默认信号,直接使用SIGKILL直接终止进程是错误的方法,精心的程序应该为SIGTERM设置处理器程序,对程序遗留物进行清除,之后发送SIGKILL,从而全身而退
- SIGTRAP 不理解 之后再说吧
- SIGTSTP 作业停止信号 具体的后面学到了再来补充
- SIGTTIN 感觉像是想读我 你就得死 后台进程对终端进程使用read();函数的时候后台进程被发送这个信号 然后就被终止了
- SIGTTON 这个是当发生write();时,,剩下的跟之前的一样
- SIGURG 发送给一个进程,表示套接字上面存在带外数据
- SIGUSR1 SIGUSR2 这两个属于没有用的信号 内核绝对不会发送这两个信号 用户用来自己定义的
- SIGWONCH 对于图形界面,当终端的窗口尺寸发生变化时 会向前台进程组发送这个,比如vim 会进行重新绘制界面当收到这个信号的时候
- SIGXCPU 进程的CPU时间超出相应的资源限制的时候
- SIGXFSZ 当突破对文件的大小限制的时候 引发
6. 关于信号的系统调用和库函数调用
这里的头文件是 signal.h
1> 设置信号的处理器程序 void (*signal(int sig , void ( *handler) (int )) ) (int ); man 2
signal 系统调用是原始的设置信号处理的API 但是在不同的Unix环境中不具备移植性,应采用
sigaction();
¥¥可以使用 typedef void ( *sighandler_t )(int ); 来简化理解上述的函数头
这个函数的返回值是上一次的信号处置所使用的函数的地址 如果发生错误则返回SIG_ERR
这里的handle也可以设置成SIG_DFL(信息处置设置为默认) 或者SIG_IGN(设置为忽略) 注意 signal 返回也可能是这两个常量。
*****信息处理器的简介--为了更好的理解上述的系统调用 就是为信号设置了一个回调函数,当收到这些信号的时候进程暂停并由内核代表进程调用这个回调函数完事后再复原进程执行
2>发送信号 int kill(pid_t pid, int sig ); 传统系统调用 返回0 or 1 and error
sig的解释显而易见 这里重点做pid的解释
- pid > 0 发送信号给指定进程
- pid = 0 同组包括自己在内的进程
- pid = -1 发往除了自己和init进程之外的所有有发送权限的进程 root 可发到所有进程 广播呀兄弟
- pid < -1 以pid绝对值为进程组ID 的下属所有进程
**非特权进程的接受与发送的一些注意事项
需要注意的信号 SIGCONT 非特权ID可以给同一个会话中的任意一个进程发送该信号
关于返回值 如果没有对应的进程id,则失败并errno设置为ESRCH(没有查到这个进程)如果无权限则 失败errno设置为EPERM
**关于sig参数的扩展 sig 设置为0的时候 发送空信号 这时的kill 就像是只用来检查进程是否存在或者是否有权限了 不过无法判断僵尸进程,这个之后的章节会讨论
3> 发送信号的其他方式 这里是库函数调用 头文件signal.h
int raise(int sig); 非0 即出错谁让你不是系统调用 错误只可能是 EINVAL 也就是sig无效
这个函数的作用是传递给进程下的调用线程 sig信号 ;
向某一进程组发送信号 int killpg(pid_t pgrp , int sig);
其实相当于 kill(-pgrp ,sig);
7. 信号的信息获取及相关内容(如信号集)的学习及相关函数
1> 显示信号描述
每个信号都有一个信号说明用来说明信号的内容 这些描述性信息在一个数组中sys_siglist 中存在 直接用此数组加信号值的索引便能获取信号的描述信息
像这样输出信号值为2-20的信号的描述
或者使用
string 中的 char * strsignal(int sig); 等于加了 一层边界检查, 错误时 返回null 或者 错误字符串(不一定)
signal.h 中的 void psignal(int sig , char * msg); 输出格式是 msg + : +提示信息 标准错误上输出不经缓冲区
2>信号集的相关概念及操作
那我们为什么需要信号集呢,是因为许多系统调用都需要一组信号作为参数 多个信号的表示可以用一个称之为信号集的数据结构来表示 sigset_t 这是一个位掩码
1> 信号集的初始化 所用头文件 signal.h 返回值都是系统调用的样子-1+errno / 0
int sigemptyset(sigset_t * set); // 初始化一个空的信号集
int sigfillset(sigset_t *set ); // 初始化一个满的信号集 包含所有信号
int sigaddset(sigset_t * set , int sig); // 添加一个信号到信号集中
int sigdelset(sigset_t *set ,int sig); // 从信号集中删除信号
int sigismember(const sigset_t * set , int sig); //判断信号是否存在于信号集中
还有GNU C库实现了三个不标准但是耐用的三个函数 用的时候最好加上测试宏 _GNU_SOURCE
还是头文件signal.h
int sigandset(sigset_t * dset , sigset_t *first , sigset_t *second); // 求交集 first and second = dset;
int sigandset(sigset_t * dset , sigset_t *first , sigset_t *second);// 求并集 同上
int sigisemptyset( const sigset_t * dset ); // 判断集合是否为空 为空则true
补充一个常量 NSIG 为最大信号编号加一 必须定义测试宏 _GNU_SOURCE
8. 信号与进程(信号掩码等内容)
1> 信号掩码
定义: 内核会为每一个进程维护一个信号掩码,也就是一组信号,并阻塞其对进程的传递(信号掩码属于线程属性 牛逼呀)
如何添加呢 分了三种方式
- 当调用信号处理器程序的时候可以自动加入 这个在安装信号处理函数的时候可以设置sigaction();
- 在安装时 可以指定一组信号 则在此处理器程序执行期间 会将其阻塞
- sigprocmask();系统调用可以显式的添加或者移除信号
这里主要讲解sigprocmask()系统调用 头文件 signal.h
int sigprocmask( int how , const sigset_t * set , sigset_t * oldset);
参数解释 how:代表对进程信号掩码的操作 其中包含的宏选择为
SIG_BLOCK 将信号集中的信号添加进信号掩码中去
SIG_UNBLOCK 与上面的表达相反
SIG_SETMASK 将set设置为新的信号掩码
一般来讲如果oldset不为空 则用于保存之前的信号掩码,当set为空的时候会 忽略how的值 当接触阻塞时 该进程会立即收到该信号 那对于这些阻塞的信号的处理是什么样子的呢 且看接下来的分解
2> 阻塞信号集处理
这里讲的也就是 当进程接收到信号掩码中的信号的时候会将该信号加入到该进程的等待信号集中去
所以呢 为了确定那些信号在等待信号集中 则可以使用如下系统调用 头文件signal.h -1+errno/ 0
int sigpending( sigset_t * set ); // 成功的话结果存入set
1> 这个单独一个点是因为我觉得比较重要 等待信号集只是一个信号掩码,就是说只能证明存在与否 而不能得到存在次数,多次发送重复信号并阻塞,则之后解除阻塞只发送给进程一次。
9. 信号处置扩展重点(一般就使用sigaction() 也是系统调用)
1> 相比于signal()系统调用的优缺点 优点 》灵活性比signal强并且提供更多的精确的控制对于处理程序 最后便是移植性比signal好得多
struct sigaction{ void (*sa_handle) (int ); sigset_t sa_mask ; //这个的意思是 在处理器程序执行期间 会对这组信号进行加入信号掩码防止比如递归中断 int sa_flags; /*control handle 第二部分详解*/ void (*sa_restorer)(void) ; //未被应用 用法比较高级呀 难受 } ;
缺点 》 使用相对复杂一点点
2> 头文件 signal.h
int sigaction(int sig , const struct sigaction *act , struct sigaction *oldact); // -1+errno / 0
其中sigaction的结构体 如上
这里详解下 sa_flags 中的掩码具体值等等
- SA_NOCLDSTOP 当子进程由于受信号作用而停止或者继续的时候 不会产生该信号 SIGCHLD
- SA_NOCLDWAIT SIGCHLD信号时 子进程不会转化成僵尸进程。。。这怎么说??具体看信号第二篇
- SA_NODEFER 不会在执行期间自动添加到信号掩码中
- SA_ONSTACK 看信号第二篇内容
- SA_RESETHAND 在调用处理器程序之前将信号处置设置为SIG_FAL(默认) 后面只能主动改变
- SA_RESTART 自动重启由处理器程序停止的进程
- SA_SIGINFO 具体的看信号第二篇中的详解
10. 进程的停止
进程自己主动挂起 知道接收到信号后执行处理器函数时 去中断该调用或者 使用信号kill 该进程
文件 unistd.h
int pause(void ); //总返回-1 and errno errno具体情况请参见信号知识第二篇