深入理解 Linux 内核

news2024/12/23 22:34:34

在这里插入图片描述

文章目录

  • 前言
  • 一、内存寻址
    • 1、内存地址
    • 2、硬件中的分段
      • (1)段选择符
    • 3、Linux 中的分段
      • (1)Linux GDT
      • (2)Linux LDT
    • 4、硬件中的分页
    • 5、Linux 中的分页
      • (1)进程页表
      • (2)内核页表
      • (3)临时内核页表
      • (4)当 RAM 小于 896MB时的最终内核页表
      • (5)当 RAM 大小在 896MB 和 4096MB 之间时的最终内核页表
      • (6)当 RAM 大于 4096MB 时的最终内核页表
      • (7)固定映射的线性地址
      • (8)处理硬件高速缓存和 TLB


前言

  本文主要用来摘录《深入理解 Linux 内核》一书中学习知识点,本书基于 Linux 2.6.11 版本,源代码摘录基于 Linux 2.6.34 ,两者之间可能有些出入。


一、内存寻址

1、内存地址

  可参考 ⇒ 1、内存寻址

2、硬件中的分段

  可参考 ⇒ 五、分段机制

(1)段选择符

  80x86 中有 6 个段寄存器,分别为 csssdsesfsgs6 个寄存器中 3 个有专门的用途:可参考 ⇒ 3、段选择符

  • cs 代码段寄存器,指向包含程序指令的段。
  • ss 栈段寄存器,指向包含当前程序栈的段。
  • ds 数据段寄存器,指向包含静态数据或者全局数据段。

  其它 3 个段寄存器做一般用途,可以指向任意的数据段。

3、Linux 中的分段

  分段可以给每一个进程分配不同的线性地址空间,而分页可以把同一线性地址空间映射到不同的物理空间。与分段相比,Linux 更喜欢使用分页方式,因为:

  • 当所有进程使用相同的段寄存器值时,内存管理变得更简单,也就是说它们能共享同样的一组线性地址。
  • Linux 设计目标之一是可以把它移植到绝大多数流行的处理器平台上。然而,如 RISC 体系结构对分段的支持很有限。

  2.6 版的 Linux 只有在 80x86 结构下才需要使用分段。

  运行在用户态的所有 Linux 进程都使用一对相同的段来对指令和数据寻址。这两个段就是所谓的用户代码段和用户数据段。类似地,运行在内核态的所有 Linux 进程都使用一对相同的段对指令和数据寻址:它们分别叫做内核代码段和内核数据段。下图显示了这四个重要段的段描述符字段的值。可参考 ⇒ 4、段描述符 一文。

在这里插入图片描述
  相应的段选择符由宏 __USER_CS__USER_DS__KERNEL_CS__KERNEL_DS 分别定义。例如,为了对内核代码段寻址,内核只需要把 __KERNEL_CS 宏产生的值装进 cs 段寄存器即可。

  注意,与段相关的线性地址从 0 开始,达到 232 - 1 的寻址限长。这就意味着在用户态或内核态下的所有进程可以使用相同的逻辑地址。

  所有段都从 0x00000000 开始,这可以得出另一个重要结论,那就是在 Linux 下逻辑地址与线性地址是一致的,即逻辑地址的偏移字段的值与相应的线性地址的值总是一致的。

  如前所述,CPU 的当前特权级(CPL)反映了进程是在用户态还是内核态,并由存放在 CS 寄存器中的段选择符的 RPL 字段指定。只要当前特权级被改变,一些段寄存器必须相应地更新。例如,当 CPL=3 时(用户态),ds 寄存器必须含有用户数据段的段选择符,而当 CPL=0 时,ds 寄存器必须含有内核数据段的段选择符。

  类似的情况也出现在 ss 寄存器中。当 CPL3 时,它必须指向一个用户数据段中的用户栈,而当 CPL0 时,它必须指向内核数据段中的一个内核栈。当从用户态切换到内核态时,Linux 总是确保 ss 寄存器装有内核数据段的段选择符。

  当对指向指令或者数据结构的指针进行保存时,内核根本不需要为其设置逻辑地址的段选择符,因为 cs 寄存器就含有当前的段选择等。例如,当内核调用一个函数时,它执行一条 call 汇编语言指令,该指令仅指定其逻辑地址的偏移量部分,而段选择符不用设置,它已经隐含在 cs 寄存器中了。因为"在内核态执行"的段只有一种,叫做代码段,由宏 __KERNEL_CS 定义,所以只要当 CPU 切换到内核态时将 __KERNEL_CS 装载进 cs 就足够了。同样的道理也适用于指向内核数据结构的指针(隐含地使用 ds 寄存器)以及指向用户数据结构的指针(内核显式地使用 es 寄存器)。

  除了刚才描述的 4 个段以外,Linux 还使用了其他几个专门的段。我们将在下一节讲述 Linux GDT 的时候介绍它们。

