系统核心解析:深入操作系统内部机制——进程管理与控制指南(一)【进程/PCB】


♥♥♥~~~~~~欢迎光临知星小度博客空间~~~~~~♥♥♥

♥♥♥零星地变得优秀~也能拼凑出星河~♥♥♥

♥♥♥我们一起努力成为更好的自己~♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

♥♥♥如果有什么问题可以评论区留言或者私信我哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨

        前面我们已经学习了系统的前置知识【冯诺依曼模型/操作系统】,这一篇博客我们开始系统的核心解析——进程!准备好了吗~我们发车去探索进程的奥秘啦~🚗🚗🚗🚗🚗🚗

目录

进程概念😍

通俗概念:执行中的程序😝

内核观点:系统资源的分配实体😄

核心本质:PCB(进程控制块)😜

进程排队与调度😎

task_struct😘

查看进程😁

创建进程😀


进程概念😍

通俗概念:执行中的程序😝

    一般教材里面对于进程是这样定义的——“运行起来的程序,加载到内存的程序”; “程序的一个执行实例,正在执行的程序”~

    这是最直观的理解。一个安静的、躺在磁盘上的可执行文件(如 a.out)只是一个程序(Program)。而当你双击运行它,系统将其代码和数据加载到内存并开始执行时,它就成为了一个进程(Process)。

    关键区别 程序静态(就像一本食谱),而进程动态(就像你按照食谱做饭的整个过程)。

内核观点:系统资源的分配实体😄

    从内核来看,进程是“担当分配系统资源(CPU时间,内存)的实体。”

    这是操作系统的视角,操作系统对下的主要工作是管理软硬件资源,而进程就是操作系统进行资源分配的基本单位。每个进程在运行时,都需要被分配内存来存放其代码和数据,需要占用CPU时间来执行指令,操作系统就像一个经理,而进程就是需要资源来完成工作的员工。

        我们上一篇博客提到操作系统进行管理的时候是先描述再组织~那么对于大量进程的管理也是一样的~

核心本质:PCB(进程控制块)😜

    操作系统如何管理这么多复杂的进程?答案就是:“先描述,再组织”。

        先描述(Describe)操作系统为每一个进程创建一个名为PCB【进程控制块】(Process Control Block) 的数据结构(在Linux中具体叫 task_struct)。这个结构体就像是进程的“简历”或“身份证”,包含了进程的所有属性信息,例如:

            进程ID(PID): 唯一标识一个进程的数字。

            进程状态(运行、睡眠、停止等)。

            程序计数器(PC): 指向下一条要执行的指令地址。

            内存指针: 指向代码和数据的地址。

            文件描述符表: 记录该进程打开的文件。

            优先级、上下文数据等。

        再组织(Organize)操作系统将所有进程的PCB(task_struct)通过链表等数据结构组织起来。这样,对进程的管理(如创建、销毁、调度、切换)就变成了对这条PCB链表的增删改查操作。

    因此,一个进程的实体 = 对应的代码和数据 + 对应的PCB数据结构

进程排队与调度😎

       由前面的概念我们可以知道,进程排队本质其实就是让对应的PCB节点进行排队。CPU的数量是远少于进程的数量,因此进程需要排队等待CPU资源。进程排队不是整个程序本身去排队,而是它的PCB去排队。操作系统调度器只关心PCB链表,它从运行队列中选中一个PCB,也就找到了这个进程的所有信息,从而能够恢复并执行它~

总结进程是正在执行的程序,它是操作系统进行资源分配的基本单位,一个进程的实体 = 对应的代码和数据 + 对应的PCB数据结构。操作系统通过一个叫做PCB(Linux中为task_struct)的数据结构来描述进程,并通过管理所有PCB组成的链表来高效地组织和管理所有进程。

task_struct😘

        前面我们提到了操作系统为每一个进程创建一个名为PCB【进程控制块】(Process Control Block) 的数据结构(在Linux中具体叫 task_struct),包含了进程的所有属性信息。那么我们来看看Linux中task_struct具体会包含些什么呢?

标示符(PID)

        作用:每个进程独一无二的身份证号

        说明:通过这个ID,系统内核和用户(通过命令)才能精确地找到并操作某一个特定的进程。

状态

        作用:描述进程当前的生命周期状态(如:运行中、睡眠中、停止、僵尸状态等)。

        说明:这是进程调度的关键依据。操作系统根据状态决定是否给这个进程分配CPU资源。

