http://blog.chinaunix.net/u2/63273/showart_613712.html 今天学习了Linux0.11核中与信号处理有关的部分,东西不多,但花了我很长时间取整明白。 在早期Linux中进程的通信方式就是信号机制。这种机制提供了一种处理异步事件得方法。信号的处理是在系统调用返回前进行的,之前在系统调用部分我已经说过,在系统调用返回之前如果发现进程收到信号,就先调用do_signal。在do_singal中会使系统调用返回后去执行信号处理程序,信号处理程序执行完毕再返回到系统调用前的地方继续执行。下面我就对这个过程展开讨论。总体思路是这样的,先搞清楚信号在Linux中是怎么表示的,再讨论一些关于信号处理的东西,最后明白信号处理过程是怎么样的。 一、信号 在Linux中,信号是用一个32位的整数表示的,它的每一个bit位代表了一种信号,因此最多可以表示32种信号。这个整数对应task_struct中signal,它记录了进程收到的信号;同时对于进程接收到的信号可以对它屏蔽,所以在task_struct中还有一个block的成员,它是此进程的信号屏蔽码。这两个同时决定了进程要处理哪个信号。 二、信号处理 对于信号的处理,系统提供了现成的处理函数,用户也可以定义自己的处理函数。那么系统是怎么知道对应的信号调用哪个处理函数的呢?这是通过task_struct中的sigaction结构体数组和sigaciton函数,sigaction结构体记录这于信号处理过程有关的信息,它有四个成员,信号处理句柄(函数指针)、信号屏蔽码、信号选项标志、信号恢复函数指针。Sigaction函数的功能就是为每个信号设置处理过程,也就是为信号绑定一个sigaction结构体。下面我就展开说一下这两个sigaction: 1、 sigaction结构体 信号处理句柄:既然它是记录于信号处理过程有关的信息的数据结构,那么它就要指明处理的函数,这个是通过信号处理句柄sa_handler指定的。在每个task_struct中有一个sigaction的数组,信号储存这各个信号对应的sigaction结构体,通过信号值减1在这个数组中就可索引到这个信号的sigaction。每个sagaction包含四个成员: 信号屏蔽码:Linux在进行信号处理中,既可以设置成允许接收本身信号,又可以不接受,这个就是通过sigaction中的信号屏蔽码sa_mask实现的,注意这个信号屏蔽码和task_struct中的block不一样,block记录的是此进程要屏蔽的所以信号,而sa_mask记录的是信号处理过程中要屏蔽的信号,在执行处理程序之前sa_mask要添加到block中,在处理结束返回时要恢复block。这样,如果想要在处理过程允许接收到本身信号,就可以将sa_mask设置为0,如果不允许就设置成这个信号,这样在信号处理中就可以屏蔽掉了。 信号选项标志sa_flags:它包含了两个信息,一个就是上面提到的是否允许在信号处理过程中接收信号本身;另一个是,该信号句柄是否只使用一次。因为在早期unix信号处理方式中,信号处理句柄只使用一次,为了兼容这个方式,在信号选项标志中包含了这个信息。 恢复函数指针:在信号处理结束返回时候的清理和恢复工作。后边还要对它说明。 2、 sigaction函数 这个系统调用函数是将某个信号与sigaction结构体进行绑定,也就是说指定这个信号的处理过程。传入的参数有三个:信号值signum、新的sigaction、存放原来sigaction的sigaction结构体oldaction。的大体过程是这样的: 1) 取出信号的原sigaction结构体,放到oldaction中。 2) 为该信号绑定新的sigaction:将新sigaction复制到task_struct的sigation数组的signum-1处。 3) 设置信号屏蔽码:根据sigaction中信号选项标志sa_flags设置sa_mask。 三、信号处理过程 上面提到了信号、信号处理以及它们之间的关联,下面到了本次话题的核心,信号处理的具体过程到底是怎么样的。这个核心就是do_signal()函数。 在系统调用返回之前,系统会通过task_struct中signal和block提取要处理的信号值,然后调用do_signal函数。这个函数的核心作用就是使在系统调用返回后,去执行信号处理函数。那么具体是怎么做的呢?呵呵,很容易想到,linux的老把戏:人工修改栈。就是说,在核心栈中,修改返回地址eip,让它指向信号处理函数。这个过程我是这样看的,它相当于系统调用返回到用户进程时候,不去执行下一条指令,而是不可中断地调用信号处理函,处理完后再返回到系统调用的下一条指令继续执行,只不过在这里是手动进行。这里就会有一个问题,既然是手动调用函数,那么就需要手动的保护寄存器、手动的将函数参数压入堆栈,在函数返回的时候要手动恢复各个寄存器的值。那么这些工作谁做呢?答案是do_signal和sa_restorer。do_signal处理要修改核心栈中程序返回地址之外,还要手动的将要保存的寄存器和信号处理过程参数压入堆栈,注意这里操作的堆栈是用户栈而不是核心栈,因为信号处理过程是在返回到用户进程之后执行的。而sa_restorer的工作就是恢复各个寄存器的值,是返回到系统调用的下一条指令处执行。下面我就展开说一下do_signal的大体工作过程: 1) 根据信号值取出sigaction。 2) 判断如果sa_flags中设置了只用这个函数句柄一次,则将这个信号的sigaction中的函数指针置空,这个主要是针对老unix中的signal()的。 3) 修改核心栈中eip,使其指向信号处理过程 4) 保护寄存器、压入函数参数。这里主要有:old_eip、eflags、edx、ecx、eax、 block、signr、sa_restorer。其中,old_eip是系统调用的下一条指令地址;block是当前进程的屏蔽码;signr是信号值;sa_restorer是信号处理后的清理函数指针,设置它就可以在信号处理后返回的时候去执行sa_restorer。 5)根据sigaction中的sa_mask设置当前进程的block。 上面提到了sa_restorer函数,它的工作过程是这样的:从栈中恢复信号屏蔽码到进程的block中;恢复各个寄存器;这个时候,栈中还有一个 old_eip,这样,在sa_restorer返回的时候,CPU就会将恢复old_eip到eip中,这样就跳到了系统调用的下一条指令了。 四、信号的发送 0.11核中提供了信号发送系统调用sys_kill(),在exit.c中定义并实现的。它实现的是向进程或者进程组发送信号,注意不是杀死进程的意思。呵呵。信号的发送其实就是将目标进程的信号位图中的指定信号位置位。Sys_kill()中有两个参数,pid和sig,pid是目标进程号,这个进程号不一定就单指哪个进程,也可以指定多个进程:系统中除了1号进程(init)所有进程、当前进程组中所有进程等。这个函数要做的就是根据pid扫描整个进程数组,找到符合要求的进程,调用send_sig()函数向此进程发送指定的信号。Send_sig()也在exit.c中定义的,就是将目标进程的信号位图中的指定信号位置位。
Linux进程-信号
最新推荐文章于 2024-11-08 14:34:47 发布