日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問 生活随笔!

生活随笔

當(dāng)前位置: 首頁 > 运维知识 > linux >内容正文

linux

linux 的多进程运行机制,Linux 多进程-2

發(fā)布時(shí)間:2025/3/12 linux 25 豆豆
生活随笔 收集整理的這篇文章主要介紹了 linux 的多进程运行机制,Linux 多进程-2 小編覺得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

揭秘文件描述符的本質(zhì)

1. 文件描述符的本質(zhì)是數(shù)組元素的下標(biāo)

右側(cè)的表稱為 i 節(jié)點(diǎn)表,在整個(gè)系統(tǒng)中只有1張。該表可以視為結(jié)構(gòu)體數(shù)組,該數(shù)組的一個(gè)元素對(duì)應(yīng)于一個(gè)物理文件。

中間的表稱為文件表,在整個(gè)系統(tǒng)中只有1張。該表可以視為結(jié)構(gòu)體數(shù)組,一個(gè)結(jié)構(gòu)體中有很多字段,其中有3個(gè)字段比較重要:

file status flags:用于記錄文件被打開來讀的,還是寫的。其實(shí)記錄的就是 open 調(diào)用中用戶指定的第2個(gè)參數(shù)

current file offset:用于記錄文件的當(dāng)前讀寫位置(指針)。正是由于此字段的存在,使得一個(gè)文件被打開并讀取后,下一次讀取將從上一次讀取的字符后開始讀取

v-node ptr:該字段是指針,指向右側(cè)表的一個(gè)元素,從而關(guān)聯(lián)了物理文件。左側(cè)的表稱為文件描述符表,每個(gè)進(jìn)程有且僅有1張。該表可以視為指針數(shù)組,數(shù)組的元素指向文件表的一個(gè)元素。最重要的是:數(shù)組元素的下標(biāo)就是大名鼎鼎的文件描述符。

open 系統(tǒng)調(diào)用執(zhí)行的操作:新建一個(gè) i 節(jié)點(diǎn)表元素,讓其對(duì)應(yīng)打開的物理文件(如果對(duì)應(yīng)于該物理文件的 i 節(jié)點(diǎn)元素已經(jīng)建立,就不做任何操作);

新建一個(gè)文件表的元素,根據(jù) open 的第2個(gè)參數(shù)設(shè)置 file status flags 字段,將 current file offset 字段置0,將 v-node ptr 指向剛建立的 i 節(jié)點(diǎn)表元素;

在文件描述符表中,尋找1個(gè)尚未使用的元素,在該元素中填入一個(gè)指針值,讓其指向剛建立的文件表元素。最重要的是:將該元素的下標(biāo)作為 open 的返回值返回。

這樣一來,當(dāng)調(diào)用 read(write) 時(shí),根據(jù)傳入的文件描述符,OS 就可以找到對(duì)應(yīng)的文件描述符表元素,進(jìn)而找到文件表的元素,進(jìn)而找到 i 節(jié)點(diǎn)表元素,從而完成對(duì)物理文件的讀寫。

2. fork 對(duì)文件描述符的影響

fork 會(huì)導(dǎo)致子進(jìn)程繼承父進(jìn)程打開的文件描述符,其本質(zhì)是將父進(jìn)程的整個(gè)文件描述符表復(fù)制一份,放到子進(jìn)程的 PCB 中。因此父、子進(jìn)程中相同文件描述符(文件描述符為整數(shù))指向的是同一個(gè)文件表元素,這將導(dǎo)致父(子)進(jìn)程讀取文件后,子(父)進(jìn)程將讀取同一文件的后續(xù)內(nèi)容。

案例分析(forkfd.c):

1 #include

2 #include

3 #include

4 #include

5 #include

6 #include

7

8 int main(void)

