一区二区三区日韩精品-日韩经典一区二区三区-五月激情综合丁香婷婷-欧美精品中文字幕专区

分享

UNIX環(huán)境編程學(xué)習(xí)筆記(19)

 戴維圖書館 2018-06-28

lienhua34
2014-10-07

在“進(jìn)程控制三部曲”中,我們學(xué)習(xí)到了 fork 是三部曲的第一部,用于創(chuàng)建一個(gè)新進(jìn)程。但是關(guān)于 fork 的更深入的一些的東西我們還沒有涉及到,例如,fork 創(chuàng)建的新進(jìn)程與調(diào)用進(jìn)程之間的關(guān)系、父子進(jìn)程的數(shù)據(jù)共享問題等。fork 是否可以無限制的調(diào)用?如果不行的話,最大限制是多少?另外,我們還將學(xué)習(xí)一個(gè) fork 的變體 vfork。

1 fork 創(chuàng)建的新進(jìn)程與調(diào)用進(jìn)程之間的關(guān)系

UNIX 操作系統(tǒng)中的所有進(jìn)程之間的關(guān)系呈現(xiàn)一個(gè)樹形結(jié)構(gòu)。除了進(jìn)程 ID 為 0(swapper 進(jìn)程)和 1(init 進(jìn)程)的進(jìn)程之外的其他進(jìn)程,都會(huì)存在一個(gè)父進(jìn)程。

fork 函數(shù)調(diào)用產(chǎn)生的新進(jìn)程的父進(jìn)程默認(rèn)即為調(diào)用進(jìn)程。fork 函數(shù)調(diào)用產(chǎn)生的父子進(jìn)程各自的運(yùn)行時(shí)間是不確定的。如果子進(jìn)程先于父進(jìn)程終止,這樣沒有什么問題。但,如果父進(jìn)程先于子進(jìn)程終止,那么子進(jìn)程是不是就沒有了父進(jìn)程,進(jìn)程樹形結(jié)構(gòu)就被破壞了?對(duì)于這個(gè)問題,UNIX 系統(tǒng)這么處理的:如果某個(gè)進(jìn)程終止了,則將該進(jìn)程的所有尚未結(jié)束的子進(jìn)程的父進(jìn)程設(shè)置為 init 進(jìn)程(init 進(jìn)程是絕不會(huì)終止的)。其操作過程大致為:在一個(gè)進(jìn)程終止時(shí),內(nèi)核逐個(gè)檢查所有活動(dòng)進(jìn)程(因?yàn)?UNIX 沒有提供一個(gè)獲取某個(gè)進(jìn)程所有子進(jìn)程的接口),如果是正在終止的進(jìn)程的子進(jìn)程,則將其父進(jìn)程設(shè)置為 init 進(jìn)程。

2 父子進(jìn)程的數(shù)據(jù)共享問題

fork 函數(shù)創(chuàng)建的子進(jìn)程會(huì)獲得父進(jìn)程的數(shù)據(jù)空間、堆和棧的副本。但是,大多數(shù)情況下,fork 之后都會(huì)緊接著調(diào)用 exec 執(zhí)行新程序,從而覆蓋了從父進(jìn)程拷貝的這些副本,這就造成了內(nèi)核做了很多無用功。

現(xiàn)在很多的實(shí)現(xiàn)都采用寫時(shí)復(fù)制(Copy-On-Write,COW)技術(shù)。fork函數(shù)調(diào)用之后,父子進(jìn)程共享這些區(qū)域,而且內(nèi)核將這些區(qū)域的權(quán)限改為只讀的。如果父、子進(jìn)程中任何一個(gè)試圖修改這些區(qū)域,則內(nèi)核只為要修改的區(qū)域做一份拷貝給該進(jìn)程。

下面我們來看一個(gè)共享數(shù)據(jù)的例子,

復(fù)制代碼
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int glob = 0;
int
main(void)
{
    int var;
    pid_t pid;
    var = 0;
    if ((pid = fork()) < 0) {
        printf("fork error: %s\n", strerror(errno));
        exit(-1);
    } else if (pid == 0) {
        var++;
        glob++;
        printf("child: glob=%d, var=%d\n", glob, var);
        exit(0);
    }
    wait(NULL);
    printf("parent: glob=%d, var=%d\n", glob, var);
    exit(0);
}
復(fù)制代碼

