admin管理员组

文章数量:1584180

Linux内核内存管理,是一个庞大而复杂的系统,可以说花上一辈子时间来研究内核中内存管理部分都不夸张。

Understanding the VM split

Linux内核通过以下两种方式管理内存:

  • 基于虚拟内存的方法,其中内存被虚拟化(实现虚拟机的一个基本因素),通过将虚拟内存映射到物理内存(当前主流的方法)
  • 直接使用物理内存,常见于小内存的单片机,或者寄存机,直接通过实际物理地址使用村粗空间(例如用汇编语言编程,会严格要求将数据或指令从哪个存储地址读取与存放)

在虚拟内存情况下,对于不同的进程而言,他们都以为自己完全独立拥有一块内存,操作系统将进程使用的虚拟内存映射到物理内存,以此来实现运行一个进程;每个进程的虚拟地址可以相同,进程所能访问的虚拟内存空间由cpu的引脚所限定。

操作系统所能使用的内存大小由CPU限定

  • On a Linux OS running on a 32-bit processor (or compiled for 32-bit), the highest virtual address will be 2^32 = 4 GB.(实际是4Gb的寻址空间,每一个地址可以存放8bit/1byte大小的内容)
  • On a Linux OS running on (and compiled for) a 64-bit processor, the highest virtual address will be 2^64 = 16 EB. (EB is short for exabyte. Believe me, it's an enormous quantity. 16 EB is equivalent to the number 16 x 10^18 .)
  • codenamemeaningvalue
    KiKibibinary Kilo2^10 = 1 024≈ 1,02 k
    MiMebibinary Mega2^20 = 1 048 576≈ 1,05 M
    GiGibibinary Giga2^30 = 1 073 741 824≈ 1,07 G
    TiTebibinary Tera2^40 = 1 099 511 627 776≈ 1,10 T
    PiPebibinary Peta2^50 = 1 125 899 906 842 624≈ 1,13 P
    EiExbibinary Exa2^60 = 1 152 921 504 606 846 976≈ 1,15 E
    ZiZebibinary Zetta2^70 = 1 180 591 620 717 411 303 424≈ 1,18 Z
    YiYobibinary Yotta2^80 = 1 208 925 819 614 629 174 706 176≈ 1,21 Y

深入了解——Hello, world C 程序

要了解虚拟内存,通过学习著名的 Hello, world C 程序并了解其在 Linux 系统上的内部工作原理能起到事半功倍的效果;

printf("Hello, world.\n");

该进程正在调用 printf() 函数。 你写过 printf() 的代码吗? “不,当然没有,”你说,“它在标准的 libc C 库中,通常是 Linux 上的 glibc (GNU libc)。”但是等一下,除非 printf()函数的代码和数据(以及类似的所有其他库 API)被实际装载到进程的虚拟内存中,否则我们怎么能访问它?(回想一下,进程以为自己拥有整个内存,所以只能访问自己的虚拟内存空间),实际上printf()函数(其实是glibc库)被映射到了进程的虚拟地址空间,printf()函数的数据部分,映射到了虚拟内存中的data段,那这是如何实现的呢?

 如果想了解虚拟内存是如何分段的,以及各个段的作用请参考:https://blog.csdn/zyqash/article/details/126372419

真相就是在应用程序启动时,作为 C 运行时环境设置的一部分,有一个小的可执行和可链接格式(ELF) 的二进制文件ld.so或ld-linux.so(被称为加载程序,自动嵌入到了打印hello world的二进制可执行文件中),它会提前运行,检测程序所依赖的所有共享库文件并通过mmap系统调用将他们全部映射到进程的虚拟内存中。 所以,当printf函数依赖的库的代码和数据在进程的虚拟内存中完成映射,就可以成功调用 printf() API并在屏幕打印hello world的了!

通过一段代码来进一步说明上述内容

[root@ct7_node02 tmp]# cat helloworld.c 
# include <stdio.h>

int main(){
    printf("Hello, world.\n");
}
[root@ct7_node02 tmp]#  gcc helloworld.c -o helloworld
[root@ct7_node02 tmp]# ./helloworld 
Hello, world.
[root@ct7_node02 tmp]# ldd ./helloworld
	linux-vdso.so.1 =>  (0x00007ffccccea000)
	libc.so.6 => /lib64/libc.so.6 (0x00007f3b6cca7000)
	/lib64/ld-linux-x86-64.so.2 (0x00007f3b6d075000)

需要注意的几个要点:

  • 每一个Linux进程,会默认自动链接最少两个对象:glibc共享库和加载程序
  • 加载程序的名称因架构而异,在x86_64系统上是ld-linux-x86-64.so.2
  • 右边括号内的地址是映射到程序中的虚拟内存中的地址,例如,在上面的输出中,glibc被映射到了进程虚拟地址中用户虚拟地址0x00007f3b6cca7000,请注意,这个地址是runtime dependent的,并不是固定的
  • 出于安全原因(以及在 x86 以外的体系结构上),最好用 objdump程序来查找此类详细信息。
  • linux-vdso.so.1 (VDSO就是Virtual Dynamic Shared Object)printf()这些标准的函数的在libc.so.6。open(),read(),write(),socket()这些却不再是glibc的了,他们在此共享库中,请参考linux-vdso.so.1介绍_小饼仙子的博客-CSDN博客_linux-vdso.so

尝试对 Hello, world 二进制可执行文件执行 strace(),您将看到大量 mmap() 系统调用,映射到 glibc(和其他)段!

[zyq@zyq tmp]$ sudo strace ./helloworld 
execve("./helloworld", ["./helloworld"], [/* 17 vars */]) = 0
brk(NULL)                               = 0x1a3e000
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f89dfaca000
access("/etc/ld.so.preload", R_OK)      = -1 ENOENT (No such file or directory)
open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
fstat(3, {st_mode=S_IFREG|0644, st_size=103949, ...}) = 0
mmap(NULL, 103949, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f89dfab0000
close(3)                                = 0
open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0\20\35\2\0\0\0\0\0"..., 832) = 832
fstat(3, {st_mode=S_IFREG|0755, st_size=2127336, ...}) = 0
mmap(NULL, 3940800, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f89df4e7000
mprotect(0x7f89df69f000, 2097152, PROT_NONE) = 0
mmap(0x7f89df89f000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1b8000) = 0x7f89df89f000
mmap(0x7f89df8a5000, 16832, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f89df8a5000
close(3)                                = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f89dfaaf000
mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f89dfaad000
arch_prctl(ARCH_SET_FS, 0x7f89dfaad740) = 0
mprotect(0x7f89df89f000, 16384, PROT_READ) = 0
mprotect(0x600000, 4096, PROT_READ)     = 0
mprotect(0x7f89dfacb000, 4096, PROT_READ) = 0
munmap(0x7f89dfab0000, 103949)          = 0
fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), ...}) = 0
mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f89dfac9000
write(1, "Hello, world.\n", 14Hello, world.
)         = 14
exit_group(14)                          = ?
+++ exited with 14 +++
[zyq@zyq tmp]$ 

 mmap系统调用的详细信息可参考认真分析mmap:是什么 为什么 怎么用 - 胡潇 - 博客园

Going beyond the printf() API

As you will know, the printf(3) API translates to the write(2) system call, which of course writes the "Hello, world" string to stdout (by default, the terminal window or the console device).

We also understand that as write(2) is a system call, this implies that the current process running this code – the process context – must now switch to kernel mode and run the kernel code of write(2) (monolithic kernel architecture)! Indeed it does. But hang on a second: the kernel code of write(2) is in kernel VAS . The point here is if the kernel VAS is outside the box, then how in the world are we going to call it?(我们知道,由于 write(2) 是一个系统调用,这意味着运行此代码的当前进程——进程上下文——现在必须切换到内核模式并运行 write(2) 的内核代码(单片内核架构)! 确实如此。 但是请稍等: write(2) 的内核代码在内核 VAS 中。 这里的重点是,如果内核 VAS 是在进程VAS的外面,那么我们到底要怎么调用它呢?)

Well, it could be done by placing the kernel in a separate 4 GB VAS, but this approach results in very slow context switching, so it's simply not done.(通过将内核放在一个单独的 4 GB VAS 中来完成,但是这种方法会导致上下文切换非常慢,所以根本不可行。)

The way it is engineered is like this: both user and kernel VASes live in the same 'box' – the available VAS. How exactly? By splitting the available address space between the user and kernel in some User:Kernel :: u:k ratio. This is called the VM split (the ratio u:k being typically expressed in gigabytes, terabytes, or even petabytes)(它的设计方式是这样的:用户和内核 VAS 都存在于同一个“盒子”中——可用的 VAS。 具体如何? 通过在一些 User:Kernel :: u:k 比率中分割用户和内核之间的可用地址空间。 这称为 VM 拆分(u:k 比率通常以千兆字节、太字节甚至 PB 表示))

The following diagram is representative of a 32-bit Linux process having a 2:2 VM split (in gigabytes); that is, the total 4 GB process VAS is split into 2 GB of user space and 2 GB of kernel-space. This is often the typical VM split on an ARM-32 system running the Linux OS:

 So, now that the kernel VAS is within the box, it's suddenly clear and critical to understand this: when a user-mode process or thread issues a system call, there is a context switch to the kernel's 2 GB VAS (various CPU registers, including the stack pointer, get updated) within the very same process's VAS. The thread issuing the system call now runs its kernel code in process context in privileged kernel mode (and works on kernel-space data). When done, it returns from the system call, context switching back into unprivileged user mode, and is now running user-mode code within the first 2 GB VAS(所以,既然内核 VAS 在盒子里,理解这一点突然变得清晰和关键:当用户模式进程或线程发出系统调用时,会有一个上下文切换到内核的 2 GB VAS(各种 CPU 寄存器, 包括堆栈指针,得到更新)在同一个进程的 VAS 中。 发出系统调用的线程现在在特权内核模式下的进程上下文中运行其内核代码(并处理内核空间数据)。 完成后,它从系统调用返回,上下文切换回非特权用户模式,现在在前 2 GB VAS 内运行用户模式代码)

The exact virtual address where the kernel VAS – also known as the kernel segment – begins is typically represented via the PAGE_OFFSET macro within the kernel. We will examine this, and some other key macros as well, in the Macros and variables describing the kernel segment layout section.(内核 VAS(也称为内核段)开始的确切虚拟地址通常通过内核中的 PAGE_OFFSET 宏表示。 我们将在描述内核段布局的宏和变量部分中检查这个以及其他一些关键宏。)

Where is this decision regarding the precise location and size of the VM split taken? Ah, on 32-bit Linux, it's a kernel build-time configurable. It's done within the kernel build as part of the make [ARCH=xxx] menuconfig procedure – for example, when configuring the kernel for a Broadcom BCM2835 (or the BCM2837) System on Chip (SoC) (the Raspberry Pi being a popular board with this very SoC). Here's a snippet from the official kernel configuration file (the output is from the Raspberry Pi console):(这个关于 VM 拆分的精确位置和大小的决定是在哪里做出的? 啊,在 32 位 Linux 上,它是内核构建时可配置的。 它是在内核构建中作为 make [ARCH=xxx] menuconfig 过程的一部分完成的——例如,在为 Broadcom BCM2835(或 BCM2837)片上系统 (SoC) 配置内核时(Raspberry Pi 是一种流行的板 这非常 SoC)。 这是官方内核配置文件的片段(输出来自 Raspberry Pi 控制台):)

$ uname -r
5.4.51-v7+
$ sudo modprobe configs << gain access to /proc/config.gz via this LKM
>>
$ zcat /proc/config.gz | grep -C3 VMSPLIT
[...]
# CONFIG_BIG_LITTLE is not set
# CONFIG_VMSPLIT_3G is not set
# CONFIG_VMSPLIT_3G_OPT is not set
CONFIG_VMSPLIT_2G=y
# CONFIG_VMSPLIT_1G is not set
CONFIG_PAGE_OFFSET=0x80000000
CONFIG_NR_CPUS=4
[...]

As seen in the preceding snippet, the CONFIG_VMSPLIT_2G kernel config option is set to y implying that the default VM split is user:kernel :: 2:2. For 32-bit(如前面的代码片段所示,CONFIG_VMSPLIT_2G 内核配置选项设置为 y,这意味着默认的 VM 拆分为 user:kernel :: 2:2。 对于 32 位)

architectures, the VM split location is tunable (as can be seen in the preceding snippet, CONFIG_VMSPLIT_[1|2|3]G; CONFIG_PAGE_OFFSET gets set accordingly). With a 2:2 VM split, PAGE_OFFSET is literally halfway, at the virtual address 0x8000 0000 (2 GB)!(VM 拆分位置是可调的(如前面的代码片段所示,CONFIG_VMSPLIT_[1|2|3]G;CONFIG_PAGE_OFFSET 得到相应设置)。 对于 2:2 VM 拆分,PAGE_OFFSET 实际上是一半,位于虚拟地址 0x8000 0000 (2 GB)!)

The default VM split for the IA-32 processor (the Intel x86-32) is 3:1 (GB). Interestingly, the (ancient) Windows 3.x OS running on the IA-32 had the same VM split, showing that these concepts are essentially OS-agnostic. Later in this chapter, we will cover several more architectures and their VM split, in addition to other details.(IA-32 处理器(Intel x86-32)的默认 VM 拆分为 3:1 (GB)。 有趣的是,在 IA-32 上运行的(古老的)Windows 3.x 操作系统具有相同的 VM 拆分,这表明这些概念本质上与操作系统无关。 在本章后面,我们将介绍更多的架构及其 VM 拆分,以及其他细节。)

Configuring the VM split is not directly possible for 64-bit architectures. So, now that we understand the VM split on 32-bit systems, let's now move on to examining how it's done on 64-bit systems.(对于 64 位架构,无法直接配置 VM 拆分。 所以,既然我们了解了 32 位系统上的 VM 拆分,现在让我们继续研究它是如何在 64 位系统上完成的。)

VM split on 64-bit Linux systems

First off, it is worth noting that on 64-bit systems, all 64 bits are not used for addressing. On a standard or typical Linux OS configuration for the x86_64 with a (typical) 4 KB page size, we use (the Least Significant Bit (LSB)) 48 bits for addressing. Why not the full 64 bits? It's simply too much! No existing computer comes close to having even half of the full 2 64 = 18,446,744,073,709,551,616 bytes, which is equivalent to 16 EB (that's 16,384 petabytes) of RAM!(首先,值得注意的是,在 64 位系统上,并非所有 64 位都用于寻址。 在具有(典型)4 KB 页面大小的 x86_64 的标准或典型 Linux 操作系统配置中,我们使用(最低有效位 (LSB))48 位进行寻址。 为什么不是完整的 64 位? 简直太多了! 没有现有的计算机能接近全部 2 64 = 18,446,744,073,709,551,616 字节的一半,这相当于 16 EB(即 16,384 PB)的 RAM!)