9 {

10 int fd, pid, status;

11 char buf[10];

12 if ((fd = open("./test.txt", O_RDONLY)) < 0) {

13 perror("open"); exit(-1);

14 }

15 if ((pid = fork()) < 0) {

16 perror("fork"); exit(-1);

17 } else if (pid == 0) { //child

18 read(fd, buf, 2);

19 write(STDOUT_FILENO, buf, 2);

20 } else { //parent

21 sleep(2);

23 lseek(fd, SEEK_CUR, 1);

24 read(fd, buf, 3);

25 write(STDOUT_FILENO, buf, 3);

26 write(STDOUT_FILENO, "\n", 1);

27 }

28 return 0;

29 }

假設(shè)./test.txt 的內(nèi)容是 abcdefg,那么子進(jìn)程的18行將讀到字符 ab;由于父、子進(jìn)程的文件描述符 fd 都指向同一個(gè)文件表元素,因此當(dāng)父進(jìn)程執(zhí)行23行時(shí),fd 對(duì)應(yīng)的文件的讀寫指針將移動(dòng)到字符 d,而不是字符 b,從而24行讀到的是字符 def ,而不是字符 bcd 。程序運(yùn)行的最終結(jié)果是打印 abdef,而不是 abbcd。

相對(duì)應(yīng)的,如果是兩個(gè)進(jìn)程獨(dú)立調(diào)用 open 去打開同一個(gè)物理文件,就會(huì)有2個(gè)文件表元素被創(chuàng)建,并且他們都指向同一個(gè) i 節(jié)點(diǎn)表元素。兩個(gè)文件表元素都有自己獨(dú)立的 current file offset 字段,這將導(dǎo)致2個(gè)進(jìn)程獨(dú)立的對(duì)同一個(gè)物理文件進(jìn)行讀寫,因此第1個(gè)進(jìn)程讀取到文件的第1個(gè)字符后,第2個(gè)進(jìn)程再去讀取該文件時(shí),仍然是讀到的是文件的第1個(gè)字符,而不是第1個(gè)字符的后續(xù)字符。

對(duì)應(yīng)用程序員而言,最重要結(jié)論是: 如果子進(jìn)程不打算使用父進(jìn)程打開的文件,那么應(yīng)該在 fork 返回后立即調(diào)用 close 關(guān)閉該文件。

父子進(jìn)程同步的功臣— wait

1. wait 的作用

在 forkbase.c 中,fork 出子進(jìn)程后,為了保證子進(jìn)程先于父進(jìn)程運(yùn)行,在父進(jìn)程中使用了 sleep(2) 的方式讓父進(jìn)程睡眠2秒。但實(shí)際上這樣做,并不能100%保證子進(jìn)程先于父進(jìn)程運(yùn)行,因?yàn)樵谪?fù)荷非常重的系統(tǒng)中,有可能在父進(jìn)程睡眠2秒期間,OS 并沒有調(diào)度到子進(jìn)程運(yùn)行,并且當(dāng)父進(jìn)程睡醒后,首先調(diào)度到父進(jìn)程運(yùn)行。那么,如何才能100%保證父、子進(jìn)程完全按程序員的安排來進(jìn)行同步呢?答案是:系統(tǒng)調(diào)用 wait!

需要包含的頭文件: 、

函數(shù)原型:pid_t wait(int * status)

功能:等待進(jìn)程結(jié)束。

返回值:若成功則為子進(jìn)程ID號(hào),若出錯(cuò)則為-1.

參數(shù)說明: status:用于存放進(jìn)程結(jié)束狀態(tài)。

wait 函數(shù)用于使父進(jìn)程阻塞,直到一個(gè)子進(jìn)程結(jié)束。父進(jìn)程調(diào)用 wait,該父進(jìn)程可能會(huì):

阻塞(如果其所有子進(jìn)程都還在運(yùn)行)。

帶子進(jìn)程的終止?fàn)顟B(tài)立即返回(如果一個(gè)子進(jìn)程已終止,正等待父進(jìn)程存取其終止?fàn)顟B(tài))。

出錯(cuò)立即返回(如果它沒有任何子進(jìn)程)。

2. 調(diào)用 wait 的實(shí)例

wait.c

1 #include

2 #include

3 #include

4 #include

5 #include

