0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

Linux内核中信号的传递过程

jf_0tjVfeJz 来源:嵌入式ARM和Linux 2024-01-17 09:51 次阅读

前面我们已经介绍了内核注意到信号的到来,调用相关函数更新进程描述符以便进程接收处理信号。但是,如果目标进程此时没有运行,内核则推迟传递信号。现在,我们看看内核如何处理进程挂起的信号。

正如第4章的从中断和异常返回一节中提到的,内核允许在进程返回到用户态执行之前,检查进程的TIF_SIGPENDING标志。因此,内核每次完成中断或异常的处理后,都会检查挂起信号是否存在。为了处理非阻塞的挂起信号,内核调用do_signal()函数,其接受2个参数

regs

current当前进程的用户态寄存器内容在内核栈中保存位置的地址。

oldset

用来保存阻塞信号位掩码数组的变量地址。

对do_signal()的描述,主要集中在信号传递的通用机制;真实的代码中涵盖了许多细节,比如处理竞态条件和其它特殊情况(如冻结系统、生成核心转储、停止和杀死整个线程组等等。我们将忽略这些细节。

如前所述,do_signal()函数通常只在CPU打算返回到用户态时才会被调用。所以,如果中断处理程序里调用do_signal(),函数直接返回:

if((regs->xcs&3)!=3)
return1;

如果oldset是NULL,使用current->blocked的地址初始化它:

if(!oldset)
oldset=¤t->blocked;

do_signal()的核心是一段循环代码,重复调用dequeue_signal()函数,直到私有和共享挂起信号队列没有被阻塞的挂起信号。dequeue_signal()的返回值存储在局部变量signr中,如果等于0,意味着所有挂起信号都被处理完,则do_signal()完成;如果返回非零,就有挂起信号等待被处理。do_signal()处理完这个信号后,会再次调用dequeue_signal()函数。

dequeue_signal()首先考虑私有挂起信号队列中的所有信号(数字从小到大),然后是共享挂起队列中的信号。它会更新相应的数据结构,标识该信号不再挂起并返回信号值。其中,涉及到清除current->pending.signal或current->signal->shared_pending.signal中的相应位,并调用recalc_sigpending()更新TIF_SIGPENDING的值。

让我们看一下do_signal()如何处理dequeue_signal()返回的每个挂起信号。首先,检查当前接收进程是否正在被其它进程监控;如果是这种情况,do_signal()调用do_notify_parent_cldstop()和schedule()使监控线程意识到该信号处理。

然后,do_signal()将待处理信号的k_sigaction数据结构的地址加载到局部变量ka中:

ka=¤t->sig->action[signr-1];

依赖具体内容,可能执行三类动作:忽略信号,执行默认动作或执行信号处理程序。

当传递的信号被忽略,do_signal()则继续处理其它挂起信号:

if(ka->sa.sa_handler==SIG_IGN)
continue;

接下来的两节,我们将描述如何执行默认动作和信号处理程序。

1 执行信号的默认动作

如果ka->sa.sa_handler等于SIG_DFL,do_signal()执行信号的默认动作。唯一的例外是,当接收进程是init时,这种情况下,信号会被抛弃:

if(current->pid==1)
continue;

对于其它进程,忽略信号的默认处理也非常简单:

if(signr==SIGCONT||signr==SIGCHLD||
signr==SIGWINCH||signr==SIGURG)
continue;

对于默认动作是stop的信号,则会停止线程组中所有进程。为此,do_signal()将它们的状态设置为TASK_STOPPED,然后调用schedule()调度进程:

if(signr==SIGSTOP||signr==SIGTSTP||
signr==SIGTTIN||signr==SIGTTOU){
if(signr!=SIGSTOP&&
is_orphaned_pgrp(current->signal->pgrp))
continue;
do_signal_stop(signr);
}

SIGSTOP和其它信号有些许不同:SIGSTOP总是停止线程组,而其它信号只有在线程组处于孤儿进程组中时才会停止该线程组。POSIX标准指明,只要线程组中某个进程在同一个会话的不同进程组中有一个父进程,它就不是孤儿进程组。因此,如果父进程已死,但是,发起进程的用户仍然处于登录中,该进程组就不是孤儿进程组。

do_signal_stop()检查当前进程是否是线程组中第一个被停止的进程。如果是,它负责停止所有进程:本质上,该函数将信号描述符中的group_stop_count字段设置为正值,并唤醒线程组中的每个进程。然后,每个进程依次查看此字段以识别正在进行的组停止,将其状态更改为TASK_STOPPED,并调用schedule()重新调度进程。do_signal_stop()函数还向线程组leader的父进程发送SIGCHLD信号,除非父进程设置了SIGCHLD的SA_NOCLDSTOP标志。

默认动作为dump的信号会在进程的工作目录中创建核心转储文件:该文件列出了进程地址空间和寄存器的完整内容。do_signal()创建核心转储文件之后,会杀死线程组。其余18个信号的默认动作是terminate,就是杀死进程。为此,调用do_group_exit(),执行一个优雅的group exit处理程序(可以参考第3章的进程终止一节)

2 捕获信号

如果信号指定了处理程序,则do_signal()执行该程序。通过调用invoking handle_signal()

handle_signal(signr,&info,&ka,oldset,regs);
if(ka->sa.sa_flags&SA_ONESHOT)
ka->sa.sa_handler=SIG_DFL;
return1;

如果接收的信号设置了SA_ONESHOT标志,则必须将其重置为默认操作,以便再次出现相同信号将不会触发信号处理程序的执行。注意do_signal()在处理完单个信号后是如何返回的。在下次调用do_signal()之前,不会考虑其他挂起的信号。这种方法确保了实时信号将按适当的顺序处理。

执行信号处理程序是一项相当复杂的任务,因为在用户态和内核态之间切换时需要小心地切换堆栈。我们在这里详细解释一下:

信号处理程序是由用户进程定义的函数,包含在用户代码段中。handle_signal()函数在内核态运行,而信号处理程序在用户态运行;这意味着当前进程必须首先在用户态执行信号处理程序,然后才能被允许恢复其“正常”执行。此外,当内核试图恢复进程的正常执行时,内核堆栈不再包含被中断程序的硬件上下文,因为内核堆栈在每次从用户态转换到内核态时都会被清空。

下图11-2说明了捕获信号的函数执行流程。假设非阻塞信号被发送给进程。中断或异常发生时,进程切换到内核态。在即将返回到用户态之前,内核调用do_signal()函数,依次处理信号(handle_signal())并配置用户态栈(setup_frame()或setup_rt_frame())。进程切换到用户态后,开始执行信号处理程序,因为该处理程序的地址被强制加载到了PC程序计数器中。当信号程序终止后,调用setup_frame()或setup_rt_frame()将返回代码加载到用户态栈中。这段返回代码会调用sigreturn()和rt_sigreturn()系统调用;相应的服务例程会将正常程序的硬件上下文内容拷贝到内核态栈并将用户态栈恢复到其原始状态(restore_sigcontext())。当系统调用终止时,正常程序继续其执行。

880c0774-b45f-11ee-8b88-92fbcf53809c.png

图11-2 捕获一个信号

现在,让我们看一下其执行细节:

2.1 Setting up the frame

为了正确设置进程的用户态栈,handle_signal()函数既可以调用setup_frame()(对于那些不需要siginfo_t的信号),也可以调用setup_rt_frame()(对于那些确定需要siginfo_t的信号)。具体调用哪个函数,依赖于信号的sigaction表中sa_flags字段的SA_SIGINFO标志。

接下来,我们看一下setup_frame()函数的具体实现:(Linux内核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c)

/*这些符号的定义在vsyscall内存页中,查看vsyscall-sigreturn.S文件*/
externvoid__user__kernel_sigreturn;
externvoid__user__kernel_rt_sigreturn;

staticvoidsetup_frame(intsig,structk_sigaction*ka,
sigset_t*set,structpt_regs*regs)
{
void__user*restorer;
structsigframe__user*frame;
interr=0;
intusig;

frame=get_sigframe(ka,regs,sizeof(*frame));

if(!access_ok(VERIFY_WRITE,frame,sizeof(*frame)))
gotogive_sigsegv;

usig=current_thread_info()->exec_domain
&¤t_thread_info()->exec_domain->signal_invmap
&&sig< 32
        ? current_thread_info()->exec_domain->signal_invmap[sig]
:sig;

err=__put_user(usig,&frame->sig);
if(err)
gotogive_sigsegv;

err=setup_sigcontext(&frame->sc,&frame->fpstate,regs,set->sig[0]);
if(err)
gotogive_sigsegv;

if(_NSIG_WORDS>1){
err=__copy_to_user(&frame->extramask,&set->sig[1],
sizeof(frame->extramask));
if(err)
gotogive_sigsegv;
}

restorer=&__kernel_sigreturn;
if(ka->sa.sa_flags&SA_RESTORER)
restorer=ka->sa.sa_restorer;

/*Setuptoreturnfromuserspace.*/
err|=__put_user(restorer,&frame->pretcode);

/*
*Thisispopl%eax;movl$,%eax;int$0x80
*
*WEDONOTUSEITANYMORE!It'sonlylefthereforhistorical
*reasonsandbecausegdbusesitasasignaturetonotice
*signalhandlerstackframes.
*/
err|=__put_user(0xb858,(short__user*)(frame->retcode+0));
err|=__put_user(__NR_sigreturn,(int__user*)(frame->retcode+2));
err|=__put_user(0x80cd,(short__user*)(frame->retcode+6));

if(err)
gotogive_sigsegv;

/*为信号处理程序配置寄存器*/
regs->esp=(unsignedlong)frame;
regs->eip=(unsignedlong)ka->sa.sa_handler;
regs->eax=(unsignedlong)sig;
regs->edx=(unsignedlong)0;
regs->ecx=(unsignedlong)0;

/*恢复用户态的段寄存器*/
set_fs(USER_DS);
regs->xds=__USER_DS;
regs->xes=__USER_DS;
regs->xss=__USER_DS;
regs->xcs=__USER_CS;

/*在进入信号处理程序时清除TF标志,但通知正在单步跟踪的跟踪器,
*跟踪器也可能希望在信号处理程序内部进行单步执行
*/
regs->eflags&=~TF_MASK;
if(test_thread_flag(TIF_SINGLESTEP))
ptrace_notify(SIGTRAP);
//...省略,打印调试信息,然后返回。

give_sigsegv:
force_sigsegv(sig,current);
}

setup_frame()接收4个参数,如下所示:

sig

信号

ka

信号的k_sigaction表地址

oldset

阻塞信号的位掩码组地址

regs

用户态寄存器内容在内核栈的保存位置

setup_frame()将一个称为frame的数据结构压倒用户态栈中,该数据结构存储着处理信号和能够正确返回到sys_sigreturn()函数的所需要信息。frame是一个sigframe表,包含以下字段(参见图11-3):

pretcode

信号处理程序的返回地址。其实就是__kernel_sigreturn标签处的汇编代码。

sig

信号,信号处理程序需要的一个参数。

sc

包含用户态进程即将切换到内核态之前的进程上下文内容,其数据类型为sigcontext(这些信息是从current的内核态栈中拷贝而来)。另外,它还包含一个进程阻塞信号的位数组。

fpstate

用来保存用户态进程的浮点寄存器信息,数据结构类型为_fpstate。(参见第3章的保存和加载FPU、MMX和XMM寄存器)。

extramask

指定阻塞实时信号的位数组。

retcode

发起sigreturn()系统调用的8字节代码。在Linux早期版本中,这段代码用来从信号处理程序返回;但Linux 2.6版本以后,仅用作符号签名,以便调试器可以识别信号的栈帧。

88212c12-b45f-11ee-8b88-92fbcf53809c.png

图11-3 用户态栈上的frame

setup_frame()函数调用get_sigframe()计算frame第一个内存位置,因为该内存位置位于用户态栈上,所以函数返回的值为(regs->esp - sizeof(struct sigframe)) & 0xfffffff8。

Linux允许进程调用signaltstack()系统调用为它们的信号处理程序指定一个替换栈;这个特性也是X/Open标准要求的。如果使用的是替换栈,get_sigframe()函数返回的是替换栈中的一个地址。对于此特性,我们不过多讨论,从概念上讲,其与常规信号处理非常类似。

因为在x86架构上,栈是向下增长的,所以,frame的首地址等于当前栈顶位置的地址减去frame的大小,结果按照8字节对齐。

返回地址使用access_ok进行验证:如果合法,函数重复调用__put_user(),以便填充frame的所有字段。pretcode字段初始化为&__kernel_sigreturn,这是vsyscall内存页上的一段汇编代码地址。(参见第10章的通过sysenter指令发起系统调用的一节)

接下来,修改内核态栈的regs内容,保证当current切换到用户态时,CPU控制权能够传递给信号处理程序:

regs->esp=(unsignedlong)frame;
regs->eip=(unsignedlong)ka->sa.sa_handler;
regs->eax=(unsignedlong)sig;
regs->edx=regs->ecx=0;
regs->xds=regs->xes=regs->xss=__USER_DS;
regs->xcs=__USER_CS;

最后,setup_frame()函数将保存在内核态栈上的段寄存器复位成用户态默认值而终止。现在,信号处理程序所需的信息都在用户态栈顶位置了。

setup_rt_frame()函数与setup_frame()类似,但是它把一个扩展帧(数据结构为rt_sigframe)存放到了用户态栈中,该扩展帧还包括与信号有关的siginfo_t表的内容。此外,该函数还将pretcode字段指向vsyscall内存页中的__kernel_rt_sigreturn代码段。

2.2 计算信号标志

配置完用户态栈后,handle_signal()函数检查与该信号相关的标志。如果该信号没有设置SA_NODEFER标志,则在信号处理程序执行期间,sigaction表中的sa_mask字段中的所有信号必须被阻塞,以便该信号快速处理完成:

if(!(ka->sa.sa_flags&SA_NODEFER)){
spin_lock_irq(¤t->sighand->siglock);
sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask);
sigaddset(¤t->blocked,sig);
recalc_sigpending(current);
spin_unlock_irq(¤t->sighand->siglock);
}