Virtual addressing and address translation

Before diving further into these details, it's very important to clearly understand a few key points.

Consider a small and typical code snippet from a C program:

int i = 5;
printf("address of i is 0x%x\n", &i);

The address you see the printf() emit is a virtual address and not a physical one. We distinguish between two kinds of virtual addresses:(您看到 printf() 发出的地址是虚拟地址,而不是物理地址。 我们区分两种虚拟地址:)

  • If you run this code in a user space process, the address of variable i that you will see is a UVA.
  • If you run this code within the kernel, or a kernel module (of course, you'd then use the printk() API), the address of variable i you will see is a Kernel Virtual Address (KVA).

Next, a virtual address is not an absolute value (an offset from 0); it's actually a bitmask:(接下来,虚拟地址不是绝对值(从 0 开始的偏移量); 它实际上是一个位掩码:)

  • On a 32-bit Linux OS, the 32 available bits are divided into what's called the Page Global Directory (PGD) value, the Page Table (PT) value, and the offset.(在 32 位 Linux 操作系统上,32 个可用位分为所谓的页全局目录 (PGD) 值、页表 (PT) 值和偏移量。)
  • These become indices via which the MMU (the Memory Management Unit that's within the silicon of modern microprocessors), with access to the kernel page tables for the current process context, performs address translation.(这些成为 MMU(现代微处理器芯片内的内存管理单元)通过访问当前进程上下文的内核页表执行地址转换的索引。)
  • As might be expected, on a 64-bit system, even with 48-bit addressing, there will be more fields within the virtual address bitmask.(正如所料,在 64 位系统上,即使使用 48 位寻址,虚拟地址位掩码内也会有更多字段。)

Okay, if this 48-bit addressing is the typical case on the x86_64 processor, then how are the bits in a 64-bit virtual address laid out? What happens to the unused 16 MSB bits? The following figure answers the question; it's a representation of the breakup of a virtual address on an x86_64 Linux system:(好的,如果这个 48 位寻址是 x86_64 处理器上的典型情况,那么 64 位虚拟地址中的位是如何布局的? 未使用的 16 个 MSB 位会发生什么? 下图回答了这个问题; 它表示 x86_64 Linux 系统上的虚拟地址分解:)

 Essentially, with 48-bit addressing, we use bits 0 to 47 (the LSB 48 bits) and ignore the Most Significant Bit (MSB) 16 bits, treating it much as a sign extension. Not so fast though; the value of the unused sign-extended MSB 16 bits varies with the address space you are in:(本质上,对于 48 位寻址,我们使用位 0 到 47 位(LSB 48 位)并忽略最高有效位 (MSB) 16 位,将其视为符号扩展。 不过没那么快; 未使用的符号扩展 MSB 16 位的值随您所在的地址空间而变化:)

  • Kernel VAS: MSB 16 bits are always set to 1.
  • User VAS: MSB 16 bits are always set to 0.

This is useful information! Knowing this, by merely looking at a (full 64-bit) virtual address, you can therefore tell whether it's a KVA or a UVA:(这是有用的信息! 知道这一点后,只需查看(完整的 64 位)虚拟地址,您就可以判断它是 KVA 还是 UVA:)

  • KVAs on a 64-bit Linux system always follow the format 0xffff .... .... .....
  • UVAs always have the format 0x0000 .... .... .....

As can now be seen (and I reiterate here), the reality is that virtual addresses are not absolute addresses (absolute offsets from zero, as you might have mistakenly imagined) but are actually bitmasks. The fact is that memory management is a complex area where the work is shared: the OS is in charge of creating and manipulating the paging tables of each process, the toolchain (compiler) generates virtual addresses, and it's the processor MMU that actually performs runtime address translation, translating a given (user or kernel) virtual address to a physical (RAM) address!(正如现在可以看到的(我在这里重申),现实情况是虚拟地址不是绝对地址(从零开始的绝对偏移量,正如您可能错误地想象的那样),但实际上是位掩码。 事实上,内存管理是一个共享工作的复杂领域:操作系统负责创建和操作每个进程的分页表,工具链(编译器)生成虚拟地址,而实际执行运行时的是处理器 MMU 地址转换,将给定的(用户或内核)虚拟地址转换为物理(RAM)地址!)

We will not delve into further details regarding hardware paging (and various hardware acceleration technologies, such as the Translation Lookaside (我们不会深入探讨有关硬件分页(以及各种硬件加速技术,例如 Translation Lookaside)

Buffer (TLB) and CPU caches) in this book. This particular topic is well covered by various other excellent books and reference sites that are mentioned in the Further reading section of this chapter.(缓冲区(TLB)和 CPU 缓存)。 本章的进一步阅读部分中提到的各种其他优秀书籍和参考网站很好地涵盖了这个特定主题。)

Back to the VAS on a 64-bit processor. The available VAS on a 64-bit system is a simply gigantic 2^64 = 16 EB (16 x 10^18 bytes!). The story goes that when AMD engineers were first porting the Linux kernel to the x86_64 (or AMD64) 64-bit processor, they would have had to decide how to lay out the process and kernel segments within this enormous VAS. The decision reached has more or less remained identical, even on today's x86_64 Linux OS. This enormous 64-bit VAS is split as follows. Here, we assume 48-bit addressing with a 4 KB page size:(回到 64 位处理器上的 VAS。 64 位系统上可用的 VAS 是一个巨大的 2^64 = 16 EB(16 x 10^18 字节!)。 故事是这样的,当 AMD 工程师第一次将 Linux 内核移植到 x86_64(或 AMD64)64 位处理器时,他们必须决定如何在这个巨大的 VAS 中布置进程和内核段。 即使在今天的 x86_64 Linux 操作系统上,所达成的决定也或多或少保持不变。 这个巨大的 64 位 VAS 拆分如下。 在这里,我们假设 48 位寻址和 4 KB 页面大小:)

  • Canonical(规范) lower half, for 128 TB: User VAS and virtual address ranges from 0x0 to 0x0000 7fff ffff ffff
  • Canonical upper half, for 128 TB: Kernel VAS and virtual address ranges from 0xffff 8000 0000 0000 to 0xffff ffff ffff fff

This 64-bit VM split on an x86_64 platform can be seen in the following figure:

 In the preceding figure, the in-between unused region – a hole or sparse region – is also called the non-canonical addresses region. Interestingly, with the 48-bit addressing scheme, the vast majority of the VAS is left unused. This is why we term the VAS as being very sparse.(在上图中,中间未使用区域(空洞或稀疏区域)也称为非规范地址区域。 有趣的是,在 48 位寻址方案中,绝大多数 VAS 都未被使用。 这就是为什么我们称 VAS 非常稀疏。)

To round off our discussion on the VM split, some common user:kernel VM split ratios for different CPU architectures are shown in the following figure (we assume an MMU page size of 4 KB):(为了结束我们对 VM 拆分的讨论,下图显示了不同 CPU 架构的一些常见用户:内核 VM 拆分比率(我们假设 MMU 页面大小为 4 KB):)

 We highlight the third row in bold red as it's considered the common case: running Linux on the x86_64 (or AMD64) architecture, with a user:kernel :: 128 TB:128 TB VM split. Also, be careful when reading the table: the numbers in the sixth and eighth columns, End vaddr, are single 64-bit quantities each and not two numbers. The number may have simply wrapped around. So, for example, in the x86_64 row, column 6, it's the single number 0x0000 7fff ffff ffff and not two numbers.(我们用粗体红色突出显示第三行,因为它被认为是常见情况:在 x86_64(或 AMD64)架构上运行 Linux,用户:内核::128 TB:128 TB 虚拟机拆分。 另外,阅读表格时要小心:第六列和第八列 End vaddr 中的数字都是单个 64 位数量,而不是两个数字。 这个数字可能只是简单地环绕。 因此,例如,在 x86_64 行第 6 列中,它是单个数字 0x0000 7fff ffff ffff 而不是两个数字。)

The third column, Addr Bits, shows us that, on 64-bit processors, no realworld processor actually uses all 64 bits for addressing.

Under the x86_64, there are two VM splits shown in the preceding table:

  • The first one, 128 TB : 128 TB (4-level paging) is the typical VM split being used on Linux x86_64-bit systems as of today (embedded laptops, PCs, workstations, and servers). It limits the physical address space to 64 TB (of RAM).
  • The second one, 64 PB : 64 PB, is, as of the time of writing at least, still purely theoretical; it comes with support for what is called 5-level paging from 4.14 Linux; the assigned VASes (56-bit addressing; a total of 128 petabytes of VAS and 4 PB of physical address space!) is so enormous that, as of the time of writing, no actual computer is (yet) using it.

What's actually residing within the kernel VAS, or as it's commonly called, the kernel segment? All kernel code, data structures (including the task structures, the lists, the kernel-mode stacks, paging tables, and so on), device drivers, kernel modules, and so on are within here (as the lower half of Figure 6.7 in Chapter 6, Kernel Internals Essentials – Processes and Threads, showed; we cover precisely this in some detail in the Understanding the kernel segment section).(内核 VAS 或通常所说的内核段中实际存在什么? 所有内核代码、数据结构(包括任务结构、列表、内核模式堆栈、分页表等)、设备驱动程序、内核模块等都在此处(如下图所示的下半部分)))

本文标签: 内存系统LinuxVMSplit