目录
一、前置准备
1、进程的基本概念
2、进程标识符PID、PPID
1)pid介绍
2)获取pid和ppid
二、fork函数
1、fork的基本介绍
1)fork(): 创建子进程
2)对于函数具体的描述
3)两个返回值
2、感受一下fork的使用
3、子进程的作用?-->实现不同于父进程的功能
4、创建的子进程如何存储,PCB都是如何确定的?-->子进程(PCB单独创建)和父进程共享代码和数据
5.为什么fork要有两个返回值?
6.为什么fork要给子进程返回0,给父进程返回子进程pid?
7.fork之后,父子进程谁先运行?
7.一个变量怎么会有不同的内容?-->地址空间
全文在Ubuntu 20.04.6 LTS的环境下使用C语言!
本篇文章需要大家具备一定的Linux的基础下,进行学习~
一、前置准备
1、进程的基本概念
【Linux】进程周边:进程概念-CSDN博客
这篇文章中已经将进程的基础概念、进程控制块PCB、查看进程的基本信息、杀掉进程,进行了介绍~
稍后本篇也会提及到部分内容。
2、进程标识符PID、PPID
1)pid介绍
进程标识符:pid
- task_struct里面的一个变量
- 当前操作系统中每个进程的唯一性标识
PPID:当前进程的父进程的标识符
获取 进程(pid) 的函数:getpid();
获取 父进程(ppid) 的函数:getppid();
进程的pid都是独一无二的,每次运行程序,都相当于一个新的进程,所以每启动一次程序,该进程的pid就会变化,但是父进程的PID不会变。(也就是无论孩子怎么变化,他的父亲该是谁,就是还是谁!)
在命令行运行的命令或程序都是bash的子进程;
几乎所有的指令都是程序,运行起来都是进程;
2)获取pid和ppid
- #include <sys/types.h>
- #include <unistd.h>
- pid_t getpid(void);
- pid_t getppid(void);
通过man 手册查询getpid()的使用。
man getpid
获取 进程(pid) 的函数:getpid();
获取 父进程(ppid) 的函数:getppid();
二、fork函数
1、fork的基本介绍
我们先来看看Linux系统中对于fork函数是怎么介绍的!
使用man指令查询
man fork
可见:
1)fork(): 创建子进程
2)对于函数具体的描述
- fork()通过复制调用进程来创建一个新进程。
- 新进程被称为子进程。调用进程被称为父进程。
- 子进程和父进程在单独的内存空间中运行。
- 子进程几乎是父进程的完全拷贝(某些情况例外)
3)两个返回值
如果成功,则在父进程中返回子进程的PID,并在子进程中返回0。
如果失败,则在父进程中返回-1,不创建子进程,并适当设置errno这就是fork的特性,具有两个返回值!
2、感受一下fork的使用
让我们根据以下代码,对于fork的使用进一步了解。
我们希望这段代码能够达到,
创建进程之后,
父进程可以输出自己的pid、以及自己对应的父进程的pid、还有fork子进程的pid
子进程可以输出自己的pid、以及自己对应的父进程的pid、还有返回值0
#include<stdio.h> #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { printf("创建子进程前,这只有一个进程,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(3);//休眠3秒 //我们创建进程,感受父子进程 printf("创建新的进程了\n"); pid_t id = fork();//接收返回值 //利用条件选择语句,区分父、子进程 //id = 0 --> 当前进程是子进程 //id = 子进程的pid -->那么当前进程是父进程 if(id == 0) { //子进程 while(1){ printf("after fork, 我是子进程: I am a prcess, pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1);//休眠1秒,避免刷新太快 } } else{ //父进程 while(1){ printf("after fork, 我是父进程: I am a prcess, pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1); } } return 0; }
具体的pid变化过程:
父子进程可以执行不同的任务
通过上面的代码,可以看到父、子进程可以利用条件选择if else同时执行不同的内容。
那么,子进程就可以做一些父进程不能做,又或是不敢做的事情,这样既不会危害到父进程,也能完成任务。
通过上文的代码实现,我们可以对fork又有了进一步的了解。那么对于fork,又会引发对应的问题.
首先,子进程究竟有什么作用?
以及创建的子进程的存储和进程信息都是如何确定的?
为什么fork要有两个返回值?
为什么fork要给子进程返回0,给父进程返回子进程pid?
父子进程怎么实现执行的不同的内容
3、子进程的作用?-->实现不同于父进程的功能
通过上面的代码和图文,可以发现fork之前的代码只有父进程执行
然而fork之后的代码父子进程都要执行
创建子进程的意义就是
为了让子进程执行和父进程不一样的代码,实现和父进程不一样的功能。
比如我们可以一边下载聊天,一边播放音乐,这两个过程就是不同的进程在执行!
那修改一下功能,#include<stdio.h> #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { printf("创建子进程前,这只有一个进程,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(3);//休眠3秒 //我们创建进程,感受父子进程 printf("创建新的进程了\n"); pid_t id = fork();//接收返回值 //利用条件选择语句,区分父、子进程 //id = 0 --> 当前进程是子进程 //id = 子进程的pid -->那么当前进程是父进程 if(id == 0) { //子进程 while(1){ printf("after fork, 我是子进程,正在播放视频: pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1);//休眠1秒,避免刷新太快 } } else{ //父进程 while(1){ printf("after fork, 我是父进程,正在进行聊天: pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1); } } return 0; }
可以查看图片,确实父子进程分别在 实现不同的功能。
4、创建的子进程如何存储,PCB都是如何确定的?-->子进程(PCB单独创建)和父进程共享代码和数据
fork会创建子进程,系统中会多出一个子进程。
操作系统以父进程为模板,为子进程创建PCB,但是子进程中是没有代码和数据的,所以子进程和父进程共享代码和数据
所以fork之后,父子进程会执行一样的代码。
5.为什么fork要有两个返回值?
- 返回不同的返回值,是为了 区分父、子
- 为了让fork以后的if、while等条件判断语句进行区分,来让父子进程执行 不同 的代码片段
- 两个进程可以执行不同的功能(进程之间是相互独立的)
最重要的也是因为,
fork执行过程中,
子进程被创建完毕,自然就会会执行父进程中的代码和数据,自然也会执行到return这一句。
因此,父子都会返回一个数值,产生了两个返回值。
以下,是fork函数执行的大概逻辑!!
可以发现,在fork函数return之前,
就已经创建了子进程,并且将子进程放入调度队列中运行了,所以当子进程,在调度队列时,它和父进程就已经分流了。
而不是真正在fork函数return之后才分流的并且创建完子进程后代码是共享的,在fork以后的代码执行了两次
很明显return也是一句代码。所以父子进程,都会执行return语句,fork函数有两个返回值
部分内容参考这个篇文章:【linux进程(二)】如何创建子进程?--fork函数深度剖析_linux系统用for循环创建三个子进程-CSDN博客
6.为什么fork要给子进程返回0,给父进程返回子进程pid?
- 一个进程只能有一个父进程,但是可以拥有多个子进程。
- 一个父进程可以创建很多个子进程,然而一个子进程只对应一个父进程,所以fork函数会返回子进程的pid给父进程,方便父进程对于子进程进行管理。
7.fork之后,父子进程谁先运行?
创建完成子进程后,这只是一个开始,系统的其他进程,父进程,子进程接下来会被调度执行!
问题是先调度谁?先创建就先调度吗?
答案明显不是!不能确定谁先执行。
在调度队列中,CPU会选择一个进程去运行它,谁先被调度,谁就先运行!
所以fork之后父子进程谁先运行,用户是不确定的,这是由各自进程PCB中的调度信息决定的,比如优先级,算法信息等等
8.一个变量怎么会有不同的内容?-->地址空间
我们继续使用一开始的代码。
在这段代码中,可以看到,if else两个都被执行了,也就是说,id这个变量满足两个不同的条件。-----> id这个变量有两个值。
#include<stdio.h> #include<stdio.h> #include<unistd.h> #include<sys/types.h> int main() { printf("创建子进程前,这只有一个进程,pid:%d,ppid:%d\n",getpid(),getppid()); sleep(3);//休眠3秒 printf("创建新的进程了\n"); pid_t id = fork();//接收返回值 if(id == 0) { //子进程 while(1){ printf("after fork, 我是子进程: pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1); } } else{ //父进程 while(1){ printf("after fork, 我是父进程:pid: %d, ppid: %d, return id: %d\n\n", getpid(), getppid(), id); sleep(1); } } return 0; }
但是同一个变量怎么可能有两个不同的值呢?
进程是有独立性的。
首先是表现在进程各自的PCB运行时不会相互影响。很明显,代码本身只是可读的,所以不会影响代码。
但是对于数据来说,
父子的数据是可能不同的(可能会被修改)
所以系统是怎样做到让数据在各个进程都自己私有一份的?
答案是
写时拷贝
(类似于学习类和对象时的深浅拷贝数据会在需要使用时,被写时拷贝到PCB。)
然而fork返回值赋值给变量时,本质也是写入,返回时也会发生写时拷贝,所以不同
的进程执行的代码中的变量id获取的值不同!
父、子进程空间分配的文章
linux多进/线程编程(2)—— fork函数和进程间“共享”数据 - 胖白白 - 博客园 (cnblogs.com)
进程专题02篇———进程共享(读时共享写时复制copy-on-write)原理详解——超经典-CSDN博客
深入学习fork的文章推荐:
【创建进程】fork函数与写时拷贝-阿里云开发者社区
fork()写时复制原理-阿里云开发者社区
参考文章:
【Linux系列】fork( )函数原理与应用详解——了解【父子进程及其特性】(代码演示,画图帮助理解,思维导图,精简)(11)_linux fork-CSDN博客