胖头猫游戏提供热门游戏下载和手游攻略!

Linux用户空间和内核空间(了解高端内存)

发布时间:2024-10-14浏览:72

各位老铁们,大家好,今天由我来为大家分享Linux用户空间和内核空间(了解高端内存),以及的相关问题知识,希望对大家有所帮助。如果可以帮助到大家,还望关注收藏下本站,您的支持是我们最大的动力,谢谢大家了哈,下面我们开始吧!

段页机制如下图。

Linux内核地址空间划分

通常32位Linux内核地址空间分为0~3G作为用户空间,3~4G作为内核空间。注意,这是32位内核地址空间划分,64位内核地址空间划分不同。

Linux内核高端内存的由来

当内核模块代码或线程访问内存时,代码中的内存地址都是逻辑地址,与真实的物理内存地址对应,需要进行地址的一一映射。例如逻辑地址0xc0000003对应的物理地址为03,0xc0000004对应的物理地址为04,……,逻辑地址与物理地址的关系为

物理地址=逻辑地址0xC0000000

假设根据上面简单的地址映射关系,内核逻辑地址空间访问为0xc0000000 ~0xffffffff,那么对应的物理内存范围为00 ~ 040000000,即只能访问1G物理内存。如果机器安装了8G物理内存,内核只能访问前1G物理内存,后面的7G物理内存将无法访问,因为内核的地址空间已经全部映射到物理内存地址范围0 0 ~ 040000000。即使安装了8G物理内存,内核如何访问物理地址040000001的内存呢?代码必须有一个内存逻辑地址。0xc0000000到0xffffffff的地址空间已经被用完,所以物理地址040000000之后的内存无法访问。

显然,内核地址空间0xc0000000 ~0xffffffff不能用于简单的地址映射。因此,x86架构将内核地址空间分为三部分:ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。 ZONE_HIGHMEM是高端内存,这就是高端内存概念的由来。

在x86架构中,三种类型的区域如下:

ZONE_DMA内存从16MB开始

ZONE_NORMAL 16MB~896MB

ZONE_HIGHMEM 896MB ~ 结束

Linux内核高端内存的理解

前面我们解释了高端内存的由来。 Linux将内核地址空间分为三部分:ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM。高端内存HIGH_MEM地址空间范围为0xF8000000到0xFFFFFFFF(896MB到1024MB)。那么内核是如何利用128MB高端内存地址空间来访问所有物理内存的呢?

当内核要访问物理地址高于896MB的内存时,它会在0xF8000000 ~0xFFFFFFFF地址空间范围内找到相应大小的空闲逻辑地址空间,并借用一段时间。借用这个逻辑地址空间,并将其映射到想要访问的物理内存(即填写内核PTE页表)。暂时使用一段时间,用完后归还。这样,其他人也可以借用这个地址空间来访问其他物理内存,实现利用有限的地址空间来访问所有物理内存。如下图所示。

例如,内核要访问从2G开始的1MB物理内存,即物理地址范围为080000000 ~0x800FFFFF。访问之前,首先找到一块1MB的空闲地址空间。假设找到的空闲地址空间为0xF8700000 ~0xF87FFFFF。使用这1MB逻辑地址空间映射到物理地址空间080000000 ~0x800FFFFF中的内存。映射关系如下:

内核访问080000000 ~0x800FFFFF物理内存后,释放0xF8700000 ~0xF87FFFFF内核线性空间。这样,其他进程或代码也可以使用地址0xF8700000 ~0xF87FFFFF来访问其他物理内存。

从上面的描述我们可以知道高端内存最基本的思想:借用一段地址空间,建立临时地址映射,使用完后释放。当到达这个地址空间时,就可以回收并访问所有物理内存。

看到这里,有人不禁要问:如果一个内核进程或模块一直占用某个逻辑地址空间而不释放怎么办?如果这种情况真的发生,内核的高端内存地址空间将会变得越来越紧张。如果被占用而不释放,即使没有映射到物理内存,也无法访问。

在香港尖沙咀的一些写字楼里,厕所和门锁很少。顾客如果想去卫生间,可以向前台领取钥匙,使用后将钥匙归还给前台。这样,虽然只有一间卫生间,但可以满足所有顾客上厕所的需求。如果顾客继续占用卫生间且不归还钥匙,其他顾客将无法使用卫生间。 Linux内核中高端内存管理的思路也类似。

Linux内核高端内存的划分

内核将高端内存分为3部分:VMALLOC_START~VMALLOC_END、KMAP_BASE~FIXADDR_START和FIXADDR_START~4G。

对于高端内存,可以通过alloc_page()或者其他函数获取对应的页。但是,如果要访问实际的物理内存,就必须将页转换成线性地址(为什么呢?想想MMU是如何访问物理内存的)。也就是说,我们需要为高端内存对应的页找到一个线性空间。这个过程称为高端内存映射。

