# kill-L1)sighup 2)sigint 3)sigquit 4)sigill 5)SIG trap 6)SIG abrt 7)SIG bus 8)sigfpe 9)sigkill 10)sigusr 11)sigsegv 12)sigusr 213)sigpipe 14)sigalrm 15)sigterm 16)sigstkflt 18)SIG cont 19)sigstop 20)sigt TP 21)sigttou 23)sigurg 24)sigxcpu 25)通过man7signal可以查看每个信号的具体含义以及对应的处理方法。
信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释信号值操作注释
执行默认操作。Linux为每个信号指定了默认操作,比如上面列表中的$ Term,这意味着终止进程。Core就是CoreDump,即在进程终止后,通过CoreDump将当前进程的运行状态保存在一个文件中,方便程序员事后分析问题。捕捉信号。我们可以为信号定义一个信号处理函数。当信号出现时,我们执行相应的信号处理功能。忽略信号。当我们不想处理一些信号的时候,我们可以忽略它们,什么都不做。有两个信号是应用进程无法捕捉和忽略的,分别是SIGKILL和SEGSTOP,用于随时中断或结束一个进程。
两个注册的处理程序都用于处理当前任务,比如调度、停止等。但两者有很多不同,不同的用途导致不同的运行逻辑,最终在代码实现上体现出不同的设计特点。主要区别是:
中断和信号都可能来源于硬件和软件,但中断处理函数注册在内核中并在内核中运行,而信号处理函数注册在用户模式中。内核收到信号后,会根据当前任务task_struct结构中与信号相关的数据结构找到相应的处理函数,最终在用户态处理中断,信号作用于整个内核。
当前任务(进程)。即信号影响的往往是一个进程,而中断处理如果出现问题则会导致整个Linux内核的崩溃更多Linux内核视频教程文本资料免费领取后台私信【内核大礼包】自行获取。
有些时候我们希望能够让信号运行一些特殊功能,所以有了自定义的信号处理函数。注册API主要有signal()和sigaction()两个,其中sigaction()比较推荐使用。
typedefvoid(*sighandler_t)(int);sighandler_tsignal(intsignum,sighandler_thandler);intsigaction(intsignum,conststructsigaction*act,structsigaction*oldact);其主要区别在于sigaction()对于信号signum会绑定对应的结构体sigaction而不仅仅是一个处理函数sighandler_t。这样做的好处是可以更精细的控制信号处理,通过不同参数实现不同的效果。例如sa_flags可以设置如
SA_ONESHOT:信号处理函数仅作用一次,之后启用默认行为SA_NOMASK:该信号处理函数执行过程中允许被其他信号或者相同信号中断,即不屏蔽SA_INTERRUPT:该信号处理函数若执行过程中被中断,则不会再调度回该函数继续执行,而是直接返回-EINTR,将执行逻辑交还给调用方SA_RESTART:与SA_INTERRUPT相反,会自动重启该函数sa_restorer保存的是sa_handler执行完毕之后,马上要执行的函数,即下一个函数地址的位置。
structsigaction{__sighandler_tsa_handler;unsignedlongsa_flags;__sigrestore_tsa_restorer;sigset_tsa_mask;/*masklastforextensibility*/};sigaction()也是glibc封装的函数,最终系统调用为rt_sigaction()。该函数首先将用户态的structsigaction结构拷贝为内核态的k_sigaction,然后调用do_sigaction()设置对应的信号处理动作。
SYSCALL_DEFINE4(rt_sigaction,int,sig,conststructsigaction__user*,act,structsigaction__user*,oact,size_t,sigsetsize){structk_sigactionnew_sa,old_sa;intret=-EINVAL;......if(act){if(copy_from_user(&new_sa.sa,act,sizeof(new_sa.sa)))return-EFAULT;}ret=do_sigaction(sig,act?&new_sa:NULL,oact?&old_sa:NULL);if(!ret&&oact){if(copy_to_user(oact,&old_sa.sa,sizeof(old_sa.sa)))return-EFAULT;}out:returnret;}do_sigaction()会将用户层传来的信号处理函数赋值给当前任务task_structcurrrent对应的sighand->action[]数组中sig信号对应的位置,以用于之后调用。
intdo_sigaction(intsig,structk_sigaction*act,structk_sigaction*oact){structtask_struct*p=current,*t;structk_sigaction*k;sigset_tmask;......k=&p->sighand->action[sig-1];spin_lock_irq(&p->sighand->siglock);if(oact)*oact=*k;if(act){sigdelsetmask(&act->sa.sa_mask,sigmask(SIGKILL)|sigmask(SIGSTOP));*k=*act;......}spin_unlock_irq(&p->sighand->siglock);return0;}信号发送来源广泛,有可能来自于用户态,有可能来自于硬件,也有可能来自于内核。
有时候,我们在终端输入某些组合键的时候会给进程发送信号,例如,Ctrl+C产生SIGINT信号,Ctrl+Z产生SIGTSTP信号。再比如,kill-9pid可以发送信号给一个进程,杀死它。有的时候,硬件异常也会产生信号。比如,执行了除以0的指令,CPU就会产生异常,然后把SIGFPE信号发送给进程。再如,进程访问了非法内存,内存管理模块就会产生异常,然后把信号SIGSEGV发送给进程。有时候,内核在某些情况下,也会给进程发送信号。例如,向读端已关闭的管道写数据时产生SIGPIPE信号,当子进程退出时,我们要给父进程发送SIG_CHLD信号等。不论通过kill或者sigqueue系统调用还是通过tkill或者tgkill发送指定线程的信号,其最终调用的均是do_send_sig_info()函数,其调用链如下所示
kill()->kill_something_info()->kill_pid_info()->group_send_sig_info()->do_send_sig_info()tkill()->do_tkill()->do_send_specific()->do_send_sig_info()tgkill()->do_tkill()->do_send_specific()->do_send_sig_info()rt_sigqueueinfo()->do_rt_sigqueueinfo()->kill_proc_info()->kill_pid_info()->group_send_sig_info()->do_send_sig_info()do_send_sig_info()会调用send_signal(),进而调用__send_signal()。这里代码比较复杂,主要逻辑如下
根据发送信号的类型判断是共享信号还是线程独享信号,由此赋值pending。如果是kill发送的,也就是发送给整个进程的,就应该发送给t->signal->shared_pending,这里面是整个进程所有线程共享的信号;如果是tkill发送的,也就是发给某个线程的,就应该发给t->pending,这里面是这个线程的task_struct独享的。调用legacy_queue()判断是否为可靠信号,不可靠则直接退出调用__sigqueue_alloc()分配一个structsigqueue对象,然后通过list_add_tail挂在structsigpending里面的链表上。调用complete_signal()分配线程处理该信号intdo_send_sig_info(intsig,structkernel_siginfo*info,structtask_struct*p,enumpid_typetype){......ret=send_signal(sig,info,p,type);......}staticintsend_signal(intsig,structkernel_siginfo*info,structtask_struct*t,enumpid_typetype){......return__send_signal(sig,info,t,type,from_ancestor_ns);}staticint__send_signal(intsig,structkernel_siginfo*info,structtask_struct*t,enumpid_typetype,intfrom_ancestor_ns){structsigpending*pending;structsigqueue*q;......pending=(type!=PIDTYPE_PID)?&t->signal->shared_pending:&t->pending;......if(legacy_queue(pending,sig))gotoret;....../**Real-timesignalsmustbequeuedifsentbysigqueue,or*someotherreal-timemechanism.Itisimplementation*definedwhetherkill()doesso.Weattempttodoso,on*theprincipleofleastsurprise,butsincekillisnot*allowedtofailwithEAGAINwhenlowonmemorywejust*makesureatleastonesignalgetsdeliveredanddon't*passontheinfostruct.*/if(sigsi_code>=0);elseoverride_rlimit=0;q=__sigqueue_alloc(sig,t,GFP_ATOMIC,override_rlimit);if(q){list_add_tail(&q->list,&pending->list);switch((unsignedlong)info){case(unsignedlong)SEND_SIG_NOINFO:clear_siginfo(&q->info);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=from_kuid_munged(current_user_ns(),current_uid());break;case(unsignedlong)SEND_SIG_PRIV:clear_siginfo(&q->info);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);}......out_set:signalfd_notify(t,sig);sigaddset(&pending->signal,sig);......complete_signal(sig,t,type);ret:trace_signal_generate(sig,info,t,type!=PIDTYPE_PID,result);returnret;} legacy_queue()中主要是判断是否为可靠信号,判断的依据是当信号小于SIGRTMIN也即32的时候,如果我们发现这个信号已经在集合里面了,就直接退出。这里之所以前32位信号称之为不可靠信号其实是历史遗留问题,早期UNIX系统只定义了32种信号,而这些经过检验被定义为不可靠信号,主要指的是进程可能对信号做出错误的反应以及信号可能丢失:UNIX系统每次信号处理完需要重新安装信号,因此容易出现各种错误。linux也支持不可靠信号,但是对不可靠信号机制做出了改进:在调用完信号处理函数后,不必重新调用该信号的安装函数(信号安装函数是在可靠机制上是实现的)。因此,linux下的不可靠信号问题主要指的是信号可能丢失。
这里之所以会出现信号丢失,是因为这些信号可能会频繁快速出现。这样信号能够处理多少,和信号处理函数什么时候被调用,信号多大频率被发送,都有关系,而信号处理函数的调用时间也是不确定的,因此这种信号称之为不可靠信号。与之相对的,其他信号称之为可靠信号,支持排队执行。
staticinlineintlegacy_queue(structsigpending*signals,intsig){return(sigsignal,sig);}#defineSIGRTMIN32#defineSIGRTMAX_NSIG#define_NSIG64 对于可靠信号我们通过__sigqueue_alloc()分配sigqueue对象,并挂载在sigpending中的链表上,最终调用complete_signal()找一个线程处理。其主要逻辑为:
首先找是否有可唤醒的线程来执行,如果是主线程或者仅有一个线程,则直接从链表主队并分配如果没找到可唤醒的线程,则查看当前是否有不需要唤醒的线程可以执行如果没找到并且该信号为非常重要的信号如SIGKILL,则强行关闭当前线程调用signal_wake_up()唤醒线程staticvoidcomplete_signal(intsig,structtask_struct*p,enumpid_typetype){structsignal_struct*signal=p->signal;structtask_struct*t;/**Nowfindathreadwecanwakeuptotakethesignaloffthequeue.**Ifthemainthreadwantsthesignal,itgetsfirstcrack.*Probablytheleastsurprisingtotheaveragebear.*/if(wants_signal(sig,p))t=p;elseif((type==PIDTYPE_PID)||thread_group_empty(p))/**Thereisjustonethreadanditdoesnotneedtobewoken.*Itwilldequeueunblockedsignalsbeforeitrunsagain.*/return;else{/**Otherwisetrytofindasuitablethread.*/t=signal->curr_target;while(!wants_signal(sig,t)){t=next_thread(t);if(t==signal->curr_target)/**Nothreadneedstobewoken.*Anyeligiblethreadswillsee*thesignalinthequeuesoon.*/return;}signal->curr_target=t;}/**Foundakillablethread.Ifthesignalwillbefatal,*thenstarttakingthewholegroupdownimmediately.*/if(sig_fatal(p,sig)&&!(signal->flags&SIGNAL_GROUP_EXIT)&&!sigismember(&t->real_blocked,sig)&&(sig==SIGKILL||!p->ptrace)){/**Thissignalwillbefataltothewholegroup.*/if(!sig_kernel_coredump(sig)){/**Startagroupexitandwakeeverybodyup.*Thiswaywedon'thaveotherthreads*runninganddoingthingsafteraslower*threadhasthefatalsignalpending.*/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;}}/**Thesignalisalreadyintheshared-pendingqueue.*Tellthechosenthreadtowakeupanddequeueit.*/signal_wake_up(t,sig==SIGKILL);return;}signal_wake_up()函数主要逻辑为:
设置TIF_SIGPENDING标记位尝试唤醒该线程/进程信号处理的调度和任务调度类似,均是采用标记位的方式进行。当信号来的时候,内核并不直接处理这个信号,而是设置一个标识位TIF_SIGPENDING来表示已经有信号等待处理。同样等待系统调用结束,或者中断处理结束,从内核态返回用户态的时候再进行信号的处理。
进程/线程的唤醒和任务调度一样最终会调用try_to_wake_up(),具体逻辑就不重复分析了。如果wake_up_state返回0,说明进程或者线程已经是TASK_RUNNING状态了,如果它在另外一个CPU上运行,则调用kick_process发送一个处理器间中断,强制那个进程或者线程重新调度,重新调度完毕后,会返回用户态运行。
staticinlinevoidsignal_wake_up(structtask_struct*t,boolresume){signal_wake_up_state(t,resume?TASK_WAKEKILL:0);}voidsignal_wake_up_state(structtask_struct*t,unsignedintstate){set_tsk_thread_flag(t,TIF_SIGPENDING);/**TASK_WAKEKILLalsomeanswakeitupinthestopped/traced/killable*case.Wedon'tcheckt->stateherebecausethereisaracewithit*executinganotherprocessorandjustnowenteringstoppedstate.*Byusingwake_up_state,weensuretheprocesswillwakeupand*handleitsdeathsignal.*/if(!wake_up_state(t,state|TASK_INTERRUPTIBLE))kick_process(t);}这里我们以一个从tap网卡中读取数据的例子来分析信号的处理逻辑。这部分涉及到了系统调用、任务调度、中断等知识,对前面的文章也算是一个回顾。从网卡读取数据会通过系统调用进入内核,之后通过函数调用表找到对应的函数执行。在读的过程中,如果没有数据处理则会调用schedule()函数主动让出CPU进入休眠状态并等待再次唤醒。
tap_do_read()主要逻辑为:
把当前进程或者线程的状态设置为TASK_INTERRUPTIBLE,这样才能使这个系统调用可以被中断。可以被中断的系统调用往往是比较慢的调用,并且会因为数据不就绪而通过schedule()让出CPU进入等待状态。在发送信号的时候,我们除了设置这个进程和线程的_TIF_SIGPENDING标识位之外,还试图唤醒这个进程或者线程,也就是将它从等待状态中设置为TASK_RUNNING。当这个进程或者线程再次运行的时候,会从schedule()函数中返回,然后再次进入while循环。由于这个进程或者线程是由信号唤醒的而不是因为数据来了而唤醒的,因而是读不到数据的,但是在signal_pending()函数中,我们检测到了_TIF_SIGPENDING标识位,这说明系统调用没有真的做完,于是返回一个错误ERESTARTSYS,然后带着这个错误从系统调用返回。如果没有信号,则继续调用schedule()让出CPUstaticssize_ttap_do_read(structtap_queue*q,structiov_iter*to,intnoblock,structsk_buff*skb){......while(1){if(!noblock)prepare_to_wait(sk_sleep(&q->sk),&wait,TASK_INTERRUPTIBLE);/*Readframesfromthequeue*/skb=skb_array_consume(&q->skb_array);if(skb)break;if(noblock){ret=-EAGAIN;break;}if(signal_pending(current)){ret=-ERESTARTSYS;break;}/*Nothingtoread,let'ssleep*/schedule();}......}schedule()会在系统调用返回或者中断返回的时刻调用exit_to_usermode_loop(),在任务调度中标记位为_TIF_NEED_RESCHED,而对于信号来说是_TIF_SIGPENDING。
staticvoidexit_to_usermode_loop(structpt_regs*regs,u32cached_flags){while(true){......if(cached_flags&_TIF_NEED_RESCHED)schedule();....../*dealwithpendingsignaldelivery*/if(cached_flags&_TIF_SIGPENDING)do_signal(regs);......if(!(cached_flags&EXIT_TO_USERMODE_LOOP_FLAGS))break;}}do_signal()函数会调用handle_signal(),这里主要存在一个问题使得逻辑变得较为复杂:信号处理函数定义于用户态,而调度过程位于内核态。
/**Notethat'init'isaspecialprocess:itdoesn'tgetsignalsitdoesn't*wanttohandle.ThusyoucannotkillinitevenwithaSIGKILLevenby*mistake.*/voiddo_signal(structpt_regs*regs){structksignalksig;if(get_signal(&ksig)){/*Whee!Actuallydeliverthesignal.*/handle_signal(&ksig,regs);return;}/*Didwecomefromasystemcall?*/if(syscall_get_nr(current,regs)>=0){/*Restartthesystemcall-nohandlerspresent*/switch(syscall_get_error(current,regs)){case-ERESTARTNOHAND:case-ERESTARTSYS:case-ERESTARTNOINTR:regs->ax=regs->orig_ax;regs->ip-=2;break;case-ERESTART_RESTARTBLOCK:regs->ax=get_nr_restart_syscall(regs);regs->ip-=2;break;}}/**Ifthere'snosignaltodeliver,wejustputthesavedsigmask*back.*/restore_saved_sigmask();}handle_signal()会判断当前是否从系统调用调度而来,当发现错误码为ERESTARTSYS的时候就知道这是从一个没有调用完的系统调用返回的,设置系统错误码为EINTR。由于此处不会直接返回任务调度前记录的用户态状态,而是进入注册好的信号处理函数,因此需要调用setup_rt_frame()构建新的寄存器结构体pt_regs。
staticvoidhandle_signal(structksignal*ksig,structpt_regs*regs){boolstepping,failed;....../*Arewefromasystemcall?*/if(syscall_get_nr(current,regs)>=0){/*Ifso,checksystemcallrestarting..*/switch(syscall_get_error(current,regs)){case-ERESTART_RESTARTBLOCK:case-ERESTARTNOHAND:regs->ax=-EINTR;break;case-ERESTARTSYS:if(!(ksig->ka.sa.sa_flags&SA_RESTART)){regs->ax=-EINTR;break;}/*fallthrough*/case-ERESTARTNOINTR:regs->ax=regs->orig_ax;regs->ip-=2;break;}}......failed=(setup_rt_frame(ksig,regs)<0);......signalsetup_rt_frame()主要调用__setup_rt_frame(),主要逻辑为:
调用get_sigframe()得到regs中的sp寄存器值,即原进程用户态的栈顶指针,将sp减去sizeof(structrt_sigframe)从而把该新建栈帧压入栈调用put_user_ex(),将sa_restorer按照函数栈的规则放到了frame->pretcode里面。函数栈里面包含了函数执行完跳回去的地址,当sa_handler执行完之后,弹出的函数栈是frame,也就应该跳到sa_restorer的地址调用setup_sigcontext()里面,将原来的pt_regs保存在了frame中的uc_mcontext里填充regs,将regs->ip设置为自定义的信号处理函数sa_handler,将栈顶regs->sp设置为新栈帧frame地址staticintsetup_rt_frame(structksignal*ksig,structpt_regs*regs){......return__setup_rt_frame(ksig->sig,ksig,set,regs);......}staticint__setup_rt_frame(intsig,structksignal*ksig,sigset_t*set,structpt_regs*regs){structrt_sigframe__user*frame;void__user*fp=NULL;interr=0;frame=get_sigframe(&ksig->ka,regs,sizeof(structrt_sigframe),&fp);......put_user_try{....../*Setuptoreturnfromuserspace.Ifprovided,useastubalreadyinuserspace.*//*x86-64shouldalwaysuseSA_RESTORER.*/if(ksig->ka.sa.sa_flags&SA_RESTORER){put_user_ex(ksig->ka.sa.sa_restorer,&frame->pretcode);}else{/*coulduseavstubhere*/err|=-EFAULT;}}put_user_catch(err);err|=setup_sigcontext(&frame->uc.uc_mcontext,fp,regs,set->sig[0]);err|=__copy_to_user(&frame->uc.uc_sigmask,set,sizeof(*set));if(err)return-EFAULT;/*Setupregistersforsignalhandler*/regs->di=sig;/*Incasethesignalhandlerwasdeclaredwithoutprototypes*/regs->ax=0;/*ThisalsoworksfornonSA_SIGINFOhandlersbecausetheyexpectthenextargumentafterthesignalnumberonthestack.*/regs->si=(unsignedlong)&frame->info;regs->dx=(unsignedlong)&frame->uc;regs->ip=(unsignedlong)ksig->ka.sa.sa_handler;regs->sp=(unsignedlong)frame;/**SetuptheCSandSSregisterstorunsignalhandlersin*64-bitmode,evenifthehandlerhappenstobeinterrupting*32-bitor16-bitcode.**SSissubtle.In64-bitmode,wedon'tneedanyparticular*SSdescriptor,butwedoneedSStobevalid.It'spossible*thattheoldSSisentirelybogus--thiscanhappenifthe*signalwe'retryingtodeliveris#GPor#SScausedbyabad*SSvalue.Wealsohaveacompatbilityissuehere:DOSEMU*reliesonthecontentsoftheSSregisterindicatingthe*SSvalueatthetimeofthesignal,eventhoughthatcodein*DOSEMUpredatessigreturn'sabilitytorestoreSS.(DOSEMU*avoidsrelyingonsigreturntorestoreSS;insteadituses*atrampoline.)Sowedoourbest:iftheoldSSwasvalid,*wekeepit.Otherwisewereplaceit.*/regs->cs=__USER_CS;if(unlikely(regs->ss!=__USER_DS))force_valid_ss(regs);return0;}sa_restorer在__libc_sigaction()函数中被赋值为restore_rt,实际上调用函数调用__NR_rt_sigreturn()
RESTORE(restore_rt,__NR_rt_sigreturn)#defineRESTORE(name,syscall)RESTORE2(name,syscall)#defineRESTORE2(name,syscall)\asm\(\".LSTART_"#name":\n"\".type__"#name",@function\n"\"__"#name":\n"\"movq#34;#syscall",%rax\n"\"syscall\n"\......__NR_rt_sigreturn()对应的内核函数为sys_rt_sigreturn(),这里会调用restore_sigframe()将pt_regs恢复成原进程的栈帧状态,从而继续执行函数调用后续的内容。
asmlinkageintsys_rt_sigreturn(structpt_regs*regs){structrt_sigframe__user*frame;/*Alwaysmakeanypendingrestartedsystemcallsreturn-EINTR*/current->restart_block.fn=do_no_restart_syscall;/**Sincewestackedthesignalona64-bitboundary,*then'sp'shouldbewordalignedhere.Ifit's*not,thentheuseristryingtomesswithus.*/if(regs->ARM_sp&7)gotobadframe;frame=(structrt_sigframe__user*)regs->ARM_sp;if(!access_ok(frame,sizeof(*frame)))gotobadframe;if(restore_sigframe(regs,&frame->sig))gotobadframe;if(restore_altstack(&frame->sig.uc.uc_stack))gotobadframe;returnregs->ARM_r0;badframe:force_sig(SIGSEGV,current);return0;}信号的发送与处理是一个复杂的过程,这里来总结一下。
假设我们有一个进程A会从tap网卡中读取数据,main函数里面调用系统调用通过中断陷入内核。按照系统调用的原理,将用户态栈的信息保存在pt_regs里面,也即记住原来用户态是运行到了lineA的地方。在内核中执行系统调用读取数据。当发现没有什么数据可读取的时候进入睡眠状态,并且调用schedule()让出CPU。将进程状态设置为可中断的睡眠状态TASK_INTERRUPTIBLE,也即如果有信号来的话是可以唤醒它的。其他的进程或者shell通过调用kill()、tkill()、tgkill()、rt_sigqueueinfo()发送信号。四个发送信号的函数,在内核中最终都是调用do_send_sig_info()。do_send_sig_info()调用send_signal()给进程A发送一个信号,其实就是找到进程A的task_struct,不可靠信号加入信号集合,可靠信号加入信号链表。do_send_sig_info()调用signal_wake_up()唤醒进程A。进程A重新进入运行状态TASK_RUNNING,接着schedule()运行。进程A被唤醒后检查是否有信号到来,如果没有,重新循环到一开始,尝试再次读取数据,如果还是没有数据,再次进入TASK_INTERRUPTIBLE,即可中断的睡眠状态。当发现有信号到来的时候,就返回当前正在执行的系统调用,并返回一个错误表示系统调用被中断了。系统调用返回的时候,会调用exit_to_usermode_loop(),这是一个处理信号的时机。调用do_signal()开始处理信号。根据信号得到信号处理函数sa_handler,然后修改pt_regs中的用户态栈的信息让pt_regs指向sa_handler,同时修改用户态的栈,插入一个栈帧sa_restorer,里面保存了原来的指向lineA的pt_regs,并且设置让sa_handler运行完毕后跳到sa_restorer运行。返回用户态,由于pt_regs已经设置为sa_handler,则返回用户态执行sa_handler。sa_handler执行完毕后,信号处理函数就执行完了,接着会跳到sa_restorer运行。sa_restorer会调用系统调用rt_sigreturn再次进入内核。在内核中,rt_sigreturn恢复原来的pt_regs,重新指向lineA。从rt_sigreturn返回用户态,还是调用exit_to_usermode_loop()。这次因为pt_regs已经指向lineA了,于是就到了进程A中接着系统调用之后运行,当然这个系统调用返回的是它被中断了没有执行完的错误。