优先级

        作用:决定进程获取CPU资源执行的先后顺序

        说明:优先级高的进程会更容易被CPU调度,从而获得更多的执行时间。

程序计数器

        作用:保存着这个进程即将要执行的下一条指令的内存地址

        说明:当进程被CPU切换出去又切换回来时【调度切换】,就靠这个值来恢复到上次的执行现场,继续运行。

内存指针

        作用:指向该进程的代码、数据以及与其他进程共享的内存区域

        说明:这些指针定义了进程在内存中的“地盘”,确保了进程能正确地访问自己的指令和数据。

上下文数据

        作用:保存进程在CPU寄存器中的数据(当进程被切换出CPU时)

        说明:这是实现进程切换的核心。进程被换下CPU前,必须把当前所有寄存器的值保存到它的task_struct中;当它再次被调度时,再把这些值重新加载到寄存器,就能毫不停顿地继续执行。就像大学生参军入伍需要办理休学把学习进度都记录下来,回来时才能接着学~

 I/O状态信息

        作用:记录进程占用的I/O设备、发出的I/O请求以及打开的文件列表

        说明:用于管理进程的所有输入输出操作,确保资源被正确分配和释放。

记账信息

        作用:统计进程使用的CPU时间、资源情况等

        说明:用于系统监控、性能分析和计费。

其他信息(后续博客会继续介绍)

查看进程😁

        通过前面我们知道了什么是进程?现在我们来看看查看进程~首先我们了解一下getpid()getppid()这两个在 Linux/Unix 系统编程中用于获取进程标识符Process ID)的基础函数~

特性getpid()getppid()
功能获取当前进程的 PID获取当前进程的父进程的 PID
头文件#include <unistd.h>#include <unistd.h>
返回值pid_t(当前进程的 PID)pid_t(父进程的 PID)
关键点唯一标识一个进程

可能变化(如果父进程先结束)

📌 通过 /proc 文件系统查看进程信息

在 Linux 中,每个正在运行的进程都会在 /proc 目录下有一个以进程 ID(PID)命名的子目录,其中包含该进程的详细信息。

除此之外,我们一般还会使用ps 命令用法,用于查看与特定关键词相关的进程信息~

📌 命令行输入

ps ajx | head -1 ; ps ajx | grep "code"

这个命令实际上由两个独立命令组成,用分号 ; 分隔:

    ps ajx | head -1

        ps ajx: 使用 BSD 风格的选项显示详细的进程信息

            a: 显示所有用户的进程

            j: 显示作业格式(包含 PGID、SID 等信息)

            x: 显示没有控制终端的进程

        | head -1: 只保留输出结果的第一行(即标题行)

    ps ajx | grep "code"

        再次运行 ps ajx 获取所有进程信息

        使用 grep "code" 过滤出包含 "code" 字符串的进程

命令执行后显示了两部分结果:

标题行(来自第一个命令):

PPID   PID PGID SID TTY      TPGID STAT UID TIME COMMAND

对于各列的含义,我们先简单了解一下:

    PPID: 父进程ID

    PID: 进程ID

    PGID: 进程组ID

    SID: 会话ID

    TTY: 控制终端

    TPGID: 终端前台进程组ID

    STAT: 进程状态

    UID: 用户ID

    TIME: 累计CPU时间

    COMMAND: 命令名称

匹配的进程(来自第二个命令):

10379 13543 13543 10379 pts/O    13543 S+    1001 0:00 ./code
12279 13598 13597 12279 pts/1    13597 S+    1001 0:00 grep --color=auto code

第一行是名为 ./code 的进程,第二行是 grep 命令本身(因为它也包含 "code")~

我们可以发现重新生成可执行程序,两次运行可执行程序的pid是不一样的,但是ppid是一样的~

为什么pid每次都不一样?

   pid(进程ID)是操作系统为每个新进程分配的唯一标识符, 每次运行程序时,系统都会创建一个新的进程实例。操作系统使用pid来管理和区分不同的进程。由于pid是动态分配的,且系统需要保证唯一性,所以每次运行程序时都会获得一个不同的pid。

 为什么ppid两次都一样?

    ppid(父进程ID)表示创建当前进程的父进程的ID。ppid始终是10379,这意味着两次运行都是在同一个父进程中启动的。当我们在终端中执行./code命令时,终端Shell(bash)会创建子进程来运行程序。因此,无论运行多少次,这些子进程的父进程都是同一个Shell进程(pid: 10379)。

