Linux内核页表初始化

开发者福利!热门AI工具限时免费用 购周边即赠Coding Plan Lite,Claude Code、Cursor等20+工具畅享,效率翻倍! 阅读详情

Linux在内核启动过程中start_kernel->setup_arch会调用如下两个函数对页表进行初始化和建立。

static inline void prepare_page_table(void)
{
    unsigned long addr;

    /*
     * Clear out all the mappings below the kernel image.
     */
    //初始化0~MODULES_VADDR(0xBF000000)地址空间的页表,0xBF000000~0xC0000000的16M空间用来存放kernel module library
    //关于PGDIR_SIZE=2M和pmd_clear下面单独叙述
    for (addr = 0; addr < MODULES_VADDR; addr += PGDIR_SIZE)
        pmd_clear(pmd_off_k(addr));

#ifdef CONFIG_XIP_KERNEL
    //XIP内核(即kernel image存放在Nor flash等可偏上执行的存储体中)存放在kernel module area,不能影响kernel image所在的地址空间的页表,etext为kernel代码段结束地址,加一页,应该是为kernel数据段保留足够的空间
    /* The XIP kernel is mapped in the module area -- skip over it */
    addr = ((unsigned long)_etext + PGDIR_SIZE - 1) & PGDIR_MASK;
#endif
    for ( ; addr < PAGE_OFFSET; addr += PGDIR_SIZE)//否则kernel module在使用时才动态加载,现在需要初始化该区域的页表
        pmd_clear(pmd_off_k(addr));

    /*
     * Clear out all the kernel space mappings, except for the first
     * memory bank, up to the end of the vmalloc region.
     */
    //由于第一个Bank存储着kernel image,这段空间的页表在head.S已经建立完成,以后无论什么动作都不能影响这段页表
    for (addr = __phys_to_virt(bank_phys_end(&meminfo.bank[0]));
         addr < VMALLOC_END; addr += PGDIR_SIZE)
        pmd_clear(pmd_off_k(addr));
}

由于ARM采用两级映射,pmd不占用字段,pmd=pgd,而这里一次清了两个pgd,和pgd的定义正好对应,所以在for循环时每次步长为PGDIR_SIZE(2M),根本的原因是ARM linux使用了ARM的两个段,在用户态为__USER_CS__USER_DS,在内核态为__KERNEL_CS和__KERNEL_DS

初始化页表里也会flush对应的TLB。

typedef unsigned long pgd_t[2];

#define pmd_clear(pmdp)			\
	do {				\
		pmdp[0] = __pmd(0);	\
		pmdp[1] = __pmd(0);	\
		clean_pmd_entry(pmdp);	\
	} while (0)

static inline pmd_t *pmd_off_k(unsigned long virt)
{
	return pmd_off(pgd_offset_k(virt), virt);
}

#define pgd_offset_k(addr)	pgd_offset(&init_mm, addr)

/* to find an entry in a page-table-directory */
#define pgd_index(addr)		((addr) >> PGDIR_SHIFT)

#define pgd_offset(mm, addr)	((mm)->pgd+pgd_index(addr))

/* to find an entry in a kernel page-table-directory */
#define pgd_offset_k(addr)	pgd_offset(&init_mm, addr)

/* Find an entry in the second-level page table.. */
#define pmd_offset(dir, addr)	((pmd_t *)(dir))

/* Find an entry in the third-level page table.. */
#define __pte_index(addr)	(((addr) >> PAGE_SHIFT) & (PTRS_PER_PTE - 1))

map_lowmem函数会为地段物理内存的每个bank逐一调用map_memory_bank做页表映射,再调用create_mapping建立页表

/*
 * Create the page directory entries and any necessary
 * page tables for the mapping specified by `md'.  We
 * are able to cope here with varying sizes and address
 * offsets, and we take full advantage of sections and
 * supersections.
 */
