[TOC]
kernel/signal.c 信号处理(Signal Handling) 进程间异步通信与事件通知 历史与背景 这项技术是为了解决什么特定问题而诞生的? kernel/signal.c
及其相关文件构成了Linux内核的信号处理子系统 。这项技术源自早期的Unix,它的诞生是为了解决进程间一种基础而重要的通信需求:异步事件通知 。
在操作系统中,经常会发生一些需要通知特定进程的、非预期的“事件”。这些事件可能源自:
用户交互 :用户在终端按下Ctrl-C
,需要通知前台进程终止。
内核异常 :一个进程执行了非法操作,如除以零或访问无效内存,内核需要通知该进程它犯了一个错误(SIGFPE
, SIGSEGV
)。
进程间协作 :一个进程需要通知另一个进程某个条件已经满足,或者请求其执行某个操作(例如,父进程通过kill()
命令通知子进程)。
系统管理 :管理员使用kill
命令向一个守护进程发送SIGHUP
信号,请求它重新加载配置文件。
如果没有信号机制,这些场景将难以处理。信号提供了一种轻量级、异步、单向 的通信方式,它模仿了硬件中断:当一个信号被“递送”(deliver)给一个进程时,该进程的正常执行流会被中断 ,转而去执行一个预先注册好的信号处理函数(Signal Handler) ,处理完毕后再返回到原来的执行点。
它的发展经历了哪些重要的里程碑或版本迭代? Linux的信号处理在保持与POSIX标准兼容的同时,也进行了大量的扩展和增强。
不可靠信号(Unreliable Signals) :早期Unix的信号模型比较简单,存在一些问题,如信号处理函数在执行期间如果再次收到同一个信号,可能会导致竞态条件,信号也可能丢失。
POSIX信号(Reliable Signals) :为了解决这些问题,POSIX标准定义了一套更健壮的信号语义。Linux从一开始就遵循了这套标准,引入了sigaction()
系统调用。它允许开发者精确地控制信号处理行为,例如,可以在处理一个信号期间阻塞其他信号,防止处理函数被重入。
实时信号(Real-time Signals) :传统的标准信号(1-31号)是不排队的。如果一个进程在处理某个信号(如SIGUSR1
)时,又收到了10个同样的信号,那么当它处理完第一个后,只会再收到一个SIGUSR1
,其余的都丢失了。为了满足实时应用的需求,Linux引入了实时信号(32号及以后)。它们是可排队的 ,并且可以携带一个整数或指针作为数据 ,这极大地增强了信号的表达能力。
信号描述符(Signalfd) :这是一个重要的现代Linux扩展。signalfd()
系统调用允许一个进程将信号的接收从传统的异步处理函数模式,转换为一个同步的、基于文件描述符的模式 。进程可以像读取一个文件或socket一样,在一个事件循环(如epoll
)中等待并读取信号,这极大地简化了复杂应用程序(尤其是事件驱动的服务器)中的信号处理逻辑。
PID命名空间支持 :在容器化环境中,信号的处理需要正确地考虑PID命名空间,确保信号只能被发送给同一命名空间内的进程。
目前该技术的社区活跃度和主流应用情况如何? 信号是Linux/Unix进程模型中一个不可或ovo割的基础部分 ,其实现极其稳定和成熟。
主流应用 :
Shell作业控制 :Ctrl-C
(SIGINT
), Ctrl-Z
(SIGTSTP
) 等都是通过信号实现的。
进程生命周期管理 :kill
命令发送的SIGTERM
(请求终止)和SIGKILL
(强制杀死)。
程序调试 :调试器使用SIGTRAP
信号来实现断点。
错误报告 :SIGSEGV
(段错误)、SIGILL
(非法指令)等是内核向进程报告严重错误的机制。
服务管理 :systemd
或init
脚本通过SIGHUP
, SIGTERM
来管理守护进程。
核心原理与设计 它的核心工作原理是什么? kernel/signal.c
的核心是管理每个进程的信号状态,并在合适的时机中断其执行以处理信号。
数据结构 :
每个进程的task_struct
中都包含一个struct signal_struct *signal
指针,指向其信号描述符。
struct signal_struct
中包含了信号处理函数表(k_sigaction
)、待处理信号队列等。
每个线程(task_struct
)还有一个pending
信号队列,用于存放发送给该特定线程的信号。
信号的生成与排队(Generating and Queuing) :
当一个进程调用kill()
、tkill()
或内核自身需要发送信号时,会调用send_signal()
等内部函数。
该函数会找到目标进程的signal_struct
,检查信号是否被阻塞,然后将信号添加到一个**待处理信号队列(pending queue)**中。
同时,会在目标进程的TIF_SIGPENDING
(Thread Information Flag)标志位上做一个标记。
信号的递送(Delivering) :
关键检查点 :内核会在几个关键的时机检查当前进程的TIF_SIGPENDING
标志,最主要的是从系统调用返回到用户空间之前 和从中断处理返回到用户空间之前 。
如果TIF_SIGPENDING
被设置,说明有待处理的信号。此时,内核不会立即返回到进程原来的执行点,而是会调用do_signal()
函数。
do_signal()
会从待处理队列中取出一个信号,查找该信号对应的处理方式(忽略、默认动作、或执行用户定义的处理函数)。
执行处理函数 :如果需要执行用户定义的处理函数,内核会在当前进程的用户空间栈上构建一个新的栈帧 ,这个栈帧会使进程的执行流“看起来”像是调用了用户的信号处理函数。然后,内核将指令指针(IP)指向该处理函数的入口,并返回到用户空间。
此时,进程就开始执行信号处理函数了。
从处理函数返回 :
当信号处理函数执行完毕后,它会调用一个特殊的返回指令。内核预先在它构建的栈帧中安排好了,这个返回指令会触发一个sigreturn()
系统调用。
在sigreturn()
中,内核会恢复 进程在被信号中断前的所有寄存器状态 ,然后将指令指针指回原来的执行点,进程就好像什么都没发生过一样继续执行了。
它的主要优势体现在哪些方面?
异步通知 :提供了一种处理异步事件的标准机制。
内核-用户空间交互 :是内核向用户空间进程报告异步硬件/软件异常的核心方式。
轻量级 :发送一个信号的开销远小于其他IPC机制(如管道或套接字)。
它存在哪些已知的劣势、局限性或在特定场景下的不适用性?
信息量有限 :传统信号只能传递一个信号编号,实时信号也只能携带一个整数。不适合传输大量数据。
-竞态条件 :虽然sigaction
解决了大部分问题,但编写正确、安全的信号处理函数仍然非常困难。处理函数中能安全调用的函数(async-signal-safe functions)非常有限。
打断执行流 :信号会打断进程的正常执行,这对于某些需要精确控制执行流程的程序来说,可能会增加复杂性。signalfd
就是为了解决这个问题。
不适合高吞吐量通信 :信号是为低频率的事件通知设计的,不应用作高频率的数据交换通道。
使用场景 在哪些具体的业务或技术场景下,它是首选解决方案?
终止或中断进程 :kill -9 PID
发送SIGKILL
,Ctrl-C
发送SIGINT
。
通知守护进程重载配置 :killall -HUP httpd
发送SIGHUP
。
子进程状态变更通知 :当一个子进程终止、停止或继续时,父进程会收到SIGCHLD
信号。
定时器到期 :setitimer()
或alarm()
系统调用在定时器到期时会向进程发送SIGALRM
信号。
是否有不推荐使用该技术的场景?为什么?
进程间数据交换 :应使用管道(pipes)、共享内存(shared memory)、套接字(sockets)或消息队列(message queues)。
高频率的事件通知 :如果事件频率非常高,信号排队可能会消耗大量资源或达到上限。应考虑使用其他IPC机制。
需要同步的请求-响应 :信号是异步的,不适合需要调用者等待一个明确返回值的场景。
对比分析 请将其 与 其他相似技术 进行详细对比。
特性
信号 (Signals)
管道 (Pipes) / FIFO
套接字 (Sockets)
共享内存 (Shared Memory)
通信模型
异步事件通知 。
字节流 。单向(pipe)或双向(socketpair)。
字节流/数据报 。双向。
直接内存访问 。
数据量
极小 (一个信号编号,可选一个整数)。
任意 (受缓冲区大小限制)。
任意 。
任意 (受分配的内存大小限制)。
开销
低 。
中等 。涉及内核缓冲区和上下文切换。
中等到高 。涉及完整的网络协议栈。
极低 (建立后)。一旦映射,访问就像访问本地变量一样快,无内核介入。
同步性
异步 。打断目标进程的执行。
同步 。read
会阻塞直到有数据。
同步/异步 (可配置)。
无内置同步 。必须配合信号量、互斥锁等外部同步原语使用。
-主要用途
终止进程、报告异常、低频事件通知。
父子进程或无关进程间的流式数据传输。
网络通信或本地进程间的高级通信。
需要最高性能、最低延迟的数据共享场景。
include/linux/sched/signal.h task_sigpending 任务信号待决 1 2 3 4 static inline int task_sigpending (struct task_struct *p) { return unlikely(test_tsk_thread_flag(p,TIF_SIGPENDING)); }
signal_pending 信号待决 1 2 3 4 5 6 7 8 9 10 static inline int signal_pending (struct task_struct *p) { if (unlikely(test_tsk_thread_flag(p, TIF_NOTIFY_SIGNAL))) return 1 ; return task_sigpending(p); }
signal_pending_state 信号待决状态 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 static inline int signal_pending_state (unsigned int state, struct task_struct *p) { if (!(state & (TASK_INTERRUPTIBLE | TASK_WAKEKILL))) return 0 ; if (!signal_pending(p)) return 0 ; return (state & TASK_INTERRUPTIBLE) || __fatal_signal_pending(p); }
signals_init 信号初始化 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 static inline void siginfo_buildtime_checks (void ) { BUILD_BUG_ON(sizeof (struct siginfo) != SI_MAX_SIZE); #define CHECK_OFFSET(field) \ BUILD_BUG_ON(offsetof(siginfo_t, field) != offsetof(kernel_siginfo_t, field)) CHECK_OFFSET(si_pid); CHECK_OFFSET(si_uid); CHECK_OFFSET(si_tid); CHECK_OFFSET(si_overrun); CHECK_OFFSET(si_value); CHECK_OFFSET(si_pid); CHECK_OFFSET(si_uid); CHECK_OFFSET(si_value); CHECK_OFFSET(si_pid); CHECK_OFFSET(si_uid); CHECK_OFFSET(si_status); CHECK_OFFSET(si_utime); CHECK_OFFSET(si_stime); CHECK_OFFSET(si_addr); CHECK_OFFSET(si_trapno); CHECK_OFFSET(si_addr_lsb); CHECK_OFFSET(si_lower); CHECK_OFFSET(si_upper); CHECK_OFFSET(si_pkey); CHECK_OFFSET(si_perf_data); CHECK_OFFSET(si_perf_type); CHECK_OFFSET(si_perf_flags); CHECK_OFFSET(si_band); CHECK_OFFSET(si_fd); CHECK_OFFSET(si_call_addr); CHECK_OFFSET(si_syscall); CHECK_OFFSET(si_arch); #undef CHECK_OFFSET BUILD_BUG_ON(offsetof(struct siginfo, si_pid) != offsetof(struct siginfo, si_addr)); if (sizeof (int ) == sizeof (void __user *)) { BUILD_BUG_ON(sizeof_field(struct siginfo, si_pid) != sizeof (void __user *)); } else { BUILD_BUG_ON((sizeof_field(struct siginfo, si_pid) + sizeof_field(struct siginfo, si_uid)) != sizeof (void __user *)); BUILD_BUG_ON(offsetofend(struct siginfo, si_pid) != offsetof(struct siginfo, si_uid)); } #ifdef CONFIG_COMPAT BUILD_BUG_ON(offsetof(struct compat_siginfo, si_pid) != offsetof(struct compat_siginfo, si_addr)); BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) != sizeof (compat_uptr_t )); BUILD_BUG_ON(sizeof_field(struct compat_siginfo, si_pid) != sizeof_field(struct siginfo, si_pid)); #endif } void __init signals_init (void ) { siginfo_buildtime_checks(); sigqueue_cachep = KMEM_CACHE(sigqueue, SLAB_PANIC | SLAB_ACCOUNT); }
thread_group_empty 线程组是否为空 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 static inline bool thread_group_leader (struct task_struct *p) { return p->exit_signal >= 0 ; } static inline int thread_group_empty (struct task_struct *p) { return thread_group_leader(p) && list_is_last(&p->thread_node, &p->signal->thread_head); }
signal_wake_up 信号唤醒 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 static inline void signal_wake_up (struct task_struct *t, bool fatal) { unsigned int state = 0 ; if (fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN)) { t->jobctl &= ~(JOBCTL_STOPPED | JOBCTL_TRACED); state = TASK_WAKEKILL | __TASK_TRACED; } signal_wake_up_state(t, state); }
arch/arm/include/asm/signal.h 1 2 3 #define _NSIG 64 #define _NSIG_BPW 32 #define _NSIG_WORDS (_NSIG / _NSIG_BPW)
include/linux/signal.h sigemptyset 信号集清空 1 2 3 4 5 6 7 8 9 10 11 12 static inline void sigemptyset (sigset_t *set ) { switch (_NSIG_WORDS) { default : memset (set , 0 , sizeof (sigset_t )); break ; case 2 : set ->sig[1 ] = 0 ; fallthrough; case 1 : set ->sig[0 ] = 0 ; break ; } }
kernel/signal.c recalc_sigpending_tsk 重新计算任务的挂起信号 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 static inline bool has_pending_signals (sigset_t *signal, sigset_t *blocked) { unsigned long ready; long i; switch (_NSIG_WORDS) { default : for (i = _NSIG_WORDS, ready = 0 ; --i >= 0 ;) ready |= signal->sig[i] &~ blocked->sig[i]; break ; case 4 : ready = signal->sig[3 ] &~ blocked->sig[3 ]; ready |= signal->sig[2 ] &~ blocked->sig[2 ]; ready |= signal->sig[1 ] &~ blocked->sig[1 ]; ready |= signal->sig[0 ] &~ blocked->sig[0 ]; break ; case 2 : ready = signal->sig[1 ] &~ blocked->sig[1 ]; ready |= signal->sig[0 ] &~ blocked->sig[0 ]; break ; case 1 : ready = signal->sig[0 ] &~ blocked->sig[0 ]; } return ready != 0 ; } #define PENDING(p,b) has_pending_signals(&(p)->signal, (b)) static bool recalc_sigpending_tsk (struct task_struct *t) { if ((t->jobctl & (JOBCTL_PENDING_MASK | JOBCTL_TRAP_FREEZE)) || PENDING(&t->pending, &t->blocked) || PENDING(&t->signal->shared_pending, &t->blocked) || cgroup_task_frozen(t)) { set_tsk_thread_flag(t, TIF_SIGPENDING); return true ; } return false ; }
recalc_sigpending 重新计算当前线程是否有挂起的信号并清除 1 2 3 4 5 6 7 8 9 10 11 12 13 14 void recalc_sigpending (void ) { if (!recalc_sigpending_tsk(current) && !freezing(current)) { if (unlikely(test_thread_flag(TIF_SIGPENDING))) clear_thread_flag(TIF_SIGPENDING); } } EXPORT_SYMBOL(recalc_sigpending);
calculate_sigpending 计算挂起的信号并清除 1 2 3 4 5 6 7 8 9 10 11 void calculate_sigpending (void ) { spin_lock_irq(¤t->sighand->siglock); set_tsk_thread_flag(current, TIF_SIGPENDING); recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); }
do_work_pending 处理待处理工作 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 asmlinkage int do_work_pending (struct pt_regs *regs, unsigned int thread_flags, int syscall) { trace_hardirqs_off(); do { if (likely(thread_flags & _TIF_NEED_RESCHED)) { schedule(); } else { if (unlikely(!user_mode(regs))) return 0 ; local_irq_enable(); if (thread_flags & (_TIF_SIGPENDING | _TIF_NOTIFY_SIGNAL)) { int restart = do_signal(regs, syscall); if (unlikely(restart)) { return restart; } syscall = 0 ; } else if (thread_flags & _TIF_UPROBE) { uprobe_notify_resume(regs); } else { resume_user_mode_work(regs); } } local_irq_disable(); thread_flags = read_thread_flags(); } while (thread_flags & _TIF_WORK_MASK); return 0 ; }
for_other_threads 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 static inline struct task_struct *__next_thread (struct task_struct *p ){ return list_next_or_null_rcu(&p->signal->thread_head, &p->thread_node, struct task_struct, thread_node); } static inline struct task_struct *next_thread (struct task_struct *p) { return __next_thread(p) ?: p->group_leader; } #define while_each_thread(g, t) \ while ((t = next_thread(t)) != g) #define for_other_threads(p, t) \ for (t = p; (t = next_thread(t)) != p; ) #define __for_each_thread(signal, t) \ list_for_each_entry_rcu(t, &(signal)->thread_head, thread_node, \ lockdep_is_held(&tasklist_lock)) #define for_each_thread(p, t) \ __for_each_thread((p)->signal, t) #define for_each_process_thread(p, t) \ for_each_process(p) for_each_thread(p, t)
retarget_shared_pending
当一个正在退出的线程tsk发现自己身上有待决的、共享的(即发送给整个线程组的)信号时,它必须将这些信号“重新投递(retarget)”给线程组中其他仍然存活的、并且没有阻塞这些信号的兄弟线程
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 static void retarget_shared_pending (struct task_struct *tsk, sigset_t *which) { sigset_t retarget; struct task_struct *t ; sigandsets(&retarget, &tsk->signal->shared_pending.signal, which); if (sigisemptyset(&retarget)) return ; for_other_threads(tsk, t) { if (t->flags & PF_EXITING) continue ; if (!has_pending_signals(&retarget, &t->blocked)) continue ; sigandsets(&retarget, &retarget, &t->blocked); if (!task_sigpending(t)) signal_wake_up(t, 0 ); if (sigisemptyset(&retarget)) break ; } }
exit_signals 退出信号处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 - exit_signals是do_exit函数在早期调用的一个核心辅助函数。它的核心作用是:将当前正在退出的任务tsk,从其线程组的信号处理体系中安全、原子地“除名”,并处理其身上所有待决(pending)的信号。 这个函数是确保一个即将死亡的任务不再接收或影响新的信号,并将其自身的“信号遗产”(待决信号)妥善处理的关键一步。 ```c void exit_signals (struct task_struct *tsk) { int group_stop = 0 ; sigset_t unblocked; if (thread_group_empty(tsk) || (tsk->signal->flags & SIGNAL_GROUP_EXIT)) { tsk->flags |= PF_EXITING; return ; } spin_lock_irq(&tsk->sighand->siglock); tsk->flags |= PF_EXITING; if (!task_sigpending(tsk)) goto out; unblocked = tsk->blocked; signotset(&unblocked); retarget_shared_pending(tsk, &unblocked); if (unlikely(tsk->jobctl & JOBCTL_STOP_PENDING) && task_participate_group_stop(tsk)) group_stop = CLD_STOPPED; out: spin_unlock_irq(&tsk->sighand->siglock); if (unlikely(group_stop)) { read_lock(&tasklist_lock); do_notify_parent_cldstop(tsk, false , group_stop); read_unlock(&tasklist_lock); } }
signal_wake_up_state 信号唤醒状态处理 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 void signal_wake_up_state (struct task_struct *t, unsigned int state) { lockdep_assert_held(&t->sighand->siglock); set_tsk_thread_flag(t, TIF_SIGPENDING); if (!wake_up_state(t, state | TASK_INTERRUPTIBLE)) kick_process(t); }
do_notify_parent 通知父进程 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 bool do_notify_parent (struct task_struct *tsk, int sig) { struct kernel_siginfo info ; unsigned long flags; struct sighand_struct *psig ; bool autoreap = false ; u64 utime, stime; WARN_ON_ONCE(sig == -1 ); WARN_ON_ONCE(task_is_stopped_or_traced(tsk)); WARN_ON_ONCE(!tsk->ptrace && (tsk->group_leader != tsk || !thread_group_empty(tsk))); do_notify_pidfd(tsk); if (sig != SIGCHLD) { if (tsk->parent_exec_id != READ_ONCE(tsk->parent->self_exec_id)) sig = SIGCHLD; } clear_siginfo(&info); info.si_signo = sig; info.si_errno = 0 ; rcu_read_lock(); info.si_pid = task_pid_nr_ns(tsk, task_active_pid_ns(tsk->parent)); info.si_uid = from_kuid_munged(task_cred_xxx(tsk->parent, user_ns), task_uid(tsk)); rcu_read_unlock(); task_cputime(tsk, &utime, &stime); info.si_utime = nsec_to_clock_t (utime + tsk->signal->utime); info.si_stime = nsec_to_clock_t (stime + tsk->signal->stime); info.si_status = tsk->exit_code & 0x7f ; if (tsk->exit_code & 0x80 ) info.si_code = CLD_DUMPED; else if (tsk->exit_code & 0x7f ) info.si_code = CLD_KILLED; else { info.si_code = CLD_EXITED; info.si_status = tsk->exit_code >> 8 ; } psig = tsk->parent->sighand; spin_lock_irqsave(&psig->siglock, flags); if (!tsk->ptrace && sig == SIGCHLD && (psig->action[SIGCHLD-1 ].sa.sa_handler == SIG_IGN || (psig->action[SIGCHLD-1 ].sa.sa_flags & SA_NOCLDWAIT))) { autoreap = true ; if (psig->action[SIGCHLD-1 ].sa.sa_handler == SIG_IGN) sig = 0 ; } if (valid_signal(sig) && sig) __send_signal_locked(sig, &info, tsk->parent, PIDTYPE_TGID, false ); __wake_up_parent(tsk, tsk->parent); spin_unlock_irqrestore(&psig->siglock, flags); return autoreap; }
init_signal_sysctls 初始化信号相关的sysctl参数 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 static const struct ctl_table signal_debug_table [] = {#ifdef CONFIG_SYSCTL_EXCEPTION_TRACE { .procname = "exception-trace" , .data = &show_unhandled_signals, .maxlen = sizeof (int ), .mode = 0644 , .proc_handler = proc_dointvec }, #endif }; static const struct ctl_table signal_table [] = { { .procname = "print-fatal-signals" , .data = &print_fatal_signals, .maxlen = sizeof (int ), .mode = 0644 , .proc_handler = proc_dointvec, }, }; static int __init init_signal_sysctls (void ) { register_sysctl_init("debug" , signal_debug_table); register_sysctl_init("kernel" , signal_table); return 0 ; }
force_sig_info_to_task: 强制向任务发送一个详细信号 此函数的核心作用是绕过目标任务对特定信号的常规处理设置 (例如忽略或阻塞), 强制将一个带有详细信息 (kernel_siginfo
) 的信号发送给该任务。它通过在发送信号前, 修改目标任务的信号处理行为 (sigaction
) 和信号掩码 (blocked
) 来实现其 “强制” 特性。此函数是内核在需要确保一个关键信号 (通常是致命错误) 必须被递送并执行默认操作时使用的最终手段。
此函数处理的 enum sig_handler
提供了不同级别的强制策略:
HANDLER_CURRENT
: 仅在信号被阻塞或忽略时才强制设为默认处理。
HANDLER_SIG_DFL
: 总是无条件地将信号处理方式重置为默认。
HANDLER_EXIT
: 最为严厉, 不仅重置为默认处理, 还将其标记为不可更改, 确保进程必定会因此信号而退出。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 enum sig_handler { HANDLER_CURRENT, HANDLER_SIG_DFL, HANDLER_EXIT, }; static int force_sig_info_to_task (struct kernel_siginfo *info, struct task_struct *t, enum sig_handler handler) { unsigned long int flags; int ret, blocked, ignored; struct k_sigaction *action ; int sig = info->si_signo; spin_lock_irqsave(&t->sighand->siglock, flags); action = &t->sighand->action[sig-1 ]; ignored = action->sa.sa_handler == SIG_IGN; blocked = sigismember(&t->blocked, sig); if (blocked || ignored || (handler != HANDLER_CURRENT)) { action->sa.sa_handler = SIG_DFL; if (handler == HANDLER_EXIT) action->sa.sa_flags |= SA_IMMUTABLE; if (blocked) sigdelset(&t->blocked, sig); } if (action->sa.sa_handler == SIG_DFL && (!t->ptrace || (handler == HANDLER_EXIT))) t->signal->flags &= ~SIGNAL_UNKILLABLE; ret = send_signal_locked(sig, info, t, PIDTYPE_PID); if (!task_sigpending(t)) signal_wake_up(t, 0 ); spin_unlock_irqrestore(&t->sighand->siglock, flags); return ret; }
force_sig_fault_to_task: 向指定任务强制发送一个包含错误信息的信号 此函数的作用是构建一个 siginfo
结构体来封装一个硬件故障或内存访问错误的详细信息, 然后调用核心的信号发送函数, 将这个表示错误的信号强制发送给指定的任务 (task_struct
)。它专门用于处理那些与特定内存地址相关的故障, 如段错误(SIGSEGV)或总线错误(SIGBUS)。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 int force_sig_fault_to_task (int sig, int code, void __user *addr, struct task_struct *t) { struct kernel_siginfo info ; clear_siginfo(&info); info.si_signo = sig; info.si_errno = 0 ; info.si_code = code; info.si_addr = addr; return force_sig_info_to_task(&info, t, HANDLER_CURRENT); }
break_trap 和 ptrace_break: 处理软件断点陷阱的核心函数 这两段代码构成了Linux内核在ARM架构上处理软件断点(Software Breakpoint)的核心逻辑。当CPU因为执行一条断点指令而陷入内核时,break_trap
函数会被内核的异常处理框架调用。它的唯一工作是调用ptrace_break
,而ptrace_break
则负责向触发断点的进程发送一个SIGTRAP
信号。这套机制是所有基于ptrace
的调试工具(如gdb
)能够工作的基石。
对于STM32H750 ARMV7M架构,这套机制的工作原理完全相同且至关重要。当你在使用J-Link或ST-Link通过gdb
调试一个运行在STM32上的Linux用户空间程序时:
你在gdb
中设置一个断点。gdb
会通过ptrace
系统调用,将目标地址的原始指令替换为一条BKPT
(断点)指令。
当你的程序执行到这个地址时,STM32H750的Cortex-M7核心会触发一个“调试监控”(DebugMonitor)或“未定义指令”(Undefined Instruction)异常,进入内核态。
内核的异常处理流程通过之前注册的钩子,最终调用break_trap(regs, instr)
。
break_trap
调用ptrace_break(regs)
。
ptrace_break
向你的用户空间程序发送SIGTRAP
信号。
这个信号会被内核的信号处理机制拦截,因为你的程序正处于被ptrace
跟踪的状态。内核会暂停你的程序,并通知调试器(gdb
)。
gdb
接收到通知后,会恢复被断点指令替换的原始指令,向你显示当前程序的状态(寄存器值、内存等),并等待你的下一步命令。
这个流程使得在嵌入式Linux系统上进行应用程序级的源码调试成为可能。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 void ptrace_break (struct pt_regs *regs) { force_sig_fault(SIGTRAP, TRAP_BRKPT, (void __user *)instruction_pointer(regs)); } static int break_trap (struct pt_regs *regs, unsigned int instr) { ptrace_break(regs); return 0 ; }
ptrace_break_init: 注册ARM软件断点陷阱 此代码段的核心作用是在Linux内核启动的早期,为ARM架构设置软件断点(Software Breakpoint)的处理机制。它通过“钩子”(hook)的形式,挂接到内核的“未定义指令”(Undefined Instruction)异常处理流程中。当一个程序(通常在调试器gdb
的控制下)执行一条特定的断点指令时,CPU会触发一个异常。这段代码确保内核能捕获这个异常,并调用相应的处理函数(break_trap
),而不是让系统崩溃。这套机制是ptrace
系统调用和调试器功能的基础。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 static struct undef_hook arm_break_hook = { .instr_mask = 0x0fffffff , .instr_val = 0x07f001f0 , .cpsr_mask = PSR_T_BIT, .cpsr_val = 0 , .fn = break_trap, }; static struct undef_hook thumb_break_hook = { .instr_mask = 0xffffffff , .instr_val = 0x0000de01 , .cpsr_mask = PSR_T_BIT, .cpsr_val = PSR_T_BIT, .fn = break_trap, }; static struct undef_hook thumb2_break_hook = { .instr_mask = 0xffffffff , .instr_val = 0xf7f0a000 , .cpsr_mask = PSR_T_BIT, .cpsr_val = PSR_T_BIT, .fn = break_trap, }; static int __init ptrace_break_init (void ) { register_undef_hook(&arm_break_hook); register_undef_hook(&thumb_break_hook); register_undef_hook(&thumb2_break_hook); return 0 ; } core_initcall(ptrace_break_init);