muduo库学习之适用场合和常用编程模型01——fork函数(多进程)

东阳的学习笔记

1 fork()函数

一个现有的进程可以调用 fork() 创建一个新的进程。

#include <unistd.h>
pid_t fork(void);

返回值:子进程返回0,父进程返回子进程 ID;若出错,返回-1

由 fork() 创建的新进程被称为子进程(child process)。fork() 函数被调用一次但返回两次:子进程的返回值是0,而父进程的返回值则是新建子进程的进程 ID.

将子进程的进程 ID 返回给父进程的理由是:因为一个进程的子进程可以有多个,但是没有一个函数使一个进程可以获得其所有子进程的进程 ID。同理父进程返回值为0的原因恰好相反,通过 getppid()可以获得父进程的进程ID,而且一个进程只能有一个父进程

子进程和父进程继续执行fork调用之后的指令。子进程是父进程的副本。例如,子进程获得父进程的数据空间、堆和栈的副本

注意:子进程拥有的是副本。父进程和子进程并不共享这些储存空间,他们共享的是正文段。

由于在 fork() 之后经常跟随着 exec,所以现在的很多实现并不执行一个父进程数据段、堆和栈的完全副本。作为替代,使用了写时复制(COW)技术。这些(存储)区域由子进程和父进程共享,而且内核将他们的访问权限变为只读

fork函数的一个实例

从运行结果可以看到:子进程对变量所做的修改并不影响父进程中该变量的值。

#include "apue.h"

int     globval = 6;
char    buf[] = "a write to stdout\n";

int
main(void)
{
     
     
    int    var = 0;
    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)    // 子进程返回0
    {
     
     
         globval++;
         var++;
    }
    else       // 父进程返回子进程ID
    {
     
     
         sleep(2);
    }

    printf("pid = %ld, glob = %d, var = %d\n", (long)getpid(), globval, var);

    exit(0);
}

运行结果
a write to stdout
before fork
pid = 8833, glob = 7, var = 89
pid = 8832, glob = 6, var = 88

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

文件共享

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

考虑下述情况,一个进程具有三个不同的打开文件,标准输入、输出、错误。在从 fork 返回时,我们有下图的结构:
在这里插入图片描述
重要的一点是:父进程和子进程共享同一个文件偏移量

如果父子进程写同一个描述符,是需要同步控制的
(1) 父进程等待子进程完成。当子进程终止时,更新相应描述符的偏移量
(2) 父进程和子进程各自执行不同的程序段。在这种情况下在fork之后父子进程各自关闭各自不需要使用的描述符。这种方法是网络服务进程经常使用的

其他共享

除了打开文件之外,父进程的很多其他属性也被子进程继承。

  • 实际用户ID、实际组ID、有效用户ID、有效组ID
  • 附属组ID
  • 近程组ID
  • 会话ID
  • 控制终端
  • 设置用户ID标志和设置组ID标志
  • 当前工作目录
  • 根目录
  • 文件模式创建屏蔽字
  • 信号屏蔽与安排
  • 对任意打开文件描述符的执行时关闭标志
  • 环境
  • 连接的共享存储段
  • 存储映像
  • 资源限制

父/子进程的区别

  • fork的返回值不同
  • 进程ID不同
  • 子进程的 tms_utime、tms_stime、tms_cutime 和 tms_ustime 的值被设置为0
  • 子进程不继承父进程设置的文件锁
  • 子进程的未处理闹钟被清除
  • 子进程的未处理信号集被设置为空集

fork使用

fork失败的两种原因:

  • 系统中已经有太多的进程
  • 该实际用户ID的进程综述超过了系统限制

fork的两种用法:

  • 一个是父进程希望复制自己,使父进程和子进程同时执行不同的代码块。这在网络服务继承中是常见的——父进程等待客户端的服务请求。当这种请求到达时,调用fork,让子进程处理该请求,自己继续等待下一个请求。
  • 一个进程要执行一个不同的程序。这对shell是常见的情况。在这种情况下,子进程从 fork 返回后立即调用 exec

猜你喜欢

转载自blog.csdn.net/qq_22473333/article/details/113481494