Linux 信号机制

1. 信号基本概念

1.1 基本概念

信号是软件中断,用来通知进程发生了异步事件。

信号屏蔽字,当前阻塞而不能递送给该进程的信号集。

内核区分信号传递的两个不同阶段:信号产生和信号传递。
信号产生:内核更新目标进程的数据结构以表示一个信号已被发送。

信号传递:内核强迫目标进程通过信号处理方法对信号做出反应。

挂起信号:已经产生但还没有传递的信号。

1.2 信号产生条件

1)当用户按某些终端键时,引发终端产生信号。

2)硬件异常产生信号,除数0、无效的内存引用等。

3)kill命令、kill函数将信号发送给指定进程或进程组。

4)某种软件条件已发生,并应将其通知有关进程。如管道读端终止,进程写此管道产生SIGPIPE,以及SIGALRM定时器超时。

1.3 信号处理方法

1)忽略此信号。注意SIGKILL和SIGSTOP不能被忽略。

2)捕捉信号。通知内核在某种信号发生时,调用一个用户函数进行处理。

3)执行系统默认动作。大多数信号系统默认动作是终止进程(有些信号在终止进程同时会产生内存映像core文件)。

1.4 常规信号和实时信号

Linux 传统的信号 1~31 为常规信号(regular signal),POSIX 引入了实时信号(real-time signal)编号为 32~64。
它们的不同在于:常规信号同一个编号在 pending 队列中只存在一份,如果有重复的则直接丢弃;实时信号的多个相同信号不能丢弃,需要保证每个信号都能送达。

Linux 常规信号介绍如下。

编号信号名称缺省操作解释POSIX
1SIGHUPTerminateHang up controlling terminal or processYes
2SIGINTTerminateInterrupt from keyboardYes
3SIGQUITDumpQuit from keyboardYes
4SIGILLDumpIllegal instructionYes
5SIGTRAPDumpBreakpoint for debuggingNo
6SIGABRTDumpAbnormal terminationYes
6SIGIOTDumpEquivalent to SIGABRTNo
7SIGBUSDumpBus errorNo
8SIGFPEDumpFloating-point exceptionYes
9SIGKILLTerminateForced-process terminationYes
10SIGUSR1TerminateAvailable to processesYes
11SIGSEGVDumpInvalid memory referenceYes
12SIGUSR2TerminateAvailable to processesYes
13SIGPIPETerminateWrite to pipe with no readersYes
14SIGALRMTerminateReal-timerclockYes
15SIGTERMTerminateProcess terminationYes
16SIGSTKFLTTerminateCoprocessor stack errorNo
17SIGCHLDIgnoreChild process stopped or terminated, or got signal if tracedYes
18SIGCONTContinueResume execution, if stoppedYes
19SIGSTOPStopStop process executionYes
20SIGTSTPStopStop process issued from ttyYes
21SIGTTINStopBackground process requires inputYes
22SIGTTOUStopBackground process requires outputYes
23SIGURGIgnoreUrgent condition on socketNo
24SIGXCPUDumpCPU time limit exceededNo
25SIGXFSZDumpFile size limit exceededNo
26SIGVTALRMTerminateVirtual timer clockNo
27SIGPROFTerminateProfile timer clockNo
28SIGWINCHIgnoreWindow resizingNo
29SIGIOTerminateI/O now possibleNo
29SIGPOLLTerminateEquivalent to SIGIONo
30SIGPWRTerminatePower supply failureNo
31SIGSYSDumpBad system callNo
31SIGUNUSEDDumpEquivalent to SIGSYSNo

1.5 信号处理过程


信号处理过程

1.6 POSIX 信号和多线程

POSIX 标准对多线程应用的信号处理有一些严格的要求:

1)信号处理程序必须在多线程应用的所有线程之前共享;不过,每个线程必须有自己的挂起信号掩码和阻塞信号掩码。
2) POSIX库函数kill和sigqueue必须向所有的多线程应用而不是某个特殊线程发送信号,有内核产生的信号同样如此。
3)每个发送给多线程应用的信号仅传送给一个线程,这个线程是由内核在从不会阻塞该信号的线程中随意选择出来的。
4)如果多线程应用发送了一个致命的信号,那么内核将杀死该应用的所有线程,而不仅仅是杀死接收信号的那个线程。

1.7 信号集相关操作

信号集用来描述信号的集合,每个信号占用一位。

typedef struct {
                unsigned long sig[_NSIG_WORDS];
} sigset_t;


int sigemptyset(sigset_t *set);                  /* 初始化由 set 指向的信号集,清除其中所有信号。 */

int sigfillset(sigset_t *set);                   /* 初始化由 set 指向的信号集,使其包含所有信号。 */

int sigaddset(sigset_t *set, int signo);         /* 将一个信号 signo 添加到现有信号集 set 中。 */

int sigdelset(sigset_t *set, int signo);         /* 将一个信号 signo 从信号集 set 中删除 */

int sigismember(const sigset_t *set, int signo); /* 判断指定信号 signo 是否在信号集 set 中。 */

1.8 进程信号相关操作

kill

kill 系统调用的功能是发送一个信号给线程组,只需要线程组挑出一个线程来响应处理信号。但是对于致命信号,线程组内所有进程都会被杀死,而不仅仅是处理信号的线程。

#include <sys/types.h>
#include <signal.h>

int kill(pid_t pid, int sig);

pid:可能选择有以下四种
1)pid大于零时,pid是信号欲送往的进程的标识。
2)pid等于零时,信号将送往所有与调用kill()的那个进程属同一个使用组的进程。
3)pid等于-1时,信号将送往所有调用进程有权给其发送信号的进程,除了进程1(init)。
4)pid小于-1时,信号将送往以-pid为组标识的进程。

sig:准备发送的信号代码,假如其值为零则没有任何信号送出,但是系统会执行错误检查,通常会利用sig值为零来检验某个进程是否仍在执行。

返回值说明: 成功,返回0。失败返回-1,errno被设为以下的某个值。
EINVAL:指定的信号码无效(参数 sig 不合法)
EPERM;权限不够无法传送信号给指定进程
ESRCH:参数 pid 所指定的进程或进程组不存在

