linux内存管理分析 二,linux内存管理分析【二】

为建立内存管理系统,在内核初始化过程中调用了下面几个函数:

init/main.c

asmlinkage void __init start_kernel(void)

{

......

初始化持久映射与临时映射的一些信息,后面持久映射和临时映射一节将详细讲解

page_address_init();

setup_arch是特定于体系架构的函数,负责初始化自举分配器和内核页表等。

setup_arch(&command_line);

......

初始化per_cpu机制的一些结构,将.data.percpu段中的数据拷贝到每个CPU的数据段中

setup_per_cpu_areas();

......

建立结点和内存域之间的关系

build_all_zonelists(NULL);

......

停用自举分配器bootmem,迁移到实际的内存管理中,初始化slab分配器,初始化进程虚拟地址空间管理结构

mm_init();

......

为每一个内存区域分配per_cpu_pageset结构并初始化其成员

setup_per_cpu_pageset();

......

}

【start_kernel--->setup_arch】

arch/arm/kernel/setup.c

void __init setup_arch(char **cmdline_p)

{

struct machine_desc *mdesc;

内核参数可以通过平坦设备树或者tags由bootloader传递给内核。

每一个机器平台都由一个struct machine_desc结构来描述,内核所支持的所有平台对应的machine_desc结构都包含在段.init.arch.info的 __arch_info_begin 到 __tagtable_end之间。但每一个平台都有其唯一的机器码machine_arch_type,可通过机器码在段.init.arch.info中找到对应的平台描述结构。 函数setup_machine_tags就是根据机器码找到对应的平台描述结构,并且分析内核参数中内存相关的信息,用以初始化内存块管理结构membank。

mdesc = setup_machine_fdt(__atags_pointer);

if (!mdesc)

mdesc = setup_machine_tags(machine_arch_type);

machine_desc = mdesc;

machine_name = mdesc->name;

根据mdesc->dma_zone_size设置DMA区域的大小arm_dma_zone_size,和DMA区域的结束地址arm_dma_limit

setup_dma_zone(mdesc);

结构struct mm_struct 管理进程的虚拟地址空间,所有内核线程都使用共同的地址空间,因为他们都是用相同的地址映射,这个地址空间由init_mm来描述。_text 和_etext表示内核镜像代码段的其实和结束位置,_etext和_edata之间是已初始化数据段,_edata到_end是未初始化数据段等,_end之后便是堆区。

init_mm.start_code = (unsigned long) _text;

init_mm.end_code   = (unsigned long) _etext;

init_mm.end_data   = (unsigned long) _edata;

init_mm.brk        = (unsigned long) _end;

内核命令行参数在函数setup_machine_tags获取并保存在了boot_command_line中

strlcpy(cmd_line, boot_command_line, COMMAND_LINE_SIZE);

*cmdline_p = cmd_line;

分析命令行参数,主要关注一些与内存相关的东西

parse_early_param();

将内存块按从小到大排序

sort(&meminfo.bank, meminfo.nr_banks, sizeof(meminfo.bank[0]), meminfo_cmp, NULL);

扫描各个内存块,检测低端内存的最大值arm_lowmem_limit,设置高端内存起始值的虚拟地址high_memory

sanity_check_meminfo();

将所有内存块添加到结构memblock的memory区中,将已使用的内存添加到reserved区中去。

arm_memblock_init(&meminfo, mdesc);

创建内核页表,初始化自举分配器

paging_init(mdesc);

内核中将许多物理资源用struct resource结构来管理,下面函数就是将IO内存作为resource注册到内核

request_standard_resources(mdesc);

......

如果内核命令行中有预留用于内核crash是的转存空间,就将这些存储空间标记为已分配        reserve_crashkernel();

......

}

【start_kernel--->setup_arch--->setup_machine_tags】

arch/arm/kernel/setup.c

static struct machine_desc * __init setup_machine_tags(unsigned int nr)

