linux系统编程手册阅读笔记-c20:信号的基本概念
2017-09-20 17:03
399 查看
chapeter 20 :信号的基本概念
内核信号机制实现
http://www.spongeliu.com/165.html当进程P2向p1发送信号后,内核接受到信号,并将其放在p1的信号队列中,当p1再次陷入内核态时,会检查信号队列,并根据相应的信号调取相应的信号处理函数。
- p1什么时候会陷入内核态?
当前进程由于系统调用、中断或异常而进入系统空间,到返回用户空间的前夕。
当前进程在内核中进入睡眠以后刚被唤醒的时候(必定是在系统调用中),或者由于不可忽略信号的存在而提前返回到用户空间
- 信号处理
当进程由于中断等陷入内核态之后,寄存器等信息会被压入内核栈,保存现场用于返回到用户栈时继续执行程序,在完成了系统调用后,会调用do_signal()函数(栈会被压入相关的信息),检查信号队列,如果有信号,则会根据信号向量表找到信号处理函数入口位置,将保存在内核栈上的eip指令寄存器修改至该入口,然后内核栈上的相应内容拷贝到用户栈上,跳转到用户态执行信号处理函数,执行完毕之后,根据(跳转时)用户堆栈记录的信息返回到内核态上,继续检查信号队列,反复这个过程,直到所有信号处理完毕。
概念
等待状态(pending):信号在产生后,会于稍后传递给某一进程,在产生和到达期间,就被称之为等待状态(pending)。如将信号添加到进程的信号掩码中,就会阻塞信号的到达,信号将处于等待状态。信号处理器程序:用于为响应传递来的信号而执行适当的任务,可以通过signal等函数进行设置
常见linux信号
信号在linux中以数字进行编号,为了更方便使用,linux也为之起了变量别名名称 | 信号值 | 描述 | SUSv3 | 默认 |
---|---|---|---|---|
SIGINT | 2 | 终端中断 | yes | term |
signal()
更改信号处理函数#include<signal.h> void (*signal(int sig, void (*handler)(int))) (int); 更改sig的信号处置函数为handler,返回旧的信号处理函数指针,如果失败将返回SIG_ERR(一个函数指针)
除了自定义的handler信号处理函数之外,还提供重置和忽略函数
void handler(int sig){ //自定义 /*-----*/ } SIG_DFL //重置默认处理函数 SIG_IGN //忽略信号,内核会直接丢弃该信号
使用样例
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<signal.h> #include<error.h> #include<string.h> #include<unistd.h> #include<typeinfo> #include<iostream> #include<cxxabi.h> using namespace std; void errExit(const char * str){ fprintf( stderr,"%s\n",str); exit(-1); } void handler(int sig){ printf("caputre ternimal interrupt\n"); } int main(int argc, char*argv[]){ void (*old_handler)(int); old_handler= signal(SIGINT,handler); //为SIGINT绑定新的信号处理函数。 if(old_handler == SIG_ERR) //如果更改信号处理函数失败 errExit("signal err"); sleep(10); /*在此时按下contrl + c*/ sleep(10); if(signal(SIGINT,old_handler)==SIG_ERR) //恢复旧的信号处理函数 errExit("signal err"); return 0; } >>>>>>>>>>>>>>>>>>>>>>>>>要使用g++进行编译 woder@ubuntu:~/project/osprogram$ ./test ^Ccaputre ternimal interrupt ^Ccaputre ternimal interrupt
上文程序一个需要注意的地方就是使用了两次sleep(),如果只使用了一个sleep(),本来处于睡眠状态的进程会因为信号的到来被唤醒,进行信号处理然后顺序执行结束进程,让人误以为执行了新的处理器函数后,还执行了原来的中断处理函数,其实没有。
kill()
发送信号函数#include<signal.h> int kill(pid_t pid, int sig); 向进程pid发送信号sig,成功返回0,失败返回-1
pid不同情况的含义
pid > 0: 发送给pid进程
pid = 0: 发送给同组进程的每个进程,包括自己
pid <-1: 发型到每个有权利可以发送的进程,除了init(pid=1)和自身进程
发送信号的权限
1.特权级,可以向所有进程发送任何信号
2.root用户和init进程是特例,只能接受已经安装了处理器函数的信号,防止init被杀死
3.发送进程的实际用户id或有效用户id等于接受进程的实际用户id或保存设置用户id。
如果无权发送的话,kill()会返回-1,并将errno置为EPERM (发送组的时候成功一个就算成功)。
raise()
向自身发送信号#include<signal.h> int raise(int sig);给自身发送sig信号
raise以及kill()向自身发送信号的时候,信号立即传递(因为发送信号的本质是通过调用系统内核进行信号发送,所以进程会马上处于内核态
raise出错将返回非0值(不一定是-1)。唯一可能发生的错误是sig无效 errno 变为EINVAL
strsignal(int sig)
显示信号的描述#include<signal.h> extern const char * const sys_siglist[];//存储着相应信号描述的数组 char *strsignal(int sig);//返回对应的信号描述字符串的地址
下列使用两种方式获取关于 contrl+c即终端中断信号的描述
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<signal.h> #include<error.h> #include<string.h> #include<unistd.h> extern const char * const sys_siglist[]; int main(int argc, char*argv[]){ char *sig_desc=strsignal(SIGINT); printf("%s\n",sig_desc); printf("%s\n",sys_siglist[SIGINT]); //两种结果都一样 return 0; } >>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>使用g++ woder@ubuntu:~/project/osprogram$ ./test Interrupt Interrupt
信号集
信号集存储着一组不同的信号,是一种数据结构,数据类型为sigset_t
初始化函数
#include<signal.h> int sigemptyset(sogset_t * set);//将信号集set中的所有信号清空 int sigfillset(sigset_t *set);//将信号集set中的添加所有信号
添加删除函数
#include<signal.h> int sigaddset(sigset_t *set,int sig);//向set中添加信号sig int sigdelset(sigset_t *set,int sig);//向set中移除信号sig
判断是否包含成员
#include<signal.h> int sigismember(const sigset_t * set,int sig);判断sig是否为set成员,如果是返回1否则0。
可以通过此函数对信号1~NSIG-1(信号数量)进行一个遍历,判断是否为当前信号集中的信号
#include<stdio.h> #include<stdlib.h> #include<unistd.h> #include<sys/types.h> #include<signal.h> #include<error.h> #include<string.h> #include<unistd.h> void print_sig_set(const sigset_t *set){ for(int i=1;i<NSIG;i++){ if(sigismember(set,i)){ printf("signal %d in set\n",i); } } } int main(int argc, char*argv[]){ sigset_t set; sigfillset(&set); print_sig_set(&set); return 0; }
信号掩码
每个进程都有一个信号掩码,即一个用来阻塞部分信号对于该进程的传递的信号集,如果被阻塞的信号发送给了进程,该信号会进入进程的等待队列,直到掩码不再对该型号进行阻塞,进程才从等待信号集中获取该信号,引发处理器函数的信号会被自动的添加到等待信号集中,等处理器函数处理完毕后,才对该信号接触阻塞sigprocmask
显示对向当前进程的信号掩码中添加,或移除信号#include<signal.h> int sigprocmask (int how, const sigset_t *set,sigset_t *oldset);成功返回0,失败返回-1
对于how有三个参数描述如下
SIG_BLOCK:将信号集set中的信号添加到信号掩码中
SIG_UNBLOCK:将信号集set中的信号从信号掩码中移除
SIG_SETMASK:将信号集set中的信号直接赋值给信号掩码
oldset是返回的之前的信号掩码
sigpending()
获取当前处于等待队列中的信号#include<signal.h> int sigpending(sigset_t* set);成功返回0,失败-1
同一信号记录在等待信号集中,阻塞多次的情况下,在解除阻塞后只传递一次
sigaction()
除了signal()可以指定信号处理函数之外,sigaction()也可以#include<signal.h> int sigaction(int sig,const struct sigaction * act, struct sigaction * oldact);成功返回0,失败-1 sigaction结构如下 struct sigaction{ void (*sa_handler)(int); //对应于signal的handler,信号处理函数的地址 sigset sa_mask; //信号掩码 int sa_flags; void (*sa_restorer)(void); };
pause
调用pause将暂停进程的执行,直至信号处理器函数终端该调用#include<unistd.h> int pause(void)l
相关文章推荐
- Linux系统编程(20)——信号基本概念
- Linux代码阅读笔记----基本概念
- [学习笔记]信号基本概念(中断和信号)/名称及常用信号/信号处理/signal函数实践
- Linux系统编程(20)——信号基本概念
- 音视频解码模块阅读笔记(三)—视频编解码的基本概念--转载
- [分布式系统学习]阅读笔记 Distributed systems for fun and profit 之一 基本概念
- 《深入理解Linux网络技术内幕》阅读笔记 --- 路由基本概念
- PKCS#1规范阅读笔记1--------基本概念
- spark源码阅读笔记RDD(一)RDD的基本概念
- JavaScript高级程序设计(第3版)阅读笔记第02天-js基本概念
- Objective-C基础笔记(1)基本概念和第一个程序
- [模式识别].(希腊)西奥多里蒂斯<第四版>笔记11之__聚类:基本概念
- MPEG2_TS流基本概念和数据结构 分类: ffmpeg-SDL-VLC-Live555 2015-04-02 15:44 401人阅读 评论(0) 收藏
- linux中信号的基本概念
- UML学习笔记(二):复习面向对象的一些基本概念
- 《C++ primer》英文第五版阅读笔记(十三)——表达式基本知识
- UNIX/LINUX信号基本概念释疑
- 计算机操作系统笔记(2)--进程管理之进程的基本概念
- Kafka学习笔记一:基本概念
- 编解码学习笔记(一):基本概念