linux 高端内存linux 复位usb设备后内容还在吗

Linux高端内存管理之非连续内存区(描述)_Linux教程_Linux公社-Linux系统门户网站
你好,游客
Linux高端内存管理之非连续内存区(描述)
来源:Linux社区&
作者:bullbat
总结了高端内存区的固定内核映射区、临时内核映射与永久内核映射。但是对于高端内存中各个区间的布置我们任然不是很清楚,首先我们从整体上看看内核对高端内存的划分情况。
如果内存足够大(比如用户:内核线性空间,内核就只能访问线性空间的第内容,如果物理内存超过则视为足够大),内核线性空间无法同时映射所有内存。这就需要将内核线性空间分出一段不直接映射物理内存,而是作为窗口分时映射使用到的未映射的内存。
相关阅读:
一、非连续内存区布局
Linux内核中对于非连续区间的开始:
#define&VMALLOC_START&&&((unsigned&long)high_memory&+&VMALLOC_OFFSET)&&
#define&VMALLOC_OFFSET&&(8&*&1024&*&1024)&&
对于变量high_memory变量:
void&__init&initmem_init(unsigned&long&start_pfn,&&
&&&&&&&&&&&&&&&&&&unsigned&long&end_pfn)&&
&&&&highstart_pfn&=&highend_pfn&=&max_&&
&&&&if&(max_pfn&&&max_low_pfn)&&
&&&&&&&&highstart_pfn&=&max_low_&&
&&&&num_physpages&=&highend_&&
&&&&high_memory&=&(void&*)&__va(highstart_pfn&*&PAGE_SIZE&-&1)&+&1;&&
其中,变量在函数中初始化为下面值
#define&MAXMEM&&(VMALLOC_END&-&PAGE_OFFSET&-&__VMALLOC_RESERVE)&&
&p&unsigned&int&__VMALLOC_RESERVE&=&128&&&&20;&/p&&&
对于非连续区间的结束定义:
#&define&VMALLOC_END&&&&(PKMAP_BASE&-&2&*&PAGE_SIZE)&&
由上面的内核代码,画出内存布局细节图如下
由上面的布局可知,然而直接映射区和连续内存之间空出来了的空间不能用,非连续空间和永久内核映射区之间也有的空间不可用,另外,内存顶端空出了不可用的。这样,高端内存能用的空间为大小的内存。
二、数据结构描述
虚拟内存区描述(对于链表)
struct&vm_struct&{&&
&&&&struct&vm_struct&&&&*&&
&&&&void&&&&&&&&&&&&*&&
&&&&unsigned&long&&&&&&&&&
&&&&unsigned&long&&&&&&&&&
&&&&struct&page&&&&&**&&
&&&&unsigned&int&&&&&&&&nr_&&
&&&&unsigned&long&&&&&&&phys_&&
&&&&void&&&&&&&&&&&&*&&
虚拟内存区描述(对于红黑树)
struct&vmap_area&{&&
&&&&unsigned&long&va_&&
&&&&unsigned&long&va_&&
&&&&unsigned&long&&&
&&&&struct&rb_node&rb_&&&&&/*&address&sorted&rbtree&*/&&
&&&&struct&list_head&&&&&&&/*&address&sorted&list&*/&&
&&&&struct&list_head&purge_&&&&/*&"lazy&purge"&list&*/&&
&&&&void&*&&
&&&&struct&rcu_head&rcu_&&
内存区由字段链接到一起,并且为了查找简单,他们以地址为次序。为了防止溢出,每个区域至少由一个页面隔离开。
三、非连续内存区初始化
非连续内存区的初始化工作在start_kernel()-&mm_init()-&vmalloc_init()完成
void&__init&vmalloc_init(void)&&
&&&&struct&vmap_area&*&&
&&&&struct&vm_struct&*&&
&&&&int&i;&&
&&&&for_each_possible_cpu(i)&{&&
&&&&&&&&struct&vmap_block_queue&*&&
&&&&&&&&vbq&=&&per_cpu(vmap_block_queue,&i);&&
&&&&&&&&spin_lock_init(&vbq-&lock);&&
&&&&&&&&INIT_LIST_HEAD(&vbq-&free);&&
&&&&&&&&INIT_LIST_HEAD(&vbq-&dirty);&&
&&&&&&&&vbq-&nr_dirty&=&0;&&
&&&&for&(tmp&=&&&tmp&=&tmp-&next)&{&&
&&&&&&&&va&=&kzalloc(sizeof(struct&vmap_area),&GFP_NOWAIT);&&
&&&&&&&&va-&flags&=&tmp-&flags&|&VM_VM_AREA;&&
&&&&&&&&va-&va_start&=&(unsigned&long)tmp-&&&
&&&&&&&&va-&va_end&=&va-&va_start&+&tmp-&&&
&&&&&&&&__insert_vmap_area(va);&&
&&&&vmap_area_pcpu_hole&=&VMALLOC_END;&&
&&&&vmap_initialized&=&true;&&
相关资讯 & & &
& (08月09日)
& (08月01日)
& (07月31日)
& (08月09日)
& (07月31日)
& (07月31日)
   同意评论声明
   发表
尊重网上道德,遵守中华人民共和国的各项有关法律法规
承担一切因您的行为而直接或间接导致的民事或刑事法律责任
本站管理人员有权保留或删除其管辖留言中的任意内容
本站有权在网站内转载或引用您的评论
参与本评论即表明您已经阅读并接受上述条款博客访问: 842598
博文数量: 538
博客积分: 967
博客等级: 准尉
技术积分: 2537
注册时间:
APP发帖 享双倍积分
IT168企业级官微
微信号:IT168qiye
系统架构师大会
微信号:SACC2013
分类: LINUX
原文地址: 作者:
内存管理子系统是linux内核最核心最重要的一部分,内核的其他部分都需要在内存管理子系统的基础上运行。而对其初始化是了解整个内存管理子系统的基础。对相关数据结构的初始化是从全局启动例程start_kernel开始的。本文详细描述了从bootloader跳转到linux内核内存管理子系统初始化期间所做的操作,从而来加深对内存管理子系统知识的理解和掌握。
内核的入口是stext,这是在arch/arm/kernel/vmlinux.lds.S中指定的。而符号stext是在arch/arm/kernel/head.S中定义的。整个初始化分为两个阶段,首先是在head.S中用汇编代码执行一些平台相关的初始化,完成后跳转到start_kernel函数用C语言代码执行剩余的通用初始化部分。整个初始化流程图如下图所示:
一、&&&& 启动条件
通常从系统上电到运行到linux kenel这部分的任务是由boot
loader来完成。Boot loader在跳转到kernel之前要完成一些限制条件:
1、CPU必须处于SVC(supervisor)模式,并且IRQ和FIQ中断必须是禁止的;
2、MMU(内存管理单元)必须是关闭的,&此时虚拟地址对应物理地址;
3、数据cache(Data cache)必须是关闭的;
4、指令cache(Instruction
cache)没有强制要求;
5、CPU通用寄存器0(r0)必须是0;
6、CPU通用寄存器1(r1)必须是ARM Linux
machine type;
7、CPU通用寄存器2(r2)必须是kernel
parameter list的物理地址;
二、&&&& 汇编代码初始化部分
汇编代码部分主要完成的工作如下所示,下文只是概述head.S中各个函数完成的主要
功能,具体的技术细节,本文不再赘述。
确定processor
确定machine
调用平台特定的__cpu_flush函数
最终跳转到start_kernel
三、&&&& C语言代码初始化部分
C语言代码初始化部分主要负责建立结点和内存域的数据结构、初始化页表、初始化用
于内存管理的伙伴系统。在启动过程中,尽管内存管理模块尚未初始化完成,但内核仍然需要分配内存以创建各种数据结构。bootmem分配器用于在启动阶段分配内存。所有涉及的实现都是在start_kernel函数中实现的,其中涉及到内存初始化的函数如下:
1、 build_all_zonelist用于结点和内存域的初始化。
在linux系统中,内存划分结点,每个结点关联到系统中的一个处理器。各个结点又划分内存域,主要包括ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM。大部分系统只有一个内存结点,下文只针对此类系统。此函数用于初始化内存的结点和内存域。内核在mm/page/page_alloc.c中定义了一个pglist_data的内存结点实例(contig_page_data)用于管理所有系统的内存。
build_all_zonelist用于初始化结点和内存域。该函数首先调用__build_all_zonelists,此函数遍历系统的每个内存结点,针对每个内存结点调用build_zonelists(),该函数的任务是在当前处理的结点和系统中其他结点的内存域之间建立一种等级次序。接下来,依据这种次序分配内存,如果在期望的结点内存域中,没有空闲内存,就去查找相邻结点的内存域。内核定义了内存的一个层次结构关系,首先试图分配廉价的内存,如果失败,则根据访问速度和容量,逐渐尝试分配更昂贵的内存。
高端内存最廉价,因为内核没有任何部分依赖于从该内存域分配的内存,如果高端内存用尽,对内核没有副作用,所以优先分配高端内存。
普通内存域的情况有所不同,许多内核数据结构必须保存在该内存域,而不能放置到高端内存域,因此如果普通内存域用尽,那么内核会面临内存紧张的情况。
DMA内存域最昂贵,因为它用于外设和系统之间的数据传输。
举例来讲,如果内核指定想要分配高端内存域。它首先在当前结点的高端内存域寻找适当的空闲内存段,如果失败,则查看该结点的普通内存域,如果还失败,则试图在该结点的DMA内存域分配。如果在3个本地内存域都无法找到空闲内存,则查看其他结点。这种情况下,备选结点应该尽可能靠近主结点,以最小化访问非本地内存引起的性能损失。
该函数接下来计算所有剩余的内存页,存放到全局变量vm_total_pages中。接下来如果空闲内存页太少,则关闭页的可迁移性,即page_group_by_mobility_disabled置位。页的可迁移性是内核为了避免内存碎片而在linux2.6.24中加入的新特性。该特性把内存页分为不可移动内存页、可回收页和可移动页三种类型来避免内存碎片。
2、 mem_init用于停用bootmem分配器并迁移到伙伴系统。
在系统初始化进行到伙伴系统分配器能够承担内存管理的责任后,必须停用bootmem分
配器。该函数遍历所有的内存域结点,对每个结点分别调用free_all_bootmem_node函数,停用bootmem分配器。该函数调用free_all_bootmem_core,首先扫描bootmem分配器位图,释放每个未用的页。到伙伴系统的接口是__free_pages_bootmem函数,该函数对每个空闲内存页调用,该函数内部依赖于标准函数__free_page。它使得这些页并入到伙伴系统的数据结构,在其中作为空闲页,用于分配管理。在页位图已经完全扫描后,它占据的内存空间也必须释放掉,此后,只有伙伴系统可用于内存分配。
3、 初始化slab分配器。
kmem_cache_init初始化内核用于小块内存区的分配器(slab分配器)。他在内核初始化阶段、伙伴系统启用之后调用。
kmem_cache_init创建系统中的第一个slab缓存,以便为kmem_cache的实例提供内存,为此,内核使用一个在编译时创建的静态数据(cache_cache)。该函数接下来初始化一般性的缓存,用作kmalloc内存的来源。为此,针对所需的各个缓存长度,分别调用kmem_cache_create函数。
4、 初始化页表,初始化bootmem分配器。
setup_arch是特定于体系结构的,用于初始化页表,初始化bootmem分配器。该函数的执行流程图如下:
4.1、setup_processor和setup_machine用来确定处理器类型和机器类型。
4.2、parse_targs用来解析从uboot传递过来的tag值。在uboot的do_bootm_linux函数中,会创建传递给内核的各个tag。tag的地址是由内核和uboot约定的,在uboot中是在函数board_init中将该约定好的地址保存在gd->bd->bi_boot_params中,如下所示:
gd->bd->bi_boot_params = CFG_BOOT_PARAMS;
在内核中,MACHINE_START定义了struct machine_desc数据结构,各成员函数在linux启动的不同时期被调用。该结构体的boot_params成员存放的就是uboot传递给内核的tag的起始地址。
MACHINE_START(hi,
&&& .phys_io&&& =
IO_SPACE_PHYS_START,
&&& .io_pg_offst&&& =
(IO_ADDRESS(IO_SPACE_PHYS_START) >> 18) & 0xfffc,
&&& .boot_params&&& =
PHYS_OFFSET + 0x100,
&&& .map_io&&&& =
hisilicon_map_io,
&&& .init_irq&& =
hisilicon_init_irq,
&&& .timer&&&&& =
&hisilicon_timer,
&&& .init_machine&& = hisilicon_init_machine,
MACHINE_END
&&& 在uboot中通过setup_memory_tags来建立内存的tag,把物理内存的起始地址和大小记录在tag里,tag头用ATAG_MEM标识。内核中有一个tagtable的表,把各种表头的标识和解析函数关联起来,关于内存的如下所示:
__tagtable(ATAG_MEM, parse_tag_mem32);
当parse_targs找到以ATAG_MEM标识的tag后,调用parse_tag_mem32,把在uboot中填充到tag里的内存起始地址和大小填充到全局变量meminfo数组中。
4.3、parse_cmdline解析命令行参数。
&&& 此函数解析由uboot传进来的命令行参数。在内核中,各个命令行参数的头都与相应的解析函数一一对应,关于内存,有如下对应:
__early_param("mem=", early_mem);
在内核中解析命令行中以"mem="开头的命令行,找到此命令行后,调用early_mem函数解析命令行。如果内核需要管理几段不同的内存,可以在uboot的bootarg环境变量中分别指定对应的内存段的起始地址和长度,如下所示:
mem=72M@0xe2000000
mem=128M@0xe8000000
表示内核需要管理两段不连续的内存,第一段起始地址为0xe2000000,大小为72M,另外一段起始地址为0xe8000000,大小为128M,内核通过early_mem函数分别把这两段内存写入到meminfo的两个bank中。
4.4、paging_init函数用来初始化页表项,启用bootmem分配器。
4.4.1、build_mem_type_table用来建立各种类型的页表选项。该函数是为了给mem_types数组中的各种类型的页表参数添加上我们的要求,主要是一级页表,二级页表,访问权限控制等标志位。
4.4.2、prepare_page_table函数清除一级页表中无效的页表,只保留物理内存在虚拟地址空间中的映射。
4.4.3、devicemaps_init函数用来清除VMALLOC_END之后的一级页表,分配vector page,调用mdesc->map_io来映射设备,mdesc->map_io即上文中提到的用MACHINE_START定义的结构体中的成员,即hisilicon_map_io
4.4.4、bootmem_init函数在做一些必要的操作后,调用bootmem_init_node来初始化一级页表、启用bootmem分配器。
&&& Linux内核的段页表项将4GB的地址空间分成4096个1MB的段(section),每个段页表项是一个unsigned long型变量,占用4字节,因此段页表项占用K的内存空间。而全局页表项的大小为2M,如下
&&& #define PGDIR_SHIFT&&&& 21
#define PGDIR_SIZE&&&&& (1UL
<< PGDIR_SHIFT)
而全局页表项的数据结构pgd_t的定义如下
typedef struct { unsigned long pgd[2]; } pgd_t;
所以一个全局页表对应两个段页表,正好符合上述定义。
该函数首先根据meminfo数组中的bank数目,分别调用map_memory_bank来映射各个物理内存区的段页表项。该函数调用create_mapping来执行具体的映射工作。
建立完物理内存的页表映射后,内核会把物理内存的起始地址的页帧号放入start_pfn,物理内存的结束地址的页帧号放入end_pfn。这里要注意如果有多个内存bank时,start_pfn和end_pfn之间可能有空洞,拿上文提到的例子来讲,start_pfn和end_pfn之间是有内存空洞的,具体如下:
mem=72M@0xe2000000
mem=128M@0xe8000000
start_pfn = 0xe2000000 >> PAGE_SHIFT;
end_pfn = (0xe8000000 + 8000000) >> PAGE_SHIFT;
在内核启动期间,由于基于物理内存的伙伴系统尚未初始化完成,但内核仍然需要分配内存以创建各种数据结构,bootmem分配器用于在启动阶段早期分配内存。bootmem分配器是一个最先适配分配器,该分配器使用一个位图来管理内存页,位图比特位的数目与系统中物理内存页的数目相同,比特位为1表示已用页,比特位为0表示空闲页。在需要分配内存时,分配器逐位扫描位图,直至找到一个能提供足够连续内存页的位置,即所谓的最先适配位置。bootmem分配器也必须管理一些数据,内核为系统中的每个结点都提供了一个bootmem_data结构的实例,用于该用途。当然,该结构不能动态分配,只能在编译时分配给内核,在UMA系统上,只有一个bootmem_data_t实例,即contig_bootmem_data。
typedef struct bootmem_data {
&&& unsigned long
node_boot_
&&& unsigned long
*node_bootmem_
&&& unsigned long
&&& unsigned long
&&& unsigned long
&&& struct
} bootmem_data_t;
node_boot_start保存了系统中物理内存的第一个页帧的编号。
node_low_pfn是系统物理内存最后一页的页帧编号。
node_bootmem_map 是指向bootmem分配器位图所在地址的指针。
last_pos是上一次分配的页帧编号。如果没有请求分配整个页帧,last_offset用作该页内部的偏移量,这使得bootmem分配器可以分配小于一整页的内存区。
last_success指定位图中上一次成功分配内存的位置,新的分配由此开始。
list:所有注册的bootmem分配器保存在一个链表中,表头是全局变量bdata_list。
内核接下来调用bootmem_bootmap_pages来计算bootmem分配器位图的大小(需要按页对齐),然后调用find_bootmap_pfn在物理内存中找到一块大小合适的内存,优先考虑内核的数据段的结尾处。接下来调用init_bootmem_node初始化bootmem分配器,该函数调用init_bootmem_core来填充bootmem_data_t结构中各个成员。
接下来调用free_bootmem_node进而调用free_bootmem_core把整个位图清零,把所有内存页标记为空闲页。然后在位图中分别把已用的内存页标记为1,已用的内存页包括位图占用的内存页、initrd占用的内存页、内核的代码段和数据段占用的内存,到此bootmem分配器正式建立起来了。
最后,free_area_init_node用来填充内存结点的数据结构pglist_data中的各个成员变量,其中zones_size表示物理内存的大小,包含内存空洞,zholes_size是内存空洞的页数。pgdat是内存结点的数据结构,node_start_pfn是物理内存的起始页帧编号。该函数首先调用calculate_node_totalpages用来计算总的物理内存页帧数,包括内存空洞,保存在内存结点数据结构的node_spanned_pages成员中,然后计算去掉内存空洞后的实际物理内存页帧数,保存在node_present_pages成员中。
由于每个内存页都需要有一个struct page的数据结构来管理,所以该函数接下来调用
alloc_node_mem_map来创建所有物理内存页的struct page实例,指向该空间的指针不仅保存在pglist_data结构的node_mem_map中,还保存在全局变量mem_map中。
&&& 最后,free_area_init_node函数调用free_area_init_core来初始化pglist_data实例的各个内存域。该函数负责初始化各个zone结构中的成员。在ARM Linux中,没有ZONE_HIGHMEM,ZONE_NORMAL初始化成0,所有可用的物理内存被放置在ZONE_DMA。主要涉及两个函数:zone_pcp_init用来初始化冷热缓存页,init_currently_empty_zone用来初始化伙伴系统的free_area列表,并将属于该内存域的所有page实例都设置成默认值。
&&& zone_pcp_init负责初始化冷热缓存页。struct zone的pageset成员用于实现冷热页分配器。内核说页是热的,意味着页已经加载到了CPU高速缓存,与在内存中的页相比,其数据能够更快的访问。相反,冷页则不在高速缓存中。在多处理器系统上每个CPU都有一个或多个高速缓存,各个CPU的管理必须是独立的。pageset是一个数组,其容量与系统能够容纳的CPU数目的最大值相同。
&&& struct zone{
&&&&&&& ………
&&&&&&& Struct per_cpu_pageset pageset[NR_CPUS];
&&&&&&& ………
NR_CPUS是一个可以在编译时配置的宏常数。在单处理器上其值总是1,针对SMP系统编译的内核中,其值可能是2到32之间。数组元素的类型为per_cpu_pageset,定义如下
struct per_cpu_pageset {
&&& struct
per_cpu_pages pcp[2];&&& /* 0: hot.& 1: cold */
} ____cacheline_aligned_in_
该结构有两个数组项,第一项管理热页,第二项管理冷页。per_cpu_pageset结构如下:
struct per_cpu_pages {
&&& struct
count记录了与该列表相关的页的数目。
high是页数上限的水印值,在需要的情况下清空列表。
batch是每次添加页数的值。
zone_pcp_init函数首先用zone_batchsize计算出批量添加页的大小,保存在batch中,然后遍历系统中所有CPU,同时调用setup_pageset填充每个per_cpu_pageset实例的常量。根据计算得到的batch大约相当于内存域中页数的0.25‰。
init_currently_empty_zone用来初始化与伙伴系统相关的free_aera列表。该函数首先调用memmap_init_zone初始化上文分配好的保存在全局变量mem_map中的物理内存的struct page实例。然后调用zone_init_free_lists初始化free_aera空闲列表。空闲页的数目free_area.nr_free当前仍然规定为0,直至停用bootmem分配器、普通的伙伴系统分配器生效时,才会设置正确的数值。
整个内核地址空间的划分请参见下图:
图中PAGE_OFFSET=0xc0000000,TEXT_OFFSET=0x在arch/arm/makefile中指定,swapper_pg_dir=0x在head.S中指定。
&&& 地址空间的第一段用于将系统的所有物理内存页映射到内核的虚拟地址空间中。由于内核地址空间从偏移量0xc0000000开始,即3GiB,所以每个虚拟地址x都对应于物理地址x-0xc0000000,因此这是一个简单的线性偏移。
&&& 直接映射区:从PAGE_OFFSET(即0xc0000000)开始到high_memory的地址空间,在内核调用bootmem_init函数中,把high_memory的值设置为实际物理内存的结束地址对应的虚拟地址,最大不超过896M。当实际物理内存大于896M时,超出的部分映射为高端内存区。物理内存的起始地址的16K未用,从swapper_pg_dir到0xc0008000的16K存放段地址的页表项内容,前文已经描述过了。从0xc0008000开始存放内核的代码段、初始化的数据段和未初始化的数据段。紧接着内核的代码段和数据段的是bootmem分配器的位图区域,上文中页提到过。
&&& VMALLOC区:虚拟内存中连续、但物理内存中不连续的内存区,可以在vmalloc区域分配。该机制通常用于用户过程,内核自身会试图尽力避免非连续的物理内存。但在已经运行了很长时间的系统上,在内核需要物理内存时,可能出现可用内存不连续的情况。此类情况,主要出现在动态加载模块时。vmalloc区域在何处结束取决于是否启用了高端内存支持。如果没有启用,那么就不需要持久映射区,因为整个物理内存都是可以直接映射的。因此根据配置的不同,该区域结束于持久内核映射或固定映射区域的起始处,中间总会留下两页,作为vmalloc区与这两个区域之间的保护措施。
&&& 持久映射区:用于将高端内存域中的非持久页映射到内核。
&&& 固定映射区:是与物理地址空间中的固定页关联的虚拟地址空间页,但具体关联的页帧可以自由选择。它通过固定公式与物理内存关联的直接映射页相反,虚拟固定映射地址与物理内存位置之间的关联可以自行定义。
&&& 在直接映射区和vmalloc区域之间有一个8M的缺口,这个缺口可用作针对任何内核故障的保护措施。如果访问越界地址,则访问失败并生成一个异常,报告该错误。如果vmalloc区域紧接着直接映射,那么访问将成功而不会注意到错误。
到此,整个linux的内存管理子系统初始化完成了,具体的细节部分请参考代码阅读体会。
阅读(615) | 评论(0) | 转发(0) |
相关热门文章
给主人留下些什么吧!~~
请登录后评论。2622人阅读
linux内存管理(23)
前面总结了高端内存中永久内核映射和临时内核映射。linux高端内存中的临时内存区为固定内存区的一部分,下面是Linux内存布局图
对于固定内存在内核中有下面描述
enum fixed_addresses {
#ifdef CONFIG_X86_32
VSYSCALL_LAST_PAGE,
VSYSCALL_FIRST_PAGE = VSYSCALL_LAST_PAGE
+ ((VSYSCALL_END-VSYSCALL_START) && PAGE_SHIFT) - 1,
VSYSCALL_HPET,
FIX_DBGP_BASE,
FIX_EARLYCON_MEM_BASE,
#ifdef CONFIG_X86_LOCAL_APIC
FIX_APIC_BASE, /* local (CPU) APIC) -- required for SMP or not */
#ifdef CONFIG_X86_IO_APIC
FIX_IO_APIC_BASE_0,
FIX_IO_APIC_BASE_END = FIX_IO_APIC_BASE_0 + MAX_IO_APICS - 1,
#ifdef CONFIG_X86_VISWS_APIC
FIX_CO_CPU, /* Cobalt timer */
FIX_CO_APIC, /* Cobalt APIC Redirection Table */
FIX_LI_PCIA, /* Lithium PCI Bridge A */
FIX_LI_PCIB, /* Lithium PCI Bridge B */
#ifdef CONFIG_X86_F00F_BUG
FIX_F00F_IDT, /* Virtual mapping for IDT */
#ifdef CONFIG_X86_CYCLONE_TIMER
FIX_CYCLONE_TIMER, /*cyclone timer register*/
#ifdef CONFIG_X86_32
FIX_KMAP_BEGIN, /* reserved pte's for temporary kernel mappings */
FIX_KMAP_END = FIX_KMAP_BEGIN+(KM_TYPE_NR*NR_CPUS)-1,
#ifdef CONFIG_PCI_MMCONFIG
FIX_PCIE_MCFG,
#ifdef CONFIG_PARAVIRT
FIX_PARAVIRT_BOOTMAP,
FIX_TEXT_POKE1, /* reserve 2 pages for text_poke() */
FIX_TEXT_POKE0, /* first page is last, because allocation is backward */
__end_of_permanent_fixed_addresses,
* 256 temporary boot-time mappings, used by early_ioremap(),
* before ioremap() is functional.
* We round it up to the next 256 pages boundary so that we
* can have a single pgd entry and a single pte table:
#define NR_FIX_BTMAPS
#define FIX_BTMAPS_SLOTS 4
FIX_BTMAP_END = __end_of_permanent_fixed_addresses + 256 -
(__end_of_permanent_fixed_addresses & 255),
FIX_BTMAP_BEGIN = FIX_BTMAP_END + NR_FIX_BTMAPS*FIX_BTMAPS_SLOTS - 1,
#ifdef CONFIG_PROVIDE_OHCI1394_DMA_INIT
FIX_OHCI1394_BASE,
#ifdef CONFIG_X86_32
FIX_WP_TEST,
#ifdef CONFIG_INTEL_TXT
FIX_TBOOT_BASE,
__end_of_fixed_addresses
ioremap的作用是将和以及物理地址空间映射到在至的的地址空间内,使得能够访问该空间并进行相应的读写操作。
start_kernel()-&setup_arch()-&early_ioremap_init()
void __init early_ioremap_init(void)
if (early_ioremap_debug)
printk(KERN_INFO &early_ioremap_init()\n&);
/*将fixed_address里的索引的虚拟地址放入slot_virt
,从代码里面可以看出,放入slot_virt中得虚拟地址为1M*/
for (i = 0; i & FIX_BTMAPS_SLOTS; i++)
slot_virt[i] = __fix_to_virt(FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*i);
/*得到固定映射区的pmd
,此pmd为虚拟地址转换为物理地址的pmd*/
pmd = early_ioremap_pmd(fix_to_virt(FIX_BTMAP_BEGIN));
memset(bm_pte, 0, sizeof(bm_pte));
/*将bm_pte页表设置为固定映射区开始地址的pmd的第一个页表;*/
pmd_populate_kernel(&init_mm, pmd, bm_pte);
* The boot-ioremap range spans multiple pmds, for which
* we are not prepared:
/*系统要求所有的ioremap映射在一个pmd上,超出这个pmd将警告*/
if (pmd != early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END))) {
WARN_ON(1);
printk(KERN_WARNING &pmd %p != %p\n&,
pmd, early_ioremap_pmd(fix_to_virt(FIX_BTMAP_END)));
printk(KERN_WARNING &fix_to_virt(FIX_BTMAP_BEGIN): %08lx\n&,
fix_to_virt(FIX_BTMAP_BEGIN));
printk(KERN_WARNING &fix_to_virt(FIX_BTMAP_END):
fix_to_virt(FIX_BTMAP_END));
printk(KERN_WARNING &FIX_BTMAP_END:
%d\n&, FIX_BTMAP_END);
printk(KERN_WARNING &FIX_BTMAP_BEGIN:
FIX_BTMAP_BEGIN);
static unsigned long slot_virt[FIX_BTMAPS_SLOTS] __#define __fix_to_virt(x) (FIXADDR_TOP - ((x) && PAGE_SHIFT))
对于的使用需要通过和进行。由于对应于的内存空间是有限的,所以对于空间的使用遵照使用结束马上释放的原则。这就是说和必须配对使用并且访问结束必须马上执行。
static void __init __iomem *
__early_ioremap(resource_size_t phys_addr, unsigned long size, pgprot_t prot)
resource_size_t last_
enum fixed_addresses idx0,
WARN_ON(system_state != SYSTEM_BOOTING);
slot = -1;
/*pre_map[]是一个索引与slot_virt[]一一对应,这段for
的含义在于找到一个没有被使用过的slot_virt[i]的页面,
该slot_virt[i]所指向的虚拟页面地址就是将会和实际物
理地址phys_addr相绑定的虚拟地址。*/
for (i = 0; i & FIX_BTMAPS_SLOTS; i++) {
if (!prev_map[i]) {
if (slot & 0) {
printk(KERN_INFO &early_iomap(%08llx, %08lx) not found slot\n&,
(u64)phys_addr, size);
WARN_ON(1);
return NULL;
if (early_ioremap_debug) {
printk(KERN_INFO &early_ioremap(%08llx, %08lx) [%d] =& &,
(u64)phys_addr, size, slot);
dump_stack();
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
if (!size || last_addr & phys_addr) {
WARN_ON(1);
return NULL;
prev_size[slot] =
* Mappings have to be page-aligned
offset = phys_addr & ~PAGE_MASK;/*offset是页内的偏移*/
phys_addr &= PAGE_MASK;/*现在phys_addr就是起始页面的地址*/
/*现在size就是指出了到底占据了多少个页面的大小*/
size = PAGE_ALIGN(last_addr + 1) - phys_
* Mappings have to fit in the FIX_BTMAP area.
/*到底我们需要多少页面*/
nrpages = size && PAGE_SHIFT;
if (nrpages & NR_FIX_BTMAPS) {
WARN_ON(1);
return NULL;
* Ok, go for it..
/*找到空闲slot所对应的fixed_address中的索引号*/
idx0 = FIX_BTMAP_BEGIN - NR_FIX_BTMAPS*
idx = idx0;
while (nrpages & 0) {
/*在bm_ptes中将指定的idx索引的页表项填充为对应的物理地址使得bm_pte[idx]指向正确的物理页面地址*/
early_set_fixmap(idx, phys_addr, prot);
phys_addr += PAGE_SIZE;
if (early_ioremap_debug)
printk(KERN_CONT &%08lx + %08lx\n&, offset, slot_virt[slot]);
/*返回phys_addr所指向的虚拟地址*/
prev_map[slot] = (void __iomem *)(offset + slot_virt[slot]);
return prev_map[slot];
参考知识库
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
访问:396142次
积分:4637
积分:4637
排名:第4819名
原创:76篇
转载:16篇
评论:66条
文章:57篇
阅读:190346
(1)(1)(1)(1)(5)(2)(2)(3)(4)(2)(1)(10)(16)(20)(23)(2)

我要回帖

更多关于 linux 高端内存用完 的文章

 

随机推荐