tkill

向指定线程发送信号

int tkill(int tid, int sig);

tgkill:

向指定进程中的线程发送信号

int tgkill(int tgid, int tid, int sig);

sigqueue

sigqueue 是比较新的发送信号系统调用,主要是针对实时信号提出的(当然也支持前32种),支持信号带有参数,与函数 sigaction 配合使用。

#include <sys/types.h>
#include <signal.h>

int sigqueue(pid_t pid, int sig, const union sigval val)

第一个参数是指定接收信号的进程ID;
第二个参数确定即将发送的信号;
第三个参数是一个联合数据结构union sigval,指定了信号传递的参数,即通常所说的4字节值。

typedef union sigval {
    int  sival_int;
    void *sival_ptr;
}sigval_t;

成功,返回 0;失败,返回 -1。

sigqueue 只能向一个进程发送信号,而不能发送信号给一个进程组。如果signo=0,将会执行错误检查,但实际上不发送任何信号,0值信号可用于检查 pid 的有效性以及当前进程是否有权限向目标进程发送信号。

sigqueue 比 kill 传递了更多的附加信息,在调用 sigqueue 时,sigval_t 指定的信息会拷贝到对应 sig 注册的3参数信号处理函数的 siginfo_t 结构中,这样信号处理函数就可以处理这些信息了。
由于sigqueue系统调用支持发送带参数信号,所以比kill()系统调用的功能要灵活和强大得多。

signal

注册信号的用户态处理函数。

#include <signal.h>

typedef void (*sighandler_t)(int);
sighandler_t signal(int signum, sighandler_t handler);

第一个参数指定信号的值;
第二个参数指定针对前面信号值的处理,可以忽略该信号(参数设为SIG_IGN);可以采用系统默认方式处理信号(参数设为SIG_DFL);也可以自己实现处理方式(参数指定一个函数地址)。

成功,返回handler值,失败,返回SIG_ERR。

sigaction

注册信号的用户态处理函数

#include <signal.h>

int sigaction(int signum,const struct sigaction *act,struct sigaction *oldact));

第一个参数为信号的值;
第二个参数是指向结构sigaction的一个实例的指针,在结构sigaction的实例中,指定了对特定信号的处理,可以为空,进程会以缺省方式对信号处理;
第三个参数oldact指向的对象用来保存返回的原来对相应信号的处理,可指定oldact为NULL。
如果把第二、第三个参数都设为NULL,那么该函数可用于检查信号的有效性。

第二个参数最为重要,其中包含了对指定信号的处理、信号所传递的信息、信号处理函数执行过程中应屏蔽掉的信号等。

sigaction结构定义如下:

struct sigaction {
            union{
                    __sighandler_t _sa_handler;
                    void (*_sa_sigaction)(int, struct siginfo *, void *);
            }_u
            sigset_t sa_mask;
            unsigned long sa_flags;
            void (*sa_restorer)(void);
}

1)联合数据结构中的两个元素_sa_handler以及*_sa_sigaction指定信号关联函数,即用户指定的信号处理函数。除了可以是用户自定义的处理函数外,还可以为SIG_DFL(采用缺省的处理方式),也可以为SIG_IGN(忽略信号)。

2)由_sa_sigaction是指定的信号处理函数带有三个参数,是为实时信号而设的(当然同样支持非实时信号),它指定一个3参数信号处理函数。
第一个参数为信号值,
第三个参数没有使用,
第二个参数是指向siginfo_t结构的指针,结构中包含信号携带的数据值,参数所指向的结构如下:

siginfo_t {
        int    si_signo;  /* 信号值,对所有信号有意义*/
        int    si_errno;  /* errno值,对所有信号有意义*/
        int    si_code;   /* 信号产生的原因,对所有信号有意义*/
        union{            /* 联合数据结构,不同成员适应不同信号 */
                          //确保分配足够大的存储空间
                int _pad[SI_PAD_SIZE];
                /* 对SIGKILL有意义的结构 */
                struct{
                    __kernel_pid_t _pid;   /* sender's pid */
                    __ARCH_SI_UID_T _uid;  /* sender's uid */
                }_kill;
                ... ...
                ... ...
                /* 对POSIX.1b信号有意义的结构 */
                struct{
                    __kernel_pid_t _pid;   /* sender's pid */
                    __ARCH_SI_UID_T _uid;  /* sender's uid */
                    sigval_t _sigval;
                } _rt;
                /* 对SIGILL, SIGFPE, SIGSEGV, SIGBUS有意义的结构 */
                struct{
                        ...
                }...
                ... ...
        }
}

前面在讨论系统调用 sigqueue 发送信号时,sigqueue 的第三个参数就是 sigval 联合数据结构,当调用sigqueue时,该数据结构中的数据就将拷贝到信号处理函数的第二个参数中。这样,在发送信号同时,就可以让信号传递一些附加信息。

3)sa_mask 指定在信号处理程序执行过程中,哪些信号应当被阻塞。缺省情况下当前信号本身被阻塞,防止信号的嵌套发送,除非指定 SA_NODEFER 或者 SA_NOMASK 标志位。
请注意sa_mask指定的信号阻塞的前提条件,是在由 sigaction 安装信号的处理函数执行过程中由 sa_mask指定的信号才被阻塞。

4)sa_flags 中标志位及含义如下表所示。

标志说明
SA_NOCLDSTOP假如signum的值是SIGCHLD,则在子进程停止或恢复执行时不会传信号给调用本系统调用的进程。
SA_NOCLDWAIT当调用此系统调用的进程之子进程终止时,系统不会建立zombie进程。
SA_SIGINFO指定信号处理函数需要三个参数,所以应使用sa_sigaction替sa_handler。
SA_RESTART内核会自动重启信号中断的系统调用,否则返回EINTR错误值。
SA_NODEFER在信号处理函数处置信号的时段中,内核不会把这个间隙中产生的信号阻塞。
SA_RESETHAND信号处理函数接收到信号后,会先将对信号处理的方式设为预设方式,而且当函数处理该信号时,后来发生的信号将不会被阻塞。
SA_ONSTACK若利用sigaltstack()建立信号专用堆栈,则此标志会把所有信号送往该堆栈。

