Linux102系列会详细讲解Linux0.11版本中的102个文件,本文讲解linux0.11的第17个文件
kernel/signal.c的文件源码。
1.kernel/signal.c的主要作用
推荐前置阅读:😉【Linux102】18-include/signal.h
这段代码是处理信号机制的核心实现,负责信号的设置、获取和处理。
这段代码实现了 Linux 内核早期的信号机制核心功能,包括:
| 1.进程的信号掩码(阻塞码)的获取与设置函数 | sys_sgetmask() sys_ssetmask() |
| 2.进程的信号处理函数的注册函数 | sys_sigaction() |
| 3.修改进程在收到特定信号时所采取的具体的行动的系统调用函数 | sys_sigaction() |
| 4.进程的信号实际处理逻辑,包括现场保存与恢复、跳转至用户态处理函数等 | do_signal() |
注意,有关信号操作的发送信号函数 send_sig()和通知父进程函数 tell_father()被包另一个程序 exit.c中。
程序中名称前缀 sig均是信号 signal 的简称。
2.signal()和 sigaction()的区别
signal()和 sigaction()的功能比较类似,都是改变信号原处理句柄(handler,或称为程序)。但:
| signal()会返回原信号处理句柄,并且在新句柄被调用一次后句柄就会恢复到器值。 |
| sigaction()则可以进行更自由的设置。 |
3.信号和进程的关系
注意前缀——“进程的”,说明进程和信号息息相关,具体如下:
在操作系统内核中,进程是信号的载体与处理主体,信号则是内核或进程间向进程传递异步事件通知的机制,二者通过内核的信号管理模块紧密关联,二者的互动是内核实现进程控制(如终止、暂停)和异步事件通知的核心机制。
4.本源码涉及的文件

| 😉【Linux102】18-include/signal.h | 本文件定义了信号处理相关数据结构,是理解Linux内核的信号机制的必知前提。 |
| 😉【Linux102】15-include/linux/sched.h | 本程序主要定义了进程调度的相关数据结构,如任务数据结构等等,理解这些数据结构是理解进程调度机制的关键,也是理解内核的关键。 一句话:我们常说的进程是XXX,但是进程究竟是什么?就定义在这个文件里面!源码面前,没有秘密。 |
| 😉【【Linux102】20-include/linux/kernel.h | 本程序主要定义了内核中最常用的一些基础函数声明,是内核运行最基本保障,比如malloc、printk、free、tyy_write、panic等函数声明,这些函数具体的实现则分布在各个.c文件中。 |
| 😉【Linux102】21-include/asm/segment.h | 本程序定义了一系列用于在 Linux 内核中访问用户空间内存的内联函数,主要通过内嵌汇编操作段寄存器fs来实现内核与用户空间的数据交互。 |
5.do_signal()函数详解
do_signal()函数是内核系统调用(int 0x80)中断处理程序中信号的预处理程序。该函数的主要作用是将信号的处理句柄插入到用户程序堆栈中。这样,在当前系统调用结束返回后就会立刻执行信号句柄程序,然后再继续执行用户的程序。

