内核第一阶段初始化

这篇博客详细介绍了Linux内核初始化的第一阶段,包括段描述符表的设置、内存映射、页目录和页表的建立,以及CPU特性的检测。通过分析代码,阐述了如何确保内核代码和启动页表所在的内存被正确映射,并设置了分页机制。最后,讨论了如何根据CPU类型调整特性并进入下一阶段的初始化。

初始化阶段1包括从内核入口1MB处的startup_32到公共的start_kernel函数

=============================== arch/i386/kernel/head.S =================================
 50 /*
 51  * 32-bit kernel entrypoint; only used by the boot CPU.  On entry,
 52  * %esi points to the real-mode code as a 32-bit pointer.
 53  * CS and DS must be 4 GB flat segments, but we don't depend on
 54  * any particular GDT layout, because we load our own as soon as we
 55  * can.
 56  */
 57 ENTRY(startup_32)
 58 
 59 /*
 60  * Set segments to known values.
 61  */
 62     cld
 63     lgdt boot_gdt_descr - __PAGE_OFFSET
 64     movl $(__BOOT_DS),%eax
 65     movl %eax,%ds
 66     movl %eax,%es
 67     movl %eax,%fs
 68     movl %eax,%gs

在内核的链接脚本中指定了startup_32是程序入口而且代码段从__PAGE_OFFSET + 0x100000处开始,所以代码中的符号地址都是高地址(3GB+1MB以上),而内核代码运行时被加载到低地址1MB,此时是实模式,代码中访问的地址要用物理地址。所以lgdt要在符号地址boot_gdt_descr上减去__PAGE_OFFSET

=============================== arch/i386/kernel/head.S =================================
450 boot_gdt_descr:
451     .word __BOOT_DS+7
452     .long boot_gdt_table - __PAGE_OFFSET

boot_gdt_descr处是要存放到gdtr的值,第一个.word表示gdt的长度-1,接下来的.long表示gdt的起始地址。

================================ include/asm/segment.h ==================================
 83 /* Simple and small GDT entries for booting only */
 84 
 85 #define GDT_ENTRY_BOOT_CS       2                                                                                                                                       
 86 #define __BOOT_CS   (GDT_ENTRY_BOOT_CS * 8)
 87 
 88 #define GDT_ENTRY_BOOT_DS       (GDT_ENTRY_BOOT_CS + 1)
 89 #define __BOOT_DS   (GDT_ENTRY_BOOT_DS * 8)

CS段描述符在第2条,DS在第3条,__BOOT_DS的值为24,所以bootgdt的大小就是24+7。

=============================== arch/i386/kernel/head.S =================================
471     .align L1_CACHE_BYTES
472 ENTRY(boot_gdt_table)
473     .fill GDT_ENTRY_BOOT_CS,8,0
474     .quad 0x00cf9a000000ffff    /* kernel 4GB code at 0x00000000 */
475     .quad 0x00cf92000000ffff    /* kernel 4GB data at 0x00000000 */

段描述符表的内容。.fill标记的意思是产生GDT_ENTRY_BOOT_CS条(2条)长度为8内容为0的条目。boot gdt总共4条,CSDS都是4G范围的平坦映射。

接着回到startup_32,把__BOOT_DS段选择子赋给dsesfsgs(没有ss),它们会刷新自己的影子寄存器。

=============================== arch/i386/kernel/head.S =================================
 70 /*
 71  * Clear BSS first so that there are no surprises...
 72  * No need to cld as DF is already clear from cld above...
 73  */
 74     xorl %eax,%eax
 75     movl $__bss_start - __PAGE_OFFSET,%edi
 76     movl $__bss_stop - __PAGE_OFFSET,%ecx
 77     subl %edi,%ecx
 78     shrl $2,%ecx
 79     rep ; stosl

清除内存上.bss段所指的范围。bss段是未初始化的全局变量和初始值为0的全局变量,它们在elf文件中只需要存放相应的符号名称,不存放那些0(初始值不为0的变量当然需要在二进制文件中存放它的初始值)。在加载的时候要分配空间给这些变量,并初始化为0

=============================== arch/i386/kernel/head.S =================================
 81 /*
 82  * Initialize page tables.  This creates a PDE and a set of page
 83  * tables, which are located immediately beyond _end.  The variable
 84  * init_pg_tables_end is set up to point to the first "safe" location.
 85  * Mappings are created both at virtual address 0 (identity mapping)
 86  * and PAGE_OFFSET for up to _end+sizeof(page tables)+INIT_MAP_BEYOND_END.
 87  *
 88  * Warning: don't use %esi or the stack in this code.  However, %esp
 89  * can be used as a GPR if you really need it...
 90  */
 91 page_pde_offset = (__PAGE_OFFSET >> 20);
 92 
 93     movl $(pg0 - __PAGE_OFFSET), %edi
 94     movl $(swapper_pg_dir - __PAGE_OFFSET), %edx
 95     movl $0x007, %eax           /* 0x007 = PRESENT+RW+USER */
 96 10:
 97     leal 0x007(%edi),%ecx           /* Create PDE entry */
 98     movl %ecx,(%edx)            /* Store identity PDE entry */
 99     movl %ecx,page_pde_offset(%edx)     /* Store kernel PDE entry */