5)sa_restorer 不再使用。

sigprocmask

调用 sigprocmask 函数可以检测或者设置进程的信号屏蔽字。

#include <signal.h>

int sigprocmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

成功,返回0,失败,返回-1。

若 oset 参数是一个非空指针,则进程的当前信号屏蔽字将通过 oset 返回。
若 set 参数是一个非空指针,则参数 how 将指示如何修改当前信号屏蔽字。
how 的可选值如下表所示。

how说明
SIG_BLOCK该进程新的信号屏蔽字是其当前信号屏蔽字和 set 指向信号集的并集。
SIG_UNBLOCK该进程的信号屏蔽字是当前信号屏蔽字和 set 所指向信号集补集的交集。set 包含了我们希望解除阻塞的信号。
SIG_SETMASK该进程新的信号屏蔽字设置为 set 所指向的信号集。

sigpending

通过 set 参数返回当前进程所有未决的信号集。

#include <signal.h>
int sigpending(sigset_t *set);

成功,返回0,失败,返回-1。

sigsuspend

用于在接收到某个信号之前,临时用mask替换进程的信号掩码, 并暂停进程执行,直到收到信号为止。

#include <signal.h>
int sigsuspend(const sigset_t *mask));

sigsuspend 返回后将恢复调用之前的信号掩码。信号处理函数完成后,进程将继续执行。
该系统调用始终返回-1,并将 errno 设置为 EINTR。

1.9 线程信号相关操作

pthread_kill

将信号发送给同一进程内指定的线程(包括自己)。

#include <signal.h>

int pthread_kill(pthread_t thread, int signo);

成功,返回0,失败,返回错误编号:
ESRCH:指定线程不存在;
EIINVAL:指定信号无效或不支持。

若signo为0(空信号),则执行错误检查并返回ESRCH,但不发送信号,用于判断指定线程是否存在。

sigwait

线程同步等待一个或多个信号发生。

#include <signal.h>

int sigwait(const sigset_t *restrict sigset, int *restrict signop);

第一个参数sigset指定线程等待的信号集;
第二个参数signop指向的整数表明接收到的信号值。

成功,返回0,并将接收到的信号值存入signop所指向的内存空间,失败,返回错误编号(errno)。
该函数将调用线程挂起,直到信号集中的任何一个信号被递送。该函数接收递送的信号后,将其从未决队列中移除(以防返回时信号被signal/sigaction安装的处理函数捕获),然后唤醒线程并返回。

若已阻塞等待信号集中的信号,则 sigwait 会自动解除信号集的阻塞状态,直到有新的信号被递送。在返回之前,sigwait 将恢复线程的信号屏蔽字。因此,sigwait 并不改变信号的阻塞状态。

sigwaitinfo

线程同步等待一个或多个信号发生。

#include <signal.h>
int sigwaitinfo(const sigset_t *set, siginfo_t *info);  

sigwaitinfo功能与sigwait类似,但在返回值上存在差别。
sigwait在signop中返回触发的信号值,而sigwaitinfo的返回值就是触发的信号值,并且如果info不为NULL,则sigwaitinfo返回时,还会在siginfo_t *info中返回更多该信号的信息,

sigtimedwait

带有超时机制同步等待一个或多个信号发生。

#include <signal.h>

int sigtimedwait(const sigset_t *set, siginfo_t *info, const struct timespec *timeout);  

sigtimedwait的行为与sigwaitinfo的行为类似,只是它多了一个超时参数timeout,也就是可以设置该函数的最大阻塞时间。

pthread_sigmask。

设置线程的信号屏蔽集。

#include <signal.h>

int pthread_sigmask(int how, const sigset_t *restrict set, sigset_t *restrict oset);

成功,返回0,失败,返回错误编码。

若参数 oset 为非空指针,则该指针返回调用前本线程的信号屏蔽字;
若参数 set 为非空指针,则参数how指示如何修改当前信号屏蔽字;
how参数可选值如下表所示。

how说明
SIG_BLOCK将set中包含的信号加入本线程的当前信号屏蔽字。
SIG_UNBLOCK从本线程的当前信号屏蔽字中移除set中包含的信号(哪怕该信号并未被阻塞)。
SIG_SETMASK将set指向的信号集设置为本线程的信号屏蔽字。

主线程调用pthread_sigmask()设置信号屏蔽字后,其创建的新线程将继承主线程的信号屏蔽字。然而,新线程对信号屏蔽字的更改不会影响创建者和其他线程。

通常,被阻塞的信号将不能中断本线程的执行,除非该信号指示致命的程序错误(如SIGSEGV)。此外,不能被忽略处理的信号(SIGKILL 和SIGSTOP )无法被阻塞。

2. 信号内核实现

信号在目标进程中注册

在进程表的表项中有一个软中断信号域,该域中每一位对应一个信号。内核给一个进程发送软中断信号的方法,是在进程所在的进程表项的信号域设置对应于该信号的位。如果信号发送给一个正在睡眠的进程,如果进程睡眠在可被中断的优先级上,则唤醒进程;否则仅设置进程表中信号域相应的位,而不唤醒进程。如果发送给一个处于可运行状态的进程,则只置相应的域即可。

进程的task_struct结构中有关于本进程中未决信号的数据成员: struct sigpending pending:

struct sigpending{

    struct sigqueue *head, *tail;

    sigset_t signal;

};

第三个成员是进程中所有未决信号集,第一、第二个成员分别指向一个sigqueue类型的结构链(称之为”未决信号信息链”)的首尾,信息链中的每个sigqueue结构刻画一个特定信号所携带的信息,并指向下一个sigqueue结构:

struct sigqueue{

    struct sigqueue *next;

    siginfo_t info;

}

信号在进程中注册指的就是信号值加入到进程的未决信号集sigset_t signal(每个信号占用一位)中,并且信号所携带的信息被保留到未决信号信息链的某个sigqueue结构中。只要信号在进程的未决信号集中,表明进程已经知道这些信号的存在,但还没来得及处理,或者该信号被进程阻塞。

