亚洲精品久久久中文字幕-亚洲精品久久片久久-亚洲精品久久青草-亚洲精品久久婷婷爱久久婷婷-亚洲精品久久午夜香蕉

您的位置:首頁技術文章
文章詳情頁

UNIX 進程揭秘

瀏覽:4日期:2024-06-17 14:26:51

分配給系統管理員的許多工作之一是確保用戶的程序正確運行。因為系統上存在其他并發運行的程序,所以此任務變得更加復雜。由于種種原因,這些程序可能會失敗、掛起或行為異常。在構建更可靠的系統時,了解 Unix® 環境如何創建、管理和銷毀這些作業是至關重要的步驟。

開發人員還必須積極了解內核如何管理進程,因為與系統的其他部分和睦相處的應用程序會占用更少的資源,并且不會頻繁地給系統管理員帶來麻煩。由于導致僵死進程(將在稍后對其進行描述)而頻繁重新啟動的應用程序明顯是不可取的。對控制進程的 UNIX 系統調用的了解可以使開發人員編寫能夠在后臺自動運行的軟件,而不是需要一個始終保持在用戶屏幕上的終端會話。

管理這些程序的基本構件就是進程。進程是賦予某個由操作系統執行的程序的名稱。如果您熟悉 ps 命令,則您應該熟悉進程清單,如清單 1 所示。

清單 1. ps 命令的輸出

sunbox#ps -ef UID  PID PPID  CSTIME TTY TIME CMDroot 0 0  0 20:15:23 ?  0:14 schedroot 1 0  0 20:15:24 ?  0:00 /sbin/initroot 2 0  0 20:15:24 ?  0:00 pageoutroot 3 0  0 20:15:24 ?  0:00 fsflush daemon  240 1  0 20:16:37 ?  0:00 /usr/lib/nfs/statd...

前三列對這里的討論非常重要。第一列列出用于運行該進程的用戶身份,第二列列出進程的 ID,第三列列出該進程的父進程 ID。最后一列是進程的描述,通常是所運行的二進制文件的名稱。每個進程都被分配一個標識符,稱為進程標識符(Process IdentifIEr,PID)。進程還有父進程,在大多數情況下就是啟動它的進程的 PID。

父 PID (PPID) 的存在意味著這是一個由別的進程創建的進程。最初創建進程的原始進程名為 init,它始終被賦予 PID 1。init 是將在內核啟動時啟動的第一個實際進程。啟動系統的其余部分是 init 的工作。init 和其他具有 PPID 0 的進程屬于內核。

使用 fork 系統調用

fork(2) 系統調用創建一個新進程。清單 2 顯示了一個簡單 C 代碼片段中使用的 fork。

清單 2. 簡單的 fork(2) 用法

sunbox$ cat fork1.c#include <unistd.h>#include <stdio.h>int main (void) {pid_t p; /* fork returns type pid_t */p = fork();printf("fork returned %dn", p);}sunbox$ gcc fork1.c -o fork1sunbox$ ./fork1fork returned 0fork returned 698

fork1.c 中的代碼不過就是發出 fork 調用,并通過一個 printf 調用來打印整數結果。雖然只發出了一個調用,但是打印了兩次輸出。這是因為在 fork 調用中創建了一個新進程。現在有兩個單獨的進程在從該調用返回結果。這通常被描述為“調用一次,返回兩次。

fork 返回的值非常有趣。其中一個返回 0;另一個返回一個非零值。獲得 0 的進程稱為子進程,非零結果屬于原始進程,即父進程。您將使用返回值來確定哪個是父進程,哪個是子進程。由于兩個進程都在同一空間中繼續運行,唯一有實際意義的區別是從 fork 返回的值。

0 和非零返回值的基本原理在于,子進程始終可以通過 getppid(2) 調用來找出其父進程是誰,但是父進程要找出它的所有子進程卻很困難。因此,要告訴父進程關于其新的子進程的信息,而子進程可在需要時查找其父進程。

考慮到 fork 的返回值,現在該代碼可以檢查確定它是父進程還是子進程,并進行相應的操作。清單 3 顯示了一個基于 fork 的結果來打印不同輸出的程序。

清單 3. 更完整的 fork 用法示例

