Linux内核-内核信号处理函数

内核信号处理函数

sigaction结构体

// 下面是 sigaction 的数据结构。 
 // sa_handler 是对应某信号指定要采取的行动。可以是上面的 SIG_DFL,或者是 SIG_IGN 来忽略 
 // 该信号,也可以是指向处理该信号的一个指针。 
 // sa_mask 给出了对信号的屏蔽码,在信号程序执行时将阻塞对这些信号的处理。 
 // sa_flags 指定改变信号处理过程的信号集。它是由 37-39 行的位标志定义的。 
 // 另外,引起触发信号处理的信号也将被阻塞,除非使用了 SA_NOMASK 标志。
struct sigaction {
    void (*sa_handler)(int);
    sigset_t sa_mask;
    int sa_flags;
    void (*sa_restorer)(void);
};

do_signal

它的核心作用是:在进程即将从内核态返回用户态的前夕,检查并处理该进程收到的待处理信号

它不直接对应任何一个系统调用。它是由所有的系统调用(以及中断/异常)在返回用户态时统一调用的“钩子函数”。

 // 系统调用中断处理程序中真正的信号处理程序(在 kernel/system_call.s,119 行)。 
 // 该段代码的主要作用是将信号的处理句柄插入到用户程序堆栈中,并在本系统调用结束 
 // 返回后立刻执行信号句柄程序,然后继续执行用户的程序。
/**
 * @brief 处理信号中断
 * 
 * @param signr 信号值
 * @param eax 通常用来执行加法,函数调用的返回值一般也放在这里面
 * @param ebx 数据存取
 * @param ecx 通常用来作为计数器,如for循环
 * @param edx 读写I/O端口时,edx用来存放端口号
 * @param fs 文件数据段
 * @param es 扩展数据段
 * @param ds 数据段
 * @param eip 指令寄存器
 * @param cs 代码段
 * @param eflags 标志寄存器
 * @param esp 栈顶指针
 * @param ss 栈段
 */
void do_signal(long signr,long eax, long ebx, long ecx, long edx,
    long fs, long es, long ds,
    long eip, long cs, long eflags,
    unsigned long * esp, long ss)

决策信号处理函数的执行

    unsigned long sa_handler;
    long old_eip=eip;
    struct sigaction * sa = current->sigaction + signr - 1;
    int longs;
    unsigned long * tmp_esp;

    sa_handler = (unsigned long) sa->sa_handler;
    if (sa_handler==1)  //如果信号处理程序为可忽略的就直接返回
        return;
    if (!sa_handler) {
        if (signr==SIGCHLD)  //如果信号处理为默认并且信号是SIGCHLD则返回
            return;
        else
            do_exit(1<<(signr-1)); //如果不是SIGCHLD直接终止进程
    }

在这里的struct sigaction * sa = current->sigaction + signr - 1;可以看出sigaction指向的是对应触发的信号的sigactionsigactiontask_struct结构体里面定义的是一个数组:struct sigaction sigaction[32]; // 信号执行属性结构,对应信号将要执行的操作和标志信息

#define SIG_DFL        ((void (*)(int))0)    /* default signal handling */  // 默认的信号处理程序(信号句柄)
#define SIG_IGN        ((void (*)(int))1)    /* ignore signal */            // 忽略信号的处理程序
  • 对于sa_handler,如果是可忽略的就直接返回,说明没有什么需要做的
  • 如果是sa_handler是默认,那么就会分情况讨论:如果是收到了SIGCHLD那么也什么都不做直接退出。如果是其他信号那么就直接终止进程。

POSIX 标准为每个信号规定了默认行为。绝大多数信号的默认行为都是 “终止进程(Terminate)”,但有几个特殊信号默认是 “忽略(Ignore)”,其中最重要的就是 SIGCHLD。在这里传入 1 << (signr-1)(即信号编号对应的位掩码),是为了告诉父进程(通过 wait() 系统调用):该子进程不是正常退出,而是因为收到了某个未捕获的信号而终止的

修改用户态执行流

    if (sa->sa_flags & SA_ONESHOT)
        sa->sa_handler = NULL;
    *(&eip) = sa_handler;// 将用户调用系统调用的代码指针 eip 指向该信号处理句柄。
    longs = (sa->sa_flags & SA_NOMASK)?7:8;
    *(&esp) -= longs;
    verify_area(esp,longs*4);

