Understanding The Linux Kernel --- Part2 Memory Addressing
内存寻址
操作系统自身不必完全了解物理内存,如今的微处理器包含的硬件线路使内存管理既高效又健壮,所以编程错误就不会对该程序之外的内存产生非法访问
- x86如何进行芯片级内存寻址
- Linux如何利用寻址硬件
x86 三种不同的地址术语
逻辑地址
逻辑地址是机器语言指令中用于表示一个操作数或者指令的地址
不同的机器架构的表现形式不一样,80X86的分段结构将每个逻辑地址都由一个段和一个偏移量来组成。
线性地址或称虚拟地址
线性地址是一个32位的无符号整数,可以用来表达高达4GB的地址,也是程序中实际应用的地址。每个进程都认为自己拥有4GB的地址空间可以使用。范围从0x0000_0000到0xffff_ffff
物理地址
用于内存芯片级内存单元的寻址,它们与微处理器的地址引脚发送到内存总线上的电信号对应
转换线路
内存控制单元MMU的分段单元硬件电路将机器语言中的逻辑地址转换成线性地址
分页单元硬件电路将线性地址转换为物理地址
对物理地址的访问,也就是对RAM芯片的访问,是并发的。多个CPU可以并发地像RAM芯片访问,但是由于对RAM芯片的读写是串行的,所以同一时间对RAM的读写必须得到仲裁:如果发现已经有一个CPU在访问RAM,那么此刻到来的其他CPU必须延迟读写操作知道正在读写的那个CPU读写完毕。
这是对于多处理器而言,但其实对于单处理器来说也是一样的,因为除了单独的那个CPU外,访问RAM芯片的还有DMA。
x86模式下的两种模式的不同硬件分段模型
x86的逻辑地址由分段模型提供,要有段地址和偏移地址
x86提供了段选择符提供段地址,并且为了快速定位到段选择子,处理器提供了段寄存器
段选择符的格式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-EXqAsTrr-1677760557953)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301143659550.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-aRXE72qt-1677760557953)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301145110077.png)]
段寄存器:
CS 代码段寄存器
SS 栈段寄存器
DS 数据段寄存器 通常作为数据源端
ES 额外的馈赠 通用目的的段寄存器 通常作为数据目的端
FS 通用目的的段寄存器
GS 通用目的的段寄存器
段描述符格式:
每个段由一个8字节的段描述符表示,它描述了一个段的所有信息。段描述符全都放在全局描述符表(GDT)或者局部描述符表(LDT)中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jrYtKt4g-1677760557954)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301144735619.png)]
根据type字段的不同,段描述符可以描述多种不同的段:
- 代码段描述符
- 数据段描述符
- 任务状态段描述符TSS
- 局部描述符表描述符
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6C5kJldU-1677760557954)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301145029556.png)]
实模式下寻址
x86:段寄存器值 << 4 + 偏移寄存器值
保护模式下寻址
宏观
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-8EziBXOU-1677760557954)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301145134344.png)]
微观
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-YMBLR4Ju-1677760557955)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301145320158.png)]
2.6版的Linux只有在80X86结构下才使用分段
运行在用户态,使用一对相同的段来寻址指令和数据
运行在内核态,使用另一对相同的段来寻址指令和数据
不需要给每个任务再单独分配段描述符
内核段描述符和用户段描述符虽然起始线性地址和长度都一样但是DPL不一样
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-2NTU6M7I-1677760557955)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301195021762.png)]
(从图中可以看出:寻址上限是2^32-1 都从0x0000_0000说明Linux下逻辑地址和线性地址是一致的,也就是说逻辑地址的偏移量字段与相应的线性地址的值总是一致的)
段选择符使用宏定义:
用户代码段:__USER_CS
用户数据段:__USER_DS
内核代码段:__KERNEL_CS
内核数据段:__KERNEL_DS
为了寻址内核代码:
CS <-- __KERNEL_CS
单处理器使用单个GDT
多出来起使用多个GDT,并且使用数组cpu_gdt_table来组织他们
任务状态段TSS每个处理器有1个
每个TSS相应的线性地址空间都是内核数据段相应的线性地址空间的一个小子集
所有的TSS都顺序地存放在init_tss数组的第N个元素
GDT的布局示意图:
每个GDT包括18个段描述符和14个空的,未使用的保留项
插入未使用的项是为了使经常一起访问的描述符能够处于同一个32Byte的硬件高速缓存中
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-zujSOWoG-1677760557955)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301204842859.png)]
Linux在X86的分段机制上运行,但是巧妙地绕开了分段,Linux主要以分页单元内存管理
分页机制
线性地址翻译成物理地址
其中一个关键任务是将所请求的访问类型与线性地址儿访问全新相比较,如果内存访问无效则产生一个缺页异常page fault
线性地址被分成以固定长度为单位的组 --> 页(page) 页内部连续的线性地址被映射到连续的物理连续地址中
物理地址RAM随之被划分成固定长度 --> 页框(page frame) 物理页
记录 线性地址 <–> 物理地址 映射 关系的数据结构叫 --> 页表(page table)
通过CR0寄存器的PG标志启用分页 PG=0则线性地址被解释成物理地址
常规分页:
Intel 80386 分页单元处理4KB的页
32位线性地址分成3个域
high10 | mid10 | low12Directory | Table | Offset
分级的目的是减少页表每个项(PTE)所要表达的信息量
如果使用一级页表 2^20个表项 每个表项4字节 需要2^20*4 = 4MB RAM
每个活动的进程都需要登记在页目录的其中一项
没必要马上为进程的所有页表都分配RAM,只有进程实际需要一个页表时才分配给它RAM会更有效率
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qBdpKLqN-1677760557956)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301214414398.png)]
CR3存放页目录所在的物理地址
页目录和页表项的格式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hi69zt6y-1677760557956)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302150222919.png)]
每个字段的含义:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-ZYzUmSXu-1677760557956)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301215158178.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-pa3peMOJ-1677760557957)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301215207227.png)]
扩展分页
线性地址解释方式变为:
High10 | Low22 高10位有效Directory | OFFSET
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-f4a6gmfD-1677760557957)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301215345526.png)]
扩展分页用于将大段的连续的线性地址转换为相应的物理地址
设置CR4处理器的PSE标志 采用混合常规分页与扩展分页
硬件的保护
页表项和页目录项的格式中有USER/SUPERVISOR标志
该标志为0 只有CPL<3(内核态)时可以对页寻址
该标志为1 所有CPL登记都可以对页寻址
常规分页举例
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-cIUpJcf8-1677760557958)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230301221529322.png)]
物理地址扩展PAE
物理地址扩展 2^32 --> 2^36 64GB
跟着分页机制也要更改
CR4的PAE标志激活PAE
页目录项中的页大小标志启用大尺寸页
PAE开启时页大小为2MB
改变后的分页机制:
64GB的RAM划分为2^24页框 2^36 / 2^24 = 2^12= 4kb 每页还是4K
页表项物理地址字段20bit --> 24bit
12标志位 + 24物理地址为 以至于原来总大小32bit --> 64bit
引入Page Directory pointer Table PDPT 页目录指针 表 CR3包含27位的PDPT基址字段
映射方式:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-40UZ95Qt-1677760557959)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302150824664.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-6VzIfQTz-1677760557960)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302150834503.png)]
Linux中的分页
Linux提供了一种同时兼容32位和64位系统的普通分页模型
对于32位系统来说 使用两级页表已经足够了
对于64位系统来说 需要更多级的页表 --> 三级甚至四级
2.6.1 采用四级分页模型:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-BqDbOxqE-1677760557961)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302104825732.png)]
Linux通过使用页上级目录和页中间目录全为0,从根本上取消了页上级目录和页中间目录字段。同样的代码再32位系统下和64位系统下都可以使用
Linux内存管理对进程处理的贡献:
- 每个进程分配一块不同的物理地址空间,访问寻址错误
- 区分页面(数据组)和页面帧(主内存中的物理地址)。这允许将同一页面存储在页面帧中,然后保存到磁盘中,然后在其他页面帧中重新加载。
每一个进程有它自己的页全局目录和自己的页表集。当发生进程切换时,Linux把cr3控制寄存器的内容保存在前一个执行进程的描述符中,然后把下一个要执行进程的描述符的值装入cr3寄存器中。因此,当新进程重新开始在CPU上执行时,分页单元指向一组正确的页表。
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-jZQqxSdU-1677760557961)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302131738714.png)]
Linux中分段分页相关的宏定义
线性地址部分
PAGE_OFFSET 线性地址的offset字段的位数 决定了页大小 X86架构下该宏值为12
#define PAGE_SHIFT 12
#define PAGE_SIZE (_AC(1,UL) << PAGE_SHIFT)
#define PAGE_MASK ( ~(PAGE_SIZE-1) )#define __AC(X,Y) (X##Y)
#define _AC(X,Y) __AC(X,Y)
#define _AT(T,X) ((T)(X))
PMD_SHIFT 线性地址的offset字段+Table字段的总和
#define PMD_SHIFT (PAGE_SHIFT + (PAGE_SHIFT-3))
#define PMD_SIZE (1UL << PMD_SHIFT)
#define PMD_MASK (~(PMD_SIZE-1))
PUD_SHIFT 页上级目录项能映射的区域大小的对数
#define PUD_SHIFT 26
#define PTRS_PER_PUD 1
#define PUD_SIZE (1UL << PUD_SHIFT)
#define PUD_MASK (~(PUD_SIZE - 1))
#define PUE_SIZE 256
PDGIR_SHIFT 页全局目录能映射的区域大小的对数
#define PGDIR_SHIFT 26
#define PGDIR_SIZE (1UL << PGDIR_SHIFT)
#define PGDIR_MASK (~(PGDIR_SIZE - 1))
#define PTRS_PER_PGD 64
PTRS_PER_PTE/PTRS_PER_PMD/PTRS_PER_PUD/PTRS_PER_PGD
计算 页表 页中间目录 页上级目录 页全局目录 中表项的个数
禁止PAE 1024 1 1 1024
开启PAE 512 412 1 4
#define PTRS_PER_PTE 4096
#define PTRS_PER_PTE (1UL << (PAGE_SHIFT-3))
#define PTRS_PER_PMD (1UL << (PAGE_SHIFT-3))
#define PTRS_PER_PGD (1UL << (PAGE_SHIFT-3))
页表处理相关
/** These are used to make use of C type-checking..*/
typedef struct { unsigned long pte; } pte_t;
typedef struct { unsigned long pmd; } pmd_t;
typedef struct { unsigned long pud; } pud_t;
typedef struct { unsigned long pgd; } pgd_t;
#define PTE_MASK PHYSICAL_PAGE_MASKtypedef struct { unsigned long pgprot; } pgprot_t;#define pte_val(x) ((x).pte)
#define pmd_val(x) ((x).pmd)
#define pud_val(x) ((x).pud)
#define pgd_val(x) ((x).pgd)
#define pgprot_val(x) ((x).pgprot)#define __pte(x) ((pte_t) { (x) } )
#define __pmd(x) ((pmd_t) { (x) } )
#define __pud(x) ((pud_t) { (x) } )
#define __pgd(x) ((pgd_t) { (x) } )
#define __pgprot(x) ((pgprot_t) { (x) } )
修改页表表项:
#define pte_none(x) (!pte_val(x))
#define pte_present(x) (pte_val(x) & (_PAGE_PRESENT | _PAGE_PROTNONE))
#define pte_clear(mm,addr,xp) do { set_pte_at(mm, addr, xp, __pte(0)); } while (0)
static inline void set_pte(pte_t *dst, pte_t val)
{pte_val(*dst) = pte_val(val);
}
set_pte_atomic
pte_same
pte_large 指向大页的检测
pte_bad 页是否存在主存中
pte_preset 页表的present字段
设置页标志的函数:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-qKpH9Hhe-1677760557962)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302153749027.png)]
对页表操作的宏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0DpYFZOh-1677760557963)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302153833219.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-awMbG7kb-1677760557964)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302153905375.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-IFHHoR4E-1677760557964)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302153947470.png)]
对页表项操作的宏
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-DG9PfamB-1677760557965)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302154024033.png)]
物理内存布局
内核初始化阶段,内核必须简历一个物理地址的映射来指定哪些物理地址范围对于内核是可用的 哪些是不可用的
哪些是映射硬件设备I/O的 哪些是BIOS数据
不可以用的物理地址空间被记录为保留的页框
含有内核代码和已经初始化的数据结构的空间被记录为保留页框
保留页框的页绝不能被动态分配或者交换到磁盘上
作为一般规则,Linux内核从物理地址0x00100000开始安装,即从第二个兆字节开始安装。所需的页帧总数取决于内核的配置方式。一个典型的配置生成的内核可以在小于3 MB的RAM中加载。
启动过程的早期内核会询问BIOS以获取物理内存的实际大小
新近计算机中内核调用BIOS过程建立一组物理地址范围和其对应的内存类型
内核执行machine_specific_memory_setup() 函数 建立起物理地址映射
BIOS提供的物理地址映射举例
开始 | 结束 | 类型 |
---|---|---|
0x0000_0000 | 0x0009_ffff | Usable |
0x0010_0000 | 0x07fe_ffff | Usable |
0x07ff_0000 | 0x07ff_ffff | 加电自检阶段BIOS写入的硬件设备信息 ACPI data |
0x07ff_3000 | 0x07ff_ffff | 映射到硬件设备的ROM芯片 ACPI NVS |
0xffff_0000 | 0xffff_ffff | 由硬件映射到BIOS Reserved |
随后执行setup_memory() 函数 分析物理内存区域表示初始化一些变量来描述内核的物理内存
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-CFBDdYGw-1677760557965)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302160256060.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-7luthg9w-1677760557965)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302160350509.png)]
可以在system.map中看到这些图中符号的线性地址,它是编译内核以后所创建的
避免kernel被装入一组不连续的页框中,Linux跳过RAM的第一个MB
_text physical addr = 0x00100000 kernel code start
_etext kernel code end
_etext ~ _edata initialized kernel data
_edata ~ _end uninitialized kernel data
图中出现的符号都是编译内核时产生的
进程页表
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-REzRgDLi-1677760557966)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302161102248.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-PzmQe3hE-1677760557966)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302161612022.png)]
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-9nSYTOjp-1677760557966)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302161622170.png)]
内核页表:
内核自己维护一组页表,放在主内核页全局目录
系统初始化后这组页表从未被任何进程或者任何内核线程直接使用
内核如何初始化自己的页表,分两步
- 实模式下,内核创建一个有限的地址空间 128KB大小 (装载内核代码段数据段,初始页表,并用于存放动态数据结构) 这个空间只能将内核装到RAM 这个阶段称为临时内核页表
- 内核充分利用剩余的RAM并适当地建立 分页表
假设内核使用的段,临时页表和128KB的内存范围能够被RAM的前8MB容纳
分页的第一阶段 要允许实模式下和保护模式下都能够很容易地对这8MB寻址
由于Linux由BIOS加载后,起始阶段其实是运行在实模式,此时并没有开启分页机制。那Linux在开启分页机制之前需要先做哪些准备工作以支持分页机制?答案是页目录项及页表项,可以根据x86的硬件分页机制知道,此时的两级映射。
一般来说,Linux内核安装在RAM中从物理地址0x0010 0000开始的地方,也就是说,从第二个MB开始。
为什么内核没有安装在RAM第一个MB开始的地方?因为PC体系结构有几个独特的地方必须考虑到。例如:
- 页框0由BIOS使用,存放加电自检(Power-On Self-Test, POST)期间检查到的系统硬件配置。
- 物理地址从0x000a 0000到0x000f ffff的范围通常留给BIOS例程,并且映ISA图形卡上的内部内存。
- 第一个MB内的其它页框可能由特定计算机模型保留
必须创建映射:
[外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-Hzk6kyXh-1677760557967)(C:\Users\EDZ\AppData\Roaming\Typora\typora-user-images\image-20230302173829395.png)]
临时页表,线性地址0的起始和0xC000_0000的起始,都指向同一片物理内存
这个过程的代码:
/* page_pde_offset/4 = 0x300,正好为页目录的0x300项(0xc000 0000的前10位) * 0x0000 0000 与 0xC000 0000在页目录项正好偏移0x300项 */
page_pde_offset = (__PAGE_OFFSET >> 20);/* 页表是放在pg0开始的内存处 */movl $(pg0 - __PAGE_OFFSET), %edimovl $(swapper_pg_dir - __PAGE_OFFSET), %edxmovl $0x007, %eax /* 0x007 = PRESENT+RW+USER */
10:leal 0x007(%edi),%ecx /* Create PDE entry *//* 填充线性地址0x0000 0000开始的目录项 */movl %ecx,(%edx) /* Store identity PDE entry *//* 填充线性地址0xc000 0000开始的目录项 */movl %ecx,page_pde_offset(%edx) /* Store kernel PDE entry */addl $4,%edx/* 一个页表包含1024项 */movl $1024, %ecx
11:/* 填充页表项,一个页表项映射4k物理内存,所以每次加0x1000 */stosladdl $0x1000,%eaxloop 11b/* End condition: we must map up to and including INIT_MAP_BEYOND_END *//* bytes beyond the end of our own page tables; the +0x007 is the attribute bits *//* INIT_MAP_BEYOND_END=128*1024, 确定建立的页表是否能够映射“最后一个页表地址” + 128K的内存* 如果不行继续建立映射关系 */leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebpcmpl %ebp,%eaxjb 10b
正式启用分页以后 目录中低地址上的页表会被释放 清除
随后 第一阶段就可以通过与物理地址相同的线性地址 或者 通过从0xc0000000开始的8MB线性地址对RAM的前8MB进行寻址
内核通过把swapper_pg_dir所有项都填充为0来创建期望的映射:
// swapper_pg_dir,这个全局变量,在汇编 head.S 中定义了
// 编译时便被分配了,这是一个真正的全局变量,生命周期与内核相等
ENTRY(swapper_pg_dir).fill 1024,4,0
临时的页面全局目录包含在swapper_pg_dir变量中。临时页面表从pg0开始存储,就在内核未初始化的数据段结束(图2-13中的符号_end)之后。
启用分页单元:
/** Enable paging*/movl $swapper_pg_dir-__PAGE_OFFSET,%eaxmovl %eax,%cr3 /* set the page table pointer.. */movl %cr0,%eaxorl $0x80000000,%eaxmovl %eax,%cr0 /* ..and set paging (PG) bit */
最终映射的建立:
建立完临时映射大概8M的内存空间后,Linux内核就运行在保护模式了,此时就可以开始进行一些内核的基本初始化工作了。
最终内核页表的初始化主要是这个函数,在 x86 上,大概分了几种情况:
- 内存小于 896M
- 内存小于 4G
- 内存大于 4G
主要都是在 init.c -> paging_init() 这个函数里实现的。
在这个函数里,会通过预定义(在配置环节)的宏定义、CPU的寄存器值等条件,完成不同情况的区分。
在这里我们不需要太纠结于Linux中页表之间的层次关系,比如觉得pgd一定要索引到pud,其实pud,pmd及pte只是Linux中对页表的一个抽象,只是一个命名而已,应该关注的是里面的内容,比如说常规分页模式下,pgd的目录项是初始化为pte的基地址及一些标志位,虽然没有pud,但是能找到下一级页表就行。下面我们从代码实现中就能看到:
函数的调用关系为:pagetable_init -> kernel_physical_mapping_init
void __init paging_init(void) // line: 499
static void __init pagetable_init (void) // line:310
static void __init kernel_physical_mapping_init(pgd_t *pgd_base) // line: 143
static void __init page_table_range_init (unsigned long start, unsigned long end, pgd_t *pgd_base) // line: 10
static void __init pagetable_init (void)
{unsigned long vaddr;pgd_t *pgd_base = swapper_pg_dir;#ifdef CONFIG_X86_PAEint i;/* Init entries of the first-level page table to the zero page *//* 在扩展模式下,pgd的前三项映射线性地址空间为0-3G,这为用户空间访问的地址范围,所以用一个空页对它进行初始化 */for (i = 0; i < PTRS_PER_PGD; i++)set_pgd(pgd_base + i, __pgd(__pa(empty_zero_page) | _PAGE_PRESENT));
#endif/* Enable PSE if available */if (cpu_has_pse) {set_in_cr4(X86_CR4_PSE);}/* Enable PGE if available */if (cpu_has_pge) {set_in_cr4(X86_CR4_PGE);__PAGE_KERNEL |= _PAGE_GLOBAL;__PAGE_KERNEL_EXEC |= _PAGE_GLOBAL;}kernel_physical_mapping_init(pgd_base);remap_numa_kva();/** Fixed mappings, only the page table structure has to be* created - mappings will be set by set_fixmap():*/vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;page_table_range_init(vaddr, 0, pgd_base);permanent_kmaps_init(pgd_base);#ifdef CONFIG_X86_PAE/** Add low memory identity-mappings - SMP needs it when* starting up on an AP from real-mode. In the non-PAE* case we already have these mappings through head.S.* All user-space mappings are explicitly cleared after* SMP startup.*/pgd_base[0] = pgd_base[USER_PTRS_PER_PGD];
#endif
}/** This maps the physical memory to kernel virtual address space, a total * of max_low_pfn pages, by creating page tables starting from address * PAGE_OFFSET.*/ // pfn 应该是 page frame number
static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{unsigned long pfn;pgd_t *pgd;pmd_t *pmd;pte_t *pte;int pgd_idx, pmd_idx, pte_ofs;pgd_idx = pgd_index(PAGE_OFFSET); // 0xC000_0000 对应的页全局目录项在页全局目录中对应的位置pgd = pgd_base + pgd_idx; // 页全局目录的一个结构体指针,指向的是页全局目录数组的某个元素pfn = 0;for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) { // 页全局目录中有 1024 个元素,遍历一遍pmd = one_md_table_init(pgd); // 最后返回的,是一个 pmd_t *;但是在二级分页的情况下,pmd 就等于 pgdif (pfn >= max_low_pfn)continue;for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) { // 二级分页下,PTRS_PER_PMD 为1,所以这个循环体只会被执行一次unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET; // 这个明显是 PA,和 pfn 相关的应该都是 PA/* Map with big pages if possible, otherwise create normal page tables. */if (cpu_has_pse) { // 如果支持大页表,也就是一页 4M;反正内核是需要连续空间,并且也不会被换出,所以使用大页表是合理的unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;// 不启用 PAE 的话,下面这两个 if 分支实际上是等价的// set_pmd 执行的是一个把值赋给指针的操作// 值=物理地址左移12位+标志位// 指针=页中间目录(数组)中的某个元素指针,note that:二级分页的情况下,页中间目录项=页全局目录项if (is_kernel_text(address) || is_kernel_text(address2))set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));elseset_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));pfn += PTRS_PER_PTE;} else { // 如果不支持大页表,页就是一页只能是 4k,那么需要把页中间目录中的每个页表项都初始化一遍,所以需要另一个循环pte = one_page_table_init(pmd);for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {// set_pte 的操作和 set_pmd 类似if (is_kernel_text(address))set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));elseset_pte(pte, pfn_pte(pfn, PAGE_KERNEL));}}}}
}
当建立完内核最终的线性映射之后,线性地址0xc0000000 - 0xc0000000+896M, 对应的物理地址就为0x00000000-0x00000000+896M,这就是所谓的线性映射。
相关文章:
Understanding The Linux Kernel --- Part2 Memory Addressing
内存寻址 操作系统自身不必完全了解物理内存,如今的微处理器包含的硬件线路使内存管理既高效又健壮,所以编程错误就不会对该程序之外的内存产生非法访问 x86如何进行芯片级内存寻址Linux如何利用寻址硬件 x86 三种不同的地址术语 逻辑地址 逻辑地址…...
前后端分页查询好大的一个坑(已解决)
前言:如果你在做前后端的分页查询,找不到错误,请你来看看是否是和我一样的情况?情况:做了一个前后盾UI的项目,有一个页面是查询系统日志,要进行分页查询;第一页的:第5页的…...
Python批量执行读取ini文件和写入ini文件时,性能比较低怎么办,给出解决方案和源码
Python批量执行读取ini文件和写入ini文件时,性能比较低怎么办,给出解决方案和源码 解决方案: 使用ConfigParser的缓存机制,可以避免频繁读取ini文件造成的性能问题。 将ini文件转换为json格式,使用json库进行读写操作…...
微机原理与接口技术 汇编语言程序设计DOS常用命令
OS(磁盘操作系统)命令,是DOS操作系统的命令,是一种面向磁盘的操作命令,主要包括目录操作类命令、磁盘操作类命令、文件操作类命令和其它命令。 使用技巧 DOS命令不区分大小写,比如C盘的Program Files&…...
4.ffmpeg命令转码规则、过滤器介绍、手动流map选项
在上章我们学习了ffmpeg命令行帮助以及选项查找 本章我们来深入学习ffmpeg命令转码规则、过滤器介绍、手动流map选项 参考链接: 1.ffmpeg命令行转码流程 ffmpeg命令行转码流程如下图所示: 对应中文则是: 步骤如下所示: ffmpeg调用libavformat库(包含解复用器)来读取输入文件…...
【python】标准库详解
注:最后有面试挑战,看看自己掌握了吗 文章目录Standard Library简介python内置对象如何安装发布第三方模块10最好用的模块汇总包的本质datetime模块案例Math模块random模块OS模块sys模块time模块总结自定义模块标准库模块用help查看time模块常用第三方库…...
Golang Map原理(底层结构、查找/新增/删除、扩缩容)
参考: 解剖Go语言map底层实现Go语言核心手册-3.字典 一、Go Map底层结构: Go map的底层实现是一个哈希表(数组 链表),使用拉链法消除哈希冲突,因此实现map的过程实际上就是实现哈希表的过程。 先来看下…...
Java_数组
数组 1.概念 需求:现在需要统计软件技术1班47名同学的成绩情况,例如计算平均成绩、最高成绩等。如果只能使用变量的话,那么需要定义100个变量,这样就比较复杂了。这时我们就可以使用数组来记住这47名同学的成绩,然…...
list与vector的区别
相信大家已经学过list与vector,关于它们的不同,我做了一些总结,如下表: vector list底层结构动态顺序表,一段连续的空间带头结点的双向链表随机访问支持随机访问,访问某个元素的效率…...
【C++、数据结构】位图、布隆过滤器、哈希切割(哈希思想的应用)
文章目录📖 前言1. 位图1.1 海量数据处理思路分析:1.2 位图的具体实现:1.3 用位图解决问题:应用一:应用二:应用三:2. 布隆过滤器2.1 布隆过滤器的概念:2.2 布隆过滤器的测试…...
计算机网络安全基础知识3:网站漏洞,安装phpstudy,安装靶场漏洞DVWA,搭建一个网站
计算机网络安全基础知识3:网站漏洞,安装phpstudy,安装靶场漏洞DVWA,搭建一个网站 2022找工作是学历、能力和运气的超强结合体,遇到寒冬,大厂不招人,可能很多算法学生都得去找开发,测…...
大话数据结构-迪杰斯特拉算法(Dijkstra)和弗洛伊德算法(Floyd)
6 最短路径 最短路径,对于图来说,是两顶点之间经过的边数最少的路径;对于网来说,是指两顶点之间经过的边上权值之和最小的路径。路径上第一个顶点为源点,最后一个顶点是终点。 6.1 迪杰斯特拉(Dijkstra&am…...
2023年全国最新食品安全管理员精选真题及答案10
百分百题库提供食品安全管理员考试试题、食品安全员考试预测题、食品安全管理员考试真题、食品安全员证考试题库等,提供在线做题刷题,在线模拟考试,助你考试轻松过关。 91.实施日常检查,如果违反关键项的,应当即作出如…...
Unity常见面试题详解(持续更新...)
一丶声明、定义、实例化、初始化 1、首先我们来讨论在C/C中的声明和定义.. 1)我们先从函数声明和定义说起... 一般我们在C里都会先定义一个函数,然后再Main函数前将函数声明,比如: //函数声明 int Add(int);int Main {} //函数…...
java高级篇之三大性质总结:原子性、可见性以及有序性
1. 三大性质简介 在并发编程中分析线程安全的问题时往往需要切入点,那就是两大核心:JMM抽象内存模型以及happens-before规则(在这篇文章中已经经过了),三条性质:原子性,有序性和可见性。关于sy…...
真涨脸,我用 Python 为朋友自动化整理表格
今天,在工作的时候,我的美女同事问我有没有办法自动生成一个这样的表格: 第一列是院校科目,第二列是年份,第三列是数量。 这张表格是基于这一文件夹填充的,之前要一个文件夹一个文件夹打开然后手动填写年份…...
MySQL学习笔记(1.操作数据库与数据的SQL)
1. 下载安装 参照:MySQL8.0下载安装_凯尔萨厮的博客-CSDN博客 2. MySQL启动与停止 方式(1).我的电脑>右键>管理>服务和应用程序>服务>(或在windows搜索栏输入services.msc) 找到MySQL80,右键启动或停止 方式(2…...
C++——特殊类设计
目录 不能被拷贝的类 只能在堆上创建对象的类 只能在栈上创建对象的类 不能被继承的类 只能创建一个对象的类(单例模式) 饿汉模式 懒汉模式 单例对象释放问题 不能被拷贝的类 C98:将拷贝构造函数与赋值运算符重载只声明不定义,并且将其访问权…...
Scratch少儿编程案例-植物大战僵尸-趣味角色版
专栏分享 点击跳转=>Unity3D特效百例点击跳转=>案例项目实战源码点击跳转=>游戏脚本-辅助自动化点击跳转=>Android控件全解手册点击跳转=>Scratch编程案例👉关于作者...
Vue的路由守卫
对于绝大部分的网站而言,都是有个人主页的,但是你如果没登陆的话,还能访问个人主页吗? 从逻辑上来讲,那肯定是不行的。 所以,要怎么阻止没登录状态下去访问个人主页呢? 就是利用路由守卫&#x…...
【算法】151. 反转字符串中的单词
链接:https://leetcode.cn/problems/reverse-words-in-a-string/给你一个字符串 s ,请你反转字符串中 单词 的顺序。单词 是由非空格字符组成的字符串。s 中使用至少一个空格将字符串中的 单词 分隔开。返回 单词 顺序颠倒且 单词 之间用单个空格连接的结…...
Azure AI基础到实战(C#2022)-认知服务(2)
目录 ComputerVisionClient Class定义构造函数属性上一节例子Task.Wait 方法其它部分分析winform调用认知服务代码剖析1、调用参数2、定义ComputerVisionClient对象,准备调用 REST API3、Authenticate4、调用REST API,这是重点和关键(1)Lambda 表达式和匿名函数(2)async(3)…...
并发就一定快吗?答:肯定不是啊
文章目录一、多线程概念1.1 程序的并发与并行1.1.1 程序的并行1.1.2 程序的并发1.2 进程与线程1.2.1 进程1.2.2 线程1.2.3 多线程并发就一定快吗?答案直接戳这里👉:多线程并发就一定快吗? 一、多线程概念 在实际应用中ÿ…...
前端的学习路线和方法
一些前端工程师面临的现状 1.没有系统的的学习基础知识 2.技术上存在短板,说句不好听的话,大多数开发者的上升通道都没有明确的路线,大公司还好,小公司基本都是后端作为开发组组长 3.前端各种技术层出不穷,需要花费…...
用C语言写一个自己的shell-Part Ⅱ--execute commands
Part Ⅱ–execute commands Exec This brings us to the exec family of functions. Namely, it has the following functions: execlexecvexecleexecveexeclpexecvp For our needs,we will use execvp whose signature looks like this int execvp(const char *file, cha…...
案例实践|运营腾讯游戏,Proxima Beta 使用 Apache Pulsar 升级团队协作与数据治理...
文章摘要本文整理自 Pulsar Summit Asia 2022 上,Proxima Beta 软件工程师施磊的分享《How to achieve better team integration and data governance by using Apache Pulsar》。本文首先将为大家介绍 CQRS 和 Event Sourcing 概念,便于了解为何 Proxim…...
Hudi的7种索引
1、Bloom Index Bloom Index (default) 使用根据记录键构建的bloom过滤器,也可以使用记录键范围修剪候选文件.原理为计算RecordKey的hash值然后将其存储到bitmap中,为避免hash冲突一般选择计算3次 HoodieKey 主键信息:主要包含recordKey 和p…...
Linux内核(十三)系统软中断 software
文章目录中断概述Linux内核中断软中断相关代码解析软中断结构体软中断类型软中断两种触发方式函数__do_softirq解析定时器的软中断实现解析定时器相关代码总结Linux版本:linux-3.18.24.x 中断概述 中断要求 快进快出,提高执行效率,…...
Linux -- 查看进程 PS 命令 详解
我们上篇介绍了, Linux 中的进程等概念,那么,在Linux 中如何查看进程呢 ??我们常用到的有两个命令, PS 和 top 两个命令,今天先来介绍下 PS 命令~!PS 命令 :作用 &#x…...
C2科一考试道路通行规定
目录 低能见度等恶劣环境下的通行规定 驾驶机动车禁止行为 停车规定 通行常识 高速公路限速规定 三观不一样的人,无论重来多少次,结果都一样 他不会懂你的委屈 只是觉得自已没错 两个人真正的可悲连吵架都不在一个点上 有句话说得好 我要是没点自我…...
网站建设从零开始 教程/成品网站货源1
本文讲的是 IoC真的重要吗?细节决定成败,安全行业已经从仅专注特征码,转变到了将IoC(入侵指标)也纳入进来。因为各方面看来,IoC都更加便捷,也兼容各种不同检测平台。是时候重视IoC并更加有效地使…...
晚上网站推广软件免费版/天天自学网网址
mongodb3版本之后支持zlib和snappy。 创建压缩的集合 db.createCollection( "email", {storageEngine:{wiredTiger:{configString: block_compressorzlib}}}) 插入测试数据: for (var i0;i<20000;i){ db.users.insert({name:i,age:12,remark:你也许会…...
拉新接单网/贵阳百度seo点击软件
生成树协议是一种二层管理协议,它通过有选择性地阻塞网络冗余链路来达到消除网络二层环路的目的,同时具备链路的备份功能。 54ne.com生成树协议和其他协议一样,是随着网络的不断发展而不断更新换代的。“生成树协议”是一个广义的概念&#x…...
saas系统是什么样的系统/优化网络培训
点击上方[word精品教程]-右上角[...]-[设为星标⭐]即可第一时间获取最新办公资讯对于办公人员来说,每天和Word打交道,处理各种工作方案、学术报告等文档,Word办公软件玩的溜是最基础的。不仅玩的溜,而且要效率高,才能成…...
高端网站制作费用/网站软件免费下载
docker-compose.yml version: "2" services:eid-service:# 指定容器名称container_name: xxx-service# 重启机制restart: always# hub地址,image版本image: hub.xxx.cn/xxx-service/xxx-service:latestvolumes:# 本地jar包路径- /opt/service/1.5/xxx-se…...
找个人给我做电影网站/宁波seo教程行业推广
2019独角兽企业重金招聘Python工程师标准>>> 改写了leader的程序,现在支持读取EXCEL表格(以路径字符串的形式作为参数传入),当然是那种一个sheet一张表的,而且格式比较固定,只能是这样的形式&am…...