16-1.【Linux系统编程】线程概念(线程优缺点、异常、用途;进程和线程之间的联系)

发布时间:2026/10/10 8:37:44
16-1.【Linux系统编程】线程概念(线程优缺点、异常、用途;进程和线程之间的联系)
目录1. Linux线程概念1.1 概念角度感性理解线程1.2 理性角度理解线程虚拟到物理内存页表相关概念1.2.1 虚拟地址和页表的由来1.2.2 物理内存管理1.2.3 页表 - 一个页表中有1024个页表项1.2.4 页目录 - 页目录中有1024个页表1.2.5 两级页表的地址转换1.2.6 缺页异常1.2.N 理解虚拟地址到物理地址的映射关系理解重点32 位虚拟地址转换为物理地址完整总结1. 基本概念2. 32 位虚拟地址的划分3. PDE、PTE 和 PFN 的关系4. 虚拟地址转换为物理地址的具体步骤5. 为什么 PFN 要左移 12 位6. PFN 和 struct page 的关系7. 面试容易考的细节8. 面试标准回答建议背诵2 线程的优缺点2.1 线程的优点2.2 线程的缺点3. 线程异常4. 线程用途5. Linux进程VS线程 -- 哪些资源共享哪些独占5.1 进程和线程5.2 进程的多个线程共享5.3 关于进程线程的问题总结附录页表和页表项1. Linux线程概念1.1 概念角度感性理解线程教材定义进程 内核数据结构 代码和数据执行流线程是进程内部的一个执行分支执行流内核和资源的角度理解线程定义进程承担分配系统资源的基本实体线程CPU调度的基本单位理解进程中的线程线程资源特征为共享进程虚拟地址空间仅拥有线程 ID、私有栈等私有资源。代码区由进程内所有线程共享线程不划分代码区函数是代码区的指令地址集合线程通过指向不同函数入口执行 ELF 程序的不同函数。进程划分单线程进程以前为方便理解以单线程讲解为主单线程进程为多线程进程的一种特殊情况多线程进程不同操作系统内核实现方式略有不同window实现进程通过PCB控制线程通过TCB控制先描述再组织Linux实现复用task_struct的PCB即线程复用进程的内核代码Linux的线程就是轻量级的进程或者是说用轻量级进程来模拟实现的。操作系统 和 具体的操作系统比如Linux的概念的区别操作系统提供的是思想具体操作系统提供的是思想的具体实现方案进程 VS 线程进程强调独占部分共享比如通信的时候线程强调共享部分独占线程的特点在一个程序里的一个执行路线就叫做线程thread。更准确的定义是线程是“一个进程内部的控制序列”一切进程至少都有一个执行线程线程在进程内部运行本质是在进程地址空间内运行在Linux系统中在CPU眼中看到的PCB都要比传统的进程更加轻量化透过进程虚拟地址空间可以看到进程的大部分资源将进程资源合理分配给每个执行流就形成了线程执行流 不过仅仅有上面的理解是不够的。要真正理解线程就必须搞清楚内核是如何进行资源划分的尤其是代码1.2 理性角度理解线程虚拟到物理内存页表相关概念1.2.1 虚拟地址和页表的由来思考一下如果在没有虚拟内存和分页机制的情况下每一个用户程序在物理内存上所对应的空间必须是连续的如下图因为每一个程序的代码、数据长度都是不一样的按照这样的映射方式物理内存将会被分割成各种离散的、大小不同的块。经过一段运行时间之后有些程序会退出那么它们占据的物理内存空间可以被回收导致这些物理内存都是以很多碎片的形式存在。怎么办呢我们希望操作系统提供给用户的空间必须是连续的但是物理内存最好不要连续。此时虚拟内存和分页便出现了如下图所示把物理内存按照一个固定的长度的页框进行分割有时叫做物理页。每个页框包含一个物理页page。一个页的大小等于页框的大小。大多数32位体系结构支持4KB 的页而 64位体系结构一般会支持8KB 的页。区分一页和一个页框是很重要的页框是一个存储区域而页是一个数据块可以存放在任何页框或磁盘中。有了这种机制CPU 便并非是直接访问物理内存地址而是通过虚拟地址空间来间接的访问物理内存地址。所谓的虚拟地址空间是操作系统为每一个正在执行的进程分配的一个逻辑地址在32位机上其范围从0 ~ 4GB。操作系统通过将虚拟地址空间和物理内存地址之间建立映射关系也就是页表这张表上记录了每一对虚拟页和物理页框的映射关系能让CPU间接的访问物理内存地址。总结一下其思想是将虚拟内存下的逻辑地址空间分为若干虚拟页将物理内存空间分为若干物理页框通过页表便能把连续的虚拟内存映射到若干个不连续的物理内存页。这样就解决了使用连续的物理内存造成的碎片问题。1.2.2 物理内存管理假设一个可用的物理内存有 4GB 的空间。按照一个页框的大小 4KB 进行划分 4GB 的空间就是4GB/4KB 1048576 个页框。有这么多的物理页操作系统肯定是要将其管理起来的操作系统需要知道哪些页正在被使用哪些页空闲等等。内核用struct page结构表示系统中的每个物理页出于节省内存的考虑struct page中使用了大量的联合体union。/* include/linux/mm_types.h */structpage{/* 原子标志有些情况下会异步更新 */unsignedlongflags;union{struct{/* 换出页列表例如由zone-lru_lock保护的active_list */structlist_headlru;/* 如果最低为为0则指向inode * address_space或为NULL * 如果页映射为匿名内存最低为置位 * 而且该指针指向anon_vma对象 */structaddress_space*mapping;/* 在映射内的偏移量 */pgoff_t index;/* * 由映射私有不透明数据 * 如果设置了PagePrivate通常用于buffer_heads * 如果设置了PageSwapCache则用于swp_entry_t * 如果设置了PG_buddy则用于表示伙伴系统中的阶 */unsignedlongprivate;};struct{/* slab, slob and slub */union{structlist_headslab_list;/* uses lru */struct{/* Partial pages */structpage*next;#ifdefCONFIG_64BITintpages;/* Nr of pages left */intpobjects;/* Approximate count */#elseshortintpages;shortintpobjects;#endif};};structkmem_cache*slab_cache;/* not slob *//* Double-word boundary */void*freelist;/* first free object */union{void*s_mem;/* slab: first object */unsignedlongcounters;/* SLUB */struct{/* SLUB */unsignedinuse:16;/* 用于SLUB分配器对象的数目 */unsignedobjects:15;unsignedfrozen:1;};};};...};union{/* 内存管理子系统中映射的页表项计数用于表示页是否已经映射还用于限制逆向映射搜索*/atomic_t _mapcount;unsignedintpage_type;unsignedintactive;/* SLAB */intunits;/* SLOB */};...#ifdefined(WANT_PAGE_VIRTUAL)/* 内核虚拟地址如果没有映射则为NULL即高端内存 */void*virtual;#endif/* WANT_PAGE_VIRTUAL */...}其中比较重要的几个参数flags用来存放页的状态。这些状态包括页是不是脏的是不是被锁定在内存中等。flag的每一位单独表示一种状态所以它至少可以同时表示出32种不同的状态。这些标志定义在linux/page-flags.h中。其中一些比特位非常重要如PG_locked用于指定页是否锁定PG_uptodate用于表示页的数据已经从块设备读取并且没有出现错误。_mapcount表示在页表中有多少项指向该页也就是这一页被引用了多少次。当计数值变为-1时就说明当前内核并没有引用这一页于是在新的分配中就可以使用它。virtual是页的虚拟地址。通常情况下它就是页在虚拟内存中的地址。有些内存即所谓的高端内存并不永久地映射到内核地址空间上。在这种情况下这个域的值为NULL需要的时候必须动态地映射这些页。要注意的是struct page与物理页相关而并非与虚拟页相关。而系统中的每个物理页都要分配一个这样的结构体让我们来算算对所有这些页都这么做到底要消耗掉多少内存。算struct page占40个字节的内存吧假定系统的物理页为4KB大小系统有4GB物理内存。那么系统中共有页面1048576个所以描述这么多页面的page结构体消耗的内存只不过40MB相对系统4GB内存而言仅是很小的一部分罢了。因此要管理系统中这么多物理页面这个代价并不算太大。要知道的是页的大小对于内存利用和系统开销来说非常重要页太大页内必然会剩余较大不能利用的空间页内碎片。页太小虽然可以减小页内碎片的大小但是页太多会使得页表太长而占用内存同时系统频繁地进行页转化加重系统开销。因此页的大小应该适中通常为512B-8KBwindows/Linux系统的页框大小为4KB。️注意操作系统也要管理每一个页1.2.3 页表 - 一个页表中有1024个页表项页表中的每一个表项指向一个物理页的开始地址。在32位系统中虚拟内存的最大空间是4GB这是每一个用户程序都拥有的虚拟内存空间。既然需要让4GB的虚拟内存全部可用那么页表中就需要能够表示这所有的4GB空间那么就一共需要4GB/4KB 1048576个表项。如下图所示虚拟内存看上去被虚线“分割”成一个个单元其实并不是真的分割虚拟内存仍然是连续的。这个虚线的单元仅仅表示它与页表中每一个表项的映射关系并最终映射到相同大小的一个物理内存页上。页表中的物理地址与物理内存之间是随机的映射关系哪里可用就指向哪里(物理页)。虽然最终使用的物理内存是离散的但是与虚拟内存对应的线性地址是连续的。处理器在访问数据、获取指令时使用的都是线性地址只要它是连续的就可以了最终都能够通过页表找到实际的物理地址。假设在32 位系统中地址的长度是4个字节那么页表中的每一个表项就是占用4个字节。所以页表占据的总空间大小就是1048576*4 4MB的大小。也就是说映射表自己本身就要占用4MB / 4KB 1024个物理页。这会存在哪些问题呢回想一下当初为什么使用页表就是要将进程划分为一个个页可以不用连续的存放在物理内存中但是此时页表就需要1024个连续的页框似乎和当时的目标有点背道而驰了…此外根据局部性原理可知很多时候进程在一段时间内只需要访问某几个页就可以正常运行了。因此也没有必要一次让所有的物理页都常驻内存。解决需要大容量页表的最好方法是把页表看成普通的文件对它进行离散分配即对页表再分页由此形成多级页表的思想。为了解决这个问题可以把这个单一页表拆分成1024 个体积更小的映射表。如下图所示。这样一来1024(每个表中的表项个数) * 1024(表的个数)仍然可以覆盖4GB 的物理内存空间。这里的每一个表就是真正的页表所以一共有1024 个页表。一个页表自身占用 4KB 那么1024 个页表一共就占用了4MB 的物理内存空间和之前没差别啊从总数上看是这样但是一个应用程序是不可能完全使用全部的 4GB 空间的也许只要几十个页表就可以了。例如一个用户程序的代码段、数据段、栈段一共就需要10 MB 的空间那么使用 3 个页表就足够了。计算过程每一个页表项指向一个 4KB 的物理页那么一个页表中 1024 个页表项一共能覆盖 4MB 的物理内存;那么 10MB 的程序向上对齐取整之后(4MB 的倍数就是 12 MB)就需要 3 个页表就可以了。1.2.4 页目录 - 页目录中有1024个页表到目前为止每一个页框都被一个页表中的一个表项来指向了那么这1024 个页表也需要被管理起来。管理页表的表称之为页目录表形成二级页表。如下图所示所有页表的物理地址被页目录表指向页目录的物理地址被CR3 寄存器指向这个寄存器中保存了当前正在执行任务的页目录地址。所以操作系统在加载用户程序的时候不仅仅需要为程序内容来分配物理内存还需要为用来保存程序的页目录和页表分配物理内存。1.2.5 两级页表的地址转换下面以一个逻辑地址为例。将逻辑地址 0000000000,0000000001,11111111111 转换为物理地址的过程在32位处理器中采用4KB的页大小则虚拟地址中低12位为页偏移剩下高20位给页表分成两级每个级别占10个bit1010。CR3 寄存器读取页目录起始地址再根据一级页号查页目录表找到下一级页表在物理内存中存放位置。根据二级页号查表找到最终想要访问的内存块号。结合页内偏移量得到物理地址。注一个物理页的地址一定是4KB 对齐的(最后的12 位全部为0 )所以其实只需要记录物理页地址的高 20 位即可。以上其实就是 MMU 的工作流程。MMU(Memory Manage Unit)是一种硬件电路其速度很快主要工作是进行内存管理地址转换只是它承接的业务之一。到这里其实还有个问题MMU要先进行两次页表查询确定物理地址在确认了权限等问题后MMU再将这个物理地址发送到总线内存收到之后开始读取对应地址的数据并返回。那么当页表变为N级时就变成了N次检索1次读写。可见页表级数越多查询的步骤越多对于CPU来说等待时间越长效率越低。让我们现在总结一下单级页表对连续内存要求高于是引入了多级页表但是多级页表也是一把双刃剑在减少连续存储要求且减少存储空间的同时降低了查询效率。有没有提升效率的办法呢计算机科学中的所有问题都可以通过添加一个中间层来解决。MMU 引入了新武器江湖人称快表的TLB 其实就是缓存Translation Lookaside Buffer学名转译后备缓冲器当CPU 给MMU 传新虚拟地址之后 MMU 先去问TLB 那边有没有如果有就直接拿到物理地址发到总线给内存齐活。但TLB 容量比较小难免发生 Cache Miss 这时候MMU 还有保底的老武器页表在页表中找到之后MMU 除了把地址发到总线传给内存还把这条映射关系给到TLB让它记录一下刷新缓存。1.2.6 缺页异常设想CPU 给 MMU 的虚拟地址在TLB 和页表都没有找到对应的物理页该怎么办呢其实这就是缺页异常Page Fault 它是一个由硬件中断触发的可以由软件逻辑纠正的错误。假如目标内存页在物理内存中没有对应的物理页或者存在但无对应权限CPU 就无法获取数据这种情况下CPU就会报告一个缺页错误。由于 CPU 没有数据就无法进行计算CPU罢工了用户进程也就出现了缺页中断进程会从用户态切换到内核态并将缺页中断交给内核的 Page Fault Handler 处理。缺页中断会交给PageFaultHandler 处理其根据缺页中断的不同类型会进行不同的处理Hard Page Fault 也被称为Major Page Fault 翻译为硬缺页错误/主要缺页错误这时物理内存中没有对应的物理页需要CPU打开磁盘设备读取到物理内存中再让MMU建立虚拟地址和物理地址的映射。Soft Page Fault 也被称为Minor Page Fault 翻译为软缺页错误/次要缺页错误这时物理内存中是存在对应物理页的只不过可能是其他进程调入的发出缺页异常的进程不知道而已此时MMU只需要建立映射即可无需从磁盘读取写入内存一般出现在多进程共享内存区域。Invalid Page Fault 翻译为无效缺页错误比如进程访问的内存地址越界访问又比如对空指针解引用内核就会报segment fault 错误中断进程直接挂掉。 注意如何理解我们之前的new和mallocnew/malloc仅向进程申请虚拟地址空间内核修改 VMA 的 start/end不立即分配物理内存物理内存延迟到首次访问该虚拟地址触发缺页中断时申请。如何理解我们之前学习的写时拷贝写时拷贝fork 后父子进程共享同一物理页框PTE 标记该页框为只读任意进程写该页框时内核对整个 4KB 页框拷贝生成新物理页框更新 PTE 指向新页框并恢复可写权限。如何区分是缺页了还是真的越界了越界了一定会报错吗缺页 vs 越界区分第一步检查虚拟地址是否在进程 VMA 映射范围内 → 不在 越界触发段错误第二步地址在范围内但 PTE 无效 → 缺页中断分配物理内存 更新 PTE越界不一定报错若逻辑越界如数组下标超界但地址仍在进程合法 VMA 范围内无硬件 / 内核异常仅程序逻辑错误。线程资源划分线程共享进程虚拟地址空间只需划分进程内的专属虚拟地址区间如线程栈、TLS即可实现线程资源的隔离。1.2.N 理解虚拟地址到物理地址的映射关系理解重点背景物理内存的管理 / 映射基本单位是4KB物理页框磁盘 IO 最小硬件单位是扇区512B/4KB文件系统存储基本单位是4KB块。32 位操作系统最大支持4GB物理内存4GB/4KB10485762²⁰个物理页框物理页框是由struct page mem_map[1048576]数组来描述所以每个page都有一个下标每个page的起始物理地址就已知了index*4KB即具体物理地址 起始物理地址 页内(4KB)偏移。我们就不需要再page里面保存page的起始地址了每个物理页框由内核全局数组struct page mem_map [1048576]描述数组下标 PFN页框号因此每个物理页框的起始物理地址可通过「下标×4KB」计算无需在struct page中存储起始地址具体物理地址 物理页框基地址 页内4KB偏移。虚拟地址32位分析0000 0000 0000 0000 00000000 0000 0000高 10 位 [0,1023]页目录索引定位页目录表中的 PDE中间 10 位 [0,1023]页表索引定位页表中的 PTE最后 12 位 [0,4095]页内偏移即具体字节的地址定位 4KB 物理页框内的具体字节4KB2¹² 字节。页表相关概念PDE (Page Directory Entry)页目录项在虚拟地址转换中PDE 用于定位二级页表的位置是CPU 从 CR3 寄存器开始查找物理地址的第一站。存储页表的物理基地址1.通常为 20 位低 12 位为 0因为页表需 4KB 对齐2.包含页表属性标志(如存在位 P、读写权限、用户 / 内核模式等)PTE(Page Table Entry)页表项在虚拟地址转换中PTE 直接映射虚拟页到物理页是 CPU 完成地址转换的最终一步。存储物理页帧的基地址(通常为 20 位低 12 位为 0因为物理页需 4KB 对齐)包含物理页属性标志(如存在位 P、读写权限、访问位、脏位等)PDE/PTE 的低 12 位本质是权限标志位寻址时不用它定位基址因物理地址低 12 位天然为 0高 20 位足够struct page物理页框物理地址的描述信息。PFNPage Frame Number页框号即为 mem_map 数组的下标通过 mem_map [PFN] 找到该物理页框对应的 struct page。找物理地址需要找到struct page物理页框的虚拟地址还要找到具体4KB的物理地址具体步骤虚拟地址高 20 位拆分为 10 位页目录索引 10 位页表索引通过二级索引定位物理内存中的 PTE低 12 位为页内偏移地址。内核管理物理页框找 struct pagePTE 高 20 位 →(转) 物理页框基地址PTE 高 20 位 12→(转) PFN物理页框基地址12找具体的物理地址PTE 为 4 字节数据高 20 位是物理页框基地址4KB 对齐低 12 位为控制 / 权限位CPU 的 MMU 将「PTE 高 20 位 12」物理页框基地址与「虚拟地址低 12 位」拼接得到具体字节的物理地址硬件层面自动完成。总结步骤虚拟地址高 10 位索引定位 PDE 地址 → PDE 高 20 位页表基址定位 PTE 地址 → PTE 高 20 位物理页基址 虚拟地址低 12 位页内偏移 物理地址。✔注意内核申请物理内存遍历 mem_map 数组→找到空闲的 struct page→取其下标PFN→计算物理页框基地址PFN×4KB写时拷贝、缺页中断、内存申请等场景内核可能需新建页表 / 更新 PTE重新建立 “虚拟页→物理页框” 的映射关系进程的地址空间由 “1 张页目录表 n 张页表” 构建虚拟地址是索引高 20 位找 PTE物理页框是映射目标最终物理地址 物理页框基地址 虚拟地址低 12 位。✔物理地址转虚拟地址反向映射简洁步骤物理地址屏蔽低 12 位页内偏移→ 物理页框基地址右移 12 位得到 PFN通过mem_map[PFN]找到对应 struct page。从 struct page 关联到目标地址空间的页目录表pgd遍历页目录项PDE和页表项PTE筛选出 “高 20 位 PFN” 的有效 PTE。提取匹配 PTE 的页目录索引高 10 位和页表索引低 10 位组合为虚拟页号高 20 位拼接原物理地址低 12 位页内偏移得到虚拟地址。总结执行流看到的资源本质是 “合法虚拟地址映射到的物理 / 文件资源”—— 虚拟地址是资源的访问接口合法性由 VMA、页表映射和权限共同保障mm_struct vm_area_struct的本质是「管理虚拟地址空间的划分、权限和映射关系」实现资源的隔离与分类页表是 “带权限的虚拟→物理映射地图”不仅指引地址转换还控制资源访问方式资源划分的本质是 “权限与访问范围的隔离”地址空间划分是实现该隔离的核心手段资源共享的本质是 “物理资源的共享”虚拟地址映射相同或不同虚拟地址指向同一物理资源是实现共享的核心途径。32 位虚拟地址转换为物理地址完整总结1. 基本概念在 Linux 中程序使用的是虚拟地址CPU 需要通过 MMU内存管理单元和页表将虚拟地址转换成物理地址才能访问对应的物理内存。大白话理解虚拟地址相当于一个查询编号页表负责记录这个编号对应哪个物理页框MMU 根据页表找到物理页框再加上页内偏移就能找到真正的物理地址。下面以传统 x86 32 位、非 PAE、4KB 页面、二级页表为例。2. 32 位虚拟地址的划分32 位虚拟地址分为三部分高 10 位页目录索引定位页目录中的 PDE。中间 10 位页表索引定位页表中的 PTE。低 12 位页内偏移定位 4KB 物理页框中的具体字节。因此也就是说32 位虚拟地址最多表示 4GiB 的虚拟地址空间。3. PDE、PTE 和 PFN 的关系名称含义作用PDE页目录项记录下一级页表的位置及控制信息PTE页表项记录物理页框的位置及控制信息PFN物理页框号表示物理内存中的第几个页框struct pageLinux 内核管理物理页框的数据结构管理物理页的状态等信息在传统 32 位非 PAE、4KB 分页中PDE 大小为 4 字节。PTE 大小为 4 字节。PTE 高 20 位保存 PFN。PTE 低 12 位主要保存权限和控制标志。注意PFN 提取出来后只有低 20 位可能非零高 12 位为 0如果用 32 位变量保存。4. 虚拟地址转换为物理地址的具体步骤具体计算第一步通过 CR3 找到 PDEPDE物理地址 页目录物理基地址 页目录索引 * 4;第二步通过 PDE 找到 PTE页表物理基地址 PDE 0xFFFFF000; PTE物理地址 页表物理基地址 页表索引 * 4;第三步通过 PTE 得到 PFN 和物理页框基地址PFN PTE 12; 物理页框基地址 PFN 12;第四步计算最终物理地址物理地址 (PTE 0xFFFFF000) | (虚拟地址 0xFFF);以上都是页表项有效、权限检查通过时的简化计算。5. 为什么 PFN 要左移 12 位因为每个物理页框大小为 4KB即 \(2^{12}\) 字节。PFN 只是物理页框的编号不是物理地址。因此也就是物理页框基地址 PFN 12;例如PFN 0xABCDE; 物理页框基地址 0xABCDE000; 页内偏移 0x123; 最终物理地址 0xABCDE123;PFN 的 20 位没有丢失只是左移后占据 32 位物理地址的高 20 位。特别注意// 先提取 PFN再左移 uint32_t base (PTE 12) 12; // 直接清除 PTE 的低 12 位 uint32_t base PTE 0xFFFFF000;这两种方式得到相同的物理页框基地址。如果直接操作完整 PTE不需要再次左移 12 位。6. PFN 和 struct page 的关系CPU 的 MMU 负责地址转换而 Linux 内核使用struct page管理物理页框。在有效、受内核管理的普通物理内存 PFN 上可以通过struct page *page pfn_to_page(PFN);找到对应的struct page。但要记住PFN 用于标识物理页框struct page用于内核管理物理页框。MMU 的地址转换不需要经过struct page。并非所有物理地址对应的 PFN 都有有效的struct page。7. 面试容易考的细节问题正确回答32 位虚拟地址最大空间是多少4GiB为什么划分为 10 10 12 位两级页表索引以及 4KB 页内偏移PDE、PTE 多大各 4 字节PFN 是多少位在该分页模式下为 20 位PFN 为什么左移 12 位因为物理页框大小为 4KBPTE 低 12 位是什么主要是权限和控制标志物理地址的低 12 位来自哪里虚拟地址的低 12 位struct page参与地址转换吗不参与负责内核物理页管理所有虚拟地址都有对应物理地址吗不一定可能缺页或访问非法另外实际 CPU 通常先查询 TLB快表。TLB 命中后可以直接利用缓存的地址映射不必每次都遍历两级页表TLB 未命中时才需要进行相应的页表查询。8. 面试标准回答建议背诵在传统 x86 32 位非 PAE、4KB 分页机制下虚拟地址被划分为高 10 位页目录索引、中间 10 位页表索引和低 12 位页内偏移。首先CPU 通过 CR3 寄存器找到页目录物理基地址利用高 10 位定位 PDE然后从 PDE 中得到页表物理基地址结合中间 10 位定位 PTE。PTE 一共 32 位高 20 位保存物理页框号 PFN低 12 位主要保存权限和控制标志。将 PFN 左移 12 位得到 4KB 对齐的物理页框基地址再结合虚拟地址低 12 位的页内偏移最终形成物理地址。整个地址转换主要由 MMU 完成而 Linux 内核中的struct page用于管理物理页框不参与硬件地址转换。最终记住这三个公式PFN PTE 12; 物理页框基地址 PFN 12; 物理地址 (PTE 0xFFFFF000) | (虚拟地址 0xFFF);这些公式适用于上述传统 32 位 4KB 分页机制PAE、x86-64 或大页映射的页表结构会有所不同。2 线程的优缺点2.1 线程的优点创建一个新线程的代价要比创建一个新进程小得多与进程之间的切换相比线程之间的切换需要操作系统做的工作要少很多最主要的区别是线程的切换虚拟内存空间依然是相同的但是进程切换是不同的。这两种上下文切换的处理都是通过操作系统内核来完成的。内核的这种切换过程伴随的最显著的性能损耗是将寄存器中的内容切换出。另外一个隐藏的损耗是上下文的切换会扰乱处理器的缓存机制。简单的说一旦去切换上下文处理器中所有已经缓存的内存地址一瞬间都作废了。还有一个显著的区别是当你改变虚拟内存空间的时候处理的页表缓冲TLB 快表会被全部刷新这将导致内存的访问在一段时间内相当的低效。但是在线程的切换中不会出现这个问题当然还有硬件cache。线程占用的资源要比进程少能充分利用多处理器的可并行数量在等待慢速I/O操作结束的同时程序可执行其他的计算任务计算密集型应用为了能在多处理器系统上运行将计算分解到多个线程中实现I/O密集型应用为了提高性能将I/O操作重叠。线程可以同时等待不同的I/O操作。2.2 线程的缺点性能损失线程创建太多的缺点一个很少被外部事件阻塞的计算密集型线程往往无法与其它线程共享同一个处理器。如果计算密集型线程的数量比可用的处理器多那么可能会有较大的性能损失这里的性能损失指的是增加了额外的同步和调度开销而可用的资源不变。健壮性降低编写多线程需要更全面更深入的考虑在一个多线程程序里因时间分配上的细微偏差或者因共享了不该共享的变量而造成不良影响的可能性是很大的换句话说线程之间是缺乏保护的。缺乏访问控制进程是访问控制的基本粒度在一个线程中调用某些OS函数会对整个进程造成影响。编程难度提高编写与调试一个多线程程序比单线程程序困难得多3. 线程异常单个线程如果出现除零野指针问题导致线程崩溃进程也会随着崩溃线程是进程的执行分支线程出异常就类似进程出异常进而触发信号机制终止进程进程终止该进程内的所有线程也就随即退出4. 线程用途合理的使用多线程能提高CPU密集型程序的执行效率合理的使用多线程能提高IO密集型程序的用户体验如生活中我们一边写代码一边下载开发工具就是多线程运行的一种表现5. Linux进程VS线程 – 哪些资源共享哪些独占进程间具有独立性线程共享地址空间也就共享进程资源5.1 进程和线程进程是资源分配的基本单位线程是调度的基本单位线程共享进程数据但也拥有自己的一部分私有数据:线程ID一组寄存器线程的上下文数据栈errno信号屏蔽字调度优先级5.2 进程的多个线程共享同一地址空间Text Segment 、Data Segment 都是共享的如果定义一个函数在各线程中都可以调用如果定义一个全局变量在各线程中都可以访问到除此之外各线程还共享以下进程资源和环境:文件描述符表每种信号的处理方式(SIG_ IGN、SIG_ DFL或者自定义的信号处理函数)当前工作目录用户id和组id进程和线程的关系如下图:5.3 关于进程线程的问题如何看待之前学习的单进程具有一个线程执行流的进程总结线程切换的成本远低于进程切换核心原因之一是线程切换无需刷新 CPU Cache/TLB快表仅需切换线程私有上下文而进程切换需切换虚拟地址空间导致 Cache/TLB 完全失效。线程的栈是独占的每个线程拥有独立的私有栈空间这是线程核心的私有资源之一。附录页表和页表项/*We keep two sets of PTEs - the hardware and the linux version. *This allows greater flexibility in the way we map the Linux bits *onto the hardware tables,and allows us to have YOUNG and DIRTY bits. * *The PTE table pointer refers to the hardware entries;he Linux *entries are stored 1024 bytes below. */// PTE的32位数据中低12位即为以下各种标志位高20位分别是页目录项和页表项// 页表标志位#defineL_PTE_PRESENT(10)#defineL_PTE_FILE(11)/* only when !PRESENT */#defineL_PTE_YOUNG(11)#defineL_PTE_BUFFERABLE(12)/* matches PTE */#defineL_PTE_CACHEABLE(13)/* matches PTE */#defineL_PTE_USER(14)#defineL_PTE_WRITE(15)#defineL_PTE_EXEC(16)#defineL_PTE_DIRTY(17)#defineL_PTE_COHERENT(19)/* I/O coherent (xsc3) */#defineL_PTE_SHARED(110)/* shared between CPUs (v6) */#defineL_PTE_ASID(111)/* non-global (use ASID, v6) */// 页表是typedefstruct{unsignedlongpte;}pte_t;// 页表项typedefstruct{unsignedlongpgd;}pgd_t;// 页全局目录项pgd_t*pgd_alloc(structmm_struct*mm){pgd_t*ret,*init;ret(pgd_t*)__get_free_page(GFP_KERNEL|__GFP_ZERO);initpgd_offset(init_mm,0UL);if(ret){#ifdefCONFIG_ALPHA_LARGE_VMALLOCmemcpy(retUSER_PTRS_PER_PGD,initUSER_PTRS_PER_PGD,(PTRS_PER_PGD-USER_PTRS_PER_PGD-1)*sizeof(pgd_t));#elsepgd_val(ret[PTRS_PER_PGD-2])pgd_val(init[PTRS_PER_PGD-2]);#endif/* The last PGD entry is the VPTB self-map. */pgd_val(ret[PTRS_PER_PGD-1])pte_val(mk_pte(virt_to_page(ret),PAGE_KERNEL));}returnret;}pte_t*pte_alloc_one_kernel(structmm_struct*mm,unsignedlongaddress){pte_t*pte(pte_t*)__get_free_page(GFP_KERNEL|__GFP_REPEAT|__GFP_ZERO);returnpte;}structmm_struct{structvm_area_struct*mmap;/* list of VMAs */structrb_rootmm_rb;structvm_area_struct*mmap_cache;/* last find_vma result */unsignedlong(*get_unmapped_area)(structfile*filp,unsignedlongaddr,unsignedlonglen,unsignedlongpgoff,unsignedlongflags);void(*unmap_area)(structmm_struct*mm,unsignedlongaddr);unsignedlongmmap_base;/* base of mmap area */unsignedlongtask_size;/* size of task vm space */unsignedlongcached_hole_size;/* if non-zero, the largest hole below free_area_cache */unsignedlongfree_area_cache;/* first hole of size cached_hole_size or larger */pgd_t*pgd;// 页目录起始地址};