《linux内核完全剖析》笔记01-启动分析

启动代码分析-《linux 0.12内核完全剖析》笔记

导语:

linux 0.12的启动代码能够给我们分析最新的linux代码给予一定的启示,启动代码虽然只有三个文件,但是对读者分析能力的要求比较高,主要是在对汇编语言以及x86编程体系的理解

一. 对linux 0.12启动代码的分析

  • bootsect.S文件主要的目的是加载setup.S和内核模块
  • setup.S 主要目的是通过biso中断读取机器的系统数据
  • head.s 主要目的是内核初始化之前的环境配置,也就是32位保护模式运行做准备

笔记重点:

1. 三段代码都涉及代码的移动

主要目的是为了空间复用,代码是从0x7C00开始执行,第一段代码就将bootsect.S的代码移动到绝对地址0x9000处然后再执行

entry start
start:
    mov ax,#BOOTSEG     "BOOTSEG为0x7C0
    mov ds,ax
    mov ax,#INITSEG     "0x9000
    mov es,ax
    mov cx,#256         "512字节
    sub si,si           "si = 0x0000
    sub di,di           "di = 0x0000
    rep                 "cx递减1 直到cx为0
    movw                "移动一个字
    jmpi    go,INITSEG  "跳转到0x9000执行
2. 机器的系统数据都是通过BIOS的功能获取到的,内核初始化的时候都要利用到这些数据
  • 最初读取加载内核代码是使用的BIOS的INT 0x13
  • setup.S中利用BIOS的INT 0x15功能读取内存的大小
  • 其他硬件数据
3. setup.S的主要目的是设置中断向量表述表和全局描述符表,以开启内核的32位保护模式

描述符表的定义是在setup.S的567行开始

gdt:
    .word   0,0,0,0     ! dummy

    .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word   0x0000      ! base address=0
    .word   0x9A00      ! code read/exec
    .word   0x00C0      ! granularity=4096, 386

    .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word   0x0000      ! base address=0
    .word   0x9200      ! data read/write
    .word   0x00C0      ! granularity=4096, 386

idt_48:
    .word   0           ! idt limit=0
    .word   0,0         ! idt base=0L

gdt_48:
    .word   0x800       ! gdt limit=2048, 256 GDT entries
    .word   512+gdt,0x9 ! gdt base = 0X9xxxx

设置GDT寄存器和IDT寄存器是在139行

lidt idt_48
lgdt gdt_48

idt_48h和gdt_48就是上面定义的
是一个6字节长的数据,前2字节是表的长度,后4字节是表的基地址,设置cr0的第0位为1开启保护模式

4. 这里有非常详细的8259A的编程资料

没有学过微机接口的同学可能对8259A的结构不够了解也是没有关系,了解这些对中断系统的了解比较关键

5. head.s最关键的地方是在32位保护模式下开启内存的分页处理机制

cr3寄存器是记录页目录的基地址,cr0的PG位置1就是开启分页处理,然后重新设置IDT和GDT,第一次设置IDT和GDT是为了head.s运行32位保护模式,设置页目录而临时设置,这个时候还不是分页模式,不能分配内存给GDT和IDT,第一次设置时IDT是空表,GDT只有3个描述符,这次设置GDT的大小是256*8-1,IDT的大小也是256,这是我认为的最主要区别。

idt_descr:
    .word 256*8-1       "idt 包含256项
    .long _idt
.align 2
.word 0
gdt_descr:
    .word 256*8-1       "gdt 包含256项
    .long _gdt          "地址为下面的_gdt标记

    .align 3
_idt:
    .fill 256,8,0       "idt存放的地方

_gdt:   
    .quad 0x0000000000000000    /* NULL descriptor */
    .quad 0x00c09a0000000fff    /* 16Mb */
    .quad 0x00c0920000000fff    /* 16Mb */
    .quad 0x0000000000000000    /* TEMPORARY - don't use */
    .fill 252,8,0           /* space for LDT's and TSS's etc */ 

结论:通过对linux 0.12启动代码的分析,可以知道linux启动的三大步骤。

  • 首先是通过BIOS提供的功能,加载linux初始化之前的环境初始化代码,setup.S和head.s;
  • 然后setup.S通过BIOS的中断服务获取系统硬件的一些参数并保存,准备给linux进行初始化提供参考;
  • 然后通过head.s设置系统的32位保护模式,并开启内存的分页模式

二. linux 4.9启动代码分析

导语:

