【Linux】深入理解进程管理与fork系统调用的实现原理

进程
基本概念
课本概念:程序的一个执行实例,正在执行的程序等
内核观点:担当分配系统资源(CPU时间,内存)的实体

操作系统在把程序加载到内存中的同时,为了更好的管理进程,还要为每一个进程创建一个task_struct,包含进程相关的所以属性,这样对进程的管理就相当于对数据结构的管理。

描述进程-PCB
进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。

课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct

task_struct-PCB的一种
在Linux中描述进程的结构体叫做task_struct。

task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。

task_ struct内容分类
标示符: 描述本进程的唯一标示符,用来区别其他进程。

状态: 任务状态,退出代码,退出信号等。

优先级: 相对于其他进程的优先级。

程序计数器: 程序中即将被执行的下一条指令的地址。

内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针

上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。

I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。

记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。

其他信息

组织进程
可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。

查看进程
首先,我们写一段代码

使用ps ajx指令查看进程

这里为什么查到两个带有myproc关键字的进程呢?

这是因为grep指令也是一个可执行程序,当使用这个指令的时候此时也是一个进程,当我们用这个进程查看带有myproc关键字的进程,当然就会把自己查出来了。

我们可以在加上一条指令屏蔽。

📢小知识:把一个程序运行起来,本质就是在系统当中启动了一个进程!一种是执行完就退出,比如:ls pwd 指令,一种是常驻进程,一直不退,直到用户退出。

通过系统调用获取进程标示符

如果想要查看进程的更详细的信息可以通过/proc系统文件夹查看

如:要获取PID为1的进程信息,你需要查看/proc/1这个文件夹

📢注意:proc文件夹里面的目录都是临时文件,当进程开始就会创建一个以这个进程的pid作为名字的文件夹,进程结束的时候就会删除这个文件夹。

例如:我们通过proc文件来查看,这个进程更详细的信息。

exe -> /home/hanbo/112/lesson9/myproc

这个exe是一个链接文件,这个链接文件指向该进程的实际可执行程序在磁盘上的路径。

cwd -> /home/hanbo/112/lesson9

cwd(current work dir):当前工作目录,这就是为什么我们在创建文件的时候会在当前目录下面创建

我想可以使用系统调用chdir来改变当前工作目录。

运行结果如下:

📢小知识:在命令行中,执行命令/执行程序,本质是bash的进程,创建子进程,来执行我们的代码。

我们如何通过系统调用来创建进程呢?

在执行fork之后就会有两个进程,一般而言,代码是会共享的,因为代码是只读的,但是数据是各自私有一份的,这是因为进程有很强的独立性,多个进程之间,运行时,互不影响,即便是父子进程,需要保证修改父进程的数据,不会影响到子进程。

因为父进程的PCB是根据磁盘加载到内存的程序的信息创建的,但是对于子进程来说只有task_struct这个结构体,没有代码和数据,所以,子进程本质是把父进程的task_struct结构体拷贝过来,这样父子进程就指向同一份代码,然后,在对子进程进行特性化设计。

📢小知识:fork之后父子进程谁先执行不确定,调度器决定。

如何创建多个子进程,进行管理呢?

#include <iostream>
#include <vector>
#include <sys/types.h>
#include <unistd.h>
using namespace std;

const int num = 10;

void SubProcessRun()
{
while(true)
{
cout << "I am sub process, pid: " <<getpid() <<" ,ppid: " << getppid() << std::endl;
sleep(5);
}
}

int main()
{
vector<pid_t> allchild;
for(int i = 0; i<num; i++)
{
pid_t id = fork();
if(id == 0)
{
// 子进程
SubProcessRun();
}
// 这里谁执行?父进程一个人
allchild.push_back(id);
}

// 父进程
cout << "我的所有的孩子是:";
for(auto child: allchild)
{
cout << child << " ";
}
cout << endl;

sleep(10);
while(true)
{
cout << "我是父进程,pid: " <<getpid() << endl;
sleep(1);
}
return 0;
}

