Go創建daemon程序
daemon的概念
守護進程(daemon)就是一直在后臺運行的進程,它沒有控制終端,無法和前臺的用戶交互。當我們打開一個終端時會創建一個session會話(shell),從用戶登錄開始到用戶退出為止,這段時間內在該終端執行的進程都屬于這一個會話。一個會話一般包含一個前臺進程組、一個后臺進程組和一個會話首進程(shell程序本身)。 例如用以下命令啟動5個進程:
$ proc1 | proc2 &
$ proc3 | proc4 | proc5
proc1和proc2屬于同一個后臺進程組,proc3、proc4、proc5屬于同一個前臺進程組,Shell進程本身屬于一個單獨的進程組。這些進程組的控制終端相同,它們屬于同一個session。 "后臺任務"與"前臺任務"的本質區別只有一個:是否繼承標準輸入。后臺任務不再繼承當前 session 的標準輸入(stdin),你無法向后臺任務輸入指令了,但是后臺任務繼承了標準輸出(stdout)和標準錯誤(stderr)后臺任務的所有輸出依然會同步地在命令行下顯示
當終端關閉或者檢測到網絡連接斷開時會將掛斷信號(SIGHUP)發送給終端控制進程(會話期首進程,shell進程)。如果會話期首進程接收到SIGHUP信號后會終止,會同時給前臺進程組發送SIGHUP信號(進程接收到SIGHUP信號默認處理是退出),shell的 huponexit 參數
(shopt | grep huponexit)
決定了shell退出時是否發送SIGHUP信號給后臺進程組。
如何實現守護進程
守護進程要與從啟動它的父進程(一般是shell程序)的運行環境隔離開來,需要處理的內容大致包括會話、控制終端、進程組、文件描述符、文件權限掩碼以及工作目錄等。
void init_daemon()
{
pid_t pid;
int i = 0;
// 1. 創建子進程,父進程退出,父進程退出子進程變成孤兒進程,孤兒進程由init進程(pid為1)收養
if ((pid = fork()) == -1) {
printf("Fork error !\n");
exit(1);
}
if (pid != 0) {
exit(0); // 父進程退出
}
// 2. 子進程調用setsid創建新會話,成為成為會話首進程,并且子進程成為一個新進程組的組長進程,而新的會話也脫離了控制終端的控制。
//(組長進程調用 setsid會出錯,所以第一步fork出子進程,fork出的子進程一定不會是組長進程)
setsid();
//3. 子進程變成無終端的會話首進程,但是它仍然可以重新申請打開一個控制終端。可以通過再次創建子進程結束當前進程,使進程不再是會話首進程來禁止進程重新打開控制終端。
if ((pid = fork()) == -1) {
printf("Fork error !\n");
exit(-1);
}
if (pid != 0) {
exit(0);
}
//4. 改變當前目錄為根目錄,重設文件權限掩碼,關閉文件描述符
//因為這幾樣東西都是繼承自父進程的
chdir("/tmp"); // 改變工作目錄
umask(0); // 重設文件掩碼
for (; i < getdtablesize(); ++i) {
close(i); // 關閉打開的文件描述符
}
return;
}
</code></pre>
go語言如何實現守護進程
目前Go程序還不能實現daemon,因為go程序在啟動時runtime可能會創建多個線程(用于內存管理,垃圾回收,goroutine管理等),而fork并不能處理好擁有多個線程的進程。
d := flag.Bool("d", false, "Whether or not to launch in the background(like a daemon)")
if *d {
cmd := exec.Command(os.Args[0],
"-close-fds",
)
serr, err := cmd.StderrPipe()
if err != nil {
log.Fatalln(err)
}
err = cmd.Start()
if err != nil {
log.Fatalln(err)
}
s, err := ioutil.ReadAll(serr)
s = bytes.TrimSpace(s)
if bytes.HasPrefix(s, []byte("addr: ")) {
fmt.Println(string(s))
cmd.Process.Release()
} else {
cmd.Process.Kill()
}
}
`
參考資料
來自: http://shanks.leanote.com/post/Go創建daemon程序