fork父子進程筆記

創建進程

#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <stdlib.h>

int main(int argc, char* argv[])
{
	pid_t id;
	
	id = fork();
	
	int i = 0;
	if(id < 0)
	{
		perror("fork");
		exit(1);
	}
    else if(id == 0)
	{
		for(i = 0; i < 10; i++)
		{
		  printf("i am child,my pid = %d   my parent pid = %d\n",getpid(),getppid());
		  sleep(1);
		}
		
	}
	else
	{
		for(i = 0; i < 10; i++)
		{
		  printf("i am parent, my pid = %d\n",getpid());
		  sleep(1);
		}

	}
}

子進程的pid爲3932   父進程的pid爲3931

 

孤兒進程

父進程中止後,子進程任然進行,此時子進程將被init進程收養。

 

進程的資源

 

fork函數時調用一次,返回兩次。在父進程和子進程中各調用一次。子進程中返回值爲0,父進程中返回值爲子進程的PID。程序員可以根據返回值的不同讓父進程和子進程執行不同的代碼。子進程是父進程的副本,獲得了父進程數據空間、堆和棧的副本;父子進程並不共享這些存儲空間,共享正文段(即代碼段);因此子進程對變量的所做的改變並不會影響父進程。一般來說,fork之後父、子進程執行順序是不確定的,這取決於內核調度算法。進程之間實現同步需要進行進程通信。
 

#include <time.h>
#include <pwd.h>
#include <grp.h>
#include <fcntl.h>
#include <sys/select.h>
#include <sys/time.h>

int g_iNum=10; //全局變量 靜態存儲區
int main()
{
    char *p = (char *)malloc(20); //堆 

    strcpy(p,"world");

    int stack_num = 100; //棧  局部變量
    pid_t pid;
    pid=fork();

    if(0==pid)
    {//這裏是子進程的代碼
        printf("----------------------------------------------------------------\n");
        printf("I am child process,g_iNum=%d\n",g_iNum);
        printf("I am child process,%s\n",p);
        printf("I am child process,stack_num =%d\n",stack_num);
        strcpy(p,"hello");
        g_iNum=5; 
        stack_num = 200;
        printf("----------------------------------------------------------------\n");
        printf("I am child process,g_iNum=%d,g_iNum_addr=%p\n",g_iNum,&g_iNum);
        printf("I am child process,%s,p_addr = %p\n",p,p);
        printf("I am child process,stack_num = %d,stack_num_addr = %p\n",stack_num,&stack_num);
    }else{
     //這裏是父進程的代碼
        printf("----------------------------------------------------------------\n");
        printf("I am parent process,g_iNum=%d\n,g_iNum_addr",g_iNum,&g_iNum);
        printf("I am parent process,%s,p_addr = %p\n",p,p);
        printf("I am parent process,stack_num = %d\n",stack_num);
        sleep(1);
        printf("----------------------------------------------------------------\n");
        printf("I am parent process,g_iNum=%d,g_iNum_addr=%p\n",g_iNum,&g_iNum);
        printf("I am parent process,%s,%p\n",p,p);
        printf("I am parent process stack_num = %d,stak_num_addr = %p\n",stack_num,&stack_num);
        return 0;
    }
}

可以看到,其值發生了改變,這也說明,父子進程的堆區數據、棧區數據、全局數據是不共享的。

但是所指向的虛擬地址是一樣的,也就是說不同的進程的變量卻有相同的虛擬地址(注:程序中變量的地址都是虛擬地址,而非物理地址)。原因是內核會爲每個進程分配4G的虛擬地址空間,這4G的虛擬地址空間地址分佈都是一樣的,由於子進程虛擬地址空間的數據都是從父進程中拷貝而來的,都是一樣的,因此相同的數據在4G的虛擬地址空間中的分佈也是一樣的。那爲什麼相同的虛擬地址(變量地址)最終會得到不同的變量值呢?原因是雖然虛擬地址分佈是一樣的,但是由於相同的虛擬地址映射到不同的物理地址,所以我們纔會得到不同的變量值。

 數據傳輸

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
int main(int argc, char* argv[])
{
	pid_t pid;
	
	int fd;
	
	char *info = "hello world";
	
	char buf[64] = {0};
	
	int nBytes;
	
	fd = open("file",O_RDWR|O_CREAT,0644);
	
	if(fd < 0)
	{
		perror("open file failed");
		exit(1);
	}
	printf("before fork\n");
	
	if((pid = fork()) < 0)
	{
		perror("fork error");
	}
    else if(pid == 0)
	{
		
		if((nBytes = write(fd,info,strlen(info)))<0)
		{
			perror("write failed");
		}
		exit(0);
	}
	else
	{
		sleep(1);
		
		lseek(fd,0,SEEK_SET);
		
		if((nBytes = read(fd,buf,64)) < 0)
		{
			perror("read failed");
		}
		printf("%s\n",buf);
		
	}
    exit(0);

}

 