(1)Linux GDT

  在单处理器系统中只有一个 GDT,而在多处理器系统中每个 CPU 对应一个 GDT。 所有的 GDT 都存放在 cpu_gdt_table 数组中,而所有 GDT 的地址和它们的大小(当初始化 gdtr 寄存器时使用)被存放在 cpu_gdt_descr 数组中。如果你到源代码索引中查看,可以看到这些符号都在文件 arch/i386/kernel/head.S 中被定义。本书中的每一个宏、函数和其他符号都被列在源代码索引中,所以能在源代码中很方便地找到它们。

  图 2-6 是 GDT 的布局示意图。每个 GDT 包含 18 个段描述符和 14 个空的,未使用的,或保留的项。插入未使用的项的目的是为了使经常一起访问的描述符能够处于同一个 32 字节的硬件高速缓存行中(参见本章 后面"硬件高速缓存"一节)。

  每一个 GDT 中包含的 18 个段描述符指向下列的段:

  • 用户态和内核态下的代码段和数据段共 4 个(参见前面一节)。
  • 任务状态段(TSS),每个处理器有 1 个。每个 TSS 相应的线性地址空间都是内核数据段相应线性地址空间的一个小子集。所有的任务状态段都顺序地存放在 init_tss 数组中,值得特别说明的是,第 nCPUTSS 描述符的 Base 字段指向 init_tss 数组的第 n 个元素。G(粒度)标志被清 0 ,而 Limit 字段置为 0xeb,因为 TSS 段是 236 字节长。Type 字段置为 911(可用的 32TSS),且 DPL 置为 0,因为不允许用户态下的进程访问 TSS 段。在第三章"任务状态段"一节你可以找到 Linux 是如何使用 TSS 的细节。参考 ==> 3.1 任务状态段

在这里插入图片描述

  • 1 个包括缺省局部描述符表的段,这个段通常是被所有进程共享的段 (参见下一节)。
  • 3 个局部线程存储(Thread-Local Storage,TLS) 段:这种机制允许多线程应用程序使用最多 3 个局部于线程的数据段。系统调用 set_thread_area()get_thread_area() 分别为正在执行的进程创建和撤消一个 TLS 段。
  • 与高级电源管理(AMP)相关的 3 个段:由于 BIOS 代码使用段,所以当 Linux APM 驱动程序调用 BIOS 函数来获取或者设置 APM 设备的状态时,就可以使用自定义的代码段和数据段。
  • 与支持即插即用(PnP)功能的 BIOS 服务程序相关的 5 个段:在前一种情况下,就像前述与 AMP 相关的 3 个段的情况一样,由于 BIOS 例程使用段,所以当 LinuxPnP 设备驱动程序调用 BIOS 函数来检测 PnP 设备使用的资源时,就可以使用自定义的代码段和数据段。
  • 被内核用来处理"双重错误"(译注 1)异常的特殊 TSS 段(参见第四章的"异常"一节)。

  如前所述,系统中每个处理器都有一个 GDT 副本。除少数几种情况以外,所有 GDT 的副本都存放相同的表项。首先,每个处理器都有它自己的 TSS 段,因此其对应的 GDT 项不同。其次,GDT 中只有少数项可能依赖于 CPU 正在执行的进程(LDTTLS 段描述符)。最后,在某些情况下,处理器可能临时修改 GDT 副本里的某个项,例如,当调用 APMBIOS 例程时就会发生这种情况。

