歡迎來到Linux教程網
Linux教程網
Linux教程網
Linux教程網
您现在的位置: Linux教程網 >> UnixLinux >  >> Linux基礎 >> 關於Linux

linux系統編程之進程(二) fork函數相關總結

fork的作用是根據一個現有的進程復制出一個新進程,原來的進程稱為父進程(Parent Process),新進程稱為子進程(Child Process)。系統中同時運行著很多進程,這些進程都是從最初只有一個進程開始一個一個復制出來的。在Shell下輸入命令可以運行一個程序,是因為Shell進程在讀取用戶輸入的命令之後會調用fork復制出一個新的Shell進程,然後新的Shell進程調用exec執行新的程序。

我們知道一個程序可以多次加載到內存,成為同時運行的多個進程,例如可以同時開多個終端窗口運行/bin/bash,另一方面,一個進程在調用exec前後也可以分別執行兩個不同的程序,例如在Shell提示符下輸入命令ls,首先fork創建子進程,這時子進程仍在執行/bin/bash程序,然後子進程調用exec執行新的程序/bin/ls,如下圖所示。

一、fork系統調用

包含頭文件 <sys/types.h> 和 <unistd.h>

函數功能:創建一個子進程

函數原型

pid_t  fork(void);

參數:無參數。

返回值:

如果成功創建一個子進程,對於父進程來說返回子進程ID

如果成功創建一個子進程,對於子進程來說返回值為0

如果為-1表示創建失敗

(1)、使用fork函數得到的子進程從父進程的繼承了整個進程的地址空間,包括:進程上下文、進程堆棧、內存信息、打開的文件描述符、信號控制設置、進程優先級、進程組號、當前工作目錄、根目錄、資源限制、控制終端等。

(2)、子進程與父進程的區別在於:

1、父進程設置的鎖,子進程不繼承

2、各自的進程ID和父進程ID不同

3、子進程的未決告警被清除;

4、子進程的未決信號集設置為空集。

(3)、fork系統調用需要注意的地方

fork系統調用之後,父子進程將交替執行。

如果父進程先退出,子進程還沒退出那麼子進程的父進程將變為init進程。(注:任何一個進程都必須有父進程)

如果子進程先退出,父進程還沒退出,那麼子進程必須等到父進程捕獲到了子進程的退出狀態才真正結束,否則這個時候子進程就成為僵進程。

子進程退出會發送SIGCHLD信號給父進程,可以選擇忽略或使用信號處理函數接收處理就可以避免僵屍進程。

(4)、寫時復制 copy on write

如果多個進程要讀取它們自己的那部分資源的副本,那麼復制是不必要的。

每個進程只要保存一個指向這個資源的指針就可以了。

如果一個進程要修改自己的那份資源的“副本”,那麼就會復制那份資源。這就是寫時復制的含義

例如fork就是基於寫時復制,只讀代碼段是可以共享的。

若使用vfork()則在還沒調用exec之前,父子進程是共享同一個地址空間,不像fork()一樣會進行拷貝

(5)、fork之後父子進程共享文件

子進程繼承了父進程打開的文件描述符,故每個打開文件的引用計數為2。

(6)、fork與vfork

在fork還沒實現copy on write之前。Unix設計者很關心fork之後未立刻執行exec所造成的地址空間浪費,所以引入了vfork系統調用。

vfork有個限制,子進程必須立刻執行_exit或者exec函數。

即使fork實現了copy on write,效率也沒有vfork高,但是我們不推薦使用vfork,因為幾乎每一個vfork的實現,都或多或少存在一定的問題。

示例程序:

/*************************************************************************
> File Name: process_fork.c
> Author: Simba
> Mail: [email protected]
> Created Time: Sat 23 Feb 2013 02:34:02 PM CST
************************************************************************/
/* 如果父進程先退出,子進程還沒退出那麼子進程的父進程將變為init進程。(注:任何一個進程都必須有父進程)
* 如果子進程先退出,父進程還沒退出,那麼子進程必須等到父進程捕獲到了子進程的退出狀態才真正結束,
* 否則這個時候子進程就成為僵進程。
*/
#include<sys/types.h>
#include<sys/stat.h>
#include<unistd.h>
#include<fcntl.h>
#include<stdio.h>
#include<stdlib.h>
#include<errno.h>
#include<string.h>
#include<signal.h>
#define ERR_EXIT(m) \
do { \
perror(m); \
exit(EXIT_FAILURE); \
} while(0)
int main(int argc, char *argv[])
{
signal(SIGCHLD, SIG_IGN); // 避免產生僵屍進程,忽略SIGCHLD信號
printf("before fork pid=%d\n", getpid());
int fd;
fd = open("test.txt", O_WRONLY);
if (fd == -1)
ERR_EXIT("open error");
pid_t pid;
pid = fork(); // 寫時復制copy on write,只讀代碼段可以共享
/* 若使用vfork()則在還沒調用exec之前,父子進程是共享同一個地址空間,
* 不像fork()一樣會進行拷貝 */
if (pid == -1)
ERR_EXIT("fork error");
if (pid > 0)
{
printf("this is parent\n");
printf("parent pid=%d child pid=%d\n", getpid(), pid);
write(fd, "parent", 6); // 父子進程共享一個文件表
sleep(10);
}
else if (pid == 0)
{
printf("this is child\n");
printf("child pid=%d parent pid=%d\n", getpid(), getppid());
write(fd, "child", 5);
}
return 0;
}

測試輸出如下:

simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ ./process_fork

before fork pid=2572

this is parent

parent pid=2572 child pid=2573

this is child

child pid=2573 parent pid=2572

simba@ubuntu:~/Documents/code/linux_programming/APUE/process$ cat test.txt

parent

child

simba@ubuntu:~/Documents/code/linux_programming/APUE/process$

可以看到因為共享一個文件表,故文件偏移也共享,父子進程打印進test.txt文件的內容是緊隨的而不是從頭開始的。

Copyright © Linux教程網 All Rights Reserved