前面我們已經(jīng)介紹了內(nèi)核注意到信號(hào)的到來(lái),調(diào)用相關(guān)函數(shù)更新進(jìn)程描述符以便進(jìn)程接收處理信號(hào)。但是,如果目標(biāo)進(jìn)程此時(shí)沒(méi)有運(yùn)行,內(nèi)核則推遲傳遞信號(hào)。現(xiàn)在,我們看看內(nèi)核如何處理進(jìn)程掛起的信號(hào)。
正如第4章的從中斷和異常返回一節(jié)中提到的,內(nèi)核允許在進(jìn)程返回到用戶態(tài)執(zhí)行之前,檢查進(jìn)程的TIF_SIGPENDING標(biāo)志。因此,內(nèi)核每次完成中斷或異常的處理后,都會(huì)檢查掛起信號(hào)是否存在。為了處理非阻塞的掛起信號(hào),內(nèi)核調(diào)用do_signal()函數(shù),其接受2個(gè)參數(shù):
regs
current當(dāng)前進(jìn)程的用戶態(tài)寄存器內(nèi)容在內(nèi)核棧中保存位置的地址。
oldset
用來(lái)保存阻塞信號(hào)位掩碼數(shù)組的變量地址。
對(duì)do_signal()的描述,主要集中在信號(hào)傳遞的通用機(jī)制;真實(shí)的代碼中涵蓋了許多細(xì)節(jié),比如處理競(jìng)態(tài)條件和其它特殊情況(如凍結(jié)系統(tǒng)、生成核心轉(zhuǎn)儲(chǔ)、停止和殺死整個(gè)線程組等等。我們將忽略這些細(xì)節(jié)。
如前所述,do_signal()函數(shù)通常只在CPU打算返回到用戶態(tài)時(shí)才會(huì)被調(diào)用。所以,如果中斷處理程序里調(diào)用do_signal(),函數(shù)直接返回:
if((regs->xcs&3)!=3) return1;
如果oldset是NULL,使用current->blocked的地址初始化它:
if(!oldset) oldset=¤t->blocked;
do_signal()的核心是一段循環(huán)代碼,重復(fù)調(diào)用dequeue_signal()函數(shù),直到私有和共享掛起信號(hào)隊(duì)列沒(méi)有被阻塞的掛起信號(hào)。dequeue_signal()的返回值存儲(chǔ)在局部變量signr中,如果等于0,意味著所有掛起信號(hào)都被處理完,則do_signal()完成;如果返回非零,就有掛起信號(hào)等待被處理。do_signal()處理完這個(gè)信號(hào)后,會(huì)再次調(diào)用dequeue_signal()函數(shù)。
dequeue_signal()首先考慮私有掛起信號(hào)隊(duì)列中的所有信號(hào)(數(shù)字從小到大),然后是共享掛起隊(duì)列中的信號(hào)。它會(huì)更新相應(yīng)的數(shù)據(jù)結(jié)構(gòu),標(biāo)識(shí)該信號(hào)不再掛起并返回信號(hào)值。其中,涉及到清除current->pending.signal或current->signal->shared_pending.signal中的相應(yīng)位,并調(diào)用recalc_sigpending()更新TIF_SIGPENDING的值。
讓我們看一下do_signal()如何處理dequeue_signal()返回的每個(gè)掛起信號(hào)。首先,檢查當(dāng)前接收進(jìn)程是否正在被其它進(jìn)程監(jiān)控;如果是這種情況,do_signal()調(diào)用do_notify_parent_cldstop()和schedule()使監(jiān)控線程意識(shí)到該信號(hào)處理。
然后,do_signal()將待處理信號(hào)的k_sigaction數(shù)據(jù)結(jié)構(gòu)的地址加載到局部變量ka中:
ka=¤t->sig->action[signr-1];
依賴(lài)具體內(nèi)容,可能執(zhí)行三類(lèi)動(dòng)作:忽略信號(hào),執(zhí)行默認(rèn)動(dòng)作或執(zhí)行信號(hào)處理程序。
當(dāng)傳遞的信號(hào)被忽略,do_signal()則繼續(xù)處理其它掛起信號(hào):
if(ka->sa.sa_handler==SIG_IGN) continue;
接下來(lái)的兩節(jié),我們將描述如何執(zhí)行默認(rèn)動(dòng)作和信號(hào)處理程序。
1 執(zhí)行信號(hào)的默認(rèn)動(dòng)作
如果ka->sa.sa_handler等于SIG_DFL,do_signal()執(zhí)行信號(hào)的默認(rèn)動(dòng)作。唯一的例外是,當(dāng)接收進(jìn)程是init時(shí),這種情況下,信號(hào)會(huì)被拋棄:
if(current->pid==1) continue;
對(duì)于其它進(jìn)程,忽略信號(hào)的默認(rèn)處理也非常簡(jiǎn)單:
if(signr==SIGCONT||signr==SIGCHLD|| signr==SIGWINCH||signr==SIGURG) continue;
對(duì)于默認(rèn)動(dòng)作是stop的信號(hào),則會(huì)停止線程組中所有進(jìn)程。為此,do_signal()將它們的狀態(tài)設(shè)置為T(mén)ASK_STOPPED,然后調(diào)用schedule()調(diào)度進(jìn)程:
if(signr==SIGSTOP||signr==SIGTSTP|| signr==SIGTTIN||signr==SIGTTOU){ if(signr!=SIGSTOP&& is_orphaned_pgrp(current->signal->pgrp)) continue; do_signal_stop(signr); }
SIGSTOP和其它信號(hào)有些許不同:SIGSTOP總是停止線程組,而其它信號(hào)只有在線程組處于孤兒進(jìn)程組中時(shí)才會(huì)停止該線程組。POSIX標(biāo)準(zhǔn)指明,只要線程組中某個(gè)進(jìn)程在同一個(gè)會(huì)話的不同進(jìn)程組中有一個(gè)父進(jìn)程,它就不是孤兒進(jìn)程組。因此,如果父進(jìn)程已死,但是,發(fā)起進(jìn)程的用戶仍然處于登錄中,該進(jìn)程組就不是孤兒進(jìn)程組。
do_signal_stop()檢查當(dāng)前進(jìn)程是否是線程組中第一個(gè)被停止的進(jìn)程。如果是,它負(fù)責(zé)停止所有進(jìn)程:本質(zhì)上,該函數(shù)將信號(hào)描述符中的group_stop_count字段設(shè)置為正值,并喚醒線程組中的每個(gè)進(jìn)程。然后,每個(gè)進(jìn)程依次查看此字段以識(shí)別正在進(jìn)行的組停止,將其狀態(tài)更改為T(mén)ASK_STOPPED,并調(diào)用schedule()重新調(diào)度進(jìn)程。do_signal_stop()函數(shù)還向線程組leader的父進(jìn)程發(fā)送SIGCHLD信號(hào),除非父進(jìn)程設(shè)置了SIGCHLD的SA_NOCLDSTOP標(biāo)志。
默認(rèn)動(dòng)作為dump的信號(hào)會(huì)在進(jìn)程的工作目錄中創(chuàng)建核心轉(zhuǎn)儲(chǔ)文件:該文件列出了進(jìn)程地址空間和寄存器的完整內(nèi)容。do_signal()創(chuàng)建核心轉(zhuǎn)儲(chǔ)文件之后,會(huì)殺死線程組。其余18個(gè)信號(hào)的默認(rèn)動(dòng)作是terminate,就是殺死進(jìn)程。為此,調(diào)用do_group_exit(),執(zhí)行一個(gè)優(yōu)雅的group exit處理程序(可以參考第3章的進(jìn)程終止一節(jié))
2 捕獲信號(hào)
如果信號(hào)指定了處理程序,則do_signal()執(zhí)行該程序。通過(guò)調(diào)用invoking handle_signal()
handle_signal(signr,&info,&ka,oldset,regs); if(ka->sa.sa_flags&SA_ONESHOT) ka->sa.sa_handler=SIG_DFL; return1;
如果接收的信號(hào)設(shè)置了SA_ONESHOT標(biāo)志,則必須將其重置為默認(rèn)操作,以便再次出現(xiàn)相同信號(hào)將不會(huì)觸發(fā)信號(hào)處理程序的執(zhí)行。注意do_signal()在處理完單個(gè)信號(hào)后是如何返回的。在下次調(diào)用do_signal()之前,不會(huì)考慮其他掛起的信號(hào)。這種方法確保了實(shí)時(shí)信號(hào)將按適當(dāng)?shù)捻樞蛱幚怼?/p>
執(zhí)行信號(hào)處理程序是一項(xiàng)相當(dāng)復(fù)雜的任務(wù),因?yàn)樵谟脩魬B(tài)和內(nèi)核態(tài)之間切換時(shí)需要小心地切換堆棧。我們?cè)谶@里詳細(xì)解釋一下:
信號(hào)處理程序是由用戶進(jìn)程定義的函數(shù),包含在用戶代碼段中。handle_signal()函數(shù)在內(nèi)核態(tài)運(yùn)行,而信號(hào)處理程序在用戶態(tài)運(yùn)行;這意味著當(dāng)前進(jìn)程必須首先在用戶態(tài)執(zhí)行信號(hào)處理程序,然后才能被允許恢復(fù)其“正常”執(zhí)行。此外,當(dāng)內(nèi)核試圖恢復(fù)進(jìn)程的正常執(zhí)行時(shí),內(nèi)核堆棧不再包含被中斷程序的硬件上下文,因?yàn)閮?nèi)核堆棧在每次從用戶態(tài)轉(zhuǎn)換到內(nèi)核態(tài)時(shí)都會(huì)被清空。
下圖11-2說(shuō)明了捕獲信號(hào)的函數(shù)執(zhí)行流程。假設(shè)非阻塞信號(hào)被發(fā)送給進(jìn)程。中斷或異常發(fā)生時(shí),進(jìn)程切換到內(nèi)核態(tài)。在即將返回到用戶態(tài)之前,內(nèi)核調(diào)用do_signal()函數(shù),依次處理信號(hào)(handle_signal())并配置用戶態(tài)棧(setup_frame()或setup_rt_frame())。進(jìn)程切換到用戶態(tài)后,開(kāi)始執(zhí)行信號(hào)處理程序,因?yàn)樵撎幚沓绦虻牡刂繁粡?qiáng)制加載到了PC程序計(jì)數(shù)器中。當(dāng)信號(hào)程序終止后,調(diào)用setup_frame()或setup_rt_frame()將返回代碼加載到用戶態(tài)棧中。這段返回代碼會(huì)調(diào)用sigreturn()和rt_sigreturn()系統(tǒng)調(diào)用;相應(yīng)的服務(wù)例程會(huì)將正常程序的硬件上下文內(nèi)容拷貝到內(nèi)核態(tài)棧并將用戶態(tài)棧恢復(fù)到其原始狀態(tài)(restore_sigcontext())。當(dāng)系統(tǒng)調(diào)用終止時(shí),正常程序繼續(xù)其執(zhí)行。
圖11-2 捕獲一個(gè)信號(hào)
現(xiàn)在,讓我們看一下其執(zhí)行細(xì)節(jié):
2.1 Setting up the frame
為了正確設(shè)置進(jìn)程的用戶態(tài)棧,handle_signal()函數(shù)既可以調(diào)用setup_frame()(對(duì)于那些不需要siginfo_t的信號(hào)),也可以調(diào)用setup_rt_frame()(對(duì)于那些確定需要siginfo_t的信號(hào))。具體調(diào)用哪個(gè)函數(shù),依賴(lài)于信號(hào)的sigaction表中sa_flags字段的SA_SIGINFO標(biāo)志。
接下來(lái),我們看一下setup_frame()函數(shù)的具體實(shí)現(xiàn):(Linux內(nèi)核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c)
/*這些符號(hào)的定義在vsyscall內(nèi)存頁(yè)中,查看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 &&sig32 ??????????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; /*為信號(hào)處理程序配置寄存器*/ regs->esp=(unsignedlong)frame; regs->eip=(unsignedlong)ka->sa.sa_handler; regs->eax=(unsignedlong)sig; regs->edx=(unsignedlong)0; regs->ecx=(unsignedlong)0; /*恢復(fù)用戶態(tài)的段寄存器*/ set_fs(USER_DS); regs->xds=__USER_DS; regs->xes=__USER_DS; regs->xss=__USER_DS; regs->xcs=__USER_CS; /*在進(jìn)入信號(hào)處理程序時(shí)清除TF標(biāo)志,但通知正在單步跟蹤的跟蹤器, *跟蹤器也可能希望在信號(hào)處理程序內(nèi)部進(jìn)行單步執(zhí)行 */ regs->eflags&=~TF_MASK; if(test_thread_flag(TIF_SINGLESTEP)) ptrace_notify(SIGTRAP); //...省略,打印調(diào)試信息,然后返回。 give_sigsegv: force_sigsegv(sig,current); }
setup_frame()接收4個(gè)參數(shù),如下所示:
sig
信號(hào)
ka
信號(hào)的k_sigaction表地址
oldset
阻塞信號(hào)的位掩碼組地址
regs
用戶態(tài)寄存器內(nèi)容在內(nèi)核棧的保存位置
setup_frame()將一個(gè)稱(chēng)為frame的數(shù)據(jù)結(jié)構(gòu)壓倒用戶態(tài)棧中,該數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)著處理信號(hào)和能夠正確返回到sys_sigreturn()函數(shù)的所需要信息。frame是一個(gè)sigframe表,包含以下字段(參見(jiàn)圖11-3):
pretcode
信號(hào)處理程序的返回地址。其實(shí)就是__kernel_sigreturn標(biāo)簽處的匯編代碼。
sig
信號(hào),信號(hào)處理程序需要的一個(gè)參數(shù)。
sc
包含用戶態(tài)進(jìn)程即將切換到內(nèi)核態(tài)之前的進(jìn)程上下文內(nèi)容,其數(shù)據(jù)類(lèi)型為sigcontext(這些信息是從current的內(nèi)核態(tài)棧中拷貝而來(lái))。另外,它還包含一個(gè)進(jìn)程阻塞信號(hào)的位數(shù)組。
fpstate
用來(lái)保存用戶態(tài)進(jìn)程的浮點(diǎn)寄存器信息,數(shù)據(jù)結(jié)構(gòu)類(lèi)型為_(kāi)fpstate。(參見(jiàn)第3章的保存和加載FPU、MMX和XMM寄存器)。
extramask
指定阻塞實(shí)時(shí)信號(hào)的位數(shù)組。
retcode
發(fā)起sigreturn()系統(tǒng)調(diào)用的8字節(jié)代碼。在Linux早期版本中,這段代碼用來(lái)從信號(hào)處理程序返回;但Linux 2.6版本以后,僅用作符號(hào)簽名,以便調(diào)試器可以識(shí)別信號(hào)的棧幀。
圖11-3 用戶態(tài)棧上的frame
setup_frame()函數(shù)調(diào)用get_sigframe()計(jì)算frame第一個(gè)內(nèi)存位置,因?yàn)樵搩?nèi)存位置位于用戶態(tài)棧上,所以函數(shù)返回的值為(regs->esp - sizeof(struct sigframe)) & 0xfffffff8。
Linux允許進(jìn)程調(diào)用signaltstack()系統(tǒng)調(diào)用為它們的信號(hào)處理程序指定一個(gè)替換棧;這個(gè)特性也是X/Open標(biāo)準(zhǔn)要求的。如果使用的是替換棧,get_sigframe()函數(shù)返回的是替換棧中的一個(gè)地址。對(duì)于此特性,我們不過(guò)多討論,從概念上講,其與常規(guī)信號(hào)處理非常類(lèi)似。
因?yàn)樵趚86架構(gòu)上,棧是向下增長(zhǎng)的,所以,frame的首地址等于當(dāng)前棧頂位置的地址減去frame的大小,結(jié)果按照8字節(jié)對(duì)齊。
返回地址使用access_ok進(jìn)行驗(yàn)證:如果合法,函數(shù)重復(fù)調(diào)用__put_user(),以便填充frame的所有字段。pretcode字段初始化為&__kernel_sigreturn,這是vsyscall內(nèi)存頁(yè)上的一段匯編代碼地址。(參見(jiàn)第10章的通過(guò)sysenter指令發(fā)起系統(tǒng)調(diào)用的一節(jié))
接下來(lái),修改內(nèi)核態(tài)棧的regs內(nèi)容,保證當(dāng)current切換到用戶態(tài)時(shí),CPU控制權(quán)能夠傳遞給信號(hào)處理程序:
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()函數(shù)將保存在內(nèi)核態(tài)棧上的段寄存器復(fù)位成用戶態(tài)默認(rèn)值而終止。現(xiàn)在,信號(hào)處理程序所需的信息都在用戶態(tài)棧頂位置了。
setup_rt_frame()函數(shù)與setup_frame()類(lèi)似,但是它把一個(gè)擴(kuò)展幀(數(shù)據(jù)結(jié)構(gòu)為rt_sigframe)存放到了用戶態(tài)棧中,該擴(kuò)展幀還包括與信號(hào)有關(guān)的siginfo_t表的內(nèi)容。此外,該函數(shù)還將pretcode字段指向vsyscall內(nèi)存頁(yè)中的__kernel_rt_sigreturn代碼段。
2.2 計(jì)算信號(hào)標(biāo)志
配置完用戶態(tài)棧后,handle_signal()函數(shù)檢查與該信號(hào)相關(guān)的標(biāo)志。如果該信號(hào)沒(méi)有設(shè)置SA_NODEFER標(biāo)志,則在信號(hào)處理程序執(zhí)行期間,sigaction表中的sa_mask字段中的所有信號(hào)必須被阻塞,以便該信號(hào)快速處理完成:
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()函數(shù)檢查該進(jìn)程是否有非阻塞的掛起信號(hào),并設(shè)置其相應(yīng)的TIF_SIGPENDING標(biāo)志。
完成之后,返回do_signal(),隨即也返回。
2.3 啟動(dòng)信號(hào)處理程序
當(dāng)從do_signal()返回后,當(dāng)前進(jìn)程切換到用戶態(tài)執(zhí)行。因?yàn)閟etup_frame()的準(zhǔn)備工作,eip寄存器指向了信號(hào)處理程序的第一條指令,而esp指向了壓入用戶態(tài)棧頂?shù)膄rame的第一個(gè)內(nèi)存位置。于是,開(kāi)始執(zhí)行信號(hào)處理程序。
2.4 終止信號(hào)處理程序
當(dāng)信號(hào)處理程序執(zhí)行完成時(shí),其棧頂?shù)姆祷氐刂分赶騰syscall內(nèi)存頁(yè)(frame中的pretcode字段)
__kernel_sigreturn: popl%eax movl$__NR_sigreturn,%eax int$0x80
因此,信號(hào)(也就是frame中的sig字段)被從棧中丟棄;然后,調(diào)用sigreturn()系統(tǒng)調(diào)用。
sys_sigreturn()函數(shù)計(jì)算regs(類(lèi)型為pt_regs)的地址,其中包含用戶進(jìn)程的硬件上下文內(nèi)容,以便完成內(nèi)核態(tài)切換到用戶態(tài)執(zhí)行。因?yàn)槲覀冊(cè)趶膬?nèi)核態(tài)切換到用戶態(tài)執(zhí)行信號(hào)處理程序的過(guò)程中,內(nèi)核態(tài)棧已經(jīng)被破壞,所以需要重新建立一個(gè)臨時(shí)內(nèi)核態(tài)棧,數(shù)據(jù)來(lái)源就是用戶態(tài)棧中配置的frame數(shù)據(jù)結(jié)構(gòu)。
asmlinkageintsys_sigreturn(unsignedlong__unused) { /*建立進(jìn)程在內(nèi)核態(tài)的臨時(shí)棧*/ structpt_regs*regs=(structpt_regs*)&__unused; //內(nèi)核態(tài)棧中用戶存儲(chǔ) structsigframe__user*frame=(structsigframe__user*)(regs->esp-8); sigset_tset; inteax; /*驗(yàn)證`frame`數(shù)據(jù)結(jié)構(gòu)是否正確*/ if(verify_area(VERIFY_READ,frame,sizeof(*frame))) gotobadframe; /*處理實(shí)時(shí)信號(hào)*/ if(__get_user(set.sig[0],&frame->sc.oldmask) ||(_NSIG_WORDS>1 &&__copy_from_user(&set.sig[1],&frame->extramask, sizeof(frame->extramask)))) gotobadframe; /*將在信號(hào)處理程序期間阻塞的信號(hào)恢復(fù)掛起狀態(tài)*/ sigdelsetmask(&set,~_BLOCKABLE); spin_lock_irq(¤t->sighand->siglock); current->blocked=set; recalc_sigpending(); spin_unlock_irq(¤t->sighand->siglock); /*將用戶態(tài)棧中的frame中保存的用戶進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并移除frame*/ if(restore_sigcontext(regs,&frame->sc,&eax)) gotobadframe; returneax; /*錯(cuò)誤數(shù)據(jù)處理*/ badframe: force_sig(SIGSEGV,current); return0; }
sys_sigreturn()函數(shù)計(jì)算出regs(類(lèi)型為pt_regs)的地址,它包含用戶進(jìn)程的硬件上下文內(nèi)容(參考第10章的參數(shù)傳遞一節(jié)。根據(jù)regs中的esp字段,就能推斷出用戶棧中的frame地址。
然后,從frame的sc字段中拷貝在調(diào)用信號(hào)處理程序之前被阻塞的信號(hào)(位數(shù)組)到當(dāng)前進(jìn)程current的blocked字段中。也就是將這些被阻塞的信號(hào)解除阻塞。調(diào)用recalc_sigpending()將這些信號(hào)重新加入到掛起信號(hào)隊(duì)列中。
接下來(lái),sys_sigreturn()函數(shù)需要將frame的sc字段中的進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并從用戶態(tài)棧中移除frame數(shù)據(jù)。這兩步的完成都是restore_sigcontext()函數(shù)實(shí)現(xiàn)的。
如果信號(hào)是由系統(tǒng)調(diào)用發(fā)送的(比如,rt_sigqueueinfo()),要求信號(hào)相關(guān)的siginfo_t表數(shù)據(jù),其機(jī)制與上面類(lèi)似。擴(kuò)展幀中的pretcode字段指向__kernel_rt_sigreturn標(biāo)簽處的匯編代碼(位于vsyscall內(nèi)存頁(yè)),這段代碼會(huì)調(diào)用rt_sigreturn()系統(tǒng)調(diào)用。相應(yīng)的系統(tǒng)服務(wù)例程sys_rt_sigreturn()將擴(kuò)展幀中的進(jìn)程硬件上下文拷貝到內(nèi)核態(tài)棧,并且將擴(kuò)展幀從用戶態(tài)棧中移除,以便恢復(fù)原始的用戶態(tài)棧。
3 系統(tǒng)調(diào)用的重新執(zhí)行
對(duì)于系統(tǒng)調(diào)用請(qǐng)求,內(nèi)核有時(shí)不能立即滿足。這時(shí)候,發(fā)起系統(tǒng)調(diào)用的進(jìn)程會(huì)被置成TASK_INTERRUPTIBLE或TASK_UNINTERRUPTIBLE狀態(tài)。
如果進(jìn)程處于TASK_INTERRUPTIBLE狀態(tài)且其它進(jìn)程發(fā)送信號(hào)給它,內(nèi)核在沒(méi)有完成系統(tǒng)調(diào)用的情況下將進(jìn)程置為T(mén)ASK_RUNNING(參考第4章的從中斷和異常返回一節(jié))。當(dāng)進(jìn)程想要切換回用戶態(tài),同時(shí)信號(hào)傳遞過(guò)來(lái)時(shí),系統(tǒng)調(diào)用服務(wù)例程還沒(méi)有完成其工作,所以會(huì)返回錯(cuò)誤碼EINTR、ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS、ERESTARTNOINTR。
事實(shí)上,在這種場(chǎng)景下,用戶態(tài)進(jìn)程能得到的錯(cuò)誤碼只能是EINTR,這意味著系統(tǒng)調(diào)用還沒(méi)有完成。應(yīng)用編程者可以檢查這個(gè)錯(cuò)誤碼并決定是否重新發(fā)起系統(tǒng)調(diào)用。其余的錯(cuò)誤碼由內(nèi)核內(nèi)部使用,指定是否在信號(hào)處理程序結(jié)束之后自動(dòng)重新執(zhí)行系統(tǒng)調(diào)用。
表11-11 列出了未完成系統(tǒng)調(diào)用相關(guān)的錯(cuò)誤碼,以及它們?nèi)N信號(hào)默認(rèn)行為的影響。表中的術(shù)語(yǔ)說(shuō)明如下:
Terminate
系統(tǒng)調(diào)用將不會(huì)自動(dòng)重新執(zhí)行;進(jìn)程將切換到用戶態(tài)下int $0x80或sysenter之后的指令處繼續(xù)執(zhí)行,同時(shí),通過(guò)寄存器eax返回-EINTR值。
Reexecute
內(nèi)核強(qiáng)制用戶進(jìn)程重新加載系統(tǒng)調(diào)用號(hào)(eax),然后重新調(diào)用int $0x80或sysenter;而進(jìn)程不會(huì)意識(shí)到重新執(zhí)行,也不會(huì)傳遞錯(cuò)誤碼給它。
Depends
只有被傳遞的信號(hào)設(shè)置了SA_RESTART標(biāo)志,系統(tǒng)調(diào)用才會(huì)被重新執(zhí)行;否則,系統(tǒng)調(diào)用將終止并返回錯(cuò)誤碼-EINTR。
表11-11 系統(tǒng)調(diào)用的重新執(zhí)行
信號(hào)行為 | EINTR | ERESTARTSYS |
ERESTARTNOHAND ERESTART_RESTARTBLOCK* |
ERESTARTNOINTR |
---|---|---|---|---|
Default | Terminate | Reexecute | Reexecute | Reexecute |
Ignore | Terminate | Reexecute | Reexecute | Reexecute |
Catch | Terminate | Depends | Terminate | Reexecute |
ERESTARTNOHAND和ERESTART_RESTARTBLOCK重啟系統(tǒng)調(diào)用的機(jī)制不同。
在傳遞信號(hào)時(shí),內(nèi)核必須在重新執(zhí)行它之前確保進(jìn)程發(fā)起了系統(tǒng)調(diào)用。這就是regs寄存器上下文的orig_eax字段發(fā)揮關(guān)鍵作用的地方。讓我們回憶一下,當(dāng)中斷或異常處理程序啟動(dòng)時(shí),這個(gè)字段是如何初始化的:
中斷
該字段為中斷IRQ減去256(因?yàn)橹袛嗵?hào)數(shù)量小于224,減去256表示內(nèi)核使用負(fù)數(shù)表示IRQ)(參考第4章的為中斷處理程序保存寄存器)。
0x80異常(包括sysenter)
該字段包含系統(tǒng)調(diào)用號(hào)(第10章的進(jìn)入和推出系統(tǒng)調(diào)用一節(jié))。
其它異常
該字段為–1(參考第4章的為異常處理程序保存寄存器)。
因此,orig_eax中的非負(fù)值意味著信號(hào)喚醒了一個(gè)在系統(tǒng)調(diào)用中休眠的可中斷進(jìn)程(TASK_INTERRUPTIBLE)。服務(wù)例程意識(shí)到了系統(tǒng)調(diào)用被中斷,因此返回一個(gè)前面提到的錯(cuò)誤碼。
3.1 重新啟動(dòng)non-caught信號(hào)中斷的系統(tǒng)調(diào)用
對(duì)于被忽略或執(zhí)行默認(rèn)動(dòng)作的信號(hào),do_signal()分析系統(tǒng)調(diào)用的錯(cuò)誤碼,判斷系統(tǒng)調(diào)用是否自動(dòng)重新執(zhí)行,如表11-1所示。如果系統(tǒng)調(diào)用必須重啟,則修改regs上下文內(nèi)容:eip-2表示將eip指向int $0x80或sysenter,eax包含系統(tǒng)調(diào)用號(hào):
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包含著系統(tǒng)調(diào)用服務(wù)例程的返回碼(參加第10章的進(jìn)入和退出系統(tǒng)調(diào)用一節(jié))。因?yàn)閕nt $0x80和sysreturn指令都是2個(gè)字節(jié)長(zhǎng)度,所以eip-2指向了int $0x80或sysenter,可以再次觸發(fā)系統(tǒng)調(diào)用。
錯(cuò)誤碼ERESTART_RESTARTBLOCK是特殊的,因?yàn)閑ax被設(shè)置為了restart_syscall()系統(tǒng)調(diào)用號(hào);因此,用戶不會(huì)重新啟動(dòng)被信號(hào)中斷的同一個(gè)系統(tǒng)調(diào)用。此錯(cuò)誤碼只有與時(shí)間有關(guān)的系統(tǒng)調(diào)用使用,這些系統(tǒng)調(diào)用重新啟動(dòng)時(shí),應(yīng)該調(diào)整其用戶態(tài)參數(shù)。典型的例子是nanosleep()系統(tǒng)調(diào)用(參考第6章的動(dòng)態(tài)定時(shí)器的應(yīng)用:nanosleep()系統(tǒng)調(diào)用):假設(shè)進(jìn)程調(diào)用它來(lái)暫停執(zhí)行20毫秒,隨后過(guò)了10毫秒之后發(fā)生了一個(gè)信號(hào)。如果系統(tǒng)調(diào)用還和平常一樣重啟,那么,總的延時(shí)時(shí)間會(huì)超過(guò)30毫秒。
相反,如果nanosleep()系統(tǒng)調(diào)用服務(wù)例程被中斷,則使用特殊服務(wù)例程的地址填充current進(jìn)程的thread_info結(jié)構(gòu)體的restart_block字段,并返回ERESTART_RESTARTBLOCK錯(cuò)誤碼。sys_restart_syscall()服務(wù)例程只執(zhí)行前面特殊的服務(wù)里程,計(jì)算首次調(diào)用和重新啟動(dòng)之間經(jīng)過(guò)的時(shí)間,從而調(diào)整延時(shí)。
3.2 重新啟動(dòng)caught信號(hào)中斷的系統(tǒng)調(diào)用
如果信號(hào)需要捕獲處理,handle_signal()分析錯(cuò)誤碼,根據(jù)sigaction中的SA_RESTART標(biāo)志,判斷是否需要重啟:
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; } }
如果必須重新啟動(dòng)系統(tǒng)調(diào)用,handle_signal()的處理方式與do_signal()完全相同;否則,它會(huì)向用戶進(jìn)程返回一個(gè)-EINTR錯(cuò)誤碼。
4 x86_64架構(gòu)-do_signal()
Linux內(nèi)核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c:
/* *注意init是一個(gè)特殊進(jìn)程:它不會(huì)收到不想處理的信號(hào)。所以,即使錯(cuò)誤地發(fā)送 *`SIGKILL`信號(hào)給它,也不會(huì)殺死它。 */ intdo_signal(structpt_regs*regs,sigset_t*oldset) { structk_sigactionka; siginfo_tinfo; intsignr; /*如果不是返回到用戶態(tài),則直接返回。*/ if((regs->cs&3)!=3){ return1; } //...省略 if(!oldset) oldset=¤t->blocked; signr=get_signal_to_deliver(&info,&ka,regs,NULL); if(signr>0){ /* *在將信號(hào)傳遞到用戶空間之前重新啟動(dòng)多有觀察點(diǎn)。 *如果觀察點(diǎn)在內(nèi)核內(nèi)部觸發(fā),寄存器將被清除。 */ if(current->thread.debugreg7) asmvolatile("movq%0,%%db7"::"r"(current->thread.debugreg7)); /*傳遞信號(hào)*/ handle_signal(signr,&info,&ka,oldset,regs); return1; } no_signal: /*是否是系統(tǒng)調(diào)用*/ //...省略(見(jiàn)前面第3.1節(jié)的處理) return0; }
x86-64架構(gòu)的handle_signal()函數(shù)(文件位置:arch/x86_64/kernel/signal.c):
staticvoid handle_signal(unsignedlongsig,siginfo_t*info,structk_sigaction*ka, sigset_t*oldset,structpt_regs*regs) { //...省略(調(diào)試信號(hào)信息) //省略(被中斷的系統(tǒng)調(diào)用的相關(guān)處理) //省略(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架構(gòu)的handle_signal()函數(shù)(文件位置:arch/i386/kernel/signal.c):
staticvoid handle_signal(unsignedlongsig,siginfo_t*info,structk_sigaction*ka, sigset_t*oldset,structpt_regs*regs) { //省略(被中斷的系統(tǒng)調(diào)用的相關(guān)處理) /*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&&sig32)) ????????????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); }
-
內(nèi)核
+關(guān)注
關(guān)注
3文章
1372瀏覽量
40277 -
Linux
+關(guān)注
關(guān)注
87文章
11292瀏覽量
209328 -
信號(hào)
+關(guān)注
關(guān)注
11文章
2789瀏覽量
76730 -
函數(shù)
+關(guān)注
關(guān)注
3文章
4327瀏覽量
62571
原文標(biāo)題:Linux內(nèi)核-信號(hào)的傳遞過(guò)程
文章出處:【微信號(hào):嵌入式ARM和Linux,微信公眾號(hào):嵌入式ARM和Linux】歡迎添加關(guān)注!文章轉(zhuǎn)載請(qǐng)注明出處。
發(fā)布評(píng)論請(qǐng)先 登錄
相關(guān)推薦
評(píng)論