正如先前描述的,recalc_sigpending()函数检查该进程是否有非阻塞的挂起信号,并设置其相应的TIF_SIGPENDING标志。

完成之后,返回do_signal(),随即也返回。

2.3 启动信号处理程序

当从do_signal()返回后,当前进程切换到用户态执行。因为setup_frame()的准备工作,eip寄存器指向了信号处理程序的第一条指令,而esp指向了压入用户态栈顶的frame的第一个内存位置。于是,开始执行信号处理程序。

2.4 终止信号处理程序

当信号处理程序执行完成时,其栈顶的返回地址指向vsyscall内存页(frame中的pretcode字段)

__kernel_sigreturn:
popl%eax
movl$__NR_sigreturn,%eax
int$0x80

因此,信号(也就是frame中的sig字段)被从栈中丢弃;然后,调用sigreturn()系统调用。

sys_sigreturn()函数计算regs(类型为pt_regs)的地址,其中包含用户进程的硬件上下文内容,以便完成内核态切换到用户态执行。因为我们在从内核态切换到用户态执行信号处理程序的过程中,内核态栈已经被破坏,所以需要重新建立一个临时内核态栈,数据来源就是用户态栈中配置的frame数据结构。

asmlinkageintsys_sigreturn(unsignedlong__unused)
{
/*建立进程在内核态的临时栈*/
structpt_regs*regs=(structpt_regs*)&__unused;
//内核态栈中用户存储
structsigframe__user*frame=(structsigframe__user*)(regs->esp-8);
sigset_tset;
inteax;

/*验证`frame`数据结构是否正确*/
if(verify_area(VERIFY_READ,frame,sizeof(*frame)))
gotobadframe;
/*处理实时信号*/
if(__get_user(set.sig[0],&frame->sc.oldmask)
||(_NSIG_WORDS>1
&&__copy_from_user(&set.sig[1],&frame->extramask,
sizeof(frame->extramask))))
gotobadframe;

/*将在信号处理程序期间阻塞的信号恢复挂起状态*/
sigdelsetmask(&set,~_BLOCKABLE);
spin_lock_irq(¤t->sighand->siglock);
current->blocked=set;
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);

