《深入分析Linux内核源代码》读后感 --所有的进程都使用相同的逻辑地址空间,那么不同的进程是如何区分自己的数据段和代码段的呢

书中讲到从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,每个进程的逻辑地址空间范围为0~4GB,而且段基址也一样。那么不同的进程是如何区分自己的数据段和代码段的呢。看到另外一篇博文介绍了:linux使用了物理内存写时复制的机制。原来是因为写时复制的机制让不同的进程区分自己的代码和数据的。下面分两半部分,分别介绍一下linux的分段机制和写时复制机制。学习就需要融会贯通。

1. 先粘贴一下书中对Linux分段的介绍:

2.3.7 Linux中的段
在这里插入图片描述
图2.9 逻辑—线性地址转换
在这里插入图片描述
图2.10 段描述符的一般格式

Intel微处理器的段机制是从8086开始提出的,那时引入的段机制解决了从CPU内部16位地址到20位实地址的转换。为了保持这种兼容性,386仍然使用段机制,但比以前复杂得多。因此,Linux内核的设计并没有全部采用Intel所提供的段方案,仅仅有限度地使用了一下分段机制。这不仅简化了Linux内核的设计,而且为把Linux移植到其他平台创造了条件,因为很多RISC处理器并不支持段机制。但是,对段机制相关知识的了解是进入Linux内核的必经之路。

从2.2版开始,Linux让所有的进程(或叫任务)都使用相同的逻辑地址空间,因此就没有必要使用局部描述符表LDT。但内核中也用到LDT,那只是在VM86模式中运行Wine,因为就是说在Linux上模拟运行Winodws软件或DOS软件的程序时才使用。

Linux在启动的过程中设置了段寄存器的值和全局描述符表GDT的内容,段的定义在include/asm-i386/segment.h中:
#define __KERNEL_CS     0x10    /* 内核代码段, index=2,TI=0,RPL=0 */
 
#define __KERNEL_DS     0x18    /* 内核数据段, index=3,TI=0,RPL=0 */
 
#define __USER_CS       0x23    /* 用户代码段, index=4,TI=0,RPL=3 */
 
#define __USER_DS       0x2B    /* 用户数据段, index=5,TI=0,RPL=3 */

从定义看出,没有定义堆栈段,实际上,Linux内核不区分数据段和堆栈段,这也体现了Linux内核尽量减少段的使用。因为没有使用LDT,因此,TI=0,并把这4个段都放在GDT中, index就是某个段在GDT表中的下标。内核代码段和数据段具有最高特权,因此其RPL为0,而用户代码段和数据段具有最低特权,因此其RPL为3。可以看出,Linux内核再次简化了特权级的使用,使用了两个特权级而不是4个。

全局描述符表的定义在arch/i386/kernel/head.S中:
ENTRY(gdt_table)
    .quad 0x0000000000000000    /* NULL descriptor */
    .quad 0x0000000000000000    /* not used */
    .quad 0x00cf9a000000ffff    /* 0x10 kernel 4GB code at 0x00000000 */
    .quad 0x00cf92000000ffff    /* 0x18 kernel 4GB data at 0x00000000 */
    .quad 0x00cffa000000ffff    /* 0x23 user   4GB code at 0x00000000 */
    .quad 0x00cff2000000ffff    /* 0x2b user   4GB data at 0x00000000 */
    .quad 0x0000000000000000    /* not used */
    .quad 0x0000000000000000    /* not used */
    /*
     * The APM segments have byte granularity and their bases
     * and limits are set at run time.
     */
    .quad 0x0040920000000000    /* 0x40 APM set up for bad BIOS's */
    .quad 0x00409a0000000000    /* 0x48 APM CS    code */
    .quad 0x00009a0000000000    /* 0x50 APM CS 16 code (16 bit) */
    .quad 0x0040920000000000    /* 0x58 APM DS    data */
    .fill NR_CPUS*4,8,0     /* space for TSS's and LDT's */

从代码可以看出,GDT放在数组变量gdt_table中。按Intel规定,GDT中的第一项为空,这是为了防止加电后段寄存器未经初始化就进入保护模式而使用GDT的。第二项也没用。从下标2到5共4项对应于前面的4种段描述符值。对照图2.10,从描述符的数值可以得出:

·段的基地址全部为0x00000000
·段的上限全部为0xffff(**即每个段的最大值limit为64k,由于粒度是4KB,每个段的最大值是256MB**)
·段的粒度G为1,即段长单位为4KB
·段的D位为1,即对这四个段的访问都为32位指令
·段的P位为1,即四个段都在内存。

由此可以得出,每个段的逻辑地址空间范围为0~4GB。读者可能对此不太理解,但只要对照图2.9就可以发现,这种设置既简单又巧妙。因为每个段的基地址为0,因此,逻辑地址到线性地址映射保持不变,也就是说,偏移量就是线性地址,我们以后所提到的逻辑地址(或虚拟地址)和线性地址指的也就是同一地址。看来,Linux巧妙地把段机制给绕过去了,而完全利用了分页机制。

从逻辑上说,Linux巧妙地绕过了逻辑地址到线性地址的映射,但实质上还得应付Intel所提供的段机制。只不过,Linux把段机制变得相当简单,它只把段分为两种:用户态(RPL=3)的段和内核态(RPL=0)的段,因此,描述符投影寄存器的内容很少发生变化,只在进程从用户态切换到内核态或者反之时才发生变化。另外,用户段和内核段的区别也仅仅在其RPL不同,因此内核根本无需访问描述符投影寄存器,当然也无需访问GDT,而仅从段寄存器的最低两位就可以获取RPL的信息。Linux这样设计所带来的好处是显而易见的,Intel的分段部件对Linux性能造成的影响可以忽略不计。

在上面描述的GDT表中,紧接着那四个段描述的两个描述符被保留,然后是四个高级电源管理(APM)特征描述符,对此不进行详细讨论。

按Intel的规定,每个进程有一个任务状态段(TSS)和局部描述符表LDT,但Linux也没有完全遵循Intel的设计思路。如前所述,Linux的进程没有使用LDT,而对TSS的使用也非常有限,每个CPU仅使用一个TSS。

通过上面的介绍可以看出,Intel的设计可谓周全细致,但Linux的设计者并没有完全陷入这种沼泽,而是选择了简洁而有效的途径,以完成所需功能并达到较好的性能为目的。

2.linux使用的物理内存写时复制的机制介绍

在 Linux 系统中,调用 fork 系统调用创建子进程时,并不会把父进程所有占用的内存页复制一份,而是与父进程共用相同的内存页,而当子进程或者父进程对内存页进行修改时才会进行复制 —— 这就是著名的 写时复制 机制。

下面我们将分析 Linux 写时复制(Copy On Write) 机制的原理。

虚拟内存与物理内存

进程的内存可分为 虚拟内存 和 物理内存。

物理内存:就是电脑安装的内存条,如果电脑安装了2GB的内存条,那么系统就用于 0 ~ 2GB 的物理内存空间。
虚拟内存:虚拟内存是使用软件虚拟的,在 32 位操作系统中,每个进程都独占 4GB 的虚拟内存空间。

应用程序使用的是 虚拟内存,比如 C 语言取地址操作符号 & 所得到的地址就是 虚拟内存地址。而 虚拟内存地址 需要映射到 物理内存地址 才能使用,如果使用没有映射的 虚拟内存地址,将会导致 缺页异常。

虚拟内存地址 映射到 物理内存地址 如下图所示:
在这里插入图片描述
如上图所示,进程A与进程B的相同 虚拟内存地址 映射到不同的 物理内存地址,这就是不同进程的相同虚拟内存地址互不影响的原因。

写时复制原理

前面介绍了 虚拟内存 与 物理内存 的概念,接下来将会介绍 Linux 写时复制 的原理。

前面说过,虚拟内存 需要与 物理内存 进行映射才能使用,如果不同进程的 虚拟内存地址 映射到相同的 物理内存地址,那么就实现了共享内存的机制。如下图所示:
在这里插入图片描述
由于进程A的 虚拟内存M 与进程B的 虚拟内存M’ 映射到相同的 物理内存G,所以当修改进程A 虚拟内存M 的数据时,进程B 虚拟内存M’ 的数据也会跟着改变。

Linux 为了加速创建子进程过程与节省内存使用的原因,实现了 写时复制 的机制。

写时复制 的原理大概如下:

创建子进程时,将父进程的 虚拟内存 与 物理内存 映射关系复制到子进程中,并将内存设置为只读(设置为只读是为了当对内存进行写操作时触发 缺页异常)。
当子进程或者父进程对内存数据进行修改时,便会触发 写时复制 机制:将原来的内存页复制一份新的,并重新设置其内存映射关系,将父子进程的内存读写权限设置为可读写。

