linux內核中的信號機制--信號處理
CPU architecture:ARM920T
本文引用地址:http://www.104case.com/article/201611/320006.htmAuthor:ce123(http://blog.csdn.net/ce123)
當進程被調度時,會調用do_notify_resume()來處理信號隊列中的信號。信號處理主要就是調用sighand_struct結構中對應的信號處理函數。do_notify_resume()(arch/arm/kernel/signal.c)函數的定義如下:
- asmlinkagevoid
- do_notify_resume(structpt_regs*regs,unsignedintthread_flags,intsyscall)
- {
- if(thread_flags&_TIF_SIGPENDING)
- do_signal(¤t->blocked,regs,syscall);
- }
- /*
- *Notethatinitisaspecialprocess:itdoesntgetsignalsitdoesnt
- *wanttohandle.ThusyoucannotkillinitevenwithaSIGKILLevenby
- *mistake.
- *
- *Notethatwegothroughthesignalstwice:oncetocheckthesignalsthat
- *thekernelcanhandle,andthenwebuildalltheuser-levelsignalhandling
- *stack-framesinonegoafterthat.
- */
- staticintdo_signal(sigset_t*oldset,structpt_regs*regs,intsyscall)
- {
- structk_sigactionka;
- siginfo_tinfo;
- intsignr;
- /*
- *Wewantthecommoncasetogofast,which
- *iswhywemayincertaincasesgetherefrom
- *kernelmode.Justreturnwithoutdoinganything
- *ifso.
- */
- if(!user_mode(regs))//regs保存的是進入內核態之前的寄存器現場,必須為用戶模式,否則直接返回
- return0;
- if(try_to_freeze())
- gotono_signal;
- if(current->ptrace&PT_SINGLESTEP)
- ptrace_cancel_bpt(current);//和調試相關,我們在后面的文章中會具體分析
- signr=get_signal_to_deliver(&info,&ka,regs,NULL);//取出等待處理的信號
- if(signr>0){
- handle_signal(signr,&ka,&info,oldset,regs,syscall);//處理信號
- if(current->ptrace&PT_SINGLESTEP)
- ptrace_set_bpt(current);
- return1;
- }
- no_signal:
- /*
- *Nosignaltodelivertotheprocess-restartthesyscall.
- */
- if(syscall){
- if(regs->ARM_r0==-ERESTART_RESTARTBLOCK){
- if(thumb_mode(regs)){
- regs->ARM_r7=__NR_restart_syscall;
- regs->ARM_pc-=2;
- }else{
- u32__user*usp;
- regs->ARM_sp-=12;
- usp=(u32__user*)regs->ARM_sp;
- put_user(regs->ARM_pc,&usp[0]);
- /*swi__NR_restart_syscall*/
- put_user(0xef000000|__NR_restart_syscall,&usp[1]);
- /*ldrpc,[sp],#12*/
- put_user(0xe49df00c,&usp[2]);
- flush_icache_range((unsignedlong)usp,
- (unsignedlong)(usp+3));
- regs->ARM_pc=regs->ARM_sp+4;
- }
- }
- if(regs->ARM_r0==-ERESTARTNOHAND||
- regs->ARM_r0==-ERESTARTSYS||
- regs->ARM_r0==-ERESTARTNOINTR){
- restart_syscall(regs);
- }
- }
- if(current->ptrace&PT_SINGLESTEP)
- ptrace_set_bpt(current);
- return0;
- }
執行do_signal()函數時,進程一定處于內核空間,通常進程只有通過中斷或者系統調用才能進入內核空間,regs保存著系統調用或者中斷時的現場。user_mode()根據regs中的cpsr寄存器來判斷是中斷現場環境還是用戶態環境。如果不是用戶態環境,就不對信號進行任何處理,直接從do_signal()函數返回。
如果user_mode()函數發現regs的現場是內核態,那就意味著這不是一次系統調用的返回,也不是一次普通的中斷返回,而是一次嵌套中斷返回(或者在系統調用過程中發生了中斷)。此時大概的執行路徑應該是這樣的:假設進場現在運行在用戶態,此時發生一次中斷,進場進入內核態(此時user_mode(regs)返回1,意味著中斷現場是用戶態。),此后在中斷返回前,發生了一個更高優先級的中斷,于是CPU開始執行高優先級的處理函數(此時user_mode(regs)返回0,意味著中斷現場是在內核態)。當高優先級中斷處理結束后,在它返回時,是不應該處理信號的,因為信號的優先級比中斷的優先級低。在這種情況下,對信號的處理將會延遲到低優先級中斷處理結束之后。相對于Windows內核來說,盡管linux內核中沒有一組顯式的操作函數來實現這一系列的優先級管理方案,但是linux內核和Windows內核都使用了同樣的機制,優先級關系為:高優先級中斷->低優先級中斷->軟中斷(類似Windows內中的DPC)->信號(類似Windows內核中的APC)->進程運行。
如果user_mode(regs)返回1,接下來會執行(中間略去一下和本文關系不大的代碼)get_signal_to_deliver(),這個函數從當前進程的信號隊列(保存Private Signal Queue和Shared Signal Queue)取出等待處理的信號(調用dequeue_signal()函數),然后根據信號定位到對應的signal_struct結構,如果信號的處理函數sa_handler為SIG_IGN,就忽略該信號,繼續取下一個信號;如果信號的處理函數sa_handler為SIG_DFL,意味著按照信號默認的處理方式對待就可以了(例如直接調用do_coredump()等)。
如果get_signal_to_deliver()函數返回值大于0,說明這個信號的處理函數是在用戶態空間(通過signal()和sigaction()等函數設置的自定義信號處理函數。),將調用handle_signal()函數進行處理。handle_signal()函數的定義如下:
- /*
- *OK,wereinvokingahandler
- */
- staticvoid
- handle_signal(unsignedlongsig,structk_sigaction*ka,
- siginfo_t*info,sigset_t*oldset,
- structpt_regs*regs,intsyscall)
- {
- structthread_info*thread=current_thread_info();
- structtask_struct*tsk=current;
- intusig=sig;
- intret;
- /*
- *Ifwewerefromasystemcall,checkforsystemcallrestarting...
- */
- if(syscall){
- switch(regs->ARM_r0){
- case-ERESTART_RESTARTBLOCK:
- case-ERESTARTNOHAND:
- regs->ARM_r0=-EINTR;
- break;
- case-ERESTARTSYS:
- if(!(ka->sa.sa_flags&SA_RESTART)){
- regs->ARM_r0=-EINTR;
- break;
- }
- /*fallthrough*/
- case-ERESTARTNOINTR:
- restart_syscall(regs);
- }
- }
- /*
- *translatethesignal
- */
- if(usig<32&&thread->exec_domain&&thread->exec_domain->signal_invmap)
- usig=thread->exec_domain->signal_invmap[usig];
- /*
- *Setupthestackframe//設置棧幀
- */
- if(ka->sa.sa_flags&SA_SIGINFO)
- ret=setup_rt_frame(usig,ka,info,oldset,regs);
- else
- ret=setup_frame(usig,ka,oldset,regs);
- /*
- *Checkthattheresultingregistersareactuallysane.
- */
- ret|=!valid_user_regs(regs);
- /*
- *Blockthesignalifwewereunsuccessful.
- */
- if(ret!=0){
- spin_lock_irq(&tsk->sighand->siglock);
- sigorsets(&tsk->blocked,&tsk->blocked,
- &ka->sa.sa_mask);
- if(!(ka->sa.sa_flags&SA_NODEFER))
- sigaddset(&tsk->blocked,sig);
- recalc_sigpending();
- spin_unlock_irq(&tsk->sighand->siglock);
- }
- if(ret==0)
- return;
- force_sigsegv(sig,tsk);
- }
1.臨時的用戶態堆棧在哪里呢?這個很好解決,因為可以直接使用進程現有的用戶態堆棧,這里要保證的是:使用結束后這個堆棧必須和使用前是一模一樣的。
2.臨時堆棧解決后,需要確定的是通過什么方法來保證返回到用戶態后,進程執行的是信號的處理函數。我們知道在進入內核態時,內核態堆棧中保存了一個中斷現場,也就是一個pt_regs結構,中斷返回地址就保存在pt_regts中的pc中,因此我們這里只要把當前進程的pt_regs中pc設置為sa_handler,然后返回到用戶態就開始從sa_handler處開始執行了。
- unsignedlonghandler=(unsignedlong)ka->sa.sa_handler;
- regs->ARM_pc=handler;
3.當信號的用戶態處理函數執行結束時,需要再次進入內核態,還原用戶態堆棧,并且修改pt_regs中的pc,保證將來能夠按照正常的方式返回用戶態。我們知道進程要主動進入內核態只有通過系統調用,出發異常等方法,為此內核專門提供了一個系統調用sys_sigreturn()(還有一個sys_rt_sigreturn()),但是如何調用sys_sigreturn()呢?強制安裝信號處理函數最后必須調用一個sigreturn()不是一個好辦法,因為不了解內核的程序員會對這個限制感到不解,為此程序員常常忘記在它們的信號處理函數的末尾調用sigreturn(),如果真是這樣,arm-linux-gcc也檢測不出這個錯誤。為此,內核修改regs的ARM_lr值,:
- regs->ARM_lr=retcode;
當用戶態信號處理函數運行結束時,會從lr取出返回地址,因此內核在構建臨時regs時,會把上面這段代碼的入口地址保存在lr,這樣當信號處理完成后,就會順利的通過系統調用sys_sigreturn()進入內核。
4.當通過構造的sys_sigreturn()返回到內核態之后,內核需要順利的返回到用戶態執行原來的代碼(信號處理前應該返回的用戶空間狀態),但是此時進入內核空間的pt_regs上下文是通過sys_sigreturn()構造的,而最初的內核堆棧中的pt_regs上下文在第一次返回用戶空間執行信號處理函數時,就已經被“銷毀”了(內核態堆棧的pt_regs上下文在中斷返回后就不復存在了)。而現在必須通過最初的pt_regs上下文返回用戶態,為此,在構建臨時堆棧環境時,內核會把最初的pt_regs上下文備份到臨時堆棧中(位于用戶態堆棧),當通過系統調用sys_sigreturn()再次進入內核時,內核從用戶態空間還原出原始的pt_regs。最后正常返回。
通過上面的討論,我們知道在這個迂回的處理過程中,關鍵在于用戶態的臨時堆棧環境的建立,這是一個sigframe結構:
- /*
- *Doasignalreturn;undothesignalstack.Thesearealignedto64-bit.
- */
- structsigframe{
- structsigcontextsc;//保存一組寄存器上下文
- unsignedlongextramask[_NSIG_WORDS-1];
- unsignedlongretcode;//保存返回地址
- structaux_sigframeaux__attribute__((aligned(8)));
- };
- structrt_sigframe{
- structsiginfo__user*pinfo;
- void__user*puc;
- structsiginfoinfo;
- structucontextuc;
- unsignedlongretcode;
- structaux_sigframeaux__attribute__((aligned(8)));
- };
- staticinlinevoid__user*
- get_sigframe(structk_sigaction*ka,structpt_regs*regs,intframesize)
- {
- unsignedlongsp=regs->ARM_sp;
- void__user*frame;
- /*
- *ThisistheX/Opensanctionedsignalstackswitching.
- */
- if((ka->sa.sa_flags&SA_ONSTACK)&&!sas_ss_flags(sp))
- sp=current->sas_ss_sp+current->sas_ss_size;
- /*
- *ATPCSB01mandates8-bytealignment
- */
- frame=(void__user*)((sp-framesize)&~7);
- /*
- *Checkthatwecanactuallywritetothesignalframe.
- */
- if(!access_ok(VERIFY_WRITE,frame,framesize))
- frame=NULL;
- returnframe;
- }
通過上面的討論,我們再回到do_signal()中來,如果有用戶態的信號處理函數,do_signal()會調用handle_signal(),handle_signal()將調用setup_frame()或者setup_rt_frame()來完成實際的工作,這里我們以setup_frame()為例進行進一步討論。
- staticint
- setup_frame(intusig,structk_sigaction*ka,sigset_t*set,structpt_regs*regs)
- {
- //在用戶態堆棧上分配一個sigframe結構
- structsigframe__user*frame=get_sigframe(ka,regs,sizeof(*frame));
- interr=0;
- if(!frame)
- return1;
- //把相關信息從內核態備份到用戶態堆棧的sigframe結構中
- err|=setup_sigcontext(&frame->sc,&frame->aux,regs,set->sig[0]);
- if(_NSIG_WORDS>1){
- err|=__copy_to_user(frame->extramask,&set->sig[1],
- sizeof(frame->extramask));
- }
- if(err==0)
- err=setup_return(regs,ka,&frame->retcode,frame,usig);
- returnerr;
- }
- staticint
- setup_return(structpt_regs*regs,structk_sigaction*ka,
- unsignedlong__user*rc,void__user*frame,intusig)
- {
- unsignedlonghandler=(unsignedlong)ka->sa.sa_handler;
- unsignedlongretcode;
- intthumb=0;
- unsignedlongcpsr=regs->ARM_cpsr&~PSR_f;
- /*
- *Maybeweneedtodelivera32-bitsignaltoa26-bittask.
- */
- if(ka->sa.sa_flags&SA_THIRTYTWO)
- cpsr=(cpsr&~MODE_MASK)|USR_MODE;
- #ifdefCONFIG_ARM_THUMB
- if(elf_hwcap&HWCAP_THUMB){
- /*
- *TheLSBofthehandlerdeterminesifweregoingto
- *beusingTHUMBorARMmodeforthissignalhandler.
- */
- thumb=handler&1;
- if(thumb)
- cpsr|=PSR_T_BIT;
- else
- cpsr&=~PSR_T_BIT;
- }
- #endif
//這里的retcode就是保存手工構造的sigreturn()代碼 - if(ka->sa.sa_flags&SA_RESTORER){
- retcode=(unsignedlong)ka->sa.sa_restorer;
- }else{
- unsignedintidx=thumb;
- if(ka->sa.sa_flags&SA_SIGINFO)
- idx+=2;
- if(__put_user(sigreturn_codes[idx],rc))
- return1;
- if(cpsr&MODE32_BIT){
- /*
- *32-bitcodecanusethenewhigh-page
- *signalreturncodesupport.
- */
- retcode=KERN_SIGRETURN_CODE+(idx<<2)+thumb;
- }else{
- /*
- *Ensurethattheinstructioncachesees
- *thereturncodewrittenontothestack.
- */
- flush_icache_range((unsignedlong)rc,
- (unsignedlong)(rc+1));
- retcode=((unsignedlong)rc)+thumb;
- }
- }
- regs->ARM_r0=usig;
- regs->ARM_sp=(unsignedlong)frame;//堆棧
- regs->ARM_lr=retcode;//返回地址,當用戶態信號處理函數結束時,就會把這個地址作為返回地址
- regs->ARM_pc=handler;//信號處理函數
- regs->ARM_cpsr=cpsr;
- return0;
- }
當setup_frame()返回后,一切準備就緒,因此可以從內核態返回了,這樣就順利過渡到用戶態的信號處理函數。當這個函數處理結束后,會通過retcode再次進入內核態,現在我們看看retcode是如何處理的,下面的代碼選自glibc(2.3.2):
- #include
- /*IfnoSA_RESTORERfunctionwasspecifiedbytheapplicationweuse
- oneofthese.Thisavoidstheneedforthekerneltosynthesiseareturn
- instructiononthestack,whichwouldinvolveexpensivecacheflushes.*/
- ENTRY(__default_sa_restorer)
- swiSYS_ify(sigreturn)
- #ifdef__NR_rt_sigreturn
- ENTRY(__default_rt_sa_restorer)
- swiSYS_ify(rt_sigreturn)
- #defineSYS_ify(syscall_name)(__NR_##syscall_name)
下面具體看看sys_sigreturn()的定義:
- asmlinkageintsys_sigreturn(structpt_regs*regs)
- {
- structsigframe__user*frame;
- sigset_tset;
- /*Alwaysmakeanypendingrestartedsystemcallsreturn-EINTR*/
- current_thread_info()->restart_block.fn=do_no_restart_syscall;
- /*
- *Sincewestackedthesignalona64-bitboundary,
- *thenspshouldbewordalignedhere.Ifits
- *not,thentheuseristryingtomesswithus.
- */
- if(regs->ARM_sp&7)
- gotobadframe;
- frame=(structsigframe__user*)regs->ARM_sp;
- if(!access_ok(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(¤tt->sighand->siglock);
- if(restore_sigcontext(regs,&frame->sc,&frame->aux))
- gotobadframe;
- /*SendSIGTRAPifweresingle-stepping*/
- if(current->ptrace&PT_SINGLESTEP){
- ptrace_cancel_bpt(current);
- send_sig(SIGTRAP,current,1);
- }
- returnregs->ARM_r0;
- badframe:
- force_sig(SIGSEGV,current);
- return0;
- }
- staticint
- restore_sigcontext(structpt_regs*regs,structsigcontext__user*sc,
- structaux_sigframe__user*aux)
- {
- interr=0;
- __get_user_error(regs->ARM_r0,&sc->arm_r0,err);
- __get_user_error(regs->ARM_r1,&sc->arm_r1,err);
- __get_user_error(regs->ARM_r2,&sc->arm_r2,err);
- __get_user_error(regs->ARM_r3,&sc->arm_r3,err);
- __get_user_error(regs->ARM_r4,&sc->arm_r4,err);
- __get_user_error(regs->ARM_r5,&sc->arm_r5,err);
- __get_user_error(regs->ARM_r6,&sc->arm_r6,err);
- __get_user_error(regs->ARM_r7,&sc->arm_r7,err);
- __get_user_error(regs->ARM_r8,&sc->arm_r8,err);
- __get_user_error(regs->ARM_r9,&sc->arm_r9,err);
- __get_user_error(regs->ARM_r10,&sc->arm_r10,err);
- __get_user_error(regs->ARM_fp,&sc->arm_fp,err);
- __get_user_error(regs->ARM_ip,&sc->arm_ip,err);
- __get_user_error(regs->ARM_sp,&sc->arm_sp,err);
- __get_user_error(regs->ARM_lr,&sc->arm_lr,err);
- __get_user_error(regs->ARM_pc,&sc->arm_pc,err);
- __get_user_error(regs->ARM_cpsr,&sc->arm_cpsr,err);
- err|=!valid_user_regs(regs);
- #ifdefCONFIG_IWMMXT
- if(err==0&&test_thread_flag(TIF_USING_IWMMXT))
- err|=restore_iwmmxt_context(&aux->iwmmxt);
- #endif
- #ifdefCONFIG_VFP
- //if(err==0)
- //err|=vfp_restore_state(&aux->vfp);
- #endif
- returnerr;
- }
評論