初始化内核页表

   内核维持着一组自己使用的页表,驻留在主内核全局目录中,主内核页全局目录的最高目录项部分作为参考模型,为系统中每个普通进程对应的页全局目录项提供参考模型(进程的内核态从oxc0000000-----oxffffffff的线性地址!)

   内核页表的初始化化分为2个阶段:

   第一阶段:在还未启动分页机制下初始化化一个寻址范围在0---8M的内核页表,这个最小限度的地址空间仅能内核装载到RAM和对其初始化核心数据结构。该部分是由startup_32()汇编语言函数实现的(arch/i386/kernel/head.s)以下是startup_32中初始化内核页表的代码片段及其解析:

              page_pde_offset = (__PAGE_OFFSET >> 20);

              movl $(pg0 - __PAGE_OFFSET), %edi
              movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
              movl $0x007, %eax   /* 0x007 = PRESENT+RW+USER */
            10:  /*初始化页目录表*/
               leal 0x007(%edi),%ecx   /* Create PDE entry */
               movl %ecx,(%edx)   /* Store identity PDE entry */
               movl %ecx,page_pde_offset(%edx)  /* Store kernel PDE entry */
               addl $4,%edx
               movl $1024, %ecx   /*设置循环次数*/
           11:              /*初始化页表*/
               stosl
               addl $0x1000,%eax
               loop 11b
              /* End condition: we must map up to and including INIT_MAP_BEYOND_END */
              /* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
               leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
               cmpl %ebp,%eax
               jb 10b
               movl %edi,(init_pg_tables_end - __PAGE_OFFSET) /* 此时的edi中存放pg0+0x2000,将此值存入init_pg_tables_end中,表示页表初始化结束 */

 

            .......................................................

 

                   /*
                     * Enable paging              启用分页机制
                   */
                  movl $swapper_pg_dir-__PAGE_OFFSET,%eax
                  movl %eax,%cr3  /* set the page table pointer.. */
                  movl %cr0,%eax
                  orl $0x80000000,%eax
                  movl %eax,%cr0  /* ..and set paging (PG) bit */

    注意代码中的子针变量都减去一个线性偏移_PAGE_OFFSET。这是因为在编译内核时。这些变量引用的是启用分页后线性空间的地址。所以为得到实际的物理地址应减去这个线性偏移量。

 

第二阶段:初始化最终内核页表,注意此时分页机制已启动。初始化使得从oxc0000000开始的线性地址转化为从0开始的物理地址。

初始化发生在paging_init()函数中,而该函数位于setup_arch()中,而setup_arch()在start_kernel函数中被调用。

   paging_init()函数执行步骤:

   1:调用pagetable_init建立内核页表;

   2:把swapper_pg_dir的物理地址写道cr3寄存器中;

   3:根据CPU能力及编译内核时的配置,正确的设置cr4的PAE标志位;

   4:调用__flush_tlb_all使得TLB(转换后援缓冲器)的所用项无效。

重点来看pagetable_init函数:

    该函数首先获得页全局目录的线性地址,然后将其传入kernel_physical_mapping_init函数,该函数最终建立线性地址到物理地址的线性映射关系。kernel_physical_mapping_init代码及解析如下:

    static void __init kernel_physical_mapping_init(pgd_t *pgd_base)
{
 unsigned long pfn;
 pgd_t *pgd;
 pmd_t *pmd;
 pte_t *pte;
 int pgd_idx, pmd_idx, pte_ofs;

 pgd_idx = pgd_index(PAGE_OFFSET);
 pgd = pgd_base + pgd_idx;
 pfn = 0;

 for (; pgd_idx < PTRS_PER_PGD; pgd++, pgd_idx++) {
  pmd = one_md_table_init(pgd);
  if (pfn >= max_low_pfn)
   continue;
  for (pmd_idx = 0; pmd_idx < PTRS_PER_PMD && pfn < max_low_pfn; pmd++, pmd_idx++) {
   unsigned int address = pfn * PAGE_SIZE + PAGE_OFFSET;

   /* Map with big pages if possible, otherwise create normal page tables. */
   if (cpu_has_pse) {
    unsigned int address2 = (pfn + PTRS_PER_PTE - 1) * PAGE_SIZE + PAGE_OFFSET + PAGE_SIZE-1;

    if (is_kernel_text(address) || is_kernel_text(address2))
     set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE_EXEC));
    else
     set_pmd(pmd, pfn_pmd(pfn, PAGE_KERNEL_LARGE));
    pfn += PTRS_PER_PTE;
   } else {
    pte = one_page_table_init(pmd);

    for (pte_ofs = 0; pte_ofs < PTRS_PER_PTE && pfn < max_low_pfn; pte++, pfn++, pte_ofs++) {
      if (is_kernel_text(address))
       set_pte(pte, pfn_pte(pfn, PAGE_KERNEL_EXEC));
      else
       set_pte(pte, pfn_pte(pfn, PAGE_KERNEL));
    }
   }
  }
 }
}