父進程首先打開了一個文件file。當調用fork創建紫進程後,子進程繼承父進程中打開的文件,也就是說父、子進程共享該文件。

 

linux會爲進程打開三個文件,標準輸入文件、標準輸出文件、標準錯誤文件。

子進程會繼承父進程打開的文件

 

兩種典型的用法

一個父進程希望複製自己,使父,子進程同時執行不同的代碼段。這在網絡服務進程中常見--父進程等待委託者的服務請求。當這種請求到達時,父進程調用fork,使子進程處理此請求,父進程則繼續等待下一個服務請求。

一個進程要執行不同的程序。 這對Shell是常見的情況。在這種情況下,子進程在從fork返回後立即調用execve來執行新程序。

 

後記:

【1】進程的基礎
 1)進程與程序
    程序:保存在磁盤上的一組指令的有序集合。靜態的,沒有任何的執行的概念。
    進程:是程序動態運行的一次執行過程。動態的,具有一定的生命週期,包括創建,調度,消亡。
 2)進程由2部分組成
    用戶空間資源:數據段,代碼段,堆棧段,BSS段,進程資源,資源自動釋放
    內核空間資源:進程標識符,PCB(進程控制塊),進程的屬性信息,不會主動的釋放資源,要需要人爲主動回收。
 3)主要的進程標識
  PID 進程PID
  PPID 父進程PID
  注意:PID是一個唯一的,正整數。
 4)進程的執行過程
 創建:每啓動(運行)一個進程,內核就會爲當前的進程分配內存空間,保存變量,代碼等。
 調度:CPU的(優先級)調度,上下文的切換(用戶空間和內核空間的切換)。
 消亡:進程結束,需要回收資源,主動回收(調用相關的函數接口)。
 5)Linux中的進程包含三個段
 數據段
 代碼段
 堆棧段
 PCB(進程控制塊)
 6)進程的種類:
    交互進程:
    該類進程是由shell控制和運行的。交互進程既可以在前臺運行,也可以在後臺運行,可以與用戶直接交互,
    且受終端的控制。
    前臺進程-----------受終端的控制
    後臺進程------------不受終端控制
    注意:將前臺進程變成後臺進程 ./a.out &
    批處理進程:
    該是一個進程序列,負責按照順序啓動,該類進程不屬於某個終端,不與用戶進行交互,在後臺運行,稱之爲進程的集合。
    守護進程:
    長期運行在後臺做某些服務,一般在Linux啓動時開始運行,系統結束時停止運行。
    本身屬於後臺進程, 不受終端控制。自己做爲會話組組長。

