• 深入浅出--UNIX多进程编程之fork()函数


    0前言

    上周都在看都在学习unix环境高级编程的第八章——进程控制。也就是这一章中。让我理解了unix中一些进程的原理。以下我就主要依照进程中最重要的三个函数来进行解说。让大家通过阅读这一篇文章彻底明确进程这点事。希望对大家有所帮助。

    1进程环境

             在学习进程之前。一定要了解一下unix的进程环境。系统怎样对进程终止,和一个程序启动终止,程序运行的原理等,这些都有助于你理解进程的运行原理。这些内容都在我的上一篇文章中,请关注:http://blog.csdn.net/wallwind/article/details/6968323。文章中讲的较为具体。

    2进程概念:

    一个进程。主要包含三个元素:

    a)        一个能够运行的程序。

    b)        和该进程相关联的全部数据(包含变量,内存空间。缓冲区等等);

    c)        程序的运行上下文(execution context)。

    最好还是简单理解为,一个进程表示的,就是一个可运行程序的一次运行过程中的一个状态。操作系统对进程的管理。典型的情况,是通过进程表完毕的。进程表中的每个表项,记录的是当前操作系统中一个进程的情况。对于单 CPU的情况而言,每一特定时刻仅仅有一个进程占用 CPU。可是系统中可能同一时候存在多个活动的(等待运行或继续运行的)进程。

    3 fork()函数

             fork()函数是进程的核心函数。

    当调用fork的时候,系统将创建一个新的进程,成为子进程(child process)。Fork函数定义形式例如以下:

    1. #include<unistd.h>  
    2.   
    3. Pid_t fork(void);//返回值:子进程返回0。父进程中返回子进程ID,出错则返回-1  


    从返回值我们能够看到,Fork函数调用了一次,可是返回两次。其差别在于在子进程中返回值是0,而父进程的返回值则是新子进程的进程ID.

             上边的概念可能对刚開始学习的人比較模糊。那么我们怎么理解fork呢?

             当你看到fork的时候。你能够把fork理解成“分叉”。在分叉的同一时候,生成的一个子进程复制了父进程的基本是全部的东西,包含代码、数据和分配给进程的资源。也就是子进程差点儿是和父进程是一模一样的。可是子进程可能会依据不同情况调用其它函数。

    比方exec函数。

             以下我们用一个经典及比較典型简单的样例来看看。

        

    1.     #include"apue.h"  
    2.   
    3.   
    4. int glob=6;  
    5.   
    6. char buf[]="a write to stdout ";  
    7.   
    8.    
    9.   
    10. int main(void)  
    11.   
    12. {  
    13.   
    14.        int var=88;  
    15.   
    16.        pid_t pid;  
    17.   
    18.    
    19.   
    20.        if(write(STDOUT_FILENO,buf,sizeof(buf)-1)!=sizeof(buf)-1)  
    21.   
    22.                 printf("writeerror");  
    23.   
    24.    
    25.   
    26.        printf("before fork with  换行符 ");  
    27.   
    28.        printf("before fork without换行符");     
    29.   
    30.        //printf(" ");  
    31.   
    32.    
    33.   
    34.        if((pid=fork())<0){  
    35.   
    36.                 printf("fork error");  
    37.   
    38.    
    39.   
    40.        }else if(pid==0){  
    41.   
    42.                 printf("I am is  child process,pid=%d ",getpid());  
    43.   
    44.                 printf("my parentprocess's pid=%d ",getppid());  
    45.   
    46.                 glob++;  
    47.   
    48.                 var++;  
    49.   
    50.        }else{  
    51.   
    52.                 printf("this is parentprocess,pid=%d ",getpid());  
    53.   
    54.                 //var++;  
    55.   
    56.        }  
    57.   
    58.    
    59.   
    60.        printf("pid=%d,glob=%d,var=%d ",getpid(),glob,var);  
    61.   
    62.        exit(0);  
    63.   
    64. }  


    输出结果:                             

    希望新手能够将以上代码自己敲一遍,然后自己运行一遍。

    从上边的结果我们一步步分析。

    首先我们看到两行文字是仅仅输出一遍的。

    既”a write to stdout”

             “Befork forkwhit h换行符”。

    也就是仅仅有一个进程运行这两个输出语句。我们先暂且不分析不带换行符的。

    当程序调用fork的时候。这个时候程序就出现了一个进程。也就是两个进程在运行。

    请看下边的简易图形

    在上边我们已经讲过fork函数会由于不同调用返回不同的函数值:

    1)在父进程中,fork返回新创建子进程的进程ID;

    2)在子进程中。fork返回0;

    3)假设出现错误,fork返回一个负值;

    如今我们看程序里的几个函数。

    Write函数是将buf内容写到标准流中。此处仅仅作为输出用。

    我们关心的是getpid()函数和getppid()函数。

    getpid()函数是用来输出当前成进程的ID,getppid()则得到当前进程的父进程的ID,从输出结果比对能够看到。子进程得到的父ID和父进程得到自己的ID是相同的。

    再来看一下全局变量glob。glob是函数外的变量。子进程将glob++处理。这个时候输出的是7,父进程没有进行处理直接输出6。我们能够知道,这两个glob变量显示是两份的。不相干的。

    局部变量var也是相同的结果。。。

    如今我们来解释一下为什么

    1. printf("before fork with  换行符 ");  
    2.   
    3. printf("before fork without换行符");     


    上一句仅仅输出了一次。而下边这句话就输出了两次。

    我们能够发现printf("before fork with  换行符 ");这句带有 既换行符的意思。而

    1. printf("before fork without换行符");     


    是没有带的。

    这个我们要知道printf函数是有缓冲机制的,相似于我们使用的write函数。但我们将想要的东西输出的时候。系统仅仅是把内容放到stdout标准输出的缓冲队列的。

    当遇到“ ”的时候,系统就把缓冲里的东西给清掉,输出到屏幕上。

            

    1. printf("beforefork with  换行符 ");  


    运行后,缓冲里没有了数据。自然子进程再次运行的时候没有内容可输出了。可是printf("before fork without换行符");的时候,子进程也会把stdout的内容再次输出来。也就是导致了内容出处了两边。假设换一下书序的,结果是不一样的哦。

    3 fork难度进阶

    csdn看到一篇比較好的解说fork的文章。

    较为深入的解说。

    以下我就经过自己的调试和理解将呈现给大家。首先看一段代码:

    1. #include "apue.h"  
    2.   
    3.    
    4.   
    5. int main(void)  
    6.   
    7. {  
    8.   
    9.        int i=0;  
    10.   
    11.        pid_t pid;  
    12.   
    13.    
    14.   
    15.        for(i=0;i<2;i++)  
    16.   
    17.        {  
    18.   
    19.                 if((pid=fork())<0){  
    20.   
    21.                         printf("forkerror ");  
    22.   
    23.                 }else if(pid==0){  
    24.   
    25.                        printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d ",i,getpid(),getppid(),pid);  
    26.   
    27.                }else{  
    28.   
    29.                        printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d ",i,getpid(),getppid(),pid);  
    30.   
    31.                         sleep(2);//inorder tochild output first  
    32.   
    33.                 }  
    34.   
    35.        }  
    36.   
    37.        exit(0);  
    38.   
    39. }  


    输出的结果为:

    好好的分析一下我们的结果。0開始的输出为2行,1开头的输出4行。

    然后我们開始观察PID的结果。从pid的结果我们找到一个关系

    既9278-》30361-》30362-》30363

    9278-》30361-》30364

    要知道。fork函数子进程调用的时候返回0,而父进程调用的时候则返回子进程的ID。

    程序运行第一步:

    1. if((pid=fork())<0){  
    2.   
    3.                         printf("forkerror ");  
    4.   
    5.                 }else if(pid==0){  
    6.   
    7.                        printf("%d,childself's pid=%d,parent's pid=%d,returnid=%d ",i,getpid(),getppid(),pid);  
    8.   
    9.                 }else{  
    10.   
    11.                        printf("%d,parentself's pid=%d,parent's father's pid=%d,returnid=%d ",i,getpid(),getppid(),pid);  
    12.   
    13.                         sleep(2);//inorder tochild output first  
    14.   
    15.                 }  


    输出的结果为:

    这是第一次循环输出输出的。

    Childpid和return 均为父ID为30361生成的30361。

    此时已经有了两个进程了。

    进程ID分别为30362和30361。

    第二次循环输出。

    我们知道了此时两个进程30362和30361。

    这两个进程分别运行自己的代码。

    ü  这个时候pid为30362的进程进行一次:

    循环。得到结果为:

         
    生成了一个子进程30363

    ü  30361进程再次运行自己的代码,也就是上述循环的部分。

    生成了一个子进程30364.

    从程序和输出结果能够看到,事实上我们的程序得到了6份的拷贝。

    通过这个样例,fork了三次,产生了三个子进程,输出了6次。你能够深刻的理解了fork工作原理了。

    再来一个样例:

    这个是典型的循环样例:

    1. #include <unistd.h>  
    2.   
    3. #include<stdio.h>  
    4.   
    5.    
    6.   
    7. int main(void)  
    8.   
    9. {  
    10.   
    11.        pid_t pid;  
    12.   
    13.        int i=0;  
    14.   
    15.        int c_cout=0;  
    16.   
    17.        int p_cout=0;  
    18.   
    19.        for(i=0;i<5;i++)  
    20.   
    21.        {  
    22.   
    23.                 if((pid=fork())<0){  
    24.   
    25.                         printf("forkerror ");  
    26.   
    27.                 }else if(pid==0){  
    28.   
    29.                         c_cout++;  
    30.   
    31.                }else{  
    32.   
    33.                         p_cout++;  
    34.   
    35.                 }  
    36.   
    37.    
    38.   
    39.        }  
    40.   
    41.        printf("c_cout=%d,p_cout=%d,pid=%d ",c_cout,p_cout,getpid());  
    42.   
    43.    
    44.   
    45. }  


    输出结果为:c_cout=5,p_cout=0,pid=1559

    c_cout=4,p_cout=1,pid=1558

    c_cout=4,p_cout=1,pid=1560

    c_cout=3,p_cout=2,pid=1557

    c_cout=4,p_cout=1,pid=1562

    c_cout=3,p_cout=2,pid=1561

    c_cout=4,p_cout=1,pid=1566

    c_cout=3,p_cout=2,pid=1565

    c_cout=3,p_cout=2,pid=1569

    c_cout=2,p_cout=3,pid=1568

    c_cout=4,p_cout=1,pid=1574

    c_cout=3,p_cout=2,pid=1573

    c_cout=3,p_cout=2,pid=1577

    c_cout=2,p_cout=3,pid=1576

    c_cout=3,p_cout=2,pid=1581

    c_cout=2,p_cout=3,pid=1580

    c_cout=2,p_cout=3,pid=1584

    c_cout=1,p_cout=4,pid=1583

    c_cout=3,p_cout=2,pid=1563

    c_cout=2,p_cout=3,pid=1556

    c_cout=2,p_cout=3,pid=1564

    c_cout=2,p_cout=3,pid=1570

    c_cout=1,p_cout=4,pid=1555

    c_cout=3,p_cout=2,pid=1575

    c_cout=2,p_cout=3,pid=1572

    c_cout=2,p_cout=3,pid=1578

    c_cout=1,p_cout=4,pid=1571

    c_cout=2,p_cout=3,pid=1582

    c_cout=1,p_cout=4,pid=1579

    c_cout=1,p_cout=4,pid=1585

    c_cout=0,p_cout=5,pid=1554

    通过推理:引用网络文章可得到:

    设f(n)表示程序中循环会运行n次时整个程序会产生的进程数,非常easy得到递推公式:

    f(n)=1+f(n-1)+f(n-2)+f(n-3)+…+f(0)

    比方for i=0;i<N;I++< p>

    由于i=0时fork()的子进程下次会继续循环n-1次,i=1时 fork()的子进程下次会仅需循环n-2 次。。。。

    当中常数1是进程本身。

    边界条件。f(0)=1

    这样,我们就得到了问题的答案:

    f(n)=1+f(n-1)+f(n-2)+…+f(0)

    f(0)=1

    这个能够求出闭形式:

    f(0)=1

    f(1)=2

    f(2)=4

    用数学归纳法能够得到f(n)=2^n

    所以对于程序一,会打印出2^5-1=31行信息。

    对于程序二,总共会产生2^5=32个进程。

    只是,我还是不知道为什么两个变量的结果怎么是那种形式的输出,求指导啊!!!

    以下贴一下比較有意思的一段代码:

    1. #include <stdio.h>   
    2.   
    3. #include <unistd.h>   
    4.   
    5. int main(int argc, char* argv[])   
    6.   
    7. {   
    8.   
    9. fork();   
    10.   
    11. fork() && fork() || fork();         
    12.   
    13. fork();   
    14.   
    15. return 0;   
    16.   
    17. }   


    我们要知道&& 和||运算符。&&有个短路现象。

    A&&B,假设A=0,就没有必要继续运行&&B了。A非0,就须要继续运行&&B。


    A||B。假设A非0。就没有必要继续运行||B了,A=0。就须要继续运行||B。

    通过绘图我们能够得到例如以下

    加上前面的fork和最后的fork,总共4*5=20个进程,除去main主进程。就是19个进程了。

    很多其它文章,欢迎关注http://blog.csdn.net/wallwind 版权归博主全部~~禁止商业用途

  • 相关阅读:
    git 基本使用
    docker下rabbitMQ高可用集群部署
    成长路上破局思维:工具化时间管理
    图解Elasticsearch的核心概念
    先森林后树木:Elasticsearch各版本升级核心内容必看
    JRebel 破解最简单的使用
    POA理论:不要被你的目标欺骗了你
    读了《跃迁-成为高手的技术》我的工资翻倍了
    微信头像地址失效踩坑记附带方案
    如何做程序员喜欢的测试妹子?
  • 原文地址:https://www.cnblogs.com/ldxsuanfa/p/10793113.html
  • Copyright © 2020-2023  润新知