UNIX-like 操作系統(tǒng),有一個(gè)強(qiáng)勁的功能:可以同時(shí)運(yùn)行多個(gè)進(jìn)程,并且讓進(jìn)程們共享 CPU,內(nèi)存,和其他的資源.我們所開(kāi)發(fā)的程序,往往會(huì)從一個(gè)進(jìn)程演變?yōu)槎鄠€(gè)進(jìn)程.的確,許多時(shí)候線程是不錯(cuò)的選擇,不過(guò)多進(jìn)程和多線程使用有許多是相通的:如何啟動(dòng)和停止進(jìn)程,如何在進(jìn)程間通信,如何同步進(jìn)程.
什么是 UNIX 進(jìn)程?
談?wù)撨M(jìn)程前,需要正確的理解"什么是進(jìn)程":
進(jìn)程是一個(gè)正在運(yùn)行的程序?qū)嵗瑘?zhí)行給定的代碼,有自己的"執(zhí)行棧內(nèi)存",有自己的"內(nèi)存頁(yè)集合",有自己的"文件描述符號(hào)碼表",有一個(gè)唯一的進(jìn)程號(hào)碼.
從這個(gè)定義可以知道,一個(gè)進(jìn)程并不代表一個(gè)程序,幾個(gè)進(jìn)程可能是同一時(shí)刻同一個(gè)程序的多個(gè)實(shí)例,他們的用戶可能是相同的,也可能是不同的.
使用系統(tǒng)調(diào)用 fork() 創(chuàng)建進(jìn)程
系統(tǒng)調(diào)用 pid_t fork(void)
用來(lái)創(chuàng)建新的進(jìn)程.這是一個(gè)非常奇特的調(diào)用,返回兩次!聽(tīng)起來(lái)很奇怪.首先,在一個(gè)正在運(yùn)行的進(jìn)程中調(diào)用 fork()
,會(huì)創(chuàng)建一個(gè)新進(jìn)程,新進(jìn)程是調(diào)用進(jìn)程的子進(jìn)程.當(dāng) fork()
時(shí),調(diào)用進(jìn)程的所有正在使用的"內(nèi)存頁(yè)"會(huì)被復(fù)制,調(diào)用進(jìn)程正在使用的"文件描述符號(hào)碼表"也會(huì)被復(fù)制,這些復(fù)制被放入新進(jìn)程中使用.因此,新進(jìn)程和調(diào)用進(jìn)程的這些內(nèi)容變成相互獨(dú)立的.