根据以上对linux 0.12的分析,知道启动必须经过的三个阶段,现在的启动过程,bootsect.S和setup.S部分
的功能已经由grub等bootloader来承担,现在主要分析等同于linux 0.12的head.s部分功能的代码,现在的内核代码因为历史原因,也因为需要对有限内存的嵌入式的支持,对内核进行了压缩,在这里暂不对这些代码进行分析

代码分析之前有几点说明
首先是所有标号地址都在编译内核的时候,多偏移3G
目的是在设置虚拟地址以后,内核映射在进程的3G~4G的位置上
因此不管是pa宏还是其他标号地址都在未启用虚拟地址之前,要减去PAGE_OFFSET

1.先来看.data段中对GDT和IDT的定义

linux/arch/x86/kernel/head_32.S的727行

  • 734行定义了GDT全局描述符,前两个字节表示表的长度,后四个字节是表的基地址
    boot_gdt - __PAGE_OFFSET为实际的内存物理地址
  • 739行定义IDT描述符,中断表描述符
  • 740行IDT_ENTRIES为中断表的大小
  • 755行GDT_ENTRY_BOOT_CS为全局描述符表大小
720 /*
721  * The IDT and GDT 'descriptors' are a strange 48-bit object
722  * only used by the lidt and lgdt instructions. They are not
723  * like usual segment descriptors - they consist of a 16-bit
724  * segment size, and 32-bit linear address value:
725  */
726 
727 .data
728 .globl boot_gdt_descr
729 .globl idt_descr
730 
731         ALIGN
732 # early boot GDT descriptor (must use 1:1 address mapping)
733         .word 0                         # 32 bit align gdt_desc.address
734 boot_gdt_descr:
735         .word __BOOT_DS+7
736         .long boot_gdt - __PAGE_OFFSET
737 
738         .word 0                         # 32-bit align idt_desc.address
739 idt_descr:
740         .word IDT_ENTRIES*8-1           # idt contains 256 entries
741         .long idt_table
742 
743 # boot GDT descriptor (later on used by CPU#0):
744         .word 0                         # 32 bit align gdt_desc.address
745 ENTRY(early_gdt_descr)
746         .word GDT_ENTRIES*8-1
747         .long gdt_page                  /* Overwritten for secondary CPUs */
748 
749 /*
750  * The boot_gdt must mirror the equivalent in setup.S and is
751  * used only for booting.
752  */
753         .align L1_CACHE_BYTES
754 ENTRY(boot_gdt)
755         .fill GDT_ENTRY_BOOT_CS,8,0
756         .quad 0x00cf9a000000ffff        /* kernel 4GB code at 0x00000000 */
757         .quad 0x00cf92000000ffff        /* kernel 4GB data at 0x00000000 */
2.初始化页目录表和页表

linux/arch/x86/kernel/head_32.S的727行

  • 217行是得到内核__PAGE_OFFSET位置的页目录项, 首先虚拟地址的组成是
10位12位10 位
页目录项索引页目录表索引物理偏移

物理地址偏移向右偏移22位得到页目录项索引,每项页目录项索引占4字节,左移2位得到特定的页目录项索引
* 228行~227行是填充页目录项
* 229行~231行是填充页表项
* 238行~245行是填充最后一页的对齐处理

216 
217 page_pde_offset = (__PAGE_OFFSET >> 20);
218 
219         movl $pa(__brk_base), %edi              /* 将__brk_base地址给%edi*/
220         movl $pa(initial_page_table), %edx      /* 将initial_page_table物理地址给%edx*/
221         movl $PTE_IDENT_ATTR, %eax              /* PTE_IDENT_ATTR 是定义页目录的属性的*/
222 10:
223         leal PDE_IDENT_ATTR(%edi),%ecx          /* Create PDE entry */
224         movl %ecx,(%edx)                        /* Store identity PDE entry */
225         movl %ecx,page_pde_offset(%edx)         /* Store kernel PDE entry */
226         addl $4,%edx                            /* 页目录项每项占4字节*/
227         movl $1024, %ecx                        /* 一个页目录项占1024项页表*/
228 11:
229         stosl                                   /* 将eax的内容填充到%edi地址处,并%edi + 4,%eax是页表项的内容*/
230         addl $0x1000,%eax                       /* 0x1000 = 2^12 = 4096*/
231         loop 11b
232         /*
233          * End condition: we must map up to the end + MAPPING_BEYOND_END.
234          */
235         movl $pa(_end) + MAPPING_BEYOND_END + PTE_IDENT_ATTR, %ebp
236         cmpl %ebp,%eax
237         jb 10b
238         addl $__PAGE_OFFSET, %edi
239         movl %edi, pa(_brk_end)
240         shrl $12, %eax
241         movl %eax, pa(max_pfn_mapped)
242 
243         /* Do early initialization of the fixmap area */
244         movl $pa(initial_pg_fixmap)+PDE_IDENT_ATTR,%eax
245         movl %eax,pa(initial_page_table+0xffc)
3.启动分页

