昨日進入 minit
之後再進到 minitSignals
,看完了針對 signal 使用的堆疊如何設置。
minitSignalMask
func minitSignalMask() {
nmask := getg().m.sigmask
for i := range sigtable {
if !blockableSig(uint32(i)) {
sigdelset(&nmask, i)
}
}
sigprocmask(_SIG_SETMASK, &nmask, nil)
}
從當前 goroutine 所屬的 thread(M)當中取得 sigmask
成員的內容這個動作,意味著存取先前存下來的 mask。我們正在初始化的這個階段,拿到的 nmask
內容全部都是零。另一個有趣的內容是 sigtable
陣列,裡面有 GO 語言重新包裝 signal 的方法,列舉一些例子如下:
var sigtable = [...]sigTabT{
/* 0 */ {0, "SIGNONE: no trap"},
/* 1 */ {_SigNotify + _SigKill, "SIGHUP: terminal line hangup"},
/* 2 */ {_SigNotify + _SigKill, "SIGINT: interrupt"},
/* 3 */ {_SigNotify + _SigThrow, "SIGQUIT: quit"},
/* 4 */ {_SigThrow + _SigUnblock, "SIGILL: illegal instruction"},
/* 5 */ {_SigThrow + _SigUnblock, "SIGTRAP: trace trap"},
/* 6 */ {_SigNotify + _SigThrow, "SIGABRT: abort"},
...
將原本的 Unix signal 拆解、重新定義成 signal handler 處理時的參考,應該也算是一種創舉吧? blockableSig
函式的判斷內容如下:
func blockableSig(sig uint32) bool {
flags := sigtable[sig].flags
if flags&_SigUnblock != 0 {
return false
}
if isarchive || islibrary {
return true
}
return flags&(_SigKill|_SigThrow) == 0
}
也就是說,要是
flags
當中包含了 _SigUnblock
,就立刻表明這個 signal 是不可以被 mask 阻擋的。這些 signal 都是同步的(synchrounous,跟隨當前的 執行緒一起執行卻出了狀況所發出的 signal,如上面列表的 SIGILL 與 SIGTRAP),而且以 GO 語言的處理方法來講會使之成為 panic。_SigKill
或是 _SigThrow
。如果是不可阻擋的那些 signal 都會進入 sigdelset
函式,將那些 signal 自 mask 中註銷掉。跑完迴圈之後,sigprocmask
將處理好的 nmask
設為所需使用的 signal mask,然後繼續。
mstart1
函式 // Install signal handlers; after minit so that minit can
// prepare the thread to be able to handle the signals.
if _g_.m == &m0 {
mstartm0()
}
在當前的 goroutine 的所屬執行緒是 m0
的情況下進入 mstartm0
函式,正式啟用在此之前的 signal 處理設定,其中最關鍵的是 initsig
函式
func mstartm0() {
...
initsig(false)
}
...
func initsig(preinit bool) {
if !preinit {
// It's now OK for signal handlers to run.
signalsOK = true
}
// For c-archive/c-shared this is called by libpreinit with
// preinit == true.
if (isarchive || islibrary) && !preinit {
return
}
preinit
在這裡是一個幫助我們理解的關鍵字,代表我們是否正透過 libpreinit
函式呼叫來執行。這裡顯然不是,所以傳入的參數是否的布林值。然而,在 GO 程式被編譯成函式庫型態的時候(-buildmode=c-archive
或 -buildmode=c-shared
),runtime/asm_amd64.s
中的 _rt0_amd64_lib
函式就會被作為全域的建構子被呼叫,裡面會呼叫到 initsig(true)
。無論如何,這裡我們只會進入第一個判斷式,將 signalOK
設起來。
for i := uint32(0); i < _NSIG; i++ {
t := &sigtable[i]
fwdSig[i] = getsig(i)
if !sigInstallGoHandler(i) {
...
continue
}
handlingSig[i] = 1
setsig(i, funcPC(sighandler))
}
}
這個迴圈一樣會跑過所有 signal。fwdSig
是一個陣列,紀錄現在的 GO 程式控制 signal 的策略(fwd 本身是 forward 的意思);它所賦值的來源 getsig
函式會使用 sigaction
系統呼叫,取得指定的 signal 的相關設定。中段的判斷部份在處理是否要為了這個 signal 自行安裝 handler,但是現在的初始狀況完全不會進到這個部份。handlingSig
陣列用來紀錄每一個 signal 是否正在使用 GO 語言的 handler,之後的執行中有一些場合(如 disable signal)會將這個值設成 0。最後的 setsig
函式,也使用了 sigaction
設定目前為止的配置到核心裡面。
看起來會用到
sigtramp
與sigreturn
兩個函式。關於sighandler
的詳細運作,有機會再追蹤進去。
mstart1
函式 if fn := _g_.m.mstartfn; fn != nil {
fn()
}
if _g_.m != &m0 {
acquirep(_g_.m.nextp.ptr())
_g_.m.nextp = 0
}
schedule()
再來是如果所屬的執行緒有合法的 mstartfn
成員的話,就執行 fn
函式。下面的判斷式則是與前一段相反,必須不是系統初始執行緒才會進來作。我們現在的初始化情況,這兩個判斷都不會生效,於是直接進入 schedule
函式。
今日簡單瀏覽一下最後一部份的 signal setup,看到 GO 語言如何管理不同的 signal,以及相當重視與 C 語言之間的界面。