{

struct tag *tags = (struct tag *)&init_tags;

struct machine_desc *mdesc = NULL, *p;

char *from = default_command_line;

init_tags.mem.start = PHYS_OFFSET;

下面循环根据机器号在段.init.arch.info中寻找对应的machine_desc结构

for_each_machine_desc(p)

if (nr == p->nr) {

printk("Machine: %s\n", p->name);

mdesc = p;

break;

}

......

Bootloader传入的参数地址存放在__atags_pointer中

if (__atags_pointer)

tags = phys_to_virt(__atags_pointer);

else if (mdesc->atag_offset)

tags = (void *)(PAGE_OFFSET + mdesc->atag_offset);

......

内核参数是由struct tag来管理,其中第一个tag类型必然是ATAG_CORE

if (tags->hdr.tag != ATAG_CORE) {

......

tags = (struct tag *)&init_tags;内核提供的一个默认参数列表

}

函数mdesc->fixup中一般会获取内存块的信息

if (mdesc->fixup)

mdesc->fixup(tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {

如果内存块已经初始化,就将参数列表中关于内存的参数标记为ATAG_NONE

if (meminfo.nr_banks != 0)

squash_mem_tags(tags);

将参数列表拷贝到一个静态数组atags_copy中

save_atags(tags);

分析内核参数,后面细讲

parse_tags(tags);

}

将解析出来的内核命令行信息拷贝到静态数组boot_command_line中。在内核启动期间用了很多静态存储空间,它们前面缀有__initdata,像这样的空间在内核启动起来后将被释放

strlcpy(boot_command_line, from, COMMAND_LINE_SIZE);

return mdesc;

}

【start_kernel--->setup_arch--->setup_machine_tags--->parse_tags】

arch/arm/kernel/setup.c

static void __init parse_tags(const struct tag *t)

{

遍历参数列表中每一个参数结构

for (; t->hdr.size; t = tag_next(t))

if (!parse_tag(t))

......

}

【start_kernel--->setup_arch--->setup_machine_tags--->parse_tags--->parse_tag】

arch/arm/kernel/setup.c

static int __init parse_tag(const struct tag *tag)

{

extern struct tagtable __tagtable_begin, __tagtable_end;

struct tagtable *t;

参数类型多种多样解析方式也各不相同,所有针对每一种参数类型都有一个对应的解析函数,这些解析函数和其参数类型由结构struct tagtable来管理。这些结构都存放在段.init.tagtable的__tagtable_begin和__tagtable_end之间。

for (t = &__tagtable_begin; t 

if (tag->hdr.tag == t->tag) {

t->parse(tag);

break;

}

return t 

}

参数ATAG_MEM的解析函数定义如下:

arch/arm/kernel/setup.c

static int __init parse_tag_mem32(const struct tag *tag)

{

return arm_add_memory(tag->u.mem.start, tag->u.mem.size);

}

__tagtable(ATAG_MEM, parse_tag_mem32);

【parse_tag_mem32--->arm_add_memory】

从ATAG_MEM参数中获取内存信息,初始化内存块管理结构

int __init arm_add_memory(phys_addr_t start, unsigned long size)

{

struct membank *bank = &meminfo.bank[meminfo.nr_banks];

if (meminfo.nr_banks >= NR_BANKS) {

printk(KERN_CRIT "NR_BANKS too low, "

"ignoring memory at 0x%08llx\n", (long long)start);

return -EINVAL;

}

size -= start & ~PAGE_MASK;

bank->start = PAGE_ALIGN(start);

#ifndef CONFIG_LPAE

if (bank->start + size start) {

size = ULONG_MAX - bank->start;

}

#endif

bank->size = size & PAGE_MASK;

if (bank->size == 0)

return -EINVAL;

meminfo.nr_banks++;

return 0;

}

【start_kernel--->setup_arch--->sanity_check_meminfo】

arch/arm/mm/mmu.c

void __init sanity_check_meminfo(void)

{

int i, j, highmem = 0;

遍历每一个内存块

for (i = 0, j = 0; i 

struct membank *bank = &meminfo.bank[j];

*bank = meminfo.bank[i];

if (bank->start > ULONG_MAX)

highmem = 1;

#ifdef CONFIG_HIGHMEM

vmalloc_min在文件arch/arm/mm/mmu.c中定义,它定义了高端内存的起始位置。PAGE_OFFSET是物理位置的起始处。如果内存块起始位置大于vmalloc_min,表示存在高端内存。如果内存扩展超过32位,它就有可能小于PAGE_OFFSET。

if (__va(bank->start) >= vmalloc_min ||

__va(bank->start) 

highmem = 1;

标志该内存块是否处于高端内存中

bank->highmem = highmem;

如果该内存块部分处于高端内存中,部分处于低端内存中就将其分为两个内存块。

if (!highmem && __va(bank->start) 

bank->size > vmalloc_min - __va(bank->start)) {

if (meminfo.nr_banks >= NR_BANKS) {

......

} else {

memmove(bank + 1, bank,

(meminfo.nr_banks - i) * sizeof(*bank));

meminfo.nr_banks++;

i++;

bank[1].size -= vmalloc_min - __va(bank->start);

bank[1].start = __pa(vmalloc_min - 1) + 1;

bank[1].highmem = highmem = 1;

j++;

}

bank->size = vmalloc_min - __va(bank->start);

}

#else 如果不支持高端内存做如下处理

bank->highmem = highmem;

if (highmem) {

......

continue;

}

if (__va(bank->start) >= vmalloc_min ||

__va(bank->start) 

......

continue;

}

if (__va(bank->start + bank->size) > vmalloc_min ||

__va(bank->start + bank->size) start)) {

unsigned long newsize = vmalloc_min - __va(bank->start);

......

bank->size = newsize;

}

#endif

求出低端内存的最大地址值

if (!bank->highmem && bank->start + bank->size > arm_lowmem_limit)

arm_lowmem_limit = bank->start + bank->size;

j++;

}

......

meminfo.nr_banks = j; 记录内存块数

计算高端内存起始地址,该值不一定等于 vmalloc_min,因为可能没有高端内存

high_memory = __va(arm_lowmem_limit - 1) + 1;

memblock_set_current_limit(arm_lowmem_limit);

}

【start_kernel--->setup_arch--->arm_memblock_init】

arch/arm/mm/init.c

void __init arm_memblock_init(struct meminfo *mi, struct machine_desc *mdesc)

{

int i;

将所有内存模块添加到memblock.memory中。结构体memblock在文件mm/memblock.c中定义,如下:

struct memblock memblock __initdata_memblock = {

.memory.regions         = memblock_memory_init_regions,

......

.reserved.regions       = memblock_reserved_init_regions,

......

};

for (i = 0; i nr_banks; i++)

memblock_add(mi->bank[i].start, mi->bank[i].size);

如果内核在rom中运行就只将它的数据段开始的空间添加到memblock.reserved中,否则将内核代码段开始的空间添加到memblock.reserved中。

#ifdef CONFIG_XIP_KERNEL

memblock_reserve(__pa(_sdata), _end - _sdata);

#else

memblock_reserve(__pa(_stext), _end - _stext);

#endif

#ifdef CONFIG_BLK_DEV_INITRD

如果支持initrd启动,此时它还不在内存中

if (phys_initrd_size &&

!memblock_is_region_memory(phys_initrd_start, phys_initrd_size)) {

pr_err("INITRD: 0x%08lx+0x%08lx is not a memory region - disabling initrd\n",

phys_initrd_start, phys_initrd_size);

phys_initrd_start = phys_initrd_size = 0;

}

if (phys_initrd_size &&

memblock_is_region_reserved(phys_initrd_start, phys_initrd_size)) {

pr_err("INITRD: 0x%08lx+0x%08lx overlaps in-use memory region - disabling initrd\n",

phys_initrd_start, phys_initrd_size);

phys_initrd_start = phys_initrd_size = 0;

}

为inird镜像预留一块存储区

if (phys_initrd_size) {

memblock_reserve(phys_initrd_start, phys_initrd_size);

initrd_start = __phys_to_virt(phys_initrd_start);

initrd_end = initrd_start + phys_initrd_size;

}

#endif

为内核页表分配存储空间

arm_mm_memblock_reserve();

......

}

【start_kernel--->setup_arch--->paging_init】

arch/arm/mm/mmu.c

void __init paging_init(struct machine_desc *mdesc)

{

void *zero_page;

memblock_set_current_limit(arm_lowmem_limit);

根据不同的arm版本初始化不同的mem_types,该结构存放着页表的一些属性相关信息

build_mem_type_table();

将除了内核镜像、主内存所在虚拟地址之外全部内存的页表清除掉

prepare_page_table();

为低端内存的所有区域创建内核页表

map_lowmem();

对DMA区域重新创建页表

dma_contiguous_remap();

为设备IO空间和中断向量表创建页表,并刷新TLB和缓存

devicemaps_init(mdesc);

获取持久映射区页表的位置,存储在pkmap_page_table中

kmap_init();

高64K是用于存放中断向量表的

top_pmd = pmd_off_k(0xffff0000);

分配一个0页,该页用于写时复制机制。

zero_page = early_alloc(PAGE_SIZE);

初始化自举内存分配,后面有专门章节讲解

bootmem_init();

empty_zero_page = virt_to_page(zero_page);

刷新数据缓存

__flush_dcache_page(NULL, empty_zero_page);

}

【start_kernel--->setup_arch--->paging_init--->prepare_page_table】

arch/arm/mm/mmu.c

static inline void prepare_page_table(void)

{

unsigned long addr;

phys_addr_t end;

模块加载的范围应该是在MODULES_VADDR到MODULES_END之间,MODULES_VADDR在文件arch/arm/include/asm/memory.h中定义,如下:

#define MODULES_VADDR           (PAGE_OFFSET - 8*1024*1024)

对于arm处理器,该区域在正常内核虚拟地址之下。清除存储空间在MODULES_VADDR之下的页表项。

for (addr = 0; addr 

pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL

addr = ((unsigned long)_etext + PMD_SIZE - 1) & PMD_MASK;

#endif

for ( ; addr 

pmd_clear(pmd_off_k(addr));

第一个存储区存放的是内核镜像,跳过该区域,即不清除这个区域的页表

end = memblock.memory.regions[0].base + memblock.memory.regions[0].size;

if (end >= arm_lowmem_limit)

end = arm_lowmem_limit;

for (addr = __phys_to_virt(end);

addr 

pmd_clear(pmd_off_k(addr));

}

【start_kernel--->setup_per_cpu_areas】

每CPU变量(per- cpu-variable)是一种内核的同步机制。每CPU变量分为静态变量和动态变量。静态变量用DEFINE_PER_CPU(type, name)来定义(CPU变量name,类型为type)。这些静态变量包含在段.data.percpu中。下面函数就是为每个CPU分配一部分空间用于动态分配per_cpu变量。并为每个CPU拷贝一份.data.percup段中的内容。

mm/percpu.c

void __init setup_per_cpu_areas(void)

{

unsigned long delta;

unsigned int cpu;

int rc;

rc = pcpu_embed_first_chunk(PERCPU_MODULE_RESERVE,

PERCPU_DYNAMIC_RESERVE, PAGE_SIZE, NULL,

pcpu_dfl_fc_alloc, pcpu_dfl_fc_free);

if (rc 

panic("Failed to initialize percpu areas.");

数组 __per_cpu_offset中存储了每个CPU,per_cpu变量区域的偏移,在以后访问per_cpu变量时将用到。

delta = (unsigned long)pcpu_base_addr - (unsigned long)__per_cpu_start;

for_each_possible_cpu(cpu)

__per_cpu_offset[cpu] = delta + pcpu_unit_offsets[cpu];

}

【start_kernel--->build_all_zonelists】

mm/page_alloc.c

void __ref build_all_zonelists(void *data)

{

设置current_zonelist_order,它决定备用内存域在pglist_data->node_zonelists中的排列顺序

set_zonelist_order();

if (system_state == SYSTEM_BOOTING) {

初始化备用结点内存域列表 pglist_data->node_zonelists。

__build_all_zonelists(NULL);

mminit_verify_zonelist();打印一些调试信息

当前进程的进程描述结构task_struct中有一个成员mems_allowed,该成员是nodemask_t类型的结构体,这个结构体在文件include/linux/nodemask.h中定义如下:

typedef struct { DECLARE_BITMAP(bits, MAX_NUMNODES); } nodemask_t;

这个结构其实就是定义了一个位域,每个位对应一个内存结点,如果置1表示该节点内存可用。在下面函数中将这个位域中每个位置1。

cpuset_init_current_mems_allowed();

} else {

#ifdef CONFIG_MEMORY_HOTPLUG

if (data)

setup_zone_pageset((struct zone *)data);

#endif

如果内核不是出于启动过程中,就停止CPU的运行来初始化备用结点内存域列表

stop_machine(__build_all_zonelists, NULL, NULL);

}

计算总的空闲内存数

vm_total_pages = nr_free_pagecache_pages();

内核通过标记页的可移动类型来避免产生过多碎片,如果可用内存太少就标记page_group_by_mobility_disabled以禁用这种反碎片机制。

if (vm_total_pages 

page_group_by_mobility_disabled = 1;

else

page_group_by_mobility_disabled = 0;

......

}

【start_kernel--->build_all_zonelists--->__build_all_zonelists】

static __init_refok int __build_all_zonelists(void *data)

{

int nid;

int cpu;

......

遍历每一个内存结点,初始化他们的备用结点内存域列表

for_each_online_node(nid) {

pg_data_t *pgdat = NODE_DATA(nid);

build_zonelists(pgdat);

build_zonelist_cache(pgdat);

}

遍历每一个CPU,初始化他们的per_cpu缓存

for_each_possible_cpu(cpu) {

setup_pageset(&per_cpu(boot_pageset, cpu), 0);

......

}

return 0;

}

【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists

struct zonelist是备用结点内存域列表管理结构,该结构在文件include/linux/mmzone.h中定义,如下:

struct zonelist {

指针zlcache_ptr通常指向本结构中的zlcache成员

struct zonelist_cache *zlcache_ptr;

MAX_ZONES_PER_ZONELIST表示所有节点内存域总和

struct zoneref _zonerefs[MAX_ZONES_PER_ZONELIST + 1];

#ifdef CONFIG_NUMA

struct zonelist_cache zlcache;

#endif

};

struct zoneref {

struct zone *zone;   指向内存域管理结构

int zone_idx;    内存域所在结点id

};

struct zonelist_cache {

存储所有内存域对应结点号

unsigned short z_to_n[MAX_ZONES_PER_ZONELIST];

fullzones是所有内存域的一个位图,如果内存域对应位置1,表示这个内存域已没有可用内存

DECLARE_BITMAP(fullzones, MAX_ZONES_PER_ZONELIST);

unsigned long last_full_zap;

};

备用结点内存域列表中内存域排列原则是:按分配代价由小到大排列。在节点间应当先排列本地结点的内存域后排其他结点内存域,在节点内先排列高端内存、然后是普通内存、再然后是DMA内存。

在结点描述结构中,节点的备用结点内存域列表定义如下:

typedef struct pglist_data {

......

struct zonelist node_zonelists[MAX_ZONELISTS];

......

}

在多处理器系统中MAX_ZONELISTS定义为2,node_zonelists[0]中排列本结点内存域,node_zonelists[1]中排列其他备用结点内存域。如果在找node_zonelists[0]中不到可用内存就到node_zonelists[1]中去分配。

mm/page_alloc.c

static void build_zonelists(pg_data_t *pgdat)

{

int j, node, load;

enum zone_type i;

nodemask_t used_mask;

int local_node, prev_node;

struct zonelist *zonelist;

int order = current_zonelist_order;

初始化备用结点内存域

for (i = 0; i 

zonelist = pgdat->node_zonelists + i;

zonelist->_zonerefs[0].zone = NULL;

zonelist->_zonerefs[0].zone_idx = 0;

}

local_node = pgdat->node_id;

load = nr_online_nodes;

prev_node = local_node;

nodes_clear(used_mask);

memset(node_order, 0, sizeof(node_order));

j = 0;

找一个与结点pgdat距离最近的结点

while ((node = find_next_best_node(local_node, &used_mask)) >= 0) {

int distance = node_distance(local_node, node);

if (distance > RECLAIM_DISTANCE)

zone_reclaim_mode = 1;

if (distance != node_distance(local_node, prev_node))

node_load[node] = load;

prev_node = node;

load--;

if (order == ZONELIST_ORDER_NODE)

将找到的最佳结点内存域排列到pgdat的备用内存域列表node_zonelists[1]中

build_zonelists_in_node_order(pgdat, node);

else

node_order[j++] = node; /* remember order */

}

if (order == ZONELIST_ORDER_ZONE) {

/* calculate node order -- i.e., DMA last! */

build_zonelists_in_zone_order(pgdat, j);

}

将结点自己的内存域排列到自己的备用结点内存域node_zonelists[0]中

build_thisnode_zonelists(pgdat);

}

【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists

--->build_zonelists_in_node_order】

mm/page_alloc.c

static void build_zonelists_in_node_order(pg_data_t *pgdat, int node)

{

int j;

struct zonelist *zonelist;

函数build_thisnode_zonelists和本函数最大的区别就在于这里取的是node_zonelists[0],而在函数build_thisnode_zonelists中取的是node_zonelists[1]。

zonelist = &pgdat->node_zonelists[0];

找到第一个空的位置

for (j = 0; zonelist->_zonerefs[j].zone != NULL; j++)

;

将结点node的所有内存区域排列在,j开始的备用列表中

j = build_zonelists_node(NODE_DATA(node), zonelist, j,

MAX_NR_ZONES - 1);

zonelist->_zonerefs[j].zone = NULL;

zonelist->_zonerefs[j].zone_idx = 0;

}

【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelists

--->build_zonelists_in_node_order--->build_zonelists_node】

mm/page_alloc.c

static int build_zonelists_node(pg_data_t *pgdat, struct zonelist *zonelist,

int nr_zones, enum zone_type zone_type)

{

struct zone *zone;

BUG_ON(zone_type >= MAX_NR_ZONES);

zone_type++;

do {

这里的zone_type,上层函数传入的是MAX_NR_ZONES ,循环中做的就是将内存域按ZONE_HIGHMEM--->ZONE_NORMAL--->ZONE_DMA的顺序排列在备用内存列表中

zone_type--;

zone = pgdat->node_zones + zone_type;

if (populated_zone(zone)) {

函数zoneref_set_zone要做的就是用zone和zone所在结点id来初始化_zonerefs。

zoneref_set_zone(zone,

&zonelist->_zonerefs[nr_zones++]);

找出整个备用列表中内存区域类型值最大的的内存区域

check_highest_zone(zone_type);

}

} while (zone_type);

return nr_zones;

}

【start_kernel--->build_all_zonelists--->__build_all_zonelists--->build_zonelist_cache】

mm/page_alloc.c

static void build_zonelist_cache(pg_data_t *pgdat)

{

struct zonelist *zonelist;

struct zonelist_cache *zlc;

struct zoneref *z;

zonelist = &pgdat->node_zonelists[0];

让结构struct zonelist的zlcache_ptr指针成员指向它自己的zlcache成员

zonelist->zlcache_ptr = zlc = &zonelist->zlcache;

bitmap_zero(zlc->fullzones, MAX_ZONES_PER_ZONELIST);

for (z = zonelist->_zonerefs; z->zone; z++)

zlc->z_to_n[z - zonelist->_zonerefs] = zonelist_node_idx(z);

}

【start_kernel--->mm_init】

init/main.c

static void __init mm_init(void)

{

为page_cgroup相关结构分配存储空间

page_cgroup_init_flatmem();

将自举分配器bootmem中的空闲空间释放到伙伴系统中,并停用自举分配器切换到伙伴系统中

mem_init();

启用slab分配器,后面另起章节专门讲解

kmem_cache_init();

一些per_cpu结构在系统启动期间做了临时初始化,这里将进行一些更完善的处理

percpu_init_late();

pgtable_cache_init();为空

初始化非连续内存分配。关于非线性内存区域的分配,后面专门有章节来讲解

vmalloc_init();

}

【start_kernel--->mm_init--->mem_init】

arch/arm/mm/init.c

void __init mem_init(void)

{

unsigned long reserved_pages, free_pages;

struct memblock_region *reg;

int i;

......

系统中可能有多个存储块,每个存储块之间可能有没用的区域,将这些 区域在bootmem中标记为空闲

free_unused_memmap(&meminfo);

将bootmem中空闲区域释放到伙伴系统中去

totalram_pages += free_all_bootmem();

#ifdef CONFIG_SA1111

PHYS_PFN_OFFSET是物理存储位置对应的页帧号。将PHYS_PFN_OFFSET到内核页表swapper_pg_dir之间的区域释放到伙伴系统中去

totalram_pages += free_area(PHYS_PFN_OFFSET,

__phys_to_pfn(__pa(swapper_pg_dir)), NULL);

#endif

将空闲的高端内存区域释放到伙伴系统中去

free_highpages();

reserved_pages = free_pages = 0;

统计所有内存块中的空闲页和非空闲页。

for_each_bank(i, &meminfo) {

struct membank *bank = &meminfo.bank[i];

unsigned int pfn1, pfn2;

struct page *page, *end;

pfn1 = bank_pfn_start(bank);

pfn2 = bank_pfn_end(bank);

page = pfn_to_page(pfn1);

end  = pfn_to_page(pfn2 - 1) + 1;

do {

if (PageReserved(page))

reserved_pages++;

else if (!page_count(page))

free_pages++;

page++;

} while (page 

}

......

}

【start_kernel--->

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值