/*将用户态栈中的frame中保存的用户进程硬件上下文拷贝到内核态栈,并移除frame*/
if(restore_sigcontext(regs,&frame->sc,&eax))
gotobadframe;
returneax;

/*错误数据处理*/
badframe:
force_sig(SIGSEGV,current);
return0;
}

sys_sigreturn()函数计算出regs(类型为pt_regs)的地址,它包含用户进程的硬件上下文内容(参考第10章的参数传递一节。根据regs中的esp字段,就能推断出用户栈中的frame地址。

然后,从frame的sc字段中拷贝在调用信号处理程序之前被阻塞的信号(位数组)到当前进程current的blocked字段中。也就是将这些被阻塞的信号解除阻塞。调用recalc_sigpending()将这些信号重新加入到挂起信号队列中。

接下来,sys_sigreturn()函数需要将frame的sc字段中的进程硬件上下文拷贝到内核态栈,并从用户态栈中移除frame数据。这两步的完成都是restore_sigcontext()函数实现的。

如果信号是由系统调用发送的(比如,rt_sigqueueinfo()),要求信号相关的siginfo_t表数据,其机制与上面类似。扩展帧中的pretcode字段指向__kernel_rt_sigreturn标签处的汇编代码(位于vsyscall内存页),这段代码会调用rt_sigreturn()系统调用。相应的系统服务例程sys_rt_sigreturn()将扩展帧中的进程硬件上下文拷贝到内核态栈,并且将扩展帧从用户态栈中移除,以便恢复原始的用户态栈。

3 系统调用的重新执行

对于系统调用请求,内核有时不能立即满足。这时候,发起系统调用的进程会被置成TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE状态。

如果进程处于TASK_INTERRUPTIBLE状态且其它进程发送信号给它,内核在没有完成系统调用的情况下将进程置为TASK_RUNNING(参考第4章的从中断和异常返回一节)。当进程想要切换回用户态,同时信号传递过来时,系统调用服务例程还没有完成其工作,所以会返回错误码EINTR、ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS、ERESTARTNOINTR。

事实上,在这种场景下,用户态进程能得到的错误码只能是EINTR,这意味着系统调用还没有完成。应用编程者可以检查这个错误码并决定是否重新发起系统调用。其余的错误码由内核内部使用,指定是否在信号处理程序结束之后自动重新执行系统调用。

表11-11 列出了未完成系统调用相关的错误码,以及它们三种信号默认行为的影响。表中的术语说明如下:

Terminate

系统调用将不会自动重新执行;进程将切换到用户态下int $0x80或sysenter之后的指令处继续执行,同时,通过寄存器eax返回-EINTR值。

Reexecute

内核强制用户进程重新加载系统调用号(eax),然后重新调用int $0x80或sysenter;而进程不会意识到重新执行,也不会传递错误码给它。

Depends

只有被传递的信号设置了SA_RESTART标志,系统调用才会被重新执行;否则,系统调用将终止并返回错误码-EINTR。

表11-11 系统调用的重新执行

信号行为 EINTR ERESTARTSYS ERESTARTNOHAND
ERESTART_RESTARTBLOCK*
ERESTARTNOINTR
Default Terminate Reexecute Reexecute Reexecute
Ignore Terminate Reexecute Reexecute Reexecute
Catch Terminate Depends Terminate Reexecute

ERESTARTNOHAND和ERESTART_RESTARTBLOCK重启系统调用的机制不同。

在传递信号时,内核必须在重新执行它之前确保进程发起了系统调用。这就是regs寄存器上下文的orig_eax字段发挥关键作用的地方。让我们回忆一下,当中断或异常处理程序启动时,这个字段是如何初始化的:

中断

该字段为中断IRQ减去256(因为中断号数量小于224,减去256表示内核使用负数表示IRQ)(参考第4章的为中断处理程序保存寄存器)。

0x80异常(包括sysenter)

该字段包含系统调用号(第10章的进入和推出系统调用一节)。

其它异常

该字段为–1(参考第4章的为异常处理程序保存寄存器)。

因此,orig_eax中的非负值意味着信号唤醒了一个在系统调用中休眠的可中断进程(TASK_INTERRUPTIBLE)。服务例程意识到了系统调用被中断,因此返回一个前面提到的错误码。

3.1 重新启动non-caught信号中断的系统调用

对于被忽略或执行默认动作的信号,do_signal()分析系统调用的错误码,判断系统调用是否自动重新执行,如表11-1所示。如果系统调用必须重启,则修改regs上下文内容:eip-2表示将eip指向int $0x80或sysenter,eax包含系统调用号:

if(regs->orig_eax>=0){
if(regs->eax==-ERESTARTNOHAND||regs->eax==-ERESTARTSYS||
regs->eax==-ERESTARTNOINTR){
regs->eax=regs->orig_eax;
regs->eip-=2;
}
if(regs->eax==-ERESTART_RESTARTBLOCK){
regs->eax=__NR_restart_syscall;
regs->eip-=2;
}
}

regs->eax包含着系统调用服务例程的返回码(参加第10章的进入和退出系统调用一节)。因为int $0x80和sysreturn指令都是2个字节长度,所以eip-2指向了int $0x80或sysenter,可以再次触发系统调用。

错误码ERESTART_RESTARTBLOCK是特殊的,因为eax被设置为了restart_syscall()系统调用号;因此,用户不会重新启动被信号中断的同一个系统调用。此错误码只有与时间有关的系统调用使用,这些系统调用重新启动时,应该调整其用户态参数。典型的例子是nanosleep()系统调用(参考第6章的动态定时器的应用:nanosleep()系统调用):假设进程调用它来暂停执行20毫秒,随后过了10毫秒之后发生了一个信号。如果系统调用还和平常一样重启,那么,总的延时时间会超过30毫秒。

相反,如果nanosleep()系统调用服务例程被中断,则使用特殊服务例程的地址填充current进程的thread_info结构体的restart_block字段,并返回ERESTART_RESTARTBLOCK错误码。sys_restart_syscall()服务例程只执行前面特殊的服务里程,计算首次调用和重新启动之间经过的时间,从而调整延时。

3.2 重新启动caught信号中断的系统调用

如果信号需要捕获处理,handle_signal()分析错误码,根据sigaction中的SA_RESTART标志,判断是否需要重启:

if(regs->orig_eax>=0){
switch(regs->eax){
case-ERESTART_RESTARTBLOCK:
case-ERESTARTNOHAND:
regs->eax=-EINTR;
break;
case-ERESTARTSYS:
if(!(ka->sa.sa_flags&SA_RESTART)){
regs->eax=-EINTR;
break;
}
/*fallthrough*/
case-ERESTARTNOINTR:
regs->eax=regs->orig_eax;
regs->eip-=2;
}
}

如果必须重新启动系统调用,handle_signal()的处理方式与do_signal()完全相同;否则,它会向用户进程返回一个-EINTR错误码。

4 x86_64架构-do_signal()

Linux内核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c:

/*
*注意init是一个特殊进程:它不会收到不想处理的信号。所以,即使错误地发送
*`SIGKILL`信号给它,也不会杀死它。
*/
intdo_signal(structpt_regs*regs,sigset_t*oldset)
{
structk_sigactionka;
siginfo_tinfo;
intsignr;

/*如果不是返回到用户态,则直接返回。*/
if((regs->cs&3)!=3){
return1;
}

//...省略

if(!oldset)
oldset=¤t->blocked;

signr=get_signal_to_deliver(&info,&ka,regs,NULL);
if(signr>0){
/*
*在将信号传递到用户空间之前重新启动多有观察点。
*如果观察点在内核内部触发,寄存器将被清除。
*/
if(current->thread.debugreg7)
asmvolatile("movq%0,%%db7"::"r"(current->thread.debugreg7));

/*传递信号*/
handle_signal(signr,&info,&ka,oldset,regs);
return1;
}

no_signal:
/*是否是系统调用*/
//...省略(见前面第3.1节的处理)
return0;
}

x86-64架构的handle_signal()函数(文件位置:arch/x86_64/kernel/signal.c):

staticvoid
handle_signal(unsignedlongsig,siginfo_t*info,structk_sigaction*ka,
sigset_t*oldset,structpt_regs*regs)
{
//...省略(调试信号信息)

//省略(被中断的系统调用的相关处理)

//省略(IA32_EMULATION配置)

setup_rt_frame(sig,ka,info,oldset,regs);

if(!(ka->sa.sa_flags&SA_NODEFER)){
spin_lock_irq(¤t->sighand->siglock);
sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask);
sigaddset(¤t->blocked,sig);
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
}
}