2.1 信号发送


这里写图片描述

SYSCALL_DEFINE2(kill, pid_t, pid, int, sig)
{
    struct siginfo info;

    info.si_signo = sig;
    info.si_errno = 0;
    info.si_code = SI_USER;
    info.si_pid = task_tgid_vnr(current);
    info.si_uid = current_uid();

    return kill_something_info(sig, &info, pid);
}

static int kill_something_info(int sig, struct siginfo *info, pid_t pid)
{
    int ret;

    /* 发送信号给特定进程的线程组 */
    if (pid > 0) {
        rcu_read_lock();
        ret = kill_pid_info(sig, info, find_vpid(pid));
        rcu_read_unlock();
        return ret;
    }

    read_lock(&tasklist_lock);
    /* 发送信号给pid进程或本进程同组进程的所有线程组 */
    if (pid != -1) {
        ret = __kill_pgrp_info(sig, info,
                pid ? find_vpid(-pid) : task_pgrp(current));
    } else {
        /* 发送信号给所有进程,除swapper、init、current进程 */
        int retval = 0, count = 0;
        struct task_struct * p;

        for_each_process(p) {
            if (task_pid_vnr(p) > 1 &&
                    !same_thread_group(p, current)) {
                int err = group_send_sig_info(sig, info, p);
                ++count;
                if (err != -EPERM)
                    retval = err;
            }
        }
        ret = count ? retval : -ESRCH;
    }
    read_unlock(&tasklist_lock);

    return ret;
}

int kill_pid_info(int sig, struct siginfo *info, struct pid *pid)
{
    int error = -ESRCH;
    struct task_struct *p;

    rcu_read_lock();
retry:
    p = pid_task(pid, PIDTYPE_PID);
    if (p) {
        error = group_send_sig_info(sig, info, p);
        if (unlikely(error == -ESRCH))
            /*
             * The task was unhashed in between, try again.
             * If it is dead, pid_task() will return NULL,
             * if we race with de_thread() it will find the
             * new leader.
             */
            goto retry;
    }
    rcu_read_unlock();

    return error;
}

/*
 * send signal info to all the members of a group
 * 向整个线程组发送信号
 */
int group_send_sig_info(int sig, struct siginfo *info, struct task_struct *p)
{
    int ret;

    rcu_read_lock();
    /* 检查权限 */
    ret = check_kill_permission(sig, info, p);
    rcu_read_unlock();

    if (!ret && sig)
        ret = do_send_sig_info(sig, info, p, true);

    return ret;
}

int do_send_sig_info(int sig, struct siginfo *info, struct task_struct *p,
            bool group)
{
    unsigned long flags;
    int ret = -ESRCH;

    if (lock_task_sighand(p, &flags)) {
        ret = send_signal(sig, info, p, group);
        unlock_task_sighand(p, &flags);
    }

    return ret;
}

static int send_signal(int sig, struct siginfo *info, struct task_struct *t,
            int group)
{
    int from_ancestor_ns = 0;

#ifdef CONFIG_PID_NS
    from_ancestor_ns = si_fromuser(info) &&
               !task_pid_nr_ns(current, task_active_pid_ns(t));
#endif

    return __send_signal(sig, info, t, group, from_ancestor_ns);
}

static int __send_signal(int sig, struct siginfo *info, struct task_struct *t,
            int group, int from_ancestor_ns)
{
    struct sigpending *pending;
    struct sigqueue *q;
    int override_rlimit;

    trace_signal_generate(sig, info, t);

    assert_spin_locked(&t->sighand->siglock);

    /* 检查是否忽略信号 */
    if (!prepare_signal(sig, t, from_ancestor_ns))
        return 0;

    /* 选择信号挂起队列:线程组共享队列 or 线程私有队列 */
    pending = group ? &t->signal->shared_pending : &t->pending;
    /*
     * Short-circuit ignored signals and support queuing
     * exactly one non-rt signal, so that we can get more
     * detailed information about the cause of the signal.
     * 检查信号是否是常规信号,并且在挂起队列中已有相同信号,则忽略。
     */
    if (legacy_queue(pending, sig))
        return 0;
    /*
     * fast-pathed signals for kernel-internal things like SIGSTOP
     * or SIGKILL.
     * SEND_SIG_NOINFO : 表示信号由用户态进程发送; 
   * SEND_SIG_PRIV : 表示信号由内核态( 进程) 发送;
   * SEND_SIG_FORCED : 表示信号由内核态( 进程) 发送, 并且信号是SIGKILL 或者SIGSTOP.
     * 强制信号,则直接快速处理
     */
    if (info == SEND_SIG_FORCED)
        goto out_set;

    /*
     * Real-time signals must be queued if sent by sigqueue, or
     * some other real-time mechanism.  It is implementation
     * defined whether kill() does so.  We attempt to do so, on
     * the principle of least surprise, but since kill is not
     * allowed to fail with EAGAIN when low on memory we just
     * make sure at least one signal gets delivered and don't
     * pass on the info struct.
     */
    if (sig < SIGRTMIN)
        override_rlimit = (is_si_special(info) || info->si_code >= 0);
    else
        override_rlimit = 0;

    q = __sigqueue_alloc(sig, t, GFP_ATOMIC | __GFP_NOTRACK_FALSE_POSITIVE,
        override_rlimit);
    if (q) {
        list_add_tail(&q->list, &pending->list);
        switch ((unsigned long) info) {
        case (unsigned long) SEND_SIG_NOINFO:
            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 = current_uid();
            break;
        case (unsigned long) SEND_SIG_PRIV:
            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);
            if (from_ancestor_ns)
                q->info.si_pid = 0;
            break;
        }

        userns_fixup_signal_uid(&q->info, t);

    } else if (!is_si_special(info)) {
        if (sig >= SIGRTMIN && info->si_code != SI_USER) {
            /*
             * Queue overflow, abort.  We may abort if the
             * signal was rt and sent by user using something
             * other than kill().
             */
            trace_signal_overflow_fail(sig, group, info);
            return -EAGAIN;
        } else {
            /*
             * This is a silent loss of information.  We still
             * send the signal, but the *info bits are lost.
             */
            trace_signal_lose_info(sig, group, info);
        }
    }

