|
1 執(zhí)行信號(hào)的默認(rèn)動(dòng)作2 捕獲信號(hào)3 系統(tǒng)調(diào)用的重新執(zhí)行4 x86_64架構(gòu)-do_signal()
前面我們已經(jīng)介紹了內(nèi)核注意到信號(hào)的到來,調(diào)用相關(guān)函數(shù)更新進(jìn)程描述符以便進(jìn)程接收處理信號(hào)。但是,如果目標(biāo)進(jìn)程此時(shí)沒有運(yùn)行,內(nèi)核則推遲傳遞信號(hào),F(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
用來保存阻塞信號(hào)位掩碼數(shù)組的變量地址。
對(duì)do_signal()的描述,主要集中在信號(hào)傳遞的通用機(jī)制;真實(shí)的代碼中涵蓋了許多細(xì)節(jié),比如處理競態(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)
return 1;
如果oldset是NULL,使用current->blocked的地址初始化它:
if (!oldset)
oldset = ¤t->blocked;
do_signal()的核心是一段循環(huán)代碼,重復(fù)調(diào)用dequeue_signal()函數(shù),直到私有和共享掛起信號(hào)隊(duì)列沒有被阻塞的掛起信號(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];
依賴具體內(nèi)容,可能執(zhí)行三類動(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;
接下來的兩節(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)處理也非常簡單:
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è)置為TASK_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)更改為TASK_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í)行該程序。通過調(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;
return 1;
如果接收的信號(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ù)捻樞蛱幚怼?br />
執(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說明了捕獲信號(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)后,開始執(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í)行。
nobefnnx2fu64026734047.png (70.53 KB, 下載次數(shù): 0)
下載附件
保存到相冊(cè)
nobefnnx2fu64026734047.png
昨天 07:43 上傳
圖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ù),依賴于信號(hào)的sigaction表中sa_flags字段的SA_SIGINFO標(biāo)志。
接下來,我們看一下setup_frame()函數(shù)的具體實(shí)現(xiàn):(Linux內(nèi)核版本是v2.6.11,文件位置:arch/x86_64/kernel/signal.c)
/* 這些符號(hào)的定義在vsyscall內(nèi)存頁中,查看vsyscall-sigreturn.S文件*/
extern void __user __kernel_sigreturn;
extern void __user __kernel_rt_sigreturn;
static void setup_frame(int sig, struct k_sigaction *ka,
sigset_t *set, struct pt_regs * regs)
{
void __user *restorer;
struct sigframe __user *frame;
int err = 0;
int usig;
frame = get_sigframe(ka, regs, sizeof(*frame));
if (!access_ok(VERIFY_WRITE, frame, sizeof(*frame)))
goto give_sigsegv;
usig = current_thread_info()->exec_domain
&& current_thread_info()->exec_domain->signal_invmap
&& sig 32
? current_thread_info()->exec_domain->signal_invmap[sig]
: sig;
err = __put_user(usig, &frame->sig);
if (err)
goto give_sigsegv;
err = setup_sigcontext(&frame->sc, &frame->fpstate, regs, set->sig[0]);
if (err)
goto give_sigsegv;
if (_NSIG_WORDS > 1) {
err = __copy_to_user(&frame->extramask, &set->sig[1],
sizeof(frame->extramask));
if (err)
goto give_sigsegv;
}
restorer = &__kernel_sigreturn;
if (ka->sa.sa_flags & SA_RESTORER)
restorer = ka->sa.sa_restorer;
/* Set up to return from userspace. */
err |= __put_user(restorer, &frame->pretcode);
/*
* This is popl %eax ; movl $,%eax ; int $0x80
*
* WE DO NOT USE IT ANY MORE! It's only left here for historical
* reasons and because gdb uses it as a signature to notice
* signal handler stack frames.
*/
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)
goto give_sigsegv;
/* 為信號(hào)處理程序配置寄存器 */
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;
regs->eax = (unsigned long) sig;
regs->edx = (unsigned long) 0;
regs->ecx = (unsigned long) 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è)稱為frame的數(shù)據(jù)結(jié)構(gòu)壓倒用戶態(tài)棧中,該數(shù)據(jù)結(jié)構(gòu)存儲(chǔ)著處理信號(hào)和能夠正確返回到sys_sigreturn()函數(shù)的所需要信息。frame是一個(gè)sigframe表,包含以下字段(參見圖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ù)類型為sigcontext(這些信息是從current的內(nèi)核態(tài)棧中拷貝而來)。另外,它還包含一個(gè)進(jìn)程阻塞信號(hào)的位數(shù)組。fpstate
用來保存用戶態(tài)進(jìn)程的浮點(diǎn)寄存器信息,數(shù)據(jù)結(jié)構(gòu)類型為_fpstate。(參見第3章的保存和加載FPU、MMX和XMM寄存器)。extramask
指定阻塞實(shí)時(shí)信號(hào)的位數(shù)組。retcode
發(fā)起sigreturn()系統(tǒng)調(diào)用的8字節(jié)代碼。在Linux早期版本中,這段代碼用來從信號(hào)處理程序返回;但Linux 2.6版本以后,僅用作符號(hào)簽名,以便調(diào)試器可以識(shí)別信號(hào)的棧幀。
0fy3ebflhda64026734147.png (55.74 KB, 下載次數(shù): 0)
下載附件
保存到相冊(cè)
0fy3ebflhda64026734147.png
昨天 07:43 上傳
圖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ī)信號(hào)處理非常類似。因?yàn)樵趚86架構(gòu)上,棧是向下增長的,所以,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)存頁上的一段匯編代碼地址。(參見第10章的通過sysenter指令發(fā)起系統(tǒng)調(diào)用的一節(jié))
接下來,修改內(nèi)核態(tài)棧的regs內(nèi)容,保證當(dāng)current切換到用戶態(tài)時(shí),CPU控制權(quán)能夠傳遞給信號(hào)處理程序:
regs->esp = (unsigned long) frame;
regs->eip = (unsigned long) ka->sa.sa_handler;
regs->eax = (unsigned long) 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)值而終止,F(xiàn)在,信號(hào)處理程序所需的信息都在用戶態(tài)棧頂位置了。
setup_rt_frame()函數(shù)與setup_frame()類似,但是它把一個(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)存頁中的__kernel_rt_sigreturn代碼段。
2.2 計(jì)算信號(hào)標(biāo)志配置完用戶態(tài)棧后,handle_signal()函數(shù)檢查與該信號(hào)相關(guān)的標(biāo)志。如果該信號(hào)沒有設(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)存位置。于是,開始執(zhí)行信號(hào)處理程序。
2.4 終止信號(hào)處理程序當(dāng)信號(hào)處理程序執(zhí)行完成時(shí),其棧頂?shù)姆祷氐刂分赶騰syscall內(nèi)存頁(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(類型為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)處理程序的過程中,內(nèi)核態(tài)棧已經(jīng)被破壞,所以需要重新建立一個(gè)臨時(shí)內(nèi)核態(tài)棧,數(shù)據(jù)來源就是用戶態(tài)棧中配置的frame數(shù)據(jù)結(jié)構(gòu)。
asmlinkage int sys_sigreturn(unsigned long __unused)
{
/* 建立進(jìn)程在內(nèi)核態(tài)的臨時(shí)棧 */
struct pt_regs *regs = (struct pt_regs *) &__unused;
// 內(nèi)核態(tài)棧中用戶存儲(chǔ)
struct sigframe __user *frame = (struct sigframe __user *)(regs->esp - 8);
sigset_t set;
int eax;
/* 驗(yàn)證`frame`數(shù)據(jù)結(jié)構(gòu)是否正確 */
if (verify_area(VERIFY_READ, frame, sizeof(*frame)))
goto badframe;
/* 處理實(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))))
goto badframe;
/* 將在信號(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))
goto badframe;
return eax;
/* 錯(cuò)誤數(shù)據(jù)處理 */
badframe:
force_sig(SIGSEGV, current);
return 0;
}
sys_sigreturn()函數(shù)計(jì)算出regs(類型為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ì)列中。
接下來,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ī)制與上面類似。擴(kuò)展幀中的pretcode字段指向__kernel_rt_sigreturn標(biāo)簽處的匯編代碼(位于vsyscall內(nèi)存頁),這段代碼會(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)核在沒有完成系統(tǒng)調(diào)用的情況下將進(jìn)程置為TASK_RUNNING(參考第4章的從中斷和異常返回一節(jié))。當(dāng)進(jìn)程想要切換回用戶態(tài),同時(shí)信號(hào)傳遞過來時(shí),系統(tǒng)調(diào)用服務(wù)例程還沒有完成其工作,所以會(huì)返回錯(cuò)誤碼EINTR、ERESTARTNOHAND、ERESTART_RESTARTBLOCK、ERESTARTSYS、ERESTARTNOINTR。
事實(shí)上,在這種場景下,用戶態(tài)進(jìn)程能得到的錯(cuò)誤碼只能是EINTR,這意味著系統(tǒng)調(diào)用還沒有完成。應(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ù)語說明如下:
Terminate
系統(tǒng)調(diào)用將不會(huì)自動(dòng)重新執(zhí)行;進(jìn)程將切換到用戶態(tài)下int $0x80或sysenter之后的指令處繼續(xù)執(zhí)行,同時(shí),通過寄存器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)行為EINTRERESTARTSYSERESTARTNOHAND
ERESTART_RESTARTBLOCK*ERESTARTNOINTRDefaultTerminateReexecuteReexecuteReexecuteIgnoreTerminateReexecuteReexecuteReexecuteCatchTerminateDependsTerminateReexecuteERESTARTNOHAND和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é)長度,所以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)用它來暫停執(zhí)行20毫秒,隨后過了10毫秒之后發(fā)生了一個(gè)信號(hào)。如果系統(tǒng)調(diào)用還和平常一樣重啟,那么,總的延時(shí)時(shí)間會(huì)超過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)過的時(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ì)殺死它。
*/
int do_signal(struct pt_regs *regs, sigset_t *oldset)
{
struct k_sigaction ka;
siginfo_t info;
int signr;
/* 如果不是返回到用戶態(tài),則直接返回。 */
if ((regs->cs & 3) != 3) {
return 1;
}
// ...省略
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)
asm volatile("movq %0,%%db7" : : "r" (current->thread.debugreg7));
/* 傳遞信號(hào) */
handle_signal(signr, &info, &ka, oldset, regs);
return 1;
}
no_signal:
/* 是否是系統(tǒng)調(diào)用 */
// ...省略(見前面第3.1節(jié)的處理)
return 0;
}
x86-64架構(gòu)的handle_signal()函數(shù)(文件位置:arch/x86_64/kernel/signal.c):
static void
handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
sigset_t *oldset, struct pt_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):
static void
handle_signal(unsigned long sig, siginfo_t *info, struct k_sigaction *ka,
sigset_t *oldset, struct pt_regs * regs)
{
// 省略(被中斷的系統(tǒng)調(diào)用的相關(guān)處理)
/* Set up the stack frame */
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);
}
}
static void setup_rt_frame(int sig, struct k_sigaction *ka, siginfo_t *info,
sigset_t *set, struct pt_regs * regs)
{
struct rt_sigframe __user *frame;
struct _fpstate __user *fp = NULL;
int err = 0;
struct task_struct *me = current;
if (used_math()) {
fp = get_stack(ka, regs, sizeof(struct _fpstate));
frame = (void __user *)round_down((unsigned long)fp - sizeof(struct rt_sigframe), 16) - 8;
if (!access_ok(VERIFY_WRITE, fp, sizeof(struct _fpstate))) {
goto give_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) {
goto give_sigsegv;
}
}
/* Create the ucontext. */
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));
}
/* Set up to return from userspace. If provided, use a stub
already in userspace. */
/* x86-64 should always use SA_RESTORER. */
if (ka->sa.sa_flags & SA_RESTORER) {
err |= __put_user(ka->sa.sa_restorer, &frame->pretcode);
} else {
/* could use a vstub here */
goto give_sigsegv;
}
if (err) {
goto give_sigsegv;
}
#ifdef DEBUG_SIG
printk("%d old rip %lx old rsp %lx old rax %lx
", current->pid,regs->rip,regs->rsp,regs->rax);
#endif
/* Set up registers for signal handler */
{
struct exec_domain *ed = current_thread_info()->exec_domain;
if (unlikely(ed && ed->signal_invmap && sig 32))
sig = ed->signal_invmap[sig];
}
regs->rdi = sig;
/* In case the signal handler was declared without prototypes */
regs->rax = 0;
/* This also works for non SA_SIGINFO handlers because they expect the
next argument after the signal number on the stack. */
regs->rsi = (unsigned long)&frame->info;
regs->rdx = (unsigned long)&frame->uc;
regs->rip = (unsigned long) ka->sa.sa_handler;
regs->rsp = (unsigned long)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;
}
}
#ifdef DEBUG_SIG
printk("SIG deliver (%s:%d): sp=%p pc=%p ra=%p
",
current->comm, current->pid, frame, regs->rip, frame->pretcode);
#endif
return;
give_sigsegv:
force_sigsegv(sig, current);
}
歡迎交流,可以掃描下面二維碼,關(guān)注本公眾號(hào)。
ejyf43fdndy64026734247.jpg (18 KB, 下載次數(shù): 0)
下載附件
保存到相冊(cè)
ejyf43fdndy64026734247.jpg
昨天 07:43 上傳
|
|