i386架构的handle_signal()函数(文件位置:arch/i386/kernel/signal.c):

staticvoid
handle_signal(unsignedlongsig,siginfo_t*info,structk_sigaction*ka,
sigset_t*oldset,structpt_regs*regs)
{
//省略(被中断的系统调用的相关处理)

/*Setupthestackframe*/
if(ka->sa.sa_flags&SA_SIGINFO)
setup_rt_frame(sig,ka,info,oldset,regs);
else
setup_frame(sig,ka,oldset,regs);

if(!(ka->sa.sa_flags&SA_NODEFER)){
spin_lock_irq(¤t->sighand->siglock);
sigorsets(¤t->blocked,¤t->blocked,&ka->sa.sa_mask);
sigaddset(¤t->blocked,sig);
recalc_sigpending();
spin_unlock_irq(¤t->sighand->siglock);
}
}
staticvoidsetup_rt_frame(intsig,structk_sigaction*ka,siginfo_t*info,
sigset_t*set,structpt_regs*regs)
{
structrt_sigframe__user*frame;
struct_fpstate__user*fp=NULL;
interr=0;
structtask_struct*me=current;

if(used_math()){
fp=get_stack(ka,regs,sizeof(struct_fpstate));
frame=(void__user*)round_down((unsignedlong)fp-sizeof(structrt_sigframe),16)-8;

if(!access_ok(VERIFY_WRITE,fp,sizeof(struct_fpstate))){
gotogive_sigsegv;
}

if(save_i387(fp)< 0) 
            err |= -1; 
    } else {
        frame = get_stack(ka, regs, sizeof(struct rt_sigframe)) - 8;
    }

    if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame))) {
        goto give_sigsegv;
    }

    if (ka->sa.sa_flags&SA_SIGINFO){
err|=copy_siginfo_to_user(&frame->info,info);
if(err){
gotogive_sigsegv;
}
}

