首页
关于
友链
推荐
肥啾解析
百度一下
肥啾GPT
Search
1
宝塔面板登录 phpMyAdmin 提示服务器和客户端上指示的HTTPS之间不匹配
274 阅读
2
Customer complaints evolve with in-car tech
188 阅读
3
JavaScript解析
153 阅读
4
内连接,左连接,右连接作用及区别
112 阅读
5
所谓关系
109 阅读
默认分类
网游架设
手机游戏
python
PHP
Mysql
VBA
C++
JAVASCRIPT
javascript基础
Oracle
生产管理
计划控制
ERP系统开发
APS排产
MES研究
考勤系统
CPA
财管
实务
经济法
战略
审计
税法
藏书架
古典名著
世界名著
编程秘籍
攻防渗透
经管书籍
大佬传经
风雅读物
考试相关
心情格言
拾玉良言
外文报刊
外刊随选
Facebook
Twitter
China Daily
软考
登录
Search
标签搜索
期刊读物
古文
何瑜明
累计撰写
178
篇文章
累计收到
154
条评论
首页
栏目
默认分类
网游架设
手机游戏
python
PHP
Mysql
VBA
C++
JAVASCRIPT
javascript基础
Oracle
生产管理
计划控制
ERP系统开发
APS排产
MES研究
考勤系统
CPA
财管
实务
经济法
战略
审计
税法
藏书架
古典名著
世界名著
编程秘籍
攻防渗透
经管书籍
大佬传经
风雅读物
考试相关
心情格言
拾玉良言
外文报刊
外刊随选
Facebook
Twitter
China Daily
软考
页面
关于
友链
推荐
肥啾解析
百度一下
肥啾GPT
搜索到
37
篇与
的结果
2026-02-09
逻辑地址转换计算
在分页存储管理中,逻辑地址通过页表映射为物理地址。页面大小决定了地址的划分方式:页内偏移位数 = log2(页面大小)逻辑地址高位表示页号,低位表示页内偏移。假设:一个小区有10栋楼(物理块),每栋楼有100个房间(每页大小100)。你朋友住“3栋16室”(逻辑地址:页号3,偏移16)。但实际小区规划变了,3栋其实在物理上是第6栋(页表:页号3→块号6)。那么物理地址 = 第6栋第16室。页面大小4KB = 4096字节的十六进制是 0x1000(因为 4096 = 16^3 = 0x1000)。为什么是0x1000?十六进制每1位代表4位二进制,3位十六进制就是12位二进制,2^12=4096。所以页内偏移范围:0x000 到 0xFFF(共4096个位置)。逻辑地址 1D16H 怎么拆分?因为一页大小是 0x1000,所以:页号 = 地址 ÷ 0x1000(整除取商)偏移 = 地址 % 0x1000(取余数)1D16H ÷ 1000H = 1 ... D16H所以页号=1,偏移=D16H。为什么除1000H?因为每0x1000个地址换一页,就像每1000个房间换一栋楼。1D16H 在第1页(因为1xxx),偏移是D16。逻辑地址:1D16H物理地址:3D16H它们只有“页号部分”从1变成3(根据页表映射),偏移部分D16H完全不变。这就是分页内存管理的核心:逻辑地址 = 页号 + 页内偏移物理地址 = 物理块号 + 同一偏移偏移永远不变,因为页内位置在物理页中相同位置。{card-default label="十进制转换十六进制" width=""}十进制转十六进制方法(以题中相关数字为例):基本方法:连续除以16,倒取余数用十进制数不断除以16,记下每次的余数(0~15),直到商为0。将余数从最后一个到第一个排列,就是十六进制数。余数10~15分别用字母A~F表示。举例:十进制 45 转十六进制:÷ 16 = 2 余 13(13是D)÷ 16 = 0 余 2余数倒序:2、D → 2DH十进制 4096 转十六进制(本题页面大小):÷ 16 = 256 余 0÷ 16 = 16 余 0÷ 16 = 1 余 0÷ 16 = 0 余 1余数倒序:1、0、0、0 → 1000H{/card-default}CPU的访问速度通常遵循以下顺序(从快到慢):寄存器(Register):位于CPU内部,速度最快。高速缓存(Cache):分为L1、L2、L3等,L1最快,L2次之,L3再次之。内存(RAM):也称为主存,速度比缓存慢。硬盘(包括SSD和HDD):外存,速度远慢于内存。网络存储:通过网络访问的存储设备,速度最慢。需要注意的是,访问速度通常用纳秒(ns)或毫秒(ms)来衡量,并且随着技术的发展,具体数值会变化,但相对顺序基本不变。{card-default label="cache相关" width=""}Cache 工作三步曲:查书桌(Cache查找)当 CPU 需要读数据时:它先看看自己手边的“小书桌”(Cache)上有没有这本书。如果有(Cache命中),直接拿来看 —— 极快。如果没有(Cache未命中),就得去“大书库”(内存)里找 —— 慢很多。上书桌(Cache加载)如果数据不在 Cache 里:CPU 去内存把需要的数据“拿过来”。同时会把这本书连同它旁边的几本书一起放到书桌上(局部性原理:下次很可能用附近的数据)。如果书桌满了,就扔掉一本最久不看的(缓存替换策略,如LRU)。保持整洁(Cache一致性)当 CPU 修改了书桌上的数据:需要标记“这本书和书库里的不一样了”。之后要么马上把修改写回书库(写通),要么先记着,等换书时再写回(写回)。确保其他部件(如其他CPU核心)不会读到过期数据。Cache 是 CPU 和内存之间的“速度缓冲器”,利用局部性原理提前准备数据,让 CPU 绝大部分时间不用等内存。"地址映像是什么"。这通常指的是在计算机体系结构中,将主存地址映射到Cache地址的方式。地址映像是Cache组织中的一个核心概念,它决定了主存中的块可以被放置在Cache中的哪个位置。地址映像的定义:地址映像是指将主存地址空间映射到Cache地址空间的规则或方法。它决定了主存中的每一个数据块可以存放在Cache中的哪些位置。常见的地址映像方式有三种:直接映射:主存中的每个块只能被放置到Cache中唯一的一个特定位置。通常,Cache被分成若干行,主存块号对Cache行数取模来决定放置到哪一行。这种方法简单,但容易发生冲突(即使Cache其他行空闲,也可能因为两个主存块映射到同一行而发生替换)。全相联映射:主存中的任何一个块可以被放置到Cache中的任意一行。这种方法灵活,冲突最少,但查找时需要比较所有行的标签,硬件成本高。组相联映射:将Cache分成若干组,每组包含若干行。主存块首先映射到特定的组(通常是通过取模运算),然后可以放置在该组内的任意一行。这是直接映射和全相联映射的折中,常用的有2路、4路组相联等。此外,地址映像还涉及到替换算法(当Cache满时选择哪个块被替换出去)和写策略(当数据被修改时如何保持Cache与主存的一致性)。通俗比喻:直接映射:停车场每辆车有固定车位(即使其他空着,也不能停)。全相联映射:停车场任意空位都可停。组相联映射:停车场分几个区,车只能停在自己区内,但区内任意空位可选。地址映像是Cache设计的核心,直接影响命中率、速度和硬件成本。{/card-default}什么是磁盘碎片?想象一个图书馆(磁盘)。一开始,书(文件)都整整齐齐、连续地放在书架上。但随着时间的推移,有人借书还书(删除和写入文件),还回来的书可能被塞进任何有空隙的地方。久而久之,一本完整的书(比如一个100页的小说)可能被拆成了几部分,散落在图书馆的不同角落。磁盘碎片 就是指一个文件的数据被分割成多个不连续的片段(称为“碎片”),分散存储在磁盘的不同物理位置。文件碎片:单个文件内容不连续。空闲空间碎片:磁盘上可用的空闲空间也是零散的小块,无法容纳较大的新文件。为什么会产生碎片?文件的创建、删除和修改:这是主因。当你删除一个文件时,它原来占用的空间就被释放出来,变成一个个“空洞”。当你保存一个新文件时,操作系统会尝试把这些“空洞”填满。如果新文件很大,它就会被拆开,分别存入多个不相邻的“空洞”中。文件系统管理机制:早期的FAT32等文件系统更容易产生碎片。现代文件系统(如NTFS、APFS、ext4)在设计上通过更智能的空间分配算法(如预分配、延迟分配)来减少碎片产生,但无法完全避免。磁盘碎片带来的问题主要问题是 性能下降,尤其对于传统的机械硬盘(HDD) 影响巨大。机械硬盘(HDD):数据存储在高速旋转的盘片上,通过磁头来回移动进行读写。当读取一个碎片化的文件时,磁头需要在盘片的不同位置之间来回跳动(寻道)并等待盘片旋转到正确位置(旋转延迟)。这大大增加了访问时间,导致系统变慢、文件打开迟缓、程序加载时间长。固态硬盘(SSD):SSD没有机械部件,通过电子信号直接访问存储芯片,因此“寻道时间”几乎为零。碎片对SSD的读取性能影响微乎其微。什么是磁盘碎片整理?磁盘碎片整理 就是一个将分散存放的文件碎片重新整理、组合,并按照连续(或尽可能连续)的方式写入磁盘,同时合并空闲空间的过程。它的核心目标:减少机械硬盘的寻道时间和旋转延迟,从而提升I/O性能。整理过程一般包括:分析:扫描磁盘,查看文件和空闲空间的碎片化程度。移动与重组:将碎片化的文件内容读取到内存中。在磁盘上找到一个足够大的连续空闲区域。将该文件的全部内容连续地写入这个新区域。标记原区域为空闲。合并空闲空间:将整理过程中产生的多个小空闲块合并成少数几个大的连续空闲块,便于未来存放大文件。位示图 或 位图,它是操作系统中用于管理磁盘空间或内存空间的一种经典且高效的数据结构。 其核心思想是:用一串二进制位(0或1)的集合,来直观映射和表示一大片存储资源中每个最小分配单元的使用状态。 想象你有一大张方格纸(代表整个磁盘或内存),每个格子代表一个最小的存储单位(如一个磁盘块 或一个内存页框)。你需要记录哪个格子是空的,哪个格子已经被用了。 位示图就是为这张方格纸配的一把“尺子”或“图例”: 这把“尺子”本身也是一个线性的序列,序列中的每一个位(bit) 对应方格纸上的一个格子。 该位的值仅有两种状态: 0:表示对应的那个格子是 空闲的。 1:表示对应的那个格子是 已占用的。 因此,位示图就是存储资源使用情况的“地图”或“状态表”。 主要用途 磁盘空闲空间管理 这是位示图最经典的应用。文件系统(如早期的FAT, Unix文件系统)用一块连续的磁盘区域来存放整个磁盘的位示图。 当需要创建一个新文件时,操作系统就扫描位示图,寻找值为 0 的位,计算出对应的空闲磁盘块,分配给文件,并将这些位标记为 1。 当删除文件时,系统将文件占用的块对应的位重新置为 0。 内存分页管理 在分页式内存管理中,物理内存被划分为固定大小的页框。 操作系统维护一个内存位示图,其中每一位对应一个物理页框,记录其是分配给了进程还是空闲。 工作原理(以磁盘管理为例) 假设一个磁盘共有 N 个块,块号从 0 到 N-1。 建立位示图:创建一个长度为 N 位的位向量。例如,磁盘有4096个块,就需要4096位的位示图,占用 4096/8 = 512 字节的存储空间。 状态映射: 块 i 的状态 = 位示图中第 i 位的值。 位示图[i] = 0 -> 块 i 空闲。 位示图[i] = 1 -> 块 i 已用。 分配磁盘块: 当需要 k 个连续(或不连续)的磁盘块时,操作系统扫描位示图,找出 k 个值为 0 的位。 通过简单的计算(块号 = 位序号),即可得到空闲块的物理块号。 将这些块分配给文件,并把对应的位置 1。 释放磁盘块: 当文件被删除,释放其占用的块时,根据块号找到位示图中对应的位,将其置 0 即可。 位示图是操作系统资源管理的基石性技术之一。它将存储空间的分配状态抽象成一张简单的二进制地图,使得操作系统能够以极小的开销、高效地掌握全局资源情况,从而完成空间的分配与回收。您之前问的“磁盘碎片整理”,其底层也需要依赖类似位示图这样的结构来了解磁盘上哪些地方是空闲的,以便移动和重组文件。 优点: 空间效率高:管理开销非常小,一个二进制位就能管理一个存储单元。 查找相对高效:寻找第一个或连续的空闲块,可以通过快速扫描位向量完成。现代CPU有专门指令(如位操作指令)能加速此过程。 概念简单清晰:实现和理解都比较容易。 缺点: 性能问题:当存储空间非常大时,位示图本身也会变得很大。为了分配空间,可能需要扫描很长的位序列才能找到空闲块(尽管可以通过缓存或优化算法缓解)。 不适用于特大容量:对于超大规模的磁盘(如数PB),位示图本身可能占用数MB甚至数十MB的内存,且扫描效率下降。
2026年02月09日
4 阅读
0 评论
0 点赞
2026-02-04
此内容被密码保护
加密文章,请前往内页查看详情
2026年02月04日
1 阅读
0 评论
0 点赞
2026-02-04
此内容被密码保护
加密文章,请前往内页查看详情
2026年02月04日
1 阅读
0 评论
0 点赞
2026-02-04
操作系统理论
一、进程的引入:为什么需要进程?问题背景:早期计算机一次只能运行一个程序。比如,你有一个计算程序和一个下载程序,你想边计算边下载,怎么办?解决方案:引入进程的概念,让多个程序可以“同时”运行。实际上,在单核CPU上,是通过快速切换来实现的,但给用户的感觉是同时运行。例子:你打开音乐播放器听歌,同时用浏览器上网。操作系统为每个程序创建了一个进程,并轮流执行它们(时间片轮转)。虽然CPU同一时刻只能执行一个进程,但由于切换很快,你感觉音乐和网页同时都在运行。二、进程是什么?具体例子:想象一个厨房(CPU)里有一个厨师(进程)。厨师要做一道菜(程序),他需要菜谱(代码)、食材(数据)、厨具(资源)。菜谱指明了步骤,但厨师在切菜的时候可能被叫去炒菜(切换),那么他必须记住切到哪了(保存现场),等他回来再接着切。进程的定义:进程是程序的一次执行过程,是系统进行资源分配和调度的基本单位。它包括代码、数据、程序计数器、寄存器、堆栈等。三、进程的状态与切换面对的问题:进程在运行中,有时需要等待外部事件(比如等待用户输入、等待磁盘读取数据),如果让CPU一直等待,就浪费了。如何提高CPU利用率?解决办法:让进程在等待时让出CPU,给其他就绪的进程使用。这就引入了进程状态。例子:假设有两个进程A和B。A需要从磁盘读取数据,于是操作系统将A的状态改为“阻塞”,并让B运行。当磁盘数据准备好后,再将A的状态改为“就绪”,等待CPU调度。典型的状态转换:就绪:进程已准备好,只等CPU。运行:进程正在CPU上执行。阻塞:进程等待某个事件(如I/O完成)。状态转换图:新建 → 就绪 ↔ 运行 → 终止 ↓ 阻塞四、进程的创建与终止创建:通过系统调用(如fork)创建新进程。例子:在Linux终端中,你输入ls命令,shell进程会调用fork创建一个子进程,然后子进程调用exec执行ls程序。终止:进程完成任务后,通过exit系统调用终止,释放资源(除了进程控制块PCB,最后会被父进程回收)。五、进程间通信(IPC)问题:进程之间是隔离的,不能直接访问对方的内存。但如果需要协作,比如一个进程生产数据,另一个进程消费数据,怎么办?解决办法:操作系统提供进程间通信的机制。具体例子:管道(pipe):就像一条水管,一端写,另一端读。例如,在shell中执行ls | grep .txt,shell创建两个进程,一个执行ls,一个执行grep,并用管道连接起来。ls的输出直接作为grep的输入。消息队列:进程A发送一个消息到队列,进程B从队列中读取。类似于发邮件。共享内存:两个进程可以访问同一块内存区域。这是最快的IPC方式,但需要同步机制(如信号量)来避免冲突。信号(signal):一个进程可以向另一个进程发送信号,比如按下Ctrl+C发送SIGINT信号终止进程。六、进程同步问题:当多个进程共享资源(比如共享内存、文件)时,可能会产生冲突。例子:有两个进程A和B,它们都要往同一个文件里写数据。如果A写了一半,B也开始写,那么文件内容就会混乱。解决办法:使用同步机制,确保同一时刻只有一个进程访问共享资源。具体方法:互斥锁(Mutex):进程在访问共享资源前加锁,访问完后解锁。其他进程必须等待锁释放。信号量(Semaphore):一个计数器,用来控制多个进程对共享资源的访问。比如,只有3个资源,信号量初始值为3,每有一个进程占用一个资源,信号量减1,当减到0时,其他进程必须等待。条件变量:用于等待某个条件成立。七、进程调度问题:有多个就绪进程,但CPU只有一个,先运行哪个?调度算法例子:先来先服务(FCFS):就像排队买票,先来的先服务。但如果有长进程先到,短进程就要等很久。短作业优先(SJF):优先运行估计运行时间最短的进程。这可以减少平均等待时间,但长进程可能一直得不到运行(饥饿)。时间片轮转(RR):每个进程分配一个时间片(比如100ms),时间片用完后,进程被剥夺CPU,放到就绪队列末尾。这样每个进程都能得到执行,但上下文切换开销较大。多级反馈队列:结合了多种算法。有多个队列,每个队列优先级不同,时间片大小也不同。新进程进入最高优先级队列,如果时间片用完还没结束,就降到下一级队列。这样短进程可以很快完成,长进程也会得到执行。八、上下文切换问题:当CPU从一个进程切换到另一个进程时,需要保存当前进程的状态,以便下次恢复。例子:进程A正在运行,时间片用完,操作系统要切换到进程B。那么,操作系统必须保存A的寄存器、程序计数器等状态到A的PCB中,然后从B的PCB中恢复B的状态,让B继续运行。开销:上下文切换需要时间,如果切换太频繁,CPU的有效工作时间就会减少。九、多线程与进程的关系问题:进程的创建、切换开销较大,而且进程间通信复杂。如果我们要在一个程序内同时做多件事,比如一个文档编辑器,一边接收用户输入,一边自动保存,一边进行拼写检查,怎么办?解决办法:引入线程。线程是进程内的执行单元,一个进程可以有多个线程,它们共享进程的资源(如内存、文件),但各自有独立的栈和寄存器。例子:一个Web服务器,每来一个客户端请求,就创建一个线程来处理。这样,多个请求可以同时被处理(在多核CPU上真正并行,单核上也是并发)。因为线程共享进程的内存,所以创建线程和线程间通信的开销比进程小。十、总结进程是操作系统资源分配的基本单位,它使得多个程序可以并发执行,提高了系统资源的利用率。但进程的创建、切换、通信都有开销,因此后来又引入了线程。理解进程的关键是理解它的状态转换、调度、同步和通信机制,这些都是为了解决多任务环境下的资源竞争和协作问题。{card-default label="线程概念" width=""}线程:进程内部的“多任务协作”一、为什么需要线程?—— 解决“进程内部并发”的问题问题场景:你使用Word编辑文档,希望同时进行拼写检查、自动保存、用户输入响应。如果用多进程实现,那么这些任务之间共享数据(文档内容)会非常麻烦(需要进程通信),而且创建进程的开销较大。解决方案:在一个进程内部创建多个线程,每个线程执行不同的任务,共享同一份数据。具体实现:线程是进程内的执行单元,一个进程可以有多个线程同一进程的线程共享内存空间(代码段、数据段、打开的文件等)每个线程有独立的栈和程序计数器例子:Word进程中的多线程Word进程: 线程1:处理用户输入(键盘、鼠标) 线程2:定时自动保存 线程3:拼写和语法检查 线程4:打印文档二、线程是什么?—— 进程的“分身”比喻:餐厅厨房里的多个厨师整个厨房 = 进程(拥有食材、厨具等资源)每个厨师 = 线程(共享厨房资源,各自做一道菜)厨师间的协作 = 线程通信(直接说话,因为共享空间)具体例子:Web服务器处理多个客户端请求单进程多线程模型: 主线程:监听端口,接受连接 工作线程1:处理客户端A的请求 工作线程2:处理客户端B的请求 工作线程3:处理客户端C的请求 所有线程共享服务器资源(如缓存、数据库连接池) 线程共享的“公共空间” vs 私有的“个人空间”进程内的线程共享资源指的是:进程的地址空间中的大部分区域,包括代码段、数据段、堆、以及打开的文件描述符等。而每个线程拥有自己独立的栈空间(用于保存局部变量和函数调用信息)以及线程私有的上下文(如寄存器状态、程序计数器等)。具体来说:代码段:所有线程执行相同的代码,它们共享进程的代码段(即程序的可执行代码)。数据段:包括全局变量和静态变量,所有线程都可以访问和修改这些变量,因此需要同步机制来避免竞态条件。堆:动态分配的内存(如malloc/new分配的内存)是共享的,所有线程都可以访问堆中的同一块内存。文件描述符:进程打开的文件、网络套接字等,所有线程共享这些文件描述符,因此一个线程打开的文件,另一个线程也可以读写。信号处理程序:进程设置的信号处理函数是共享的,但每个线程可以有自己独立的信号屏蔽字。当前工作目录:进程的当前工作目录是共享的,一个线程改变工作目录会影响其他线程。用户ID和组ID:进程的用户ID和组ID是共享的。每个线程私有的资源包括:线程ID:每个线程有唯一的ID。寄存器状态:包括程序计数器、栈指针等,每个线程有自己的寄存器状态。栈:每个线程有自己独立的栈,用于存储局部变量、函数调用链等。线程之间不能直接访问其他线程的栈。信号屏蔽字:每个线程可以独立设置阻塞或解除阻塞某些信号。线程私有数据:有些系统支持线程私有数据(Thread Local Storage, TLS),即每个线程可以拥有某些数据的独立副本。理解线程共享哪些资源非常重要,因为这直接影响到多线程程序的设计和同步机制的使用。例如,共享的数据需要加锁保护,而线程私有的数据则不需要。三、线程 vs 进程 —— 详细对比四、线程同步——解决“共享数据混乱”问题问题场景:两个线程同时操作同一个银行账户,一个取钱,一个存钱,如果没有同步,账户余额可能会出错。互斥锁(Mutex)—— 卫生间一次只能一个人用// 银行账户例子 int balance = 1000; // 共享变量 pthread_mutex_t lock; // 线程A:取钱100 pthread_mutex_lock(&lock); int temp = balance; temp -= 100; balance = temp; pthread_mutex_unlock(&lock); // 线程B:存钱200 pthread_mutex_lock(&lock); int temp = balance; temp += 200; balance = temp; pthread_mutex_unlock(&lock);条件变量(Condition Variable)—— 排队等空位// 生产者-消费者问题(线程版) queue buffer; // 共享缓冲区 pthread_mutex_t mutex; pthread_cond_t cond_full; // 缓冲区满的条件变量 pthread_cond_t cond_empty; // 缓冲区空的条件变量 // 生产者线程 pthread_mutex_lock(&mutex); while (buffer.is_full()) { pthread_cond_wait(&cond_full, &mutex); // 等待缓冲区不满 } buffer.push(item); pthread_cond_signal(&cond_empty); // 通知消费者缓冲区不空了 pthread_mutex_unlock(&mutex); // 消费者线程 pthread_mutex_lock(&mutex); while (buffer.is_empty()) { pthread_cond_wait(&cond_empty, &mutex); // 等待缓冲区不空 } item = buffer.pop(); pthread_cond_signal(&cond_full); // 通知生产者缓冲区不满了 pthread_mutex_unlock(&mutex);读写锁(Read-Write Lock)—— 图书馆规则场景:多个线程同时读一个配置,偶尔有线程写配置 规则: 1. 可以多个线程同时读 2. 写的时候不能有任何其他读或写 实现: 读写锁有三种状态:读模式加锁、写模式加锁、不加锁 写模式加锁时,其他线程的读或写都会被阻塞 读模式加锁时,其他线程的写会被阻塞,但读可以继续五、线程池——避免“频繁创建销毁线程”的开销问题场景:Web服务器为每个请求创建一个新线程,请求处理完后销毁。当请求很多时,创建和销毁线程的开销很大。解决方案:预先创建一组线程,重复使用。线程池组成:1. 任务队列:存放待处理的任务 2. 工作线程:从任务队列取任务执行 3. 管理器:创建、销毁线程,调整线程数量例子:Java中的线程池ExecutorService pool = Executors.newFixedThreadPool(10); // 创建10个线程的池 // 提交100个任务 for (int i = 0; i < 100; i++) { pool.submit(new Task(i)); } pool.shutdown(); // 关闭线程池优势:降低资源消耗:重复利用已创建的线程提高响应速度:任务到达时,线程已存在,无需等待创建提高可管理性:可以统一管理线程调试多线程问题常见问题与排查工具问题1:竞态条件症状:结果不确定,每次运行可能不同 工具:ThreadSanitizer (TSan)、Helgrind问题2:死锁症状:程序卡住,不响应 工具:jstack (Java)、pstack、gdb 例子:jstack <pid> | grep -A 10 deadlock问题3:活锁症状:线程忙碌但无进展 例子:两个线程互相"礼让",都不断重试失败问题4:资源泄漏症状:内存或线程数持续增长 工具:jvisualvm、top -H、pmap总结:线程使用指南黄金法则:能不共享就不共享:减少共享数据,避免同步问题能无锁就无锁:使用原子变量、不可变对象必须同步时,粒度尽量小:减少锁竞争优先使用高级工具:线程池、并发集合、同步工具类线程是强大的并发工具,但"能力越大,责任越大"。理解其原理,遵循最佳实践,才能写出正确高效的多线程程序。现代软件中的线程使用案例1:Android应用的主线程与工作线程// Android中,UI操作必须在主线程(UI线程) class MainActivity : AppCompatActivity() { fun loadUserData(userId: String) { // 在后台线程执行耗时操作 thread { val userData = apiService.getUserData(userId) // 网络请求(可能耗时) // 回到主线程更新UI runOnUiThread { textView.text = userData.name // UI操作必须在主线程 progressBar.visibility = View.GONE } } } }规则:主线程只处理UI更新和用户交互,耗时操作(网络、数据库、计算)必须放在工作线程。案例2:Redis的单线程模型(为什么快?)Redis核心是单线程的,但性能极高,原因: 1. 纯内存操作:无磁盘I/O阻塞 2. 非阻塞I/O:使用epoll多路复用 3. 避免锁竞争:单线程不需要同步 4. 数据结构优化:高效的数据结构 适用场景:高并发、数据量小、读写频繁 不适用场景:复杂计算、大数据处理案例3:Nginx的多进程+多线程混合模型Nginx架构: ┌─ Master进程(管理)─────────────────────┐ │ ├─ Worker进程1(独立) │ │ │ ├─ 线程1:处理连接A │ │ │ ├─ 线程2:处理连接B │ │ │ └─ 线程池:处理磁盘I/O │ │ ├─ Worker进程2(独立) │ │ └─ Worker进程N(独立) │ 设计优点: 1. 进程隔离:一个Worker崩溃不影响其他 2. 线程共享:同一Worker内线程共享缓存 3. 充分利用多核:每个Worker绑定一个CPU核心{/card-default}虚拟存储管理系统的基础是程序的局部性理论,这个理论的基本含义是指程序执行时往往不会均匀地访问主存储器单元。根据这个理论,Denning 提出了工作集理论。工作集是进程运行时被频繁访问的页面集合。在进程运行时,如果它的工作集页面都在主存储器(内存)内,能够使该进程有效地运行,否则会出现频繁的页面调入/调出现象。{card-default label="局部性原理" width=""}局部性原理是虚拟存储技术的理论基础,包括两个方面:时间局部性:如果程序中的某条指令或数据被访问,那么不久之后它很可能再次被访问。典型例子包括循环、重复调用的函数等。空间局部性:如果程序访问了某个存储单元,那么其附近的存储单元也可能很快被访问。典型例子包括顺序执行的指令、数组的连续访问等。基于局部性原理,程序运行时只需将当前使用的部分页面装入内存,而非全部程序,从而实现虚拟存储器的高效管理。{/card-default}{card-default label="工作集理论" width=""}{/card-default}{card-default label="抖动(Thrashing)" width=""}当系统内存资源不足时,进程的工作集无法完全驻留内存,导致频繁的页面置换(调入/调出)。此时,CPU 大量时间用于处理缺页中断,实际工作效率下降,这种现象称为“抖动”。防止抖动的常用方法是采用工作集模型,确保每个活跃进程的工作集常驻内存,或采用页面置换算法(如工作集时钟算法)来动态调整内存分配。{/card-default}{card-default label="虚拟存储管理" width=""}虚拟存储技术允许程序部分装入内存即可运行,通过缺页中断机制和页面置换算法,实现内存的自动管理。其核心思想正是基于局部性原理:程序在一段时间内访问的页面相对集中,因此只需将这部分页面保留在内存中即可高效运行。工作集理论为虚拟存储器的内存分配和置换策略提供了重要指导。{/card-default}进程通信PV操作与信号量核心知识点总结初值=0 意味着初始状态没有任何可用资源,事件未发生,必须等待通知P操作 就像"瞅一眼桌子",发现没饭就睡觉(进程阻塞)V操作 就像"外卖员按门铃",把你叫醒继续执行(进程唤醒)原子性保证:// 假设S=1,两辆车同时P操作 车A: 读取S=1 → S--=0 → 进入停车场 车B: 读取S=1 → S--=0 → 也进入停车场 ❌ // 结果:一个车位停两辆车!碰撞!正确顺序:先P资源信号量,再P互斥信号量原理:如果先拿锁,当资源耗尽时,其他进程无法进入临界区释放资源,形成死锁初值=1:商场厕所(互斥访问)场景:只有一间厕所,拉屎要排队数据库行锁:1个线程修改,其他线程阻塞打印机互斥:1个人打印,其他人排队技术映射:保护临界区,一次只允许一个进程访问初值=N:停车场(资源池)场景:10个车位,先到先得图书馆座位:初值=500个座位数据库连接池:初值=5个连接线程池任务队列:初值=100个任务容量资源申请顺序不一致Process1 { P(A); P(B); ... V(B); V(A); } // 先A后B Process2 { P(B); P(A); ... V(A); V(B); } // ❌ 先B后A // 结果:Process1占A等B,Process2占B等A → **死锁!**正确:全局统一起始顺序Process1 { P(A); P(B); ... } Process2 { P(A); P(B); ... } // ✅ 顺序一致信号量负值理解错误题目:3个进程共享1台打印机,信号量初值=1,当前值=-2,问什么情况?❌ 错误答案:有2台打印机空闲(完全错误!)✅ 正确答案:2个进程在等待打印机(|-2|=2)编写PV操作(黄金顺序)Process { 准备工作; P(资源信号量); // 1️⃣ 先检查资源是否可用 P(互斥信号量); // 2️⃣ 再进入临界区(上锁) 操作共享资源; // 3️⃣ 临界区代码 V(互斥信号量); // 4️⃣ 先出临界区(解锁) V(通知信号量); // 5️⃣ 再通知其他进程 后续操作; }📌 初值选择:互斥用1,同步用0,资源就是N📌 PV本质:P是申请,不够就等;V是释放,唤醒他人📌 操作顺序:先资源后锁,先解锁后通知📌 信号量值:正数可用,零刚好用,负数等几📌 死锁避免:统一申请顺序,限制最大并发进程调度知识点进程调度本质核心问题:CPU只有一个,成百上千个进程要运行,按什么顺序执行?生活类比:餐厅只有一个厨师,怎么安排炒菜顺序才能让顾客最满意?{card-default label="六大调度算法" width=""}1.先来先服务(FCFS)原理:排队买奶茶,先到先得典型问题:护航效应(长作业阻塞短作业)2.短作业优先(SJF)原理:执行时间短的优先,分抢占/非抢占问题:长作业可能饿死生活场景:快餐店小份餐优先出餐,大胃王套餐最后做3.优先级调度原理:按优先级执行,分静态/动态优先级生活场景:医院急诊分级(濒危>危重>急症>非急症)数字越小优先级越高进程 | 到达 | 执行 | 优先级 P1 | 0ms | 8ms | 3 P2 | 1ms | 4ms | 1 ← 最高 P3 | 2ms | 9ms | 4 抢占式执行 0----1----5----14 |P1 |P2 |P1 |P34.时间片轮转(RR)原理:每个人轮流吃一口,看起来都很忙生活场景:自助餐每人限时5分钟,轮流用餐时间片=5ms执行示例:进程 | 到达 | 执行 P1 | 0ms | 13ms P2 | 1ms | 4ms P3 | 2ms | 6ms 0---5---9---15---18 |P1 |P2 |P3 |P15.多级队列调度原理:按性质分多个队列,不同队列不同策略生活场景:火车站分窗口:军人/急客窗口(优先级+RR)普通窗口(FCFS)团购窗口(批处理)前台队列(交互式):RR调度,时间片=8ms 后台队列(批处理):FCFS,时间片=32ms 系统队列(内核):优先级最高 队列规则:进程固定属于某队列队列间按优先级调度队列内用各自算法6.多级反馈队列(MFQ)原理:RR + 优先级 + 队列升级(Linux实际使用)生活场景:餐厅会员体系:新客→普通座(Q0)超时用餐→升包厢(Q1)长期用餐→升VIP(Q2)Q0(优先级最高):RR时间片=8msQ1:RR时间片=16msQ2(最低):FCFS时间片=32ms核心规则:新进程进入Q0用完时间片未结束 → 降级到下一队列等待过久未执行 → 提升到上级队列高优先级队列非空时,不执行低优先级{/card-default}{card-default label="核心计算模板" width=""}P1: 到达=0, 执行=7 P2: 到达=2, 执行=4 P3: 到达=4, 执行=1 0-3: P1 → 3-6: P2 → 6-7: P3 → 7-10: P2 → 10-14: P1 进程 | 到达 | 服务 | 完成 | 周转 | 带权 | 等待 P1 | 0 | 7 | 14 | 14 | 2.0 | 0+(10-3)=7 P2 | 2 | 4 | 10 | 8 | 2.0 | (3-2)+(7-6)=2 P3 | 4 | 1 | 7 | 3 | 3.0 | 6-4=2 计算平均值 平均等待 = (7+2+2)/3 = 3.67ms 平均周转 = (14+8+3)/3 = 8.33ms 平均带权 = (2+2+3)/3 = 2.33陷阱1:抢占式vs非抢占式混淆错误:题目说"抢占式SJF",却按非抢占计算正确:SRTF要在每个新进程到达时重新选择最短剩余时间P1(执行10ms)运行中,P2(执行1ms)到达 → 立即抢占P1执行P2 ✅陷阱2:时间片轮转切换开销忽略时间片=5ms,切换开销=1ms P1(0-4)运行 → P2(5-9)运行 → 实际耗时不是5ms而是6ms!陷阱3:带权周转时间算错执行时间=3ms,周转=24ms 带权 = 24/3 = 8 ✅ 不是 3/24 = 0.125 ❌陷阱4:响应时间理解错误错误:认为响应时间=结束时间正确:响应时间=首次运行时间-到达时间(第一次被调度)陷阱5:多级队列饥饿问题低优先级队列进程永远不执行 → 解决方案:优先级老化(等待越久优先级越高)陷阱6:护航效应识别题目:长作业+多个短作业用FCFS后果:短作业等待时间巨长 → 解决方案:用SJF或RR{/card-default}现实操作系统应用Linux CFS调度器核心理念:完全公平调度(Completely Fair Scheduler)关键技术:红黑树管理进程,按虚拟运行时间vruntime排序计算公式:vruntime = 实际时间 × (NICE_0_LOAD / 权重)调度周期:所有进程至少运行一次的时间生活类比:班级轮流发言,说话时间短的人优先Windows调度器32级优先级:0-31,0为最低动态调整:前台窗口线程+2,I/O操作后+1实时优先级:16-31级(需管理员权限实时系统调度硬实时 必须准时完成 最早截止时间优先(EDF)软实时 偶尔超时允许 速率单调调度(RMS)案例:导弹控制→EDF算法;视频播放器→RMS算法进程调度就是CPU的"交通警察":FCFS是排队,SJF是绿色通道,RR是轮流执政,多级反馈是智能交通系统,最终目标——让所有"车辆"都满意!{card-default label="进程资源图" width=""}分配边:进程与具体某个已持有实例的关系。(历史:已满足的请求)申请边:进程与整个资源类的关系。(当前:一个新的、未满足的请求)在进程资源图中,箭头是描述进程和资源之间关系的关键元素,它清晰地表达了资源的申请和分配状态。简单来说,箭头从谁指向谁,就表示“谁需要谁”或“谁等待谁”。申请边(Request Edge)方向:从 进程 指向 资源类(方框)。涵义:表示该进程正在申请(等待)该资源类中的一个实例,但尚未得到。状态:进程因此被阻塞,处于等待状态。口语化理解:“进程想要一个这种资源,但还没拿到。”分配边(Assignment Edge) / 持有边(Allocation Edge)方向:从 资源类中的某个具体实例(方框中的圆点)指向 进程。涵义:表示该资源类中的一个具体实例已经分配给了这个进程,由该进程持有。状态:进程正占用着此资源。口语化理解:“这个资源实例已经在进程手里了。如果资源有空闲,请求会被瞬间满足,申请边在快照中就不会出现(它会被立即转换为一条分配边)。因此,在静态资源图分析中,默认图中出现的每一条申请边都对应一个被阻塞的进程。假设资源有三个能分配的资源,但是已经有三个分配箭头了,这时还有一个申请边,那就是阻塞关于b图:R2的3个资源已经占用了2个,当进程P1和P3请求占用R2的时候,无论分配给哪一方都可以使两个进程都满足所需的资源,从而可以化简,P2也可得所需的R1资源。因此P1和P3是非阻塞节点。疑问:“申请边存在不是意味着阻塞吗?”是的,在静态快照中,画出的申请边意味着该进程在快照时刻因该请求而处于阻塞状态。但是,在动态化简分析中,我们关心的是这种阻塞是否是“永久性的”。如果系统当前有足够的空闲资源能立即(或在其他进程释放资源后)满足该请求,那么这个阻塞就是暂时的、可解除的。这样的进程在化简中就被归为“非阻塞节点”。“非阻塞节点”的真实含义:更准确的说法是“可立即满足或可被化简的节点”。它不是指进程在快照时刻未阻塞,而是指从当前系统状态出发,该进程的请求能够在不引起死锁的情况下被满足。{/card-default}动态分区内存管理中的空闲分区合并(或合并自由块)在动态分区分配方式中,当进程运行结束释放内存时,系统会将其占用的分区回收,并检查该分区是否与相邻的空闲分区相邻(地址连续)。如果回收区与上方和下方的空闲区都相邻,则系统会将这三个分区合并为一个更大的空闲分区,从而导致空闲区总数减少1。此过程旨在减少外部碎片,提高内存利用率。要保证系统中 6 个并发进程(每个进程都需要 2 个互斥资源 R)不发生死锁,资源 R 的最少数目为 7。推导过程:在最坏情况下,每个进程都已获得 1 个资源,同时还需要 1 个资源才能继续执行。此时如果系统中只剩 1 个可用资源,则可分配给任意一个进程,使其满足全部资源需求并运行完毕,之后释放的资源又能供其他进程使用,从而避免死锁。因此,最少资源数 = 进程数 × (每个进程所需资源数 - 1) + 1 = 6 × (2 - 1) + 1 = 7。
2026年02月04日
5 阅读
0 评论
0 点赞
2026-01-29
此内容被密码保护
加密文章,请前往内页查看详情
2026年01月29日
1 阅读
0 评论
0 点赞
1
2
3
...
8
0:00