UNIX再学习 -- 进程关系
生活随笔
收集整理的這篇文章主要介紹了
UNIX再学习 -- 进程关系
小編覺得挺不錯的,現在分享給大家,幫大家做個參考.
APUE 第 10 章信號講完,回過頭來看一下第 9 章的進程關系。終端登錄和網絡登錄部分,我們只講 Linux 系統的。
一、終端登錄
我記得我們講 root 登錄設置時有提到,參看:C語言再學習 -- Ubuntu 12.04 root用戶登錄設置 其中,使用Ctrl+Alt+F1?進入純命令模式,重新依據上面的設置,重啟 OK!Ctrl+Alt切換到Windows
Ctrl+Alt+F7?退出純命令模式。
我們來看一下,進入純命令模式界面:
那什么是 Linux 的終端登錄?參看:Linux 終端及終端登錄過程簡介1、當系統自舉時,內核創建進程 ID 為 1 的進程,也就是 init 進程。init 進程使系統進入多用戶模式。init 進程根據配置文件 /etc/inittab 確定需要打開哪些終端,對每一個允許登錄的終端設備,init 調用一次 fork,它所生成的子進程則執行 getty(exec)程序。(不同操作系統配置文件可能不同)。2.getty 為終端設備調用 open 函數,以讀寫方式將終端打開。然后 getty 輸出“longin:”之類的信息,并等待用戶鍵入用戶名。3.當用戶鍵入用戶名后,getty 工作完成。然后調用 login 程序:execle(“/bin/login”,”login”,”-p”,username,(char *)0,envp)
4.密碼驗證無誤后,login 將切換目錄到用戶的 home 目錄,改變該終端設備的權限,login 進程改變為登錄用戶 ID 并調用改用戶的登錄 shell:execl(“/bin/sh”,”-sh”,(char *)0)
5.登錄shell讀取其啟動文件 (Bourne shell) 和 Korn shell。
從 getty 開始 exec 到 login,再 exec 到 bash,其實都是同一個進程,因此控制終端沒變,文件描述符 0、1、2 也仍然指向控制終端。由于 fork 會復制 PCB 信息,所以由 Shell 啟動的其它進程也都是如此。
擴展:Linux進程間關系之終端與終端登錄人家講的比我詳細多了。。
二、網絡登錄
linux 網絡登錄使用擴展的因特網服務守護進程 xinetd,它等待大多數網路連接。作為系統啟動的一部分,init 調用一個 shell,使其執行 shell 腳本 /etc/rc。由此 shell 腳本啟動一個守護進程?xinetd。一旦此 shell 腳本終止,xinetd 的父進程就變成 init。xinetd 等待 TCP/IP 連接請求到達主機,而當一個連接請求到達時,它執行一次 fork,然后生成的子進程 exec 適當的程序。就比如我們之前有講過的 telnet 遠程控制,,參看:Hi3516A開發--環境搭建工具重點是,當通過終端或網絡登錄時,我們得到了一個登錄 shell,其標準輸入、標準輸出和標準錯誤要么連接一個終端設備,要么連接一個偽終端設備上。
三、進程組 ID
我們之前講過進程 ID、父進程 ID。參看:UNIX再學習 -- 函數 fork 和 vfork #include <sys/types.h> #include <unistd.h> pid_t getpid(void); 返回:調用進程的進程 ID pid_t getppid(void); 返回:調用進程的父進程 ID uid_t getuid(void); 返回:調用進程的實際用戶 ID uid_t geteuid(void); 返回:調用進程的有效用戶 ID gid_t getgid(void); 返回:調用進程的實際組 ID gid_t getegid(void); 返回:調用進程的有效組 ID 注意,這些函數都沒有出錯返回 (1)示例說明#include <stdio.h> #include <unistd.h> #include <sys/types.h> int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); return 0; } 輸出結果: pid = 3028 ppid = 2808 uid = 0 euid = 0 gid = 0 egid = 0 //每次執行結果都不一定相同
每個進程除了有一個進程 ID 之外,還屬于一個進程組。我們講信號的時候,有提到過。那時不知道啥意思。 參看:UNIX再學習 -- 信號
進程組是一個或多個進程的集合。通常,它們是在同一作業中結合起來的,同一進程組中的各進程接收來自同一終端的各種信號。每個進程組有一個唯一的進程組 ID。進程組 ID 類似于進程 ID — 它是一個正整數,并存放在 pid_t 數據類型中。函數 getpgrp 返回調用進程的進程組 ID。 #include <unistd.h>int setpgid(pid_t pid, pid_t pgid);pid_t getpgid(pid_t pid);pid_t getpgrp(void); /* POSIX.1 version */pid_t getpgrp(pid_t pid); /* BSD version */int setpgrp(void); /* System V version */int setpgrp(pid_t pid, pid_t pgid); /* BSD version */ 可以看出系統不同用的函數也不同,建議使用 POSIX.1 規定中的無參數 getprgp() 函數。 示例說明: #include <stdio.h> #include <unistd.h> #include <sys/types.h> int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());return 0; } 輸出結果: pid = 3497 ppid = 3401 uid = 0 euid = 0 gid = 0 egid = 0 ----------------- pgrp = 3497 每個進程組有一個組長進程。組長進程的進程組 ID 等于其進程 ID。 進程組組長可以創建一個進程組,常見該組中的進程,然后終止。只要在某個進程組中有一個進程存在,則該進程組就存在,這與其組長進程是否終止無關。從進程組創建開始到其中最后一個進程離開為止的時間區間稱為進程組的生命期。某個進程組中的最后一個進程可以終止,也可以轉移到另一個進程組。
進程調用 setpgid 可以加入一個現有的進程組或者創建一個新進程組。 setpgid 函數將 pid 進程的進程組 ID 設置為 pgid。如果這兩個參數相等,則由 pid 指定的進程編程進程組組長。如果 pid 是 0,則使用調用者的進程 ID。另外,如果 pgid 是 0,則由 pid 指定的進程 ID 用作進程組 ID。 示例說明: #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h>int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());return 0; } 輸出結果: pid = 3593 ppid = 3401 uid = 0 euid = 0 gid = 0 egid = 0 ----------------- pgrp = 3593 ----------------- pgrp = 3401
四、 會話
會話(session)是一個或多個進程組的集合。 舉個例子:如上例中,在一個會話中有 3 個進程組。通常是由 shell 的管道將幾個進程編程一組的。
1、進程調用 setsid 函數建立一個新會話。
#include <unistd.h> pid_t setsid(void); 返回值:成功返回進程組 ID;失敗返回 -1函數解析:
如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新會話。具體會發生以下 3 件事。 (1)該進程變成新會話的會話首進程(會話首進程是創建該會話的進程)。此時,該進程是新會話中的唯一進程。 (2)該進程成為一個新進程組的組長進程。新進程組 ID 是該調用進程的進程 ID。 (3)該進程沒有控制終端。如果在調用 setsid 之前進程有一個控制終端,那么這種聯系也被切斷。示例說明:
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h>int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ()); /*if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());*/if(setsid() == -1)perror ("setsig"), exit (1);return 0; } 輸出結果: pid = 3738 ppid = 3401 uid = 0 euid = 0 gid = 0 egid = 0 ----------------- pgrp = 3738 setsig: Operation not permitted示例解析:
該示例中進程組長 ID 就是進程 ID。看一下條件,如果調用此函數的進程不是一個進程組的組長,則此函數創建一個新會話。那 setsig 肯定出錯了。 如果將注釋去掉,改變它的進程組 ID,則 setsig 會建立新會話。且返回進程組 ID。 #include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h>int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());if (-1 == (setpgid (0, getppid ())))perror ("setpgid"), exit (1);printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());pid_t sig = setsid ();if(sig == -1)perror ("setsig"), exit (1);printf ("sig = %d\n", sig);return 0; } 輸出結果: pid = 3771 ppid = 3401 uid = 0 euid = 0 gid = 0 egid = 0 ----------------- pgrp = 3771 ----------------- pgrp = 3401 sig = 37712、getsid 函數返回會話首進程的進程 ID
#include <unistd.h> pid_t getsid(pid_t pid); 返回值:成功返回會話首進程的進程組 ID;失敗返回 -1(1)函數解析
如若 pid 是 0,getsid 返回調用進程的會話首進程的進程組 ID。 出于安全方面的的考慮,一些實現有如下限制:如若 pid 并不屬于調用者所在的會話,那么調用進程就不能得到該會話首進程的進程組 ID。(2)示例說明
#include <stdio.h> #include <unistd.h> #include <sys/types.h> #include <stdlib.h>int main (void) { printf ("pid = %d\n", getpid ()); printf ("ppid = %d\n", getppid ()); printf ("uid = %d\n", getuid ()); printf ("euid = %d\n", geteuid ()); printf ("gid = %d\n", getgid ()); printf ("egid = %d\n", getegid ()); printf ("-----------------\n");printf ("pgrp = %d\n", getpgrp ());//pid 為 0printf ("sid = %d\n", getsid (0));return 0; } 輸出結果: pid = 3809 ppid = 3401 uid = 0 euid = 0 gid = 0 egid = 0 ----------------- pgrp = 3809 sid = 3401五、控制終端
一個會話可以有一個控制終端。這通常是終端設備(在終端登錄情況下)或偽終端設備(在網絡登錄情況下)。建立與控制終端連接的會話首進程被稱為控制進程。一個會話中的幾個進程組可被分為一個前臺進程組以及一個或多個后臺進程組。如果一個會話有一個控制終端,則它有一個前臺進程組,其他進程組為后臺進程組。無論何時鍵入終端的中斷鍵(常常是 Delete 或 Ctrl+C),都會將中斷信號發送至前臺進程組的所有進程。無論何時鍵入終端的退出鍵(常常是 Ctrl+\),都會將退出信號發送至前臺進程組的所有進程。如果終端接口檢測調制解調器(或網線)已經斷開連接,則將掛斷信號發送至控制進程(會話首進程)。通常我們不必擔心控制終端,登錄時,將自動建立控制終端。六、作業控制
作業控制是 BSD 在 1980 年左右增加的一個特性。它允許在一個終端上啟動多個作業(進程組),它控制哪一個作業可以訪問該終端以及哪些作業在后臺運行。作業控制要求以下 3 種形式的支持。 (1)支持作業控制的 shell (2)內核中的終端驅動程序必須支持作業控制 (3)內核必須提供對某些作業控制信號的支持后臺運行是用 ‘&’比如: ??#./a.out & ?? 比如 shell 腳本里使用,./thttpd.sh & ? 實際上有 3 個特殊字符可使終端驅動程序產生信號,并將它們發送至前臺進程組,它們是: 中斷字符(一般采用Delet 或 Ctrl+C)產生 SIGINT 退出字符(一般采用Ctrl+\)產生 SIGQUIT 掛起字符(一般采用 Ctrl+Z)產生 SIGTSTP 可以用 bg 使其后臺繼續運行,fg 使其轉入前臺運行。
七、shell 執行程序
讓我們檢驗一下 shell 是如何執行程序的,以及這與進程組、控制終端和會話等概念的關系。shell 編程有講,參看:UNIX再學習 -- shell編程使用 ps 命令,首先使用不支持作業控制的、在 Solaris 上運行的經典 Bourne shell。 #include <stdio.h> #include <stdlib.h> #include <unistd.h> int main (void) { pid_t pid, pr; pid = fork (); if (pid == -1) perror ("fail to fork"), exit (1); else if (pid == 0) { printf ("這是子進程 pid = %d", getpid ()); printf ("父進程的 ppid = %d\n", getppid ()); } else { pr = wait (NULL); //while (1); sleep (10); //可以保證子進程先被調度 printf ("這是父進程 ppid = %d\n", getpid ()); } return 0; } 實驗: 在一個終端執行 ./a.out # ./a.out 這是子進程 pid = 3951父進程的 ppid = 3950 (十秒后) 這是父進程 ppid = 3950 在另一個終端,查看進程信息 # ps -C a.out -o pid,ppid,pgid,sid,comm PID PPID PGID SID COMMAND3950 3868 3950 3868 a.out總結
以上是生活随笔為你收集整理的UNIX再学习 -- 进程关系的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 日常生活 -- 嵌入式再学习前言
- 下一篇: @RequestBody、@Respon