/*Createtheucontext.*/
err|=__put_user(0,&frame->uc.uc_flags);
err|=__put_user(0,&frame->uc.uc_link);
err|=__put_user(me->sas_ss_sp,&frame->uc.uc_stack.ss_sp);
err|=__put_user(sas_ss_flags(regs->rsp),
&frame->uc.uc_stack.ss_flags);
err|=__put_user(me->sas_ss_size,&frame->uc.uc_stack.ss_size);
err|=setup_sigcontext(&frame->uc.uc_mcontext,regs,set->sig[0],me);
err|=__put_user(fp,&frame->uc.uc_mcontext.fpstate);
if(sizeof(*set)==16){
__put_user(set->sig[0],&frame->uc.uc_sigmask.sig[0]);
__put_user(set->sig[1],&frame->uc.uc_sigmask.sig[1]);
}else{
err|=__copy_to_user(&frame->uc.uc_sigmask,set,sizeof(*set));
}

/*Setuptoreturnfromuserspace.Ifprovided,useastub
alreadyinuserspace.*/
/*x86-64shouldalwaysuseSA_RESTORER.*/
if(ka->sa.sa_flags&SA_RESTORER){
err|=__put_user(ka->sa.sa_restorer,&frame->pretcode);
}else{
/*coulduseavstubhere*/
gotogive_sigsegv;
}