我们来验证一下父进程的身份,命令行输入:

ps ajx | head -1 && ps axj | grep 10379

所以我们在命令行中启动命令/程序的时候都会变成进程,该进程的父进程是bash!由子进程来运行命令/程序~

那么我们可以知道Linux系统是通过父进程创建子进程的方式来增多进程!!!

创建进程😀

        我们知道Linux系统是通过父进程创建子进程的方式来增多进程!那么父进程是怎么创建子进程的呢?通过前面我们知道进程= 代码和数据 + PCB。创建进程我们就需要介绍一个Linux系统中的系统调用fork()~

我们可以使用man fork来简单看一下:

我们首先试用一下fork(),来看下面一段代码:

你们可以猜测到运行结果是什么吗?

见证奇迹的时刻到了,我们可以发现打印了两次这是一条消息~接下来我们慢慢分析

fork之后,打印了两次这是一条消息,说明不仅仅是一个进程执行了这条语句,一个是父进程,那么另外一个就是我们的子进程~

fork() 系统调用(system call)的核心思想:复制自己

        当一个进程(我们称之为父进程)调用 fork() 时,操作系统会做这样一件事:几乎原封不动地复制当前进程,从而创建一个新的进程(我们称之为子进程)。

这个“复制”包括:

    代码段:和父进程一模一样。

    数据段:堆、栈、初始化的数据等,内容相同但位于不同的物理内存中。

    进程上下文:打开的文件描述符、信号处理方式、进程状态等。

我们来验证一下:修改代码

代码运行结果:

同时fork()系统调用还有一个核心特性:

一次调用,两次返回

这是 fork 最神奇也最容易让人困惑的地方(怎么会有两个返回值呢):

    在父进程中,fork() 调用返回的是新创建的子进程的进程ID(PID)(一个大于0的数字)

    在子进程中,fork() 调用返回 0。

通过返回值,程序可以判断自己当前是父进程还是子进程,从而执行不同的分支代码。

我们同样可以通过一段代码来验证一下:

果不其然,结果确实如此~

这不禁让人困惑,怎么会有两个返回值呢?

        事实上不是“一个函数返回两次”,严格来说,fork() 系统调用只执行了一次(创建子进程),但它的返回过程发生了两次——分别在两个不同的进程中

        return 的本质是写入,当函数执行到return的时候,主体功能已经完成了,从内核返回用户态的本质,就是将内核准备好的“返回值”写入到用户空间那个等待接收返回值的变量(id)中,操作系统为两个进程写入了不同的值

特性父进程子进程
诞生方式由用户启动fork() 系统调用创建
fork() 返回值子进程的 PID (>0)0
执行起点fork() 调用之后继续执行fork() 调用之后开始执行
关系调用者被创建者

    神奇的“分身术”比喻:
    可以把 fork() 调用点想象成一个“分身点”。

        父进程执行到此处,喊了一声“分身!”(系统调用)。

        操作系统内核施展分身术,创造了一个一模一样的子进程。

        法术完成后:

            父进程听到耳边传来一句话:“你的分身ID是 1234”。

            子进程一诞生,听到的第一句话是:“你是分身,你的编号是0”。

        然后,父子两人都从“分身!”这句话之后开始继续各自的人生(执行后续代码),并且都记住了自己听到的那句话(不同的返回值)。

现在我们只是创建了一个子进程,如果我现在创建更多的子进程呢?

事实上,由于进程调度顺序不确定,父进程和子进程的输出可能会交错出现,也就是父子进程的运行顺序是不确定的~

总结

        fork是Unix/Linux创建新进程的核心系统调用。其核心机制是“复制父进程”:内核以父进程为模板,初始化子进程的控制结构(task_struct),并默认通过“写时复制”(Copy-On-Write)技术让父子进程高效共享相同的代码和数据页,仅在需要修改时才创建副本,从而保证进程数据独立性。fork的神奇之处在于“一次调用,两次返回”:在父进程中返回子进程的PID,在子进程中返回0。通过判断返回值,父子进程可执行不同代码分支,这是实现并发任务和“fork+exec”执行新程序的基础。它高效地实现了进程隔离与资源共享的平衡。


