当前位置:操作系统 > Unix/Linux >>

FreeBSD的Loader和内核初始化

loader也是一个 BTX 客户,在这里不作详述。已有一部内容全面的手册 loader(8) ,由Mike Smith书写。比loader更底层的BTX的机理已经在前面讨论过。 loader 的主要任务是引导内核。当内核被装入内存后,即被loader调用:

  sys/boot/common/boot.c:

  /* 从loader中调用内核中对应的exec程序 */

  module_formats[km->m_loader]->l_exec(km);loader跳转至哪里呢?那就是内核的入口点。让我们来看一下链接内核的命令:sys/conf/Makefile.i386:

  ld -elf -Bdynamic -T /usr/src/sys/conf/ldscript.i386

  -export-dynamic -dynamic-linker /red/herring -o kernel -X locore.o

  在这一行中有一些有趣的东西。首先,内核是一个ELF动态链接二进制文件,可是动态链接器却是/red/herring,一个莫须有的文件。其次,看一下文件sys/conf/ldscript.i386,可以对理解编译内核时ld的选项有一些启发。阅读最前几行,字符串sys/conf/ldscript.i386:

  ENTRY(btext)

  表示内核的入口点是符号 `btext'。这个符号在locore.s中定义:sys/i386/i386/locore.s:

  .text

  /**********************************************************************

  *

  * This is where the bootblocks start us, set the ball rolling...

  * 入口

  */

  NON_GPROF_ENTRY(btext)

  首先将寄存器EFLAGS设为一个预定义的值0x00000002,然后初始化所有段寄存器:sys/i386/i386/locore.s

  /* 不要相信BIOS给出的EFLAGS值 */

  pushl

  $PSL_KERNEL

  popfl

  /*

  * 不要相信BIOS给出的%fs、%gs值。相信引导过程中设定的%cs、%ds、%es、%ss值

  */

  mov %ds, %ax

  mov %ax, %fs

  mov %ax, %gs

  btext调用例程recover_bootinfo(),identify_cpu(),create_pagetables()。

  这些例程也定在locore.s之中。这些例程的功能如下:recover_bootinfo

  这个例程分析由引导程序传送给内核的参数。引导内核有3种方式:

  由loader引导(如前所述), 由老式磁盘引导块引导,无盘引导方式。

  这个函数决定引导方式,并将结构struct bootinfo存储至内核内存。

  identify_cpu 这个函数侦测CPU类型,将结果存放在变量_cpu中。

  create_pagetables 这个函数为分页表在内核内存空间顶部分配一块空间,

  并填写一定内容 下一步是开启VME(如果CPU有这个功能):

  testl

  $CPUID_VME, R(_cpu_feature)

  jz

  1f

  movl

  %cr4, %eax

  orl $CR4_VME, %eax

  movl

  %eax, %cr4

  然后,启动分页模式:/* Now enable paging */

  movl

  R(_IdlePTD), %eax

  movl

  %eax,%cr3

  /* load ptd addr into mmu */

  movl

  %cr0,%eax

  /* get control word */

  orl $CR0_PE|CR0_PG,%eax

  /* enable paging */

  movl

  %eax,%cr0

  /* and let's page NOW! */

  由于分页模式已经启动,原先的实地址寻址方式随即失效。

  随后三行代码用来跳转至虚拟地址:

  pushl

  $begin

  /* jump to high virtualized address */

  ret

  /* 现在跳转至KERNBASE,那里是操作系统内核被链接后真正的入口 */

  begin:

  函数init386()被调用;随参数传递的是一个指针,指向第一个空闲物理页。

  随后执行mi_startup()。init386是一个与硬件系统相关的初始化函数,

  mi_startup()是个与硬件系统无关的函数(前缀'mi_'表示Machine Independent,

  不依赖于机器)。内核不再从mi_startup()里返回;调用这个函数后,

  内核完成引导:sys/i386/i386/locore.s:

  movl

  physfree, %esi

  pushl

  %esi

  /* 送给init386()的第一个参数 */

  call

  _init386

  /* 设置386芯片使之适应UNIX工作 */

  call

  _mi_startup /* 自动配置硬件,挂接根文件系统,等 */

  hlt

  /* 不再返回到这里! */

  1.7.1 init386()init386()定义在sys/i386/i386/machdep.c中,它针对Intel 386芯片进行低级初始化。loader已将CPU切换至保护模式。

  loader已经建立了最早的任务。译者注: 每个"任务"都是与其它“任务”相对独立的执行环境。

  任务之间可以分时切换,这为并发进程/线程的实现提供了必要基础。对于Intel 80x86任务的描述,在这个任务中,内核将继续工作。在讨论其代码前,

  我将处理器对保护模式必须完成的一系列准备工作一并列出:

  初始化内核的可调整参数,这些参数由引导程序传来准备GDT(全局描述符表)

  准备IDT(中断描述符表)初始化系统控制台初始化DDB(内核的点调试器),如果它被编译进内核的话初始化TSS(任务状态段)准备LDT(局部描述符表)建立proc0(0号进程,即内核的进程)的pcb(进程控制块)init386()首先初始化内核的可调整参数,这些参数由引导程序传来。先设置环境指针(environment pointer, envp)调用,再调用init_param1()。

  envp指针已由loader存放在结构bootinfo中:sys/i386/i386/machdep.c:

  kern_envp = (caddr_t)bootinfo.bi_envp + KERNBASE;

  /* 初始化基本可调整项,如hz等 */

  init_param1();

  init_param1()定义在sys/kern/subr_param.c之中。这个文件里有一些sysctl项,

  两个函数,init_param1()和init_param2()。这两个函数从init386()中调用:sys/kern/subr_param.c

  hz = HZ;

  TUNABLE_INT_FETCH("kern.hz", &hz);

  TUNABLE__FETCH用来获取环境变量的值:/usr/src/sys/sys/kernel.h

  #define TUNABLE_INT_FETCH(path, var)

  getenv_int((path), (var))

  Sysctlkern.hz是系统时钟频率。同时,这些sysctl项被init_param1()设定:

  kern.maxswzone, kern.maxbcache, kern.maxtsiz, kern.dfldsiz, kern.dflssiz,

  kern.maxssiz, kern.sgrowsiz。然后init386() 准备全局描述符表(Global Descriptors Table, GDT)。

  在x86上每个任务都运行在自己的虚拟地址空间里,这个空间由"段址:偏移量"的数对指定。

  举个例子,当前将要由处理器执行的指令在 CS:EIP,那么这条指令的线性虚拟地址就是“代码段虚拟段地址CS” + EIP。为了简便,段起始于虚拟地址0,终止于界限4G字节。所以,在这个例子中,指令的线性虚拟地址正是EIP的值。

  段寄存器,如CS、DS等是选择符,即全局描述符表中的索引(更精确的说,索引并非选择符的全部,而是选择符中的INDEX部分)。译者注: 对于80386,选择符有16位,INDEX部分是其中的高13位。

  FreeBSD的全局描述符表为每个CPU保存着15个选择符:sys/i386/i386/machdep.c:

  union descriptor gdt[NGDT * MAXCPU];

  /* 全局描述符表 */

  sys/i386/include/segments.h:

  /*

  * 全局描述符表(GDT)中的入口

  */

  #define GNULL_SEL

  0

  /* 空描述符 */

  #define GCODE_SEL

  1

  /* 内核代码描述符 */

  #define GDATA_SEL

  2

  /* 内核数据描述符 */

  #define GPRIV_SEL

  3

  /* 对称多处理(SMP)每处理器专有数据 */

  #define GPROC0_SEL

  4

  /* Task state process slot zero and up, 任务状态进程 */

  #define GLDT_SEL

  5

  /* 每个进程的局部描述符表 */

  #define GUSERLDT_SEL

  6

  /* 用户自定义的局部描述符表 */

  #define GTGATE_SEL

  7

  /* 进程任务切换关口 */

  #define GBIOSLOWMEM_SEL 8

  /* BIOS低端内存访问(必须是这第8个入口) */

  #define GPANIC_SEL

  9

  /* 会导致全系统异常中止工作的任务状态 */

  #define GBIOSCODE32_SEL 10

  /* BIOS接口(32位代码) */

  #define GBIOSCODE16_SEL 11

  /* BIOS接口(16位代码) */

  #define GBIOSDATA_SEL

  12

  /* BIOS接口(数据) */

  #define GBIOSUTIL_SEL

  13

  /* BIOS接口(工具) */

  #define GBIOSARGS_S
CopyRight © 2022 站长资源库 编程知识问答 zzzyk.com All Rights Reserved
部分文章来自网络,