out_set:
    signalfd_notify(t, sig);
    /* 更新pending队列中对应bit */
    sigaddset(&pending->signal, sig);
    /* 查找能接收信号的线程 */
    complete_signal(sig, t, group);
    return 0;
}

static int prepare_signal(int sig, struct task_struct *p, int from_ancestor_ns)
{
    struct signal_struct *signal = p->signal;
    struct task_struct *t;

    if (unlikely(signal->flags & SIGNAL_GROUP_EXIT)) {
        /*
         * The process is in the middle of dying, nothing to do.
         * 若进程正在退出,则什么也不做
         */
    } else if (sig_kernel_stop(sig)) {
        /*
         * This is a stop signal.  Remove SIGCONT from all queues.
         * 若是stop信号,则移除已有的continue信号
         */
        rm_from_queue(sigmask(SIGCONT), &signal->shared_pending);
        t = p;
        do {
            rm_from_queue(sigmask(SIGCONT), &t->pending);
        } while_each_thread(p, t);
    } else if (sig == SIGCONT) {
        unsigned int why;
        /*
         * Remove all stop signals from all queues, wake all threads.
         * 若是continue信号,则移除已有stop信号,并唤醒所有进程
         */
        rm_from_queue(SIG_KERNEL_STOP_MASK, &signal->shared_pending);
        t = p;
        do {
            task_clear_jobctl_pending(t, JOBCTL_STOP_PENDING);
            rm_from_queue(SIG_KERNEL_STOP_MASK, &t->pending);
            if (likely(!(t->ptrace & PT_SEIZED)))
                wake_up_state(t, __TASK_STOPPED);
            else
                ptrace_trap_notify(t);
        } while_each_thread(p, t);

        /*
         * Notify the parent with CLD_CONTINUED if we were stopped.
         *
         * If we were in the middle of a group stop, we pretend it
         * was already finished, and then continued. Since SIGCHLD
         * doesn't queue we report only CLD_STOPPED, as if the next
         * CLD_CONTINUED was dropped.
         */
        why = 0;
        if (signal->flags & SIGNAL_STOP_STOPPED)
            why |= SIGNAL_CLD_CONTINUED;
        else if (signal->group_stop_count)
            why |= SIGNAL_CLD_STOPPED;

        if (why) {
            /*
             * The first thread which returns from do_signal_stop()
             * will take ->siglock, notice SIGNAL_CLD_MASK, and
             * notify its parent. See get_signal_to_deliver().
             */
            signal->flags = why | SIGNAL_STOP_CONTINUED;
            signal->group_stop_count = 0;
            signal->group_exit_code = 0;
        }
    }

    return !sig_ignored(p, sig, from_ancestor_ns);
}

static int sig_ignored(struct task_struct *t, int sig, int from_ancestor_ns)
{
    /*
     * Blocked signals are never ignored, since the
     * signal handler may change by the time it is
     * unblocked.
     * 被阻塞的信号将不会被忽略
     */
    if (sigismember(&t->blocked, sig) || sigismember(&t->real_blocked, sig))
        return 0;

    if (!sig_task_ignored(t, sig, from_ancestor_ns))
        return 0;

    /*
     * Tracers may want to know about even ignored signals.
     * 进程没有被跟踪,则忽略
     */
    return !t->ptrace;
}

static int sig_task_ignored(struct task_struct *t, int sig,
        int from_ancestor_ns)
{
    void __user *handler;

    /* 获取信号处理函数 */
    handler = sig_handler(t, sig);

    /* 信号是默认处理,进程无法被杀死的,则忽略信号 */
    if (unlikely(t->signal->flags & SIGNAL_UNKILLABLE) &&
            handler == SIG_DFL && !from_ancestor_ns)
        return 1;

    return sig_handler_ignored(handler, sig);
}

static int sig_handler_ignored(void __user *handler, int sig)
{
    /* Is it explicitly or implicitly ignored? */
    /* SIG_IGN显示忽略信号,SIG_DEL且为SIGCONT、SIGCHLD、SIGWINCH、SIGURG内核隐式忽略信号 */
    return handler == SIG_IGN ||
        (handler == SIG_DFL && sig_kernel_ignore(sig));
}

static void complete_signal(int sig, struct task_struct *p, int group)
{
    struct signal_struct *signal = p->signal;
    struct task_struct *t;

    /*
     * Now find a thread we can wake up to take the signal off the queue.
     *
     * If the main thread wants the signal, it gets first crack.
     * Probably the least surprising to the average bear.
     */
    /* 判断目标进程p是否合适 */
    if (wants_signal(sig, p))
        t = p;
    else if (!group || thread_group_empty(p))
        /*
         * There is just one thread and it does not need to be woken.
         * It will dequeue unblocked signals before it runs again.
         * 该信号是否是发送给一个线程组并且该线程组不为空
         */
        return;
    else {
        /*
         * Otherwise try to find a suitable thread.
         */
        t = signal->curr_target;
        while (!wants_signal(sig, t)) {
            t = next_thread(t);
            if (t == signal->curr_target)
                /*
                 * No thread needs to be woken.
                 * Any eligible threads will see
                 * the signal in the queue soon.
                 */
                return;
        }
        signal->curr_target = t;
    }

    /*
     * Found a killable thread.  If the signal will be fatal,
     * then start taking the whole group down immediately.
     * 判断该信号是否是致命信号,如果是的话就添加SIGKILL信号给每一个线程,并且唤醒它来处理
     */
    if (sig_fatal(p, sig) &&
        !(signal->flags & (SIGNAL_UNKILLABLE | SIGNAL_GROUP_EXIT)) &&
        !sigismember(&t->real_blocked, sig) &&
        (sig == SIGKILL || !t->ptrace)) {
        /*
         * This signal will be fatal to the whole group.
         */
        if (!sig_kernel_coredump(sig)) {
            /*
             * Start a group exit and wake everybody up.
             * This way we don't have other threads
             * running and doing things after a slower
             * thread has the fatal signal pending.
             */
            signal->flags = SIGNAL_GROUP_EXIT;
            signal->group_exit_code = sig;
            signal->group_stop_count = 0;
            t = p;
            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;
        }
    }

    /*
     * The signal is already in the shared-pending queue.
     * Tell the chosen thread to wake up and dequeue it.
     */
    signal_wake_up(t, sig == SIGKILL);
    return;
}