深度理解为什么,fork之后代码会有两份?

在执行return id之前,创建子进程的工作肯定是已经完成的了,那么之后,代码都是父子进程共享的都会执行。

实际上,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行,所以return id其实返回了两次,然后,我们用变量进行接收的时候,因为父子进程的数据都是独立的,这时候就拿到两个返回值了。

fork之后代码共享,指的是整个程序代码共享还是只有,fork之后的程序代码共享呢?

以下是ChatGPT生成的内容

Fork 之后的代码共享
在 fork 操作之后,父进程和子进程之间的 代码共享 指的是 整个程序代码部分 共享,而不仅仅是 fork 之后的程序代码。

1. 代码共享
fork 后,父进程和子进程共享程序的 代码段,即存放指令集的部分。
无论父进程还是子进程,它们都执行相同的程序代码,尽管它们的执行路径可以不同。子进程会从 fork 返回后继续执行,或者可以通过 exec 系列函数加载新程序。
由于操作系统使用内存映射技术,这些代码段是 只读 的,并且不会被复制,从而实现父子进程的代码共享。
2. 数据段不共享
除了代码段,数据段(如堆、栈等)是 不共享 的。操作系统使用 写时复制(Copy-On-Write,COW) 技术来管理数据段:
初始时,父子进程共享相同的数据页。
一旦某个进程修改某一数据页,操作系统会为该进程分配新的内存,以避免修改影响到另一个进程。
总结
fork 后,父进程和子进程共享的是 整个程序的代码,包括加载到内存中的指令部分。
对于 数据段(堆、栈等),父子进程通过 写时复制 技术共享内存,直到某个进程修改数据才会分配新的内存页。
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/qq_74319491/article/details/144156075

版权声明:
作者:SE_Wang
链接:https://www.cnesa.cn/2897.html
来源:CNESA
文章版权归作者所有,未经允许请勿转载。

THE END
分享
二维码
打赏
海报
【Linux】深入理解进程管理与fork系统调用的实现原理
进程 基本概念 课本概念:程序的一个执行实例,正在执行的程序等 内核观点:担当分配系统资源(CPU时间,内存)的实体 操作系统在把程序加载到内存中的同时,为了更好的管理进程,还要为每一个进程创建一个task_struct,包含进程相关的所以属性,这样对进程的管理就相当于对数据结构的管理。 描述进程-PCB 进程信息被放在一个叫做进程控制块的数据结构中,可以理解为进程属性的集合。 课本上称之为PCB(process control block),Linux操作系统下的PCB是: task_struct task_struct-PCB的一种 在Linux中描述进程的结构体叫做task_struct。 task_struct是Linux内核的一种数据结构,它会被装载到RAM(内存)里并且包含着进程的信息。 task_ struct内容分类 标示符: 描述本进程的唯一标示符,用来区别其他进程。 状态: 任务状态,退出代码,退出信号等。 优先级: 相对于其他进程的优先级。 程序计数器: 程序中即将被执行的下一条指令的地址。 内存指针: 包括程序代码和进程相关数据的指针,还有和其他进程共享的内存块的指针 上下文数据: 进程执行时处理器的寄存器中的数据[休学例子,要加图CPU,寄存器]。 I/O状态信息: 包括显示的I/O请求,分配给进程的I/O设备和被进程使用的文件列表。 记账信息: 可能包括处理器时间总和,使用的时钟数总和,时间限制,记账号等。 其他信息 组织进程 可以在内核源代码里找到它。所有运行在系统里的进程都以task_struct链表的形式存在内核里。 查看进程 首先,我们写一段代码 使用ps ajx指令查看进程 这里为什么查到两个带有myproc关键字的进程呢? 这是因为grep指令也是一个可执行程序,当使用这个指令的时候此时也是一个进程,当我们用这个进程查看带有myproc关键字的进程,当然就会把自己查出来了。 我们可以在加上一条指令屏蔽。 📢小知识:把一个程序运行起来,本质就是在系统当……
<<上一篇
下一篇>>