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

目录

一、信号发送简介

二、实现原理

三、代码实现


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

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

一、信号发送简介

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

1)kill 系统调用

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

cpp 复制代码
#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)的简单封装,函数原型如下:

cpp 复制代码
#include <signal.h>

int raise(int sig);

3)pthread_kill() 函数

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

cpp 复制代码
#include <signal.h>
#include <pthread.h>

int pthread_kill(pthread_t thread, int sig);

4)alarm() 函数

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

cpp 复制代码
#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)所有类型的信号发送在内核中的实现

cpp 复制代码
/* 发送信号给进程、进程组 */
SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
    kill_something_info(sig, &info, pid)
        if (pid > 0)
            /* 发送给进程 */
            kill_proc_info
                kill_pid_info
                    group_send_sig_info
                        do_send_sig_info
                            send_signal_locked
        if (pid != -1) {
            /* 发送给进程组 */
            __kill_pgrp_info
                do_each_pid_task(pgrp, PIDTYPE_PGID, p) {
                    group_send_sig_info
                        do_send_sig_info
                            send_signal_locked
                }
        } else {
            /* 发送给所有进程 */
            for_each_process(p) {
                group_send_sig_info(sig, info, p,PIDTYPE_MAX)
                    do_send_sig_info
                        send_signal_locked
            }
        }
        

/* 发送信号给指定线程 */
SYSCALL_DEFINE2(tkill, pid_t, pid, int, sig)
    do_tkill
        do_send_specific
            do_send_sig_info
                send_signal_locked

2)send_signal_locked 函数实现

cpp 复制代码
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 *pending
		struct 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 == SIGKILL
                return 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_STOPPED
                        wake_up_state(t, __TASK_STOPPED)
                    } else
                        ptrace_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 false
                
                if (t->ptrace && sig != SIGKILL)
                    return false
                
                return sig_task_ignored(t, sig, force) {
                    void __user *handler = sig_handler(t, sig)
                        return t->sighand->action[sig - 1].sa.sa_handler
                        
                    return 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_ignored
            
            return !is_ignored
		}//prepare_signal
		
        if(!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_queue
        if (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 = NULL
           
            rcu_read_lock()
            struct ucounts *ucounts = task_ucounts(t)
            sigpending = inc_rlimit_get_ucounts(ucounts, UCOUNT_RLIMIT_SIGPENDING)
            rcu_read_unlock()
            if (!sigpending)
                return NULL
            if (override_rlimit || likely(sigpending <= task_rlimit(t, RLIMIT_SIGPENDING))) {
                q = kmem_cache_alloc(sigqueue_cachep, gfp_flags)
            }
            return q
        }//__sigqueue_alloc
    
        if (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 = sig
                    q->info.si_errno = 0
                    q->info.si_code = SI_USER
                    q->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())
                    break
                case (unsigned long) SEND_SIG_PRIV:
                    clear_siginfo(&q->info)
                    q->info.si_signo = sig
                    q->info.si_errno = 0
                    q->info.si_code = SI_KERNEL
                    q->info.si_pid = 0
                    q->info.si_uid = 0
                    break
                default:
                    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->signal
            struct task_struct *t
            
            /* 8.1) 判断目标任务是否能够处理该信号 */
            ret = wants_signal(sig, p) {
                if (sigismember(&p->blocked, sig))  /* 目标任务屏蔽了该信号 */
                    return false
                if (p->flags & PF_EXITING)          /* 目标任务正在执行退出流程 */
                    return false
                if (sig == SIGKILL)                 /* 信号为SIGKILL */
                    return true
                if (task_is_stopped_or_traced(p))
                    return false
                return 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_target
                while (!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_EXIT
                    signal->group_exit_code = sig
                    signal->group_stop_count = 0
                    
                    t = 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_up
            return
        }//complete_signal
    ret:
        return ret
	}//__send_signal_locked
}
相关推荐
树℡独4 小时前
ns-3仿真之应用层(五)
服务器·网络·tcp/ip·ns3
嵩山小老虎5 小时前
Windows 10/11 安装 WSL2 并配置 VSCode 开发环境(C 语言 / Linux API 适用)
linux·windows·vscode
Fleshy数模5 小时前
CentOS7 安装配置 MySQL5.7 完整教程(本地虚拟机学习版)
linux·mysql·centos
a41324475 小时前
ubuntu 25 安装vllm
linux·服务器·ubuntu·vllm
Configure-Handler6 小时前
buildroot System configuration
java·服务器·数据库
津津有味道6 小时前
易语言TCP服务端接收刷卡数据并向客户端读卡器发送指令
服务器·网络协议·tcp·易语言
Fᴏʀ ʏ꯭ᴏ꯭ᴜ꯭.7 小时前
Keepalived VIP迁移邮件告警配置指南
运维·服务器·笔记
Genie cloud7 小时前
1Panel SSL证书申请完整教程
服务器·网络协议·云计算·ssl
一只自律的鸡7 小时前
【Linux驱动】bug处理 ens33找不到IP
linux·运维·bug
17(无规则自律)7 小时前
【CSAPP 读书笔记】第二章:信息的表示和处理
linux·嵌入式硬件·考研·高考