fork系统调用

一次调用两次返回:一次在父进程中返回,返回值是子进程的pid;另一次是在子进程中返回,返回值为0。在Linux下如果内存没有被写的话,那么父子进程是共用地址空间的,所以内存中的同一个fork函数会在两个进程中调用到。在父进程中返回的就是子进程id,子进程中返回的是0。

子进程是父进程的副本:子进程获得父进程的数据段【意味着全部变量不互相影响】、堆栈段、栈的副本。(所谓副本是重新复制一份(或者写时复制),并且不共享)。子进程和父进程共享代码段,同一个物理内存段。

由于在fork之后经常跟随着exec,所以现在的很多实现并不执行一个父进程数据段、栈和堆的完全复制。作为替代,使用了写时复制(Copy-On-Write,COW)技术。这些区域由父、子进程共享,而且内核将它们的访问权限改变为只读的。如果父、子进程中的任一个试图修改这些区域,则内核只为修改区域的那块内存制作一个副本,通常是虚拟存储器系统中的一“页”。

子进程和父进程执行顺序:在fork之后是父进程先执行还是子进程先执行是不确定的。这取决于内核所使用的调度算法。如果要求父、子进程之间相互同步,则要求某种形式的进程间通信。

文件共享:在重定向父进程的标准输出时,子进程的标准输出也被重定向。实际上,fork的一个特性是父进程的所有打开文件描述符都被复制到子进程中。父、子进程的每个相同的打开描述符共享一个文件表项

(1)父进程等待子进程完成。在这种情况下,父进程无需对其描述符做任何处理。当子进程终止后,它曾进行过读、写操作的任一共享描述符的文件偏移量已执行了相应的更新。

(2)父、子进程各自执行不同的程序段。在这种情况下,在fork之后,父、子进程各自关闭它们不需要使用的文件描述符,这样就不会干扰对方使用的文件描述符。这种方法是网络服务进程中经常使用的。

父进程和子进程共享的属性:

  • 实际用户ID、实际组ID、有效用户ID、有效组ID。
  • 附加组ID。
  • 进程组ID。
  • 会话ID。
  • 控制终端。
  • 设置用户ID标志和设置组ID标志。
  • 当前工作目录。
  • 根目录。
  • 文件模式创建屏蔽字。
  • 信号屏蔽和安排。
  • 针对任一打开文件描述符的在执行时关闭(close-on-exec)标志。
  • 环境。
  • 连接的共享存储段。
  • 存储映射。
  • 资源限制。

父、子进程之间的区别是:

  • fork的返回值。
  • 进程ID不同。
  • 两个进程具有不同的父进程ID:子进程的父进程ID是创建它的进程的ID,而父进程的父进程ID则不变。
  • 子进程的tms_utime、tms_stime、tms_cutime已经tms_ustime均被设置为0.
  • 父进程设置的文件锁不会被子进程继承。
  • 子进程的未处理的闹钟(alarm)被清除。
  • 子进程的未处理信号集设置为空集。

fork有下面两种用法:

(1)一个父进程希望复制自己,使父、子进程同时执行不同的代码段。这在网络服务进程中是常见的——父进程等待客户端的服务请求。当这种请求到达时,父进程调用fork,使子进程处理此请求。父进程则继续等待下一个服务请求到达。

(2)一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从fork返回后立即调用exec。

 

以下代码输出-的个数:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
int main(void)
{
   int i;
   for(i=0; i<2; i++){
      fork();
      printf("-");
   }
   wait(NULL);
   wait(NULL);
   return 0;
}

 

带有阴影的两个子进程,复制了其父进程的文件描述符,复制了父进程的标准输出缓冲区(标准IO缓冲)的内容。

 

参考:https://www.cnblogs.com/nufangrensheng/p/3509492.html

 

为什么子进程先运行?

内核一般会先调度子进程,因为很多情况下子进程是要马上执行exec,会清空栈、堆。。这些和父进程共享的空间,加载新的代码段。。。,这就避免了“写时复制”拷贝共享页面的机会。如果父进程先调度很可能写共享页面,会产生“写时复制”的无用功。所以,一般是子进程先调度滴。

int     glob = 6;       /* external variable in initialized data */
char    buf[] = "a write to stdout\n";
int main(void)
{
        int     var;    /* automatic variable on the stack */
        pid_t   pid;
        var = 88;
        if(write(STDOUT_FILENO, buf, sizeof(buf) - 1) != sizeof(buf) -1)
                err_sys("write error");
        printf("before fork\n");        /* we don't flush stdout */
        if((pid = fork()) < 0){
                err_sys("fork error");
        }
        else if(pid == 0){       /* child */
                glob++;         /* modify variables */
                var++;
        }
        else{
                sleep(2);       /* parent */
        }
        printf("pid = %d, glob = %d, var = %d\n", getpid(), glob, var);
        exit(0);
}

虚拟地址肯定相同,不能看出是否采用了写实复制。

 

区别于vfork:

vfork()函数:不会将父进程的地址空间完全复制到子进程中,因为vfork通常立即调用exec函数,分配地址空间,映射自己的物理内存,不需要映射到父进程的物理内存。并且,vfork的子进程先运行结束,父进程才运行。vfork的子进程在调用exec函数之前,运行在父进程的空间中。

fork需要复制页表项,是页表项指向相同的物理内存。vfork不需要复制页表项。

 

 

 

 

 

 

 

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值