帝游网提供最新手游APP下载和游戏攻略!

Linux段页内存管理技术详解

发布时间:2024-09-24浏览:101

你遇到过Linux内核的陷阱吗?

1. 概述

1.虚拟地址空间

内存是通过指针来寻址的,因此CPU的字长决定了CPU可以管理的地址空间的大小。该地址空间称为虚拟地址空间。因此,32位CPU的虚拟地址空间大小为4G。这与实际物理内存量无关。 Linux内核将虚拟地址空间分为两部分:

其中一部分可供用户进程使用。这部分地址是地址空间的低地址部分,从0到TASK_SIZE,称为用户空间。

其中一部分保留给内核使用。这部分地址是地址空间的高地址部分,从KERNELBASE到末尾,称为内核空间。

一些与之相关的宏:

KERNELBASE:内核虚拟地址空间的起始地址,通常与PAGE_OFFSET相同,但也可能不同。

PAGE_OFFSET:内核虚拟地址空间低端内存的起始地址

PHYSICAL_START:内核物理地址的起始地址

MEMORY_START:内核低端内存的物理起始地址

当切换进程时,用户进程可用的部分会发生变化,但保留给内核使用的部分在切换进程时保持不变。在32位系统上,两部分的典型划分比例是3:1(这个比例是可以修改的),即4G虚拟地址空间中的3G可供用户进程访问,而另外1G则保留给用户进程使用。核心。在这个划分中,用户进程可用的虚拟地址空间为0x00000000-0xbffffffff,内核的虚拟地址空间为0xc0000000-0xffffffff。为不同的进程使用不同的用户空间可以将不同进程的用户空间部分相互隔离,从而保护进程的用户空间部分。对内核空间的保护是通过CPU的特权级别来实现的。所有现代CPU 都提供多个特权级别。每个权限级别获得的权限是不同的。当CPU处于一定的权限级别时,它只能执行符合要求的程序。受此级别权限限制的操作。 Linux采用了两种权限级别,分别对应内核权限和用户权限,并对属于内核的内存空间添加了权限限制,使得CPU只有在内核权限级别时才能访问这些内存区域,这样也保护了内核空间。站起来了。

2、物理地址到虚拟地址的映射

可用的物理内存被映射到内核虚拟地址空间。在32位系统中,内核会直接将一部分物理内存映射到内核的虚拟地址空间中。如果访问内存时使用的虚拟地址与内核虚拟地址起始值之间的偏移量不超过这部分内存的大小,则该虚拟地址将直接与物理页框关联;否则必须借助“高端内存”来访问。因此,也可以看出,之所以采用“高端内存”,是因为CPU可寻址的虚拟地址可能小于实际的物理地址。内存,因此必须求助于其他机制(“高内存”)来访问所有内存。在IA-32系统上,这个空间的大小是896M。 64位系统不使用高端内存。这是因为64位系统的理论地址空间远大于实际的物理内存(至少目前如此),因此没有必要诉诸“高端内存”。对于用户进程来说,由于其所有的内存访问都是通过页表进行的,不会直接进行,所以用户进程不存在高端内存。高端内存由32位架构内核使用。在32位架构内核中,要使用高端内存,必须首先使用kmap将高端内存映射到内核的虚拟地址空间中。

3.内存类型

从硬件角度来看,有两种不同类型的机器,每种机器使用不同的方式来管理内存。

UMA(统一内存访问):统一内存访问机,以连续的方式组织可用内存。在SMP系统中,每个CPU可以以相同的速度访问内存。

NUMA(非均匀内存访问):非均匀内存访问机器始终是多处理器机器。系统的每个CPU都有本地内存,支持快速访问。系统中的所有处理器都通过总线连接,可以访问其他CPU的本地内存,但速度不如访问本地内存快。

如果想在lnux中支持NUMA系统,需要打开CONFIG_NUMA选项。

2. 内存组织

