linux子进程执行起点

参考博客

首先给出结论:

子进程继承了父进程的程序计数器的当前值,也就是继承了fork()语句执行时父进程当前的环境,而不是程序的初始环境,所以子进程从fork()语句之后开始执行。

fork调用的一个奇妙之处就是它仅仅被调用一次,却能够返回两次,它可能有三种不同的返回值:

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

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

3)如果出现错误,fork返回一个负值;

(注意,这里所说的返回两个值只是在调用fork函数时的特性,实际上,子进程有自己的进程号,通过 getpid() 函数得到的就是真实的进程号,并不等于0)


创建新进程成功后,系统中出现两个基本完全相同的进程,这两个进程执行没有固定的先后顺序,哪个进程先执行要看系统的进程调度策略。此时,两个进程都从fork开始往下执行,只是pid不同。

进程复制时还有两个知识点:

1) Linux下一个进程在内存里有三部分的数据,就是"代码段"、"堆栈段"和"数据段"。

"代码段",顾名思义,就是存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用相同的代码段。

"堆栈段"存放的就是子程序的返回地址、子程序的参数以及程序的局部变量。

“数据段”则存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)

父进程和子进程共用“代码段”,而“堆栈段”和“数据段”不能共用,需要拷贝复制。

一般CPU都是以"页"为单位来分配内存空间的,每一个页都是实际物理内存的一个映像,象INTEL的CPU,其一页在通常情况下是4086字节大小,而无论是数据段还是堆栈段都是由许多"页"构成的,fork函数复制这两个段,只是"逻辑"上的,并非"物理"上的,也就是说,实际执行fork时,物理空间上两个进程的“数据段”和“堆栈段”都还是共享着的,当有一个进程写了某个数据时,这时两个进程之间的数据才有了区别,系统就将有区别的"页"从物理上也分开,也叫写时复制技术(copy-on-write)。系统在空间上的开销就可以达到最小。

2)   函数fork( )用来创建一个新的进程,该进程几乎是当前进程的一个完全拷贝;子进程和父进程使用相同的代码段;子进程复制父进程的堆栈段和数据段。这样,父进程的所有数据都可以留给子进程,其中包括程序计数器的值。

上图表示一个含有fork的程序,而fork语句可以看成将程序切为AB两个部分。然后整个程序会如下运行:

      step1、设由shell直接执行程序,生成了进程PP执行完Part. A的所有代码。

      step2、当执行到pid = fork();时,P启动一个进程QQP的子进程,和P是同一个程序的进程。Q继承P的所有变量、环境变量、程序计数器的当前值。

      step3、在P进程中,fork()QPID返回给变量pid,并继续执行Part. B的代码。

      step4、在进程Q中,将0赋给pid,并继续执行Part. B的代码。

      这里有三个点非常关键:

      1P执行了所有程序,而Q只执行了Part. B,即fork()后面的程序。(这是因为Q继承了PPC-程序计数器)

      2Q继承了fork()语句执行时当前的环境,而不是程序的初始环境。

      3Pfork()语句启动子进程Q,并将QPID返回,而Q中的fork()语句不启动新进程,仅将0返回。

#include <unistd.h> 
#include <sys/types.h>
main () 
{ 
         pid_t pid; 
        printf("hello!\n");  
         pid=fork();
        if (pid < 0) 
                printf("error in fork!"); 
         else if (pid == 0) 
                printf("i am the child process, my process id is %d\n ",getpid());
         else 
                printf("i am the parent process, my process id is %d\n",getpid());
        printf("bye!\n");
} 


这里可以看出parent process执行了printf("hello!\n"); child process没有执行printf("hello!\n"); 

有一个让人很迷惑的例子:

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

main () 
{ 
         pid_t pid; 
        printf("fork!");    //printf("fork!\n")
        pid=fork(); 

         if (pid < 0) 
                printf("error in fork!\n"); 
         else if (pid == 0) 
                printf("i am the child process, my process id is %d\n",getpid());
         else 
                printf("i am the parent process, my process id is %d\n",getpid());
} 


此时打印输出了两个fork!这不免让人以为是child process从#include处开始执行,所以也执行了printf("fork!"); 语句。

其实不然,出现这种问题的原因在于:

这就跟Printf的缓冲机制有关了,printf某些内容时,操作系统仅仅是把该内容放到了stdout的缓冲队列里了,并没有实际的写到屏幕上。但是,只要看到有\n,则会立即刷新stdout,因此就马上能够打印了.

mian函数(parent process)运行了printf("fork!"), "fork!"仅仅被放到了缓冲里,再运行到fork,缓冲里面的 AAAAAA 被子进程(child process)继承了,因此在子进程度stdout缓冲里面就也有了"fork!"。所以,你最终看到的会是 "fork!" printf2!!!! 
mian函数(parent process)运行 printf("fork!\n"),"fork!" 被立即打印到了屏幕上,之后fork到的子进程(child process)里的stdout缓冲里不会有"fork!"内容因此你看到的结果会是"fork!" printf1!!!!















  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值