【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之后父子进程谁先执行不确定,调度器决定。

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

cpp 复制代码
#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 后,父进程和子进程共享的是 整个程序的代码,包括加载到内存中的指令部分。
  • 对于 数据段 (堆、栈等),父子进程通过 写时复制 技术共享内存,直到某个进程修改数据才会分配新的内存页。
相关推荐
fen_fen7 分钟前
Docker如何运行一个python脚本Hello World
运维·docker·容器
TianyaOAO1 小时前
inmp+discuz论坛
linux·运维·服务器
星光璀璨山河无恙1 小时前
【Linux】grep命令
大数据·linux
寒月6581 小时前
黑盒白盒测试
运维·服务器
I love this bad girl1 小时前
防火墙旁挂部署+故障切换
服务器·网络·数据库
echo爱学易语言1 小时前
Linux ufw命令丨Linux网络防火墙ufw命令详解
服务器·网络·数据库
海绵波波1071 小时前
zerotier实现内网穿透(访问内网服务器)
运维·服务器
CL_IN1 小时前
金蝶云星空与华为云AX无缝数据集成技术详解
服务器·前端·华为云
helpme流水1 小时前
使用秘钥登录服务器
运维·服务器·github
ccino .1 小时前
【实现多网卡电脑的网络连接共享】
运维·服务器