linux/arch/x86/kernel/head_32.S的398行

393 enable_paging:
394 
395 /*
396  * Enable paging
397  */
398         movl $pa(initial_page_table), %eax
399         movl %eax,%cr3          /* set the page table pointer.. */
400         movl $CR0_STATE,%eax
401         movl %eax,%cr0          /* ..and set paging (PG) bit */
402         ljmp $__BOOT_CS,$1f     /* Clear prefetch and normalize %eip */
403 1:
404         /* Shift the stack pointer to a virtual address */
405         addl $__PAGE_OFFSET, %esp
4.设置IDT
406 
407 /*
408  * start system 32-bit setup. We need to re-do some of the things done
409  * in 16-bit mode for the "real" operations.
410  */
411         movl setup_once_ref,%eax
412         andl %eax,%eax
413         jz 1f                           # Did we do this already?
414         call *%eax
5.设置内核运行环境并跳转到start_kernel
447 is486:
448         movl $0x50022,%ecx      # set AM, WP, NE and MP
449         movl %cr0,%eax
450         andl $0x80000011,%eax   # Save PG,PE,ET
451         orl %ecx,%eax
452         movl %eax,%cr0
453 
454         lgdt early_gdt_descr
455         lidt idt_descr
456         ljmp $(__KERNEL_CS),$1f
457 1:      movl $(__KERNEL_DS),%eax        # reload all the segment registers
458         movl %eax,%ss                   # after changing gdt.
459 
460         movl $(__USER_DS),%eax          # DS/ES contains default USER segment
461         movl %eax,%ds
462         movl %eax,%es
463 
464         movl $(__KERNEL_PERCPU), %eax
465         movl %eax,%fs                   # set this cpu's percpu
466 
467         movl $(__KERNEL_STACK_CANARY),%eax
468         movl %eax,%gs
469 
470         xorl %eax,%eax                  # Clear LDT
471         lldt %ax
472 
473         pushl $0                # fake return address for unwinder
474         jmp *(initial_code)

initial_code的定义如下

654 ENTRY(initial_code)
655         .long i386_start_kernel
656 ENTRY(setup_once_ref)
657         .long setup_once
6.启动入口分析

第97行

 96 __HEAD
 97 ENTRY(startup_32)
 98         movl pa(initial_stack),%ecx         #将堆的初始地址给ecx
 99         
100 /*
    *esi存放了boot_param结构体的地址
    *OFFSET(BP_loadflags,boot_params,hdr.loadflag)
    *位于arch/x86/kernel/asm-offsets.c的84行
    *从bootloader传过来的参数告诉内核
    *是否需要设置保护模式
    *不需要则跳转到下标为的语句
    */
102         testb $KEEP_SEGMENTS, BP_loadflags(%esi)
103         jnz 2f
104 
105 /*
106  * 重新设置一下保护模式
     * 加载ds,es,fs,gs,ss
107  */
108         lgdt pa(boot_gdt_descr)
109         movl $(__BOOT_DS),%eax
110         movl %eax,%ds
111         movl %eax,%es
112         movl %eax,%fs
113         movl %eax,%gs
114         movl %eax,%ss
115 2:
116         leal -__PAGE_OFFSET(%ecx),%esp
117 
118 /*
119  * 清空BSS
120  */
121         cld
122         xorl %eax,%eax
123         movl $pa(__bss_start),%edi
124         movl $pa(__bss_stop),%ecx
125         subl %edi,%ecx
126         shrl $2,%ecx
127         rep ; stosl
128 /*
129  * 拷贝boot_params结构体的内容到内核中
135  */
136         movl $pa(boot_params),%edi
137         movl $(PARAM_SIZE/4),%ecx
138         cld
139         rep
140         movsl
141         movl pa(boot_params) + NEW_CL_POINTER,%esi
142         andl %esi,%esi
143         jz 1f                   # No command line
144         movl $pa(boot_command_line),%edi
145         movl $(COMMAND_LINE_SIZE/4),%ecx
146         rep
147         movsl
148 1:
149 
结论:

经过分析,基本了解head_32.S是linux进入start_kernel之前,为内核初始化准备环境,设置GDT,IDT,开启32位保护模式,开启分页模式。和linux 0.12的head.s的功能如出一辙。

  • 2
    点赞
  • 3
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值