<ruby id="bdb3f"></ruby>

    <p id="bdb3f"><cite id="bdb3f"></cite></p>

      <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
        <p id="bdb3f"><cite id="bdb3f"></cite></p>

          <pre id="bdb3f"></pre>
          <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

          <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
          <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

          <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                <ruby id="bdb3f"></ruby>

                ThinkChat2.0新版上線,更智能更精彩,支持會話、畫圖、視頻、閱讀、搜索等,送10W Token,即刻開啟你的AI之旅 廣告
                # 6.6 信號處理機制 我們已經知道了 Go 運行時調度以 Goroutine 的方式調度了所有用戶態代碼。 每個 Goroutine 都有可能在不同的線程上重新被執行。 那么如果用戶態的某個 Goroutine 需要接收系統信號, 如何才能確保某個線程的信號能夠正確的發送到可能在其他線程上執行的監聽信號的 Goroutine 呢? 本節我們討論調度器里涉及的 signal 信號處理機制。 ## 6.6.1 信號與軟中斷 信號機制是 UNIX、類 UNIX 甚至其他 POSIX 兼容系統上規定的一種進程異步通信的限制形式。 用于提醒某個事件的發生狀態。 信號被定義為整數,產生信號的條件包括用戶使用某些按鍵組合(比如 Control + C)、 硬件異常、`kill`信號等等。 這些信號通常有三種不同的處理方式:忽略、捕獲或者執行系統的默認行為。 忽略與捕獲處理無法處理`SIGKILL`和`SIGSTOP`,默認處理通常為停止進程。 而對于捕獲處理而言,當信號發生時,操作系統將中斷用戶代碼,并保存其執行的上下文,切換到內核空間 并重新切換到用戶空間來執行預先設置好的信號處理回調。當回調執行完畢之后,會重新切換回內核 空間,并從中斷的位置進行恢復,如圖 1 所示。 ![](https://golang.design/under-the-hood/assets/signal-handler.png)**圖 1: 用戶空間與內核空間的信號處理流程** 早期的 UNIX 系統特性是,當某個進程執行一個長時間系統調用時發生阻塞, 如果此時捕獲到一個信號,則系統調用便被中斷且不再執行,并以失敗返回。 因此系統調用也會被分為兩類:低速系統調用和高速系統調用。低速系統調用可能使進程永遠阻塞, 例如 IO。 系統調用`sigaltstack`可以用于定義一個備用的信號棧來獲取一個存在的額外信號棧的狀態。 一個額外的信號棧會在信號處理執行中進行使用。 每個進程都包含一個信號屏蔽字(signal mask),規定了當前要阻塞遞送到該進程的信號集。 對于每種可能的信號,屏蔽字中都有一位與之對應。 對于某種信號,若其對應位置已設置,則它當前是被阻塞的。 如果要檢測和修改當前信號屏蔽字,則需要調用`sigprocmask`系統調用來進行。通過`_SIG_SETMASK`可以直接設置想要的屏蔽字,并獲得原先的屏蔽字。 值得注意的是,信號會在所有線程中進行共享。 換句話說,盡管某個線程可以阻止某些信號,但當線程修改了某個信號相關的處理行為后, 所有線程都必須共享這個行為帶來的變化。 如果一個線程選擇忽略某個信號,則其他線程可以恢復信號的默認處理行為,或者設置為信號設置 一個新的處理函數,進而能夠撤銷線程的信號選擇。 進程中信號會被傳遞給單個線程,如果信號與硬件故障或計時器超時有關, 信號便會被發送到引起事件的線程中去,而其他的信號則被發送到任一個線程。 Linux 上的線程以獨立進程進行實現,通過`clone`調用來共享資源,因此 Linux 上的信號 處理與類 Unix 系統略有不同,POSIX.1 線程模型會在異步信號發送到進程后, 且沒有阻塞屏蔽字時接受信號。而 Linux 上每個線程作為獨立進程執行,系統無法選擇沒有 阻塞屏蔽字的線程,進而無法注意到這個信號。所以 Linux 上也存在不可靠信號和可靠信號的 概念。其中不可靠信號可能丟失,多次發送相同的信號只能收到一次,取值從 1 至 31; 可靠信號則可以進行排隊,取值從 32 至 64。 這便是運行時信號處理的基本原理。 ## 6.6.2 處理函數的初始化 [6.3 調度循環](https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/exec)中討論過了 M 的生命周期,M 可以在兩種情況下被創建: 1. 程序運行之初的 M0,無需創建已經存在的系統線程,只需對其進行初始化即可。其函數調用鏈如下所示: ~~~ schedinit ? mcommoninit ? mpreinit ? msigsave ? initSigmask ? mstart ~~~ 2. 需要時創建的 M,某些特殊情況下一定會創建一個新的 M 并進行初始化,而后創建系統線程。這些情況包括: 1. startm 時沒有空閑 m 2. startTemplateThread 時 3. startTheWorldWithSema 時 p 如果沒有 m 4. main 時創建系統監控 5. oneNewExtraM 時 其調用鏈為: ~~~ newm ? allocm ? mcommoninit ? mpreinit ? newm1 ? newosproc ? mstart ~~~ 在`mcommoninit`里,會在一個父線程(或引導時的主線程)上調用`mpreinit`,并最終會為一個 M 創建`gsignal`,是一個在 M 上用于處理信號的 Goroutine。因此,除了 g0 外,其實第一個創建的 g 應該是它, 但是它并沒有設置 Goid (Goroutine ID): ``` func mcommoninit(mp *m) { ... // 初始化 gsignal,用于處理 m 上的信號。 mpreinit(mp) // gsignal 的運行棧邊界處理 if mp.gsignal != nil { mp.gsignal.stackguard1 = mp.gsignal.stack.lo + _StackGuard } ... } // 從一個父線程上進行調用(引導時為主線程),可以分配內存 func mpreinit(mp *m) { mp.gsignal = malg(32 * 1024) // OS X 需要 &gt;= 8K,此處創建處理 singnal 的 g mp.gsignal.m = mp // 指定 gsignal 擁有的 m } ``` ### 獲取原始信號屏蔽字 在調度器的初始化的階段,`initSigmask`目標旨在記錄主線程 M0 創建之初的屏蔽字`sigmask`: ``` func schedinit() { _g_ := getg() ... mcommoninit(_g_.m) ... msigsave(_g_.m) initSigmask = _g_.m.sigmask ... } ``` 其中`msigsave`通過`sigprocmask`這個系統調用將當前`m0`的屏蔽字保存到`mp.sigmask`上: ``` const _SIG_SETMASK = 3 // msigsave 將當前線程的信號屏蔽字保存到 mp.sigmask。 //go:nosplit //go:nowritebarrierrec func msigsave(mp *m) { sigprocmask(_SIG_SETMASK, nil, &amp;mp.sigmask) } ``` `sigprocmask`的本質為系統調用,其返回值通過`old`交付給調用者: ``` type sigset uint32 //go:nosplit //go:nowritebarrierrec func sigprocmask(how int32, new, old *sigset) { rtsigprocmask(how, new, old, int32(unsafe.Sizeof(*new))) } //go:noescape func rtsigprocmask(how int32, new, old *sigset, size int32) ``` `rtsigprocmask`在 Linux 上由匯編直接包裝`rt_sigprocmask`調用: ``` TEXT runtime·rtsigprocmask(SB),NOSPLIT,$0-28 MOVL how+0(FP), DI MOVQ new+8(FP), SI MOVQ old+16(FP), DX MOVL size+24(FP), R10 MOVL $SYS_rt_sigprocmask, AX SYSCALL CMPQ AX, $0xfffffffffffff001 JLS 2(PC) MOVL $0xf1, 0xf1 // crash RET ``` 注意,`rt_sigprocmask`只適用于單個線程的調用,多線程上的調用時未定義行為, 不過初始化階段的此時還未創建其他線程,因此此調用時安全的。 在 Darwin 系統中,所有的信號處理函數均通過`pthread_sigmask`來完成: ``` //go:nosplit //go:cgo_unsafe_args func sigprocmask(how uint32, new *sigset, old *sigset) { libcCall(unsafe.Pointer(funcPC(sigprocmask_trampoline)), unsafe.Pointer(&amp;how)) } func sigprocmask_trampoline() ``` ``` TEXT runtime·sigprocmask_trampoline(SB),NOSPLIT,$0 PUSHQ BP MOVQ SP, BP MOVQ 8(DI), SI // arg 2 new MOVQ 16(DI), DX // arg 3 old MOVL 0(DI), DI // arg 1 how CALL libc_pthread_sigmask(SB) TESTL AX, AX JEQ 2(PC) MOVL $0xf1, 0xf1 // crash POPQ BP RET ``` `msigsave`執行完畢后,`sigmask`最后保存到`initSigmask`這一全局變量中, 用于初始化新創建的 M 的信號屏蔽字: ``` // 用于新創建的 M 的信號掩碼 signal mask 的值。 var initSigmask sigset func schedinit() { ... initSigmask = _g_.m.sigmask ... } ``` 用于當新創建 M 時(`newm`),將 M 的`sigmask`進行設置。 ### 初始化信號棧 在進入`mstart`后,調用鏈關系就變成了: ~~~ mstart ? mstart1 ? minit ? mstartm0 (僅當 m0 調用) ? schedule ? mexit ? sigblock ? unminit ~~~ `mstart1`會調用`minit`進行初始化: ``` func minit() { minitSignals() ... } func minitSignals() { minitSignalStack() minitSignalMask() } ``` M 在初始化過程中,會判定當前線程是否設置了備用信號棧, 正常情況下一個新創建的 M 是沒有備用信號棧的。 如果沒有,則會將`m.gsignal`的執行棧設置為備用信號棧,用于處理產生的信號。 另一種情況是,當使用 cgo 時,非 Go 線程可能調用 Go 代碼, 而這時用戶態的 C 代碼可能已經為非 Go 線程設置了信號棧,這時的替換必須小心。 因此如果 M 已經存在了備用信號棧,則會將現有的信號棧保存到`m.goSigStack`中。 ``` type stackt struct { // 信號棧 ss_sp *byte ss_flags int32 pad_cgo_0 [4]byte ss_size uintptr } // 如果沒有為線程設置備用信號棧(正常情況),則將備用信號棧設置為 gsignal 棧。 // 如果為線程設置了備用信號棧(非 Go 線程設置備用信號棧然后調用 Go 函數的情況), // 則將 gsignal 棧設置為備用信號棧。 // 如果沒有使用 cgo 我們還設置了額外的 gsignal 信號棧(無論其是否已經被設置) // 記錄在 newSigstack 中做出的選擇, // 以便可以在 unminit 中撤消。 func minitSignalStack() { _g_ := getg() // 獲取原有的信號棧 var st stackt sigaltstack(nil, &amp;st) if st.ss_flags&amp;_SS_DISABLE != 0 { // 如果禁用了當前的信號棧 // 則將 gsignal 的執行棧設置為備用信號棧 signalstack(&amp;_g_.m.gsignal.stack) _g_.m.newSigstack = true } else { // 否則將 m 的 gsignal 棧設置為從 sigaltstack 返回的備用信號棧 setGsignalStack(&amp;st, &amp;_g_.m.goSigStack) _g_.m.newSigstack = false } } // 將 s 設置為備用信號棧,此方法僅在信號棧被禁用時調用 //go:nosplit func signalstack(s *stack) { st := stackt{ss_size: s.hi - s.lo} setSignalstackSP(&amp;st, s.lo) sigaltstack(&amp;st, nil) } //go:nosplit func setSignalstackSP(s *stackt, sp uintptr) { *(*uintptr)(unsafe.Pointer(&amp;s.ss_sp)) = sp } // setGsignalStack 將當前 m 的 gsignal 棧設置為從 sigaltstack 系統調用返回的備用信號堆棧。 // 它將舊值保存在 *old 中以供 restoreGsignalStack 使用。 // 如果非 Go 代碼設置了,則在處理信號時使用備用棧。 //go:nosplit //go:nowritebarrierrec func setGsignalStack(st *stackt, old *gsignalStack) { g := getg() if old != nil { old.stack = g.m.gsignal.stack old.stackguard0 = g.m.gsignal.stackguard0 old.stackguard1 = g.m.gsignal.stackguard1 old.stktopsp = g.m.gsignal.stktopsp } stsp := uintptr(unsafe.Pointer(st.ss_sp)) g.m.gsignal.stack.lo = stsp g.m.gsignal.stack.hi = stsp + st.ss_size g.m.gsignal.stackguard0 = stsp + _StackGuard g.m.gsignal.stackguard1 = stsp + _StackGuard } ``` ### 初始化信號屏蔽字 當設置好信號棧后,會開始對 M 設置信號的屏蔽字,通過`sigmask`來獲得當前 M 的屏蔽字,而后通過遍歷所有運行時信號表來對屏蔽字進行初始化: ``` func minitSignalMask() { nmask := getg().m.sigmask // 遍歷整個信號表 for i := range sigtable { // 判斷某個信號是否為不可阻止的信號, // 如果是不可阻止的信號,則刪除對應的屏蔽字所在位 if !blockableSig(uint32(i)) { sigdelset(&amp;nmask, i) } } // 重新設置屏蔽字 sigprocmask(_SIG_SETMASK, &amp;nmask, nil) } // 判斷某個信號是否為不可阻止的信號 // 1. 當信號是非阻塞信號,則不可阻止 // 2. 當改程序為模塊時,則可阻止 // 3. 當信號為 Kill 或 Throw 時,可阻止,否則不可阻止 func blockableSig(sig uint32) bool { flags := sigtable[sig].flags if flags&amp;_SigUnblock != 0 { return false } if isarchive || islibrary { return true } return flags&amp;(_SigKill|_SigThrow) == 0 } func sigdelset(mask *sigset, i int) { *mask &amp;^= 1 &lt;&lt; (uint32(i) - 1) } ``` ``` type sigTabT struct { flags int32 name string } var sigtable = [...]sigTabT{ /* 0 */ {0, "SIGNONE: no trap"}, /* 1 */ {_SigNotify + _SigKill, "SIGHUP: terminal line hangup"}, ... /* 63 */ {_SigNotify, "signal 63"}, /* 64 */ {_SigNotify, "signal 64"}, } const ( _SigNotify = 1 &lt;&lt; iota // let signal.Notify have signal, even if from kernel _SigKill // if signal.Notify doesn't take it, exit quietly _SigThrow // if signal.Notify doesn't take it, exit loudly _SigPanic // if the signal is from the kernel, panic _SigDefault // if the signal isn't explicitly requested, don't monitor it _SigGoExit // cause all runtime procs to exit (only used on Plan 9). _SigSetStack // add SA_ONSTACK to libc handler _SigUnblock // always unblock; see blockableSig _SigIgn // _SIG_DFL action is to ignore the signal ) ``` ## 6.6.3 信號處理 萬事俱備,只欠東風。信號處理相關的初始化已經完成,包括了信號的屏蔽字、信號棧等。 正式進入調度循環之前,在 M0 上將調用`mstartm0`,進而調用`initsig`初始化信號,針對每個信號進行單獨處理: ``` //go:yeswritebarrierrec func mstartm0() { ... initsig(false) } //go:nosplit //go:nowritebarrierrec func initsig(preinit bool) { ... for i := uint32(0); i &lt; _NSIG; i++ { t := &amp;sigtable[i] if t.flags == 0 || t.flags&amp;_SigDefault != 0 { continue } // 此時不需要原子操作,因為此時沒有其他運行的 Goroutine fwdSig[i] = getsig(i) // 檢查該信號是否需要設置 signal handler if !sigInstallGoHandler(i) { // 即使不設置 signal handler,在必要時設置 SA_ONSTACK if fwdSig[i] != _SIG_DFL &amp;&amp; fwdSig[i] != _SIG_IGN { setsigstack(i) } else if fwdSig[i] == _SIG_IGN { sigInitIgnored(i) } continue } handlingSig[i] = 1 setsig(i, funcPC(sighandler)) } } ``` 對于一個需要設置`sighandler`的信號,會通過`setsig`來設置信號對應的動作(action): ``` //go:nosplit //go:nowritebarrierrec func setsig(i uint32, fn uintptr) { var sa usigactiont sa.sa_flags = _SA_SIGINFO | _SA_ONSTACK | _SA_RESTART sa.sa_mask = ^uint32(0) if fn == funcPC(sighandler) { if iscgo { fn = funcPC(cgoSigtramp) } else { fn = funcPC(sigtramp) } } *(*uintptr)(unsafe.Pointer(&amp;sa.__sigaction_u)) = fn sigaction(i, &amp;sa, nil) } ``` 值得注意的是這里有一個特殊處理,當`fn`為`sighandler`時候, 產生信號后的動作并非直接調用`sighandler`,而是被替換為了`sigtramp`: ``` TEXT runtime·sigtramp(SB),NOSPLIT,$72 ... MOVQ DX, ctx-56(SP) MOVQ SI, info-64(SP) MOVQ DI, signum-72(SP) MOVQ $runtime·sigtrampgo(SB), AX CALL AX ... RET ``` 進而調用`sigtrampgo`。這樣的處理方式是因為,`sighandler`會將產生的信號交給對應的 g ,此時還無法決定究竟誰來進行處理。 因此,當信號發生時,而`sigtrampgo`會被調用: ``` //go:nosplit //go:nowritebarrierrec func sigtrampgo(sig uint32, info *siginfo, ctx unsafe.Pointer) { if sigfwdgo(sig, info, ctx) { return } ... setg(g.m.gsignal) ... sighandler(sig, info, ctx, g) setg(g) ... } ``` 而`sigfwdgo`用于約定該信號是否應該由 Go 進行處理, 如果不由 Go 進行處理(例如 cgo)則將其轉發到 Go 代碼之前設置的 handler 上。 我們暫時關注 Go 端的情況,代碼會繼續執行,將 g 設置為`gsignal`,從而來到了`sighandler`: ``` //go:nowritebarrierrec func sighandler(sig uint32, info *siginfo, ctxt unsafe.Pointer, gp *g) { _g_ := getg() c := &amp;sigctxt{info, ctxt} // profile 時鐘超時 if sig == _SIGPROF { sigprof(c.sigpc(), c.sigsp(), c.siglr(), gp, _g_.m) return } if sig == _SIGTRAP &amp;&amp; testSigtrap != nil &amp;&amp; testSigtrap(info, (*sigctxt)(noescape(unsafe.Pointer(c))), gp) { return } // 用戶信號 if sig == _SIGUSR1 &amp;&amp; testSigusr1 != nil &amp;&amp; testSigusr1(gp) { return } if sig == sigPreempt { // 可能是一個搶占信號 doSigPreempt(gp, c) // 即便這是一個搶占信號,它也可能與其他信號進行混合,因此我們 // 繼續進行處理。 } flags := int32(_SigThrow) if sig &lt; uint32(len(sigtable)) { flags = sigtable[sig].flags } if flags&amp;_SigPanic != 0 &amp;&amp; gp.throwsplit { // 我們無法安全的 sigpanic 因為它可能造成棧的增長,因此忽略它 flags = (flags &amp;^ _SigPanic) | _SigThrow } ... if c.sigcode() != _SI_USER &amp;&amp; flags&amp;_SigPanic != 0 { // 產生 panic 的信號 ... c.preparePanic(sig, gp) return } // 對用戶注冊的信號進行轉發 if c.sigcode() == _SI_USER || flags&amp;_SigNotify != 0 { if sigsend(sig) { return } } // 設置為可忽略的用戶信號 if c.sigcode() == _SI_USER &amp;&amp; signal_ignored(sig) { return } // 處理 KILL 信號 if flags&amp;_SigKill != 0 { dieFromSignal(sig) } // 非 THROW,返回 if flags&amp;_SigThrow == 0 { return } // 處理一些直接 panic 的情況 ... } ``` 注意,在信號處理中,當信號為`sigPreempt`時,將觸發運行時的異步搶占機制,我們會在[6.8 協作與搶占](https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/preemption)一節中進行討論。 函數`sigsend`會將用戶信號發送到信號隊列`sig`中: ``` var sig struct { note note mask [(_NSIG + 31) / 32]uint32 wanted [(_NSIG + 31) / 32]uint32 ignored [(_NSIG + 31) / 32]uint32 recv [(_NSIG + 31) / 32]uint32 state uint32 delivering uint32 inuse bool } func sigsend(s uint32) bool { bit := uint32(1) &lt;&lt; uint(s&amp;31) if !sig.inuse || s &gt;= uint32(32*len(sig.wanted)) { return false } atomic.Xadd(&amp;sig.delivering, 1) // We are running in the signal handler; defer is not available. if w := atomic.Load(&amp;sig.wanted[s/32]); w&amp;bit == 0 { atomic.Xadd(&amp;sig.delivering, -1) return false } // Add signal to outgoing queue. for { mask := sig.mask[s/32] if mask&amp;bit != 0 { atomic.Xadd(&amp;sig.delivering, -1) return true // signal already in queue } if atomic.Cas(&amp;sig.mask[s/32], mask, mask|bit) { break } } // Notify receiver that queue has new bit. Send: for { switch atomic.Load(&amp;sig.state) { default: throw("sigsend: inconsistent state") case sigIdle: if atomic.Cas(&amp;sig.state, sigIdle, sigSending) { break Send } case sigSending: // notification already pending break Send case sigReceiving: if atomic.Cas(&amp;sig.state, sigReceiving, sigIdle) { notewakeup(&amp;sig.note) break Send } } } atomic.Xadd(&amp;sig.delivering, -1) return true } ``` 用戶信號的接收方是通過 os/signal 完成的,我們隨后討論。 ## 6.6.4 輔 M 線程 輔 M 是一個用于服務非 Go 線程(cgo 產生的線程)回調的 M。 ``` //go:yeswritebarrierrec func mstartm0() { // 創建一個額外的 M 服務 non-Go 線程(cgo 調用中產生的線程)的回調,并且只創建一個 // windows 上也需要額外 M 來服務 syscall.NewCallback 產生的回調,見 issue #6751 if (iscgo || GOOS == "windows") &amp;&amp; !cgoHasExtraM { cgoHasExtraM = true newextram() } initsig(false) } // newextram 分配一個 m 并將其放入 extra 列表中 // 它會被工作中的本地 m 調用,因此它能夠做一些調用 schedlock 和 allocate 類似的事情。 func newextram() { c := atomic.Xchg(&amp;extraMWaiters, 0) if c &gt; 0 { for i := uint32(0); i &lt; c; i++ { oneNewExtraM() } } else { // 確保至少有一個額外的 M mp := lockextra(true) unlockextra(mp) if mp == nil { oneNewExtraM() } } } // onNewExtraM 分配一個 m 并將其放入 extra list 中 func oneNewExtraM() { mp := allocm(nil, nil) gp := malg(4096) gp.sched.pc = funcPC(goexit) + sys.PCQuantum gp.sched.sp = gp.stack.hi gp.sched.sp -= 4 * sys.RegSize gp.sched.lr = 0 gp.sched.g = guintptr(unsafe.Pointer(gp)) gp.syscallpc = gp.sched.pc gp.syscallsp = gp.sched.sp gp.stktopsp = gp.sched.sp gp.gcscanvalid = true gp.gcscandone = true casgstatus(gp, _Gidle, _Gdead) gp.m = mp mp.curg = gp mp.lockedInt++ mp.lockedg.set(gp) gp.lockedm.set(mp) gp.goid = int64(atomic.Xadd64(&amp;sched.goidgen, 1)) ... // 給垃圾回收器使用 allgadd(gp) atomic.Xadd(&amp;sched.ngsys, +1) // 將 m 添加到 extra m 鏈表中 mnext := lockextra(true) mp.schedlink.set(mnext) extraMCount++ unlockextra(mp) } ``` ## 6.6.5 對`os/signal`包的支持 我們已經看到了用戶注冊的信號會通過`sigsend`進行發送,這就是我們使用`os/signal`包的核心。 在使用`os/signal`后,會調用`signal.init`函數,懶惰的注冊一個用戶端的信號處理循環(當調用 Notify 時啟動): ``` var ( watchSignalLoopOnce sync.Once watchSignalLoop func() ) func init() { signal_enable(0) // 首次調用,進行初始化 watchSignalLoop = loop } func loop() { for { process(syscall.Signal(signal_recv())) } } ``` 這個`signal_enable`和`signal_recv`用于激活運行時的信號隊列,并從中接受信號: ``` // 啟用運行時信號隊列 //go:linkname signal_enable os/signal.signal_enable func signal_enable(s uint32) { if !sig.inuse { // The first call to signal_enable is for us // to use for initialization. It does not pass // signal information in m. sig.inuse = true // enable reception of signals; cannot disable noteclear(&amp;sig.note) return } if s &gt;= uint32(len(sig.wanted)*32) { return } w := sig.wanted[s/32] w |= 1 &lt;&lt; (s &amp; 31) atomic.Store(&amp;sig.wanted[s/32], w) i := sig.ignored[s/32] i &amp;^= 1 &lt;&lt; (s &amp; 31) atomic.Store(&amp;sig.ignored[s/32], i) sigenable(s) } // 從信號隊列中接受信號 //go:linkname signal_recv os/signal.signal_recv func signal_recv() uint32 { for { // Serve any signals from local copy. for i := uint32(0); i &lt; _NSIG; i++ { if sig.recv[i/32]&amp;(1&lt;&lt;(i&amp;31)) != 0 { sig.recv[i/32] &amp;^= 1 &lt;&lt; (i &amp; 31) return i } } // Wait for updates to be available from signal sender. Receive: for { switch atomic.Load(&amp;sig.state) { default: throw("signal_recv: inconsistent state") case sigIdle: if atomic.Cas(&amp;sig.state, sigIdle, sigReceiving) { notetsleepg(&amp;sig.note, -1) noteclear(&amp;sig.note) break Receive } case sigSending: if atomic.Cas(&amp;sig.state, sigSending, sigIdle) { break Receive } } } // Incorporate updates from sender into local copy. for i := range sig.mask { sig.recv[i] = atomic.Xchg(&amp;sig.mask[i], 0) } } } ``` 當接受到信號后,信號`sig`會被發送到用戶在`Ignore/Notify/Stop`上所注冊的 channel 上: ``` func process(sig os.Signal) { n := signum(sig) if n &lt; 0 { return } handlers.Lock() defer handlers.Unlock() for c, h := range handlers.m { if h.want(n) { // 發送 select { case c &lt;- sig: default: } } } ... // Stop 的處理 } ``` 例如`signal.Notify`,將信號 channel 注冊到 handler 全局變量中: ``` var handlers struct { sync.Mutex m map[chan&lt;- os.Signal]*handler ref [numSig]int64 stopping []stopping } func Notify(c chan&lt;- os.Signal, sig ...os.Signal) { if c == nil { panic("os/signal: Notify using nil channel") } watchSignalLoopOnce.Do(func() { if watchSignalLoop != nil { go watchSignalLoop() } }) handlers.Lock() defer handlers.Unlock() h := handlers.m[c] if h == nil { if handlers.m == nil { handlers.m = make(map[chan&lt;- os.Signal]*handler) } h = new(handler) handlers.m[c] = h // 保存到 handler 中 } add := func(n int) { if n &lt; 0 { return } if !h.want(n) { h.set(n) if handlers.ref[n] == 0 { enableSignal(n) } handlers.ref[n]++ } } if len(sig) == 0 { for n := 0; n &lt; numSig; n++ { add(n) } } else { for _, s := range sig { add(signum(s)) } } } ``` ## 6.6.6 小結 由于調度器在 Go 程序運行時的特殊地位,以及在進行跨語言調用時需要`cgo`的支持, 運行時信號處理相對而言還是較為復雜的,需要一套完整的機制來對各種情況進行處理, 甚至對用戶態代碼的`os/signal`進行支持。當然,信號處理的功能遠不止如此, 利用此信號機制還可以實現搶占式調度,我們將在[6.7 協作與搶占](https://golang.design/under-the-hood/zh-cn/part2runtime/ch06sched/preemption)中 再來討論這一機制的另一巨大作用。
                  <ruby id="bdb3f"></ruby>

                  <p id="bdb3f"><cite id="bdb3f"></cite></p>

                    <p id="bdb3f"><cite id="bdb3f"><th id="bdb3f"></th></cite></p><p id="bdb3f"></p>
                      <p id="bdb3f"><cite id="bdb3f"></cite></p>

                        <pre id="bdb3f"></pre>
                        <pre id="bdb3f"><del id="bdb3f"><thead id="bdb3f"></thead></del></pre>

                        <ruby id="bdb3f"><mark id="bdb3f"></mark></ruby><ruby id="bdb3f"></ruby>
                        <pre id="bdb3f"><pre id="bdb3f"><mark id="bdb3f"></mark></pre></pre><output id="bdb3f"></output><p id="bdb3f"></p><p id="bdb3f"></p>

                        <pre id="bdb3f"><del id="bdb3f"><progress id="bdb3f"></progress></del></pre>

                              <ruby id="bdb3f"></ruby>

                              哎呀哎呀视频在线观看