信号的产生:
1、终端按键,ctrl+C sigint信号
2、硬件异常:除0、无效的内存引用等。由硬件检测,并通知内核,然后内核为该条件发生时运行的进程产生适当的信号。
3、进程调用kill函数、用户执行kill命令。
4、某种软件条件发生,通知进程时。例如:sigpipe sigalarm。
信号的处理:
1、忽略。sigkill和sigstop不能忽略不能捕捉。
2、捕捉信号。signal函数 用户函数。
3、系统默认动作。
void (*signal(int signo, void (*func)(int)))(int); 返回值函数指针,有个int的参数。
执行低速系统调用 阻塞时 捕捉到一个信号,则调用被中断。系统调用返回出错,errno是EINTR。
低速系统调用是使进程永远阻塞的一类调用:读文件、写文件、打开、pause、进程间通信函数。
作者:peilin song
链接:https://www.zhihu.com/question/24913599/answer/115102869
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
链接:https://www.zhihu.com/question/24913599/answer/115102869
来源:知乎
著作权归作者所有,转载请联系作者获得授权。
信号是一种软件层面上对中断的模拟,而这种软件模拟的信号或者说中断的产生,有三大类:
1. 硬件异常产生的错误。比如非法访问内存,除数为0...
2. 外部信号。键盘上的Ctrl-C产生SGINT信号,定时器到期产生SIGALRM…
3. 显示的请求。 主要是通过Kill函数发送信号给指定进程。
在Linux里面每个进程都是按照进程描述符 task_struct 结构创建的,还有一个叫 task vector的东西 ,从名字上就能看出来这是一个数组,这里面保存的是指向每一个进程的指针,即指向每一个 task_struct的指针,因此一个Linux系统最大的进程数,取决于 task vector这个数组的大小,一般默认是512个。
在进程描述符 task_struct里面,其中一项是 Signal_Strct,在 Signal_Strct这里面有一项 list_head的描述符,在这里面有一个 sigset_t表,定义了64种信号的所代表的含义。
铺垫了很多,其实主要是为了铺垫64种信号存放的位置。也就是说在每个进程之中,都有存着一个表,里面存着每种信号所代表的含义,而这也是信号机制的根本。
由于信号的触发和发送是随机的,也就是异步的。接收进程是无法预知什么时间,会收到哪个信号的。下面就开始讲下信号的详细发送机制,举例说明, 如果有A,B两个进程,A进程接收到出发条件,开始发送信号给B进程,信号并不是直接从进程A发送给进程B,而是要通过内核来进行转发。之所以要通过内核来转发,这样做的目的应该也是为了对进程的管理和安全因素考虑。因为在这些信号当中,SIGSTOP和SIGKILL这两个信号是可以将接收此信号的进程停掉的,而这类信号,肯定是需要有权限才可以发出的,不能够随便哪个程序都可以随便停掉别的进程。
A进程发送的信号消息,其实就是根据上面的那个信号表,根据需要对相应的表项进行设置。内核接受到这个信号消息后,会先检查A进程是否有权限对B进程的信号表对应的项进行设置,如果可以,就会对B进程的信号表进行设置,这里面信号处理有个特点,就是没有排队的机制,也就是说某个信号被设置之后,如果B进程还没有来及进行响应,那么如果后续第二个同样的信号消息过来,就会被阻塞掉,也就是丢弃。
内核对B进程信号设置完成后,就会发送中断请求给B进程,这样B进程就进入到内核态,这个时候进程B根据那个信号表,查找对应的此信号的处理函数,然后设置frame,设置好之后,跳回到用户态执行信号处理函数,处理完成后,再次返回到内核态,再次设置frame,然后再次返回用户态,从中断位置开始继续执行。
这个frame其实就是在用户态和内核态之间跳转的时候,对堆栈现场的压栈保存。
大致的原理就是这么一个过程,在真正使用的时候,就比较简单了,用kill函数发送信号,在接收进程里,通过signal或者signalaction函数调用sighandler,来启动对应的函数处理信号消息。
1. 硬件异常产生的错误。比如非法访问内存,除数为0...
2. 外部信号。键盘上的Ctrl-C产生SGINT信号,定时器到期产生SIGALRM…
3. 显示的请求。 主要是通过Kill函数发送信号给指定进程。
在Linux里面每个进程都是按照进程描述符 task_struct 结构创建的,还有一个叫 task vector的东西 ,从名字上就能看出来这是一个数组,这里面保存的是指向每一个进程的指针,即指向每一个 task_struct的指针,因此一个Linux系统最大的进程数,取决于 task vector这个数组的大小,一般默认是512个。
在进程描述符 task_struct里面,其中一项是 Signal_Strct,在 Signal_Strct这里面有一项 list_head的描述符,在这里面有一个 sigset_t表,定义了64种信号的所代表的含义。
铺垫了很多,其实主要是为了铺垫64种信号存放的位置。也就是说在每个进程之中,都有存着一个表,里面存着每种信号所代表的含义,而这也是信号机制的根本。
由于信号的触发和发送是随机的,也就是异步的。接收进程是无法预知什么时间,会收到哪个信号的。下面就开始讲下信号的详细发送机制,举例说明, 如果有A,B两个进程,A进程接收到出发条件,开始发送信号给B进程,信号并不是直接从进程A发送给进程B,而是要通过内核来进行转发。之所以要通过内核来转发,这样做的目的应该也是为了对进程的管理和安全因素考虑。因为在这些信号当中,SIGSTOP和SIGKILL这两个信号是可以将接收此信号的进程停掉的,而这类信号,肯定是需要有权限才可以发出的,不能够随便哪个程序都可以随便停掉别的进程。
A进程发送的信号消息,其实就是根据上面的那个信号表,根据需要对相应的表项进行设置。内核接受到这个信号消息后,会先检查A进程是否有权限对B进程的信号表对应的项进行设置,如果可以,就会对B进程的信号表进行设置,这里面信号处理有个特点,就是没有排队的机制,也就是说某个信号被设置之后,如果B进程还没有来及进行响应,那么如果后续第二个同样的信号消息过来,就会被阻塞掉,也就是丢弃。
内核对B进程信号设置完成后,就会发送中断请求给B进程,这样B进程就进入到内核态,这个时候进程B根据那个信号表,查找对应的此信号的处理函数,然后设置frame,设置好之后,跳回到用户态执行信号处理函数,处理完成后,再次返回到内核态,再次设置frame,然后再次返回用户态,从中断位置开始继续执行。
这个frame其实就是在用户态和内核态之间跳转的时候,对堆栈现场的压栈保存。
大致的原理就是这么一个过程,在真正使用的时候,就比较简单了,用kill函数发送信号,在接收进程里,通过signal或者signalaction函数调用sighandler,来启动对应的函数处理信号消息。