sunbox$ cat fork2.c#include <unistd.h>#include <stdio.h>int main (void) {pid_t p;printf("Original program, pid=%dn", getpid());p = fork();if (p == 0) {printf("In child process, pid=%d, ppid=%dn",getpid(), getppid());} else {printf("In parent, pid=%d, fork returned=%dn",getpid(), p);}}sunbox$ gcc fork2.c -o fork2sunbox$ ./fork2Original program, pid=767In child process, pid=768, ppid=767In parent, pid=767, fork returned=768

清單 3 在每個步驟打印出 PID,并且該代碼檢查從 fork 返回的值來確定哪個進程是父進程,哪個進程是子進程。對所打印的 PID 進行比較,可以看到原始進程是父進程 (PID 767),并且子進程 (PID 768) 知道其父進程是誰。請注意子進程如何通過 getppid 來知道其父進程以及父進程如何使用 fork 來定位其子進程。

現在您已經了解了復制某個進程的方法,下面讓我們研究如何運行一個不同的進程。fork 只是進程機制中的一半。exec 系列系統調用運行實際的程序。

使用 exec 系列系統調用

exec 的工作是將當前進程替換為一個新進程。請注意“替換這個措詞的含義。在您調用 exec 以后,當前進程就消失了,新進程就啟動了。如果希望創建一個單獨的進程,您必須首先運行 fork,然后在子進程中執行 (exec) 新的二進制文件。清單 4 顯示了這樣一種情況。

清單 4. 通過將 fork 與 exec 配合使用來運行不同的程序

sunbox$ cat exec1.c#include <unistd.h>#include <stdio.h>int main (void) {/* Define a null terminated array of the command to run  followed by any parameters, in this case none */char *arg[] = { "/usr/bin/ls", 0 };/* fork, and exec within child process */if (fork() == 0) {printf("In child process:n");execv(arg[0], arg);printf("I will never be calledn");}printf("Execution continues in parent processn");}sunbox$ gcc exec1.c -o exec1sunbox$ ./exec1In child process:fork1.c exec1fork2  exec1.c fork1fork2.c Execution continues in parent process

清單 4 中的代碼首先定義一個數組,其中第一個元素是要執行的二進制文件的路徑,其余元素充當命令行參數。根據手冊頁的描述,該數組以 Null 結尾。在從 fork 系統調用返回以后,將指示子進程執行 (execv) 新的二進制文件。

execv 調用首先取得一個指向要運行的二進制文件名稱的指針,然后取得一個指向您前面聲明的參數數組的指針。該數組的第一個元素實際上是二進制文件的名稱,因此參數實際上是從第二個元素開始的。請注意,該子進程一直沒有從 execv 調用返回。這表明正在運行的進程已被新進程所替換。

還存在其他執行 (exec) 某個進程的系統調用,它們的區別在于接受參數的方式和是否需要傳遞環境變量。execv(2) 是替換當前映像的較簡單方法之一,因為它不需要關于環境的信息,并且它使用以 Null 結尾的數組。其他選項包括 execl(2)(它單獨接受各個參數)或 execvp(2)(它也接受一個以 Null 結尾的環境變量數組)。使問題復雜化的是,并非所有操作系統都支持所有變體。關于使用哪一種變體的決定取決于平臺、編碼風格和是否需要定義任何環境變量。

調用 fork 時,打開的文件會發生什么情況呢?

當某個進程復制它自身時,內核生成所有打開的文件描述符的副本。文件描述符是指向打開的文件或設備的整數,并用于執行讀取和寫入。如果在調用 fork 前,某個程序已經打開了一個文件,如果兩個進程都嘗試執行讀取或寫入,會發生什么情況呢?一個進程會改寫另一個進程中的數據嗎?是否會讀取該文件的兩個副本?清單 5 對此進行了研究,它打開兩個文件——一個文件用于讀取,另一個文件用于寫入——并讓父進程和子進程同時執行讀取和寫入。

清單 5. 同時對同一文件執行讀取和寫入的兩個進程

