訊號提供了一種通知進程系統事件發生的機制,它也是作為使用者進程之間通訊和同步的 一種原始機制。在進程遷移的情況下,如何處理訊號呢?這部分介紹了MOSIX系統對訊號 機制的處理。 LINUX訊號機制訊號是非同步進程間通訊機制,是在軟體層次上對中斷機制的一種類比。LINUX核心的信 號機制符合POSIX.4的規定,這是POSIX.1標準的一個超集。 每個進程的task_struct結構中都有個指標sig,指向一個singal_struct結構,結構中的 數組action[]相當於一個訊號向量表,每個元素確定了進程接收到一個具體的訊號時應 該採取的行動。 struct signal_struct { atomic_t count; struct k_sigaction action[_NSIG]; spinlock_t siglock; }; 那麼系統如何判斷一個進程是否有訊號在等待處理呢?這是通過task_struct結構中的si gpending成員。task_struct結構中的blocked成員則為屏蔽訊號的集合,pending成員則 為訊號隊列,每產生一個訊號則把它掛入這個隊列,訊號位元影像signal也儲存在其中。 使用者常常要自己定義對訊號的處理常式,並且使用者的處理函數是位於使用者空間的。LINUX 提供了系統調用signal(sys_signal)和sigaction(sys_sigaction 或sys_rt_sigacti on)為訊號設定處理向量。使用者佈建訊號處理的時機我們是不能確定的,可以在進程遷 移前,也可以在進程遷移之後,進程可以在不同的節點間多次遷移,因此,如何保證信 號不被丟失並且都能被正確處理就很重要。並且我們注意到,進程在遷移時,並不將信 號向量表遷移到目標進程,而只是將進程的非同步訊號和強制訊號資訊傳送到目標進程【 參見mig_send_misc()和mig_do_receive_misc()】。 struct asig_h { unsigned int sigs;/*訊號*/ int nforced;/*核心發送的強制訊號的個數*/ }; struct mosix_task {。。。。。。。 uint32_t asig; /*到達REMOTE的訊號 */ siginfo_t *forced_sigs; /* REMOTE強制訊號資訊*/ int nforced_sigs; /* REMOTE強制訊號的個數 */ short sigmig; /* 遷移時接收的訊號 */ } int mig_send_misc(int credit) { struct mig_misc_h m; register struct task_struct *p = current; ……………….. m.asig.sigs = p->mosix.asig; m.asig.nforced = p->mosix.nforced_sigs; forced_sigs = p->mosix.forced_sigs; sti(); if(comm_send(MIG_MISC, &m, sizeof(m), forced_sigs, m.asig.nforced * sizeof(siginfo_t), 0)) ……………… } 因此,我們可以說進程訊號處理的狀態是保留在DEPUTY方的。這樣做也是很自然的。首 先在MOSIX中,對於REMOTE進程,幾乎所有的系統調用都是請求DEPUTY來處理,和訊號相 關的一些系統調用也不了例外。例如,sigprocmask()改變本進程得訊號屏蔽位元影像,sigp ending()檢查有哪些訊號已到達而未被處理,signal()和sigaction()安裝訊號處 理程式。其次,在不少核心操作中,進程進入睡眠以後剛被喚醒時,都會活動訊號的存 在從而提前返回到使用者空間。而DEPUTTY和REMOTE可以分別看作對系統上下文和使用者上下 文的抽象,所以DEPUTY保留著訊號處理的狀態。 訊號響應 對訊號的檢測和響應總是發生在系統空間,通常發生在兩種情況下:第一,當前進程由 於系統調用、中斷或異常而進入系統空間以後,從系統空間返回到使用者空間的前夕。第 二,當前進程在核心中進入睡眠以後剛被喚醒時,由於訊號的存在而提前返回到使用者空 間。 當進程由於中斷進入系統空間以後,中斷處理常式服務完後,將會轉到入口ret_from_in tr 。當進程由於異常而進入系統空間後,將會跳到error_code從而最終轉到ret_from_e xception處理【參見entry.S】。如果中斷或異常發生於使用者空間,則轉移到ret_check_ reschedule,否則發生於核心空間,則到達restore_all。當進程由於系統調用進入系統 空間,將最終走到ret_from_sys_call。 ENTRY(ret_from_sys_call) 。。。。。 ret_check_reschedule: cli # need_resched and signals atomic test cmpl $0,need_resched(%ebx)/*判斷是否需要調度*/ jne reschedule cmpl $0,sigpending(%ebx) /*判斷是否有懸掛的訊號/ jne signal_return /*如果有訊號待處理,則跳到signal_return */ straight_to_mosix: call SYMBOL_NAME(mosix_pre_usermode_actions) testl %eax,%eax jne ret_from_sys_call restore_all: RESTORE_ALL ALIGN signal_return: sti # 開中斷 handler testl $(VM_MASK),EFLAGS(%esp)/*是否處於VM86模式*/ movl %esp,%eax jne v86_signal_return /*是VM86模式的話,則轉到v86_signal_return */ call SYMBOL_NAME(do_signal) /* do_signal 對訊號進行處理*/ jmp straight_to_mosix 從代碼中我們可以看到,如果有訊號待處理,則在退出系統空間前,會跳到signal_retu rn,調用do_signal處理訊號。 我們看看do_signal ,它對訊號作出具體的反應。如果當前進程是REMOTE,它只是簡單 的返回0。否則,該函數根據當前進程的signal域,確定進程收到了那些訊號。對進程收 到的每一個訊號,從進程的訊號等待隊列中找到該訊號對應的附加資訊,從進程的sig域 的action數組中找到訊號的處理常式及其相關的資訊。於是,如果使用者佈建了訊號處理 程式(在使用者空間中),則最終會通過函數handle_signal()準備好對處理常式的執行 。 使用者提供的訊號處理常式是在使用者空間執行的,而且執行完畢以後還還要回到系統空間 。LINUX實現的機制如下: 使用者空間堆棧中為訊號處理常式的執行預先建立一個架構,架構中包括一個作為局部量 的資料結構,並把系統空間的"原始架構"儲存在這個資料結構中 在訊號處理常式中插入對系統調用sigreturn()的調用 將系統空間堆棧中"原始架構"修改成為執行訊號處理常式所需的架構 "返回"到使用者空間,但是卻執行訊號處理常式 訊號處理常式執行完畢後,通過系統調用sigreturn()重返系統空間 在系統調用sigreturn()中從使用者空間恢複"原始架構" 最後再返回到使用者空間,繼續執行原先的使用者程式 對於本地進程,這是在handle_signal()中由setup_rt_frame()或setup_frame()作出安 排的。但是,對於DEPUTY進程,則是通過mosix_deputy_setup_frame()實現的。因為, 我們已經知道,DEPUTY是永遠運行在核心態中的;進程遷移後,程式碼片段和資料區段等都完 全遷移到遠程REMOTE進程。因此,訊號處理常式必然是在REMOTE進程上啟動並執行。那麼, 這又是如何?的呢。 mosix_deputy_setup_frame()通過deputy_request()函數向REMOTE發送DEP_SETUPFRAM E請求,REMOTE將在remote_wait()函數中接收到該請求,調用remote_setup_frame() 來響應該請求。REMOTE進程在remote_setup_frame()中,根據DEPUTY傳來的參數,通 過setup_rt_frame()或setup_frame()安排好一個架構。 這樣,當REMOTE進程從系統空間返回到使用者空間時,將執行訊號處理常式。然後,將通 過sigreturn()系統調用重返系統空間。sys_sigreturn()的作用就是從使用者空間執行信 號處理常式的架構中恢複當初系統空間中的原始架構。它通過restore_sigcontext() 恢複架構的。但是對於DEPUTY進程,則是通過mosix_deputy_restore_sigcontext()函 數來恢複系統空間的原始架構的【參見sys_sigreturn()】。這裡,因為是REMOTE進程調 用sigreturn()系統調用,因此根據我們前面對系統調用的分析,REMOTE進程向DEPUTY進 程發送REM_SYSCALL請求,DEPUTY將在通過deputy_syscall()函數中調用sys_sigretur n()來響應該請求。 mosix_deputy_restore_sigcontext()則向REMOTE進程發送DEP_RESTORESIGCONTEXT請 求。REMOTE在向DEPUTY發送REM_SYSCALL請求後,將處於remote_wait()迴圈中等待REM_S YSCALL請求的應答【參見remote_standard_system_call()】。REMOTE在remote_wait() 中接收到DEP_RESTORESIGCONTEXT請求後,則通過remote_restore_sigcontext()函數調 用restore_sigcontext()真正恢複核心空間的原始架構。此後,當REMOTE進程從系統 空間返回後,將回到訊號處理前原先的使用者空間處繼續往下執行。 訊號發送 發送一個訊號給進程可以在使用者空間通過系統調用發送,如通過sys_kill和 syr_rt_sigqueueinfo調用發送。核心也可以通過force_sig()和force_sig_info()向進 程強制發送訊號,將屏蔽位強制清除,不允許目標進程忽略該訊號。 在使用者空間向一個進程發送訊號由系統調用sys_kill()實現。該函數調用函數kill_some thing_info(),它根據情況,或者向單個進程發送訊號(kill_proc_info()),或者向一 個進程組中的所有進程發送訊號(kill_pg_info()), 最終都會調用函數send_sig_info( )來完成真正的訊號發送。kill_pg_info()中,通過for_each_local_task(p)來尋找屬於 同組的進程。這是因為MOSIX中, 訊號只會發給本地進程,而不會發送給REMOTE進程的 。對於REMOTE進程,當通過kill()發送訊號時,根據我們前面對系統調用的分析,我 們知道最終將是由DEPUTY來調用sys_kill()。因此,訊號是被掛入DEPUTY的task_struct 結構中的pending隊列裡。 非同步和強制訊號的處理 核心也會向進程發送訊號,例如當頁面異常而又無法恢複時,do_page_fault()頁面異 常處理常式會通過force_sig()zx向當前進程發送一個SIGBUS訊號。核心發送的訊號一般 都是需要立即作出反應的。MOSIX對系統發送訊號的處理方式也和使用者發送訊號有所不同 ,核心發送的"強制"訊號都儲存在mosix_task的forced_sigs指標中。 我們首先看看函數force_sig_info()。如果訊號的目的地為REMOTE進程,則: 如果處於中斷服務中,則系統panic 得到進程已有的強制訊號數(n= t->mosix.nforced_sigs)並試圖分配記憶體(x = kmalloc((n + 1) * sizeof(siginfo_t), GFP_KERNEL))用於儲存這n+1個訊號資訊 如果記憶體配置失敗,則盡量再次發送該訊號send_sig(sig, t, 0);返回0 因為分配記憶體返回時,可能已經處理了一部分訊號,因此要進行檢測。如果是,則釋放 剛剛申請的記憶體,跳到第二步 將儲存的siginfo_t和新的info拷貝到分配的記憶體中,儲存在mosix結構的forced_sigs中 ,並增加強制訊號計數t->mosix.forced_sigs = x; t->mosix.nforced_sigs++; 我們前面已經分析過,REMOTE進程從系統空間返回到使用者態之前,將會調用remote_pre_ usermode_actions()函數。remote_pre_usermode_actions()函數將會檢測當前進程是否 有非同步或"強制"訊號待處理。如果有,它會通過函數transfer_signals_to_deputy() 發送REM_ASIG請求將訊號傳遞給DEPUTY處理【參見remote_pre_usermode_actions()】。 DEPUTY則會通過函數deputy_analyse_remote_signals()來處理REM_ASIG請求。它首先 從串連中獲得"強制"訊號資訊,通過force_sig_info()向當前進程(即DEPUTY本身) 發送強制資訊。然後得到每個訊號,依次處理,一般都是通過send_sig發給當前進程。
|