該程序在 fork 之后的父進(jìn)程等待子進(jìn)程結(jié)束,而子進(jìn)程將整型變量glob 和 var 都加了 1. 編譯該程序,生成并執(zhí)行 forkdemo. 從下面的運(yùn)行結(jié)果,我們看到子進(jìn)程修改的 glob 和 var 變量對(duì)父進(jìn)程沒有任何影響。

lienhua34:demo$ gcc -o forkdemo forkdemo.c
lienhua34:demo$ ./forkdemo
child: glob=1, var=1
parent: glob=0, var=0

雖說子進(jìn)程享用的是父進(jìn)程的數(shù)據(jù)副本,子進(jìn)程的修改對(duì)父進(jìn)程沒有任何影響。但有個(gè)比較特殊的情況:文件 I/O。fork 會(huì)將父進(jìn)程的所有打開文件描述符都復(fù)制到子進(jìn)程。父子進(jìn)程中相同的文件描述符則共享同一個(gè)文件表項(xiàng)(關(guān)于文件描述符和文件表項(xiàng)的關(guān)系請(qǐng)參考文檔“內(nèi)核 I/O 數(shù)據(jù)結(jié)構(gòu)”)。下面我們看一個(gè)例子,

復(fù)制代碼
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int
main(void)
{
    pid_t pid;
    printf("before fork\n");
    if ((pid = fork()) < 0) {
        printf("fork error: %s\n", strerror(errno));
        exit(-1);
    } else if (pid == 0) {
        printf("in child process\n");
        exit(0);
    }
    wait(NULL);
    printf("in parent process\n");
    exit(0);
}
復(fù)制代碼

編譯該程序,生成并執(zhí)行文件 forkdemo,

復(fù)制代碼
lienhua34:demo$ gcc -o forkdemo forkdemo.c
lienhua34:demo$ ./forkdemo
before fork
in child process
in parent process
lienhua34:demo$ ./forkdemo > foo
lienhua34:demo$ cat foo
before fork
in child process
before fork
in parent process
復(fù)制代碼

在沒有對(duì)標(biāo)準(zhǔn)輸出重定向之前,運(yùn)行 forkdemo 看不出啥問題。當(dāng)重定向標(biāo)準(zhǔn)輸出到一個(gè)文件(./forkdemo > foo)時(shí),我們可以看到父進(jìn)程打印的字符串在子進(jìn)程打印的字符串之后。這是因?yàn)楦缸舆M(jìn)程標(biāo)準(zhǔn)輸出共享了同一個(gè)文件表項(xiàng),也即共享了同一個(gè)文件偏移量。

另外,我們注意到在標(biāo)準(zhǔn)輸出沒有重定向時(shí),字符串“before fork”只輸出一次,但是在標(biāo)準(zhǔn)輸出重定向到文件之后輸出了兩次。這是因?yàn)闃?biāo)準(zhǔn)I/O 庫(kù)函數(shù) printf 在標(biāo)準(zhǔn)輸出連接到終端設(shè)備時(shí)是行緩沖的,于是在 fork函數(shù)之后,緩沖區(qū)中的數(shù)據(jù)已經(jīng)被沖洗了。而當(dāng)標(biāo)準(zhǔn)輸出重定向文件之后,printf 函數(shù)就變成了全緩沖了,在 fork 之前調(diào)用 printf 函數(shù)將字符串“before fork”寫到緩沖區(qū)中,fork 時(shí)該字符串還在緩沖區(qū)中,于是便拷貝一份給子進(jìn)程。當(dāng)父子進(jìn)程都調(diào)用 exit 函數(shù)之后,緩沖區(qū)中的數(shù)據(jù)都被沖洗到文件中,于是被出現(xiàn)了兩份“before fork”。

3 fork 典型應(yīng)用場(chǎng)景

fork 有兩種典型的應(yīng)用場(chǎng)景:

· 創(chuàng)建一個(gè)新進(jìn)程執(zhí)行新的程序。即調(diào)用 fork 之后子進(jìn)程立即調(diào)用 exec函數(shù)執(zhí)行一個(gè)新程序,例如文檔“進(jìn)程控制三部曲”中的示例 2.

