Linux多進程編程

来源:http://www.cnblogs.com/xiaojiang1025/archive/2016/10/06/5934317.html
-Advertisement-
Play Games

概述 多進程代碼區模型(其他區參見copy on write): getpid()、getppid() getuid()、geteuid() getgid(),getegid() fork() include include if(0==pid){ int res=execl("./proc","p ...


概述

多進程代碼區模型(其他區參見copy-on-write):

#include <sys/types.h> 
#include <unistd.h>
#include <stdlib.h>
#include <sys/wait.h>
getpid()/getuid()/getgid()                  //獲得PID/UID/GID
fork()/vfork()                              //創建子進程
exec() family                               //替代子進程
atexit()/on_exit()/exit()/_exit()/_Exit()   //退出子進程
wait()/waitpid()                            //獲得子進程退出狀態

getpid()、getppid()

//getpid() 返回調用進程的PID
//getppid() 返回調用進程的父進程的PID
pid_t getpid(void);         //pid_t是int
pid_t getppid(void); 

getuid()、geteuid()

getuid()返回調用進程的UID
geteuid()返回調用進程的effective UID
uid_t getuid(void);         //uid_t是unsigned int
uid_t geteuid(void); 

getgid(),getegid()

//getgid()返回調用進程的real GID
//getegid()返回調用進程的effective GID
id_t getgid(void);      //gid_t是unsigned int
gid_t getegid(void);        
printf("pid=%d\n",getpid());
    printf("ppid=%d\n",getppid());
    printf("uid=%d\n",getuid());
    printf("gid=%d\n",getgid()); 
}

fork()

//創建子進程,在父進程中返回子進程的PID,在子進程中返回0,失敗在父進程中返回-1
pid_t fork(void); 
#include<unistd.h>
#include<stdlib.h>
pid_t pid=fork();
if(-1==pid)
    perror(“fork”),exit(-1);
if(0==pid){
    //child process
    exit(0);
}

int main(){
    pid_t pid=fork();
    if(-1==pid)
        perror("fork"),exit(-1);
    printf("pid=%d\n",pid);
    if(0==pid){
        printf("I'm child,my PID:%d,my parent's PID:%d\n",getpid(),getppid());
        sleep(3);
        printf("I'm child,my PID:%d,my parent's PID:%d\n",getpid(),getppid());
    }else{
        sleep(1);
        printf("I'm parent,my PID:%d, my child's PID:%d\n",getpid(),pid);
    }
    return 0;
}
$./a.out 
pid=2915
pid=0
I'm child,    my PID:2915, my parent's PID:2914
I'm parent,my PID:2914, my child's   PID:2915
I'm child,  my PID:2915, my parent's PID:1  #一個Orphan
#卡在這, 因為兩個進程搶一個終端,不是死迴圈,直接[ENTER]就行
//創建任意多個進程:子進程幹活,父進程創建一個爹一堆兒子
int i=0;
for(i=0;i<10;i++){  //創建10個進程, 只有parent在執行for()因為child在每次迴圈體內就exit()了
    pid_t pid=fork();
    if(-1==pid)
        perror("fork"),exit(-1);
    if(0==pid){
        …
        exit(0);    //終止子進程, 自然也就跳出了迴圈,防止再fork()
    }
}

父子進程代碼區執行次序

fork()產生的所有進程共用代碼區,copy-on-write其他區)

  • fork()之前的代碼, 由parent執行一次
  • fork()之後的代碼, 由父子進程各執行一次
  • fork()的返回值由父子進程各自返回一次

copy-on-write模型:

fork()一下乾的幾件事:

  • 給P2分配Text段, Data段, Heap段, Stack段的虛擬地址,都指向P1中相應的物理地址
  • P2的Text段是鐵定和P1共用同一個物理地址了, 剩下的Data,Heap,Stack待定
  • 如果one of them 改變了這三個段的內容, 就把原來的數據複製一份給P2, 這樣P2就有了相應的新的物理地址

