云计算百科
云计算领域专业知识百科平台

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指向的是对应触发的信号的sigaction,sigaction在task_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

赞(0)
未经允许不得转载:网硕互联帮助中心 » Linux内核-内核信号处理函数
分享到: 更多 (0)

评论 抢沙发

评论前必须登录!