Linux内核对于一致和不一致的内存访问系统使用相同的数据结构,因此不同内存布局的内存管理算法几乎没有差异。对于UMA系统,可以将其视为只有一个NUMA节点的NUMA系统,即NUMA的一种特例。这将简化内存管理的其他部分,其他部分可以认为它们正在处理NUMA 系统。

1.基本概念及相关数据结构

Linux引入了一个称为节点的概念。一个节点对应一个存储体。对于UMA系统,只有一个节点。对应的数据结构是“struct pglist_data”。对于NUMA系统来说,整个系统的内存是由一个名为node_data的struct pglist_data (page_data_t)指针数组来管理的。 NUMA系统的内存划分如图:

每个节点分为多个zone,每个zone对应一个内存区域。内核引入了枚举常量zone_type来描述zone的类型:

[cpp]查看plaincopyenumzone_type{#ifdefCONFIG_ZONE_DMAZONE_DMA,#endif#ifdefCONFIG_ZONE_DMA32ZONE_DMA32,#endifZONE_NORMAL,#ifdefCONFIG_HIGHMEMZONE_HIGHMEM,#endifZONE_MOVABLE,MAX_NR_ZONES};

它们的用途不同:

ZONE_DMA:可用作DMA的内存区域。这类内存区域处于物理内存的低端,主要是因为ISA设备只能使用低端地址来进行DMA操作。

ZONE_NORMAL:由内核直接映射到自己的虚拟地址空间的地址。

ZONE_HIGHMEM:无法直接映射到内核虚拟地址空间的地址。

ZONE_MOVABLE:伪区域,用于防止物理内存碎片的机制中

MAX_NR_ZONES:结束标记

显然,区域的类型根据内核配置项的不同而变化。每个zone都关联一个数组,用于组织和管理属于该zone的物理内存页。区域由数据结构struct zone 表示。所有节点都保存在一个链表中。使用时,内核始终尝试从与运行进程的CPU 关联的NUMA 节点请求内存。这需要使用备用列表。每个节点通过struct zonelist提供一个备用列表。该列表包含可用于代替该节点分配内存的其他节点。顺序代表分配的优先级,优先级越高。级别越高。

2.页面

1. 页面概念

内核使用struct page作为管理物理内存的基本单位。从内核的角度来看,所有RAM都被划分为固定长度的页框。每个页框包含一个页面,这意味着页框的长度与页面的长度相同。页框是主存的一部分,是一个存储区域。页和页框的区别在于,页是抽象的数据结构,可以存储在任何地方,而页框是真实的存储区域。 struct page 包含跟踪物理页框当前用途的信息。例如页数、标志等。

2. 将页面映射到区域

内核使用struct page的flags中的字段来保存该页面所属的区域和节点。这是通过set_page_zone 和set_page_node 完成的,它们由函数set_page_links 调用。

3.页表

1.页表机制

CPU管理虚拟地址,因此物理地址需要映射到虚拟地址才能被CPU使用。用于将虚拟地址空间映射到物理地址空间的数据结构称为页表。在使用4k页面大小的情况下,4k地址空间需要2^20个页表项。即使每个页表项的大小为4字节,仍然需要4M内存,并且每个进程都需要有自己的页表,这成为巨大的内存开销。而且,在大多数情况下,虚拟地址空间的大部分区域都没有被使用,因此不需要为虚拟地址空间中的每个页面分配管理结构。因此,在实际应用中,采用以下解决方案:

采用多级页表,每个线性地址被视为“页目录表+页目录表+…+页目录表+页表+页内偏移”的形式,每个位组根据它的意义。用于查找对应表中的数据,最终找到页表。

进程的页表仅包含它使用的地址空间。进程未使用的地址空间不需要添加到进程的页表中。

仅当进程实际需要时,RAM 才会分配给页表,而不是一开始就为进程的所有页分配空间。

页表包含有关页面的信息,例如是否存在于主存中、是否“脏”、访问所需的权限级别、读写标志、缓存策略等。内核的页表存储在全局变量swapper_pg_dir,应用进程的页表存放在task_struct-mm-pgd中。当应用进程切换时,进程的页表也会被切换(schedule--__schedule--context_switch--switch_mm--switch_mmu_context--local_flush_tlb_mm)。

Linux 使用4 级分页模型。如下:

虽然采用4级模型,但:

对于32 位且未启用物理地址扩展的系统,将使用辅助页表。 Linux的做法是将页上级目录表和页中间目录表所包含的位数设置为0,并且使页全局目录表中的位数包括除页表和偏移量之外的所有位,从而取消这两级目录。同时,为了让代码能够在32位和64位环境下运行,Linux保留了这两个二级目录在指针序列中的位置。方法就是将这两个二级目录包含的条目数设置为1(这里注意的是,即使只有一位也可以代表两个术语,所以需要这个设置)。

对于启用物理地址扩展的32位系统,使用三级页表。

对于64位系统,取决于硬件如何划分线性地址位。

在Linux中,每个进程都有自己的页面全局目录(PGD)和自己的一组页表。当发生进程切换时,Linux会完成页表切换。使用该方案后,每个虚拟地址被划分为相应的位组,其中PGD用于索引每个进程特有的页全局表来查找PUD,PUD用于索引进程的页父目录表来查找PMD等等,直到找到PTE。 PTE是页表数组。该表中的条目包含指向页框的指针以及与页面访问控制相关的信息,例如权限、是否在主存中、是否包含“脏”数据等。OFFSET用作表条目偏移量。使用这种机制后,虚拟地址空间中不存在的内存区域对应的PUD、PMD、PTE将不会被创建,从而节省了地址空间。

但采用这种机制后,每次寻址都需要多次查表才能找到对应的物理地址,从而减少了快递量。 CPU 使用高速缓存和TLB 来加速寻址过程。访问内存时,如果虚拟地址对应的TLB存在,即TLB命中,则直接访问。否则,必须使用相关页表项更新TLB(此时可能需要创建新的页表项),然后继续。使用权。下图展示了CPU的虚拟地址到真实地址的转换过程:

当访问的地址没有对应的TLB表项时,就会产生TLB中断。在TLB中断中,会有:

首先查找访问地址对应的页表。如果找不到对应的页表,就会生成对应的页表项(powerpc通过调用读写异常处理函数来完成这个过程)。

使用PTE 的内容更新TLB。

TLB的内容更新后,仍然可能会出现读写异常(也称为页面错误),因为页表项虽然存在,但其内容可能是非法的(例如页表不在内存中) 。

2.x86架构中的页面

1.地址空间

使用x86时,必须区分三个不同的地址:

逻辑地址:机器语言指令仍然使用这个地址来指定操作数的地址或指令的地址。这种寻址方法在Intel的分段结构中特别具体,它允许MS-DOS或Windows程序员将程序划分为多个段。每个逻辑地址由段和偏移量组成。

线性地址:线性地址是一个32位无符号整数,最多可以表示2的32次方(4GB)的地址。通常用十六进制来表示线性地址,其取值范围为0x00000000~0xffffffff。

物理地址:即内存单元的实际地址,用于芯片级内存单元寻址。物理地址也用32 位无符号整数表示。

物理地址中的MMU。转换过程如图所示:

2. 细分

1)细分机制

