【Linux】理解父子进程(系统调用创建进程,fork函数,写时拷贝)

       

目录

fork函数

返回值

内存分配


        父子进程是操作系统一个重要的概念,特别是在多任务处理和并发编程中,在Linux中,每个进程都有一个唯一的进程ID,并且每个进程都有可能创建其他进程。当一个进程创建了一个新的进程时,新创建的进程就成为了原始进程的子进程。

        同样用生活中的例子来理解。在一个家庭中(类比一个操作系统中),父母决定做晚饭(做饭就是一个进程),他们分配给孩子一个任务,让大儿子洗菜,小儿子烧水(洗菜和烧水就是两个子进程),父母和孩子之间相互协作,共同完成了这一顿晚饭,这就是父子进程之间相互协作独立执行任务的特性。

fork函数

“fork()”函数是在Linux中用于创建新进程的系统调用之一。调用fork()函数时,操作系统会创建当前进程的一个副本,即子进程。

函数原型

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

fork函数的定义在unistd.h头文件中完成。

返回值

考虑一个问题:fork函数的返回值是什么呢?我们用下面这段代码来验证一下

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int main()
 {
    int ret = fork();
    printf("hello proc : %d!, ret: %d\n", getpid(), ret);
    sleep(1);
    return 0;
 }

结果如下:

诶?明明应该只有一行输出结果,为什么这里会有两行呢,而且结果还不一样

事实上,这两行输出就是父子进程并发执行的结果。第一行是父进程的运行结果,此时的进程id(PID)是“65781”,fork的返回值是子进程id(PPID)“65782”;第二行是子进程的运行结果,fork返回值是“0”。

下面这段代码就可以更清晰地观察父子进程:

#include <stdio.h>
#include <sys/types.h>
#include <unistd.h>
 
int main()
 {
    int ret = fork();
    if(ret < 0){
        perror("fork");
        return 1;
    }
    else if(ret == 0){ //child
        printf("I am child : %d!, ret: %d\n", getpid(), ret);
    }else{ //father
        printf("I am father : %d!, ret: %d\n", getpid(), ret);
    }
    sleep(1);
    return 0;
 }

运行结果如下: 

内存分配

        在fork函数调用之后,父进程和子进程都将拥有相同的内存空间映像,但它们是相互独立的。这意味着,当其中一个进程修改了内存中的数据,另一个内存不会受到影响。那么这是如何实现的呢?如果父子进程都指向同一块内存空间,那么数据的修改会互相影响,所以它们是指向两块不同的空间,只是这两块空间存放相同的数据,是这样吗?nonono,系统用了一个很聪明的方法,那就是写时拷贝。

写时拷贝是一种延迟复制技术,它使得在fork函数中进行子进程的创建时,实际上并不立即复制父进程的内存空间,而是等到子进程尝试修改其中某个页面时才进行复制。这样可以减少内存开销,提高效率,特别是当父子进程在大部分时间内只读取数据而不修改时。下面用一张图来加深理解:

 下面用代码来验证fork函数的写时拷贝:

#include <stdio.h>
#include <unistd.h>

int main() {
    int x = 1;
    pid_t pid = fork();

    if (pid == 0) {
        // 子进程
		sleep(1);
		printf("x的值:%d,x的地址:%p\n",x,&x);
		x = 10; // 修改子进程的 x 值
    } else if (pid > 0) {
        // 父进程
		x = 20; // 修改父进程的 x 值
		sleep(2);
		printf("x的值:%d,x的地址:%p\n",x,&x);
    } else {
        // fork() 调用失败
        fprintf(stderr, "fork() failed\n");
        return 1;
    }

    return 0;
}

这里的sleep()函数是为了达到 父进程修改变量-->显示子进程信息,子进程修改变量-->显示父进程信息 的进程效果,如果父子进程之间变量的修改没有互相影响,就说明进行了写时拷贝。

来看结果:

可以看出父子进程对x的修改并没有影响到对方,但是为什么这里x的地址是相同的呢?

因为在fork函数调用后,操作系统为父子进程分配了不同的栈空间,因此它们各自的变量‘x’都位于不同的栈空间之中。x的地址实际上是相对于各自的栈空间的偏移量,并不是指向相同的物理内存地址。实际上它们所指的是不同的物理内存地址。

换句话说,虽然地址看起来相同,但实际上它们处于不同的内存空间,所以这并不违背写时拷贝的原理。

一上就是父子进程与fork函数的相关知识了,欢迎在评论区留言,觉得俺的博客对你有帮助的可以点赞关注支持一波窝~😉

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

谁在夜里看海.

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值