if(err){
gotogive_sigsegv;
}

#ifdefDEBUG_SIG
printk("%doldrip%lxoldrsp%lxoldrax%lx
",current->pid,regs->rip,regs->rsp,regs->rax);
#endif

/*Setupregistersforsignalhandler*/
{
structexec_domain*ed=current_thread_info()->exec_domain;
if(unlikely(ed&&ed->signal_invmap&&sig< 32))
            sig = ed->signal_invmap[sig];
}
regs->rdi=sig;
/*Incasethesignalhandlerwasdeclaredwithoutprototypes*/
regs->rax=0;

/*ThisalsoworksfornonSA_SIGINFOhandlersbecausetheyexpectthe
nextargumentafterthesignalnumberonthestack.*/
regs->rsi=(unsignedlong)&frame->info;
regs->rdx=(unsignedlong)&frame->uc;
regs->rip=(unsignedlong)ka->sa.sa_handler;

regs->rsp=(unsignedlong)frame;

set_fs(USER_DS);
if(regs->eflags&TF_MASK){
if((current->ptrace&(PT_PTRACED|PT_DTRACE))==(PT_PTRACED|PT_DTRACE)){
ptrace_notify(SIGTRAP);
}else{
regs->eflags&=~TF_MASK;
}
}

#ifdefDEBUG_SIG
printk("SIGdeliver(%s:%d):sp=%ppc=%pra=%p
",
current->comm,current->pid,frame,regs->rip,frame->pretcode);
#endif

return;

give_sigsegv:
force_sigsegv(sig,current);
}

