0


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

【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>usingnamespace std;constint num =10;voidSubProcessRun(){while(true){
        cout <<"I am sub process, pid: "<<getpid()<<" ,ppid: "<<getppid()<< std::endl;sleep(5);}}intmain(){
    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);}return0;}

在这里插入图片描述

深度理解为什么,fork之后代码会有两份?
在这里插入图片描述

在执行

return id

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

实际上,在fork函数被调用之前的代码被父进程执行,而fork函数之后的代码,则默认情况下父子进程都可以执行,所以

return id

其实返回了两次,然后,我们用变量进行接收的时候,因为父子进程的数据都是独立的,这时候就拿到两个返回值了。

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

以下是ChatGPT生成的内容

Fork 之后的代码共享

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

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

在这里插入图片描述

标签: linux 运维 服务器

本文转载自: https://blog.csdn.net/qq_74319491/article/details/144156075
版权归原作者 星光终将不负赶路人 所有, 如有侵权,请联系我们删除。

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

还没有评论