这里先是判断信号处理函数是否是一次性的,如果是一次性的就会把这个信号处理句柄置空

然后将用户的eip指针指向信号处理程序,并且在用户栈中为这块程序分配堆栈空间,这里可以看出信号处理程序是运行在用户态空间的,在函数结束后就会去执行信号处理程序

如果使用了SA_NOMASK则表示在进行当前信号处理程序时候该信号不会被阻塞默认是会被阻塞的

// 在用户堆栈中从下到上存放 sa_restorer, 信号 signr, 屏蔽码 blocked(如果 SA_NOMASK 置位), 
    // eax, ecx, edx, eflags 和用户程序原代码指针
    tmp_esp=esp;
    put_fs_long((long) sa->sa_restorer,tmp_esp++);
    put_fs_long(signr,tmp_esp++);
    if (!(sa->sa_flags & SA_NOMASK))
        put_fs_long(current->blocked,tmp_esp++);
    put_fs_long(eax,tmp_esp++);
    put_fs_long(ecx,tmp_esp++);
    put_fs_long(edx,tmp_esp++);
    put_fs_long(eflags,tmp_esp++);
    put_fs_long(old_eip,tmp_esp++);
    current->blocked |= sa->sa_mask;

接着就会去设置信号处理函数的栈结构,放入各种寄存器信息和状态寄存器,以及原本用户程序的中断指针

sa_restorer是信号处理程序结束之后跳转到执行的程序,sa_restorer 里通常只有一条核心指令——触发 int 0x80调用 sys_sigreturn

sys_signal

// signal()系统调用。类似于 sigaction()。为指定的信号安装新的信号句柄(信号处理程序)。 
 // 信号句柄可以是用户指定的函数,也可以是 SIG_DFL(默认句柄)或 SIG_IGN(忽略)。 
 // 参数 signum --指定的信号;handler -- 指定的句柄;restorer -- [??] 
 // 函数返回原信号句柄。
 /**
  * @brief 为指定的信号安装新的信号句柄(信号处理程序)
  * 
  * @param signum 信号值(指定的信号)
  * @param handler 指定的句柄
  * @param restorer 指定的恢复句柄
  * @return int 返回当前任务的指定信号处理句柄(??返回原信号句柄)
  */
int sys_signal(int signum, long handler, long restorer)
{
    struct sigaction tmp;

    if (signum<1 || signum>32 || signum==SIGKILL)
        return -1;
    tmp.sa_handler = (void (*)(int)) handler;        // 指定的信号处理句柄
    tmp.sa_mask = 0;                                // 执行时的信号屏蔽码
    tmp.sa_flags = SA_ONESHOT | SA_NOMASK;            // 该句柄只使用 1 次后就恢复到默认值
    tmp.sa_restorer = (void (*)(void)) restorer;    // 指定信号恢复句柄
    handler = (long) current->sigaction[signum-1].sa_handler;
    current->sigaction[signum-1] = tmp;
    return handler;
}

sys_signal为指定信号安装新的信号处理函数,并且返回设置前的信号处理句柄

sys_sigaction

改变一个进程在受到信号时的操作

**
 * @brief 改变进程在收到一个信号时的操作
 * 
 * @param signum 除了 SIGKILL 以外的任何信号
 * @param action 新的信号操作结构体
 * @param oldaction 保存原始action
 * @return int success-0, fail-1
 */
int sys_sigaction(int signum, const struct sigaction * action,
    struct sigaction * oldaction)
{
    struct sigaction tmp;

    if (signum<1 || signum>32 || signum==SIGKILL)
        return -1;
    tmp = current->sigaction[signum-1];
    get_new((char *) action,
        (char *) (signum-1+current->sigaction));
    if (oldaction)
        save_old((char *) &tmp,(char *) oldaction);
    if (current->sigaction[signum-1].sa_flags & SA_NOMASK)
        current->sigaction[signum-1].sa_mask = 0;
    else
        current->sigaction[signum-1].sa_mask |= (1<<(signum-1));
    return 0;
}

将新的action替换调原来task_struct中对应信号的sigaction

上一篇
下一篇