【Linux 0.12】内核代码——信号
内核代码
signal.c 程序
类 UNIX 系统中,信号是软中断处理机制。早期 UNIX 内核中信号处理的方式并不可靠,信号可能被丢失,在运行特定代码时不能屏蔽某个指定信号等等。
内核代码中通常使用一个无符号长整型(32 bit)的数据来表示信号。因此,最多可以表示 32 个不同的信号。在 Linux 0.12 版本中,定义了 22 种信号。其中 20 个信号是 POSIX.1 标准中规定的所有信号,另外两种是 Linux 的专用信号:SIGUNUSED(未定义) 和 SIGSTKFLT(堆栈错)。信号的具体名称和定义可以参阅 include/signal.h 头文件。
对于进程来说,收到一个信号时,可以有三种处理或者操作方式:
- 忽略信号:大多数信号都可以被进程忽略,除了 SIGKILL 和 SIGSTOP。原因是必须给超级用户提供一个确定的方法来终止或停止任何进程。此外,如果忽略了某些硬件异常的信号,则系统将变得不正常。
- 捕获信号:事先告诉内核,在信号发生时调用用户自行编写的信号处理函数。
- 执行默认操作:内核为每一种信号都定义了默认操作。通常情况下默认操作就是终止运行。
三个系统调用函数:
sys_ssetmask()
:设置进程信号阻塞码sys_sgetmask()
:获取进程信号阻塞码sys_signal()
:信号处理系统调用sys_sigaction()
:为指定的信号安装新的信号句柄
signal() 函数和 sigaction() 函数
signal()
和 sigaction()
的功能类似,都用于更改默认的信号处理函数。signal()
在特殊时刻可能会导致信号丢失。(因为 signal() 函数注册的处理函数在被调用一次后会复原。在重新设置之前可能会有新的信号发生,但是没被捕获)
1 | int sigaction(int sig, struct sigaction *act, struct sigaction *oldact); |
- act 不为空时,可以根据传入的 act 修改信号的行为。
- oldact 不为空时,会返回结构体中原来信号的设置信息。
act.sa_mask
标注了一些信号位。这表示,当信号来临,执行 sigaction
注册的处理函数时,会暂时屏蔽 act.sa_mask
标注的信号。
do_signal() 函数
do_signal()
函数是是一个系统调用的中断服务程序。它的功能是将信号的处理句柄插入到用户程序的用户态堆栈中。这样在当前系统调用结束后,就会立刻执行信号句柄程序。(位于 kernel/signal.c)
用户态程序使用系统调用进入内核时,该进程的内核态堆栈上会自动压入上下文(context):
- 用户程序的 ss 和 esp
- 用户态程序的 cs 和 eip(系统调用完成后,下一条要执行的指令)
- 其他信息:用户态的 eflags
当处理完系统调用后,将准备调用 do_signal() 函数处理信号。所以 do_signal() 函数的参数实际上是内核态的栈数据。
处理默认句柄:如果当前信号的 handler 是 SIG_IGN,则直接忽略;如果是 SIG_DFL,则调用 exit 退出。
如果该句柄信号只需要使用一次,则将该句柄置为空(SIG_DFL)
1
2if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;将用户调用系统调用的代码指针 eip 指向信号处理句柄
1
*(&eip) = sa_handler;
这里的 eip 是从用户态切换到内核态时,位于内核态堆栈的自动保存的上下文环境里的 eip,并不是直接修改拥护态的 eip。当从内核态返回拥护态时,内核态堆栈里保存的这些数据会被写入寄存器中。
将内核态保存的原 esp 的值减去 7或8 个 long (28 或 32 字节)的值,并将一些参数压入用户态堆栈。
**目的是:让程序返回用户态之后,去执行信号的处理函数 handler **
(个人理解:用户注册的处理函数 handler 位于用户态,所以在程序返回用户态之后再去执行处理函数。)
压入用户态堆栈的参数包括 handler 的参数,以及旧的 用户态 eip。排布方法参考栈溢出。
关于信号活动的恢复函数 sa_restorer
当前程序的 sigaction 结构体中,有一个函数指针 sa_restorer,主要作用是在信号处理程序结束之后,恢复用户程序执行系统调用后的和一些寄存器函数,并清除作为信号处理程序参数的信号值 signr。
sa_restorer 函数在信号处理程序之后执行,说明这个函数的定义也位于用户态。事实上,它有由函数库提供。在 lib-2.2.2 函数库的 misc/ 目录下有它的定义。
sa_restorer 函数存在的意义是,让程序看起来好像就是直接从系统调用中返回的,而不是中间又插入了对信号的处理。
exit.c 程序
该程序主要描述了:进程(任务)的终止和退出的相关事宜。
似乎没有在 do_exit() 系统调用中找到 atexit 相关的内容。