审核编辑:汤梓红
声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 内核
    +关注

    关注

    3

    文章

    1309

    浏览量

    39846
  • Linux
    +关注

    关注

    87

    文章

    10990

    浏览量

    206735
  • 信号
    +关注

    关注

    11

    文章

    2639

    浏览量

    75388
  • 函数
    +关注

    关注

    3

    文章

    3868

    浏览量

    61309

原文标题:Linux内核-信号的传递过程

文章出处:【微信号:嵌入式ARM和Linux,微信公众号:嵌入式ARM和Linux】欢迎添加关注!文章转载请注明出处。

收藏 人收藏

    评论

    相关推荐

    linux内核信号是如何处理的?看完全懂了……

    本文简单介绍下Linux信号处理机制,为介绍二进制翻译下信号处理机制做一个铺垫。 本文主要参考书目《Linux内核源代码情景分析》《独辟蹊径
    的头像 发表于 11-16 05:11 1.4w次阅读
    <b class='flag-5'>linux</b><b class='flag-5'>内核</b><b class='flag-5'>信号</b>是如何处理的?看完全懂了……

    Linux内核的编译主要过程

    Linux内核的编译主要过程: 配置、编译、安装 。
    发表于 08-08 16:02 508次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>的编译主要<b class='flag-5'>过程</b>

    Linux内核中信号详解

    信号和多线程程序 4 与信号相关的数据结构 4.2.1 x86/Linux2.6.11的定义 4.2.2 x86-64/Linux2.6.11的定义 4.2.3 x86-64/
    的头像 发表于 01-13 09:40 762次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b><b class='flag-5'>中信号</b>详解

    一文详解Linux内核-信号的产生过程

    许多内核函数产生信号:它们完成信号处理的第一阶段,也就是更新一个或多个进程描述符。
    的头像 发表于 01-13 13:48 670次阅读

    Linux内核地址映射模型与Linux内核高端内存详解

    Linux 操作系统和驱动程序运行在内核空间,应用程序运行在用户空间,两者不能简单地使用指针传递数据,因为Linux使用的虚拟内存机制,用户空间的数据可能被换出,当
    发表于 05-08 10:33 3329次阅读
    <b class='flag-5'>Linux</b><b class='flag-5'>内核</b>地址映射模型与<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>高端内存详解

    Linux内核启动过程和Bootloader(总述)

    供给 Linux 内核Linux 内核在启动过程中会根据该处理器类型调用相应的初始化程序(4)设置 L
    发表于 08-18 17:35

    Linux内核自解压过程

    Linux内核的启动流程。有兴趣的用户可以参考其他书籍或资料进行深入了解。  嵌入式linux内核的启动全过程主要分为三个阶段。第一阶段为
    发表于 12-29 07:35

    linux内核启动内核解压过程分析

    linux启动时内核解压过程分析,一份不错的文档,深入了解内核必备
    发表于 03-09 13:39 1次下载

    Linux内核源码分析--内核启动命令行的传递过程

    内核的启动参数其实不仅仅包含在了cmdline中,cmdline不过是bootloader传递内核的信息中的一部分。bootloader和内核的通信方式根据构架的不同而异。
    发表于 05-05 15:28 1560次阅读

    你了解u-boot与linux内核间的参数传递过程

    U-boot会给Linux Kernel传递很多参数,如:串口,RAM,videofb、MAC地址等。而Linux kernel也会读取和处理这些参数。两者之间通过struct tag来传递
    发表于 05-13 10:00 1484次阅读
    你了解u-boot与<b class='flag-5'>linux</b><b class='flag-5'>内核</b>间的参数<b class='flag-5'>传递</b><b class='flag-5'>过程</b>?

    BootLoader与Linux内核的参数传递

    Linux 内核是怎样接收BootLoader传递过来的内核参数,zImage 启动过程如下图所示。(图有时间再画)在文件 arch/arm
    发表于 04-02 14:31 279次阅读

    LINUX内核信号量设计与实现

    控制路径可以睡眠。我们从 LINUX内核信号量最直观的设计/实现出发,通过一步步改进,揭示在x86平台上完整的信号量设计/实现,然后探讨在不同平台上通用的
    发表于 01-14 16:55 18次下载

    LINUX内核信号量设计与实现

    控制路径可以睡眠。我们从 LINUX内核信号量最直观的设计/实现出发,通过一步步改进,揭示在x86平台上完整的信号量设计/实现,然后探讨在不同平台上通用的
    发表于 01-14 16:55 5次下载

    BootLoader与Linux内核的参数传递详细资料说明

    不同的体系结构,如 ARM, Powerpc,X86,MIPS等。本文着重介绍 Bootloader与内核之间参数传递这一基本功能。本文的硬件平台是基于AT91RM9200处理器系统,软件平台是 Linux-2.6.19,2
    发表于 03-16 10:39 13次下载
    BootLoader与<b class='flag-5'>Linux</b><b class='flag-5'>内核</b>的参数<b class='flag-5'>传递</b>详细资料说明

    Linux内核模块参数传递与sysfs文件系统

    Linux应用开发中,为使应用程序更加灵活地执行用户的预期功能,我们有时候会通过命令行传递一些参数到main函数中,使得代码逻辑可以依据参数执行不同的任务。同样,Linux内核也提供了
    发表于 06-07 16:23 1353次阅读