fork函式的學習--深入瞭解計算機系統
fork函式的學習
1、介紹
父程式可以通過呼叫fork函式建立一一個新的執行的子程式。
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);
//返回:子程式返回0,父程式返回子程式的PID,如果出錯,則為-1。
新建立的子程式幾乎但不完全與父程式相同。子程式得到與父程式使用者級虛擬地址空間相同的(但是獨立的)一份副本,包括程式碼和資料段、堆、共享庫以及使用者棧。子程式還獲得與父程式任何開啟檔案描述符相同的副本,這就意味著當父程式呼叫fork 時,子程式可以讀寫父,程式中開啟的任何檔案。父程式和新建立的子程式之間最大的區別在於它們有不同的PID。
fork函式是有趣的(也常常令人迷惑),因為它只被呼叫一次,卻會返回兩次:一次是在呼叫程式(父程式)中,一次是在新建立的子程式中。在父程式中,fork 返回子程式的PID。在子程式中,fork返回0。 因為子程式的PID總是為非零,返回值就提供一個明確的方法來分辨程式是在父程式還是在子程式中執行。
程式碼框1 展示了一個使用fork 建立子程式的父程式的示例。當fork呼叫在第6行返回時,在父程式和子程式中x的值都為1。子程式在第8行加一-並輸出它的x的副本。相似地,父程式在第13行減- -並輸出它的x的副本。
int main()
{
pid_t pid;
int x=1;
pid = Fork() ;
if(pid == 0){ /*Child*/
printf("child : x=%d\n", ++x) ;
exit(0) ;
}
/* Parent */
printf ("parent: x=%d\n", --x);
exit(0) ;
}
程式碼框1
當在Unix系統上執行這個程式時,我們得到程式碼框2中的結果:
linux> . /fork
parent: x=0
child : x=2
程式碼框2
這個簡單的例子有一些微妙的方面。
●呼叫一次,返回兩次。fork 函式被父程式呼叫一次,但是卻返回兩次——一次是返回到父程式,一次是返回到新建立的子程式。對於只建立一個子程式的程式來說,這還是相當簡單直接的。但是具有多個fork 例項的程式可能就會令人迷惑,需要仔細地推敲了。
●併發執行。父程式和子程式是併發執行的獨立程式。核心能夠以任意方式交替執行它們的邏輯控制流中的指令。在我們的系統上執行這個程式時,父程式先完成它的printf語句,然後是子程式。然而,在另一個系統上可能正好相反。一般而言,作為程式設計師,我們決不能對不同程式中指令的交替執行做任何假設。
●相同但是獨立的地址空間。如果能夠在fork函式在父程式和子程式中返回後立即暫停這兩個程式,我們會看到兩個程式的地址空間都是相同的。每個程式有相同的使用者棧、相同的本地變數值、相同的堆、相同的全域性變數值,以及相同的程式碼。因此,在我們的示例程式中,當fork函式在第6行返回時,本地變數x在父程式和子程式中都為1。然而,因為父程式和子程式是獨立的程式,它們都有自己的私有地址空間。後面,父程式和子程式對x所做的任何改變都是獨立的,不會反映在另一個程式的記憶體中。這就是為什麼當父程式和子程式呼叫它們各自的printf語句時,它們中的變數x會有不同的值。
●共享檔案。當執行這個示例程式時,我們注意到父程式和子程式都把它們的輸出顯示在螢幕.上。原因是子程式繼承了父程式所有的開啟檔案。當父程式呼叫fork時,stdout檔案是開啟的,並指向螢幕。子程式繼承了這個檔案,因此它的輸出也是指向螢幕的。
如果你是第一次學習fork函式,畫程式圖通常會有所幫助,程式圖是刻畫程式語句的偏序的一種簡單的前趨圖。每個頂點a對應於一條程式語句的執行。有向邊a-→b表示語句a發生在語句b之前。邊上可以標記出一些資訊,例如一個變數的當前值。對應於printf語句的頂點可以標記上printf的輸出。每張圖從一個頂點開始,對應於呼叫main的父程式。這個頂點沒有人邊,並且只有一個出邊。每個程式的頂點序列結束於一個對應於exit呼叫的頂點。這個頂點只有一條人邊,沒有出邊。
例如,圖1展示了程式碼框1中示例程式的程式圖。初始時,父程式將變數x設定為1。父程式呼叫fork, 建立一個子程式,它在自己的私有地址空間中與父程式併發執行。對於執行在單處理器上的程式,對應程式圖中所有頂點的拓撲排序(topological sort)表 示程式中語句的一個可行的全序排列。下面是一個理解拓撲排序概念的簡單方法:給定程式圖中頂點的一個排列,把頂點序列從左到右寫成-行,然後畫出每條有向邊。排列是一個拓撲排序,當且僅當畫出的每條邊的方向都是從左往右的。因此,在程式碼框1的示例程式中,父程式和子程式的printf語句可以以任意先後順序執行,因為每種順序都對應於圖頂點的某種拓撲排序。
圖1 程式碼框1中示例程式的程式圖
程式圖特別有助於理解帶有巢狀fork呼叫的程式。例如,程式碼框3中的程式原始碼中兩次呼叫了fork。 對應的程式圖(圖2)可幫助我們看清這個程式執行了四個程式,每個都呼叫了一次printf,這些printf可以以任意順序執行。
int main()
{
Fork();
Fork();
printf ("hel1o\n");
exit(0);
}
程式碼框3
圖2 程式碼框2中巢狀fork的程式圖
2、例項
1、fork0
/*
* fork0 - The simplest fork example
* Call once, return twice
* Creates child that is identical to parent
* Returns 0 to child process
* Returns child PID to parent process
*/
void fork0()
{
if (fork() == 0) {
printf("Hello from child\n");
}
else {
printf("Hello from parent\n");
}
}
2、fork1
/*
* fork1 - Simple fork example
* Parent and child both run same code
* Child starts with identical private state
*/
void fork1()
{
int x = 1;
pid_t pid = fork();
if (pid == 0) {
printf("Child has x = %d\n", ++x);
}
else {
printf("Parent has x = %d\n", --x);
}
printf("Bye from process %d with x = %d\n", getpid(), x);
}
3、fork2
/*
* fork2 - Two consecutive forks
* Both parent and child can continue forking
* Ordering undetermined
*/
void fork2()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("Bye\n");
}
4、fork3
/*
* fork3 - Three consective forks
* Parent and child can continue forking
*/
void fork3()
{
printf("L0\n");
fork();
printf("L1\n");
fork();
printf("L2\n");
fork();
printf("Bye\n");
}
5、fork4
/*
* fork4 - Nested forks in parents
*/
void fork4()
{
printf("L0\n");
if (fork() != 0) {
printf("L1\n");
if (fork() != 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
6、fork5
/*
* fork5 - Nested forks in children
*/
void fork5()
{
printf("L0\n");
if (fork() == 0) {
printf("L1\n");
if (fork() == 0) {
printf("L2\n");
}
}
printf("Bye\n");
}
7、fork6
void cleanup(void) {
printf("Cleaning up\n");
}
/*
* fork6 - Exit system call terminates process
* call once, return never
*/
void fork6()
{
atexit(cleanup);
fork();
exit(0);
}
8、fork7
/*
* fork7 - Demonstration of zombies.
* Run in background and then perform ps
*/
void fork7()
{
if (fork() == 0) {
/* Child */
printf("Terminating Child, PID = %d\n", getpid());
exit(0);
} else {
printf("Running Parent, PID = %d\n", getpid());
while (1)
; /* Infinite loop */
}
}
子程式結束後,如果父程式一直不結束,會變為僵死程式,需要殺死它的父程式才能殺死僵死程式
9、fork8
/*
* fork8 - Demonstration of nonterminating child.
* Child still running even though parent terminated
* Must kill explicitly
*/
void fork8()
{
if (fork() == 0) {
/* Child */
printf("Running Child, PID = %d\n",
getpid());
while (1)
; /* Infinite loop */
} else {
printf("Terminating Parent, PID = %d\n",
getpid());
exit(0);
}
}
子程式沒有結束
10、fork9
/*
* fork9 - synchronizing with and reaping children (wait)
*/
void fork9()
{
int child_status;
if (fork() == 0) {
printf("HC: hello from child\n");
exit(0);
} else {
printf("HP: hello from parent\n");
wait(&child_status);
printf("CT: child has terminated\n");
}
printf("Bye\n");
}
11、fork10
#define N 5
/*
* fork10 - Synchronizing with multiple children (wait)
* Reaps children in arbitrary order
* WIFEXITED and WEXITSTATUS to get info about terminated children
*/
void fork10()
{
pid_t pid[N];
int i, child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
exit(100+i); /* Child */
}
for (i = 0; i < N; i++) { /* Parent */
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
這個函式是建立了N個子程式,然後父程式也執行五次等待,每次等待N個子程式中任意一個結束後列印一次,然後繼續迴圈
12、fork11
/*
* fork11 - Using waitpid to reap specific children
* Reaps children in reverse order
*/
void fork11()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0)
exit(100+i); /* Child */
for (i = N-1; i >= 0; i--) {
pid_t wpid = waitpid(pid[i], &child_status, 0);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminate abnormally\n", wpid);
}
}
這裡跟fork10基本一致,只是wait換成了waitpid,且waitpid指定了具體的pid號,即指定了具體的等待的子程式
13、fork12
/*********
* Signals
*********/
/*
* fork12 - Sending signals with the kill() function
*/
void fork12()
{
pid_t pid[N];
int i;
int child_status;
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}
這裡跟fork10差不多,只是父程式會利用kill函式向指定一個子程式號傳送終止訊號,被kill的子程式是異常退出的
14、fork13
/*
* int_handler - SIGINT handler
*/
void int_handler(int sig)
{
printf("Process %d received signal %d\n", getpid(), sig); /* Unsafe */
exit(0);
}
/*
* fork13 - Simple signal handler example
*/
void fork13()
{
pid_t pid[N];
int i;
int child_status;
signal(SIGINT, int_handler);
for (i = 0; i < N; i++)
if ((pid[i] = fork()) == 0) {
/* Child: Infinite Loop */
while(1)
;
}
for (i = 0; i < N; i++) {
printf("Killing process %d\n", pid[i]);
kill(pid[i], SIGINT);
}
for (i = 0; i < N; i++) {
pid_t wpid = wait(&child_status);
if (WIFEXITED(child_status))
printf("Child %d terminated with exit status %d\n",
wpid, WEXITSTATUS(child_status));
else
printf("Child %d terminated abnormally\n", wpid);
}
}
這裡仍然是利用kill函式向子程式發訊號,子程式被kill時,本來是異常終止,但是子程式的signal函式接收到了SIGINT訊號,呼叫handler函式處理,並正確的退出,因此子程式並不算是異常退出
15、fork14
/*
* child_handler - SIGCHLD handler that reaps one terminated child
*/
int ccount = 0;
void child_handler(int sig)
{
int child_status;
pid_t pid = wait(&child_status);
ccount--;
printf("Received SIGCHLD signal %d for process %d\n", sig, pid); /* Unsafe */
fflush(stdout); /* Unsafe */
}
/*
* fork14 - Signal funkiness: Pending signals are not queued
*/
void fork14()
{
pid_t pid[N];
int i;
ccount = N;
signal(SIGCHLD, child_handler);
for (i = 0; i < N; i++) {
if ((pid[i] = fork()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
}
while (ccount > 0)
;
}
子程式正常退出時,會向父程式傳送SIGCHLD訊號,signal函式接收到該訊號,呼叫handler函式處理,handler函式等待一個子程式結束,然後ccount會減一,父程式由於ccount = N,一開始會陷入死迴圈,直到子程式全部被回收為止,但是如果已有一個子程式先sleep結束向父程式傳送訊號,到父程式處理該訊號之前,所有的子程式傳送的SIGCHLD訊號都會被丟棄,因此這個函式的輸出是不確定的,並且如果子程式未全部結束,父程式會一直持續下去,此時掛起當前程式後使用ps命令檢視會看到已經結束的子程式會變成僵死程式
16、fork15
/*
- child_handler2 - SIGCHLD handler that reaps all terminated children
/
void child_handler2(int sig)
{
int child_status;
pid_t pid;
while ((pid = wait(&child_status)) > 0) {
ccount–;
printf(“Received signal %d from process %d\n”, sig, pid); / Unsafe /
fflush(stdout); / Unsafe */
}
}
/*
-
fork15 - Using a handler that reaps multiple children
*/
void fork15()
{
pid_t pid[N];
int i;
ccount = N;signal(SIGCHLD, child_handler2);
for (i = 0; i < N; i++)
if ((pid[i] = for
k()) == 0) {
sleep(1);
exit(0); /* Child: Exit */
}
while (ccount > 0) {
pause();
}
}
這裡跟14略有不同,父程式在迴圈時會pause,pause是掛起該程式等待一個訊號出現才繼續,同時,handler函式做了迴圈處理,當父程式接收到訊號後,雖然pause函式結束了,但是handler接管了執行權,handler的迴圈會等待五個子程式全部退出,因此能確保子程式全部被回收,且父程式也能正常退出
17、fork16
/*
* fork16 - Demonstration of using /bin/kill program
*/
void fork16()
{
if (fork() == 0) {
printf("Child1: pid=%d pgrp=%d\n",
getpid(), getpgrp());
if (fork() == 0)
printf("Child2: pid=%d pgrp=%d\n",
getpid(), getpgrp());
while(1);
}
}
子程式由哪個程式建立,就屬於哪個程式組,因此這三個程式都是一個組的
18、fork17
/*
* Demonstration of using ctrl-c and ctrl-z
*/
void fork17()
{
if (fork() == 0) {
printf("Child: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
else {
printf("Parent: pid=%d pgrp=%d\n",
getpid(), getpgrp());
}
while(1);
}
這個和fork16一模一樣,目的在於明白ctrl + z和ctrl + c的區別
相關文章
- 深入理解計算機系統學習- 計算機系統漫遊計算機
- 深入瞭解機器學習機器學習
- 深入理解計算機系統-學習筆記 (1)計算機筆記
- 作業系統---之fork()函式作業系統函式
- 深入瞭解Azure 機器學習的工作原理機器學習
- 深入理解計算機系統計算機
- 嵌入式系統要如何學習?帶你瞭解嵌入式系統學習方法
- 《深入理解計算機系統原理》學習筆記與習題答案(一)計算機筆記
- 深入理解計算機系統:程式計算機
- 深入瞭解下replace函式函式
- 瞭解計算機體系結構(4)計算機
- 面試官:說說你對Fork/Join的平行計算框架的瞭解?面試框架
- 深入理解計算機系統系列(第一章--計算機系統漫遊)計算機
- 深入瞭解PBKDF2:密碼學中的關鍵推導函式密碼學函式
- fork()與vfork()函式函式
- CnosDB:深入瞭解時序資料處理函式函式
- 瞭解 JavaScript 函數語言程式設計 - 宣告式函式JavaScript函數程式設計函式
- 【一】瞭解計算機的原理以及Python計算機Python
- 對函式的初步瞭解函式
- 全面瞭解Vue3的 ref 和相關函式和計算屬性Vue函式
- 理解 pcntl_fork 函式函式
- 研究linux函式 之 fork()Linux函式
- 學習PHP中統計擴充套件函式的使用PHP套件函式
- 計算機系統計算機
- 《深入理解計算機系統》實驗二 —— Bomb Lab計算機
- 《深入理解計算機系統》實驗三 —— Buf Lab計算機
- 基於函式計算快速搭建Zblog部落格系統函式
- 【多程式】Linux中fork()函式詳解|多程式Linux函式
- spark RDD的學習,filter函式的學習,split函式的學習SparkFilter函式
- 嵌入式系統開發學習如何起步、如何深入?
- ES6深入學習(二)關於函式函式
- 函式的學習函式
- 計算機系統結構--複習(Part 1)計算機
- MySQL函式大全(字串函式,數學函式,日期函式,系統級函式,聚合函式)MySql函式字串
- 計算機的作業系統計算機作業系統
- 一、你瞭解機器學習技術體系嗎機器學習
- [轉載] python數學計算模組之math常用函式學習使用Python函式
- 作業系統:計算機的生態系統作業系統計算機