6 void pr_exit(intstatus);

7 int main(void)

8 {

9 pid_t pid;

10 int status;

11 if ( (pid = fork()) < 0)

12 { perror("fork");exit(-1); }

13 else if (pid == 0) { /* child */

14 sleep(1);

15 printf("inchild\n");

16 exit(101);

17 }

18 if (wait(&status) != pid) /* wait for child */

19 { perror("wait");exit(-2); }

20 printf("in parent\n");

21 pr_exit(status); /* and print itsstatus */

22 if ( (pid = fork()) < 0)

23 { perror("fork");exit(-1); }

24 else if (pid == 0) /*child */

25 abort(); /* generates SIGABRT */

26 if (wait(&status) != pid) /* wait for child */

27 { perror("wait");exit(-2); }

28 pr_exit(status); /* and printits status */

29 if ( (pid = fork()) < 0)

30 { perror("fork");exit(-1); }

31 else if (pid == 0) /*child */

32 status /= 0; /* divide by 0 generates SIGFPE */

33 if (wait(&status) != pid) /* wait for child */

34 { perror("wait");exit(-1); }

35 pr_exit(status); /* and printits status */

36 exit(0);

37 }

38 void pr_exit(int status) {

39 if (WIFEXITED(status))

40 printf("normallytermination, low-order 8 bit of exit status = %d\n", WEXITSTATUS(status));

41 else if(WIFSIGNALED(status))

42 printf("abnormallytermination, singal number = %d\n", WTERMSIG(status));

43 }

運(yùn)行結(jié)果分析:

11行創(chuàng)建了一個(gè)子進(jìn)程,13行根據(jù) fork 的返回值區(qū)分父、子進(jìn)程。 我們先看父進(jìn)程,父進(jìn)程從18行運(yùn)行,這里調(diào)用了 wait 函數(shù)等待子進(jìn)程結(jié)束,并將子進(jìn)程結(jié)束的狀態(tài)保存在 status 中。這時(shí),父進(jìn)程就阻塞在 wait 這里了,這樣就保證了子進(jìn)程先運(yùn)行。子進(jìn)程從13行開始運(yùn)行,然后 sleep 1秒,打印出“in child”后,調(diào)用exit函數(shù)退出進(jìn)程。這里exit中有個(gè)參數(shù)101,表示退出的值是101。.子進(jìn)程退出后,父進(jìn)程 wait 到了子進(jìn)程的狀態(tài),并把狀態(tài)保存到了 status 中。后面的 pr_exit 函數(shù)是用來對(duì)進(jìn)程的退出狀態(tài)進(jìn)行打印。接下來,父進(jìn)程又創(chuàng)建一個(gè)子進(jìn)程,然后又一次調(diào)用 wait 函數(shù)等待子進(jìn)程結(jié)束,父進(jìn)程這時(shí)候阻塞在了 wait 這里。子進(jìn)程開始執(zhí)行,子進(jìn)程里面只有一句話:abort(),abort 會(huì)結(jié)束子進(jìn)程并發(fā)送一個(gè) SIGABORT 信號(hào),喚醒父進(jìn)程。所以父進(jìn)程會(huì)接受到一個(gè) SIGABRT 信號(hào),并將子進(jìn)程的退出狀態(tài)保存到 status 中。然后調(diào)用 pr_exit 函數(shù)打印出子進(jìn)程結(jié)束的狀態(tài)。然后父進(jìn)程再次創(chuàng)建了一個(gè)子進(jìn)程,依然用 wait 函數(shù)等待子進(jìn)程結(jié)束并獲取子進(jìn)程退出時(shí)的狀態(tài)。子進(jìn)程里面就一句 status/= 0,這里用0做了除數(shù),所以子進(jìn)程會(huì)終止,并發(fā)送一個(gè) SIGFPE 信號(hào),這個(gè)信號(hào)是用來表示浮點(diǎn)運(yùn)算異常,比如運(yùn)算溢出,除數(shù)不能為0等。這時(shí)候父進(jìn)程 wait 函數(shù)會(huì)捕捉到子進(jìn)程的退出狀態(tài),然后調(diào)用 pr_exit 處理。 pr_exit 函數(shù)將 status 狀態(tài)傳入,然后判斷該狀態(tài)是不是正常退出,如果是正常退出會(huì)打印出退出值;不是正常退出會(huì)打印出退出時(shí)的異常信號(hào)。這里用到了幾個(gè)宏,簡單解釋如下:

WIFEXITED: 這個(gè)宏是用來判斷子進(jìn)程的返回狀態(tài)是不是為正常,如果是正常退出,這個(gè)宏返回真。

WEXITSTATUS: 用來返回子進(jìn)程正常退出的狀態(tài)值。

WIFSIGNALED: 用來判斷子進(jìn)程的退出狀態(tài)是否是非正常退出,若非正常退出時(shí)發(fā)送信號(hào),則該宏返回真。

WTERMSIG: 用來返回非正常退出狀態(tài)的信號(hào) number。 所以這段代碼的結(jié)果是分別打印出了三個(gè)子進(jìn)程的退出狀態(tài)和異常結(jié)束的信號(hào)編號(hào)。

進(jìn)程控制地字第1號(hào)系統(tǒng)調(diào)用 — exec

當(dāng)一個(gè)程序調(diào)用 fork 產(chǎn)生子進(jìn)程,通常是為了讓子進(jìn)程去完成不同于父進(jìn)程的某項(xiàng)任務(wù),因此含有 fork 的程序,通常的編程模板如下:

if ((pid = fork()) == 0) {

dosomething in child process;

exit(0);

}

do something in parent process;

這樣的編程模板使得父、子進(jìn)程各自執(zhí)行同一個(gè)二進(jìn)制文件中的不同代碼段,完成不同的任務(wù)。這樣的編程模板在大多數(shù)情況下都能勝任,但仔細(xì)觀察這種編程模板,你會(huì)發(fā)現(xiàn)它要求程序員在編寫源代碼的時(shí)候,就要預(yù)先知道子進(jìn)程要完成的任務(wù)是什么。這本不是什么過分的要求,但在某些情況下,這樣的前提要求卻得不到滿足,最典型的例子就是 Linux 的基礎(chǔ)應(yīng)用程序 —— shell。你想一想,在編寫 shell 的源代碼期間,程序員是不可能知道當(dāng) shell 運(yùn)行時(shí),用戶輸入的命令是 ls 還是 cp,難道你要在 shell 的源代碼中使用 if--elseif--else if--else if …… 結(jié)構(gòu),并拷貝 ls、cp 等等外部命令的源代碼到 shell 源代碼中嗎?退一萬步講,即使這種弱智的處理方式被接受的話,你仍然會(huì)遇到無法解決的難題。想一想,如果用戶自己編寫了一個(gè)源程序,并將其編譯為二進(jìn)制程序 test,然后再在 shell 命令提示符下輸入./test,對(duì)于采用前述弱智方法編寫的 shell,它將情何以堪?

看來天字1號(hào)雖然很牛,但亦難以獨(dú)木擎天,必要情況下,也需要地字1號(hào)予以協(xié)作,啊,偉大的團(tuán)隊(duì)精神!

1. exec 的機(jī)制和用法

下面就詳細(xì)介紹一下進(jìn)程控制地字第1號(hào)系統(tǒng)調(diào)用——exec 的機(jī)制和用法。

(1)exec 的機(jī)制:

在用 fork 函數(shù)創(chuàng)建子進(jìn)程后,子進(jìn)程往往要調(diào)用 exec 函數(shù)以執(zhí)行另一個(gè)程序。 當(dāng)子進(jìn)程調(diào)用 exec 函數(shù)時(shí),會(huì)將一個(gè)二進(jìn)制可執(zhí)行程序的全路徑名作為參數(shù)傳給exec,exec 會(huì)用新程序代換子進(jìn)程原來全部進(jìn)程空間的內(nèi)容,而新程序則從其 main 函數(shù)開始執(zhí)行,這樣子進(jìn)程要完成的任務(wù)就變成了新程序要完成的任務(wù)了。 因?yàn)檎{(diào)用exec并不創(chuàng)建新進(jìn)程,所以前后的進(jìn)程 ID 并未改變。exec 只是用另一個(gè)新程序替換了當(dāng)前進(jìn)程的正文、數(shù)據(jù)、堆和棧段。進(jìn)程還是那個(gè)進(jìn)程,但實(shí)質(zhì)內(nèi)容已經(jīng)完全改變。呵呵,這是不是和中國A股的借殼上市有異曲同工之妙? 順便說一下,新程序的 bss 段清0這個(gè)操作,以及命令行參數(shù)和環(huán)境變量的指定,也是由 exec 完成的。

(2)exec 的用法:

函數(shù)原型: int execle(const char * pathname,const char * arg0, ... (char *)0, char * const envp [] )

返回值: exec 執(zhí)行失敗返回-1,成功將永不返回(想想為什么?)。哎,牛人就是有脾氣,天字1號(hào)是調(diào)用1次,返回2次;地字1號(hào),干脆就不返回了,你能奈我何?

參數(shù):

pathname: 新程序的二進(jìn)制文件的全路徑名

arg0:新程序的第1個(gè)命令行參數(shù) argv[0],之后是新程序的第2、3、4……個(gè)命令行參數(shù),以 (char*)0 表示命令行參數(shù)的結(jié)束

envp:新程序的環(huán)境變量

2. exec 的使用實(shí)例

echoall.c

1 #include

2 #include

3 #include

4

5 int main(int argc, char*argv[])

6 {

7 int i;

8 char **ptr;

9 extern char **environ;

10 for (i = 0; i < argc; i++) /* echo all command-line args */

11 printf("argv[%d]:%s\n", i, argv[i]);

12 for (ptr = environ; *ptr != 0;ptr++) /* and all env strings */

13 printf("%s\n",*ptr);

21 }

將此程序進(jìn)行編譯,生成二進(jìn)制文件命名為 echoall,放在當(dāng)前目錄下。很容易看出,此程序運(yùn)行將打印進(jìn)程的所有命令行參數(shù)和環(huán)境變量。

!源文件過長,請(qǐng)直接查看源代碼 exec.c

程序運(yùn)行結(jié)果:

1 argv[0]: echoall

2 argv[1]: myarg1

3 argv[2]: MY ARG2

4 USER=unknown

5 PATH=/tmp

6 argv[0]: echoall

7 argv[1]: only 1 arg

8 ORBIT_SOCKETDIR=/tmp/orbit-dennis

9 SSH_AGENT_PID=1792

10 TERM=xterm

11 SHELL=/bin/bash

12 XDG_SESSION_COOKIE=0a13eccc45d521c3eb847f7b4bf75275-1320116445.669339

13 GTK_RC_FILES=/etc/gtk/gtkrc:/home/dennis/.gtkrc-1.2-gnome2

14 WINDOWID=62919986

15 GTK_MODULES=canberra-gtk-module

16 USER=dennis

.......

運(yùn)行結(jié)果分析: 1-5行是第1個(gè)子進(jìn)程14行運(yùn)行新程序 echoall 的結(jié)果,其中:1-3行打印的是命令行參數(shù);4、5行打印的是環(huán)境變量。 6行之后是第2個(gè)子進(jìn)程23行運(yùn)行新程序 echoall 的結(jié)果,其中:6、7行打印的是命令行參數(shù);8行之后打印的是環(huán)境變量。之所以第2個(gè)子進(jìn)程的環(huán)境變量那么多,是因?yàn)槌绦?3行調(diào)用 execlp 時(shí),沒有給出環(huán)境變量參數(shù),因此子進(jìn)程就會(huì)繼承父進(jìn)程的全部環(huán)境變量。

總結(jié)

以上是生活随笔為你收集整理的linux 的多进程运行机制,Linux 多进程-2的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。

如果覺得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。