注意:
"寫時(shí)復(fù)制",許多現(xiàn)代系統(tǒng)采用了這些技術(shù).當(dāng)
fork()
時(shí),系統(tǒng)并不立刻復(fù)制"內(nèi)存頁(yè)"和"文件描述符號(hào)碼表",只是在新進(jìn)程中做一個(gè)映射,這樣會(huì)節(jié)省很多復(fù)制開(kāi)銷.當(dāng)新進(jìn)程中有代碼修改"內(nèi)存頁(yè)"和"文件描述符號(hào)碼表"時(shí),系統(tǒng)會(huì)立刻復(fù)制一個(gè)副本,用于修改.
還有一個(gè)需要注意的:
"文件描述符號(hào)碼表":操作系統(tǒng)每打開(kāi)一個(gè)文件,會(huì)在內(nèi)核緩存中保存文件資源,并為每一個(gè)文件建立一些散列表,記錄文件信息,同時(shí)為開(kāi)發(fā)者提供一個(gè)非負(fù)整數(shù)的號(hào)碼(文件表的索引),用于引用內(nèi)存中的文件資源.因此,多個(gè)進(jìn)程的"文件描述符號(hào)碼表"雖然是獨(dú)立的,但是號(hào)碼指向的文件卻可能是同一個(gè)文件,對(duì)同一個(gè)文件的修改是可以影響到多個(gè)進(jìn)程.由此,出現(xiàn)了另一項(xiàng)技術(shù):"同步",防止多進(jìn)程同時(shí)修改文件資源.
fork()
返回兩次,返回值有 3 種情況:
- 如果返回的是 0 ,那么返回值位于新進(jìn)程中
- 如果返回的是 >0,那么返回值位于調(diào)用進(jìn)程中,返回值是新進(jìn)程的進(jìn)程號(hào)碼.返回值
pid_t
定義在sys/types.h
頭文件中,通常是一個(gè)整數(shù) - 如果返回的是 -1,那么返回值位于調(diào)用進(jìn)程中,創(chuàng)建新進(jìn)程失敗
例子:
#include <stdio.h>
#include <stdlib.h>
#define MAX_COUNT 200
void doChild(void);
void doParent(void);
void main(void) {
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
}
void doChild(void) {
int i;
for (i = 1; i <= MAX_COUNT; i++)
printf("This line is from child, value = %d\n", i);
printf("*** Child process is done ***\n");
}
void doParent(void) {
int i;
for (i = 1; i <= MAX_COUNT; i++)
printf("This line is from parent, value = %d\n", i);
printf("*** Parent is done ***\n");
}
對(duì)于這個(gè)例子,我準(zhǔn)備了一個(gè) Nim 語(yǔ)言版本的:
import posix, os
const maxCount = 200
proc doChild()
proc doParent()
proc main() =
var pid = fork()
case pid
of -1:
raise newException(OsError, osLastError().osErrorMsg())
of 0:
doChild()
quit(QuitSuccess)
else:
doParent()
proc doChild() =
for i in 1..maxCount:
echo "This line is from child, value = ", i
echo "*** Child process is done ***"
proc doParent() =
for i in 1..maxCount:
echo "This line is from parent, value = ", i
echo "*** Parent process is done ***"
main()
子進(jìn)程結(jié)束
一旦創(chuàng)建了一個(gè)新進(jìn)程,其就成為調(diào)用進(jìn)程的子進(jìn)程.有兩種可能存在的情況:父進(jìn)程先于子進(jìn)程退出,子進(jìn)程先于父進(jìn)程退出:
當(dāng)一個(gè)子進(jìn)程退出時(shí),并不立刻清空進(jìn)程表,而是向父進(jìn)程發(fā)送一個(gè)信號(hào).父進(jìn)程需要對(duì)此應(yīng)答,然后系統(tǒng)會(huì)完全清除子進(jìn)程.假設(shè)父進(jìn)程沒(méi)有應(yīng)答,或者應(yīng)答之前子進(jìn)程退出,子進(jìn)程會(huì)被系統(tǒng)設(shè)置為"僵尸"狀態(tài).
當(dāng)一個(gè)父進(jìn)程退出時(shí),如果有幾個(gè)子進(jìn)程仍在運(yùn)行,這些子進(jìn)程會(huì)變成"孤兒進(jìn)程"."孤兒進(jìn)程"會(huì)立刻被 "init" 超級(jí)進(jìn)程接管,作為其父進(jìn)程."init" 進(jìn)程能夠確保這些子進(jìn)程在退出時(shí)不會(huì)變?yōu)椋⒔┦M(jìn)程",因?yàn)?"init" 進(jìn)程總是應(yīng)答子進(jìn)程的退出.("init" 進(jìn)程是隨操作系統(tǒng)啟動(dòng)的第一個(gè)進(jìn)程,進(jìn)程號(hào)碼是 1)
使用系統(tǒng)調(diào)用 wait() 應(yīng)答子進(jìn)程
應(yīng)答子進(jìn)程的簡(jiǎn)單方法,是使用系統(tǒng)調(diào)用 pid_t wait(int * status)
.當(dāng)調(diào)用這個(gè)函數(shù)時(shí),有以下可能:
- 進(jìn)程中沒(méi)有子進(jìn)程,立刻返回
- 進(jìn)程中的子進(jìn)程已經(jīng)退出,并且是"僵尸"狀態(tài),立刻應(yīng)答并返回,取得子進(jìn)程狀態(tài)(通過(guò)參數(shù))
- 進(jìn)程中的子進(jìn)程都在運(yùn)行,立刻阻塞,直到有一個(gè)子進(jìn)程退出或者收到一個(gè) "SIGCHILD" 信號(hào)時(shí)返回,取得子進(jìn)程狀態(tài)(通過(guò)參數(shù))

