Linux学习笔记(六)--Linux进程概念

张开发
2026/4/12 0:02:57 15 分钟阅读
Linux学习笔记(六)--Linux进程概念
前言:冯诺依曼体系:核心定义:​​冯·诺依曼体系结构​​也称为​​普林斯顿结构​​是一种将​​程序指令存储器和数据存储器合并在一起​​的计算机设计概念。它由美籍匈牙利数学家​​约翰·冯·诺依曼​​及其团队在1945年的一份报告中首次明确提出。冯诺依曼体系结构组成:冯诺依曼体系结构主要由五大核心部分组成,下面为大家一一介绍它们.运算器:负责执行所有的算术运算和逻辑运算(核心部件:算术逻辑单元)控制器:是整个计算机的指挥中心。它从存储器中读取指令进行分析然后发出控制信号协调其他所有部件有序工作。(与运算器的关系:运算器和控制器共同构成了现代计算机的​​中央处理器​​。)存储器:用于存储​​程序​​和​​数据​​。程序是指令的集合数据是程序处理的对象。(内存)输入设备:将外部的原始数据、程序或命令输入到计算机中。(例子​​键盘、鼠标、扫描仪等。)输出设备:将计算机处理后的结果以人们能够理解的形式呈现出来。(例子​​显示器、打印机、音响等。)运算器和存储器的集合称为CPU.这五大部件通过​​总线(系统总线和IO总线)​​连接在一起进行数据和指令的传输。冯诺依曼体系结构示例图:两用户通过QQ聊天整个数据是如何流动的(忽略网络情况)?两个用户分别是两个冯诺依曼体系结构首先用户1通过键盘输入信息,键盘将输入的字符转为数字信号,这些数据通过系统总线被存储到用户1的存储器(内存中),如何通过CPU处理信息后返回到内存中,然后处理好的数据通过总线发送到网卡中,再由用户2的网卡接收数据包,数据包通过总线传输到用户2的存储器中,再由用户B的CPU解包信息,将解包后的文字信息返回到存储器中,再从存储器通过总线发送到输出设备(显示器)上.这就是两台计算机在冯·诺依曼体系结构下协同完成一次即时通讯任务的经典过程。那么为什么需要存储器呢?为什么不能直接通过传递信息给CPU来直接发送信息?核心答案就是存储器解决了计算机内部不同部件之间巨大的“速度差”和“时序差”问题是保证高效、有序工作的关键。​我们可以从上面的图中看到输入设备​​如键盘和​​输出设备​​如显示器、网卡都不直接与CPU相连它们的所有数据交换都必须通过​​存储器​​这个中间环节。其中外设的运算速度远不及CPU,如果让CPU直接处理键盘输入或网络数据它99.999%的时间都在无所事事地“空等”效率极其低下。所以这个时候就需要我们存储器这个角色派上用场,它是这两个部件的高速缓存区,存储器的速度介于两者之间。外部设备可以慢慢地把数据写入存储器攒够一批后再通知CPU来快速处理。操作系统:概念:任何计算机系统都包含一个基本的程序集合称为操作系统(OS)。笼统的理解操作系统包括内核进程管理内存管理文件管理驱动管理和其他程序例如函数库shell程序等等目的:(1)将复杂的硬件操作细节隐藏起来提供简单统一的接口如“打开文件”、“打印文档”让用户和程序员无需关心底层硬件具体如何实现。(2)作为资源管理器公平、高效地分配和调度有限的硬件资源如CPU时间、内存空间、磁盘空间、I/O设备给多个程序和用户。(3)防止多个同时运行的程序相互干扰保护系统进程和用户数据不被非法访问或破坏。计算机层状体系结构:下面是一张计算机层状体系结构的图片:计算机层状体系结构从上至下可以分为三层,它们分别是用户部分,系统软件部份,硬件部分.各层的功能以及组件的功能如下:用户层这是所有操作的起点和终点​​。用户可以是正在使用计算机的人也可以是另一个正在请求服务的系统。用户不直接与硬件打交道而是通过下面的层层接口来使用计算机。用户操作接口层包含 “shell 外壳”提供命令行交互界面、“lib库”提供函数等资源支持、“部分指令”负责接收并初步处理用户操作请求为后续与系统调用交互做准备。系统调用接口层作为用户操作接口与操作系统之间的桥梁将用户或上层的请求转换为操作系统能识别的调用形式实现请求的传递。系统调用就像是一家公司对外的“服务窗口”普通员工应用程序不能直接进入仓库硬件或指挥经理操作系统必须通过填写标准化申请表系统调用来提出正式请求。操作系统层承担 “内存管理”分配、回收内存等、“进程管理”进程的创建、调度、终止等、“文件管理”文件的存储、读写、组织等、“驱动管理”管理各类设备驱动程序等核心系统管理功能是系统软件的核心对硬件资源进行抽象和管理。驱动程序层包含 “网卡驱动”“硬盘驱动”“其他驱动”负责将操作系统的请求转换为硬件设备能理解的命令实现硬件设备的具体控制是操作系统与底层硬件之间的 “翻译官”。底层硬件层由 “网卡”“硬盘”“其他如 CPU、内存芯片等” 组成是计算机系统的物理基础执行最底层的硬件操作如数据的物理存储、网络数据的收发等。接口与操作系统的关系:接口是操作系统对外提供的服务窗口和边界是用户和应用程序访问操作系统核心功能的唯一安全通道。​进程:在上述的计算机层状体系结构图中,我们可以将一个程序比作一个菜谱,它是一堆静态的指令和数据.而进程就像一个厨师来按照食谱炒菜的这一个过程,它是一个动态的执行过程包含了所用的食材数据、厨房灶具CPU、内存等资源、以及炒菜的每个步骤执行流程。定义:进程是计算机中的程序关于某个数据集合的一次运行活动​​是​​系统进行资源分配和调度的基本单位​​。(它是“执行中的程序”​​程序是静态的、存储在磁盘上的文件而进程是动态的、被加载到内存中并正在执行的程序实例。它是“资源分配的单位”​​操作系统以进程为单位来分配CPU时间、内存空间、文件句柄等系统资源。)为什么需要进程?因为为了让我们可以一次性执行多个程序,保证每个程序都有独立的内存空间不会互相干扰,所以操作系统通过创建一个个独立的进程来实现​​并发执行​​、​​资源隔离​​和​​公平调度​​。进程控制块(PCB(process control block))任何一个进程,在加载到内存中的时候,形成真正的进程时,操作系统要先创建描述进程的结构体对象,这个结构体对象就叫做PCB(进程控制块).这是操作系统​​管理和控制进程的核心数据结构​​。每个进程都有一个唯一的PCB。操作系统通过PCB来感知进程的存在。在Linux操作系统下的PCB叫做task_struct.PCB记录了:进程标识符PID​​唯一的身份证号码。进程状态​​运行/等待/就绪优先级: 相对于其他进程的优先级。程序计数器PC​​下一条要执行的指令的地址。​​CPU寄存器​​进程被切换时需要保存当前的运行现场如累加器的值以便下次能接着执行。内存管理信息​​记录了程序代码、数据、堆栈在​​内存​​中的位置基址、界限寄存器等。​​这实现了进程间的内存隔离。​​​​I/O状态信息​​进程所占用的​​I/O设备​​列表、打开的文件列表等。进程 内核PCB数据结构对象 自己的代码和数据Linux如何组织进程?进程关系组织进程树:Linux 中的所有进程构成了一棵​​树形结构​​这棵树的根是 ​​init进程PID 1​​通常是systemd或init。组织方式​​每个task_struct中都包含指向其亲属的指针(1)parent指向父进程的task_struct。(2)children链表头链接所有子进程的task_struct。(3)sibling链表节点用于将本进程链接到兄弟进程的链表中。如何查看​​使用pstree命令可以清晰地看到这棵进程树。目的:(1)资源继承​​子进程继承父进程的某些属性如打开的文件描述符。(2)进程管理​​父进程可以通过wait()系统调用来等待和获取子进程的退出状态防止其成为“僵尸进程”。所有进程的全局组织任务列表:内核需要一种方式来跟踪​​所有的进程​​这是通过一个名为 ​​init_task​​ 的​​双向循环链表​​实现的。组织方式​​每个task_struct都包含一个tasks链表节点struct list_head tasks。所有的task_struct通过这个节点连接成一个巨大的循环链表。(init_task是这个链表的头节点一个特殊的“0号进程”。)目的:(1)全局遍历​​当需要遍历系统中的所有进程时例如ps命令或killall命令的实现内核会遍历这个任务列表。(2)进程查找​​通过 PID 查找进程时也可以辅助遍历虽然有更高效的哈希表优化。高效的 PID 到进程的查找PID 哈希表:通过 PID一个数字快速找到对应的task_struct是一个常见操作。如果每次都遍历任务列表效率极低。组织方式​​内核维护了多个 ​​PID 哈希表​​。(这是一个哈希数据结构将 PID 作为键将对应的task_struct地址作为值。当需要根据 PID 查找进程时内核计算 PID 的哈希值直接定位到对应的哈希桶从而极快地找到目标task_struct。)目的:实现 ​​O(1)​​ 时间复杂度的 PID 查找极大提升性能例如发送信号kill(pid)时就需要这个操作。查看进程信息:进程的信息可以通过 /proc 系统文件夹查看,大多数进程信息同样可以使用top和ps这些用户级工具来获取(1)ps功能​​显示​​当前时刻​​的进程状态。它提供的是执行命令那一刻的快照。常用组合​​ps aux​​最常用​​显示所有用户的所有进程详细信息。a显示所有用户的进程。u以用户为主的格式显示。x显示没有控制终端的进程通常是后台进程、守护进程。关键列:USER进程所有者。 PID进程ID。 %CPUCPU占用百分比。 %MEM内存占用百分比。 VSZ虚拟内存大小。 RSS常驻内存集大小实际物理内存。 TTY进程所在的终端。?表示与终端无关。 STAT进程状态非常重要见下文解释。 START进程启动时间。 TIME进程占用CPU的总时间。 COMMAND启动进程的命令。 ps -ef以完整格式列表显示所有进程。 -e显示所有进程。 -f显示完整格式UID, PID, PPID等。STAT状态码解析:R(Running)正在运行或可运行在运行队列中。 S(Sleeping)睡眠中可被中断等待事件完成。 D(Disk Sleep)不可中断的睡眠通常是在等待I/O不能用kill杀死。 T(Stopped)已停止通常由信号 SIGSTOPCtrlZ导致。 Z(Zombie)僵尸进程进程已终止但父进程尚未回收其资源。 高优先级。 N低优先级。 s会话首进程session leader。 l多线程进程。 位于前台进程组。初始fork--通过系统调用创建进程基本功能:fork()通过复制调用进程父进程创建一个新的子进程。子进程是父进程的副本拥有相同的代码、数据段、堆栈、文件描述符等但实际采用​​写时拷贝技术优化性能。调用一次返回两次父进程中返回子进程的PID0。子进程中返回0。失败时返回-1如进程数达到上限。为什么fork要给子进程返回0,给父进程返回子进程的PID?原因:为了​​明确区分父子进程的执行逻辑​​并提供一种简单可靠的进程控制机制。子进程返回0​​子进程需要知道自己是被fork()创建的且可以通过返回值0明确识别自己的身份。这样子进程可以执行与父进程不同的代码逻辑例如调用exec()加载新程序。父进程返回子进程的PID​​父进程需要知道子进程的PID以便后续管理如通过wait()回收资源、发送信号等。如果返回0父进程将无法区分自己是父进程还是子进程。一个函数是如何做到返回两次的?因为在fork中,并不是函数本身真的返回了两次而是因为 ​​fork调用后系统创建了一个与原进程几乎完全相同的子进程​​两个进程父进程和子进程会​​各自​​从fork调用处继续执行并分别得到不同的返回值。写时拷贝:写时拷贝是 Linux 内核中用于优化进程创建和内存管理的关键技术。它通过​​延迟内存复制​​来提高性能减少不必要的开销。作用:当调用fork()创建子进程时传统方式会​​立即复制父进程的所有内存​​但这会导致大量无用的内存拷贝,而写时拷贝优化了这一过程,具体分为三个部分:(1)父子进程共享相同的内存页​​代码段、数据段、堆栈等。(2)仅当某一进程尝试修改内存时内核才真正复制该页​​使父子进程拥有独立的副本。(3)如果子进程不修改内存则永远不会复制​​节省时间和内存。写时拷贝工作流程:(1)fork()调用时​​内核​​仅复制进程的页表​​指向相同物理内存并标记所有内存页为 ​​COW​​只读。父子进程的代码、数据、堆栈等仍然共享同一份物理内存。​​(2) 写操作触发时​​当​​父进程或子进程尝试写入某个内存页​​时CPU 会触发​​页错误。内核检测到该页是 ​​COW​​ 页于是(1)分配新的物理内存页​​。(2)​​复制旧页内容到新页​​。(3)更新进程的页表​​使其指向新页。(4)恢复进程执行​​此时写入操作在新页上进行不影响另一个进程。#include unistd.h #include stdio.h int main() { int x 42; // 父子进程共享该变量COW pid_t pid fork(); if (pid 0) { // 子进程尝试修改 x x 100; // 触发 COW内核复制该页 printf(Child: x %d\n, x); // 输出 100 } else { // 父进程的 x 仍然是 42 printf(Parent: x %d\n, x); // 输出 42 } return 0; }基本用法:#include unistd.h #include stdio.h int main() { pid_t pid fork(); if (pid -1) { perror(fork failed); return 1; } else if (pid 0) { printf(Child process (PID: %d)\n, getpid()); } else { printf(Parent process (PID: %d), Child PID: %d\n, getpid(), pid); } return 0; }fork 之后通常要用if进行分流关键特性:写时复制子进程共享父进程的内存空间仅当任一进程尝试修改内存时内核才会复制相关部分减少开销。独立运行​​子进程与父进程并发执行调度顺序由内核决定。继承资源​​子进程继承父进程的文件描述符、信号处理方式、环境变量等。

更多文章