对应高端内存的三部分,高端内存映射有三种方式:

映射到“内核动态映射空间”(非连续内存分配)

这种方法很简单,因为通过vmalloc(),在“内核动态映射空间”申请内存时,有可能从高端内存中获取页面(参见vmalloc的实现),所以有可能高-end内存被映射到“内核动态映射空间”“空间”。

永久内核映射

如果通过alloc_page()获得高端内存对应的页,如何为其找到线性空间呢?

内核为此专门预留了一个线性空间,从PKMAP_BASE到FIXADDR_START,用于映射高端内存。在2.6内核上,这个地址范围在4G-8M和4G-4M之间。这个空间称为“内核永久映射空间”或“永久内核映射空间”。该空间使用与其他空间相同的页目录表。对于内核来说,它是swapper_pg_dir。对于普通进程,由CR3寄存器指向。通常情况下,该空间大小为4M,因此只需要一张页表。内核通过pkmap_page_table来查找这个页表。通过kmap(),可以将一个页面映射到这个空间。由于这个空间大小为4M,因此最多可以同时映射1024个页面。因此,未使用的页面应及时从该空间中释放(即释放映射关系)。通过kunmap(),可以从这个空间释放一个页面对应的线性地址。

临时内核映射

内核在FIXADDR_START和FIXADDR_TOP之间保留了一些线性空间以供特殊需要。这个空间称为“固定映射空间”。在这个空间中,有一部分用于高端内存的临时映射。

这个空间有以下几个特点:

(1)每个CPU占用一块空间

(2)将每个CPU占用的空间划分为多个小空间。每个小空间的大小为1 页。每个小空间都有其用途。这些用途在kmap_types.h km_type 中定义。

当需要进行临时映射时,需要指定映射的目的。根据映射目的,可以找到对应的小空间,然后将该空间的地址作为映射地址。这意味着临时映射将导致先前的映射被覆盖。临时映射是通过kmap_atomic()实现的。

可以参考:Linux高端内存映射等。

常见问题:

1、用户空间(进程)有高端内存的概念吗?

用户进程没有高端内存的概念。高端内存仅存在于内核空间中。用户进程最多只能访问3G物理内存,而内核进程可以访问所有物理内存。

2. 64位内核有高端内存吗?

目前的现实情况是,高端内存在64位Linux内核中并不存在,因为64位内核可以支持超过512GB的内存。如果机器上安装的物理内存超过了内核地址空间,就会出现高端内存。

3. 用户进程可以访问多少物理内存?内核代码可以访问多少物理内存?

在32位系统上,用户进程最多可以访问3GB,内核代码可以访问所有物理内存。

在64位系统上,用户进程最多可以访问超过512GB,内核代码可以访问所有物理内存。

4、高端内存与物理地址、逻辑地址、线性地址的关系是什么?

高端内存只与逻辑地址有关,与逻辑地址和物理地址没有直接关系。

5. 为什么不将所有地址空间分配给内核?

如果所有的地址空间都给了内存,那么用户进程如何使用内存呢?如何保证内核使用的内存不与用户进程冲突?

(1) 我们忽略Linux对分段内存映射的支持。在保护模式下,我们知道,无论CPU运行在用户态还是内核态,CPU执行程序访问的地址都是虚拟地址。 MMU必须读取控制寄存器CR3中的值作为当前页目录的指针,然后根据分页内存映射机制(参见相关文档)将虚拟地址转换为真实的物理地址,以便CPU实际上可以访问物理地址。

(2)对于32位Linux来说,每个进程都有4G的寻址空间,但是当一个进程访问其虚拟内存空间中的某个地址时,如何才能不与其他进程的虚拟空间混淆呢?每个进程都有自己的页目录PGD,Linux将这个目录的指针存储在进程对应的内存结构task_struct.(struct mm_struct)mm-pgd中。每当一个进程被调度(schedule())并即将进入运行状态时,Linux内核就会用该进程的PGD指针设置CR3(switch_mm())。

do_fork() --copy_mm() --mm_init() --pgd_alloc() --set_pgd_fast() --get_pgd_slow() --memcpy(PGD + USER_PTRS_PER_PGD, swapper_pg_dir + USER_PTRS_PER_PGD, (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t) )

这样,每个进程的页目录就被分为两部分。第一部分是“用户空间”,用于映射其整个进程空间(0x0000 0000-0xBFFF FFFF),是一个3G字节的虚拟地址;第二部分是“系统空间”,用于映射(0xC000 0000-0xFFFF FFFF)1G字节虚拟地址。可以看出,Linux系统中每个进程的页目录第二部分都是相同的,所以从进程的角度来看,每个进程都有4G字节的虚拟空间,下面的3G字节是自己的用户空间,最高1G字节是所有进程和内核共享的系统空间。