例子:
#include <stdio.h>
#include <stdlib.h>
#define MAX_COUNT 200
void doChild(void);
void doParent(void);
void main(void) {
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
int status;
pid = wait(&status);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(void) {
int i;
for (i = 1; i <= MAX_COUNT; i++)
printf("This line is from child, value = %d\n", i);
printf("*** Child process is done ***\n");
}
void doParent(void) {
int i;
for (i = 1; i <= MAX_COUNT; i++)
printf("This line is from parent, value = %d\n", i);
printf("*** Parent is done ***\n");
}
注:當(dāng)一個(gè)進(jìn)程正常或者異常結(jié)束時(shí),內(nèi)核就向其父進(jìn)程發(fā)送 "SIGCHILD" 信號(hào).子進(jìn)程結(jié)束是一個(gè)異步事件,(可以在父進(jìn)程運(yùn)行的任何時(shí)候發(fā)生),所以這種信號(hào)由內(nèi)核向父進(jìn)程發(fā)送異步通知.父進(jìn)程可以選擇忽略該信號(hào),也可以提供一個(gè)信號(hào)處理函數(shù):
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#define MAX_COUNT 200
void catchChild(int sigNumber);
void doChild(void);
void doParent(void);
void main(void) {
signal(SIGCHLD, catchChild);
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
sleep(1);
printf("*** Parent exits ***\n");
exit(0);
}
void catchChild(int sigNumber) {
int childStatus;
pid_t pid = wait(&childStatus);
printf("*** Parent detects process %d is done ***\n", pid);
}
void doChild(void) {
printf("*** Child process is done ***\n");
}
void doParent(void) {
printf("*** Parent is done ***\n");
}
也可以:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#define MAX_COUNT 200
void catchChild(int sigNumber);
void doChild(void);
void doParent(void);
void main(void) {
struct sigaction action;
struct sigaction oaction;
action.sa_flags = 0;
action.sa_handler = catchChild;
sigaction(SIGCHLD, &action, &oaction);
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
sleep(1);
printf("*** Parent exits ***\n");
exit(0);
}
void catchChild(int sigNumber) {
int childStatus;
pid_t pid = wait(&childStatus);
printf("*** Parent detects process %d is done ***\n", pid);
}
void doChild(void) {
printf("*** Child process is done ***\n");
}
void doParent(void) {
printf("*** Parent is done ***\n");
}
使用系統(tǒng)調(diào)用 exec() 執(zhí)行程序
有時(shí)候,創(chuàng)建一個(gè)子進(jìn)程,但是并不想運(yùn)行和父進(jìn)程同樣的程序.這時(shí)候可以使用 exec
家族執(zhí)行另一個(gè)二進(jìn)制文件.exec
家族函數(shù)包括: execl()
,execlp()
,execle()
,execv()
,execvp()
,execve()
.這里介紹一下 int execvp(const char *file, char *const argv[])
.
- 第一個(gè)參數(shù)是一個(gè)字符串,表示可執(zhí)行文件的路徑
- 第二個(gè)參數(shù)是傳遞給可執(zhí)行程序的參數(shù),同
int main(int argc, char **argv)
中的argv
.
當(dāng) execvp()
調(diào)用時(shí),給出的二進(jìn)制程序文件會(huì)被加載進(jìn)內(nèi)存,該子進(jìn)程 fork()
出來(lái)的所有"內(nèi)存頁(yè)"和"文件描述符號(hào)碼表"都會(huì)清空.子進(jìn)程的進(jìn)程號(hào)碼不變.然后子進(jìn)程運(yùn)行新的二進(jìn)制程序.
如果 execvp()
執(zhí)行程序成功,該函數(shù)不會(huì)返回,其下面的代碼不會(huì)繼續(xù)運(yùn)行,因?yàn)橐呀?jīng)轉(zhuǎn)入新的程序中.如果執(zhí)行程序失敗,返回 -1.

