1.信号概念
信号是软件中断,很多比较重要的应用程序都需要信号处理。信号是一种进程之间或者内核与进程间异步通信的一种机制,例如:用户在终端键入中断键,会通过信号机制停止一个程序。
2.信号的共性
- 简单
- 不能携带大量信息
- 满足某个特设条件才发送
3.信号的机制
A给B发送信号,B 收到信号之前执行自己的代码,收到信号后,不管执行到程序的什么位置,都要暂停运行,去处理信号,处理完毕再继续执行。与硬件中断类似——异步模式。但信号是软件层面上实现的中断,早期常被称为“软中断”。
信号的特质:由于信号是通过软件方法实现,其实现手段导致信号有很强的延时性。但对于用户来说,这个延迟时间非常短,不易察觉。
每个进程收到的所有信号,都是由内核负责发送,内核处理
4.与信号相关的事件和状态
产生信号:
- 按键产生:如:Ctrl+c、Ctrl+z、Ctrl+/
- 系统调用产生:如:kill、raise、abort
- 软件条件产生:如:定时器 alarm
- 硬件异常产生:如:非法访问内存(段错误)、除 0(浮点数例外)、内存对齐出错(总线错误)
- 命令产生:如:kill命令
递达:递送并且到达进程。
未决:产生和递达之间的状态。主要由于阻塞(屏蔽)导致该状态。
信号的处理方式:
- 执行默认动作(每一个信号都有默认的处理事件)
- 忽略
- 捕捉(调用户处理函数 )
linux内核的进程控制块PCB是一个结构体,包含了信号相关信息,主要指阻塞信号集和未决信号集。
阻塞信号集:将某系信号加入集合,对他们设置屏蔽,当屏蔽x信号后,在收到该信号,该信号的处理将推后(解除屏蔽后)
未决信号集:信号产生,未决信号集中该信号位立刻反转1,表示信号处于未决状态。当信号被处理对应位反转为0.这一刻往往非长短暂。
5.信号四要素
在信号使用之前,要确定其四要素
编号、名称、事件、默认处理动作
6.信号的产生
- 终端按键产生信号
- 硬件异常产生信号
- kill函数/命令产生信号
- 函数原型
1 int kill(pid_t pid, int sig); //成功:0;失败:-1,设置errno 2 //sig:不推荐直接使用数字,应使用宏名,因为不同操作系统信号编号可能不同,但名称一致 3 //pid > 0: 发送信号给指定的进程 4 //pid = 0: 发送信号给 与调用 kill 函数进程属于同一进程组的所有进程。 5 //pid < 0: 取|pid|发给对应进程组。 6 //pid = -1:发送给进程有权限发送的系统中所有进程
- kill命令产生信号:kill -SIGKILL pid
- 函数原型
- 软件条件产生信号
- alarm函数 : 设置定时清。指定seconds后,内核会给当前进程发送SIGALRM信号,进程收到该信号,默认动作中止,每个进程都有且只有一个唯一定时器。
-
1 unsigned int alarm(unsigned int seconds); //返回 0 或剩余的秒数,无失败。
- 使用time命令查看程序执行的时间
- time ./test
- 实际执行时间=系统时间+用户时间+等待时间
- time ./test > out
- 结论:程序运行的瓶颈在于IO,优化程序,首选优化IO
- setitimer函数原型:设置定时器。可替代alarm函数。精度微秒,可以实现周期定时
-
1 int setitimer (int which, const struct itimerval *new_value, struct itimerval *old_value); //成功:0;失败:-1,设置errno
//which
:指定定时方式(①自然定时:ITIMER_REAL
)(②虚拟空间计时(用户空间)ITIMER_VIRTUAL
)(③运行时计时(用户+内核):ITIMER_PROF
)
//参数二
:结构体设置间隔时间和单次时间
//参数三
:返回旧闹钟的剩余时间(传出参数)
//it_value
:结构体内部的参数:第一次触发信号的时间
//it_interval
:结构体内部的参数:用来设定两次定时任务之间间隔的时间(第二次和第一次 or 第三次和第二次之间的时间间隔)
7.信号集操作函数+原理(重)
控制原理:内核通过读取未决信号集来判断信号是否应被处理。信号屏蔽字mask可以影响未决信号集。而我们可以再应用程序中自定义set来改变mask。以达到屏蔽指定信号的目的。(重点)
sigprocmask函数
作用:用来屏蔽信号、解除屏蔽也是用该函数。其本质,读取或修改进程的信号屏蔽字(PCB中)
1 int sigprocmask(int how, const sigset_t *set, sigset_t *oldset); //成功:0;失败:-1,设置 errno 2 //set:传入参数,是一个位图,set 中哪位置 1,就表示当前进程屏蔽哪个信号。 3 //oldset:传出参数,保存旧的信号屏蔽集 4 //how 参数取值: 假设当前的信号屏蔽字为 mask 5 //SIG_BLOCK: 该进程新的信号屏蔽字是其当前信号屏蔽字和set指向信号集的并集。set包含了我们希望阻塞的附加信号。 6 //SIG_UNBLOCK: 该进程新的信号屏蔽字是其当前信号屏蔽字和set所指向信号集的补集的交集。set包含了我们希望解除阻塞的信号. 7 //SIG_SETMASK: 自定义set替换mask(mask表示系统自带的阻塞信号集)
sigpending函数
作用:读取当前进程的未决信号集
1 int sigpending(sigset_t *set); //set 传出参数。 返回值:成功:0;失败:-1,设置 errno
8.信号捕捉
signal函数
作用:注册一个信号捕捉函数(注册而非创建)
原型:
1 sighandler_t signal(int signum, sighandler_t handler); 2 typedef void (*sighandler_t)(int);//函数指针类型 需要注意的就是需要传入一个整形参数 不管用不用
sigaction函数
作用:修改信号处理动作(通常在Linux用来注册一个信号的捕捉函数)
原型:
1 int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact); 2 //act: 传入参数、新的处理方式 3 //oldact: 传出参数,旧的处理方式
9.信号捕捉特性(重)
- 进程正常运行时,默认PCB中有一个信号屏蔽字,假定为✳,它决定了进程自动屏蔽那些信号。当注册了某个信号捕捉函数,捕捉到该信号后,要调用该函数。而该函数有可能执行很长时间,在这期间所屏蔽的信号不由✳来指定。而是用sa_mask来指定。调用完信号处理函数,在恢复为✳。
- xxx信号捕捉函数执行期间,xxx信号自动被屏蔽
- 阻塞的常规信号不支持排队、产生多次只记录一次。
10.内核实现信号捕捉过程
11.SIGCHLD信号
产生条件:
子进程中止时
子进程接收到SIGSTOP信号停止时
子进程处在停止态,接收到SIGCONT后唤醒时
借助该信号回收进程:
子进程结束运行,其父进程会收到 SIGCHLD 信号。该信号的默认处理动作是忽略。可以捕捉该信号,在捕捉函数中完成子进程状态的回收。
原创文章,作者:ItWorker,如若转载,请注明出处:https://blog.ytso.com/282227.html