vfork()

//創建子進程,父進程會等待子進程退出之後在繼續執行,還是順序執行, 在子進程執行期間,父進程被掛起,此期間子進程和父進程共用所有的記憶體資源,vfork()多用在在不拷貝餓哦父進程頁表的情況下創建新的進程,單獨使用沒有多線程的價值, 主要和exec()搭配使用。
//子進程終止時不能從當前函數返回/調用exit函數, 可以調用_exit(), 該函數保證了子進程先於父進程執行
pid_t vfork(void);
int main(){
    pid_t pid=vfork();
    if(-1==pid)
        perror("vfork"),exit(-1);
    if(0==pid){
        printf("child %d starts\n",getpid());
        sleep(2);
        //跳轉出去, 調用execl()
        int res=execl("./proc","proc",NULL);    
        //"ls"表示執行方式, 以字元串的形式傳進來
        if(-1==res)
            perror("execl"),_exit(-1);//ATTENTION,用_exit()
    }
    printf("parent starts\n");
    printf("parent ends\n");
    return 0;
}
//execl()可以跳出當前進程(VS fork()), 去執行一個完全不同的文件,可以幫助vfork()實現多進程,
//父進程結束了,系統就會顯示[~/Desktop/160512/Code]$,此時發現從已經終結的子進程跳轉出的的文件還沒執行完, 再列印ls -l的內容
$./a.out 
child 4258 starts
parent starts
parent ends
[~/Desktop/160512/Code]$total 20
-rw-rw-r-- 1 tarena tarena  754  5月 12 11:03 01waitpid.c
-rw-rw-r-- 1 tarena tarena  449  5月 12 10:31 02vfork.c
-rw-rw-r-- 1 tarena tarena  489  5月 12 11:28 03execl.c
-rwxrwxr-x 1 tarena tarena 7499  5月 12 11:28 a.out
*/

exec()

用一個新的進程影像替代當前的進程映像,失敗返回-1設errno
extern char **environ;
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[]);
int execvpe(const char *file, char *const argv[], char *const envp[]);

ATTENTION: vfork()主要與exec family搭配使用, 主要用語子進程執行與父進程完全不同代碼段的場合中, 其中vfork()專門用於創建進程, exec family 專門用於跳轉執行
, fork()雖然也可以和exec family 搭配使用, 但是fork()會複製父進程的記憶體空間, 複製完了又跳出去, 沒什麼意義, 效率不如(vfork(), exec family)

#include<unistd.h>
#include<sys/types.h>
if(0==pid){
        int res=execl("./proc","proc",NULL);    //"ls"就是執行方式, 以字元串的形式傳進來, “proc”也是
        if(-1==res)
            perror("execl"),_exit(-1);      //ATTENTION,vfork用_exit()
}

7種進程終止

  • 正常終止:
    1. 從 main() 返回
    2. 調用 exit() / _exit() / _Exit()
    3. 最後一個線程從其啟動常式返回
    4. 最後一個線程調用pthread_exit()
  • 異常終止:
    1. 調用abort()
    2. 接到一個信號並終止
    3. 最後一個線程對取消請求作出響應

exit()

//引起進程的正常終止,所謂正常終止是按照註冊的反順序依次調用atexit()和on_exit()里註冊的函數。VS _exit()和_Exit()會立即終止進程
//進程終止後會傳遞退出碼給父進程,這個"退出碼&0377"可以被父進程的wait()系列函數捕獲並解析。
//系統使用8位二進位表示進程退出號,就是0~255,這也是為什麼exit()返回status&0377給父進程的原因, 其實是取低八位二進位. 如果exit(10000),實際返回的就是16. 
void exit(int status);

atexit()

//註冊一個正常終止進程時執行的函數,這個函數的參數必須是void,註冊成功返回0,失敗返回非0
int atexit(void (*function)(void)); //參數是函數指針