(4) 现在假设我们有以下场景:

在进程A中,通过系统调用sethostname(const char *name, seze_t len)设置计算机在网络中的“主机名”。

在这种场景下,我们必然会涉及到将数据从用户空间传输到内核空间的问题。 name是用户空间中的地址,必须通过系统调用将其设置为内核中的地址。我们看一下这个过程中的一些细节: 系统调用的具体实现是将系统调用的参数存放在寄存器ebx、ecx、edx、esi、edi中(最多5个参数,这个场景有两个name和len) ),然后将系统调用号存放在寄存器eax中,然后通过中断指令“int 80”使进程A进入系统空间。由于进程的CPU运行级别小于等于系统调用设置的陷门访问级别3,因此可以畅通无阻地进入系统空间执行为int 80设置的函数指针system_call()。 ()属于内核空间,其运行级别DPL为0,CPU需要将栈切换到内核栈,也就是进程A的系统空间栈。我们知道,当内核为一个新的进程创建task_struct结构体时进程中,它分配两个连续的page,大小为8K,并使用底部约1k的大小作为task_struct(如#define alloc_task_struct() ((struct task_struct *) __get_free_pages( GFP_KERNEL,1))),而其余的的内存用于系统空间的堆栈空间,即从用户空间转移到系统空间时,堆栈指针esp变成(alloc_task_struct()+8192),这就是为什么系统空间通常使用宏的原因定义current(参见其实现)用于获取当前进程的task_struct地址。进程每次从用户空间进入系统空间时,系统栈已经被压入用户栈SS、用户栈指针ESP、EFLAGS、用户空间CS、EIP,然后system_call()将eax压入,然后调用SAVE_ALL压入依次ES、DS、EAX、EBP、EDI、ESI、EDX、ECX、EBX,然后调用sys_call_table+4*%EAX,本例为sys_sethostname()。

小结

进程寻址空间0~4G 进程在用户态只能访问0~3G,3G~4G只能在进入内核态时访问。每个进程通过系统调用进入内核态时虚拟空间的3G~4G部分是相同的。当进程从用户态进入内核态时,不会引起CR3的变化,但会引起堆栈的变化。 Linux简化了分段机制,使得虚拟地址和线性地址始终保持一致。因此,Linux的虚拟地址空间也是0~4G。 Linux内核将这4G字节空间分为两部分。最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF)由内核使用,称为“内核空间”。低3G字节(从虚拟地址0x00000000到0xBFFFFFFF)被每个进程使用,称为“用户空间”。由于每个进程都可以通过系统调用进入内核,所以Linux内核是由系统中所有进程共享的。所以,从具体进程来看,每个进程可以拥有4G字节的虚拟空间。

Linux采用两级保护机制:0级针对内核,3级针对用户程序。从图中可以看出(这里无法表示图),每个进程都有自己的私有用户空间(0~3G),对于系统中的其他进程是不可见的。虚拟内核空间的顶部1GB 由所有进程和内核共享。

1虚拟内核空间到物理空间的映射

内核代码和数据存储在内核空间中,而用户程序代码和数据存储在进程的用户空间中。无论是内核空间还是用户空间,它们都在虚拟空间中。读者可能会问,系统启动时,内核代码和数据不是加载到物理内存中的吗?为什么它们也在虚拟内存中?这和编译器有关,稍后我们会通过详细讨论来理解这一点。

虽然内核空间占据了每个虚拟空间的最高1GB字节,但到物理内存的映射总是从最低地址(0x00000000)开始。对于内核空间来说,它的地址映射是一种非常简单的线性映射。0xC0000000是物理地址和线性地址之间的位移,在Linux代码中称为PAGE_OFFSET。

我们看一下include/asm/i386/page.h中关于内核空间地址映射的描述和定义:

源码中的注释指出,如果你的物理内存大于950MB,则需要在编译内核时添加CONFIG_HIGHMEM4G和CONFIG_HIGHMEM64G选项。我们暂时不考虑这种情况。如果物理内存小于950MB,那么对于内核空间来说,给定一个虚拟地址x,它的物理地址为“x- PAGE_OFFSET”,给定一个物理地址x,它的虚拟地址为“x+ PAGE_OFFSET”。

再次强调,宏__pa() 仅将内核空间中的虚拟地址映射到物理地址,绝不适用于用户空间。用户空间中的地址映射要复杂得多。

2内核映像

在下面的描述中,我们将内核代码和数据称为内核映像。系统启动时,Linux内核映像安装在物理地址0x00100000开始处,即从1MB开始的区间(这1M保留作其他用途)。然而,在正常操作期间,整个内核映像应该位于虚拟内核空间中。因此,在链接内核映像时,链接器会向所有符号地址添加偏移量PAGE_OFFSET。这样,内核映像就从内核空间的开头开始。地址是0xC0100000。