· 父進(jìn)程希望復(fù)制自己,使父、子進(jìn)程同時(shí)執(zhí)行不同的代碼段。這在網(wǎng)絡(luò)服務(wù)進(jìn)程中比較常見:父進(jìn)程等待客戶端的服務(wù)請(qǐng)求,當(dāng)接收到一個(gè)請(qǐng)求之后,父進(jìn)程調(diào)用 fork,然后讓子進(jìn)程處理該請(qǐng)求,而父進(jìn)程繼續(xù)等待下一個(gè)服務(wù)請(qǐng)求。其代碼框架如下所示:

復(fù)制代碼
void serve(int sockfd)
{
    int clfd;
    pid_t pid;
    for (;;) {
        clfd = accept(sockfd, NULL, NULL);
        if (clfd < 0) {
            /* print error message */
            continue;
        }
        if ((pid = fork()) < 0) {
            /* fork error */
            continue;
        } else if (pid == 0) {
            /* deal with clfd in child process */
            close(clfd);
            exit(0);
        } else {
            /* in parent process,
            close the accepted socket "clfd",
            then continues to listen next socket connection. */
        } 
    }
}
復(fù)制代碼

4 fork 函數(shù)調(diào)用次數(shù)的最大限制是多少

每個(gè)實(shí)際用戶 ID 具有一個(gè)在任何時(shí)刻的最大進(jìn)程數(shù)。CHILD_MAX 規(guī)定了每個(gè)實(shí)際用戶 ID 在任一時(shí)刻可具有的最大進(jìn)程數(shù)。我們看下面一個(gè)例子,

復(fù)制代碼
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int
main(void)
{
    pid_t pid;
    int count;
    printf("CHILD_MAX: %ld\n", sysconf(_SC_CHILD_MAX));
    count = 1;
    for (;;) {
        if ((pid = fork()) < 0) {
            printf("fork error: %s\n", strerror(errno));
            break;
        } else if (pid == 0) {
            sleep(3);
            exit(0);
        }
        count++;
    }
    printf("count: %d\n", count);
    exit(0);
}
復(fù)制代碼

編譯該程序,生成并運(yùn)行文件 forkdemo,

lienhua34:demo$ gcc -o forkdemo forkdemo.c
lienhua34:demo$ ./forkdemo
CHILD_MAX: 15969
fork error: Resource temporarily unavailable
count: 15737

從上面的運(yùn)行結(jié)果可以看出我的系統(tǒng)規(guī)定了每個(gè)實(shí)際用戶 ID 在任一時(shí)刻可具有的最大進(jìn)程數(shù)為 15969。而在 for 循環(huán)中 fork 創(chuàng)建了 15737 個(gè)進(jìn)程(包括調(diào)用進(jìn)程本身)之后,fork 就因?yàn)闆]有可用資源而創(chuàng)建新進(jìn)程失敗。

5 fork 的變體vfork

vfork 函數(shù)是 fork 函數(shù)的一個(gè)變體,其調(diào)用序列和返回值與 fork 函數(shù)一致,不過兩者的語義不同。維基百科上關(guān)于 vfork 的說明如下(參考fork(system_call))。

Vfork is a variant of fork with the same calling convention and much the same semantics; it originated in the 3BSD version of Unix,[citation needed] the first Unix to support virtual memory. It was standardized by POSIX, which permitted vfork to have exactly the same behavior as fork, but marked obsolescent in the 2004 edition,[4] and has disappeared from subsequent editions.

我們看到在 POSIX 2004 版本中已經(jīng)將 vfork 函數(shù)注為過時(shí)的,而且在之后的版本中已經(jīng)不再出現(xiàn) vfork 函數(shù)了。但是,既然《APUE》中講到了這個(gè),那我們就來看一下 vfork 函數(shù)跟 fork 函數(shù)到底有什么區(qū)別吧。

vfork 函數(shù)和 fork 函數(shù)的區(qū)別有兩點(diǎn):

1. fork 會(huì)將父進(jìn)程的地址空間拷貝給子進(jìn)程;而 vfork 沒有,子進(jìn)程在父進(jìn)程的地址空間中運(yùn)行。

2. fork 無法確保父子進(jìn)程的執(zhí)行順序;而 vfork 保證子進(jìn)程先執(zhí)行,父進(jìn)程會(huì)一直阻塞直到子進(jìn)程調(diào)用 exit 或 exec。(注:vfork 的這個(gè)特征可能會(huì)導(dǎo)致死鎖,若子進(jìn)程在調(diào)用 exit 或 exec 之前依賴于父進(jìn)程的進(jìn)一步動(dòng)作,而父進(jìn)程也正在等待子進(jìn)程,于是出現(xiàn)了循環(huán)等待的問題。)