/*
 * 判断是否能够发送信号给该进程,
 * 1.在以下情况下无法发送该信号给进程:
 *   a.信号被进程阻塞
 *   b.进程正在退出
 *   c.进程处于暂停状态
 * 2.在以下情况下能够发送信号给进程:
 *   a.信号是SIGKILL,必须发送
 *   b.进程运行在当前CPU上
 *   c.进程当前没有挂起的信号
 */

static inline int wants_signal(int sig, struct task_struct *p)
{
    if (sigismember(&p->blocked, sig))
        return 0;
    if (p->flags & PF_EXITING)
        return 0;
    if (sig == SIGKILL)
        return 1;
    if (task_is_stopped_or_traced(p))
        return 0;
    return task_curr(p) || !signal_pending(p);
}

/*
 * Tell a process that it has a new active signal..
 *
 * NOTE! we rely on the previous spin_lock to
 * lock interrupts for us! We can only be called with
 * "siglock" held, and the local interrupt must
 * have been disabled when that got acquired!
 *
 * No need to set need_resched since signal event passing
 * goes through ->blocked
 */
void signal_wake_up(struct task_struct *t, int resume)
{
    unsigned int mask;

    /* 把标志TIF_SIGPENDING 加到thread_info->flags 中*/
    set_tsk_thread_flag(t, TIF_SIGPENDING);

    /*
     * For SIGKILL, we want to wake it up in the stopped/traced/killable
     * case. We don't check t->state here because there is a race with it
     * executing another processor and just now entering stopped state.
     * By using wake_up_state, we ensure the process will wake up and
     * handle its death signal.
     */
    mask = TASK_INTERRUPTIBLE;
    if (resume)
        mask |= TASK_WAKEKILL;
    /* 唤醒目标线程 */
    if (!wake_up_state(t, mask))
        kick_process(t);
}

2.2 信号处理

信号处理的关键是信号的响应时机,对一个进程发送一个信号以后,只是简单把信号挂载到目标进程的信号 pending 队列上去,信号真正得到执行的时机是进程在内核态执行完异常/中断返回到用户态的时刻。


信号处理

arch/arm/kernel/entry-common.S

/*
 * This is the fast syscall return path.  We do as little as
 * possible here, and this includes saving r0 back into the SVC
 * stack.
 */