on_exit()

//和atexit()類似,用於註冊exit()時執行的函數, 不同之處是on_exit註冊的函數可以帶參數,這個function的兩個形參分別是通過exit()傳入的int型 和 通過on_exit()傳入的*arg
//同一個函數可以被多次註冊,註冊一次退出進程時就會被執行一次
//fork()出的子進程會繼承父進程的註冊函數,但一旦調用了exec(),所有註冊的函數都會被移除
//成功返回0,失敗返回非0

int on_exit(void (*function)(int , void *), void *arg);
#include<stdlib.h>
void fa(int status,void* pv){
    printf("obtained status=%d\n",status);
    free(pv);
    pv=NULL;
}
int main(){
    pid_t pid=fork();
    if(0==pid){
        …
        int *pi=(int*)malloc(sizeof(int));  //沒有錯誤處理
        if(0!=on_exit(fa,pi))
            perror("on_exit"),exit(-1);
        …
        exit(100); //terminate child and free dynamic memory automatically
    }
}

_exit()/_Exit():

//立即終止調用的進程,所有的子進程都會掛到PID1下,父進程會收到SIGCHLD信號,還可以用wait()接收退出碼

void _exit(int status);     //<unistd.h>
void _Exit(int status);     //<stdlib.h>
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
void fa(){
    printf("所有需要善後處理的工作都可以交給我哦!\n");
}
int main(){
    int res=atexit(fa);
    if(0!=res)
        perror("atexit"),exit(-1);
    printf("main start\n");
    _exit(0);       //立即終止,如果這句話留著fa()不會執行
    printf("main end\n");
    return 0;       //把_exit()註釋掉就會執行fa()
}

Orphan VS Zombie

Orphan Process:一個parent退出,而它的一個或多個child還在運行,那麼這些child將成為orphan。將被init(PID==1)收養,並由init對它們完成狀態收集工作。init會迴圈地wait()直到這些child完成了他們的工作. 即當一個孤兒進程凄涼地結束了其生命周期的時候,init進程就會代表黨和政府出面處理它的一切善後工作。因此孤兒進程並不會有什麼危害。

Zombie Process: 一個使用fork()創建的child,如果child退出,而parent並沒有調用wait/waitpid獲取child的狀態信息,那麼child的process descriptor和PID仍然保存在系統中。此時的child就變成了zombie。因為系統的PID總數是有限的, parent不斷的創建child而不去wait,系統早晚會被拖垮.

總結:

  • Orphan/Zombie都是因為在parent中沒有wait掉child, 不同之處是orphan的parent已經沒了, 由init來接管了,而zombie有個缺德的parent, 不wait還不撒手,拖累了系統
  • $ps 一下Zombie的進程狀態是’Z’

wait(), waitpid(), waitid()

//wait for process to change state
//wait()掛起父進程,直到一個子進程結束
//waitpid()掛起父進程,直到指定的子進程終止
//wait()相當於waitpid(-1, &status, 0)
//成功返回子進程的PID,失敗返回-1設errno

pid_t wait(int *status);
pid_t waitpid(pid_t pid, int *status, int options);