#include <stdio.h>#include <strings.h>#include <unistd.h>#include <sys/types.h>#include <sys/stat.h>#include <fcntl.h>int main(void) {int fd_in, fd_out;char buf[1024];memset(buf, 0, 1024); /* clear buffer*/fd_in = open("/tmp/infile", O_RDONLY);fd_out = open("/tmp/outfile", O_WRONLY|O_CREAT);fork(); /* It doesn't matter about child vs parent */while (read(fd_in, buf, 2) > 0) { /* Loop through the infile */printf("%d: %s", getpid(), buf);/* Write a line */sprintf(buf, "%d Hello, world!nr", getpid());write(fd_out, buf, strlen(buf));sleep(1);memset(buf, 0, 1024); /* clear buffer*/}sleep(10);}sunbox$ gcc fdtest1.c -o fdtest1sunbox$ ./fdtest12875: 12874: 22875: 32874: 42875: 52874: 62874: 7sunbox$ cat /tmp/outfile2875 Hello, world!2874 Hello, world!2875 Hello, world!2874 Hello, world!2875 Hello, world!2874 Hello, world!2874 Hello, world!

清單 5 是用于打開文件的簡單程序,并派生 (fork) 為父進程和子進程。每個進程從同一文件描述符(它只是一個包含數字 1 至 7 的文本文件)執行讀取操作,并連同 PID 一起打印所讀取的內容。在讀取一行之后,將 PID 寫到輸出文件。當輸入文件中沒有其他字符可供讀取時,循環結束。

清單 5 的輸出表明,當一個進程從該文件讀取時,兩個進程的文件指針都在移動。同樣地,當向某個文件寫入時,下一個字符被寫到文件結尾。這是非常有意義的,因為內核跟蹤打開文件的信息。文件描述符只不過是進程的標識符。

您可能還知道,標準輸出(屏幕)也是一個文件描述符。此文件描述符在 fork 調用期間被復制,這就是兩個進程都能對屏幕執行寫入操作的原因。

父進程或子進程的終止

進程必須在某個時候終止。問題只是哪個進程首先終止:父進程還是子進程。

父進程在子進程之前終止

如果父進程在子進程之前終止,孤立的子進程需要知道它們的父進程是誰。記住,每個進程都有父進程,并且您可以跟蹤從每個子進程一直到 PID 1(或稱為 init)的整個進程家族樹。當某個父進程終止時,init 將接納所有子進程,如清單 6 所示。

清單 6. 在子進程之前終止的父進程

#include <unistd.h>#include <stdio.h>int main(void) {int i;if (fork()) {/* Parent */sleep(2);_exit(0);}for (i=0; i < 5; i++) {printf("My parent is %dn", getppid());sleep(1);}}sunbox$ gcc dIE1.c -o die1sunbox$ ./die1My parent is 2920My parent is 2920sunbox$ My parent is 1My parent is 1My parent is 1

在此例中,父進程調用 fork,等待兩秒鐘,然后退出。子進程在五秒鐘內繼續打印其父 PID。可以看到,PPID 在父進程終止后更改為 1。Shell 提示符的返回也是非常有趣的。由于子進程在后臺運行,父進程一終止,控制即返回到 Shell。

子進程在父進程之前終止

清單 7 與清單 6 相反——即在父進程之前終止的子進程。為更好地說明所發生的事情,進程本身中沒有打印任何內容。而有趣的信息來自于進程清單。

清單 7. 子進程在父進程之前終止

sunbox$ cat dIE2.c#include <unistd.h>#include <stdio.h>int main(void) {int i;if (!fork()) {/* Child exits immediately*/_exit(0);}/* Parent waits around for a minute */sleep(60);}sunbox$ gcc die2.c -o die2sunbox$ ./die2 &[1] 2934sunbox$ ps -ef | grep 2934sean 2934 2885  0 21:43:05 pts/1  0:00 ./die2sean 2935 2934  0- ?  0:00 <defunct>sunbox$ ps -ef | grep 2934[1]+ Exit 199./die2

die2 使用 & 操作符在后臺運行,然后顯示一個進程清單,并且僅顯示正在運行的進程及其子進程。PID 2934 是父進程,PID 2935 是派生 (fork) 并立即終止的進程。盡管子進程提前退出,但它仍然在進程表中作為失效 (defunct) 進程存在,或稱為僵死 (zombie) 進程。當父進程在 60 秒以后終止時,兩個進程都消失了。