6.sys_signal()函数详解
sys_signal()函数原型
int sys_signal(int signum, long handler, long restorer);
sys_signal()函数的参数restorer是一函数指针。该函数由Libc函数库提供用于恢复系统调用后的返回值和一些寄存器。在编译链接程序时由Libc函数库提供,用于在信号处理程序结束后清理用户态堆栈,并恢复系统调用存放在eax中的返回值。
7.如何处理信号?
进程中的信号是用于进程与内核、进程之间通信的一种简单消息,通常是下表中的一个标号数值,并且不携带任何其他的信息。 例如当一个子进程终止或结束时,就会产生一个标号为17的 SIGCHLD信号发送给父进程,以通知父进程有关子进程的当前状态。
关于一个进程如何处理收到的信号,一般有两种做法:
| 进程如何处理收到的信号? |
|---|
| 一是程序的进程不去处理,此时该信号会由系统相应的默认信号处理程序进行处理; |
| 第二种做法是进程使用自己的信号处理程序来处理信号。 |
下面我们再逐行解释源码:
8.源码注释版本
#include <linux/sched.h> // 包含进程调度相关定义(如current进程结构体)
#include <linux/kernel.h> // 包含内核基本函数和宏定义
#include <asm/segment.h> // 包含段操作函数(如put_fs_byte、get_fs_byte等)
#include <signal.h> // 包含信号相关常量和结构体定义(如sigaction、SIGKILL等)
void do_exit(int error_code); // 声明进程退出函数(在exit.c中实现)
// 作用:系统调用,获取当前进程的信号掩码(被阻塞的信号集合)。
// current:指向当前运行进程的结构体指针(在 sched.h 中定义)。
// blocked:进程结构体中记录被阻塞信号的字段(位掩码,每一位代表一个信号是否被阻塞)。
int sys_sgetmask()
{
return current->blocked;
}
// 作用:系统调用,设置当前进程的信号掩码。
// 关键逻辑:SIGKILL(终止信号)不能被阻塞,
// 因此通过& ~(1 << (SIGKILL - 1))强制清除新掩码中 SIGKILL 的阻塞位。
int sys_ssetmask(int newmask)
{
int old = current->blocked; // 保存旧的信号掩码
// 设置新的信号掩码,但强制不能阻塞SIGKILL(1 << (SIGKILL - 1)是SIGKILL的位掩码)
current->blocked = newmask & ~(1 << (SIGKILL - 1));
return old; // 返回旧的信号掩码
}
/**
* func descp: 保存旧的信号处理结构(save_old)
*/
// 静态内联函数:将内核空间的信号处理结构(struct sigaction)复制到用户空间。
// verify_area:检查用户空间地址的合法性(内核安全机制)。
// put_fs_byte:内核向用户空间写入数据的函数(因内核和用户空间地址空间分离,需特殊处理)。
static inline void save_old(char *from, char *to)
{
int i;
// 验证用户空间地址to的内存是否可写(避免内核访问非法用户地址)
verify_area(to, sizeof(struct sigaction));
// 逐字节将内核空间的from复制到用户空间的to(信号处理结构)
for (i = 0; i < sizeof(struct sigaction); i++)
{
put_fs_byte(*from, to); // 将内核数据写入用户空间
from++;
to++;
}
}
/**
* func descp: 获取新的信号处理结构(get_new)
*/
// 静态内联函数:将用户空间定义的信号处理结构复制到内核空间。
// get_fs_byte:从用户空间读取数据到内核的函数(与put_fs_byte对应)。
static inline void get_new(char *from, char *to)
{
int i;
// 逐字节将用户空间的from复制到内核空间的to(信号处理结构)
for (i = 0; i < sizeof(struct sigaction); i++)
*(to++) = get_fs_byte(from++); // 从用户空间读取数据到内核
}
/**
* func descp: 注册信号处理函数(sys_signal)
*/
// 系统调用:注册信号处理函数(早期的signal()系统调用实现)。
// 关键标志:
// SA_ONESHOT:信号处理函数执行一次后自动失效(需重新注册)。
// SA_NOMASK:信号处理期间不阻塞自身(允许嵌套触发同一信号)。
int sys_signal(int signum, long handler, long restorer)
{
struct sigaction tmp; // 临时信号处理结构
// 检查信号编号合法性:1~32之间,且不能是SIGKILL(SIGKILL不能被捕获/修改)
if (signum < 1 || signum > 32 || signum == SIGKILL)
return -1;
// 初始化临时信号处理结构
tmp.sa_handler = (void (*)(int))handler; // 信号处理函数(用户提供)
tmp.sa_mask = 0; // 信号处理期间不阻塞其他信号
tmp.sa_flags = SA_ONESHOT | SA_NOMASK; // 标志:一次有效+不自动阻塞自身
tmp.sa_restorer = (void (*)(void))restorer; // 信号处理完成后恢复现场的函数
// 保存旧的信号处理函数(用于返回给用户)
handler = (long)current->sigaction[signum - 1].sa_handler;
// 更新当前进程的信号处理结构
current->sigaction[signum - 1] = tmp;
return handler; // 返回旧的信号处理函数地址
}
/**
* func descp: 高级信号处理设置(sys_sigaction)
*/
// 系统调用:更灵活的信号处理设置(对应标准的sigaction()系统调用)。
// 功能:允许用户自定义信号处理函数、掩码和标志,比sys_signal更强大。
// 关键逻辑:自动处理信号掩码(若未设置SA_NOMASK,则信号处理期间自动阻塞自身)。
int sys_sigaction(int signum, const struct sigaction *action,
struct sigaction *oldaction)
{
struct sigaction tmp; // 临时保存旧的信号处理结构
// 检查信号编号合法性(同sys_signal)
if (signum < 1 || signum > 32 || signum == SIGKILL)
return -1;
// 保存当前信号的旧处理结构
tmp = current->sigaction[signum - 1];
// 将用户提供的新处理结构从用户空间复制到内核(更新当前进程的信号处理)
get_new((char *)action, (char *)(signum - 1 + current->sigaction));
// 如果需要返回旧处理结构,将其从内核复制到用户空间
if (oldaction)
save_old((char *)&tmp, (char *)oldaction);
// 根据标志更新信号掩码:
// 如果设置了SA_NOMASK,则不阻塞任何信号;否则自动阻塞当前信号
if (current->sigaction[signum - 1].sa_flags & SA_NOMASK)
current->sigaction[signum - 1].sa_mask = 0;
else
current->sigaction[signum - 1].sa_mask |= (1 << (signum - 1));
return 0; // 成功
}
/**
* func descp: 信号处理核心逻辑(do_signal)
*/
// 核心函数:当内核检测到进程有未处理的信号时,调用此函数处理。
// 执行流程:
// 检查信号处理函数:忽略(1)、默认(NULL)或用户自定义。
// 若为一次性处理(SA_ONESHOT),清空处理函数。
// 修改指令指针(eip),让进程从用户态的信号处理函数开始执行。
// 保存现场:将寄存器、原指令地址等压入用户栈,供处理完成后恢复。
// 阻塞sa_mask中指定的信号,避免处理期间被干扰。
void do_signal(long signr, long eax, long ebx, long ecx, long edx,
long fs, long es, long ds,
long eip, long cs, long eflags,
unsigned long *esp, long ss)
{
unsigned long sa_handler; // 信号处理函数地址
long old_eip = eip; // 保存原指令地址(信号处理完成后返回)
// 获取当前信号对应的处理结构
struct sigaction *sa = current->sigaction + signr - 1;
int longs; // 栈上需要保存的长整型数据个数
unsigned long *tmp_esp; // 临时栈指针
sa_handler = (unsigned long)sa->sa_handler; // 获取处理函数地址
// 处理函数为1:忽略信号(SIG_IGN)
if (sa_handler == 1)
return;
// 处理函数为NULL:使用默认处理
if (!sa_handler)
{
if (signr == SIGCHLD) // SIGCHLD(子进程状态变化)默认忽略
return;
else // 其他信号默认处理:进程退出
do_exit(1 << (signr - 1));
}
// 如果是一次性处理(SA_ONESHOT),执行后清空处理函数(下次用默认行为)
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
// 修改指令指针:跳转到信号处理函数(用户态执行)
*(&eip) = sa_handler;
// 计算需要保存到栈的参数个数:SA_NOMASK则7个,否则8个(多保存一个信号掩码)
longs = (sa->sa_flags & SA_NOMASK) ? 7 : 8;
// 调整用户栈指针:为保存参数预留空间
*(&esp) -= longs;
verify_area(esp, longs * 4); // 验证栈空间合法性
tmp_esp = esp; // 临时栈指针
// 将以下数据依次压入用户栈(供信号处理函数和恢复现场使用)
put_fs_long((long)sa->sa_restorer, tmp_esp++); // 恢复现场函数地址
put_fs_long(signr, tmp_esp++); // 信号编号
if (!(sa->sa_flags & SA_NOMASK))
put_fs_long(current->blocked, tmp_esp++); // 信号掩码(若需要)
put_fs_long(eax, tmp_esp++); // 寄存器eax
put_fs_long(ecx, tmp_esp++); // 寄存器ecx
put_fs_long(edx, tmp_esp++); // 寄存器edx
put_fs_long(eflags, tmp_esp++); // 标志寄存器
put_fs_long(old_eip, tmp_esp++); // 原指令地址(处理完成后返回)
// 信号处理期间阻塞sa_mask中指定的信号
current->blocked |= sa->sa_mask;
}
9.源码完整版
/*
* linux/kernel/signal.c
*
* (C) 1991 Linus Torvalds
*/
#include <linux/sched.h>
#include <linux/kernel.h>
#include <asm/segment.h>
#include <signal.h>
void do_exit(int error_code);
int sys_sgetmask()
{
return current->blocked;
}
int sys_ssetmask(int newmask)
{
int old = current->blocked;
current->blocked = newmask & ~(1 << (SIGKILL - 1));
return old;
}
static inline void save_old(char *from, char *to)
{
int i;
verify_area(to, sizeof(struct sigaction));
for (i = 0; i < sizeof(struct sigaction); i++)
{
put_fs_byte(*from, to);
from++;
to++;
}
}
static inline void get_new(char *from, char *to)
{
int i;
for (i = 0; i < sizeof(struct sigaction); i++)
*(to++) = get_fs_byte(from++);
}
int sys_signal(int signum, long handler, long restorer)
{
struct sigaction tmp;
if (signum < 1 || signum > 32 || signum == SIGKILL)
return -1;
tmp.sa_handler = (void (*)(int))handler;
tmp.sa_mask = 0;
tmp.sa_flags = SA_ONESHOT | SA_NOMASK;
tmp.sa_restorer = (void (*)(void))restorer;
handler = (long)current->sigaction[signum - 1].sa_handler;
current->sigaction[signum - 1] = tmp;
return handler;
}
int sys_sigaction(int signum, const struct sigaction *action,
struct sigaction *oldaction)
{
struct sigaction tmp;
if (signum < 1 || signum > 32 || signum == SIGKILL)
return -1;
tmp = current->sigaction[signum - 1];
get_new((char *)action,
(char *)(signum - 1 + current->sigaction));
if (oldaction)
save_old((char *)&tmp, (char *)oldaction);
if (current->sigaction[signum - 1].sa_flags & SA_NOMASK)
current->sigaction[signum - 1].sa_mask = 0;
else
current->sigaction[signum - 1].sa_mask |= (1 << (signum - 1));
return 0;
}
void do_signal(long signr, long eax, long ebx, long ecx, long edx,
long fs, long es, long ds,
long eip, long cs, long eflags,
unsigned long *esp, long ss)
{
unsigned long sa_handler;
long old_eip = eip;
struct sigaction *sa = current->sigaction + signr - 1;
int longs;
unsigned long *tmp_esp;
sa_handler = (unsigned long)sa->sa_handler;
if (sa_handler == 1)
return;
if (!sa_handler)
{
if (signr == SIGCHLD)
return;
else
do_exit(1 << (signr - 1));
}
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
*(&eip) = sa_handler;
longs = (sa->sa_flags & SA_NOMASK) ? 7 : 8;
*(&esp) -= longs;
verify_area(esp, longs * 4);
tmp_esp = esp;
put_fs_long((long)sa->sa_restorer, tmp_esp++);
put_fs_long(signr, tmp_esp++);
if (!(sa->sa_flags & SA_NOMASK))
put_fs_long(current->blocked, tmp_esp++);
put_fs_long(eax, tmp_esp++);
put_fs_long(ecx, tmp_esp++);
put_fs_long(edx, tmp_esp++);
put_fs_long(eflags, tmp_esp++);
put_fs_long(old_eip, tmp_esp++);
current->blocked |= sa->sa_mask;
}
10.源码图像版