static void __init create_mapping(struct map_desc *md)
{
	unsigned long phys, addr, length, end;
	const struct mem_type *type;
	pgd_t *pgd;
        //在内核为个bank建立页表时,它虚拟地址不可能落在user空间
	if (md->virtual != vectors_base() && md->virtual < TASK_SIZE) {
		printk(KERN_WARNING "BUG: not creating mapping for "
		       "0x%08llx at 0x%08lx in user region\n",
		       __pfn_to_phys((u64)md->pfn), md->virtual);
		return;
	}
//高端内存通过alloc_page+kmap的形式映射,在初始化阶段不可能有bank的虚拟地址落在VMALLOC线性地址空间
	if ((md->type == MT_DEVICE || md->type == MT_ROM) &&
	    md->virtual >= PAGE_OFFSET && md->virtual < VMALLOC_END) {
		printk(KERN_WARNING "BUG: mapping for 0x%08llx at 0x%08lx "
		       "overlaps vmalloc space\n",
		       __pfn_to_phys((u64)md->pfn), md->virtual);
	}

	type = &mem_types[md->type];
//如果有大于4G的物理内存,单独建立
	/*
	 * Catch 36-bit addresses
	 */
	if (md->pfn >= 0x100000) {
		create_36bit_mapping(md, type);
		return;
	}

	addr = md->virtual & PAGE_MASK;
	phys = (unsigned long)__pfn_to_phys(md->pfn);
	length = PAGE_ALIGN(md->length + (md->virtual & ~PAGE_MASK));//将bank整页对齐

	if (type->prot_l1 == 0 && ((addr | phys | length) & ~SECTION_MASK)) {//不能section(1M)对齐的
		printk(KERN_WARNING "BUG: map for 0x%08lx at 0x%08lx can not "
		       "be mapped using pages, ignoring.\n",
		       __pfn_to_phys(md->pfn), addr);
		return;
	}

	pgd = pgd_offset_k(addr);
	end = addr + length;
	do {
		unsigned long next = pgd_addr_end(addr, end);

		alloc_init_section(pgd, addr, next, phys, type);//真正建立页表的函数

		phys += next - addr;
		addr = next;
	} while (pgd++, addr != end);
}

static void __init alloc_init_section(pgd_t *pgd, unsigned long addr,
				      unsigned long end, unsigned long phys,
				      const struct mem_type *type)
{
	pmd_t *pmd = pmd_offset(pgd, addr);

	/*
	 * Try a section mapping - end, addr and phys must all be aligned
	 * to a section boundary.  Note that PMDs refer to the individual
	 * L1 entries, whereas PGDs refer to a group of L1 entries making
	 * up one logical pointer to an L2 table.
	 */
	if (((addr | end | phys) & ~SECTION_MASK) == 0) {//完整的一个section(1M)直接生成段页表
		pmd_t *p = pmd;

		if (addr & SECTION_SIZE)
			pmd++;

		do {
			*pmd = __pmd(phys | type->prot_sect);//填充段页表地址和section相关属性
			phys += SECTION_SIZE;
		} while (pmd++, addr += SECTION_SIZE, addr != end);

		flush_pmd_entry(p);
	} else {//不是段对齐的,要分配二级页表
		/*
		 * No need to loop; pte's aren't interested in the
		 * individual L1 entries.
		 */
		alloc_init_pte(pmd, addr, end, __phys_to_pfn(phys), type);
	}
}

static void __init alloc_init_pte(pmd_t *pmd, unsigned long addr,
                  unsigned long end, unsigned long pfn,
                  const struct mem_type *type)
{
    pte_t *pte;

    if (pmd_none(*pmd)) {//check pmd指向的L1页表中的页表项是否存在,不存在的话使用Bootmem分配所需的二级页表空间
        pte = alloc_bootmem_low_pages(2 * PTRS_PER_PTE * sizeof(pte_t));
        __pmd_populate(pmd, __pa(pte) | type->prot_l1);//填充二级页表,即pte的物理地址和prot_l1
    }

    pte = pte_offset_kernel(pmd, addr);//建立pte
    do {
        set_pte_ext(pte, pfn_pte(pfn, __pgprot(type->prot_pte)), 0);//set_pte_ext与平台相关,它完成硬件页表和内核页表两者的创建
        pfn++;
    } while (pte++, addr += PAGE_SIZE, addr != end);}






















        