【2】進程的相關命令
  top 動態查看進程的屬性信息(PR,NI優先級)
  ps 
  px ajx  查看進程的屬性信息
  ps aux  查看進程的屬性信息
  特殊:0號進程:內核進程
        1號進程:init進程
        每一個進程都有父進程,除了0號進程(理解用2叉樹理解)
  ps  axj 指令:
  ps aux | grep 可執行程序的名稱
  ps aux/-aux | grep a.out
        PPID       PID   PGID      SID   TTY      TPGID        STAT (***)    UID     TIME   COMMAND
        0          1      1        1     ?           -1         Ss       0     0:01    /sbin/init
        父親進程id 進程號 進程組id 會話id 終端     終端進程組id 狀態    用戶id   時間   進程命程
       
       int getdtablesize(void);   // 得到最大能打開文件描述符個數
       Here are the different values that the s, stat and state output specifiers (header "STAT" or "S") will
       display to describe the state of a process:
       D    uninterruptible sleep (usually IO)  // 不可中斷睡眠態
       R    running or runnable (on run queue)  // 運行態(正在運行,等待運行)
       S    interruptible sleep (waiting for an event to complete)  // 睡眠態
       T    stopped, either by a job control signal or because it is being traced.  // 停止態
       X    dead (should never be seen)  // 死亡態,瞬間發生,程序員不可見
       Z    defunct ("zombie") process, terminated but not reaped by its parent.  // 殭屍態

       For BSD formats and when the stat keyword is used, additional characters may be displayed:
       <    high-priority (not nice to other users)  // 高優先級進程
       N    low-priority (nice to other users)  // 低優先級進程
       L    has pages locked into memory (for real-time and custom IO) 
       s    is a session leader  // 會話組組長
       注意:進程---》進程組組長---》會話組組長
       l    is multi-threaded (using CLONE_THREAD, like NPTL pthreads do)  // 進程中包含了線程
       +    is in the foreground process group.  // 前臺進程
  kill
  1)kill -l  查看信號
       SIGINT       //默認屬性結束進程  ctrl+c
       SIGQUIT      //默認屬性結束進程  ctrl+ \
       SIGKILL      // 殺死進程
       SIGUSR1      // 用戶自定義的信號,默認結束進程
       SIGUSR2      // 用戶自定義的信號,默認結束進程
       SIGCHLD      // 兒子進程死亡,系統會給父親進程發送 SIGCHLD 信號
       SIGALRM      // 鬧鐘信號,默認結束進程
       SIGSTOP      // 停止信號, 進程進入暫停態  ctrl + z
       SIGTSTP      // 停止信號
       SIGCONT      // 恢復運行信號
  注意:SIGKILL,SIGSTOP不能被捕捉,忽略,屏蔽。
  2)kill 向指定的進程發送信號
  kill +信號的名稱(通過kill -l查看得到的信號)+PID 向執行的進程發送信號。
  注意:只有 kill -9 + pid 是具有殺死進程的功能,其他的數字沒有。 
  bg  將掛起的進程放到後臺執行(暫停進程)
  ./aout &  將普通執行的程序放到後臺執行
  fg  將後臺進程放到前臺執行
  注意:bg 1
        bg 2
        fg 1
        fg 2
        ...
  nice 在進程啓動前改變進程的優先級,並將進程運行起來
  sudo nice -n (優先級)(可正可負)+ ./a.out
  sudo nice -n -10 ./3
  renice 在進程運行過程中改變進程的優先級
  sudo renice -n +(優先級數)+pid
  sudo renice -n -15 pid
  ps -le
 