/* pid in waitpid() and kill()
                                                    
pid>0   //指定pid
pid=0   //GID是調用進程PID的子進程
pid=-1  //任何子進程
pid<-1  //GID是PID的子進程
*/
/*options(Bitwaise Or) 
WNOHANG         //如果沒有子進程終止就立即返回return immediately if no child has exited.
WUNTRACED       //如果一個子進程stoped且沒有被traced,那麼立即返回
WCONTINUED (since Linux 2.6.10) //如果stoped的子進程通過SIGCONT複蘇,那麼立即返回 
*/
/*
如果退出不是NULL,wait()會使用形參指針帶出退出碼,這個退出碼可以使用下列巨集解讀
WIFEXITED(status)       //如果子進程正常退出返回真
WEXITSTATUS(status)     //返回子進程的退出碼,當且僅當WIFEXITED為真時有效
WIFSIGNALED(status)     //如果子進程被一個信號終止時返回真
WTERMSIG(status)        //返回終止子進程的信號編號,當且僅當WIFSIGNALED為真時有效
WCOREDUMP(status)       //如果子進程導致了"核心已轉儲"則返回真,當且僅當WIFSIGNALED為真時有效r
WIFSTOPPED(status)      //如果子進程被一個信號暫停時返回真,當且僅當調用進程使用WUNTRACED或子進程正在traced時有效
WSTOPSIG(status)        //返回引起子進程暫停的信號編號,當且僅當WIFSTOPPED為真時有效
WIFCONTINUED(status)(since Linux 2.6.10)//如果子進程收到SIGCONT而複蘇時返回真
*/
if(0==pid){
    …
    exit(100);      //把child的退出狀態信息設為100
}
int status=0;
int res=wait(&status);  //status用來接收結果
if(-1==res)
    perror("wait"),exit(-1);
if(WIFEXITED(status))               //ATTENTION:這個巨集要int不是int*,和wait不一樣
    printf("child%d end normally, status is:%d\n",res,WEXITSTATUS(status)); //將列印出exit()里的狀態

例子

/*------
file.c
------*/
#include<unistd.h>
#include<fcntl.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
int main(){
    int fd=open("a.txt",O_RDWR|O_TRUNC|O_CREAT,0664);
    pid_t pid=fork();
    if(-1==pid)
        perror("fork"),exit(-1);
    if(0==pid){
        printf("child:%d start\n",getpid());
//      char buf[]="world";     //這個變數沒必要,常量就行
        int res=write(fd,"hello",sizeof("hello"));
        if(-1==res)
            perror("child write"),exit(-1);
        res=close(fd);          //child把parent的數據複製過來,所以fd需要關閉
        if(-1==res)
            perror("child close"),exit(-1);
        printf("parent:%d end\n",getpid());
        exit(0);
    }
    printf("parent:%d start\n",getpid());
    sleep(1);
    int res=write(fd,"world",sizeof("world"));
    if(-1==res)
        perror("parent write"),exit(-1);
    res=close(fd);
    if(-1==res)
        perror("parent close"),exit(-1);
    printf("parent:%d end\n",getpid());
    return 0;
}

Note:

  • 運行結果a.txt兩個進程沒有覆蓋=>父子進程使用的讀寫位置信息是同一份=>文件表是同一份=>但是兩個是不同的fd, 所以fork()創建子進程也會複製一個文件描述符總表
  • 正是因為使用讀寫一次 offset會向後移, 所以沒有覆蓋, 因為後來的是使用前面留下的offset的位置, 所以使用的讀寫信息是一樣的
/*--------------------------------------------
child終止時自動釋放malloc()
----------------------------------------------*/
#include<unistd.h>
#include<sys/types.h>
#include<stdio.h>
#include<stdlib.h>
#define PI 3.141592657
int *ipdata=NULL;
///*
void fa(){
    free(ipdata);
    ipdata=NULL;
}
//*/
int main(){
    pid_t pid=fork();
    if(-1==pid)
        perror("fork"),exit(-1);
    if(0==pid){
        printf("1");
        printf("child starts\n");
        ipdata=(int*)malloc(sizeof(int));
        if(NULL==ipdata)
            printf("malloc fails\n");
//          perror("malloc"),exit(-1);  //???
//      *ipdata=data;
        int res=atexit(fa);
        if(0!=res){
            printf("atexit fails\n");
            exit(EXIT_FAILURE);
        }

        printf("Please input a radius:");
        scanf("%d",ipdata);
        printf("primeter is:%lf\n",2*PI*(*ipdata));
        exit(EXIT_SUCCESS);
    }
    printf("parent starts\n");
    int stat=0;
    pid=wait(&stat);
    if(WIFEXITED(stat))
        printf("child has terminated,its status:%d\n",WEXITSTATUS(stat));
    return 0;
}