调用结束后还要初始化固定地址映射的线性地址页表代码如下:

              vaddr = __fix_to_virt(__end_of_fixed_addresses - 1) & PMD_MASK;    //将固定地址映射页表放在页全局目录的1023项(最后一项)
              page_table_range_init(vaddr, 0, pgd_base);  

最后为了初始化高地址内存而调用permanent_kmaps_init方法。

 

 

 

 

 

 

 

 

 

  

 

  

  

 

Linux内核初始化过程是一个复杂且关键的任务,它确保了系统能够正确地启动并进入可用状态。以下是对Linux内核初始化步骤的概述: ### 内核入口点和早期初始化内核被加载到内存后,控制权会传递给内核的第一个执行点,通常是`startup_32`或`startup_64`函数,这取决于目标架构。在这个阶段,内核开始进行基本的硬件设置和页表初始化。例如,在x86架构上,`startup_32`负责初始化一个最小限度的内核页表,以支持从0到8MB的地址空间访问,这对于内核解压和进一步初始化是必要的[^3]。 ### 解压内核 对于压缩的内核映像(如zImage或bzImage),需要先通过`extract_kernel`函数将其解压出真正的Linux内核文件vmlinux。这个过程通常在实模式下完成,并且一旦解压完成,控制权就会转移到解压后的内核入口点。 ### 启动主函数start_kernel 一旦基本的硬件环境准备好,内核将跳转到`start_kernel`函数,这是内核初始化的核心部分。在此期间,内核会继续进行更详细的硬件初始化,包括中断控制器、时钟、定时器、内存管理子系统等。同时也会初始化进程调度器、创建初始进程(通常是init进程)以及挂载根文件系统。 ### 架构特定初始化 不同的处理器架构可能有其特定的初始化需求。比如,在`start_kernel`调用之前,某些架构相关的初始化工作必须完成,包括设置CPU特性、缓存配置等。这些初始化步骤确保了内核可以在特定的硬件平台上正确运行[^2]。 ### 设备驱动和子系统初始化 随着核心内核组件的初始化,设备驱动程序和其他子系统(如网络堆栈、虚拟文件系统)也开始初始化。例如,GPIO相关的初始化工作涉及到结构体`struct gpio_desc`的定义和使用,它包含了关于每个GPIO引脚的信息,如所属的芯片、标志位以及调试信息等[^4]。 ### 用户空间启动 最后,当所有必要的内核和服务都已启动,内核会尝试执行第一个用户空间程序,通常是/sbin/init,从而正式将控制权交给用户空间,标志着Linux系统的启动完成。 ```c // 示例代码片段 - 简化版的start_kernel伪代码 asmlinkage void __init start_kernel(void) { char *command_line; extern const struct kernel_param __start___param[], __stop___param[]; /* * Need to disable interrupts early to avoid race conditions with * local interrupt control. */ local_irq_disable(); early_irq_init(); init_IRQ(); tick_init(); rcu_init(); init_timers(); hrtimers_init(); softirq_init(); timekeeping_init(); time_init(); sched_init(); ... } ``` 上述代码展示的是`start_kernel`函数的一部分,它展示了如何禁用本地中断并调用一系列初始化函数来配置各种内核子系统。
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值