当前位置: 首页 > ai >正文

Linux signal 图文详解(二)信号发送

目录

一、信号发送简介

二、实现原理

三、代码实现


(代码:linux 6.3.1,架构:arm64)

One look is worth a thousand words.  —— Tess Flanders

一、信号发送简介

        Linux中,信号(signal)是进程间通信的一种重要机制,用于通知进程、线程、进程组发生了某种事件。用户态程序可以通过多种方式向其他进程发送信号,以下是常见的几种方法:

1)kill 系统调用

        kill 可以向指定的进程、进程组发送特定信号,函数原型如下:

#include <signal.h>/** - pid:目标进程 ID,特殊值含义:*     pid > 0:发送信号给 ID 为 pid 的进程*     pid = 0:发送信号给与当前进程同组的所有进程*     pid = -1:发送信号给所有有权限发送的进程(除了 init 进程)*     pid < -1:发送信号给进程组 ID 为 |pid| 的所有进程* - sig:要发送的信号编号(如 SIGTERM、SIGKILL 等)*/
int kill(pid_t pid, int sig);

2)raise() 函数

        raise()函数用于向当前进程发送信号,是kill(getpid(), sig)的简单封装,函数原型如下:

#include <signal.h>int raise(int sig);

3)pthread_kill() 函数

        多线程环境下,pthread_kill() 用于向进程内指定的线程发送信号,函数原型如下:

#include <signal.h>
#include <pthread.h>int pthread_kill(pthread_t thread, int sig);

4)alarm() 函数

        alarm() 用于在指定秒数后,向当前进程发送SIGALRM信号,函数原型如下:

#include <unistd.h>unsigned int alarm(unsigned int seconds);

        由此可见,信号的发送对象既可以是一个线程、也可以是一个进程、进程组,对于发送给指定线程的信号,信号的处理就是指定线程负责完成;

        而针对发送给进程的信号,最终处理该信号的可能是进程中任意一个有能力处理的线程,具体由谁处理,是由内核发送信号的时候,就已经决定好了的!

二、实现原理

        上一节介绍的各种信号发送的用户态接口,这些接口在内核中,最终对应的 “信号发送” 函数实际上都是同一个:send_signal_locked

        以下就是信号发送最核心代码的完整逻辑:

        信号发送的处理流程主要分两块内容:

        1)检查信号的类型、信号是否被目标task屏蔽、信号是否为legacy信号(主要涉及 步骤1 ~ 步骤6);

        2)将信号设置到对应的私有、共享信号pending队列中,并给选取到的task置位TIF_SIGPENDING代表有信号需要处理,然后唤醒该task(若该任务处于挂起状态)去处理信号(主要涉及 步骤7 ~ 步骤10)。

        这里需要注意的是:信号的唯一处理时机是内核态返回用户态前夕,task的挂起只可能是在内核态,因此这里唤醒任务后,任务也是从内核态挂起的地方继续运行,直到其内核态处理完毕,并走到返回用户态前夕时,就会去处理pending的信号了!

        一些特殊的信号处理:

        1)SIGCONT、SIGSTOP类型信号

                对于这类信号的处理,是在信号发送时,就直接在内核态中进行处理了(具体见步骤2、步骤3)

        2)Fatal信号

                对于fatal类型的信号,是需要整个进程下的所有线程都去立刻处理该信号的,因此会将进程中的所有线程都置位TIF_SIGPENDING、并设置相应信号pending位,然后唤醒所有线程去立刻处理Fatal信号。

三、代码实现

1)所有类型的信号发送在内核中的实现

/* 发送信号给进程、进程组 */
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)kill_something_info(sig, &info, pid)if (pid > 0)/* 发送给进程 */kill_proc_infokill_pid_infogroup_send_sig_infodo_send_sig_infosend_signal_lockedif (pid != -1) {/* 发送给进程组 */__kill_pgrp_infodo_each_pid_task(pgrp, PIDTYPE_PGID, p) {group_send_sig_infodo_send_sig_infosend_signal_locked}} else {/* 发送给所有进程 */for_each_process(p) {group_send_sig_info(sig, info, p,PIDTYPE_MAX)do_send_sig_infosend_signal_locked}}/* 发送信号给指定线程 */
SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig)do_tkilldo_send_specificdo_send_sig_infosend_signal_locked

2)send_signal_locked 函数实现

