久久ER99热精品一区二区-久久精品99国产精品日本-久久精品免费一区二区三区-久久综合九色综合欧美狠狠

新聞中心

EEPW首頁 > 嵌入式系統 > 設計應用 > linux內核中的信號機制--信號處理

linux內核中的信號機制--信號處理

作者: 時間:2016-11-22 來源:網絡 收藏
Kernel version:2.6.14

CPU architecture:ARM920T

本文引用地址:http://cqxgywz.com/article/201611/320006.htm

Author:ce123(http://blog.csdn.net/ce123)

當進程被調度時,會調用do_notify_resume()來處理信號隊列中的信號。信號處理主要就是調用sighand_struct結構中對應的信號處理函數。do_notify_resume()(arch/arm/kernel/signal.c)函數的定義如下:

[plain]view plaincopy
print?
  1. asmlinkagevoid
  2. do_notify_resume(structpt_regs*regs,unsignedintthread_flags,intsyscall)
  3. {
  4. if(thread_flags&_TIF_SIGPENDING)
  5. do_signal(¤t->blocked,regs,syscall);
  6. }
_TIF_SIGPENDING標志是在signal_wake_up()函數中設置的,檢查該標志后,接下來就調用do_signal()函數,我們來看看do_signal()(arch/arm/kernel/signal.c)的具體定義:

[plain]view plaincopy
print?
  1. /*
  2. *Notethatinitisaspecialprocess:itdoesntgetsignalsitdoesnt
  3. *wanttohandle.ThusyoucannotkillinitevenwithaSIGKILLevenby
  4. *mistake.
  5. *
  6. *Notethatwegothroughthesignalstwice:oncetocheckthesignalsthat
  7. *thekernelcanhandle,andthenwebuildalltheuser-levelsignalhandling
  8. *stack-framesinonegoafterthat.
  9. */
  10. staticintdo_signal(sigset_t*oldset,structpt_regs*regs,intsyscall)
  11. {
  12. structk_sigactionka;
  13. siginfo_tinfo;
  14. intsignr;
  15. /*
  16. *Wewantthecommoncasetogofast,which
  17. *iswhywemayincertaincasesgetherefrom
  18. *kernelmode.Justreturnwithoutdoinganything
  19. *ifso.
  20. */
  21. if(!user_mode(regs))//regs保存的是進入內核態之前的寄存器現場,必須為用戶模式,否則直接返回
  22. return0;
  23. if(try_to_freeze())
  24. gotono_signal;
  25. if(current->ptrace&PT_SINGLESTEP)
  26. ptrace_cancel_bpt(current);//和調試相關,我們在后面的文章中會具體分析
  27. signr=get_signal_to_deliver(&info,&ka,regs,NULL);//取出等待處理的信號
  28. if(signr>0){
  29. handle_signal(signr,&ka,&info,oldset,regs,syscall);//處理信號
  30. if(current->ptrace&PT_SINGLESTEP)
  31. ptrace_set_bpt(current);
  32. return1;
  33. }
  34. no_signal:
  35. /*
  36. *Nosignaltodelivertotheprocess-restartthesyscall.
  37. */
  38. if(syscall){
  39. if(regs->ARM_r0==-ERESTART_RESTARTBLOCK){
  40. if(thumb_mode(regs)){
  41. regs->ARM_r7=__NR_restart_syscall;
  42. regs->ARM_pc-=2;
  43. }else{
  44. u32__user*usp;
  45. regs->ARM_sp-=12;
  46. usp=(u32__user*)regs->ARM_sp;
  47. put_user(regs->ARM_pc,&usp[0]);
  48. /*swi__NR_restart_syscall*/
  49. put_user(0xef000000|__NR_restart_syscall,&usp[1]);
  50. /*ldrpc,[sp],#12*/
  51. put_user(0xe49df00c,&usp[2]);
  52. flush_icache_range((unsignedlong)usp,
  53. (unsignedlong)(usp+3));
  54. regs->ARM_pc=regs->ARM_sp+4;
  55. }
  56. }
  57. if(regs->ARM_r0==-ERESTARTNOHAND||
  58. regs->ARM_r0==-ERESTARTSYS||
  59. regs->ARM_r0==-ERESTARTNOINTR){
  60. restart_syscall(regs);
  61. }
  62. }
  63. if(current->ptrace&PT_SINGLESTEP)
  64. ptrace_set_bpt(current);
  65. return0;
  66. }

執行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()函數的定義如下:

[plain]view plaincopy
print?
  1. /*
  2. *OK,wereinvokingahandler
  3. */
  4. staticvoid
  5. handle_signal(unsignedlongsig,structk_sigaction*ka,
  6. siginfo_t*info,sigset_t*oldset,
  7. structpt_regs*regs,intsyscall)
  8. {
  9. structthread_info*thread=current_thread_info();
  10. structtask_struct*tsk=current;
  11. intusig=sig;
  12. intret;
  13. /*
  14. *Ifwewerefromasystemcall,checkforsystemcallrestarting...
  15. */
  16. if(syscall){
  17. switch(regs->ARM_r0){
  18. case-ERESTART_RESTARTBLOCK:
  19. case-ERESTARTNOHAND:
  20. regs->ARM_r0=-EINTR;
  21. break;
  22. case-ERESTARTSYS:
  23. if(!(ka->sa.sa_flags&SA_RESTART)){
  24. regs->ARM_r0=-EINTR;
  25. break;
  26. }
  27. /*fallthrough*/
  28. case-ERESTARTNOINTR:
  29. restart_syscall(regs);
  30. }
  31. }
  32. /*
  33. *translatethesignal
  34. */
  35. if(usig<32&&thread->exec_domain&&thread->exec_domain->signal_invmap)
  36. usig=thread->exec_domain->signal_invmap[usig];
  37. /*
  38. *Setupthestackframe//設置棧幀
  39. */
  40. if(ka->sa.sa_flags&SA_SIGINFO)
  41. ret=setup_rt_frame(usig,ka,info,oldset,regs);
  42. else
  43. ret=setup_frame(usig,ka,oldset,regs);
  44. /*
  45. *Checkthattheresultingregistersareactuallysane.
  46. */
  47. ret|=!valid_user_regs(regs);
  48. /*
  49. *Blockthesignalifwewereunsuccessful.
  50. */
  51. if(ret!=0){
  52. spin_lock_irq(&tsk->sighand->siglock);
  53. sigorsets(&tsk->blocked,&tsk->blocked,
  54. &ka->sa.sa_mask);
  55. if(!(ka->sa.sa_flags&SA_NODEFER))
  56. sigaddset(&tsk->blocked,sig);
  57. recalc_sigpending();
  58. spin_unlock_irq(&tsk->sighand->siglock);
  59. }
  60. if(ret==0)
  61. return;
  62. force_sigsegv(sig,tsk);
  63. }
在這樣情況下,進程當前處于內核態,而信號處理函數卻處于用戶態,為此必須在進程的用戶態構造一個臨時的堆棧環境(因為進程的信號處理函數在進行函數調用以及使用局部變量時需要使用堆棧。),然后進入用戶態執行信號處理函數,最后再返回內核態繼續執行。在這個過程中,有以下幾個問題需要解決:

1.臨時的用戶態堆棧在哪里呢?這個很好解決,因為可以直接使用進程現有的用戶態堆棧,這里要保證的是:使用結束后這個堆棧必須和使用前是一模一樣的。

2.臨時堆棧解決后,需要確定的是通過什么方法來保證返回到用戶態后,進程執行的是信號的處理函數。我們知道在進入內核態時,內核態堆棧中保存了一個中斷現場,也就是一個pt_regs結構,中斷返回地址就保存在pt_regts中的pc中,因此我們這里只要把當前進程的pt_regs中pc設置為sa_handler,然后返回到用戶態就開始從sa_handler處開始執行了。

[plain]view plaincopy
print?
  1. unsignedlonghandler=(unsignedlong)ka->sa.sa_handler;
  2. regs->ARM_pc=handler;

3.當信號的用戶態處理函數執行結束時,需要再次進入內核態,還原用戶態堆棧,并且修改pt_regs中的pc,保證將來能夠按照正常的方式返回用戶態。我們知道進程要主動進入內核態只有通過系統調用,出發異常等方法,為此內核專門提供了一個系統調用sys_sigreturn()(還有一個sys_rt_sigreturn()),但是如何調用sys_sigreturn()呢?強制安裝信號處理函數最后必須調用一個sigreturn()不是一個好辦法,因為不了解內核的程序員會對這個限制感到不解,為此程序員常常忘記在它們的信號處理函數的末尾調用sigreturn(),如果真是這樣,arm-linux-gcc也檢測不出這個錯誤。為此,內核修改regs的ARM_lr值,:

[plain]view plaincopy
print?
  1. 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結構:

[plain]view plaincopy
print?
  1. /*
  2. *Doasignalreturn;undothesignalstack.Thesearealignedto64-bit.
  3. */
  4. structsigframe{
  5. structsigcontextsc;//保存一組寄存器上下文
  6. unsignedlongextramask[_NSIG_WORDS-1];
  7. unsignedlongretcode;//保存返回地址
  8. structaux_sigframeaux__attribute__((aligned(8)));
  9. };
  10. structrt_sigframe{
  11. structsiginfo__user*pinfo;
  12. void__user*puc;
  13. structsiginfoinfo;
  14. structucontextuc;
  15. unsignedlongretcode;
  16. structaux_sigframeaux__attribute__((aligned(8)));
  17. };
其中的sigcontext的作用類似于pt_regs用于保存相關寄存器上下文,原始的pt_regs的相關信息就備份在這里。而整個sigframe結構是通過get_sigframe()函數在進程用戶態空間分配的,get_sigframe()定義如下:

[plain]view plaincopy
print?
  1. staticinlinevoid__user*
  2. get_sigframe(structk_sigaction*ka,structpt_regs*regs,intframesize)
  3. {
  4. unsignedlongsp=regs->ARM_sp;
  5. void__user*frame;
  6. /*
  7. *ThisistheX/Opensanctionedsignalstackswitching.
  8. */
  9. if((ka->sa.sa_flags&SA_ONSTACK)&&!sas_ss_flags(sp))
  10. sp=current->sas_ss_sp+current->sas_ss_size;
  11. /*
  12. *ATPCSB01mandates8-bytealignment
  13. */
  14. frame=(void__user*)((sp-framesize)&~7);
  15. /*
  16. *Checkthatwecanactuallywritetothesignalframe.
  17. */
  18. if(!access_ok(VERIFY_WRITE,frame,framesize))
  19. frame=NULL;
  20. returnframe;
  21. }
get_sigframe()通過用戶態空間堆棧的sp-sizeof(struct sigframe)在用戶態堆棧的頂部分配了一篇存儲空間,將來使用完成后,再通過sp+sizeof(struct sigframe)還原。

通過上面的討論,我們再回到do_signal()中來,如果有用戶態的信號處理函數,do_signal()會調用handle_signal(),handle_signal()將調用setup_frame()或者setup_rt_frame()來完成實際的工作,這里我們以setup_frame()為例進行進一步討論。

[plain]view plaincopy
print?
  1. staticint
  2. setup_frame(intusig,structk_sigaction*ka,sigset_t*set,structpt_regs*regs)
  3. {
  4. //在用戶態堆棧上分配一個sigframe結構
  5. structsigframe__user*frame=get_sigframe(ka,regs,sizeof(*frame));
  6. interr=0;
  7. if(!frame)
  8. return1;
  9. //把相關信息從內核態備份到用戶態堆棧的sigframe結構中
  10. err|=setup_sigcontext(&frame->sc,&frame->aux,regs,set->sig[0]);
  11. if(_NSIG_WORDS>1){
  12. err|=__copy_to_user(frame->extramask,&set->sig[1],
  13. sizeof(frame->extramask));
  14. }
  15. if(err==0)
  16. err=setup_return(regs,ka,&frame->retcode,frame,usig);
  17. returnerr;
  18. }
setup_return()設置返回地址,其定義如下:

[plain]view plaincopy
print?
  1. staticint
  2. setup_return(structpt_regs*regs,structk_sigaction*ka,
  3. unsignedlong__user*rc,void__user*frame,intusig)
  4. {
  5. unsignedlonghandler=(unsignedlong)ka->sa.sa_handler;
  6. unsignedlongretcode;
  7. intthumb=0;
  8. unsignedlongcpsr=regs->ARM_cpsr&~PSR_f;
  9. /*
  10. *Maybeweneedtodelivera32-bitsignaltoa26-bittask.
  11. */
  12. if(ka->sa.sa_flags&SA_THIRTYTWO)
  13. cpsr=(cpsr&~MODE_MASK)|USR_MODE;
  14. #ifdefCONFIG_ARM_THUMB
  15. if(elf_hwcap&HWCAP_THUMB){
  16. /*
  17. *TheLSBofthehandlerdeterminesifweregoingto
  18. *beusingTHUMBorARMmodeforthissignalhandler.
  19. */
  20. thumb=handler&1;
  21. if(thumb)
  22. cpsr|=PSR_T_BIT;
  23. else
  24. cpsr&=~PSR_T_BIT;
  25. }
  26. #endif
  27. //這里的retcode就是保存手工構造的sigreturn()代碼
  28. if(ka->sa.sa_flags&SA_RESTORER){
  29. retcode=(unsignedlong)ka->sa.sa_restorer;
  30. }else{
  31. unsignedintidx=thumb;
  32. if(ka->sa.sa_flags&SA_SIGINFO)
  33. idx+=2;
  34. if(__put_user(sigreturn_codes[idx],rc))
  35. return1;
  36. if(cpsr&MODE32_BIT){
  37. /*
  38. *32-bitcodecanusethenewhigh-page
  39. *signalreturncodesupport.
  40. */
  41. retcode=KERN_SIGRETURN_CODE+(idx<<2)+thumb;
  42. }else{
  43. /*
  44. *Ensurethattheinstructioncachesees
  45. *thereturncodewrittenontothestack.
  46. */
  47. flush_icache_range((unsignedlong)rc,
  48. (unsignedlong)(rc+1));
  49. retcode=((unsignedlong)rc)+thumb;
  50. }
  51. }
  52. regs->ARM_r0=usig;
  53. regs->ARM_sp=(unsignedlong)frame;//堆棧
  54. regs->ARM_lr=retcode;//返回地址,當用戶態信號處理函數結束時,就會把這個地址作為返回地址
  55. regs->ARM_pc=handler;//信號處理函數
  56. regs->ARM_cpsr=cpsr;
  57. return0;
  58. }

當setup_frame()返回后,一切準備就緒,因此可以從內核態返回了,這樣就順利過渡到用戶態的信號處理函數。當這個函數處理結束后,會通過retcode再次進入內核態,現在我們看看retcode是如何處理的,下面的代碼選自glibc(2.3.2):

[plain]view plaincopy
print?
  1. #include
  2. /*IfnoSA_RESTORERfunctionwasspecifiedbytheapplicationweuse
  3. oneofthese.Thisavoidstheneedforthekerneltosynthesiseareturn
  4. instructiononthestack,whichwouldinvolveexpensivecacheflushes.*/
  5. ENTRY(__default_sa_restorer)
  6. swiSYS_ify(sigreturn)
  7. #ifdef__NR_rt_sigreturn
  8. ENTRY(__default_rt_sa_restorer)
  9. swiSYS_ify(rt_sigreturn)
  10. #defineSYS_ify(syscall_name)(__NR_##syscall_name)

下面具體看看sys_sigreturn()的定義:

[plain]view plaincopy
print?
  1. asmlinkageintsys_sigreturn(structpt_regs*regs)
  2. {
  3. structsigframe__user*frame;
  4. sigset_tset;
  5. /*Alwaysmakeanypendingrestartedsystemcallsreturn-EINTR*/
  6. current_thread_info()->restart_block.fn=do_no_restart_syscall;
  7. /*
  8. *Sincewestackedthesignalona64-bitboundary,
  9. *thenspshouldbewordalignedhere.Ifits
  10. *not,thentheuseristryingtomesswithus.
  11. */
  12. if(regs->ARM_sp&7)
  13. gotobadframe;
  14. frame=(structsigframe__user*)regs->ARM_sp;
  15. if(!access_ok(VERIFY_READ,frame,sizeof(*frame)))
  16. gotobadframe;
  17. if(__get_user(set.sig[0],&frame->sc.oldmask)
  18. ||(_NSIG_WORDS>1
  19. &&__copy_from_user(&set.sig[1],&frame->extramask,
  20. sizeof(frame->extramask))))
  21. gotobadframe;
  22. sigdelsetmask(&set,~_BLOCKABLE);
  23. spin_lock_irq(¤t->sighand->siglock);
  24. current->blocked=set;
  25. recalc_sigpending();
  26. spin_unlock_irq(¤tt->sighand->siglock);
  27. if(restore_sigcontext(regs,&frame->sc,&frame->aux))
  28. gotobadframe;
  29. /*SendSIGTRAPifweresingle-stepping*/
  30. if(current->ptrace&PT_SINGLESTEP){
  31. ptrace_cancel_bpt(current);
  32. send_sig(SIGTRAP,current,1);
  33. }
  34. returnregs->ARM_r0;
  35. badframe:
  36. force_sig(SIGSEGV,current);
  37. return0;
  38. }
這個函數主要調用restore_sigcontext()根據用戶態態堆棧上的sigframe備份還原pt_regs。

[plain]view plaincopy
print?
  1. staticint
  2. restore_sigcontext(structpt_regs*regs,structsigcontext__user*sc,
  3. structaux_sigframe__user*aux)
  4. {
  5. interr=0;
  6. __get_user_error(regs->ARM_r0,&sc->arm_r0,err);
  7. __get_user_error(regs->ARM_r1,&sc->arm_r1,err);
  8. __get_user_error(regs->ARM_r2,&sc->arm_r2,err);
  9. __get_user_error(regs->ARM_r3,&sc->arm_r3,err);
  10. __get_user_error(regs->ARM_r4,&sc->arm_r4,err);
  11. __get_user_error(regs->ARM_r5,&sc->arm_r5,err);
  12. __get_user_error(regs->ARM_r6,&sc->arm_r6,err);
  13. __get_user_error(regs->ARM_r7,&sc->arm_r7,err);
  14. __get_user_error(regs->ARM_r8,&sc->arm_r8,err);
  15. __get_user_error(regs->ARM_r9,&sc->arm_r9,err);
  16. __get_user_error(regs->ARM_r10,&sc->arm_r10,err);
  17. __get_user_error(regs->ARM_fp,&sc->arm_fp,err);
  18. __get_user_error(regs->ARM_ip,&sc->arm_ip,err);
  19. __get_user_error(regs->ARM_sp,&sc->arm_sp,err);
  20. __get_user_error(regs->ARM_lr,&sc->arm_lr,err);
  21. __get_user_error(regs->ARM_pc,&sc->arm_pc,err);
  22. __get_user_error(regs->ARM_cpsr,&sc->arm_cpsr,err);
  23. err|=!valid_user_regs(regs);
  24. #ifdefCONFIG_IWMMXT
  25. if(err==0&&test_thread_flag(TIF_USING_IWMMXT))
  26. err|=restore_iwmmxt_context(&aux->iwmmxt);
  27. #endif
  28. #ifdefCONFIG_VFP
  29. //if(err==0)
  30. //err|=vfp_restore_state(&aux->vfp);
  31. #endif
  32. returnerr;
  33. }
restore_sigcontext()很簡單,當它返回后,通過sys_sigreturn()在內核態堆棧建立的pt_regs已經變為調用信號處理函數之前的pt_regs。這樣,sys_sigreturn()返回用戶態時,就順利地過渡到處理之前的狀態了。


評論


技術專區

關閉