例如,进程的页目录PGD(一种内核数据结构)位于内核空间中。当切换进程时,必须设置寄存器CR3指向新进程的页目录PGD。该目录的起始地址是内核空间中的虚拟地址,但CR3需要物理地址。在这种情况下,必须使用__pa。 () 执行地址转换。 mm_context.h中有这么一行语句:

这是一行嵌入的汇编代码。其含义是通过__pa()将下一个进程的页目录next_pgd的起始地址转换为物理地址,存储到某个寄存器中,然后使用mov指令写入CR3寄存器。中间。处理完这行语句后,CR3接下来指向新进程的页目录表PGD。

用户评论

青瓷清茶倾城歌

这篇文章解释得非常清楚,终于明白了用户空间和内核空间的区别!之前一直觉得这两个概念很模糊,现在看懂了高端内存的概念就更清晰了。

    有9位网友表示赞同!

何必锁我心

高层内存这个概念真的很抽象,幸好这篇文章用通俗易懂的语言讲解,配上图示和例子,理解起来就容易多了。之前也想过要学习 Linux 内核空间的概念,看来这条路线对新手来说比较容易上手。

    有7位网友表示赞同!

陌颜幽梦

内核空间和用户空间之间的边界真是太重要了!感觉这个概念在安全性和性能方面都很有意义。我一直在想怎么提高系统的安全性,现在看来硬件层面的管理也是关键。

    有19位网友表示赞同!

有恃无恐

这篇文章很有帮助,让我对 Linux 体系结构有了更深刻的理解。高阶内存这个概念确实很重要,特别是当开发系统级软件时。不过我一直好奇高端物理内存和虚拟地址空间到底是怎么映射的,希望后续文章能深入讲解一下。

    有16位网友表示赞同!

安陌醉生

作为一名 C/C++ 开发人员,对内核空间的了解的确非常必要!这篇博客讲得很好,让我更清晰地看到了用户空间和内核空间的区别。

    有16位网友表示赞同!

素衣青丝

感觉这种“理解高端内存”的方式有点像是强行压缩内容,很多关键性问题都没有深入展开来说明。比如究竟如何判断进程需要使用到高端内存,以及在使用过程中会遇到的常见问题等等。

    有14位网友表示赞同!

铁树不曾开花

这篇文章虽然解释了内核空间和用户空间的区别,但对高级内存技术的应用场景却缺乏足够的阐述,仅仅停留在概念层面,对于想要深入了解这个领域的读者来说意义不大。

    有8位网友表示赞同!

北染陌人

我平时也使用过 Linux 系统,只是对于内核空间的理解还是比较浅薄。这篇博客让我知道,学习内核空间需要从一些基础的概念入手,比如用户空间和内核空间的区别,以及高端内存是如何工作的等等。

    有13位网友表示赞同!

为爱放弃

感觉文章有点过于简化了这个复杂话题,高端内存是一个非常重要的概念,涉及到的机制非常多,仅仅依靠这几句话无法真正理解它的奥秘。还需要结合更深入的学习资料才能真正掌握核心的知识点。

    有5位网友表示赞同!

怀念·最初

用户空间和内核空间的概念真的太重要了!尤其是在开发安全高效的软件时。我很赞赏这篇文章的写作风格,通俗易懂,非常适合初学者入门,希望能有更多针对不同应用场景的高端内存使用技巧分享。

    有9位网友表示赞同!

丢了爱情i

写的很详细,很感谢作者能够将知识点解释得这么清楚!我最近正在学习 Linux 内核,这篇博客对我帮助很大。

    有20位网友表示赞同!

迷路的男人

高端内存的概念对于软件开发人员来说至关重要!这篇文章的讲解非常到位,让我对Linux内核空间有了更深入的理解。

    有9位网友表示赞同!

减肥伤身#

这篇博文虽然讲得很浅显,但是对于初学者来说已经足够了,至少能让我大致了解 Linux 的工作机制。期待作者后续继续发关于内存管理方面的文章,希望能深入探讨一些更细节的技术问题。

    有10位网友表示赞同!

哽咽

我一直对系统级的知识不太感冒,但最近在做项目的时候发现用户空间和内核空间的概念真的很重要。这篇博客让我第一次真正理解了这两个概念之间的区别,感觉很有收获!

    有12位网友表示赞同!

没过试用期的爱~

作者分析的很具体,讲解的也很生动形象,特别是对于高端内存的使用场景进行了详细说明,真的帮到我了! 希望能看到更多类似的文章分享!

    有8位网友表示赞同!

病房

看完这篇文章后我觉得Linux内核的空间设计确实非常巧妙。用户空间和内核空间的分界清晰,高端内存的设计也让人眼前一亮。

    有10位网友表示赞同!

热点资讯