写时复制 过程如下图所示:

在这里插入图片描述
当创建子进程时,父子进程指向相同的 物理内存,而不是将父进程所占用的 物理内存 复制一份。这样做的好处有两个:

加速创建子进程的速度。 
减少进程对物理内存的使用。

如上图所示,当父进程调用 fork 创建子进程时,父进程的 虚拟内存页M 与子进程的 虚拟内存页M 映射到相同的 物理内存页G,并且把父进程与子进程的 虚拟内存页M 都设置为只读(因为设置为只读后,对内存页进行写操作时,将会发生 缺页异常,从而内核可以在缺页异常处理函数中进行物理内存页的复制)。

当子进程对 虚拟内存页M 进行写操作,便会触发 缺页异常(因为已经将 虚拟内存页M 设置为只读)。在缺页异常处理函数中,对 物理内存页G 进行复制一份新的 物理内存页G’,并且将子进程的 虚拟内存页M 映射到 物理内存页G’,同时将父子进程的 虚拟内存页M 设置为可读写。总结本篇文章主要介绍了 Linux 写时复制 的原理,写时复制 是 Linux 创建子进程高效的关键所在,而且还能节省对物理内存使用

上半部分出处:
Linux中的段
摘抄:陈莉君 《深入分析Linux内核源代码》
https://mrhopehub.github.io/2014/11/22/linux-segment.html

下半部分出处:
作者:Jayden
链接:https://zhuanlan.zhihu.com/p/366707663
来源:知乎
著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。

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

如若内容造成侵权/违法违规/事实不符,请联系我们进行投诉反馈qq邮箱809451989@qq.com,一经查实,立即删除!

相关文章

FFmpeg之AVFormat

文章目录 一、概述二、解封装流程三、重要结构体3.1、AVFormatContext3.2、AVInputFormat3.3、AVOutputFormat3.4、AVStream 四、重要函数分析4.1、avformat_alloc_context4.2、avformat_open_input4.2.1、init_input4.2.2、av_probe_input_format2 4.3、avformat_find_stream_…

RAG基础功能优化、以及RAG架构优化

RAG基础功能优化 对RAG的基础功能优化,我们要从RAG的流程入手[1],可以在每个阶段做相应的场景优化。 从RAG的工作流程看,能优化的模块有:文档块切分、文本嵌入模型、提示工程优化、大模型迭代。下面针对每个模块分别做说明&#…

【C++】vector的使用及模拟实现

目录 一、vector的介绍及使用1.1 介绍vector1.2 vector的使用1.2.1 构造1.2.2 遍历访问1.2.3 容量空间1.2.4 增删查改 二、vector的模拟实现2.1 成员变量2.2 迭代器相关函数2.3 构造-析构-赋值重载2.3.1 无参构造2.3.2 有参构造12.3.3 有参构造22.3.4 拷贝构造2.3.5 赋值重载2.…

【线路图】 DC-DC升压恒压控制驱动芯片 2.8-40V AP8100

说明 AP8100 是一款外围电路简单的 BOOST 升压恒压控 制驱动芯片,适用于 2.8-40V 输入电压范围的升压恒 压电源应用领域,启动电压可以低至 2.5V 。 芯片会根据负载的大小自动切换 PWM , PFM 和 BURST 模式以提高各个负载端的…

【U盘修复】

