linux创建进程读共享写复制,Linux进程创建之fork浅析

在Linux 内核中,无时无刻不维护着进程,从进程的创建到进程销毁,每一个环境都有着复杂的细节。本篇介绍Linux 内核如何创建进程,深入理解 fork 函数以及子进程的创建,对理解多进程开发也至关重要。

fork() 函数

首先来看下fork() 函数,其作用是创建子进程。头文件与函数原型如下

#include

// 参数: void

// 返回值: pid_t 创建的子进程ID

pid_t fork(void);

返回值:fork() 返回值会返回两次,分别在父进程和子进程中返回。

在父进程中返回子进程的ID,在子进程中返回0。所以可以通过fork 的返回值来区分父进程与子进程。

在父进程中返回 -1 ,表示创建子进程失败,并设置 errorno。如下面两种情况导致创建失败:当前系统的进程数已经达到了系统规定的上限,这时 errno 的值被设置为EAGAIN

系统内存不足,这时 errno 的值被设置为 ENOMEM

fork() 示例

下面创建一个子进程,来说明父进程与子进程的执行顺序。

#include

#include

int main(){

// 创建进程

pid_t pid = fork();

// 判断当前进程是父进程 还是子进程

if (pid > 0){// 进程号 > 0,即为子进程的进程号,当前为父进程

printf("pid: %dn", pid);

printf("I am parent process, pid: %d, ppid: %dn", getpid(), getppid());

}

else if (pid == 0){// 进程号 == 0,表示当前为子进程

printf("I am child process, pid: %d, ppid: %dn", getpid(), getppid());

}

for (int i = 0; i < 5; i++){

printf("pid: %d, i : %dn", getpid(), i);

sleep(1);

}

return 0;

}

编译执行,可以看到,子进程创建成功,两个进程同时执行,父进程ID 为412552,子进程ID为512553,由于sleep() 函数,使得函数阻塞,所以父进程与子进程交替执行。

39dc8cdc8bff6ad8e03306b5bef81d08.png

父子进程的虚拟地址空间

通过 fork函数创建进程后,可以通过返回值判断是父进程还是子进程。对于父进程与子进程如何执行,下面介绍fork函数调用后,父子进程如何执行,在进程中虚拟地址空间中如何体现。

首先,我们先看一下上述示例的执行顺序,父进程执行执行fork后,返回子进程ID,pid 大于0,所以输出 if(pid>0) 的内容。

39dc8cdc8bff6ad8e03306b5bef81d08.png

子进程在创建成功后,在子进程中返回 0,从当前位置开始执行,所以 pid=0 会输出 else 语句。

39dc8cdc8bff6ad8e03306b5bef81d08.png

对于虚拟空间地址来说,子进程会拷贝父进程的虚拟地址空间。所以,fork后子进程的用户区与父进程的用户区相同,也会拷贝内核区内容,仅仅是进程的 pid不同。

39dc8cdc8bff6ad8e03306b5bef81d08.png

对于父进程中的栈空间的变量,也会原封不动的拷贝至子进程的栈空间中。但这两个变量互不影响,父进程改变变量不会影响子进程变量。看如下程序展示父子进程中栈空间变量的使用。

#include

#include

int main(){

// 创建进程

pid_t pid = fork();

// 局部变量

int num = 10;

// 判断当前进程是父进程 还是子进程

if (pid > 0){// 进程号 > 0,即为子进程的进程号,当前为父进程

printf("I am parent process, pid: %d, ppid: %dn", getpid(), getppid());

printf("parent process num : %dn", num);

num += 10;

printf("parent process num + 10 : %dn", num);

}

else if (pid == 0){// 进程号 == 0,表示当前为子进程

printf("I am child process, pid: %d, ppid: %dn", getpid(), getppid());

printf("child process num : %dn", num);

num += 100;

printf("child process num + 100 : %dn", num);

}

return 0;

}

编译执行,可以看到,父进程中的局部变量num 与子进程的局部变量互不影响。

c34ee0424a61a00c64067af2de744244.png

读时共享,写时拷贝技术

实际上,准确的来说,Linux的fork 是通过 写时拷贝 (copy-on-write)实现。写时拷贝是一种可以推迟甚至不用避免拷贝的技术。更具体来讲,在执行fork语句后,内核并不复制父进程的整个地址空间,而是父子进程共享父进程的地址空间(此时父子进程对于地址空间是只读指令),在父进程或者子进程进行写指令时,子进程才会复制一份地址空间,从而使得父子进程拥有自己的虚拟地址空间,在自己的地址空间进行写操作。也就是说,资源的复制是在需要写入时才会进行,在此之前,只会以只读方式进行共享。

对于文件资源,fork之后的父子进程共享文件,fork之后的父进程与子进程的文件描述符表指向相同的文件表,引用计数增加,共享文件偏移指针。

fork函数就介绍到这里了,本篇介绍了创建子进程的过程,理解父子进程间虚拟地址空间的共享与复制。在多线程开发中,可以轻松分析父子进程的执行顺序与内存共享。

点关注,不迷路。一键三连是对我最大的支持,欢迎关注编程小镇,每天涨一点新姿势

内容来源于网络如有侵权请私信删除

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值