当前位置: 首页 > news >正文

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。%CPU:CPU占用百分比。%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):已停止(通常由信号 SIGSTOPCtrl+Z导致)。Z(Zombie):僵尸进程(进程已终止,但父进程尚未回收其资源)。<:高优先级。N:低优先级。s:会话首进程(session leader)。l:多线程进程。+:位于前台进程组。

初始fork--通过系统调用创建进程

基本功能:fork()通过复制调用进程(父进程)创建一个新的子进程。子进程是父进程的副本,拥有相同的代码、数据段、堆栈、文件描述符等(但实际采用​​写时拷贝技术优化性能)。调用一次,返回两次:父进程中返回子进程的PID(>0)。子进程中返回 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) {// 子进程尝试修改 xx = 100;  // 触发 COW,内核复制该页printf("Child: x = %d\n", x);  // 输出 100} else {// 父进程的 x 仍然是 42printf("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进行分流

关键特性:

写时复制:子进程共享父进程的内存空间,仅当任一进程尝试修改内存时,内核才会复制相关部分,减少开销。

独立运行​​:子进程与父进程并发执行,调度顺序由内核决定。

继承资源​​:子进程继承父进程的文件描述符、信号处理方式、环境变量等。

http://www.dtcms.com/a/403970.html

相关文章:

  • C语言自学--数据在内存中的存储
  • 石家庄网站制作哪家好wordpress 优化数据库
  • 《基于Qt的车载系统项目》
  • 有哪些免费推广软件网站seo推广排名
  • 41.传输层协议UDP
  • 优良的定制网站建设提供商c2c模式的网站
  • 记力扣2516.每种字符至少取k个 练习理解
  • 广州站电话科创纵横 网站建设
  • 进程与集群:提升性能
  • 北京建设信源官方网站如何让wordpress文本小工具支持php和简码?
  • NLP算法岗位面试题精讲:深入理解LoRA与QLoRA
  • 基于神经控制微分方程的采集无关深度学习用于定量MRI参数估计|文献速递-文献分享
  • 无锡嘉饰茂建设网站的公司天河区网站制作
  • 应用程序映像(Application Image)是什么?
  • 访问的网站显示建设中wordpress tag伪静态
  • 单调速率调度(RMS)算法
  • 百度智能云一念·智能创作平台
  • 做网站订阅号丰台建设企业网站
  • shell编程:sed - 流编辑器(2)
  • 在Grafana中配置MySQL数据源并创建查询面板
  • 做的比较好的二手交易网站有哪些小学学校网站建设计划书
  • OneSignal v2 PHP手搓请求消息推送-供参考
  • 中国建站公司wordpress主题 下单
  • Qt DPI相关逻辑
  • 约束优化问题的常用解决办法及优缺点、轨迹规划中应用
  • 电子元器件基础知识day1
  • 【C++游记】C++11特性
  • 光子、光量子、量子三者的关系
  • 网站更改目录做301承德信息网络有限公司
  • Pytorch中stack()方法的总结及理解