内核页表初始化 内核内存初始化 暂时用 2.6.11 x86 平台的内核来做讲解。 从 bois 中获取各种配置信息,我也不懂,所以仅介绍结果 该篇文章已假设你了解了内核的内存总体的分布,如果没有请参考我的这篇文章 物理内存分布 首先可用的物理内存(地址)不是连续分布的,有很多空洞。因为这些空洞要么不可用,要么被其他外设占用,所以在内核初始化时,必须从bois中获取可用于物理内存。另外由于内存只识别 4K... 阅读详情

相关推荐

linux 内核 内存管理 初始化 页表

linux内核在启动分机制时就已经有了一个可用的页表,这个最初的页表是手工创建了,而且仅有为数不多的几个面,进入start_kernel()以后需要把原来的页表完善一下,具体工作在start_kernel() --> setup_arch() --> paging_init()函数中。paing_init()函数定义在arch/x86/mm/init_32.c中。 void __init p

卢坤的专栏 4109

Linux ko内存

insmod插入ko时,提升申请内存失败。这是因为MODULES的空间分配不够大。Linux有一个专门的内核模块映射空间,是专门给ko插入时用的。地址范围是MODULES_VADDR~MODULES_END-1 然而MODULES_VADDR是可以配置的,该宏定位的位置为内核代码下的arch/arm/include/asm/memory.h,不同内核路径可能不一样。MODULES_VADDR定义的是: #define MODULES_VADDR (PAGE_OFFSET - SZ_16...

rfasm的专栏 1136

LAMMPS ReaxFF模拟后处理:如何用Python精准追踪C-N键的断裂与形成?

本文详细介绍了如何利用Python对LAMMPS ReaxFF模拟结果进行后处理,精准追踪C-N键的断裂与形成过程。通过解析键级输出文件、构建原子类型映射字典,并实现C-N键的统计与可视化,为材料科学和催化反应研究提供了高效的数据分析解决方案。文章特别强调了ReaxFF力场在断键分析中的关键作用,并提供了完整的Python代码实现。

weixin_28706923的博客 327

linux内存源码分析 - 页表初始化

这些多个主内核全局目录被称为。我们知道,内核可使用的线性地址就只有1G大小(0xC0000000 ~ 0xFFFFFFFF),而用于ZONE_DMA和ZONE_NORMAL这两个区的映射已经花掉了896MB的线性地址空间,最后只剩下128MB用于映射高端内存,如果内存大于1G,比如2G的情况下,高端内存区大小就为1152MB,这个128MB大小的线性地址空间是完全不够直接映射高端内存的,所以对于高端内存的处理,linux并不会直接映射,而是在需要的时候才进行映射,不需要的时候就释放映射,回收线性地址。

m0_74282605的博客 392

Linux 内核页表管理

Linux x86_64系统下的内核页表管理

小立仔 4711

linux内核页表初始化,初始化内核页表

内核维持着一组自己使用的页表,驻留在主内核全局目录中,主内核全局目录的最高目录项部分作为参考模型,为系统中每个普通进程对应的全局目录项提供参考模型(进程的内核态从oxc0000000-----oxffffffff的线性地址!)内核页表初始化化分为2个阶段:第一阶段:在还未启动分机制下初始化化一个寻址范围在0---8M的内核页表,这个最小限度的地址空间仅能内核装载到RAM和对其初始化核心数据...

weixin_28966407的博客 940

linux内核页表0x300000,Linux内核页表初始化