(2)Linux LDT

  大多数用户态下的 Linux 程序不使用局部描述符表,这样内核就定义了一个缺省的 LDT 供大多数进程共享。缺省的局部描述符表存放在 default_ldt 数组中。它包含 5 个项,但内核仅仅有效地使用了其中的两个项:用于 iBCS 执行文件的调用门和 Solaris/x86 可执行文件的调用门(参见第二十章的"执行域"一节)。调用门是 80x86 微处理器提供的一种机制,用于在调用预定义函数时改变 CPU 的特权级,由于我们不会再更深入地讨论它们,所以请参考 Intel 文档以获取更多详情。

  在某些情况下,进程仍然需要创建自己的局部描述符表。这对有些应用程序很有用,像 Wine 那样的程序,它们执行面向段的微软 Windows 应用程序。modify_ldt() 系统调用允许进程创建自己的局部描述符表。
  任何被 modify_ldt() 创建的自定义局部描述符表仍然需要它自己的段。当处理器开始执行拥有自定义局部描述符表的进程时,该 CPUGDT 副本中的 LDT 表项相应地就被修改了。
  用户态下的程序同样也利用 modify_ldt() 来分配新的段,但内核却从不使用这些段,它也不需要了解相应的段描述符,因为这些段描述符被包含在进程自定义的局部描述符表中了。

4、硬件中的分页

  参考 ⇒ 六、分页机制