Note:

  • 用全局變數做橋梁
  • atexit()裡面的函數一定是int *(void)函數的形參列表變了也不行
  • ATTENTION: vfork()的child雖然整個記憶體區都是和parent共用的, 但是變數的作用域還是在啊, 所以你跨函數使用變數肯定要傳參的啊
/*--------------------------------------------
on_exit.c, child終止時自動釋放malloc()
----------------------------------------------*/
#include<unistd.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<sys/wait.h>
#include<stdio.h>
#include<stdlib.h>
#include<signal.h>
void fa(int status,void* pv){
    printf("obtained status=%d\n",status);
    free(pv);
    pv=NULL;
}

int main(){
    //創建子進程,使用fork()
    pid_t pid=fork();
    if(-1==pid){
        printf("parent starts%d\n",pid);
    }
    if(0==pid){
        printf("child starts%d\n",getpid());
        int *pi=(int*)malloc(sizeof(int));
        if(NULL==pi)
            printf("malloc error"),exit(-1);
        //use on_exit()register function
        if(0!=on_exit(fa,pi))
            perror("on_exit"),exit(-1);
        printf("please input a radius(int)\n");
        scanf("%d",pi);
        printf("the primeter is:%lf\n",2*3.14*(*pi));
        //terminate child and free dynamic memory automatically
        exit(100);
    }

    //父進程等待子進程終止, 獲取退出狀態
    int status=0;
    int res=waitpid(pid,&status,0);
    if(-1==res)
        perror("waitpid"),exit(-1);
    if(WIFEXITED(status))
        printf("status of child:%d\n",WEXITSTATUS(status));
    return 0;
}

您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 1.代碼 input_subsys.drv.c 在linux輸入子系統(input subsystem)之按鍵輸入和LED控制的基礎上有小改動,input_subsys_test.c不變。 input_subsys.drv.c 2. input_subsys_drv.c, input.c, evde ...
  • 通信模型: 1. 獲取key值 :ftok() 2. 創建/獲取共用記憶體 :shmget() 3. 掛接共用記憶體 :shmat() 4. 脫接共用記憶體 :shmdt() 5. 刪除共用記憶體 :shmctl() 使用的頭文件 ftok() pathname :文件名 proj_id : 1~255的一 ...
  • ipcs ipcs m 查看系統中已經存在的共用記憶體 shmid :共用記憶體的id perms :permission nattch :number attatch ipcs q 查看系統中現有的消息隊列 used byte: 隊列的大小 message : 隊列中消息的條數 ipcs s 查看系統 ...
  • 管道是Linux的十種文件類型之一,使用管道通信本質上還是以文件作為通信的媒介 有名管道+無名管道=管道 有名管道(FIFO文件):就是 有文件名的管道, 可以用於任意兩個進程間的通信 無名管道(pipe文件):就是沒有文件名的管道, 只能用於父子進程之間的通信 mkfifo 創建有名管道,管道不能 ...
  • Linux中, 系統為每個系統都維護了三種計時器,分別為: 真實計數器, 虛擬計時器以及實用計時器, 一般情況下都使用真實計時器 getitimer()/setitimer() which //具體的計時器類型 1. ITIMER_REAL :真實計時器 統計進程消耗的真實時間 通過定時產生SIGA ...
  • 信號本質上就是一個軟體中斷,它既可以作為兩個進程間的通信的方式, 更重要的是, 信號可以終止一個正常程式的執行, 通常被用於處理意外情況 , 信號是非同步的, 也就是進程並不知道信號何時會到達 $kill 9 3390 向PID為3390的進程發送編號為9的信號= 一個兩個進程間通信的方式之一 一共6 ...
  • 環境:虛擬機VMware10 首先瞭解幾個註意的地方: 一、分區類型: 1、主分區:最多只能有四個; 2、擴展分區:最多只能有一個,且主分區加上擴展分區最多只能有四個,擴展分區不能寫入數據,只能包含邏輯分區 3、邏輯分區:可以寫入數據和格式化 舉個例子如圖: 其中1、2、3為主分區,4為擴展分區,5 ...
  • 向一個/一些進程發送一個信號 $kill [ slL] [...] 指定發送的信號,可以使用名稱或者信號編號 列出當前系統的所有信號 ...