在x86段机制中,逻辑地址由两部分组成,即段部分(选择器)和偏移部分。段构成逻辑地址到线性地址转换的基础。如果我们把段看成一个对象,它的描述如下:

段基地址:线性地址空间中段的起始地址。

段限制(Limit):表示逻辑地址中段内可以使用的最大偏移量。

段属性(Attribute):表示段的特征。例如,该段是否可以读取或写入,或者该段是否作为程序执行、该段的特权级别等。

段边界定义逻辑地址空间中段的大小。段内偏移量从0 到limit 的逻辑地址对应于从Base 到Base+Limit 的线性地址。在段内,偏移量大于段边界的逻辑地址将没有意义。使用这样的逻辑地址,系统将产生异常。另外,如果要访问某个段,系统会根据该段的属性来检查访问者是否有访问权限。如果没有,将会产生异常。例如,在80386中,如果要在只读段中写入,80386会根据该段的属性检测到这是违规操作,并产生异常。下图显示了段如何从逻辑地址空间重定位到线性地址空间。图中左侧表示逻辑地址空间,定义了三个段A、B、C,段容量分别为LimitA、LimitB、LimitC。图中的虚线将逻辑地址空间中的段A、B、C连接到线性地址空间区域来表示这种转换。

段的基地址、限制和保护属性存储在段的描述符表中,并且在虚拟地址到线性地址转换过程中访问描述符。段描述符依次存储在内存中的段描述符表中,该表是段描述符数组。简单来说,段描述符表存储的是段描述符,段描述符中包含了硬件将逻辑地址转换为线性地址所需的全部信息。每个段描述符定义了线性地址空间中的一段地址、它的属性以及它与逻辑地址空间的映射关系。其实就是如何从逻辑地址空间映射到线性地址空间。