Linux内核启动过程中start_kernel->setup_arch会调用如下两个函数对页表进行初始化和建立。static inline void prepare_page_table(void){unsigned long addr;/** Clear out all the mappings below the kernel image.*///初始化0~MODULES_VADDR...

weixin_39589241的博客 243

linux内核启动设置页表,Linux内核页表初始化

Linux内核启动过程中start_kernel->setup_arch会调用如下两个函数对页表进行初始化和建立。static inline void prepare_page_table(void){unsigned long addr;/** Clear out all the mappings below the kernel image.*///初始化0~MODULES_VADDR...

weixin_30236323的博客 394

Linux 内核页表初始化

前面我们讨论了描述符对象分配,以及初始化。接下来我们再来了解内核页表建立初始化处理。针对ARM64进行。 start_kernel --> setup_arch --> paging_init() void __init paging_init(void) {  phys_addr_t pgd_phys = early_pgtable_alloc(); 分配全局目录表地址

星空探索 1871

Linux ARM64 内核页表初始化 paging_init

本文基于Linux 6.16内核,分析了ARM64架构下内存映射初始化过程。重点研究了paging_init()函数如何通过map_mem()建立内核虚拟地址空间映射。

小立仔 692

linux内核初始化-19,Linux内核代码之初始化内核临时页表---经典至极(FutureChen的日记)...

来源:http://www.douban.com/note/57007577/Linux内核代码之初始化内核临时页表---经典至极 FutureChen 2010-01-15 18:51:04漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到世上可用...

weixin_33602696的博客 151

Linux内核代码之初始化内核临时页表

漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到世上可用的内存资源远远不止1MB。此时linux的欲望开始膨胀,最为满足它欲望的第一步,它开始抢占内存资源的前8MB。   初始化临时内核页表是在startup_32汇编语言函数中完成的。在ULK所述

beswkwangbo的专栏 639

Linux内核架构浅谈48-Linux内存初始化页表与内存域数据结构的建立流程

摘要:Linux内核内存初始化过程是整个系统启动的关键环节,主要包括页表建立和内存域数据结构初始化两大核心任务。页表初始化遵循四级模型(PGD/PUD/PMD/PTE),通过硬件MMU实现虚拟地址到物理地址的映射;内存域则按用途划分为ZONE_DMA、ZONE_NORMAL和ZONE_HIGHMEM,构成"结点-内存域-帧"的三级管理体系。初始化完成后,系统会释放自举内存分配器,激活正式的伙伴系统和Slab分配器。该过程为后续内存管理提供了基础框架,理解其实现原理对排查内存问题和优化系

文石_2009的博客 799

Linux内核代码之初始化内核临时页表---经典至极(转)

<br /> <br /><br />漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到世上可用的内存资源远远不止1MB。此时linux的欲望开始膨胀,最为满足它欲望的第一步,它开始抢占内存资源的前8MB。 初始化临时内核页表是在startup_32汇编语言函数中完成的。在ULK所述中,假设内核能容纳于RAM的前8MB空间,然后对RAM的前8MB进行恒等映

sploving的专栏 1291

Linux内核代码之初始化内核临时页表---经典至极

http://www.douban.com/note/57007577/ Linux内核代码之初始化内核临时页表---经典至极 2010-01-15 18:51:04 漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到

kendyhj9999的专栏 1090

进程页表内核页表页表初始化

摘要:linux刚刚加电启动时,如何从实模式进入保护模式?启动分机制的前提是什么?如何保证分机制之前和之后通过实地址和虚拟地址都能访问到同一个物理地址呢?内核页表是如何进行初始化的?用户进程不能访问内核的数据是在初始化的哪个阶段决定的?这些内容,都牵扯到linu的进程页表内核页表,以及内核页表初始化。本文也主要为你解答这些疑问. 本文来源:进程页表内核页表页表初始化 1....

473687880 1297

Linux内核代码之初始化内核临时页表---经典至极(FutureChen的日记)

来源:http://www.douban.com/note/57007577/ Linux内核代码之初始化内核临时页表---经典至极  FutureChen 2010-01-15 18:51:04 漫长而黑暗的史前时代终于到了setup。在setup汇编函数中,linux通过设置cr0寄存器的PE位(从实模式切换到保护模式)完成了史前文明到现代文明的转变。在setup时期,linux已经了解到

LBO4031的专栏 1286
上一篇: Linux内存分配alloc_page和__get_free_page详注(伙伴管理系统Buddy)
下一篇: Linux scheduler(cfs) introduction
glmwu
博客等级 码龄23年 7粉丝 14原创
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值