5、Linux 中的分页

  Linux 采用了一种同时适用于 32 位和 64 位系统的普通分页模型。正像前面 “64 位系统中的分页” 一节所解释的那样,两级页表对 32 位系统来说已经足够了,但 64 位系统需要更多数量的分页级别。直到 2.6.10 版本,Linux 采用三级分页的模型。从 2.6.11 版本开始,采用了四级分页模型(注 5)。图 2-12 中展示的 4 种页表分别被为:

  • 页全局目录(Page Global Directory
  • 页上级目录(Page Upper Directory
  • 页中间目录(Page Middle Directory
  • 页表(Page Table

  页全局目录包含若干页上级目录的地址,页上级目录又依次包含若干页中间目录的地址,而页中间目录又包含若干页表的地址。每一个页表项指向一个页框。线性地址因此被分成五个部分。图 2-12 没有显示位数,因为每一部分的大小与具体的计算机体系结构有关。

在这里插入图片描述

  对于没有启用物理地址扩展的 32 位系统,两级页表已经足够了。Linux 通过使 “页上级目录” 位和 “页中间目录” 位全为 0,从根本上取消了页上级目录和页中间目录字段。不过,页上级目录和页中间目录在指针序列中的位置被保留,以便同样的代码在 32 位系统和 64 位系统下都能使用。内核为页上级目录和页中间目录保留了一个位置,这是通过把它们的页目录项数设置为 1,并把这两个目录项映射到页全局目录的一个适当的目录项而实现的。

  启用了物理地址扩展的 32 位系统使用了三级页表。Linux 的页全局目录对应 80x86 的页目录指针表(PDPT),取消了页上级目录,页中间目录对应 80x86 的页目录,Linux 的页表对应 80x86 的页表。

  最后,64 位系统使用三级还是四级分页取决于硬件对线性地址的位的划分(见表 2-4)。

  Linux 的进程处理很大程度上依赖于分页。事实上,线性地址到物理地址的自动转换使下面的设计目标变得可行:

  • 给每一个进程分配一块不同的物理地址空间,这确保了可以有效地防止寻址错误。
  • 区别页(即一组数据)和页框(即主存中的物理地址)之不同。这就允许存放在某个页框中的一个页,然后保存到磁盘上,以后重新装入这同一页时又可以被装在不同的页框中。这就是虚拟内存机制的基本要素(参见第十七章)。

  在本章剩余的部分,为了具体起见,我们将涉及 80x86 处理器使用的分页机制。

  我们将在第九章看到,每一个进程有它自己的页全局目录和自己的页表集。当发生进程切换时(参见第三章 “进程切换” 一节),LinuxCR3 控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符的值装入 CR3 寄存器中。因此,当新进程重新开始在 CPU 上执行时,分页单元指向一组正确的页表。

(1)进程页表

  进程的线性地址空间分成两部分:

  • 0x000000000xbfffffff 的线性地址,无论进程运行在用户态还是内核态都可以寻址。
  • 0xc00000000xffffffff 的线性地址,只有内核态的进程才能寻址。

  当进程运行在用户态时,它产生的线性地址小于 0xc0000000 ;当进程运行在内核态时,它执行内核代码,所产生的地址大于等于 0xc0000000 。但是,在某些情况下,内核为了检索或存放数据必须访问用户态线性地址空间。

(2)内核页表

(3)临时内核页表

(4)当 RAM 小于 896MB时的最终内核页表

(5)当 RAM 大小在 896MB 和 4096MB 之间时的最终内核页表

(6)当 RAM 大于 4096MB 时的最终内核页表

(7)固定映射的线性地址

(8)处理硬件高速缓存和 TLB

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

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

相关文章

CRLF注入漏洞、URL重定向、资源处理拒绝服务详细介绍(附实例)

目录 一、CRLF注入漏洞 漏洞简介 演示介绍 漏洞检测工具:CRLFuzz 二、URL重定向漏洞 漏洞简介 漏洞相关业务 演示介绍 创建重定向虚假钓鱼网站 三、WEB 拒绝服务 简介 漏洞相关业务 演示介绍 一、CRLF注入漏洞 漏洞简介 CRLF 注入漏洞,是因…

centos7 firewall-cmd主机之间端口转发

目录 1. firewalld1.1 firewalld守护进程1.2 控制端口/服务1.3 伪装IP1.4 端口转发 2. 案例2.1 配置ServerA2.2 安装nginx测试 (可选)2.3 开启端口2.4 伪装IP2.5 端口转发2.6 配置ServerB2.7 修改nginx页面显示内容2.8 访问ServerB2.9 访问ServerA 1. fi…

低代码是开发的未来,还是只能解决边角问题的鸡肋?

随着互联网行业寒冬期的到来,降本增效、开源节流几乎成为了全球互联网厂商共同的应对措施,甚至高薪酬程序员的“35岁危机”一下子似乎变成了现实。程序员的高薪吸引了各行各业的“跨界选手”,是编程门槛降低了吗?不全是&#xff0…

搭建linux邮件服务器

参考:企业级邮件服务器实战_哔哩哔哩_bilibili Linux 平台开源免货的邮件服务器包括: Sendmail、Postix、Omail ; 邮件服务器构成了电子邮件系统的核心,每个收信人都有一个位于某个邮件服务器上的邮箱(mailbox),一个邮件消息的典型旅程是从…

管道命令(cut、grep、sort、wc、uniq、tee、tr、col、join、paste、expand/unexpand、split、xargs)

文章目录 管道命令(pipe)选取命令:cut、grepcut使用案例cut的优点缺点 grep使用案例 排序命令:sort、wc、uniqsort使用案例 uniq使用案例 wc使用案例 双向重定向:tee使用案例 字符转换命令:tr、col、join、paste、expandtr使用案例…

非量表数据应该如何分析?

问卷中的非量表数据应该怎么分析? 样本特征分析 对于非量表题的描述可以使用频数分析或者可视化图形进行描述,比如单选题也可以使用柱形图等进行展示,通过结果展示了解样本的基本情况,最后结合分析结果提出建议等。差异分析 除此之…

mybatis中进行时间范围查询

一 oracle数据库 数据库时间类型为DATE TO_CHAR 把日期或数字转换为字符串 TO_DATE 把字符串转换为数据库中的日期类型 TO_DATE(char, ‘格式’) TO_NUMBER 将字符串转换为数字 TO_NUMBER(char, ‘格式’) 1、入参是String类型的数据 mybatis 处理时间范围 使用TO_DATE函数…

2023个税验证Excel表

根据北京市工资计算公式制作该表格,用来验证每月发放工资是否有误,统计年度总收入等。 下载链接如下(提升等级用): https://download.csdn.net/download/wayright/87732783 不下载,按照上面表格数据自己制作…

下载高清图片素材,就上这6个网站,免费还能商用

图片素材网站我已经推荐过很多了,今天就再给大家推荐6个高清图片素材网,免费下载哦~建议收藏起来。 1、菜鸟图库 https://www.sucai999.com/pic.html?vNTYwNDUx 我推荐过很多次的一个设计素材网站,除了设计类,还有很多自媒体可…

el-input-number 输入框添加单位

需求 使用 element-ui 的 InputNumber 控件,实现金额填写,需要在数字后面添加一个单位:元 实现效果 代码部分 <template><el-dialogclass="morendialog":title="(formData.id ? 修改 : 新增) + title":visi

没有什么比破除束缚更自由的事情了

我发现&#xff0c;我时常处于一种自我消耗、内耗的状态中&#xff0c;令我难以振作起来去改变现状。因此&#xff0c;“拒绝内耗&#xff0c;提升表达力&#xff0c;努力提升自我”成为了我必须完成的小目标。 在高中和大学的的时候&#xff0c;有一段时间&#xff0c;我曾经…

【牛客网】迷宫问题与年终奖

目录 一、编程题 1.迷宫问题 2.年终奖 二、选择题 1、将N条长度均为M的有序链表进行合并&#xff0c;合并以后的链表也保持有序&#xff0c;时间复杂度为()? 2、大小为MAX的循环队列中&#xff0c;f为当前对头元素位置&#xff0c;r为当前队尾元素位置(最后一个元素的位…

Ansys Zemax | 设计抬头显示器时要使用哪些工具 – 第一部分

本文演示了如何使用OpticStudio工具设计分析抬头显示器(HUD)性能&#xff0c;即全视场像差(FFA)和NSC矢高图。(联系我们获取文章附件) 初始结构 HUD简介 以下为HUD的示意图。液晶显示器作为光源发光&#xff0c;光线被HUD的两个反射镜反射&#xff0c;然后通过风挡玻璃反射&am…

零死角玩转stm32中级篇3-SPI总线

一.基础知识 1.什么是SPI SPI&#xff08;Serial Peripheral Interface&#xff0c;串行外设接口&#xff09;是一种同步的串行通信协议&#xff0c;它被用于在微控制器、存储器芯片、传感器和其他外围设备之间传输数据。SPI通常由四个线组成&#xff1a;时钟线&#xff08;SC…

对git的简单总结

Git的基本使用 配置用户名和邮箱常见的操作查看仓库的状态远端仓库整体流程分支本地分支命令远端分支命令 这几天在做毕业设计&#xff0c;需要用到git&#xff0c;所以简单总结一下git的基本使用。 配置用户名和邮箱 git config --global user.name "Your Name" g…

【Vue】Vue-cli,创建项目设置自定义默认配置

Vue2.0&#xff0c;Vue-cli项目配置 步骤一&#xff0c;打开文件夹&#xff0c;导航栏输入cmd&#xff0c;打开命令行窗口步骤二&#xff0c;输入命令步骤三&#xff0c;选择第三个自定义新建项目步骤四&#xff0c;选择需要的项目模块&#xff0c;空格选择完&#xff0c;回车步…

Semaphore详解

Semaphore的基本使用场景是限制一定数量的线程能够去执行. 举个简单的例子: 一个单向隧道能同时容纳10个小汽车或5个卡车通过(1个卡车等效与2个小汽车), 而隧道入口记录着当前已经在隧道内的汽车等效比重. 比如1个小汽车和1个卡车, 则隧道入口显示3. 若隧道入口显示10表示已经…

《string的模拟实现》

本文主要介绍库里面string类的模拟实现 文章目录 前言一、string的构造函数①无参的构造函数②带参的构造函数③修改构造函数 二、析构函数三、拷贝构造四、赋值重载五、返回size 、capacity和empty六、[]的运算符重载七、迭代器① 正向迭代器。② 正向const迭代器 八、string比…

PointPillars点云编码器代码运行过程中的问题及解决

PointPillars:点云编码器&#xff0c;编码特征可以与任何标准的 2D 卷积检测架构一起使用。任务是目标检测。来自CVPR2019 论文地址&#xff1a;https://arxiv.org/pdf/1812.05784.pdf 代码地址&#xff1a;GitHub - nutonomy/second.pytorch: PointPillars for KITTI object…

【LeetCode】297. 二叉树的序列化与反序列化

1.问题 序列化是将一个数据结构或者对象转换为连续的比特位的操作&#xff0c;进而可以将转换后的数据存储在一个文件或者内存中&#xff0c;同时也可以通过网络传输到另一个计算机环境&#xff0c;采取相反方式重构得到原数据。 请设计一个算法来实现二叉树的序列化与反序列…