11.源码注释版图像

12.进程信号表
Linux系统所支持的信号见表:
| 标号 | 名 称 | 说 明 | 默认操作 |
|---|---|---|---|
| 1 | SIGHUP | (Hangup)当你不再控制终端时,或者当你关闭 Xterm 或断开 modem 连接时内核会产生该信号。后台程序常用其重新读取配置文件 | (Abort)挂断控制终端或进程 |
| 2 | SIGINT | (Interrupt)来自键盘的终端,通常终端驱动程序会将其与 Ctrl + C 绑定 | (Abort)程序被终止 |
| 3 | SIGQUIT | (Quit)来自键盘的终端,通常终端驱动程序会将其与 Ctrl + \ 绑定 | (Dump)程序被终止并产生 dump core 文件 |
| 4 | SIGILL | (Illegal Instruction)程序出错或者执行了非法操作指令 | (Dump)程序被终止并产生 dump core 文件 |
| 5 | SIGTRAP | (Breakpoint/Trace Trap)调试用,跟踪断点 | (Dump)程序被终止并产生 dump core 文件 |
| 6 | SIGABRT | (Abort)放弃执行,异常结束 | (Dump)程序被终止并产生 dump core 文件 |
| 6 | SIGIOT | (IO Trap)同 SIGABRT | (Dump)程序被终止并产生 dump core 文件 |
| 7 | SIGUNUSED | (Unused)没有使用 | - |
| 8 | SIGFPE | (Floating Point Exception)浮点异常 | (Dump)程序被终止并产生 dump core 文件 |
| 9 | SIGKILL | (Kill)程序被终止,该信号不能被捕获或忽略,想立刻终止进程就发此信号,程序无清理机会 | (Abort)程序被终止 |
| 10 | SIGUSR1 | (User Defined Signal 1)用户定义的信号 | (Abort)进程被终止 |
| 11 | SIGSEGV | (Segmentation Violation)当程序引用无效内存时产生,比如访问没有映射的内存、写只读内存等 | (Dump)程序被终止并产生 dump core 文件 |
| 12 | SIGUSR2 | (User Defined Signal 2)保留给用户程序用于 IPC 或其他目的 | (Abort)进程被终止 |
| 13 | SIGPIPE | (Pipe)当程序向一个套接字或管道写时,由于没有读者而产生该信号 | (Abort)进程被终止 |
| 14 | SIGALRM | (Alarm)该信号会在用户调用 alarm 系统调用所设置的延迟秒数到后产生,常用于到期提醒 | (Abort)进程被终止 |
| 15 | SIGTERM | (Terminate)用于和善地要求一个程序终止,是 kill 的默认信号,与 SIGKILL 不同,该信号能被捕获做退出前清理 | (Abort)进程被终止 |
| 16 | SIGSTKFLT | (Stack Fault on Coprocessor)协处理器堆栈错误 | (Abort)进程被终止 |
| 17 | SIGCHLD | (Child)父进程发出,停止或终止子进程,可改变含义挪作他用 | (Ignore)子进程停止或结束 |
| 18 | SIGCONT | (Continue)该信号致使被 SIGSTOP 停止的进程恢复运行,可被捕获 | (Continue)恢复进程的执行 |
| 19 | SIGSTOP | (Stop)停止进程的运行,该信号不可被捕获或忽略 | (Stop)停止进程运行 |
| 20 | SIGTSTP | (Terminal Stop)向终端发送停止进程,该信号可以被捕获或忽略 | (Stop)停止进程运行 |
| 21 | SIGTTIN | (TTY Input on Background)后台程序试图从一个不再被控制的终端上读取数据,此时进程将被停止,直到收到 SIGCONT | (Stop)停止进程运行 |
| 22 | SIGTTOU | (TTY Output on Background)后台进程试图向一个不再被控制的终端上输出数据,此时进程将被停止,直到收到 SIGCONT | (Stop)停止进程运行 |

