怎么建设自己淘宝网站首页,网站制作加教程视频教程,我的世界做弊端网站,网站备案信息如何下载目录 前言1. pid ppid2. forka. 为什么 fork 要给子进程返回 0#xff0c; 给父进程返回子进程的 pid #xff1f;b. 一个函数是如何做到两次的#xff1f;c. fork 函数在干什么#xff1f;d. 一个变量怎么做到拥有不同的内容的#xff1f;e. 拓展#xff1a;… 目录 前言1. pid ppid2. forka. 为什么 fork 要给子进程返回 0 给父进程返回子进程的 pid b. 一个函数是如何做到两次的c. fork 函数在干什么d. 一个变量怎么做到拥有不同的内容的e. 拓展fork()之后父子进程谁先运行 前言
该篇文章是继 添加链接描述 文章的后续针对 linux 中的 task_struct 进程的 PID也即标识符介绍和系统调用中的 fork 展开初步的认识。
task_ struct 内容分类
标识符: 描述本进程的唯一标识符用来区别其他进程。
状态: 任务状态退出代码退出信号等。
优先级: 相对于其他进程的优先级。
程序计数器: 程序中即将被执行的下一条指令的地址。其作用就相当于进程运行了一段时间后因为系统调度等原因停止了对该进程的执行而后续回来继续执行的时候需要知道上次执行到什么地方了。也好比我们看书今天看完不想看了之后会在此处做一下标记方便后续继续向下观看
内存指针: 包括程序代码和进程相关数据的指针还有和其他进程共享的内存块的指针比如记录了该进程所匹配的代码和数据的存储位置
上下文数据: 进程执行时处理器的寄存器中的数据[休学例子要加图CPU寄存器]。
IO状态信息: 包括显示的I/O请求分配给进程的 IO 设备和被进程使用的文件列表。
记账信息: 可能包括处理器时间总和使用的时钟数总和时间限制记账号等。保证系统调度的公平等
其他信息1. pid ppid
在前面的进程相关的文章我们已经知道了因为用户不擅长直接对操作系统进行访问并且操作系统也不会相信用户因此用户无法直接访问操作系统。而在上一篇文章的末尾我们简单见过了进程的 PID但是那是通过系统指令获取到的 PID而作为用户在编程语言上在无法访问操作系统拿到数据的前提下如何获取进程的 PID 等进程信息呢
// 测试demo
#includeiostream
#includeunistd.h
using namespace std;int main()
{while(1){cout I am a process, my pid is getpid() , my parentId is getppid() \n;sleep(1);}return 0;
}左右对比我们是可以得知在我们通过 c / c 编写的程序运行起来后系统会自动会该进程创建一个 PID并且在cpp 中预取这个 PID 和我们在系统中获取的 PID 是一致的。
所以PID 有什么用呢 ---- 既然 PID 是每个进程的唯一标识符那么当我们对进程进行管理的时候就可以通过其PID 对该进程进行操作比如杀掉该进程 kill -9 20059 等操作。 而当我们结束上一次进程重新运行我们的程序时我们又会发现它的 pid 不一样了这也是正常的现象。但是我们可以发现进程自己的 pid 会变但是他的父进程的 pid 确不会变一直是 2742好奇心驱动我们去查看这个父进程它到底是谁 没错2742 它就是我们的 bash这个 bash 进程是由 xshell 为我们创建的一个命令行的进程所以同理当我们断开对 linux 的链接之后再一次链接 linux进程的 ppid 也会随之改变即我们每一次通过xshell 远程连接我们的服务器时xshell 都会为我们重新分配一个 bash 进程用来给我们提供命令行服务 2. fork
但是上述的这些进程可都是操作系统给我们创建出来的。那么现在我要手动创建进程该怎么做呢 ---- 调用 fork 系统函数。那么修改后的 demo 代码如下
int main()
{cout before the fork!\n;fork();cout after the fork!\n;sleep(1)return 0;
}很明显在执行完 fork函数之后fork 之后的代码被执行了 两次为什么最简单的回答就是因为 fork 是创建进程的系统函数因此在执行完 fork 之后会有两个进程同时执行这一份代码所以代码一共被执行了两次
但是上述仅仅是我们的猜测为了更清楚的了解 fork 函数干了什么我们需要通过 man 手册进一步了解 fork 函数 以上是 fork 函数介绍中的一段信息但是有一点奇怪的是这个函数怎么可以返回两个值呢据 man 手册的介绍如果进程创建成功返回这个进程的 pid 给它的父进程返回 0 给自己创建失败则返回 -1 给父进程。这显然是我们无非理解的在 C/C 语言当中函数的返回值一直都只能有一个啊而现在这个 fork 函数它告诉我有两个返回值
多的不说我们再来做一个 demo 测试到底是不是真的这样这个函数有两个返回值。
int main()
{cout I am a process, pid: getpid() , ppid: getppid() \n;pid_t pid fork();if(pid 0){// 子进程部分while(1){cout I am a child process, pid: getpid() , ppid: getppid() \n;sleep(1);}}else if(pid 0){// 父进程部分while(1){cout I am a parent process, pid: getpid() , ppid: getppid() \n;sleep(1);}}else{cout error\n;}return 0;
}没错就是这么神奇如果站在语言上的认知这是根本无法理解的现象每个 if 分支里面都是一个死循环但是运行起来确出现了两个 if 分支里的内容所以这可以进一步的说明了fork 之后会多一个进程而这个进程是由原来的进程所创建出来的它的 ppid 即父进程就是创建它的进程的 pid 而 fork 作为系统调用接口也会为创建出来的进程进行属性初始化分配 pid 等操作。所以站在系统的角度看待这一段代码的话那么就能说明原本的进程作为父进程在执行完 fork 函数之后接收到的是创建出来的子进程的 pid因此会执行第一个 if 分支而子进程自己接收到 0 的返回值所以执行第二个 if 分支的内容。
一般而言fork 之后的代码是父进程与子进程共享的所以返回值的不同恰恰是为了区分让不同的执行流执行不同的代码块
接下来我们要回答几个关于 fork 的问题。
a. 为什么 fork 要给子进程返回 0 给父进程返回子进程的 pid
你知道的父亲永远只有一个而孩子可以有很多个一个孩子也不可能同时有两个父亲。因此在操作系统中同理一个父进程可以有多个子进程但是不可能存在一个子进程有多个父进程所以把子进程的 pid 给父进程是为了让父进程可以明确的找到它的子进程而子进程永远只有一个父进程就不用谈找不到这件事了。假设今天父进程有10个子进程但是它并不知道这个进程跟那个进程的 pid也就无法明确指定操作其中某一个子进程了再者父进程在创建时父进程有自己的内核 pcb 数据结构也有自己的代码和数据那现在父进程创建出一个子进程之后系统会为子进程其分配一个 pcb这没问题但是子进程应该执行什么样的代码和访问什么样的数据呢 而开始创建子进程是时候子进程是没有自己的代码和数据的所以子进程只能与父进程共享一样的代码数据另谈那问题又来了当 cpu 在调度的时候父进程在执行这一份代码子进程也是执行的这一份代码这也是上面实验时fork 之后的代码会重复执行两遍的原因。那这有什么意义呢或者说同样的代码为什么要执行两遍呢所以问题就回归到了 我们为什么要创建这个子进程 所以一定是为了让父进程和子进程执行不同的代码完成不同的工作所以就需要让 fork 具有不同的返回值才能达到区别不同的执行流
b. 一个函数是如何做到两次的
既然 fork 是用于创建进程的一个系统调用函数而站在系统层面上创建进程就要为其创建一个 pcb并且每个进程需要有与其匹配的代码和数据这是系统在创建进程时需要做的工作。那么我们就不难猜测fork 在干什么。
pid_t fork(void)
{创建子进程填充 PCB 对应的内容让父子进程共享同一份代码到这一步父子进程都用拥有了自己独立的 task_struct即 PCB可以被 cpu 调度运行了......return ret;
}我们知道的是子进程被创建出来之后父子进程会共享代码因此 fork 之后的代码才会被执行两次。现在的问题就是那么 return ret 是不是一条代码 ---- 它是代码是不争的事实又因为在 return 之前子进程就已经被创建了并且也完成了其 pcb 的各种初始化工作同父进程一样拥有独立的 task_struct所以既然在 return 语句的时候子进程就已经完全存在了那么 return 语句就自然是会被父子进程都执行所以父进程返回一次子进程返回一次这个函数一共就返回了两次
c. fork 函数在干什么
其实这个问题在上面的介绍中就已经不难得知了。fork 就是在创建一个进程并且用其父进程对应的字段来初始化子进程而因为子进程刚创建出来没有自己的代码和数据所以就需要和父进程共享同一份代码数据另谈。我们都知道之所以可以共享代码是因为代码存储在系统的常量区它是不能够被修改的因此父进程并不会因为与子进程进行代码共享而受到影响。但是数据就不一定数据是可以被修改的所以假如父进程和子进程共享同一份数据然后子进程需要对其中的数据进行修改这个数据又恰恰是父进程的某一个条件判断所需的数据这就势必会影响到父进程的正常运行那怎么办呢所以子进程可以将父进程的数据拷贝一份给自己独立使用自己怎么修改都不会影响到父进程。但是问题又来了假设父进程当中有100个全局变量数据而将来子进程只需要用到其中一个其它的 99 个甚至更多都不需要修改这样就会导致系统资源变少利用率也变低。而实际上被创建出来的子进程需要修改到的数据其实是不大的基本不可能有子进程需要修改父进程的全部数据。因此在面对数据方面子进程则是采用了 写时拷贝 的策略当子进程与父进程进行数据共享之后子进程需要修改数据系统就会为子进程开辟一块属于子进程自己的空间并且将要修改的数据拷贝一份供子进程修改和使用子进程需要多少空间系统就开辟多少而后续子进程访问的也是自己的数据这样父子进程就不会互相影响因为我们需要保证让进程之间相互独立互不影响。总不能是今天我csdn网页的进程奔溃了连同我的音乐进程也奔溃了吧。
d. 一个变量怎么做到拥有不同的内容的
这个问题对 b 问题的一个延续在上面的问答中我们已经能够得知一个函数之所以可以返回两个值是因为在 return 的时候子进程就已经被创建出来了并且开始与父进程共享代码在数据层面上子进程并不是完全与父进程进行数据共享而是采用了 写时拷贝 的策略。所以现在需要确定的是return 是不是一种对数据的修改或者是算不算数据的写入 ---- return 将数据进行返回所以有数据就必须要接收也即数据的写入而写入就是修改的范畴所以当系统检测到子进程要对父进程的数据进行修改的时候就会为其开辟一块自己的空间供子进程使用。所以站在系统层面上这个变量数据在内存中存在两份一份是父进程的一份是子进程的所以当我们站在语言的角度上才看到了一个变量拥有两个不一样的值。
e. 拓展fork()之后父子进程谁先运行
如果大家有这方面的疑惑的话就需要深入了解在系统调度器方面的知识因为谁先运行这是调度器决定的在系统层面上谁先运行取决于调度器决定先将哪个进程提携给 cpu 执行。而每个系统的调度原理都不太一样所以这方面又是一门足以压死人的学问小篇也是无能为力。 进程介绍到这里的时候还远远没有结束我们现在只是弄清楚进程是什么以及与进程相关的系统调用 fork但是进程还会有所谓的状态比如进程等待堵塞等等。但是由于篇幅问题关于进程的状态等方面的信息小篇会在后续文章中一一介绍。
如果感觉该篇文章给你带来了收获可以 点赞 收藏⭐️ 关注➕ 支持一下
感谢各位观看