U盘当成重装系统的U盘启动器之后,可能会从128G显示为 32 G,并且Windows自带的分区工具不管用,其实并不是U盘坏了,此时你需要将此U盘的所有分区删除,然后创建新的分区。 推荐使用的一款分区(Partition&…

02-python的基础语法-01python字面量/注释/数据类型/数据类型转换

字面量 在代码中,被写下来的固定的值,被称为字面量。 python中哪些值是可以被写出来的呢?又该如何写呢? 字符串:又称文本,是由任意数量的字符如中文,英文,各类符号,数字组成。 这…

一文解析 Copycat Dex与 Bitcat Dex的区别

Copycat Dex和 Bitcat Dex都带一个 Cat 并且都是衍生品协议,很多人都会误认为这两个是同一个项目,实际不然。它们是面向两个不同赛道、不同资产类型的衍生品项目。 Copycat Dex和 Bitcat Dex都是衍生品 DEX,它们最本质的区别主要在于&#xf…

前后端跨域问题

告别烦恼,彻底解决跨域问题的终极指南-chrome的安全进阶之路_chrom 强制跨域-CSDN博客

Linux内核架构和工作原理详解(二)

Linux内核体系结构简析简析 图1 Linux系统层次结构 最上面是用户(或应用程序)空间。这是用户应用程序执行的地方。用户空间之下是内核空间,Linux 内核正是位于这里。GNU C Library (glibc)也在这里。它提供了连接内核…

软件测试要学习的基础知识——白盒测试

白盒测试是通过检查软件内部的逻辑结构,对软件中的逻辑路径进行覆盖测试,以确定实际运行状态与预期状态是否一致。 白盒测试又被称为: 透明盒测试 结构化测试 逻辑驱动测试 基于代码的测试 白盒测试的常用技术分类 一、静态分析&#x…

LLM之LangChain(二)| LangChain中的Agent

在本文中,我们将讨论LangChain中的Agent及其各种类型。但在深入研究Agent之前,让我们先了解一下什么是LangChain和Agent。 一、什么是LangChain? LangChain是一种功能强大的自动化工具,可用于各种任务,它提供了可用于…

【AIGC】智能革命的动物寓言——颠覆性的智慧之美

AIGC动物提示词绘画技巧 利用AIGC(Artificial Intelligence Generated Content,人工智能生成内容)技术进行绘画创作时,可以结合上述关键信息来设计和绘制不同风格的角色。具体步骤和讲解如下: 输入关键信息与风格设定…

C语言——详解字符函数和字符串数组(上)

目录 一、strlen的使用和模拟实现 1.strlen()函数的介绍 2.strlen()函数的具体使用 3.strlen函数的注意事项 4.strlen函数的模拟实现 二、strcpy的使用和模拟实现 1.strcpy()函数的介绍 2.strcpy()函数的具体使用 3.strcpy()函数的注意事项 4.strcpy函数的模拟实现 …

数据库概述、部署MySQL服务、必备命令 、密码管理、安装图形软件、SELECT语法 、筛选条件

1 案例1:构建MySQL服务器 1.1 问题 在IP地址192.168.88.50主机和192.168.88.51主机上部署mysql服务练习必备命令的使用 1.2 方案 准备2台虚拟机,要求如下: 1.3 步骤 实现此案例需要按照如下步骤进行。 步骤一:安装软件 命令…

2024.1.16 网络编程 作业

思维导图 练习题 1.基于UDP的TFTP文件传输&#xff0c;实现文件下载上传 #include <myhead.h>int main(int argc, char const *argv[]) {// 创建套接字UDP通信int sockfd socket(AF_INET, SOCK_DGRAM, 0);if (-1 sockfd){perror("socket error");return -1…

Qt-UI界面无法输入名字

在UI界面“在这里输入”&#xff0c;直接双击填写名称&#xff0c;无论是中文还是英文都没有反应。解决方案&#xff1a; 双击“在这里输入之后”&#xff0c;在可编辑状态下&#xff0c;空格→enter键&#xff0c;然后在右下角属性框的title中直接填写中文或英文名&#xff0…

C#核心--思维导图

对应《C#--核心》&#xff08;http://t.csdnimg.cn/cpRbZ&#xff09;

SQL 语言详解

SQL 详解 我们通常可以将 SQL 分为四类&#xff0c;分别是 DDL&#xff08;数据定义语言&#xff09;、DML&#xff08;数据操作语言&#xff09;、DQL&#xff08;数据查询语言&#xff09;和 DCL&#xff08;数据控制语言&#xff09;。DDL 主要用于创建、删除、修改数据库中…

LeetCode刷题---反转链表II

LeetCode官方给出的解题思路 在需要反转的区间里&#xff0c;每遍历到一个节点&#xff0c;让这个新节点来到反转部分的起始位置。 使用了三指针的思想。 定义三个节点: curr&#xff1a;指向待反转区域的第一个节点 left&#xff1b; next&#xff1a;永远指向 curr 的下一个节…

uni-app的组件(一)

scroll-view 可滚动视图区域。用于区域滚动 <scroll-view scroll-y"true" :scroll-top"scrollTop" class"scroll-y" scroll"scroll"><view id"demo1" class"scroll-view-item bg-red">A</view>…