【3】進程的相關係統調用
    源進程與新創建進程的關係:子進程精確複製父進程的代碼,複製了代碼段,數據段,堆棧段,BSS段,文件
    描述符,緩衝區空間;空間各自獨立,PID,PPID,ino號,進程PCB控制塊沒有複製。
    1)進程的創建---fork
     #include <unistd.h>
        pid_t fork(void);
    函數的功能:創建新的子進程(在已有的進程的基礎上)
    參數:無
    返回值:pid < 0 出錯 沒有創建子進程
            pid =0 執行子進程
            pid >0 執行父進程
    注意:孩子進程從fork之後執行,因爲在fork之後纔出現父子進程。
          父子進程的執行順序不確定,誰先搶佔到CPU誰先執行。
          誰先執行並不代表誰先執行完。
          父子進程沒有分開執行
          父子進程分開執行
    2)getpid
     #include <sys/types.h>
     #include <unistd.h>
     pid_t getpid(void);
    函數的功能:獲取當前進程的PID
    參數:無
    返回值:當前進程的PID
    getppid
     pid_t getppid(void);
    函數的功能:獲取當前進程的父進程PID
    參數:無
    返回值:當前進程的父進程PID
    3)子進程對父進程緩衝區的處理
    結論:創建父子進程,子進程精確複製父進程,複製包括數據段,代碼段,BSS段,堆空間,棧空間,文件描述符(特例),     用戶空間的緩衝區,當子進程複製父進程的緩衝後,隨着子進程結束,會刷新緩衝區當中的數據,所以造成在fork之前的打印語句會執行2次。
    注意:子進程是在fork語句之後執行的。
     子進程對父進程打開的文件描述符的處理
     注意:雖然子進程複製用戶空間父進程的文件描述符,但是在內核空間對應的文件管理表項中的文件指針是共享的,====》共享偏移量
    4)殭屍進程和孤兒進程
      1>殭屍進程是如何產生?
      父進程活着,兒子進程死了,但是父進程並沒有爲兒子進程回收屍體,從而產生殭屍進程
      2>如何避免殭屍進程?
       1)形成孤兒進程---解決殭屍進程。
       2)父進程活着,兒子進程死了,讓父親進程主動調用回收函數去回收兒子進程的資源(wait,waitpid)
      3>孤兒進程是如何產生的?
      當父親進程優先於子進程結束,子進程被init(1號進程)收養,此時將這個進程稱之爲孤兒進程。
      注意:孤兒進程的產生時一個正常的現象。
    5)進程的退出
    exit----結束一個進程,會刷新緩衝區
    _exit-----結束一個進程,不會刷新緩衝區
    注意:如果程序正常結束也會刷新緩衝區

    exit/_exit與return的區別
    1)return 結束一個函數體,函數體結束,進程不一定結束
    2)exit/_exit結束一個進程,進程結束,函數體一定結束。
    6)wait/waitpid
    wait----回收任意一個子進程的資源


    #include <sys/types.h>
        #include <sys/wait.h>
    pid_t wait(int *status);
    函數的功能:阻塞等待回收子進程的資源
    參數:status 進程結束時的狀態信息
    返回值:成功返回回收子進程的pid
            失敗-1
    waitpid---自己指定回收某一個子進程的資源。
    pid_t waitpid(pid_t pid, int *status, int options);
    函數的功能:回收特定子進程的資源
    參數:pid 要回收子進的pid
          pid <-1 回收當前調用進程|pid|等於同組pid的任意子進程
          pid =-1 回收任意一個子進程的資源 ==wait
          pid =0 回收同組進程下的任意一個子進程
          pid >0 回收指定子進程的pid
          status 子進程結束時的狀態
          options   0   阻塞
                   WNOHANG 非阻塞       
    返回值:成功返回子進程pid 
            WNOHNAG 沒有子進程結束 0
            失敗-1;
    7)阻塞與非阻塞(銀行辦業務)
    阻塞:一直等待條件的發生,直到得到結果,保證了結果。
    非阻塞:等待條件的發生,但是不一定等待到結果,保證了時間。
    8)exec族函數---在子進程當中啓動新的程序
     execl
       #include <unistd.h>
       int execl(const char *path, const char *arg, ...);
       函數的功能:在子進程中運行新的程序代碼
       參數:path 要執行程序的路徑+名稱 
       注意:path必須是一個絕對路徑
     execlp
     int execlp(const char *file, const char *arg, ...);
       函數的功能:在子進程中運行新的程序代碼
       參數:path 要執行程序的路徑+名稱
       注意:文件的名稱可以不用寫絕對路徑,可以在當前的環境變量下去尋找。
     execle
     int execle(const char *path, const char *arg,
                  ..., char * const envp[]);
     函數的功能:在子進程中運行新的程序代碼
     注意:path必須是絕對路徑,最後要以一個新的指針數組結束。
     execv
     int execv(const char *path, char *const argv[]);
       函數的功能:在子進程中運行新的程序代碼
       注意:path是一個絕對路徑
             只有2個參數,沒有可變參數
             結尾以指針數組結束
       先定義一個指針數據,將要運行的命令寫入指針數組,然後將指針數組的名稱傳入
     system
     #include <stdlib.h>
     int system(const char *command);
    函數的功能:在當前運行的進程當中啓動新的程序
    參數:command 命令
    返回值:成功返回非負數
            失敗-1
    9)守護進程
        守護進程的定義:守護進程,也就是通常所說的Daemon進程,是Linux中的後臺服務進程。它是一個生存期較長的進程,
       通常獨立於控制終端並且週期性的執行某種任務或等待處理某些發生的事件
       守護進程常常在系統啓動時開始運行,在系統關閉時終止
       Linux系統有很多守護進程,大多數服務都是用守護進程實現的
        守護進程創建的步驟:
        1,創建子進程,父進程退出
           fork
        2,在子進程中創建新會話 ,並且成爲會話組組長
          setsid
        3,改變當前目錄爲根目錄
          chdir
        4,重設文件權限掩碼 
           umask---放開權限
        5,關閉文件描述符 
            getdtablesize
        6,做服務
            write
            read
        
          

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章