♥♥♥本篇博客内容结束,期待与各位优秀程序员交流,有什么问题请私信♥♥♥

♥♥♥如果这一篇博客对你有帮助~别忘了点赞分享哦~♥♥♥

✨✨✨✨✨✨个人主页✨✨✨✨✨✨


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。
如若转载,请注明出处:http://www.pswp.cn/bicheng/96790.shtml
繁体地址,请注明出处:http://hk.pswp.cn/bicheng/96790.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

微论-神经网络特征空间的动态聚集,对抗灾难性遗忘的新范式

这是一个非常有趣且富有想象力的理论构想。受陀螺仪启发&#xff0c;我将陀螺仪的“定轴性”与“进动性”原理引入神经网络的特征空间&#xff0c;探讨一种对抗灾难性遗忘的新范式。---### **基于陀螺仪原理的神经网络记忆巩固理论探讨**#### **引言&#xff1a;记忆的流失与稳…

鸿蒙审核问题——折叠屏展开态切换时,输入框内容丢失

文章目录背景解决历程1、无意中发现了眉目2、确定问题原因3、解决办法4、官方文档5、总结背景 奇葩的事情年年有啊&#xff0c;今年特别多。这不今天又遇到了一个奇葩的问题。鸿蒙NextAPP上架AppGallery市场&#xff0c;审核拒了&#xff0c;说是折叠屏手机展开态切换时&#…

前后端分离架构中,Node.js的底层实现原理与线程池饥饿问题解析

在VueJava/.NET的前后端分离架构中&#xff0c;Node.js的底层实现原理与线程池饥饿问题解析 一、架构概述&#xff1a;Node.js的定位与角色 在现代Web开发中&#xff0c;Vue.js作为前端框架与Java/.NET后端结合的架构非常流行。在这种架构中&#xff0c;Node.js通常扮演着两个关…

Django ModelForm:快速构建数据库表单

Django 中的 forms.ModelForm —— 它是 Django 表单系统和 ORM 的一个“桥梁”&#xff0c;能帮助你快速基于 数据库模型&#xff08;Model&#xff09; 自动生成表单&#xff0c;极大减少重复代码。1. 什么是 ModelForm 普通 Form (forms.Form)&#xff1a;完全手写字段&…

补 json的作用

&#xff1a;“我开车直接拧钥匙就能走&#xff0c;为什么还要看仪表盘和用中控台&#xff1f;”直接点击“运行”&#xff0c;就像是汽车的自动驾驶模式。它能帮你开起来&#xff0c;但你不知道它走的是哪条路&#xff0c;油门踩多深。使用 launch.json 配置&#xff0c;就像是…

apache详细讲解(apache介绍+apache配置实验+apache实现https网站)

1.apache HTTP server介绍httpd项目地址:https://httpd.apache.org/ 在Apache2中有三种工作模式&#xff0c;使用者可以根据不同的业务场景来进行选择(1)prefork模式prefork模式是一种老而稳的模式:一个主进程管理者多个子进程&#xff0c;每个子进程单独处理用户请求&#xf…

jajajajajajajava

线程1 线程概念进程:进程指正在内存中运行的程序。进程具有一定的独立性。线程:线程是进程中的一个执行单元。负责当前进程中程序的执行。一个进程中至少有一个线程。如果一个进程中有多个线程&#xff0c;称之为多线程程序。java中的线程采用的是抢占式调度&#xff0c;如果线…

虚拟机CentOS里JDK的安装与环境配置

---本文以JDK17为例---步骤 1&#xff1a;进入/tmp临时目录# 进入临时目录 cd /tmp步骤 2&#xff1a;下载 Java 17 安装包wget https://github.com/adoptium/temurin17-binaries/releases/download/jdk-17.0.9%2B9/OpenJDK17U-jdk_x64_linux_hotspot_17.0.9_9.tar.gz步骤 3&am…

mybatis-plus多租户兼容多字段租户标识

默认租户插件处理器的缺陷 在springboot工程中引入mybatis-plus的租户插件TenantLineInnerInterceptor&#xff0c;能简化我们的数据隔离操作&#xff0c;例如各类含租户用户登录权限的rest接口中&#xff0c;不需要再根据登录用户-set租户条件-触发查询&#xff0c;租户插件能…

HBase高级特性(布隆过滤器和协处理器)、列族设计、rowkey设计以及热点问题处理