#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#define MAX_COUNT 200
void doChild(char **argv);
void doParent(void);
void main(int argc, char **argv) {
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild(argv);
exit(0);
default:
doParent();
}
int status;
pid = wait(&status);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(char **argv) {
execvp(argv[1], NULL);
printf("*** Child process is done ***\n");
}
void doParent(void) {
printf("*** Parent is done ***\n");
}
使用系統(tǒng)調(diào)用 pipe() 創(chuàng)建管道,在進(jìn)程間通信
一旦創(chuàng)建了多個(gè)進(jìn)程后,我們突然意識(shí)到?jīng)]有辦法在進(jìn)程之間通信.在多進(jìn)程的程序中,我們常常需要進(jìn)程之間共同完成一些任務(wù).在父-子進(jìn)程這樣的關(guān)系進(jìn)程,可以使用管道(匿名管道)進(jìn)行通信.
系統(tǒng)調(diào)用 int pipe(int fds[2])
,創(chuàng)建一對(duì)管道,第一個(gè)用于讀,第二個(gè)用于寫,返回兩個(gè)對(duì)應(yīng)的文件描述符號(hào)碼.
管道非常受限:
- 每一個(gè)都是單向的,要么只能讀,要么只能寫
- 管道只能在有父-子關(guān)系的進(jìn)程中使用,不相關(guān)的進(jìn)程之間無(wú)法使用.
管道也有一些優(yōu)點(diǎn):.
- 管道中傳輸?shù)氖亲止?jié)流,寫入的順序和讀取的順序是一致的
- 管道中不會(huì)丟失數(shù)據(jù),除非讀取端提前關(guān)閉讀取管道