當子進程終止時,會使用一個名為 SIGCHLD 的信號來通知其父進程。該通知的確切機制現在對您并不重要。重要的是父進程必須以某種方式確認子進程的終止。子進程從終止時起就一直處于僵死狀態,直到父進程確認該信號為止。僵死進程不運行或消耗 CPU 周期;它只是占用進程表空間。當父進程終止時,內核最終能夠回收未確認的子進程以及父進程。這意味著可消除僵死進程的唯一方法是終止父進程。處理僵死進程的最好方法是首先確保它們不會發生。清單 8 中的代碼實現了一個處理傳入的 SIGCHLD 信號的信號處理程序。

清單 8. 實際操作中的信號處理程序

#include <unistd.h>#include <stdio.h>#include <sys/types.h>#include <sys/wait.h>void sighandler(int sig) {printf("In signal handler for signal %dn", sig);/* wait() is the key to acknowledging the SIGCHLD */wait(0);}int main(void) {int i;/* Assign a signal handler to SIGCHLD */sigset(SIGCHLD, &sighandler);if (!fork()) {/* Child */_exit(0);}sleep(60);}sunbox$ gcc dIE3.c -o die3sunbox$ ./die3 &[1] 3116sunbox$ In signal handler for signal 18ps -ef | grep 3116sean 3116 2885  0 22:37:26 pts/1  0:00 ./die3

由于使用了 sigset 函數(它向信號處理程序分配一個函數指針),清單 8 比前一個示例稍微復雜一點,。每當進程接收到某個已處理的信號時,就會調用通過 sigset 分配的函數。對于 SIGCHLD 信號,應用程序必須調用 wait(3c) 函數,以等待子進程退出。由于該進程已經退出,這相當于向內核確認了子進程的終止。實際上,父進程所做的工作可能不只是確認該信息。它還可能需要清理子進程的數據。

在執行 die3 以后,代碼檢查了進程清單,并干凈地執行子進程。然后使用值 18 (SIGCHLD) 來調用信號處理程序,確認子進程的退出,并且父進程返回到 sleep(60)。

總結

Unix 進程是在某個進程調用 fork 時創建的,fork 將正在運行的可執行進程一分為二。然后該進程可以執行 exec 系列中的某個系統調用,從而將當前運行的映像替換為新的映像。

當父進程終止時,其所有子進程將由 PID 為 1 的 init 接納。如果子進程在父進程之前終止,則會向父進程發送一個信號,然后子進程轉變為僵死狀態,直到該信號得到確認,或父進程被終止。

現在您已了解了進程是如何創建和銷毀的,您已經為處理運行您系統的進程作了更好的準備,尤其是大量使用多進程的系統,例如 Apache。如果您需要執行某些故障排除,能夠跟蹤某個特定進程的進程樹還允許您將任何應用程序追溯到創建它的進程。

標簽: Unix系統
主站蜘蛛池模板: 国产在线观看a | 天天拍夜夜操 | 国产亚洲一区二区三区不卡 | 婷婷国产天堂久久综合五月 | 色噜噜五月综合激情久久爱 | 一级黄色大毛片 | 毛片应用 | 国拍在线精品视频免费观看 | 清纯唯美亚洲综合激情 | 国产精品免费观看 | 3p久久| 色涩网站 | 成人久久久久久 | 97超级碰碰碰碰在线视频 | 香蕉片视频在线观看 | 欧美福利片在线观看 | 91精品国产综合久久欧美 | 国产精品玖玖玖在线观看 | 99精品国产美女福到在线不卡 | 日韩精品高清自在线 | 亚洲精品国产福利片 | 亚洲精品14p | 免费中日高清无专码有限公司 | 国产视频1 | 国产成人高清精品免费观看 | 爱福利极品盛宴 | 免费三及片 | 黄在线观看免费 | 狠狠色婷婷综合天天久久丁香 | 日本一线一区二区三区免费视频 | 久久久久久久97 | 国产日韩欧美一区二区三区在线 | 黄色免费a级片 | 亚洲日本人成中文字幕 | 国产精品久久久久影院免费 | 一级特级欧美午夜片免费观看 | 国产毛片一区二区三区 | 国产精品亚洲专区一区 | 国产成本人三级在线观看网站 | 欧美日韩成人高清色视频 | 国产在线精品福利大全 |