2)linux中的段

各种段描述符都存储在段描述符表中,要么在GDT中,要么在LDT中。描述符表(即段表)定义了386系统所有段的状态。所有描述符表本身占用的内存空间是8字节的倍数,空间大小范围从8字节(至少包含1个描述符)到64K字节(最多包含8K描述符)。

全局描述符表(GDT):全局描述符表GDT(Global Descriptor Table)包含系统中所有任务所共有的那些段的描述符。

本地描述符表(LDT):本地描述符表LDT(本地描述符表)包含与给定任务相关的描述符。每个任务都有一个LDT。通过LDT,给定任务的代码和数据可以与其他任务隔离。

每个任务的局部描述符表LDT也用一个描述符来表示,称为LDT描述符,它包含局部描述符表的信息,放置在全局描述符表GDT中。然而Linux很少使用分段机制。这是因为分段和分页都可以用来将物理地址划分成小的地址片段,因此它们彼此是冗余的。分段可以为不同的进程分配不同的线性地址空间,而分页可以将相同的线性地址空间映射到不同的物理地址空间。 Linux使用分页机制的原因如下:

如果所有进程都使用相同的线性地址空间,内存管理会更简单

许多其他CPU 架构对分段的支持有限

在Linux中,所有运行在用户态的进程都使用相同的指令段和数据段,因此这两个段又称为用户数据段和用户指令段。同样,内核使用自己的内核数据段和内核数据段。这些部分分别使用宏_ _USER_CS、_ _USER_DS、_ _KERNEL_CS 和_ _KERNEL_DS 进行定义。这些段都从0开始并且大小相同。因此,在Linux中,线性地址和逻辑地址是相同的,内核和用户进程都可以使用相同的逻辑地址。逻辑地址也是虚拟地址。这与其他架构类似。统一。单处理器系统只有一个GDT,而多处理器系统中的每个CPU都有一个GDT。 GDT存储在cpu_gdt_table中。 GDT包含用户数据段、用户指令段、内核数据段、内核指令段和一些其他段信息。绝大多数Linux用户程序不使用LDT。内核定义了一个大多数进程共享的默认LDT。它存储在default_ldt中。如果应用程序需要创建自己的本地描述表,可以通过modify_ldt系统调用来完成。使用此系统调用创建的LDT 需要有自己的段。应用程序还可以通过modify_ldt创建自己的段。

4.内存管理初始化

1.初始化过程

内存初始化的关键是page_data_t数据结构及其下属数据结构(zone、page)的初始化。宏NODE_DATA用于获取指定节点对应的page_data_t。在多节点系统中,节点数据结构为struct pglist_data *node_data[];该宏获取对应节点对应的数据结构。如果是单节点系统,节点数据结构为struct pglist_data contig_page_data;这个宏直接返回它。

1.初始化代码流程

系统启动代码中与内存管理相关的初始化代码如图:

其功能是:

setup_arch: 架构相关的初始化,其中包括内存管理的架构相关部分的初始化。此时引导分配器被初始化。

在setup_per_cpu_areas:SMP 中,该函数初始化源代码中静态定义的每CPU 变量。这种类型的变量对于系统中的每个CPU 都有一个副本。这些变量保存在内核二进制文件的单独段中。

build_all_zonelists: 创建节点和区域数据结构

mem_init: 初始化内存分配器

setup_per_cpu_pageset: 遍历系统中的所有zone,为每个zone的所有CPU分配页集(热页缓存和冷页缓存)并初始化它们。在调用此函数之前,只有引导页集可用。

2. 节点和区域的初始化

build_all_zonelists会遍历系统中的所有节点,并为每个节点的内存域生成一个数据结构。它最终会使用节点数据结构来调用build_zonelists。该函数将在该节点的内存与系统中其他节点之间建立距离关系。距离表示从其他节点进行分配的成本。因此,距离越大,分配成本也越大;后续的内存分配会根据这个距离,优先考虑本地的。如果本地不可用,则按照距离由近到远进行分配,直至成功或全部失败。在节点的内存域中:

高端内存被认为是最便宜的,因为内核不依赖高端内存,其耗尽不会对系统产生负面影响。

DMA 被认为是最昂贵的,因为它有特殊的用途。它用于与外设交换数据。

普通内存介于两者之间,因为内核的某些部分依赖于普通内存,所以它的耗尽会对系统产生影响。

分配内存时,假设指定内存区域的昂贵度为A,则分配过程为:

先从这个节点开始尝试分配,然后从A开始按成本递增的顺序尝试,直到最昂贵的区域

如果从该节点分配失败,则根据距离关系依次检查其他点。在检查每个节点时,仍然从A开始按照成本递增的顺序进行尝试,直到最昂贵的区域。

2. 特定于架构的设置

1. 内核在内存中的布局

这是默认布局,但有一些例外:

PHYSICAL_START 可用于配置和修改内核在内存中的位置。

内核可以编译为可重定位的二进制文件,在这种情况下,引导加载程序确定内核的位置。

默认情况下,内核安装在RAM 中,从物理地址0x00100000 开始。也就是从2M开始的。没有安装在1M地址空间开头的原因:

页帧0 由BIOS 使用,包含加电自检(POST) 期间检查的系统硬件配置。

从0x000a0000 到0x000fffff 的物理地址范围通常保留给BIOS 程序使用

第一个MB 内的其他页框可能被某些计算机型号保留

_edata和_end之间的初始化数据部分占用的内存有一部分在初始化完成后就不再需要了,可以回收。您可以控制哪些部件可以回收,哪些部件不能回收。内核占用的内存被分为若干段,其边界存储在变量中。可以通过System.map查看相关信息。系统启动后,还可以通过/proc/iomem查看相关信息。

2.初始化步骤

在start_kernel中,会调用setup_arch来进行架构相关的初始化。 setup_arch会完成启动分配器的初始化以及各个内存域的初始化(paging_init)。 paging_init最终会调用free_area_init_node,这是一个与体系结构无关的函数,它将完成node和zone数据结构的初始化。

3、分页机制的初始化

Linux内核将虚拟地址空间分为两部分:用户空间和内核空间。当切换进程时,用户进程可用的部分会发生变化,但保留给内核使用的部分在切换进程时保持不变。在32位系统上,两部分的典型划分比例是3:1(这个比例是可以修改的),即4G虚拟地址空间中的3G可供用户进程访问,而另外1G则保留给用户进程使用。核心。在32位系统中,内核地址空间分为几个部分,如下图:

3.1 直接映射

第一部分用于直接将一部分物理内存映射到内核的虚拟地址空间。如果访问内存时使用的虚拟地址与内核虚拟地址起始值之间的偏移量不超过这部分内存的大小,那么该虚拟地址将直接与物理页框关联;否则必须通过“高端内存”访问。在IA-32系统上,这部分空间的大小为896M。对于内存直接映射的部分,内核提供了两个宏:

__pa(vaddr):用于返回虚拟地址vaddr对应的物理地址。

__va(paddr):用于返回物理地址paddr对应的虚拟地址。

其余部分被内核用于其他目的:

虚拟地址连续但物理地址不连续的内存区域可以从VMALLO区域分配。这种机制通常用于用户进程,内核本身会尽可能尝试使用连续的物理地址。当然,当直接映射的部分不能满足需要时,内核也会使用这个区域。该区域由ppc32 中的ioremap 使用。

持久映射区域用于将高端内存中的非持久页映射到内核中。

固定映射用于与物理地址空间中的固定页相关联的虚拟地址页,但物理地址页,即页框,可以自由选择。

每个内存区域的边界由图中所示的常量定义。 high_memory 定义直接映射区域的边界。系统中定义了一些与页面相关的常量:

num_physpages:最高可用页框的页框号

Totalram_pages:可用页框总数

min_low_pfn:内核映像之后RAM中第一个可用的页帧号

max_pfn:最后可用的页框号

max_low_pfn:内核直接映射的最后一个页框的页框号(在低端内存中)

Totalhigh_pages:非内核直接映射的页框总数(在高端内存中)

直接映射的内存区域和用于vmalloc的内存区域之间有一个大小为VMALLOC_OFFSET的间隙,用于保护内核的地址免遭越界访问(跨越直接映射区域)。

3.2 vmalloc区

vmalloc区域的起始位置取决于high_memory和VMALLOC_OFFSET。到哪里结束取决于是否启用了高端内存支持。如果未启用高内存支持,则不需要持久映射区域,因为所有内存都可以直接映射。

3.3 持久映射区域

持久映射页从PKMAP_BASE 开始,其大小由LAST_PKMAP 表示有多少页。

3.4 固定映射区域

固定映射的优点是,在编译时,这种类型的地址被视为常量,内核一旦启动就为其分配物理地址。对此类地址的引用比普通指针更快。在上下文切换期间,内核不会刷新与固定地址映射对应的TLB,因此对这些地址的访问总是通过缓存。对于每个固定地址,必须创建一个常量并将其添加到名为“fixed_addresses”的枚举列表中。内核提供了virt_to_fix和fix_to_virt用于虚拟地址和固定地址常量之间的转换。

set_fixmap用于建立固定地址常量与物理页的对应关系。

3.5 热页和冷页

free_area_init_node最终将调整为zone_pcp_init,它将计算该区域的批次值。 setup_per_cpu_pageset将完成冷热缓存的初始化。

3.启动时的内存管理

bootmem 分配器被内核用来在引导过程中分配内存。这是一个非常简单的先适应分配器。它使用位图来管理页面、位

