Linux内核信号处理
目录
Linux内核信号处理
在Linux内核中,信号处理是一个复杂的过程,涉及用户态和内核态的交互。以下是信号处理的详细流程,结合代码和注释进行说明。
1. 信号处理的整体流程
信号处理的流程可以分为以下几个步骤:
信号产生 :内核或用户程序发送信号。
信号传递 :内核将信号添加到目标进程的信号队列中。
信号检查 :在进程从内核态返回到用户态时,内核检查是否有未处理的信号。
信号处理 :
- 如果进程注册了信号处理函数,内核会调用该函数。
- 如果没有注册处理函数,内核会执行默认行为(如终止进程、忽略信号等)。
信号处理完成 :进程返回到被信号打断的代码位置继续执行。
2. 信号处理的代码实现
以下是Linux内核中信号处理的核心代码片段,结合注释进行说明。
(1) 信号传递
当内核需要向进程发送信号时,会调用
send_signal()
函数。
// kernel/signal.c
static int send_signal(int sig, struct kernel_siginfo *info, struct task_struct *t, int group)
{
struct sigpending *pending;
struct sigqueue *q;
// 获取目标进程的信号队列
pending = group ? &t->signal->shared_pending : &t->pending;
// 分配一个信号队列项
q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit);
// 将信号添加到队列中
sigaddset(&pending->signal, sig);
list_add_tail(&q->list, &pending->list);
// 唤醒进程(如果进程处于可中断睡眠)
signal_wake_up(t, sig == SIGKILL);
return 0;
}
send_signal()
:将信号添加到目标进程的信号队列中。signal_wake_up()
:如果进程处于可中断睡眠状态,唤醒进程。
(2) 信号检查
在进程从内核态返回到用户态时,内核会调用
do_signal()
函数检查是否有未处理的信号。
// arch/x86/kernel/signal.c
void do_signal(struct pt_regs *regs)
{
struct ksignal ksig;
// 检查是否有未处理的信号
if (get_signal(&ksig)) {
// 如果有信号,处理信号
handle_signal(&ksig, regs);
return;
}
// 如果没有信号,恢复执行
restore_saved_sigmask();
}
get_signal()
:从信号队列中获取一个未处理的信号。handle_signal()
:调用用户注册的信号处理函数。
(3) 信号处理
如果进程注册了信号处理函数,内核会调用
handle_signal()
函数。
// arch/x86/kernel/signal.c
static void handle_signal(struct ksignal *ksig, struct pt_regs *regs)
{
// 设置用户态的信号处理函数
regs->ip = (unsigned long) ksig->ka.sa.sa_handler;
regs->sp = (unsigned long) ksig->ka.sa.sa_restorer;
// 切换到用户态执行信号处理函数
return;
}
regs->ip
:设置指令指针(IP)为信号处理函数的地址。regs->sp
:设置栈指针(SP)为信号处理函数的栈。
(4) 信号处理完成
信号处理函数执行完毕后,进程会返回到被信号打断的代码位置继续执行。
// arch/x86/kernel/signal.c
void sys_rt_sigreturn(void)
{
// 恢复被信号打断的上下文
restore_sigcontext(¤t->thread.regs);
// 返回到用户态继续执行
return;
}
restore_sigcontext()
:恢复被信号打断的寄存器上下文。sys_rt_sigreturn()
:返回到用户态继续执行。
3. 信号处理的场景和影响
(1) 场景1:进程正在运行
场景 :进程正在执行用户态代码,突然收到信号(如
SIGINT
)。流程 :
- 内核将信号添加到进程的信号队列中。
- 进程从内核态返回到用户态时,检查到未处理的信号。
- 内核调用用户注册的信号处理函数。
- 信号处理函数执行完毕后,进程返回到被信号打断的代码位置继续执行。
影响 :进程的执行被信号打断,但会继续执行。
(2) 场景2:进程处于可中断睡眠
场景 :进程正在等待I/O操作(如
read()
),突然收到信号(如SIGTERM
)。流程 :
- 内核将信号添加到进程的信号队列中。
- 内核唤醒进程,并将其状态设置为
TASK_RUNNING
。 - 进程从内核态返回到用户态时,检查到未处理的信号。
- 内核调用用户注册的信号处理函数。
- 信号处理函数执行完毕后,进程返回到被信号打断的代码位置继续执行。
影响 :进程被信号唤醒,I/O操作可能被中断。
(3) 场景3:进程处于不可中断睡眠
场景 :进程正在等待硬件I/O操作(如磁盘读写),突然收到信号(如
SIGKILL
)。流程 :
- 内核将信号添加到进程的信号队列中。
- 由于进程处于不可中断睡眠状态,信号不会唤醒进程。
- 进程继续等待硬件I/O操作完成。
- 硬件I/O操作完成后,进程被唤醒并处理信号。
影响 :信号不会立即生效,进程必须等待硬件操作完成。
4. 总结
信号处理是Linux内核中一个重要的机制,涉及用户态和内核态的交互。通过
send_signal()
、
do_signal()
、
handle_signal()
等函数,内核实现了信号的传递、检查和处理。信号处理的具体行为和影响取决于进程的当前状态和信号类型。