信号是一种在进程间异步相应的方式,进程可以通过系统调用设置不同信号的相应回调,每次调度之前内核都会检查进程的信号,如果有置位的信号,则会唤醒该进程,下面两个是设置信号的调用
// signal()系统调用。类似于sigaction()。为指定的信号安装新的信号句柄(信号处理程序)。
// 信号句柄可以是用户指定的函数,也可以是SIG_DFL(默认句柄)或SIG_IGN(忽略)。
// 参数signum --指定的信号;handler -- 指定的句柄;restorer –原程序当前执行的地址位置。
// 函数返回原信号句柄。
int sys_signal (int signum, long handler, long restorer)
{
struct sigaction tmp;
if (signum < 1 || signum > 32 || signum == SIGKILL) // 信号值要在(1-32)范围内,
return -1; // 并且不得是SIGKILL。
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;
}
// sigaction()系统调用。改变进程在收到一个信号时的操作。signum 是除了SIGKILL 以外的任何
// 信号。[如果新操作(action)不为空]则新操作被安装。如果oldaction 指针不为空,则原操作
// 被保留到oldaction。成功则返回0,否则为-1。
int sys_sigaction (int signum, const struct sigaction *action,
struct sigaction *oldaction)
{
struct sigaction tmp;
// 信号值要在(1-32)范围内,并且信号SIGKILL 的处理句柄不能被改变。
if (signum < 1 || signum > 32 || signum == SIGKILL)
return -1;
// 在信号的sigaction 结构中设置新的操作(动作)。
tmp = current->sigaction[signum - 1];
get_new ((char *) action, (char *) (signum - 1 + current->sigaction));
// 如果oldaction 指针不为空的话,则将原操作指针保存到oldaction 所指的位置。
if (oldaction)
save_old ((char *) &tmp, (char *) oldaction);
// 如果允许信号在自己的信号句柄中收到,则令屏蔽码为0,否则设置屏蔽本信号。
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;
}
然后信号处理函数有点意思,因为是在内核态本来是要返回到用户态并从中断点重新开始运行的。但内核如果检测到需要处理信号的时候,就会把eip改成信号处理入口地址,然后通过修改用户态的栈,把中断返回出的eip和段等必要的寄存器压入,这样当从内核恢复到用户态的时候首先执行的是信号处理函数,信号处理函数返回的时候,会从堆栈中弹出原来的地址,但这个时候已经被内核修改成了当初中断打断的地方,于是就继续从中断点开始执行了
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; //current->sigaction[signu-1]。
int longs;
unsigned long *tmp_esp;
sa_handler = (unsigned long) sa->sa_handler;
// 如果信号句柄为SIG_IGN(忽略),则返回;如果句柄为SIG_DFL(默认处理),则如果信号是
// SIGCHLD 则返回,否则终止进程的执行
if (sa_handler == 1)
return;
if (!sa_handler)
{
if (signr == SIGCHLD)
return;
else
// 这里应该是do_exit(1<<signr))。
do_exit (1 << (signr - 1)); // [?? 为什么以信号位图为参数?不为什么!??]
}
// 如果该信号句柄只需使用一次,则将该句柄置空(该信号句柄已经保存在sa_handler 指针中)。
if (sa->sa_flags & SA_ONESHOT)
sa->sa_handler = NULL;
// 下面这段代码将信号处理句柄插入到用户堆栈中,同时也将sa_restorer,signr,进程屏蔽码(如果
// SA_NOMASK 没置位),eax,ecx,edx 作为参数以及原调用系统调用的程序返回指针及标志寄存器值
// 压入堆栈。因此在本次系统调用中断(0x80)返回用户程序时会首先执行用户的信号句柄程序,然后
// 再继续执行用户程序。
// 将用户调用系统调用的代码指针eip 指向该信号处理句柄。
*(&eip) = sa_handler;
// 如果允许信号自己的处理句柄收到信号自己,则也需要将进程的阻塞码压入堆栈。
longs = (sa->sa_flags & SA_NOMASK) ? 7 : 8;
// 将原调用程序的用户的堆栈指针向下扩展7(或8)个长字(用来存放调用信号句柄的参数等),
// 并检查内存使用情况(例如如果内存超界则分配新页等)。
*(&esp) -= longs;
verify_area (esp, longs * 4);
// 在用户堆栈中从下到上存放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_mask 中的码位。
}
网友评论