100     addl $4,%edx
101     movl $1024, %ecx
102 11:
103     stosl
104     addl $0x1000,%eax
105     loop 11b
106     /* End condition: we must map up to and including INIT_MAP_BEYOND_END */
107     /* bytes beyond the end of our own page tables; the +0x007 is the attribute bits */
108     leal (INIT_MAP_BEYOND_END+0x007)(%edi),%ebp
109     cmpl %ebp,%eax
110     jb 10b
111     movl %edi,(init_pg_tables_end - __PAGE_OFFSET)

va右移20位就是该地址在页目录表中表项偏移,page_pde_offset就是内核代码高地址空间的起始页目录项偏移。
pg0是在下面的链接脚本中定义,是bss段之后的第一个页地址,链接脚本中的变量用的也是虚拟地址(高地址)。93行把pg0的物理地址存放到edipg0标志着内核代码所占内存的界限,同时它也是第0个页表的地址,最初映射的页表都是直接从这里分配的。

=========================== arch/i386/kernel/vmlinux.lds.s ============================
目录 1 引子 2 1.1 上电 2 1.2 BIOS时代 3 1.3 内核引导程序 5 2 内核映像的形成 8 2.1 MakeFile预备知识 9 2.1.1 Makefile书写规则 9 2.1.2 Makefile变量 10 2.1.3 条件判断 14 2.1.4 函数 17 2.1.5 隐含规则 17 2.1.6 定义模式规则 19 2.1 KBuild体系 23 2.1.1 内核目标 24 2.1.2 主机程序 26 2.1.3 编译标志 27 2.2 内核编译分析 28 2.2.1 编译配置 29 2.2.2 寻找第一个目标 32 2.2.3 prepare和scripts目标 38 2.2.4 递归编译各对象 41 2.2.5 链接vmlinux 44 2.2.6 制作bzImage 50 3 实模式下的内核代码 57 3.1 内核映像内存布局 58 3.2 实模式汇编代码header.S 60 3.2.1 无用的bootsect代码 60 3.2.2 初始化头变量hdr 63 3.2.3 准备实模式下C语言环境 64 3.3 实模式代码main函数 69 3.3.1 复制初始化头变量 71 3.3.2 初始化堆 74 3.3.3 确保支持当前运行的CPU 75 3.3.4 设置BIOS的x86模式 76 3.3.5 内存的检测 78 3.3.6 设置键盘属性 81 3.3.7 填充系统环境配置表 82 3.3.8 填充IST信息 83 3.3.9 设置Video模式 83 3.4 实模式代码go_to_proteced_mode函数 91 3.4.1 禁止可屏蔽和不可屏蔽中断 92 3.4.2 打开A20地址线 93 3.4.3 安装临时全局描述符表 99 3.4.4 第一次启动保护模式 101 4 保护模式下的内核代码 107 4.1 32位x86保护模式代码 107 4.1.1 内核解压缩的前期工作 108 4.1.2 解压缩内核 111 4.1.3 第二次启动保护模式 121 4.1.4 第一次启动分页管理 124 4.1.5 初始化0号进程 128 4.2 向start_kernel进发 131 4.2.1 初始化中断描述符表 132 4.2.2 第三次启动保护模式 137 4.2.3 启动x86虚拟机 141 5 走向现代:start_kernel函数 144 5.1 初始化同步与互斥环境 148 5.1.1 屏蔽中断 148 5.1.2 启动大内核锁 152 5.1.3 注册时钟通知链 153 5.1.4 激活第一个CPU 155 5.1.5 初始化地址散列表 160 5.1.6 打印版本信息 161 5.2 执行setup_arch()函数 166 5.2.1 拷贝可用内存区信息 171 5.2.2 获得总页面数 175 5.2.3 着手建立永久内核页表 177 5.2.4 第二次启动分页管理 181 5.2.5 建立内存管理架构 186 5.2.6 添砖加瓦 192 5.3 设置每CPU环境 206 5.4 初始化内存管理区列表 211 5.5 利用early_res分配内存 214 5.6 触碰虚拟文件系统 223 5.7 初始化异常服务 224 5.8 初始化内存管理 230 5.8.1 启用伙伴算法 230 5.8.2 初始化slab分配器 241 5.8.3 初始化非连续内存区 250 5.9 初始化调度程序 251 5.10 初始化中断处理系统 256 5.10.1 设置APIC中断服务 256 5.10.2 初始化本地软时钟 264 5.10.3 软中断初始化 268 5.10.4 初始化定时器中断 271 5.11 走进start_kernel尾声 273 5.11.1 初始化slab的后续工作 273 5.11.2 启动console 275 5.11.3 一些简单的函数 276 5.11.4 校准CPU时钟速度 279 5.11.5 创建一些slab缓存 282 5.12 安装根文件系统 287 5.12.1 创建VFS相关slab缓存 288 5.12.2 安装rootfs 291 5.12.3 安装proc文件系统 296 6 后start_kernel时代 298 6.1 创建1号进程 298 6.2 子系统的初始化 306 6.3 启动shell环境 309
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值