#include <stdio.h>
#include <stdlib.h>
#define BUFFSIZE 6
void doChild(int pipefds[2]);
void doParent(int pipefds[2]);
void main(void) {
int pipefds[2];
pipe(pipefds);
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild(pipefds);
exit(0);
default:
doParent(pipefds);
}
int status;
pid = wait(&status);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(int pipefds[2]) {
close(pipefds[1]);
char buff[BUFFSIZE];
read(pipefds[0], buff, sizeof(buff));
printf("Recv %s.\n", buff);
printf("*** Child process is done ***\n");
}
void doParent(int pipefds[2]) {
close(pipefds[0]);
char buff[BUFFSIZE] = "Hello";
write(pipefds[1], buff, sizeof(buff));
printf("*** Parent is done ***\n");
}
如果 pipe()
調(diào)用成功,將會(huì)創(chuàng)建一對(duì)管道.pipefds[0] 用于讀取,pipefds[1] 用于寫入.我們首先使用 pipe()
創(chuàng)建了管道,然后調(diào)用 fork()
創(chuàng)建一個(gè)子進(jìn)程,fork()
會(huì)把 pipe()
創(chuàng)建的管道描述符做一份復(fù)制給新進(jìn)程.因此,在新進(jìn)程(子進(jìn)程)也存在同樣的"文件描述符號(hào)碼表",他們都指向操作系統(tǒng)內(nèi)核緩沖中存放的管道文件資源.另外,我們?cè)诟缸舆M(jìn)程分別關(guān)閉了不需要的"文件描述符號(hào)碼表",雖然不是必須的,但是"打開(kāi)的文件"是有上限的,盡可能的關(guān)閉不使用的總是好的.
close()
只會(huì)減少內(nèi)核緩沖中保存的"文件資源"的引用計(jì)數(shù),而不是真的關(guān)閉"文件資源".當(dāng)引用計(jì)數(shù)變?yōu)?0 的時(shí)候,才會(huì)真正關(guān)閉"文件資源".同樣的,每次 open()
或者以其他方式打開(kāi)一個(gè)"文件資源"時(shí),也會(huì)把引用計(jì)數(shù)加 1.
從上面的代碼也可以看出,管道實(shí)際上是屬于文件性質(zhì),對(duì)管道的操作跟對(duì)文件的操作是相同的.這意味著你也可以把管道設(shè)置為非阻塞的(請(qǐng)關(guān)注我的后續(xù)文字: POSIX 程序設(shè)計(jì) __ IO),可以使用文件操作的常用函數(shù).
在更復(fù)雜的程序中,你可能需要一個(gè)雙向通信的管道,那么你必須建立兩對(duì)管道:每一個(gè)進(jìn)程使用兩個(gè)端進(jìn)行讀和寫.這時(shí)候你必須要小心,因?yàn)檫@樣使用管道很容易"死鎖".我們沒(méi)有用鎖,怎么會(huì)出現(xiàn)"死鎖"?事實(shí)上,這種情況只是跟"死鎖"相同的表現(xiàn).
進(jìn)程 A 進(jìn)程 B
pipefds_a[1] ----------> pipefds_a[0]
pipefds_b[0] <---------- pipefds_b[1]
比如在上圖中,進(jìn)程 A 和進(jìn)程 B 分別通過(guò)一對(duì)管道進(jìn)行讀寫.
假設(shè)進(jìn)程 A 使用 pipefds_b[0,阻塞在讀取時(shí),進(jìn)程 B 也使用 pipefds_b[1] 阻塞在讀取.這時(shí)候,兩邊都沒(méi)有字節(jié)流發(fā)送,進(jìn)程 A 和進(jìn)程 B 就會(huì)一直阻塞在那里,什么都不做了.
假設(shè)進(jìn)程 A 使用 pipefds_a[1] 寫入.基于字節(jié)流的讀寫往往都是基于內(nèi)核緩沖區(qū),管道也不例外.當(dāng)創(chuàng)建管道的時(shí)候,內(nèi)核同時(shí)為管道分配了一塊內(nèi)存緩沖,管道寫入的時(shí)候會(huì)寫入到這塊內(nèi)核緩沖區(qū)上,管道讀取的時(shí)候也會(huì)從這塊內(nèi)核緩沖區(qū)讀走數(shù)據(jù).如果寫入的數(shù)據(jù)很大,內(nèi)核緩沖區(qū)已經(jīng)填滿了,那么進(jìn)程 A 就會(huì)阻塞在那,等待內(nèi)核緩沖區(qū)清空,繼續(xù)寫入.然而此時(shí),進(jìn)程 B 并沒(méi)有讀取數(shù)據(jù),也在使用 pipefds_b[1] 寫入管道的內(nèi)核緩沖區(qū),也阻塞在那里.這樣,兩個(gè)進(jìn)程也同時(shí)阻塞,什么都不做了.
建議:可能的話,把管道讀寫設(shè)置為非阻塞的.
使用系統(tǒng)調(diào)用 mkfifo() 創(chuàng)建命名管道,在進(jìn)程間通信
管道 pipe()
有一個(gè)限制:只能在父-子進(jìn)程之間使用(祖父-孫子也可以).在不相干的進(jìn)程之間通信,可以使用命名管道 FIFO.int mkfifo(const char * filename, mode_t mode)
打開(kāi)一個(gè)文件,然后進(jìn)行寫入數(shù)據(jù),寫入完畢關(guān)閉,另外一個(gè)進(jìn)程就可以打開(kāi)同一個(gè)文件進(jìn)行讀取數(shù)據(jù).
說(shuō)白了,管道是使用內(nèi)存交換數(shù)據(jù),命名管道使用一個(gè)實(shí)際的文件交換數(shù)據(jù).操作一個(gè)命名管道和操作一個(gè)普通文件非常相似.另外,命名管道不能夠同時(shí)讀和寫,要么讀,要么寫,每一個(gè)操作只能等待另一個(gè)的完成.因?yàn)槭菍?duì)文件的操作,需要有文件的讀寫權(quán)限.
從一個(gè)命名管道讀取時(shí),如果沒(méi)有數(shù)據(jù),會(huì)阻塞,而不是立刻返回 EOF(這與普通文件的讀不同).當(dāng)命名管道寫入時(shí),如果內(nèi)核緩沖區(qū)塞滿了,但是沒(méi)有消費(fèi)者(讀取),進(jìn)程會(huì)阻塞在那,直到內(nèi)核緩沖區(qū)的數(shù)據(jù)被讀取清空.
建議:可能的話,把命名管道讀寫設(shè)置為非阻塞的.

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/stat.h>
#define BUFFSIZE 6
void doChild(void);
void doParent(void);
void main(void) {
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
int status;
pid = wait(&status);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(void) {
mkfifo("./fifo_file", S_IFIFO | 0666);
char buff[BUFFSIZE];
int fd = open("./fifo_file", O_RDONLY);
read(fd, buff, sizeof(buff));
printf("Recv %s.\n", buff);
printf("*** Child process is done ***\n");
}
void doParent(void) {
mkfifo("./fifo_file", S_IFIFO | 0666);
char buff[BUFFSIZE] = "Hello";
int fd = open("./fifo_file", O_WRONLY);
write(fd, buff, sizeof(buff));
printf("Write %s.\n", buff);
printf("*** Parent is done ***\n");
}
使用系統(tǒng)調(diào)用 socket() 創(chuàng)建套接字,在進(jìn)程間通信
這是一個(gè)廣博的話題,應(yīng)該占用單獨(dú)一個(gè)章節(jié).

這里我們單獨(dú)介紹一下 UNIX Domain Socket,這是一個(gè)非常好用的通信方式,跟管道是非常相似,甚至工作方式接近相同,但是擁有非常棒的優(yōu)點(diǎn):進(jìn)程之間可以是不相關(guān)的,通信方式是雙向的(管道是單向的).
我們可以使用普通的 int socket(int domain, int type, int protocol)
系統(tǒng)調(diào)用,并設(shè)置 domain 為 AF_UNIX
,采用本機(jī)通信來(lái)建立本機(jī)多進(jìn)程之間的通信.不過(guò),UNIX 系統(tǒng)為這種場(chǎng)景提供了一個(gè)更簡(jiǎn)便可行的方案:int socketpair(int domain, int type, int protocol, int sockfd[2])
.
使用 socketpair()
,會(huì)創(chuàng)建一對(duì)管道,每一端都可以讀寫,是雙向的,跨進(jìn)程的.除此之外,跟管道是相同的工作方式.
建議:對(duì)于進(jìn)程間通信,這是一個(gè)非常效率穩(wěn)定的方式,你也可以將其設(shè)置為非阻塞的.
#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#define BUFFSIZE 6
void doChild(int pipefds[2]);
void doParent(int pipefds[2]);
void main(void) {
int pipefds[2];
socketpair(AF_UNIX, SOCK_STREAM, 0, pipefds);
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild(pipefds);
exit(0);
default:
doParent(pipefds);
}
int status;
pid = wait(&status);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(int pipefds[2]) {
close(pipefds[1]);
char buff[BUFFSIZE];
read(pipefds[0], buff, sizeof(buff));
printf("Child recv %s.\n", buff);
char buff2[BUFFSIZE] = "OK";
write(pipefds[0], buff2, sizeof(buff2));
printf("Child send %s.\n", buff2);
printf("*** Child process is done ***\n");
}
void doParent(int pipefds[2]) {
close(pipefds[0]);
char buff[BUFFSIZE] = "Hello";
write(pipefds[1], buff, sizeof(buff));
printf("Parent send %s.\n", buff);
char buff2[BUFFSIZE];
read(pipefds[1], buff2, sizeof(buff2));
printf("Parent recv %s.\n", buff2);
printf("*** Parent is done ***\n");
}
使用系統(tǒng)調(diào)用 mmap() 創(chuàng)建內(nèi)存映射,在進(jìn)程間通信
系統(tǒng)調(diào)用 void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset)
在虛擬內(nèi)存空間創(chuàng)建一個(gè)內(nèi)存映射,映射的內(nèi)存可以與其他進(jìn)程共享:
- 映射同一個(gè)文件同一個(gè)區(qū)域,共享相同的"物理內(nèi)存頁(yè)"
- 通過(guò)
fork()
創(chuàng)建的子進(jìn)程,會(huì)復(fù)制父進(jìn)程的"內(nèi)存頁(yè)",可以在這些內(nèi)存頁(yè)中映射共享內(nèi)存

