当前位置:   article > 正文

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

fork函数创建是一定有子进程吗

       

目录

fork函数

返回值

内存分配


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

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

fork函数

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

函数原型

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

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

返回值

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

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

结果如下:

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

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

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

  1. #include <stdio.h>
  2. #include <sys/types.h>
  3. #include <unistd.h>
  4. int main()
  5. {
  6. int ret = fork();
  7. if(ret < 0){
  8. perror("fork");
  9. return 1;
  10. }
  11. else if(ret == 0){ //child
  12. printf("I am child : %d!, ret: %d\n", getpid(), ret);
  13. }else{ //father
  14. printf("I am father : %d!, ret: %d\n", getpid(), ret);
  15. }
  16. sleep(1);
  17. return 0;
  18. }

运行结果如下: 

内存分配

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

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

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

  1. #include <stdio.h>
  2. #include <unistd.h>
  3. int main() {
  4. int x = 1;
  5. pid_t pid = fork();
  6. if (pid == 0) {
  7. // 子进程
  8. sleep(1);
  9. printf("x的值:%d,x的地址:%p\n",x,&x);
  10. x = 10; // 修改子进程的 x 值
  11. } else if (pid > 0) {
  12. // 父进程
  13. x = 20; // 修改父进程的 x 值
  14. sleep(2);
  15. printf("x的值:%d,x的地址:%p\n",x,&x);
  16. } else {
  17. // fork() 调用失败
  18. fprintf(stderr, "fork() failed\n");
  19. return 1;
  20. }
  21. return 0;
  22. }

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

来看结果:

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

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

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

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

声明:本文内容由网友自发贡献,不代表【wpsshop博客】立场,版权归原作者所有,本站不承担相应法律责任。如您发现有侵权的内容,请联系我们。转载请注明出处:https://www.wpsshop.cn/w/喵喵爱编程/article/detail/882973
推荐阅读
相关标签