前面我們已經介紹了內核注意到信號的到來,調用相關函數更新進程描述符以便進程接收處理信號。但是,如果目標進程此時沒有運行,內核則推遲傳遞信號。現在,我們看看內核如何處理進程掛起的信號。
正如第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())。當系統調用終止時,正常程序繼續其執行。
圖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 &&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; /*為信號處理程序配置寄存器*/ 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版本以后,僅用作符號簽名,以便調試器可以識別信號的棧幀。
圖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&&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); }
-
內核
+關注
關注
3文章
1410瀏覽量
41118 -
Linux
+關注
關注
87文章
11465瀏覽量
212830 -
信號
+關注
關注
11文章
2843瀏覽量
77928 -
函數
+關注
關注
3文章
4371瀏覽量
64229
原文標題:Linux內核-信號的傳遞過程
文章出處:【微信號:嵌入式ARM和Linux,微信公眾號:嵌入式ARM和Linux】歡迎添加關注!文章轉載請注明出處。
發布評論請先 登錄
Linux內核中信號詳解

Linux內核地址映射模型與Linux內核高端內存詳解

Linux內核源碼分析--內核啟動命令行的傳遞過程
你了解u-boot與linux內核間的參數傳遞過程?

BootLoader與Linux內核的參數傳遞
LINUX內核的信號量設計與實現
LINUX內核的信號量設計與實現
BootLoader與Linux內核的參數傳遞詳細資料說明

評論