本系列将直击C语言的本质基础,流利处理出各个易错、实用的实战点,并非从零开始学习C。
专注讲解Linux中的常用命令,共计发布100+文章。
本系列将精讲Linux0.11内核中的每一个文件,共计会发布100+文章。
😉【Linux102】11-kernel/vsprintf.c
😉【Linux102】12-include/stdarg.h
😉【Linux102】14-kernel/system_call.s
😉【Linux102】15-include/linux/sched.h
😉【Linux102】18-include/signal.h
😉【Linux102】19-include/sys/types.h
😉【Linux102】20-include/linux/kernel.h
😉【Linux102】21-include/asm/segment.h
😉【Linux102】22-include/linux/head.h
😉【Linux102】23-include/linux/mm.h
😉【Linux102】24-include/linux/fs.h
😉【Linux102】26-include/sys/wait.h
😉【Linux102】27-include/inux/tty.h
😉【Linux102】28-include/termios.h
😉【Linux102】30-include/sys/times.h
😉【Linux102】31-include/sys/utsname.h
😉【Linux102】32-include/stddef.h
和Linux内核102系列不同,本系列将会从全局描绘Linux内核的各个模块,而非逐行源码分析,适合想对Linux系统有宏观了解的家人阅读。
😉【Linux】Linux概述1-linux对物理内存的使用
关于小希
😉嘿嘿嘿,我是小希,专注Linux内核领域,同时讲解C语言、汇编等知识。
我的微信:C_Linux_Cloud,期待与您学习交流!

加微信请备注哦
小希的座右铭:
别看简单,简单也是难。别看难,难也是简单。我的文章都是讲述简单的知识,如果你喜欢这种风格:
下一期想看什么?在评论区留言吧!我们下期见!


被折叠的 条评论
为什么被折叠?