send_signal_locked(int sig, struct kernel_siginfo *info, struct task_struct *t, enum pid_type type) {bool force = false/* Check whether signal must be sent ? */...return __send_signal_locked(sig, info, t, type, force) {struct sigpending *pendingstruct sigqueue *q/* 1) 预处理信号, 若是STOP/CONTINUE类型的信号则先做一些处理, 若目标进程被置位SIGNAL_GROUP_EXIT则无需发送信号, *    最后检查信号是否被目标进程ignore** 处理影响进程级别的 stop/continue信号,与其他信号的action不同,如果是这两个信号的话,对应的sigaction会立即执行,* 并且不论blocking, ignoring, handling.* This does the actual continuing for SIGCONT, but not the actual stopping for stop signals.* The process stop is done as a signal action for SIG_DFL.*/res = prepare_signal(sig, struct task_struct *p = t, force) {struct signal_struct *signal = p->signal/* 1.1) 若目标进程的signal->flags被置位SIGNAL_GROUP_EXIT, 说明其需要走退出流程, 无需再给它发送信号了, 信号发送提前结束 */if (signal->flags & SIGNAL_GROUP_EXIT) {if (signal->core_state)return sig == SIGKILLreturn false} else if (sig_kernel_stop(sig)) {/* 1.2) 若是STOP类型的信号, 则清空所有pending位图(进程共享/线程私有)中的SIGCONT信号位 */siginitset(&flush, sigmask(SIGCONT))flush_sigqueue_mask(&flush, &signal->shared_pending)for_each_thread(p, t)flush_sigqueue_mask(&flush, &t->pending)} else if (sig == SIGCONT) {/* 1.3) 若是CONTINUE类型的信号, 则清空所有pending位图中的STOP类型信号位, 并唤醒所有线程 */siginitset(&flush, SIG_KERNEL_STOP_MASK)flush_sigqueue_mask(&flush, &signal->shared_pending)for_each_thread(p, t) {flush_sigqueue_mask(&flush, &t->pending)task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING)if (likely(!(t->ptrace & PT_SEIZED))) {t->jobctl &= ~JOBCTL_STOPPEDwake_up_state(t, __TASK_STOPPED)} elseptrace_trap_notify(t)}}/* 1.4) 检查发送的信号是否被目标进程忽略(如: 其handler为SIG_IGN等) */is_ignored = sig_ignored(p, sig, force) {if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))return falseif (t->ptrace && sig != SIGKILL)return falsereturn sig_task_ignored(t, sig, force) {void __user *handler = sig_handler(t, sig)return t->sighand->action[sig - 1].sa.sa_handlerreturn sig_handler_ignored(handler, sig) {/* Is it explicitly or implicitly ignored? */return handler == SIG_IGN || (handler == SIG_DFL && sig_kernel_ignore(sig))}//sig_handler_ignored}//sig_task_ignored}//sig_ignoredreturn !is_ignored}//prepare_signalif(!res)goto ret/* 2) 根据信号发送类型, 判断信号是发送给进程还是线程的, 获取对应的pending信号队列 */pending = (type != PIDTYPE_PID) ? &t->signal->shared_pending : &t->pending/* 3) 若是legacy类型信号, 且信号已经被设置到pending队列中, 则提前结束信号发送 */is_iegacy = legacy_queue(pending, sig) {return (sig < SIGRTMIN) && sigismember(&signals->signal, sig)   // SIGRTMIN: 32}//legacy_queueif (is_iegacy)goto ret/* 4) 若是给内核线程发送一个SIGKILL信号, 则跳过siginfo allocation过程 */if ((sig == SIGKILL) || (t->flags & PF_KTHREAD))goto out_set/* 5) 若当前没有超过实时信号的MAX, 则分配一个sigqueue对象 */q = __sigqueue_alloc(sig, t, GFP_ATOMIC, override_rlimit, 0) {struct sigqueue *q = NULLrcu_read_lock()struct ucounts *ucounts = task_ucounts(t)sigpending = inc_rlimit_get_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING)rcu_read_unlock()if (!sigpending)return NULLif (override_rlimit || likely(sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) {q = kmem_cache_alloc(sigqueue_cachep, gfp_flags)}return q}//__sigqueue_allocif (q) {/* 6) 将sigqueue对象添加到对应的pending队列中, 并设置sigqueue->info字段内容 */list_add_tail(&q->list, &pending->list)switch ((unsigned long) info) {case (unsigned long) SEND_SIG_NOINFO:clear_siginfo(&q->info)q->info.si_signo = sigq->info.si_errno = 0q->info.si_code = SI_USERq->info.si_pid = task_tgid_nr_ns(current, task_active_pid_ns(t))q->info.si_uid = from_kuid_munged(task_cred_xxx(t, user_ns), current_uid())breakcase (unsigned long) SEND_SIG_PRIV:clear_siginfo(&q->info)q->info.si_signo = sigq->info.si_errno = 0q->info.si_code = SI_KERNELq->info.si_pid = 0q->info.si_uid = 0breakdefault:copy_siginfo(&q->info, info)break}}//if (q)out_set:signalfd_notify(t, sig) {if (unlikely(waitqueue_active(&tsk->sighand->signalfd_wqh)))wake_up(&tsk->sighand->signalfd_wqh)}/* 7) 将信号添加到对应的pending位图中 */sigaddset(&pending->signal, sig)/* 8) 在目标进程中搜寻并唤醒一个可以处理该信号的线程, 同时置位TIF_SIGPENDING, 若是致命信号则将所有线程都置位SIGKILL, 并全部唤醒 */complete_signal(sig, struct task_struct *p = t, type) {struct signal_struct *signal = p->signalstruct task_struct *t/* 8.1) 判断目标任务是否能够处理该信号 */ret = wants_signal(sig, p) {if (sigismember(&p->blocked, sig))  /* 目标任务屏蔽了该信号 */return falseif (p->flags & PF_EXITING)          /* 目标任务正在执行退出流程 */return falseif (sig == SIGKILL)                 /* 信号为SIGKILL */return trueif (task_is_stopped_or_traced(p))return falsereturn task_curr(p) || !task_sigpending(p)}//wants_signal/* 8.2) 若目标任务不能处理该信号, 则搜寻进程中其他线程是否有能够处理该信号的 */if (ret) {t = p} else if ((type == PIDTYPE_PID) || thread_group_empty(p)) {return} else {t = signal->curr_targetwhile (!wants_signal(sig, t)) {t = next_thread(t)if (t == signal->curr_target)return}signal->curr_target = t}/* 8.3) 若信号是致命信号, 则将进程中所有线程都置位SIGKILL信号, 并将所有线程唤醒 */if (sig_fatal(p, sig) && (signal->core_state || !(signal->flags & SIGNAL_GROUP_EXIT)) &&!sigismember(&t->real_blocked, sig) && (sig == SIGKILL || !p->ptrace)){if (!sig_kernel_coredump(sig)) {/* 给进程signal置位SIGNAL_GROUP_EXIT, 代表进程需要走退出流程 */signal->flags = SIGNAL_GROUP_EXITsignal->group_exit_code = sigsignal->group_stop_count = 0t = p/* 将所有线程都置位SIGKILL pending信号位并唤醒它们 */do {task_clear_jobctl_pending(t, JOBCTL_PENDING_MASK)sigaddset(&t->pending.signal, SIGKILL)signal_wake_up(t, 1)} while_each_thread(p, t)return}}/* 8.4) 唤醒处理该信号的任务, 并为其置位TIF_SIGPENDING */signal_wake_up(t, fatal = sig == SIGKILL) {if (fatal && !(t->jobctl & JOBCTL_PTRACE_FROZEN)) {t->jobctl &= ~(JOBCTL_STOPPED | JOBCTL_TRACED)state = TASK_WAKEKILL | __TASK_TRACED}signal_wake_up_state(t, state) {set_tsk_thread_flag(t, TIF_SIGPENDING)### 若wake_up_state返回0, 代表目标task已经在其他CPU上运行, 此时调用kick_process发送核间中断, 使其在中断返回前夕相应信号if (!wake_up_state(t, state | TASK_INTERRUPTIBLE))kick_process(struct task_struct *p = t) {preempt_disable()cpu = task_cpu(p)if ((cpu != smp_processor_id()) && task_curr(p))smp_send_reschedule(cpu)preempt_enable()}}//signal_wake_up_state}//signal_wake_upreturn}//complete_signalret:return ret}//__send_signal_locked
}
http://www.xdnf.cn/news/19465.html

相关文章:

  • 为什么服务器接收 URL 参数时会接收到解码后的参数
  • DHT11-温湿度传感器
  • openEuler2403部署Redis8集群
  • 京东入局外卖,还有很多问题。
  • Ubuntu 服务器实战:Docker 部署 Nextcloud+ZeroTier,打造可远程访问的个人云
  • 学习 Android (十八) 学习 OpenCV (三)
  • OpenHarmony 分布式感知中枢深度拆解:MSDP 框架从 0 到 1 的实战指南
  • 餐饮外卖同城配送酒水寄存餐品加价换购促销小程序APP
  • Windows 安装配置解压版MongoDb
  • TFT屏幕:STM32硬件SPI+DMA+队列自动传输
  • 【RelayMQ】基于 Java 实现轻量级消息队列(五)
  • 2025 最新Vue前端面试题目 (9月最新)
  • AI 重构医疗诊断:影像识别准确率突破 98%,基层医院如何借技术缩小诊疗差距?
  • 设备服务管理上报方案
  • 两轮车车机 OS 演进路线深度解析
  • libmodbus源码分析
  • 【前端】《手把手带你入门前端》前端的一整套从开发到打包流程, 这篇文章都会教会你;什么是vue,Ajax,Nginx,前端三大件?
  • 差角函数差角矩阵位置编码
  • 无需服务器也能建网站:Docsify+cpolar让技术文档分享像写笔记一样简单
  • 手机版碰一碰发视频源码搭建,技术实现与实操指南
  • 鸿蒙开发进阶(HarmonyOS)
  • Unity中多线程与高并发下的单例模式
  • MobaXterm介绍
  • Git将多笔patch合并成一笔
  • 苹果 Safari 地址栏可能被超大光标视觉欺骗
  • HarvardX TinyML小笔记2(番外3:数据工程)(TODO)
  • 杰理ac791无法控制io脚原因
  • Coze源码分析-工作空间-项目开发-后端源码
  • 传输层TCP 与 安全层SSL/TLS
  • shell之扩展