ret_fast_syscall:
 UNWIND(.fnstart    )
 UNWIND(.cantunwind )
    disable_irq             @ disable interrupts
    ldr r1, [tsk, #TI_FLAGS]
    tst r1, #_TIF_WORK_MASK
    bne fast_work_pending
#if defined(CONFIG_IRQSOFF_TRACER)
    asm_trace_hardirqs_on
#endif

    /* perform architecture specific actions before user return */
    arch_ret_to_user r1, lr

    restore_user_regs fast = 1, offset = S_OFF
 UNWIND(.fnend      )

/*
 * Ok, we need to do extra processing, enter the slow path.
 */
fast_work_pending:
    str r0, [sp, #S_R0+S_OFF]!      @ returned r0
work_pending:
    tst r1, #_TIF_NEED_RESCHED
    bne work_resched
    tst r1, #_TIF_SIGPENDING|_TIF_NOTIFY_RESUME
    beq no_work_pending
    mov r0, sp              @ 'regs'
    mov r2, why             @ 'syscall'
    tst r1, #_TIF_SIGPENDING        @ delivering a signal?
    movne   why, #0             @ prevent further restarts
    bl  do_notify_resume
    b   ret_slow_syscall        @ Check work again

work_resched:
    bl  schedule
/*
 * "slow" syscall return path.  "why" tells us if this was a real syscall.
 */
ENTRY(ret_to_user)
ret_slow_syscall:
    disable_irq             @ disable interrupts
ENTRY(ret_to_user_from_irq)
    ldr r1, [tsk, #TI_FLAGS]
    tst r1, #_TIF_WORK_MASK
    bne work_pending
no_work_pending:
#if defined(CONFIG_IRQSOFF_TRACER)
    asm_trace_hardirqs_on
#endif
    /* perform architecture specific actions before user return */
    arch_ret_to_user r1, lr

    restore_user_regs fast = 0, offset = 0
ENDPROC(ret_to_user_from_irq)
ENDPROC(ret_to_user)

asmlinkage void
do_notify_resume(struct pt_regs *regs, unsigned int thread_flags, int syscall)
{
    if (thread_flags & _TIF_SIGPENDING)
        do_signal(regs, syscall);

    if (thread_flags & _TIF_NOTIFY_RESUME) {
        clear_thread_flag(TIF_NOTIFY_RESUME);
        tracehook_notify_resume(regs);
        if (current->replacement_session_keyring)
            key_replace_session_keyring();
    }
}

static void do_signal(struct pt_regs *regs, int syscall)
{
    unsigned int retval = 0, continue_addr = 0, restart_addr = 0;
    struct k_sigaction ka;
    siginfo_t info;
    int signr;

    /*
     * We want the common case to go fast, which
     * is why we may in certain cases get here from
     * kernel mode. Just return without doing anything
     * if so.
     */
    if (!user_mode(regs))
        return;

    /*
     * If we were from a system call, check for system call restarting...
     * 如果是系统调用被中断,检查是否需要重启系统调用
     */
    if (syscall) {
        continue_addr = regs->ARM_pc;
        restart_addr = continue_addr - (thumb_mode(regs) ? 2 : 4);
        retval = regs->ARM_r0;

        /*
         * Prepare for system call restart.  We do this here so that a
         * debugger will see the already changed PSW.
         */
        switch (retval) {
        case -ERESTARTNOHAND:
        case -ERESTARTSYS:
        case -ERESTARTNOINTR:
            regs->ARM_r0 = regs->ARM_ORIG_r0;
            regs->ARM_pc = restart_addr;
            break;
        case -ERESTART_RESTARTBLOCK:
            regs->ARM_r0 = -EINTR;
            break;
        }
    }

    if (try_to_freeze())
        goto no_signal;

    /*
     * Get the signal to deliver.  When running under ptrace, at this
     * point the debugger may change all our registers ...
     * 从线程的信号 pending 队列中取出信号,如果没有对应的用户自定义处理函数,则执行默认的内核态处理函数。
     */
    signr = get_signal_to_deliver(&info, &ka, regs, NULL);
    if (signr > 0) {
        sigset_t *oldset;

        /*
         * Depending on the signal settings we may need to revert the
         * decision to restart the system call.  But skip this if a
         * debugger has chosen to restart at a different PC.
         */
        if (regs->ARM_pc == restart_addr) {
            if (retval == -ERESTARTNOHAND
                || (retval == -ERESTARTSYS
                && !(ka.sa.sa_flags & SA_RESTART))) {
                regs->ARM_r0 = -EINTR;
                regs->ARM_pc = continue_addr;
            }
        }

        if (test_thread_flag(TIF_RESTORE_SIGMASK))
            oldset = &current->saved_sigmask;
        else
            oldset = &current->blocked;
        /* 执行信号处理函数 */
        if (handle_signal(signr, &ka, &info, oldset, regs) == 0) {
            /*
             * A signal was successfully delivered; the saved
             * sigmask will have been stored in the signal frame,
             * and will be restored by sigreturn, so we can simply
             * clear the TIF_RESTORE_SIGMASK flag.
             */
            if (test_thread_flag(TIF_RESTORE_SIGMASK))
                clear_thread_flag(TIF_RESTORE_SIGMASK);
        }
        return;
    }

 no_signal:
    if (syscall) {
        /*
         * Handle restarting a different system call.  As above,
         * if a debugger has chosen to restart at a different PC,
         * ignore the restart.
         */
        if (retval == -ERESTART_RESTARTBLOCK
            && regs->ARM_pc == continue_addr) {
            if (thumb_mode(regs)) {
                regs->ARM_r7 = __NR_restart_syscall - __NR_SYSCALL_BASE;
                regs->ARM_pc -= 2;
            } else {
#if defined(CONFIG_AEABI) && !defined(CONFIG_OABI_COMPAT)
                regs->ARM_r7 = __NR_restart_syscall;
                regs->ARM_pc -= 4;
#else
                u32 __user *usp;

                regs->ARM_sp -= 4;
                usp = (u32 __user *)regs->ARM_sp;

                if (put_user(regs->ARM_pc, usp) == 0) {
                    regs->ARM_pc = KERN_RESTART_CODE;
                } else {
                    regs->ARM_sp += 4;
                    force_sigsegv(0, current);
                }
#endif
            }
        }

        /* If there's no signal to deliver, we just put the saved sigmask
         * back.
         * 恢复保存的信号掩码
         */
        if (test_thread_flag(TIF_RESTORE_SIGMASK)) {
            clear_thread_flag(TIF_RESTORE_SIGMASK);
            sigprocmask(SIG_SETMASK, &current->saved_sigmask, NULL);
        }
    }
}

int get_signal_to_deliver(siginfo_t *info, struct k_sigaction *return_ka,
              struct pt_regs *regs, void *cookie)
{
    struct sighand_struct *sighand = current->sighand;
    struct signal_struct *signal = current->signal;
    int signr;

relock:
    /*
     * We'll jump back here after any time we were stopped in TASK_STOPPED.
     * While in TASK_STOPPED, we were considered "frozen enough".
     * Now that we woke up, it's crucial if we're supposed to be
     * frozen that we freeze now before running anything substantial.
     */
    try_to_freeze();

    spin_lock_irq(&sighand->siglock);
    /*
     * Every stopped thread goes here after wakeup. Check to see if
     * we should notify the parent, prepare_signal(SIGCONT) encodes
     * the CLD_ si_code into SIGNAL_CLD_MASK bits.
     * 子进程状态变化时,发送信号给父进程
     */
    if (unlikely(signal->flags & SIGNAL_CLD_MASK)) {
        int why;

        if (signal->flags & SIGNAL_CLD_CONTINUED)
            why = CLD_CONTINUED;
        else
            why = CLD_STOPPED;

        signal->flags &= ~SIGNAL_CLD_MASK;

        spin_unlock_irq(&sighand->siglock);

        /*
         * Notify the parent that we're continuing.  This event is
         * always per-process and doesn't make whole lot of sense
         * for ptracers, who shouldn't consume the state via
         * wait(2) either, but, for backward compatibility, notify
         * the ptracer of the group leader too unless it's gonna be
         * a duplicate.
         */
        read_lock(&tasklist_lock);
        do_notify_parent_cldstop(current, false, why);

        if (ptrace_reparented(current->group_leader))
            do_notify_parent_cldstop(current->group_leader,
                        true, why);
        read_unlock(&tasklist_lock);

        goto relock;
    }

    for (;;) {
        struct k_sigaction *ka;

        if (unlikely(current->jobctl & JOBCTL_STOP_PENDING) &&
            do_signal_stop(0))
            goto relock;

        if (unlikely(current->jobctl & JOBCTL_TRAP_MASK)) {
            do_jobctl_trap();
            spin_unlock_irq(&sighand->siglock);
            goto relock;
        }

        /* 从pending队列中,取出优先级最高信号 */
        signr = dequeue_signal(current, &current->blocked, info);

        if (!signr)
            break; /* will return 0 */

        if (unlikely(current->ptrace) && signr != SIGKILL) {
            signr = ptrace_signal(signr, info,
                          regs, cookie);
            if (!signr)
                continue;
        }

        /* 取出信号处理函数 */
        ka = &sighand->action[signr-1];

        /* Trace actually delivered signals. */
        trace_signal_deliver(signr, info, ka);

        /* 信号处理方法:忽略 */
        if (ka->sa.sa_handler == SIG_IGN) /* Do nothing.  */
            continue;
        /* 用户态处理函数 */
        if (ka->sa.sa_handler != SIG_DFL) {
            /* Run the handler.  */
            *return_ka = *ka;

            if (ka->sa.sa_flags & SA_ONESHOT)
                ka->sa.sa_handler = SIG_DFL;

            break; /* will return non-zero "signr" value */
        }

        /*
         * Now we are doing the default action for this signal.
         * 内核态默认处理方法,默认忽略
         */
        if (sig_kernel_ignore(signr)) /* Default is nothing. */
            continue;

        /*
         * Global init gets no signals it doesn't want.
         * Container-init gets no signals it doesn't want from same
         * container.
         *
         * Note that if global/container-init sees a sig_kernel_only()
         * signal here, the signal must have been generated internally
         * or must have come from an ancestor namespace. In either
         * case, the signal cannot be dropped.
         */
        if (unlikely(signal->flags & SIGNAL_UNKILLABLE) &&
                !sig_kernel_only(signr))
            continue;

        /* 默认处理方法STP:SIGSTOP、SIGTSTP、SIGTTIN、SIGTTOU */
        if (sig_kernel_stop(signr)) {
            /*
             * The default action is to stop all threads in
             * the thread group.  The job control signals
             * do nothing in an orphaned pgrp, but SIGSTOP
             * always works.  Note that siglock needs to be
             * dropped during the call to is_orphaned_pgrp()
             * because of lock ordering with tasklist_lock.
             * This allows an intervening SIGCONT to be posted.
             * We need to check for that and bail out if necessary.
             */
            if (signr != SIGSTOP) {
                spin_unlock_irq(&sighand->siglock);

                /* signals can be posted during this window */

                if (is_current_pgrp_orphaned())
                    goto relock;

                spin_lock_irq(&sighand->siglock);
            }

            if (likely(do_signal_stop(info->si_signo))) {
                /* It released the siglock.  */
                goto relock;
            }

            /*
             * We didn't actually stop, due to a race
             * with SIGCONT or something like that.
             */
            continue;
        }

        spin_unlock_irq(&sighand->siglock);

        /*
         * Anything else is fatal, maybe with a core dump.
         */
        current->flags |= PF_SIGNALED;

        /* 默认处理方法COREDUMP */
        if (sig_kernel_coredump(signr)) {
            if (print_fatal_signals)
                print_fatal_signal(regs, info->si_signo);
            /*
             * If it was able to dump core, this kills all
             * other threads in the group and synchronizes with
             * their demise.  If we lost the race with another
             * thread getting here, it set group_exit_code
             * first and our do_group_exit call below will use
             * that value and ignore the one we pass it.
             */
            do_coredump(info->si_signo, info->si_signo, regs);
        }

        /*
         * Death signals, no core dump.
         * 默认处理方法Terminate
         */
        do_group_exit(info->si_signo);
        /* NOTREACHED */
    }
    spin_unlock_irq(&sighand->siglock);
    return signr;
}

static int
handle_signal(unsigned long sig, struct k_sigaction *ka,
          siginfo_t *info, sigset_t *oldset,
          struct pt_regs * regs)
{
    struct thread_info *thread = current_thread_info();
    struct task_struct *tsk = current;
    int usig = sig;
    int ret;

    /*
     * translate the signal
     */
    if (usig < 32 && thread->exec_domain && thread->exec_domain->signal_invmap)
        usig = thread->exec_domain->signal_invmap[usig];

    /*
     * Set up the stack frame
     * 构造返回堆栈,将用户态返回地址替换成用户注册的信号处理函数
     */
    if (ka->sa.sa_flags & SA_SIGINFO)
        ret = setup_rt_frame(usig, ka, info, oldset, regs);
    else
        ret = setup_frame(usig, ka, oldset, regs);

    /*
     * Check that the resulting registers are actually sane.
     */
    ret |= !valid_user_regs(regs);

    if (ret != 0) {
        force_sigsegv(sig, tsk);
        return ret;
    }

    /*
     * Block the signal if we were successful.
     * 阻塞当前信号
     */
    spin_lock_irq(&tsk->sighand->siglock);
    sigorsets(&tsk->blocked, &tsk->blocked,
          &ka->sa.sa_mask);
    if (!(ka->sa.sa_flags & SA_NODEFER))
        sigaddset(&tsk->blocked, sig);
    recalc_sigpending();
    spin_unlock_irq(&tsk->sighand->siglock);

    return 0;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux信号机制是进程间通信的一种方式。它通过向进程发送信号来通知进程发生了某些事件,例如用户按下了某个键,或者内核发现了一个错误等等。进程可以选择忽略信号,或者处理信号并执行一些特定的操作。 为了进行Linux信号机制的实验,可以按照以下步骤进行: 1. 编写一个简单的C程序,用于演示信号的发送和接收。程序可以使用signal()函数来处理信号。在该程序中,可以使用kill()函数向指定进程发送信号,或者使用raise()函数向当前进程发送信号。 2. 运行该程序,并使用ps命令查看进程的PID。然后,可以使用kill命令向该进程发送信号,例如: ``` kill -SIGINT <PID> ``` 这将向指定的进程发送一个SIGINT信号,该信号通常用于中断进程。 3. 在程序中添加信号处理函数,以便在收到信号时执行特定的操作。例如,可以使用sigaction()函数来注册信号处理程序,如下所示: ``` struct sigaction sigact; sigemptyset(&sigact.sa_mask); sigact.sa_flags = 0; sigact.sa_handler = handle_signal; sigaction(SIGINT, &sigact, NULL); ``` 这将注册一个名为handle_signal()的函数,用于处理SIGINT信号。 4. 可以编写多个程序,并使用信号来实现进程间通信。例如,一个进程可以向另一个进程发送信号,以通知它执行某些操作。 5. 最后,可以尝试使用其他类型的信号,例如SIGTERM、SIGKILL等,以了解它们的不同之处。 总之,Linux信号机制是一个非常强大的工具,可以用于实现进程间通信、错误处理等。通过实验,我们可以更深入地了解信号的工作原理,并学会如何使用它们来编写更高效的程序。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值