在阐述HBase高级特性和热点问题处理前&#xff0c;首先回顾一下HBase的特点&#xff1a;分布式、列存储、支持实时读写、存储的数据类型都是字节数组byte[]&#xff0c;主要用来处理结构化和半结构化数据&#xff0c;底层数据存储基于hdfs。 同时&#xff0c;HBase和传统数据库…

redis sentinel 与 clauster 的区别

Redis Sentinel(哨兵)和Redis Cluster(集群)是Redis提供的两种不同的高可用和扩展性解决方案,它们的设计目标和适用场景有显著区别: 1. 核心功能与目标 Redis Sentinel 主要解决主从架构的高可用问题,实现自动故障转移 监控主从节点状态,当主节点故障时自动将从节点提…

MySQL数据库中快速导入大数据sql

1.PwerShell命令页面导入全表数据库 -P3310 指定数据库端口号Get-Content "本地sql文件目录" | .\mysql -u root -p -P 33102.PwerShell命令页面导入单表到数据库 -P3310 指定数据库端口号Get-Content "本地sql文件目录" | .\mysql -u root -p -P 3310 数…

消息类型proto的编写和生成

消息类型proto的编写和生成 代码如下&#xff1a; syntax"proto3"; package xypmq;enum ExchangeType {UNKNOWNTYPE0;DIRECT1;FANOUT2;TOPIC3; };enum DeliveryMode {UNKNOWNMODE0;UNDURABLE1;DURABLE2; };message BasicProperties {string id1;DeliveryMode deliver…

Vuetify:构建优雅Vue应用的Material Design组件库

Vuetify是一个基于Material Design设计规范的Vue.js UI组件库&#xff0c;它提供了80多个精心设计的组件&#xff0c;帮助开发者快速构建美观且功能丰富的企业级应用。核心特性1. 完整的Material Design实现// 所有组件遵循Material Design规范 <v-btn color"primary&q…

SpringBoot 注解深剖:@RequestParam 与 @RequestBody 的终极对决,90% 的开发者都踩过这些坑!

在 SpringBoot 开发中&#xff0c;处理 HTTP 请求参数是我们每天都要面对的工作。而RequestParam和RequestBody这两个注解&#xff0c;就像是我们手中的两把利剑&#xff0c;既能高效解决问题&#xff0c;用不好也可能 "误伤" 自己。作为一名资深 Java 开发者&#x…

【Docker】P2 Docker环境构建准备:MacOS 与 Linux

目录操作系统与 Docker 的兼容性分析Docker 技术本质MacOS 环境下的 Docker 构建1. 安装前准备2. Docker Desktop安装3. 镜像加速配置高级操作&#xff1a;文件共享配置Linux 环境下的 Docker 构建卸载历史版本配置软件源Docker 核心组件安装系统服务配置镜像加速器配置应用配置…

OpenCV 发票识别全流程:透视变换与轮廓检测详解

目录 前言 一、核心技术原理&#xff1a;透视变换与轮廓检测 1. 透视变换&#xff1a;让倾斜发票 “正过来” &#xff08;1&#xff09;什么是透视变换&#xff1f; &#xff08;2&#xff09;透视变换的 5 个关键步骤 2. 轮廓检测&#xff1a;精准定位发票区域 &#x…

并发:使用volatile和不可变性实现线程安全

《Java并发编程实战》中的VolatileCachedFactorizer展示了如何使用volatile和不可变性来实现线程安全。解决了简单缓存实现中可能出现的线程安全问题&#xff0c;同时避免了全量同步带来的性能开销。 场景背景 假设有一个服务&#xff08;如因数分解服务&#xff09;&#xff0…

Linux x86 stability和coredump

1 POSIX pthread_create原理 1&#xff09;fork()、pthread_create()、vfork()对应的系统调用分别是sys_fork()、sys_clone()、sys_vfork()&#xff0c;它们在内核中都是通过do_fork()实现的。 2&#xff09;系统中所有的进程都组织在init_task.tasks链表下面&#xff0c;每个进…

【PyTorch】多对象分割

对象分割任务的目标是找到图像中目标对象的边界。实际应用例如自动驾驶汽车和医学成像分析。这里将使用PyTorch开发一个深度学习模型来完成多对象分割任务。多对象分割的主要目标是自动勾勒出图像中多个目标对象的边界。 对象的边界通常由与图像大小相同的分割掩码定义&#xf…