我們來對(duì)比一下 vfork 和 fork 在處理數(shù)據(jù)方面有什么不同,

復(fù)制代碼
#include <stdlib.h>
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <errno.h>
int glob = 0;
int
main(void)
{
    int var;
    pid_t pid;
    var = 0;
    if ((pid = vfork()) < 0) {
        printf("fork error: %s\n", strerror(errno));
        exit(-1);
    } else if (pid == 0) {
        var++;
        glob++;
        printf("child: glob=%d, var=%d\n", glob, var);
        exit(0);
    }
    printf("parent: glob=%d, var=%d\n", glob, var);
    exit(0);
}
復(fù)制代碼

上面程序拷貝了上面 fork 函數(shù)處理共享數(shù)據(jù)的示例程序,將 fork 改成vfork,并且去掉了 wait(NULL) 語句。保存為 vforkdemo.c,編譯該程序,生成并執(zhí)行 vforkdemo 文件,

lienhua34:demo$ gcc -o vforkdemo vforkdemo.c
lienhua34:demo$ ./vforkdemo
child: glob=1, var=1
parent: glob=1, var=1

從上面的運(yùn)行結(jié)果,我們看到 vfork 創(chuàng)建的子進(jìn)程修改了 glob 和 var變量之后,父進(jìn)程也看到了這個(gè)修改。

vfork 函數(shù)的出現(xiàn)原因可能是早期系統(tǒng)的 fork 沒有實(shí)現(xiàn)寫時(shí)復(fù)制技術(shù),導(dǎo)致每次 fork 調(diào)用做了很多無用功(大多數(shù)情況下都是 fork 之后調(diào)用 exec執(zhí)行新程序)且效率不高,于是便創(chuàng)造了 vfork 函數(shù)。而現(xiàn)在的實(shí)現(xiàn)基本都是采用寫時(shí)復(fù)制技術(shù),而且 vfork 函數(shù)使用不當(dāng)還會(huì)出現(xiàn)死鎖,于是 vfork函數(shù)也便沒有了存在的必要性。

(done)

    本站是提供個(gè)人知識(shí)管理的網(wǎng)絡(luò)存儲(chǔ)空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點(diǎn)。請(qǐng)注意甄別內(nèi)容中的聯(lián)系方式、誘導(dǎo)購(gòu)買等信息,謹(jǐn)防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請(qǐng)點(diǎn)擊一鍵舉報(bào)。
    轉(zhuǎn)藏 分享 獻(xiàn)花(0

    0條評(píng)論

    發(fā)表

    請(qǐng)遵守用戶 評(píng)論公約

    類似文章 更多

    欧美精品在线观看国产| 亚洲中文字幕在线观看四区| 亚洲视频在线观看你懂的| 亚洲欧美黑人一区二区| 国产一区二区精品高清免费| 加勒比系列一区二区在线观看| 中国黄色色片色哟哟哟哟哟哟| 亚洲天堂男人在线观看| 欧美午夜一级特黄大片| 国产精品亚洲二区三区| 国产精品久久精品国产| 中文字幕欧美视频二区| 黄色国产精品一区二区三区| 欧美日韩国产黑人一区| 国产一级一片内射视频在线| 东北老熟妇全程露脸被内射| 一区二区在线激情视频| 欧美日韩国产二三四区| 亚洲午夜福利视频在线| 国产精品不卡高清在线观看| 国产一级内射麻豆91| 国产又粗又猛又黄又爽视频免费| 很黄很污在线免费观看| 日韩中文无线码在线视频 | 乱女午夜精品一区二区三区| 国产不卡在线免费观看视频| 99久热只有精品视频最新| 日韩免费av一区二区三区| 好吊一区二区三区在线看| 成年男女午夜久久久精品| 婷婷亚洲综合五月天麻豆| 午夜福利视频日本一区| 精品国产亚洲一区二区三区| 欧美日韩一区二区三区色拉拉| 国语久精品在视频在线观看| 亚洲永久一区二区三区在线| 日韩欧美国产精品自拍| 亚洲一区二区三区av高清| 欧美同性视频免费观看| 噜噜中文字幕一区二区| 日韩欧美一区二区久久婷婷|