<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>

                ??一站式輕松地調用各大LLM模型接口,支持GPT4、智譜、豆包、星火、月之暗面及文生圖、文生視頻 廣告
                # 第?34?章?終端、作業控制與守護進程 **目錄** + [1\. 終端](ch34s01.html) + [1.1\. 終端的基本概念](ch34s01.html#id2890359) + [1.2\. 終端登錄過程](ch34s01.html#id2891132) + [1.3\. 網絡登錄過程](ch34s01.html#id2891618) + [2\. 作業控制](ch34s02.html) + [2.1\. Session與進程組](ch34s02.html#id2892071) + [2.2\. 與作業控制有關的信號](ch34s02.html#id2892541) + [3\. 守護進程](ch34s03.html) ## 1.?終端 ### 1.1.?終端的基本概念 在UNIX系統中,用戶通過終端登錄系統后得到一個Shell進程,這個終端成為Shell進程的控制終端(Controlling Terminal),在[第?1?節 “引言”](ch30s01.html#process.intro)講過,控制終端是保存在PCB中的信息,而我們知道`fork`會復制PCB中的信息,因此由Shell進程啟動的其它進程的控制終端也是這個終端。默認情況下(沒有重定向),每個進程的標準輸入、標準輸出和標準錯誤輸出都指向控制終端,進程從標準輸入讀也就是讀用戶的鍵盤輸入,進程往標準輸出或標準錯誤輸出寫也就是輸出到顯示器上。此外在[第?33?章 _信號_](ch33.html#signal)還講過,在控制終端輸入一些特殊的控制鍵可以給前臺進程發信號,例如Ctrl-C表示`SIGINT`,Ctrl-\表示`SIGQUIT`。 在[第?28?章 _文件與I/O_](ch28.html#io)中講過,每個進程都可以通過一個特殊的設備文件`/dev/tty`訪問它的控制終端。事實上每個終端設備都對應一個不同的設備文件,`/dev/tty`提供了一個通用的接口,一個進程要訪問它的控制終端既可以通過`/dev/tty`也可以通過該終端設備所對應的設備文件來訪問。`ttyname`函數可以由文件描述符查出對應的文件名,該文件描述符必須指向一個終端設備而不能是任意文件。下面我們通過實驗看一下各種不同的終端所對應的設備文件名。 **例?34.1.?查看終端對應的設備文件名** ``` #include <unistd.h> #include <stdio.h> int main() { printf("fd 0: %s\n", ttyname(0)); printf("fd 1: %s\n", ttyname(1)); printf("fd 2: %s\n", ttyname(2)); return 0; } ``` 在圖形終端窗口下運行這個程序,可能會得到 ``` $ ./a.out fd 0: /dev/pts/0 fd 1: /dev/pts/0 fd 2: /dev/pts/0 ``` 再開一個終端窗口運行這個程序,可能又會得到 ``` $ ./a.out fd 0: /dev/pts/1 fd 1: /dev/pts/1 fd 2: /dev/pts/1 ``` 用Ctrl-Alt-F1切換到字符終端運行這個程序,結果是 ``` $ ./a.out fd 0: /dev/tty1 fd 1: /dev/tty1 fd 2: /dev/tty1 ``` 讀者可以再試試在Ctrl-Alt-F2的字符終端下或者在`telnet`或`ssh`登陸的網絡終端下運行這個程序,看看結果是什么。 ### 1.2.?終端登錄過程 一臺PC通常只有一套鍵盤和顯示器,也就是只有一套終端設備,但是可以通過Ctrl-Alt-F1~Ctrl-Alt-F6切換到6個字符終端,相當于有6套虛擬的終端設備,它們共用同一套物理終端設備,對應的設備文件分別是`/dev/tty1`~`/dev/tty6`,所以稱為虛擬終端(Virtual Terminal)。設備文件`/dev/tty0`表示當前虛擬終端,比如切換到Ctrl-Alt-F1的字符終端時`/dev/tty0`就表示`/dev/tty1`,切換到Ctrl-Alt-F2的字符終端時`/dev/tty0`就表示`/dev/tty2`,就像`/dev/tty`一樣也是一個通用的接口,但它不能表示圖形終端窗口所對應的終端。 再舉個例子,做嵌入式開發時經常會用到串口終端,目標板的每個串口對應一個終端設備,比如`/dev/ttyS0`、`/dev/ttyS1`等,將主機和目標板用串口線連起來,就可以在主機上通過Linux的`minicom`或Windows的超級終端工具登錄到目標板的系統。 內核中處理終端設備的模塊包括硬件驅動程序和線路規程(Line Discipline)。 **圖?34.1.?終端設備模塊** ![終端設備模塊](https://box.kancloud.cn/2016-04-02_56ff80da04b7d.png) 硬件驅動程序負責讀寫實際的硬件設備,比如從鍵盤讀入字符和把字符輸出到顯示器,線路規程像一個過濾器,對于某些特殊字符并不是讓它直接通過,而是做特殊處理,比如在鍵盤上按下Ctrl-Z,對應的字符并不會被用戶程序的`read`讀到,而是被線路規程截獲,解釋成`SIGTSTP`信號發給前臺進程,通常會使該進程停止。線路規程應該過濾哪些字符和做哪些特殊處理是可以配置的。 終端設備有輸入和輸出隊列緩沖區,如下圖所示。 **圖?34.2.?終端緩沖** ![終端緩沖](https://box.kancloud.cn/2016-04-02_56ff80da149a3.png) 以輸入隊列為例,從鍵盤輸入的字符經線路規程過濾后進入輸入隊列,用戶程序以先進先出的順序從隊列中讀取字符,一般情況下,當輸入隊列滿的時候再輸入字符會丟失,同時系統會響鈴警報。終端可以配置成回顯(Echo)模式,在這種模式下,輸入隊列中的每個字符既送給用戶程序也送給輸出隊列,因此我們在命令行鍵入字符時,該字符不僅可以被程序讀取,我們也可以同時在屏幕上看到該字符的回顯。 現在我們來看終端登錄的過程: 1、系統啟動時,`init`進程根據配置文件`/etc/inittab`確定需要打開哪些終端。例如配置文件中有這樣一行: ``` 1:2345:respawn:/sbin/getty 9600 tty1 ``` 和`/etc/passwd`類似,每個字段用`:`號隔開。開頭的1是這一行配置的id,通常要和`tty`的后綴一致,配置`tty2`的那一行id就應該是2。第二個字段2345表示運行級別2~5都執行這個配置。最后一個字段`/sbin/getty 9600 tty1`是`init`進程要`fork`/`exec`的命令,打開終端`/dev/tty1`,波特率是9600(波特率只對串口和Modem終端有意義),然后提示用戶輸入帳號。中間的`respawn`字段表示`init`進程會監視`getty`進程的運行狀態,一旦該進程終止,`init`會再次`fork`/`exec`這個命令,所以我們從終端退出登錄后會再次提示輸入帳號。 有些新的Linux發行版已經不用`/etc/inittab`這個配置文件了,例如Ubuntu用`/etc/event.d`目錄下的配置文件來配置`init`。 2、`getty`根據命令行參數打開終端設備作為它的控制終端,把文件描述符0、1、2都指向控制終端,然后提示用戶輸入帳號。用戶輸入帳號之后,`getty`的任務就完成了,它再執行`login`程序: ``` execle("/bin/login", "login", "-p", username, NULL, envp); ``` 3、`login`程序提示用戶輸入密碼(輸入密碼期間關閉終端的回顯),然后驗證帳號密碼的正確性。如果密碼不正確,`login`進程終止,`init`會重新`fork`/`exec`一個`getty`進程。如果密碼正確,`login`程序設置一些環境變量,設置當前工作目錄為該用戶的主目錄,然后執行Shell: ``` execl("/bin/bash", "-bash", NULL); ``` 注意`argv[0]`參數的程序名前面加了一個`-`,這樣`bash`就知道自己是作為登錄Shell啟動的,執行登錄Shell的啟動腳本。從`getty`開始`exec`到`login`,再`exec`到`bash`,其實都是同一個進程,因此控制終端沒變,文件描述符0、1、2也仍然指向控制終端。由于`fork`會復制PCB信息,所以由Shell啟動的其它進程也都是如此。 ### 1.3.?網絡登錄過程 虛擬終端或串口終端的數目是有限的,虛擬終端一般就是`/dev/tty1`~`/dev/tty6`六個,串口終端的數目也不超過串口的數目。然而網絡終端或圖形終端窗口的數目卻是不受限制的,這是通過偽終端(Pseudo TTY)實現的。一套偽終端由一個主設備(PTY Master)和一個從設備(PTY Slave)組成。主設備在概念上相當于鍵盤和顯示器,只不過它不是真正的硬件而是一個內核模塊,操作它的也不是用戶而是另外一個進程。從設備和上面介紹的`/dev/tty1`這樣的終端設備模塊類似,只不過它的底層驅動程序不是訪問硬件而是訪問主設備。通過[例?34.1 “查看終端對應的設備文件名”](ch34s01.html#jobs.ttyname)的實驗結果可以看到,網絡終端或圖形終端窗口的Shell進程以及它啟動的其它進程都會認為自己的控制終端是偽終端從設備,例如`/dev/pts/0`、`/dev/pts/1`等。下面以`telnet`為例說明網絡登錄和使用偽終端的過程。 **圖?34.3.?偽終端** ![偽終端](https://box.kancloud.cn/2016-04-02_56ff80da25fc1.png) 1. 用戶通過`telnet`客戶端連接服務器。如果服務器配置為獨立(Standalone)模式,則在服務器監聽連接請求是一個`telnetd`進程,它`fork`出一個`telnetd`子進程來服務客戶端,父進程仍監聽其它連接請求。 另外一種可能是服務器端由系統服務程序`inetd`或`xinetd`監聽連接請求,`inetd`稱為Internet Super-Server,它監聽系統中的多個網絡服務端口,如果連接請求的端口號和`telnet`服務端口號一致,則`fork`/`exec`一個`telnetd`子進程來服務客戶端。`xinetd`是`inetd`的升級版本,配置更為靈活。 2. `telnetd`子進程打開一個偽終端設備,然后再經過`fork`一分為二:父進程操作偽終端主設備,子進程將偽終端從設備作為它的控制終端,并且將文件描述符0、1、2指向控制終端,二者通過偽終端通信,父進程還負責和`telnet`客戶端通信,而子進程負責用戶的登錄過程,提示輸入帳號,然后調用`exec`變成`login`進程,提示輸入密碼,然后調用`exec`變成Shell進程。這個Shell進程認為自己的控制終端是偽終端從設備,偽終端主設備可以看作鍵盤顯示器等硬件,而操作這個偽終端的“用戶”就是父進程`telnetd`。 3. 當用戶輸入命令時,`telnet`客戶端將用戶輸入的字符通過網絡發給`telnetd`服務器,由`telnetd`服務器代表用戶將這些字符輸入偽終端。Shell進程并不知道自己連接的是偽終端而不是真正的鍵盤顯示器,也不知道操作終端的“用戶”其實是`telnetd`服務器而不是真正的用戶。Shell仍然解釋執行命令,將標準輸出和標準錯誤輸出寫到終端設備,這些數據最終由`telnetd`服務器發回給`telnet`客戶端,然后顯示給用戶看。 如果`telnet`客戶端和服務器之間的網絡延遲較大,我們會觀察到按下一個鍵之后要過幾秒鐘才能回顯到屏幕上。這說明我們每按一個鍵`telnet`客戶端都會立刻把該字符發送給服務器,然后這個字符經過偽終端主設備和從設備之后被Shell進程讀取,同時回顯到偽終端從設備,回顯的字符再經過偽終端主設備、`telnetd`服務器和網絡發回給`telnet`客戶端,顯示給用戶看。也許你會覺得吃驚,但真的是這樣:每按一個鍵都要在網絡上走個來回! BSD系列的UNIX在`/dev`目錄下創建很多`ptyXX`和`ttyXX`設備文件,`XX`由字母和數字組成,`ptyXX`是主設備,相對應的`ttyXX`是從設備,偽終端的數目取決于內核配置。而在SYS V系列的UNIX上,偽終端主設備是`/dev/ptmx`,“mx”表示Multiplex,意思是多個主設備復用同一個設備文件,每打開一次`/dev/ptmx`,內核就分配一個主設備,同時在`/dev/pts`目錄下創建一個從設備文件,當終端關閉時就從`/dev/pts`目錄下刪除相應的從設備文件。Linux同時支持上述兩種偽終端,目前的標準傾向于SYS V的偽終端。 ## 2.?作業控制 ### 2.1.?Session與進程組 在[第?1?節 “信號的基本概念”](ch33s01.html#signal.intro)中我說過“Shell可以同時運行一個前臺進程和任意多個后臺進程”其實是不全面的,現在我們來研究更復雜的情況。事實上,Shell分前后臺來控制的不是進程而是作業(Job)或者進程組(Process Group)。一個前臺作業可以由多個進程組成,一個后臺作業也可以由多個進程組成,Shell可以同時運行一個前臺作業和任意多個后臺作業,這稱為作業控制(Job Control)。例如用以下命令啟動5個進程(這個例子出自[[APUE2e]](bi01.html#bibli.apue "Advanced Programming in the UNIX Environment")): ``` $ proc1 | proc2 & $ proc3 | proc4 | proc5 ``` 其中`proc1`和`proc2`屬于同一個后臺進程組,`proc3`、`proc4`、`proc5`屬于同一個前臺進程組,Shell進程本身屬于一個單獨的進程組。這些進程組的控制終端相同,它們屬于同一個Session。當用戶在控制終端輸入特殊的控制鍵(例如Ctrl-C)時,內核會發送相應的信號(例如`SIGINT`)給前臺進程組的所有進程。各進程、進程組、Session的關系如下圖所示。 **圖?34.4.?Session與進程組** ![Session與進程組](https://box.kancloud.cn/2016-04-02_56ff80da38e77.png) 現在我們從Session和進程組的角度重新來看登錄和執行命令的過程。 1. `getty`或`telnetd`進程在打開終端設備之前調用`setsid`函數創建一個新的Session,該進程稱為Session Leader,該進程的id也可以看作Session的id,然后該進程打開終端設備作為這個Session中所有進程的控制終端。在創建新Session的同時也創建了一個新的進程組,該進程是這個進程組的Process Group Leader,該進程的id也是進程組的id。 2. 在登錄過程中,`getty`或`telnetd`進程變成`login`,然后變成Shell,但仍然是同一個進程,仍然是Session Leader。 3. 由Shell進程`fork`出的子進程本來具有和Shell相同的Session、進程組和控制終端,但是Shell調用`setpgid`函數將作業中的某個子進程指定為一個新進程組的Leader,然后調用`setpgid`將該作業中的其它子進程也轉移到這個進程組中。如果這個進程組需要在前臺運行,就調用`tcsetpgrp`函數將它設置為前臺進程組,由于一個Session只能有一個前臺進程組,所以Shell所在的進程組就自動變成后臺進程組。 在上面的例子中,`proc3`、`proc4`、`proc5`被Shell放到同一個前臺進程組,其中有一個進程是該進程組的Leader,Shell調用`wait`等待它們運行結束。一旦它們全部運行結束,Shell就調用`tcsetpgrp`函數將自己提到前臺繼續接受命令。但是注意,如果`proc3`、`proc4`、`proc5`中的某個進程又`fork`出子進程,子進程也屬于同一進程組,但是Shell并不知道子進程的存在,也不會調用`wait`等待它結束。換句話說,`proc3 | proc4 | proc5`是Shell的作業,而這個子進程不是,這是作業和進程組在概念上的區別。一旦作業運行結束,Shell就把自己提到前臺,如果原來的前臺進程組還存在(如果這個子進程還沒終止),則它自動變成后臺進程組(回顧一下[例?30.3 “fork”](ch30s03.html#process.simplefork))。 下面看兩個例子。 ``` $ ps -o pid,ppid,pgrp,session,tpgid,comm | cat PID PPID PGRP SESS TPGID COMMAND 6994 6989 6994 6994 8762 bash 8762 6994 8762 6994 8762 ps 8763 6994 8762 6994 8762 cat ``` 這個作業由`ps`和`cat`兩個進程組成,在前臺運行。從`PPID`列可以看出這兩個進程的父進程是`bash`。從`PGRP`列可以看出,`bash`在id為6994的進程組中,這個id等于`bash`的進程id,所以它是進程組的Leader,而兩個子進程在id為8762的進程組中,`ps`是這個進程組的Leader。從`SESS`可以看出三個進程都在同一Session中,`bash`是Session Leader。從`TPGID`可以看出,前臺進程組的id是8762,也就是兩個子進程所在的進程組。 ``` $ ps -o pid,ppid,pgrp,session,tpgid,comm | cat & [1] 8835 $ PID PPID PGRP SESS TPGID COMMAND 6994 6989 6994 6994 6994 bash 8834 6994 8834 6994 6994 ps 8835 6994 8834 6994 6994 cat ``` 這個作業由`ps`和`cat`兩個進程組成,在后臺運行,`bash`不等作業結束就打印提示信息`[1] 8835`然后給出提示符接受新的命令,`[1]`是作業的編號,如果同時運行多個作業可以用這個編號區分,8835是該作業中某個進程的id。請讀者自己分析`ps`命令的輸出結果。 ### 2.2.?與作業控制有關的信號 我們通過實驗來理解與作業控制有關的信號。 ``` $ cat & [1] 9386 $ (再次回車) [1]+ Stopped cat ``` 將`cat`放到后臺運行,由于`cat`需要讀標準輸入(也就是終端輸入),而后臺進程是不能讀終端輸入的,因此內核發`SIGTTIN`信號給進程,該信號的默認處理動作是使進程停止。 ``` $ jobs [1]+ Stopped cat $ fg %1 cat hello(回車) hello ^Z [1]+ Stopped cat ``` `jobs`命令可以查看當前有哪些作業。`fg`命令可以將某個作業提至前臺運行,如果該作業的進程組正在后臺運行則提至前臺運行,如果該作業處于停止狀態,則給進程組的每個進程發`SIGCONT`信號使它繼續運行。參數`%1`表示將第1個作業提至前臺運行。`cat`提到前臺運行后,掛起等待終端輸入,當輸入`hello`并回車后,`cat`打印出同樣的一行,然后繼續掛起等待輸入。如果輸入Ctrl-Z則向所有前臺進程發`SIGTSTP`信號,該信號的默認動作是使進程停止。 ``` $ bg %1 [1]+ cat & [1]+ Stopped cat ``` `bg`命令可以讓某個停止的作業在后臺繼續運行,也需要給該作業的進程組的每個進程發`SIGCONT`信號。`cat`進程繼續運行,又要讀終端輸入,然而它在后臺不能讀終端輸入,所以又收到`SIGTTIN`信號而停止。 ``` $ ps PID TTY TIME CMD 6994 pts/0 00:00:05 bash 11022 pts/0 00:00:00 cat 11023 pts/0 00:00:00 ps $ kill 11022 $ ps PID TTY TIME CMD 6994 pts/0 00:00:05 bash 11022 pts/0 00:00:00 cat 11024 pts/0 00:00:00 ps $ fg %1 cat Terminated ``` 用`kill`命令給一個停止的進程發`SIGTERM`信號,這個信號并不會立刻處理,而要等進程準備繼續運行之前處理,默認動作是終止進程。但如果給一個停止的進程發`SIGKILL`信號就不同了。 ``` $ cat & [1] 11121 $ ps PID TTY TIME CMD 6994 pts/0 00:00:05 bash 11121 pts/0 00:00:00 cat 11122 pts/0 00:00:00 ps [1]+ Stopped cat $ kill -KILL 11121 [1]+ Killed cat ``` `SIGKILL`信號既不能被阻塞也不能被忽略,也不能用自定義函數捕捉,只能按系統的默認動作立刻處理。與此類似的還有`SIGSTOP`信號,給一個進程發`SIGSTOP`信號會使進程停止,這個默認的處理動作不能改變。這樣保證了不管什么樣的進程都能用`SIGKILL`終止或者用`SIGSTOP`停止,當系統出現異常時管理員總是有辦法殺掉有問題的進程或者暫時停掉懷疑有問題的進程。 上面講了如果后臺進程試圖從控制終端讀,會收到`SIGTTIN`信號而停止,如果試圖向控制終端寫呢?通常是允許寫的。如果覺得后臺進程向控制終端輸出信息干擾了用戶使用終端,可以設置一個終端選項禁止后臺進程寫。 ``` $ cat testfile & [1] 11426 $ hello [1]+ Done cat testfile $ stty tostop $ cat testfile & [1] 11428 [1]+ Stopped cat testfile $ fg %1 cat testfile hello ``` 首先用`stty`命令設置終端選項,禁止后臺進程寫,然后啟動一個后臺進程準備往終端寫,這時進程收到一個`SIGTTOU`信號,默認處理動作也是停止進程。 ## 3.?守護進程 Linux系統啟動時會啟動很多系統服務進程,例如[第?1.3?節 “網絡登錄過程”](ch34s01.html#jobs.netlogin)講的`inetd`,這些系統服務進程沒有控制終端,不能直接和用戶交互。其它進程都是在用戶登錄或運行程序時創建,在運行結束或用戶注銷時終止,但系統服務進程不受用戶登錄注銷的影響,它們一直在運行著。這種進程有一個名稱叫守護進程(Daemon)。 下面我們用`ps axj`命令查看系統中的進程。參數`a`表示不僅列當前用戶的進程,也列出所有其他用戶的進程,參數`x`表示不僅列有控制終端的進程,也列出所有無控制終端的進程,參數`j`表示列出與作業控制相關的信息。 ``` $ ps axj PPID PID PGID SID TTY TPGID STAT UID TIME COMMAND 0 1 1 1 ? -1 Ss 0 0:01 /sbin/init 0 2 0 0 ? -1 S< 0 0:00 [kthreadd] 2 3 0 0 ? -1 S< 0 0:00 [migration/0] 2 4 0 0 ? -1 S< 0 0:00 [ksoftirqd/0] ... 1 2373 2373 2373 ? -1 S<s 0 0:00 /sbin/udevd --daemon ... 1 4680 4680 4680 ? -1 Ss 0 0:00 /usr/sbin/acpid -c /etc ... 1 4808 4808 4808 ? -1 Ss 102 0:00 /sbin/syslogd -u syslog ... ``` 凡是`TPGID`一欄寫著-1的都是沒有控制終端的進程,也就是守護進程。在`COMMAND`一列用`[]`括起來的名字表示內核線程,這些線程在內核里創建,沒有用戶空間代碼,因此沒有程序文件名和命令行,通常采用以`k`開頭的名字,表示Kernel。`init`進程我們已經很熟悉了,`udevd`負責維護`/dev`目錄下的設備文件,`acpid`負責電源管理,`syslogd`負責維護`/var/log`下的日志文件,可以看出,守護進程通常采用以`d`結尾的名字,表示Daemon。 創建守護進程最關鍵的一步是調用`setsid`函數創建一個新的Session,并成為Session Leader。 ``` #include <unistd.h> pid_t setsid(void); ``` 該函數調用成功時返回新創建的Session的id(其實也就是當前進程的id),出錯返回-1。注意,調用這個函數之前,當前進程不允許是進程組的Leader,否則該函數返回-1。要保證當前進程不是進程組的Leader也很容易,只要先`fork`再調用`setsid`就行了。`fork`創建的子進程和父進程在同一個進程組中,進程組的Leader必然是該組的第一個進程,所以子進程不可能是該組的第一個進程,在子進程中調用`setsid`就不會有問題了。 成功調用該函數的結果是: * 創建一個新的Session,當前進程成為Session Leader,當前進程的id就是Session的id。 * 創建一個新的進程組,當前進程成為進程組的Leader,當前進程的id就是進程組的id。 * 如果當前進程原本有一個控制終端,則它失去這個控制終端,成為一個沒有控制終端的進程。所謂失去控制終端是指,原來的控制終端仍然是打開的,仍然可以讀寫,但只是一個普通的打開文件而不是控制終端了。 **例?34.2.?創建守護進程** ``` #include <stdlib.h> #include <stdio.h> #include <fcntl.h> void daemonize(void) { pid_t pid; /* * Become a session leader to lose controlling TTY. */ if ((pid = fork()) < 0) { perror("fork"); exit(1); } else if (pid != 0) /* parent */ exit(0); setsid(); /* * Change the current working directory to the root. */ if (chdir("/") < 0) { perror("chdir"); exit(1); } /* * Attach file descriptors 0, 1, and 2 to /dev/null. */ close(0); open("/dev/null", O_RDWR); dup2(0, 1); dup2(0, 2); } int main(void) { daemonize(); while(1); } ``` 為了確保調用`setsid`的進程不是進程組的Leader,首先`fork`出一個子進程,父進程退出,然后子進程調用`setsid`創建新的Session,成為守護進程。按照守護進程的慣例,通常將當前工作目錄切換到根目錄,將文件描述符0、1、2重定向到`/dev/null`。Linux也提供了一個庫函數`daemon(3)`實現我們的`daemonize`函數的功能,它帶兩個參數指示要不要切換工作目錄到根目錄,以及要不要把文件描述符0、1、2重定向到`/dev/null`。 ``` $ ./a.out $ ps PID TTY TIME CMD 11494 pts/0 00:00:00 bash 13271 pts/0 00:00:00 ps $ ps xj | grep a.out 1 13270 13270 13270 ? -1 Rs 1000 0:05 ./a.out 11494 13273 13272 11494 pts/0 13272 S+ 1000 0:00 grep a.out (關閉終端窗口重新打開,或者注銷重新登錄) $ ps xj | grep a.out 1 13270 13270 13270 ? -1 Rs 1000 0:21 ./a.out 13282 13338 13337 13282 pts/1 13337 S+ 1000 0:00 grep a.out $ kill 13270 ``` 運行這個程序,它變成一個守護進程,不再和當前終端關聯。用`ps`命令看不到,必須運行帶`x`參數的`ps`命令才能看到。另外還可以看到,用戶關閉終端窗口或注銷也不會影響守護進程的運行。
                  <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>

                              哎呀哎呀视频在线观看