1表示页忙,0表示空闲。需要分配内存时就扫描位图,直到找到第一个能够满足需求的内存区域。 1. 数据结构 内核为每个节点都分配了一个struct bootmem_data结构的实例用来管理该node的内存。 2. 初始化 在不同的架构下初始化的代码不尽相同,但是都是在paging_int中被调用。 3. 分配器接口 alloc_bootmem*用于分配内存free_bootmem*用于释放内存 4. 停用bootmem分配器 当slab系统完成初始化,能够承担内存分配工作时,需要停掉该分配器,这是通过free_all_bootmem(UMA系统)或free_all_bootmem_node(NUMA系统)来完成的 5. 释放初始化数据 内核提供了两个属性__init用于标记初始化函数,__initdata用于标记初始化数据,这意味着这个函数/数据在初始化完成后其内存就不需了,可以进行回收利用。 4. 内核页表的初始化 以powerpc为例,内核页表的初始化由MMU_init来完成,它在start_kernel之前被调用: MMU_init->mapin_ram->__mapin_ram_chunk->map_page, map_page的代码如下: [cpp]view plaincopyintmap_page(unsignedlongva,phys_addr_tpa,intflags){pmd_t*pd;pte_t*pg;interr=-ENOMEM;/*Useupper10bitsofVAtoindexthefirstlevelmap*/pd=pmd_offset(pud_offset(pgd_offset_k(va),va),va);/*Usemiddle10bitsofVAtoindexthesecond-levelmap*/pg=pte_alloc_kernel(pd,va);if(pg!=0){err=0;/*ThePTEshouldneverbealreadysetnorpresentinthe*hashtable*/BUG_ON((pte_val(*pg)&(_PAGE_PRESENT|_PAGE_HASHPTE))&&flags);set_pte_at(&init_mm,va,pg,pfn_pte(pa>>PAGE_SHIFT,__pgprot(flags)));}returnerr;} 再看下init_mm的相关定义: [cpp]view plaincopystructmm_structinit_mm={.mm_rb=RB_ROOT,.pgd=swapper_pg_dir,.mm_users=ATOMIC_INIT(2),.mm_count=ATOMIC_INIT(1),.mmap_sem=__RWSEM_INITIALIZER(init_mm.mmap_sem),.page_table_lock=__SPIN_LOCK_UNLOCKED(init_mm.page_table_lock),.mmlist=LIST_HEAD_INIT(init_mm.mmlist),INIT_MM_CONTEXT(init_mm)}; 因此可见,kernel的页表是保存在swapper_pg_dir中的。它是init_task的active_mm: [cpp]view plaincopy#defineINIT_TASK(tsk)\{\.state=0,\.stack=&init_thread_info,\.usage=ATOMIC_INIT(2),\.flags=PF_KTHREAD,\.prio=MAX_PRIO-20,\.static_prio=MAX_PRIO-20,\.normal_prio=MAX_PRIO-20,\.policy=SCHED_NORMAL,\.cpus_allowed=CPU_MASK_ALL,\.nr_cpus_allowed=NR_CPUS,\.mm=NULL,\.active_mm=&init_mm,\[cpp]view plaincopytructtask_structinit_task=INIT_TASK(init_task); init_task是内核代码开始位置被执行的: [cpp]view plaincopy/**Thisiswherethemainkernelcodestarts.*/start_here:/*ptrtocurrent*/lisr2,init_task@horir2,r2,init_task@l start_here在start_kernel之前被执行。在start_kernel里rest_init会启动kernel_init来启动一个init进程,init_task并不是init进程,init_task是内核启动主代码所在的上下文,该进程最后停在了cpu_idle中(start_kernel->rest_init->cpu_idle),好吧,它的真面目出来了,它就是创世界的进程,并且最后变成了无所事事的idle了。

用户评论

玻璃渣子

哇!这篇博客讲解Linux段页式内存管理技术真的是太棒了!以前我总是对这些概念一头雾水,现在终于明白了其中的奥妙之处。特别是关于页面分配、段表和虚拟地址的解释非常清晰易懂,受益匪浅!

    有13位网友表示赞同!

龙吟凤

刚开始看开头还挺懵逼,不过越看下去就觉得越有趣了。虽然Linux内核代码挺难懂的,但博主把复杂的技术点都用通俗易懂的语言讲解清楚了,这也太牛了吧!我已经记下文中提到的几个关键点准备去继续深入研究linux内存管理机制了。

    有7位网友表示赞同!

枫无痕

这篇帖子很有深度,我虽然是学网络安全出身的,对操作系统架构还没那么了解,但很多概念也能看明白。博主分析得很细致,把段页式内存管理的优缺点都概括出来了,让我对Linux内核管理内存的方式有更清晰的概念了。

    有13位网友表示赞同!

墨城烟柳

说实话,感觉这篇文章有点太技术专业了,我虽然学的是计算机科学,但对内存管理的深入理解还是略微不足。希望能有一些更加图形化的解释或案例,能更容易地理解段页式内存管理的原理以及应用场景。

    有14位网友表示赞同!