一周排行
    -Advertisement-
    Play Games
  • 示例項目結構 在 Visual Studio 中創建一個 WinForms 應用程式後,項目結構如下所示: MyWinFormsApp/ │ ├───Properties/ │ └───Settings.settings │ ├───bin/ │ ├───Debug/ │ └───Release/ ...
  • [STAThread] 特性用於需要與 COM 組件交互的應用程式,尤其是依賴單線程模型(如 Windows Forms 應用程式)的組件。在 STA 模式下,線程擁有自己的消息迴圈,這對於處理用戶界面和某些 COM 組件是必要的。 [STAThread] static void Main(stri ...
  • 在WinForm中使用全局異常捕獲處理 在WinForm應用程式中,全局異常捕獲是確保程式穩定性的關鍵。通過在Program類的Main方法中設置全局異常處理,可以有效地捕獲並處理未預見的異常,從而避免程式崩潰。 註冊全局異常事件 [STAThread] static void Main() { / ...
  • 前言 給大家推薦一款開源的 Winform 控制項庫,可以幫助我們開發更加美觀、漂亮的 WinForm 界面。 項目介紹 SunnyUI.NET 是一個基於 .NET Framework 4.0+、.NET 6、.NET 7 和 .NET 8 的 WinForm 開源控制項庫,同時也提供了工具類庫、擴展 ...
  • 說明 該文章是屬於OverallAuth2.0系列文章,每周更新一篇該系列文章(從0到1完成系統開發)。 該系統文章,我會儘量說的非常詳細,做到不管新手、老手都能看懂。 說明:OverallAuth2.0 是一個簡單、易懂、功能強大的許可權+可視化流程管理系統。 有興趣的朋友,請關註我吧(*^▽^*) ...
  • 一、下載安裝 1.下載git 必須先下載並安裝git,再TortoiseGit下載安裝 git安裝參考教程:https://blog.csdn.net/mukes/article/details/115693833 2.TortoiseGit下載與安裝 TortoiseGit,Git客戶端,32/6 ...
  • 前言 在項目開發過程中,理解數據結構和演算法如同掌握蓋房子的秘訣。演算法不僅能幫助我們編寫高效、優質的代碼,還能解決項目中遇到的各種難題。 給大家推薦一個支持C#的開源免費、新手友好的數據結構與演算法入門教程:Hello演算法。 項目介紹 《Hello Algo》是一本開源免費、新手友好的數據結構與演算法入門 ...
  • 1.生成單個Proto.bat內容 @rem Copyright 2016, Google Inc. @rem All rights reserved. @rem @rem Redistribution and use in source and binary forms, with or with ...
  • 一:背景 1. 講故事 前段時間有位朋友找到我,說他的窗體程式在客戶這邊出現了卡死,讓我幫忙看下怎麼回事?dump也生成了,既然有dump了那就上 windbg 分析吧。 二:WinDbg 分析 1. 為什麼會卡死 窗體程式的卡死,入口門檻很低,後續往下分析就不一定了,不管怎麼說先用 !clrsta ...
  • 前言 人工智慧時代,人臉識別技術已成為安全驗證、身份識別和用戶交互的關鍵工具。 給大家推薦一款.NET 開源提供了強大的人臉識別 API,工具不僅易於集成,還具備高效處理能力。 本文將介紹一款如何利用這些API,為我們的項目添加智能識別的亮點。 項目介紹 GitHub 上擁有 1.2k 星標的 C# ...