#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/mman.h>
#include <pthread.h>
#define BUFFSIZE 6
void doChild(void);
void doParent(void);
struct Data {
pthread_mutex_t mutex;
int state;
};
static struct Data *data;
void main(void) {
int fd;
pthread_mutexattr_t mattr;
fd = open("/dev/zero", O_RDWR, 0);
data = (struct Data *)mmap(0, sizeof(struct Data),
PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
close(fd);
pthread_mutexattr_init(&mattr);
pthread_mutexattr_setpshared(&mattr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&data->mutex, NULL);
data->state = 0;
pid_t pid = fork();
switch (pid) {
case -1:
perror("Counld not fork");
exit(1);
case 0:
doChild();
exit(0);
default:
doParent();
}
int status;
pid = wait(&status);
printf("Parent state %d\n", data->state);
printf("*** Parent detects process %d is done ***\n", pid);
printf("*** Parent exits ***\n");
exit(0);
}
void doChild(void) {
pthread_mutex_lock(&data->mutex);
data->state = 1;
pthread_mutex_unlock(&data->mutex);
printf("*** Child process is done ***\n");
}
void doParent(void) {
printf("*** Parent is done ***\n");
}
在這個(gè)程序中,我們首先定義了一個(gè)全局共享數(shù)據(jù) data
,在后面的代碼中使用 mmap()
對(duì)其申請(qǐng)內(nèi)存,并映射內(nèi)存頁(yè).open()
打開(kāi)了一個(gè) /dev/zero
的設(shè)備,當(dāng)從這個(gè)設(shè)備讀取數(shù)據(jù)時(shí),獲得的是字符 \0
,可以用來(lái)初始化一個(gè)文件(也可以向這個(gè)字符設(shè)備寫入,字符設(shè)備會(huì)丟棄數(shù)據(jù),什么也不做).然后我們?cè)O(shè)置共享數(shù)據(jù)的 state
為 0,初始化線程鎖,通過(guò) pthread_mutexattr_setpshared
系統(tǒng)調(diào)用設(shè)置鎖的共享屬性是進(jìn)程共享 PTHREAD_PROCESS_SHARED
.然后調(diào)用 fork()
創(chuàng)建子進(jìn)程,在子進(jìn)程中修改共享數(shù)據(jù) data
的 state
字段為 1.最后在父進(jìn)程中 wait
等待并應(yīng)答子進(jìn)程,打印最后的共享數(shù)據(jù) data
的 state
值.你會(huì)發(fā)現(xiàn),state
已經(jīng)變?yōu)?1,這表示父進(jìn)程和子進(jìn)程確實(shí)共享了 data
.
這種數(shù)據(jù)通信,是基于內(nèi)存映射的,所以操作非常快.
更多的進(jìn)程通信
還有一些進(jìn)程通信方式,包括但不限于:
- 消息隊(duì)列
- 信號(hào)量
- 共享內(nèi)存(類似于 mmap,但是可移植較差)
友情提示:這些方式無(wú)法使用 select()
或者 poll()
,進(jìn)程等待消息只能通過(guò)消息隊(duì)列,不能通過(guò)其他類似通知的方式知道消息到來(lái).除非你沒(méi)有更好的法子的時(shí)候,選擇這些通信方式.
常用編程 API
pid_t fork(void)
// 創(chuàng)建一個(gè)子進(jìn)程
pid_t wait(int *status)
// 等待并應(yīng)答一個(gè)隨機(jī)子進(jìn)程
pid_t waitpid(pid_t pid, int * status, int options)
// 等待并應(yīng)答一個(gè)特定子進(jìn)程
int execve(const char * filename, char const *argv, char const *envp)
int execl( const char *path, const char *arg, ...)
int execlp(const char *file, const char *arg, ...)
int execle(const char *path, const char *arg, ..., char * const envp[])
int execv( const char *path, char *const argv[])
int execvp(const char *file, char *const argv[])
// 在一個(gè)子進(jìn)程中,執(zhí)行一個(gè)二進(jìn)制可執(zhí)行文件,運(yùn)行新程序
void exit(int status)
// 退出進(jìn)程
pid_t getpid(void) // 獲取當(dāng)前進(jìn)程號(hào)碼
pid_t getppid(void) // 獲取當(dāng)前進(jìn)程的父進(jìn)程號(hào)碼
pid_t getpgrp(void) // 獲取當(dāng)前進(jìn)程的進(jìn)程組號(hào)碼
pid_t getpgid(pid_t pid) // 獲取特定進(jìn)程的進(jìn)程組號(hào)碼
pid_t getsid(pid_t pid) // 獲取特定進(jìn)程的進(jìn)程組首進(jìn)程號(hào)碼
int setpgid(pid_t pid, pid_t pgid)
// 設(shè)置特定進(jìn)程的進(jìn)程組號(hào)碼
pid_t setsid(void)
// 使當(dāng)前進(jìn)程創(chuàng)建一個(gè)新會(huì)話,并成為新進(jìn)程組首進(jìn)程
uid_t getuid(void) // 獲取當(dāng)前進(jìn)程的用戶號(hào)碼
uid_t geteuid(void) // 獲取當(dāng)前進(jìn)程的有效用戶號(hào)碼
gid_t getgid(void) // 獲取當(dāng)前進(jìn)程的用戶組號(hào)碼
gid_t getegid(void) // 獲取當(dāng)前進(jìn)程的有效用戶組號(hào)碼
int setuid(uid_t uid) // 設(shè)置當(dāng)前進(jìn)程的用戶號(hào)碼
int seteuid(uid_t uid) // 設(shè)置當(dāng)前進(jìn)程的有效用戶號(hào)碼
int setgid(gid_t gid) // 設(shè)置當(dāng)前進(jìn)程的用戶組號(hào)碼
int setegid(gid_t gid) // 設(shè)置當(dāng)前進(jìn)程的有效用戶組號(hào)碼
int pipe(int fds[2])
// 創(chuàng)建一對(duì)匿名管道
int mkfifo(const char * filename, mode_t mode)
// 創(chuàng)建一對(duì)命名管道
int socketpair(int domain, int type, int protocol, int sockfd[2])
// 創(chuàng)建一對(duì)本機(jī)套接字管道
void *mmap(void *addr, size_t length, int port, int flags, int fd, off_t offset)
// 創(chuàng)建一個(gè)內(nèi)存映射
友情提示:出于演示簡(jiǎn)潔,本文字中的函數(shù)調(diào)用,大部分沒(méi)有檢查創(chuàng)建的正確性,實(shí)際項(xiàng)目中,應(yīng)該有嚴(yán)格的檢查.