無極卍盜

我很佩服博主能够把复杂的技术点翻译成通俗易懂的话语,这篇文章简直是Linux爱好者的必读之作!学习了这么多年的编程基本功,我对操作系统内核机制却始终了解不多,看了你的文章对段页式内存管理有了更清晰的认识,真是太感谢了!

    有20位网友表示赞同!

逃避

我觉得这段写得非常好,能够详细地解释Linux段页式内存管理的关键概念,例如:虚地址、实地址、页面和段等等。我一直在寻找这样的资料来深入了解Windows内核的实现原理,这篇文章很有帮助!不过对于实际应用层面,希望能提供一些更具体的场景分析和案例介绍。

    有18位网友表示赞同!

怀念·最初

对Linux不太感兴趣,但看到这段提到的虚拟内存机制还是很有启发性的,它巧妙地利用了程序的间接跳转地址来管理系统内存空间,有效提高了内存利用效率。这种技术也应用在其他操作系统里面吧?

    有16位网友表示赞同!

颓废人士

这篇博文分析得太透彻了!让我对Linux段页式内存管理有了更深刻的理解。例如,它是如何将虚拟地址映射到物理地址上的,以及如何在进程间进行内存隔离等等,都被解释得很清晰,让我受益匪浅!

    有7位网友表示赞同!

棃海

感觉文章有点长,我是学生党,没多少时间学习这些复杂的操作系统原理。能否简化一下内容,重点突出段页式内存管理的核心概念和实际应用场景?这样更能吸引我这种入门级的读者。

    有9位网友表示赞同!

繁华若梦

对Linux操作系统了解不多,这段的讲解让我茅塞顿开!原来内存管理这么有深度啊!不过文中代码的演示有点看不懂,建议增加一些文字解释或注释,方便对代码理解较弱的读者。

    有13位网友表示赞同!

见朕骑妓的时刻

这篇文章真是太棒了!给我打开了 Linux 内核世界的大门!之前一直听说段页式内存管理是Linux的优亮点,现在终于明白它是怎么做到了!感觉学习linux内核还要更加系统地去研究下

    有11位网友表示赞同!

抚笙

虽然文章比较深入,但讲得还是挺明白的。我以前对操作系统只是略知一二,这篇文章让我对 Linux 的段页式内存管理有了更清晰的认识!真是受益匪浅。

    有16位网友表示赞同!

愁杀

作为一个从事软件开发的小白,我对内核相关的技术理解还很有限。这篇博文虽然有些理论性强,但是我觉得对理解 Linux 内核内存管理机制很有帮助。我准备慢慢研读下去!

    有13位网友表示赞同!

揉乱头发

这篇文章对Linux段页式内存管理的解释非常详细,涵盖了从概念到实现原理各个方面。对于想要深入了解Linux内核的朋友来说,这是一篇必读的文章。

    有18位网友表示赞同!

掉眼泪

学习Linux系统要重视内存管理机制,这篇博文讲解得相当到位!内容丰富,能够解答很多疑惑。不过个人认为可以多一些案例分析,以便更好地理解段页式内存管理的实际应用场景。

    有17位网友表示赞同!

醉枫染墨

太赞了!这篇文章简直是 Linux 内核学习者的福音! 以前我总觉得内存管理很复杂,看完这篇博客后,终于清楚这段机制是怎么工作的

    有9位网友表示赞同!

墨城烟柳

对Linux段页式内存管理技术的讲解非常全面和清晰,尤其是对于一些比较抽象的概念,博主用通俗易懂的语言解释得非常好。学习这类知识确实需要时间和耐心,我很感谢作者分享如此宝贵的知识!

    有8位网友表示赞同!

浅笑√倾城

我觉得文章结构还可以改进一下,比如将一些技术细节分成几个不同的章节,方便读者阅读和理解。另外,增加一些代码示例会更有帮助,可以让读者更好地掌握段页式内存管理的具体实现过程。

    有8位网友表示赞同!

热点资讯