XV6学习笔记(1)

1. 启动与加载

首先我们先来分析pc的启动。其实这个都是老生常谈了,但是还是很重要的(也不知道面试官考不考这玩意),

1. 启动的第一件事-bios

首先启动的第一件事就是运行bios,这个时候我们的机器位于实模式,也就是16位地址。这个时候能访问的空间只有1mb

  1. 就是设置cs寄存器的值为0xFFFF, ip的值为0x0000
  2. 这个就是bios的地址,然后我们会去运行bios执行各种对硬件的检查
  3. 但是xv6和之前的jos(也就是828)中都没有这样做,作为一个精简的os系统,

2. bootloader的汇编程序

我们的引导程序位于第一个扇区内。第一个扇区地址为0x7c00。会在bios结束之后跳转到这里来

整个bootloader程序分为两个部分。第一部分是汇编程序,第二部分则是c语言

  1. 第一件做的事情是关中断 + 清空寄存器

  2. 第二件就是打开A20.

    打开A20是非常重要的一件事情。这是突破16位的关键。这里参考了别人的博客

    我们具体来看 xv6 的实现代码

    1. seta20.1:
    2. inb $0x64,%al # Wait for not busy
    3. testb $0x2,%al
    4. jnz seta20.1
    5. movb $0xd1,%al # 0xd1 -> port 0x64
    6. outb %al,$0x64
    7. seta20.2:
    8. inb $0x64,%al # Wait for not busy
    9. testb $0x2,%al
    10. jnz seta20.2
    11. movb $0xdf,%al # 0xdf -> port 0x60
    12. outb %al,$0x60

    这里 bootasm.S用了两个方法 seta20.1seta20.2 来实现通过 804x 键盘控制器打开 A20 gate。

    第一步是向 804x 键盘控制器的 0x64 端口发送命令。这里传送的命令是 0xd1,这个命令的意思是要向键盘控制器的 P2 写入数据。这就是 seta20.1 代码段所做的工作(具体的解释可以参看我在代码中写的注释)。

    第二步就是向键盘控制器的 P2 端口写数据了。写数据的方法是把数据通过键盘控制器的 0x60 端口写进去。写入的数据是 0xdf,因为 A20 gate 就包含在键盘控制器的 P2 端口中,随着 0xdf 的写入,A20 gate 就被打开了。

    接下来要做的就是进入“保护模式”了。

  3. 准备GDT表

    进入保护模式之后,我们的寻址就要根据段地址 + 段内偏移来做了,所有这个全局段描述表非常关键啊

    GDT 表里的每一项叫做“段描述符”,用来记录每个内存分段的一些属性信息,每个“段描述符”占 8 字节,我们先来看一眼这个段描述符的具体结构:

  1. GDT 也搞定了,接下来我们就要把我们刚刚在内存中设定好的 GDT 的位置告诉 CPU。CPU 单独为我们准备了一个寄存器叫做 GDTR 用来保存我们 GDT 在内存中的位置和我们 GDT 的长度。

    GDTR 寄存器一共 48 位,其中高 32 位用来存储我们的 GDT 在内存中的位置,其余的低 16 位用来存我们的 GDT 有多少个段描述符。并且还专门提供了一个指令用来让我们把 GDT 的地址和长度传给 GDTR 寄存器,来看 xv6 的代码:

  1. lgdt gdtdesc

而这个 gdtdesc 和 gdt 一起放在了 bootasm.S 文件的最底部,我们看一眼:

  1. gdtdesc:
  2. .word (gdtdesc - gdt - 1) # 16 位的 gdt 大小sizeof(gdt) - 1
  3. .long gdt # 32 位的 gdt 所在物理地址
  1. 在xv6中,我们的cpu利用四个控制寄存器来进行一些状态控制,想要进入保护模式需要修改cr0寄存器

  • PG    为 0 时代表只使用分段式,不使用分页式

             为 1 是启用分页式
  • PE    为 0 时代表关闭保护模式,运行在实模式下

             为 1 则开启保护模式

最后看一下在xv6中如何做到开启保护模式的

  1. lgdt gdtdesc
  2. movl %cr0, %eax
  3. orl $CR0_PE_ON, %eax
  4. movl %eax, %cr0

而这里其实就是把 cr0 寄存器的值 或上 $CR0_PE_ON的值。而. CR0_PE_ON = 0x0......1

这里的意思就是开启保护模式

  1. 进入c语言之前的一些汇编
  1. # Jump to next instruction, but in 32-bit code segment.
  2. # Switches processor into 32-bit mode.
  3. ljmp $PROT_MODE_CSEG, $protcseg
  4. .code32 # Assemble for 32-bit mode
  5. protcseg:
  6. # Set up the protected-mode data segment registers
  7. movw $PROT_MODE_DSEG, %ax # Our data segment selector
  8. movw %ax, %ds # -> DS: Data Segment
  9. movw %ax, %es # -> ES: Extra Segment
  10. movw %ax, %fs # -> FS
  11. movw %ax, %gs # -> GS
  12. movw %ax, %ss # -> SS: Stack Segment
  13. # Set up the stack pointer and call into C.
  14. movl $start, %esp
  15. call bootmain

3. bootloader的c语言程序

  1. 首先去磁盘第一个扇区读取内核的ELF文件
  2. 判断是否是一个有效的ELF头文件
  3. 然后逐段把操作系统从磁盘中读到内核中
  4. 最后执行内核的程序,此后操作系统就交由内核处理
  1. void
  2. bootmain(void)
  3. {
  4. struct Proghdr *ph, *eph;
  5. int i;
  6. // read 1st page off disk
  7. readseg((uint32_t) ELFHDR, SECTSIZE*8, 0);
  8. // is this a valid ELF?
  9. if (ELFHDR->e_magic != ELF_MAGIC)
  10. goto bad;
  11. // load each program segment (ignores ph flags)
  12. ph = (struct Proghdr *) ((uint8_t *) ELFHDR + ELFHDR->e_phoff);
  13. eph = ph + ELFHDR->e_phnum;
  14. for (; ph < eph; ph++) {
  15. // p_pa is the load address of this segment (as well
  16. // as the physical address)
  17. readseg(ph->p_pa, ph->p_memsz, ph->p_offset);
  18. for (i = 0; i < ph->p_memsz - ph->p_filesz; i++) {
  19. *((char *) ph->p_pa + ph->p_filesz + i) = 0;
  20. }
  21. }
  22. // call the entry point from the ELF header
  23. // note: does not return!
  24. ((void (*)(void)) (ELFHDR->e_entry))();
  25. bad:
  26. outw(0x8A00, 0x8A00);
  27. outw(0x8A00, 0x8E00);
  28. while (1)
  29. /* do nothing */;
  30. }

2. 执行内核

好文分享

kernel.ld中有一些关于内核的设置

  1. SECTIONS
  2. {
  3. /* Link the kernel at this address: "." means the current address */
  4. . = 0xF0100000;
  5. /* AT(...) gives the load address of this section, which tells
  6. the boot loader where to load the kernel in physical memory */
  7. .text : AT(0x100000) {
  8. *(.text .stub .text.* .gnu.linkonce.t.*)
  9. }
  10. PROVIDE(etext = .); /* Define the 'etext' symbol to this value */
  11. .rodata : {
  12. *(.rodata .rodata.* .gnu.linkonce.r.*)
  13. }

这里设置了内核的代码段位于内存中的0x100000位置,而所对应的虚拟地址为0xF0100000

好了下面就可以去entry.S看一下内核的代码了

1. 设置页表开启分页

  1. 对于64位机,CR3寄存器也从32位变成了64位,它的主要功能还是用来存放页目录表物理内存基地址,每当进程切换时,Linux就会把下一个将要运行进程的页目录表物理内存基地址等信息存放到CR3寄存器中。

  1. 首先开启4MB内存页。这里是通过设置cr4寄存器的PSE位来实现的
  1. .globl entry
  2. entry:
  3. # Turn on page size extension for 4Mbyte pages
  4. movl %cr4, %eax
  5. orl $(CR4_PSE), %eax
  6. movl %eax, %cr4
  1. 设置页目录开启页表

这里通过代码我们可以得到页表的基地址就在entrypgdir中,这个变量可以在main.c中找到

开启页表就是通过调整cr0寄存器的位来实现的

  1. # Set page directory
  2. movl $(V2P_WO(entrypgdir)), %eax
  3. movl %eax, %cr3
  4. # Turn on paging.
  5. movl %cr0, %eax
  6. orl $(CR0_PG|CR0_WP), %eax
  7. movl %eax, %cr0
  1. // Boot page table used in entry.S and entryother.S.
  2. // Page directories (and page tables), must start on a page boundary,
  3. // hence the "__aligned__" attribute.
  4. // Use PTE_PS in page directory entry to enable 4Mbyte pages.
  5. __attribute__((__aligned__(PGSIZE)))
  6. pde_t entrypgdir[NPDENTRIES] = {
  7. // Map VA's [0, 4MB) to PA's [0, 4MB)
  8. [0] = (0) | PTE_P | PTE_W | PTE_PS,
  9. // Map VA's [KERNBASE, KERNBASE+4MB) to PA's [0, 4MB)
  10. [KERNBASE>>PDXSHIFT] = (0) | PTE_P | PTE_W | PTE_PS,
  11. };
  12. //PAGEBREAK!
  13. // Blank page.

将这些宏定义都转义过来我们看看这个页表的样子

  1. unsigned int entrypgdir[1024] = {
  2. [0] = 0 | 0x001 | 0x002 | 0x080, // 0x083 = 0000 1000 0011
  3. [0x80000000 >> 22] = 0 | 0x001 | 0x002 | 0x080 // 0x083
  4. };

当然这里只是一个临时页表。这里只有两个页表项 0x000000000x80000000,而且两个页表项索引的内存物理地址都是 0 ~ 4MB

把虚拟地址空间的地址范围:0x80100000 -0x80500000,映射到物理地址范围:0x00000000 - 0x00400000上面。也可以把虚拟地址范围:0x00000000 - 0x00400000,同样映射到物理地址范围:0x00000000~0x00400000上面。任何不再这两个虚拟地址范围内的地址都会引起一个硬件异常。虽然只能映射这两块很小的空间,但是已经足够刚启动程序的时候来使用了。

这里的jos里地址就是0xF0100000,不过逻辑都是一模一样的

  1. 设置内核栈以及跳转到c语言到main.c
  1. # Set up the stack pointer.
  2. movl $(stack + KSTACKSIZE), %esp
  3. # Jump to main(), and switch to executing at
  4. # high addresses. The indirect call is needed because
  5. # the assembler produces a PC-relative instruction
  6. # for a direct jump.
  7. mov $main, %eax
  8. jmp *%eax
  9. .comm stack, KSTACKSIZE

3. main.c

  1. // Bootstrap processor starts running C code here.
  2. // Allocate a real stack and switch to it, first
  3. // doing some setup required for memory allocator to work.
  4. int
  5. main(void)
  6. {
  7. kinit1(end, P2V(4*1024*1024)); // phys page allocator
  8. kvmalloc(); // kernel page table
  9. mpinit(); // collect info about this machine
  10. lapicinit();
  11. seginit(); // set up segments
  12. cprintf("\ncpu%d: starting xv6\n\n", cpu->id);
  13. picinit(); // interrupt controller
  14. ioapicinit(); // another interrupt controller
  15. consoleinit(); // I/O devices & their interrupts
  16. uartinit(); // serial port
  17. pinit(); // process table
  18. tvinit(); // trap vectors
  19. binit(); // buffer cache
  20. fileinit(); // file table
  21. iinit(); // inode cache
  22. ideinit(); // disk
  23. if(!ismp)
  24. timerinit(); // uniprocessor timer
  25. startothers(); // start other processors
  26. kinit2(P2V(4*1024*1024), P2V(PHYSTOP)); // must come after startothers()
  27. userinit(); // first user process
  28. // Finish setting up this processor in mpmain.
  29. mpmain();
  30. }

这里做了各种对于os的初始化。接下来我们将会看到 xv6 的内核是如何实现内存管理、进程管理、IO 操作等化操作系统所应该具有的功能,同时会结合jos也就是mit6.828进行对比一下。

XV6学习笔记(1) : 启动与加载的更多相关文章

  1. [置顶] iOS学习笔记47——图片异步加载之EGOImageLoading

    上次在<iOS学习笔记46——图片异步加载之SDWebImage>中介绍过一个开源的图片异步加载库,今天来介绍另外一个功能类似的EGOImageLoading,看名字知道,之前的一篇学习笔 ...

  2. Flutter学习笔记(19)--加载本地图片

    如需转载,请注明出处:Flutter学习笔记(19)--加载本地图片 上一篇博客正好用到了本地的图片,记录一下用法: 首先新建一个文件夹,这个文件夹要跟目录下 然后在pubspec.yaml里面声明出 ...

  3. Quartz.net 2.x 学习笔记03-使用反射加载定时任务

    将定时任务信息存储在XML文件中,使用反射加载定时任务 首先新建一个MVC的空站点,使用NuGet添加对Quartz.net和Common.Logging.Log4Net1213的引用,同时使用NuG ...

  4. [OpenCV学习笔记3][图像的加载+修改+显示+保存]

    正式进入OpenCV学习了,前面开始的都是一些环境搭建和准备工作,对一些数据结构的认识主要是Mat类的认识: [1.学习目标] 图像的加载:imread() 图像的修改:cvtColor() 图像的显 ...

  5. Android学习笔记_36_ListView数据异步加载与AsyncTask

    一.界面布局文件: 1.加入sdcard写入和网络权限: <!-- 访问internet权限 --> <uses-permission android:name="andr ...

  6. 【OpenCV学习笔记之一】图像加载,修改及保存

    加载图像(用cv::imread)imread功能是加载图像文件成为一个Mat对象 其中第一个参数表示图像文件名称第二个参数 表示加载的图像是什么类型 支持常见的三个参数值IMREAD_UNCHANG ...

  7. WMS学习笔记:1.尝试加载WMS

    1.首先找一个可用的WMS栅格地图服务:http://demo.cubewerx.com/demo/cubeserv/cubeserv.cgi 获取GetCapabilities: http://de ...

  8. Android学习笔记_51_转android 加载大图片防止内存溢出

    首先来还原一下堆内存溢出的错误.首先在SD卡上放一张照片,分辨率为(3776 X 2520),大小为3.88MB,是我自己用相机拍的一张照片.应用的布局很简单,一个Button一个ImageView, ...

  9. Laravel 学习笔记之 Composer 自动加载

    说明:本文主要以Laravel的容器类Container为例做简单说明Composer的自动加载机制. Composer的自动加载机制 1.初始化一个composer项目 在一个空目录下compose ...

随机推荐

  1. Vue 动态参数

    v-on和v-bind可以动态绑定一个参数,用[]来绑定一个可以改变的值. <li v-on:[event]="print"></li> <scrip ...

  2. 2300+字!在不同系统上安装Docker!看这一篇文章就够了

    辰哥准备出一期在Docker跑Python项目的技术文,比如在Docker跑Django或者Flask的网站.跑爬虫程序等等. 在Docker跑Python程序的时候不会太过于细去讲解Docker的基 ...

  3. 『心善渊』Selenium3.0基础 — 22、使用浏览器加载项配置实现用户免登陆

    目录 1.浏览器的加载项配置 2.加载Firefox配置 3.加载Chrome配置 1.浏览器的加载项配置 在很多情况下,我们在登录网站的时候,浏览器都会弹出一个是否保存登录账号的信息.如果我们选择保 ...

  4. mybatis源码简单分析

    mybatis入门介绍 /** * 1. 接口式编程 * 原生: Dao =====> DaoImpl * mybatis : Mapper =====> xxxMapper * 2. S ...

  5. Parrot os 安装vmtools

    1.更新源(这步个人觉得官方源还可以,没网上说的那么慢) vim /etc/apt/sources.list.d/parrot.list linux命令 ,按i进入修改模式,修改结束,之后先按esc, ...

  6. wireshark 调试 https/http2和grpc流量

    本文浏览器以 Chrom 为例 平常需要抓包的场景比较少,记录一下防止下次忘记配置 1. 解析 TLS 在本地创建用于保存 ssl logfile 的文件(文件可以存放到任意位置), 并添加到环境变量 ...

  7. python numpy 数据集合操作函数

    arrarray([0, 1, 2, 3, 4, 5, 6, 7, 8, 9])arr1array([0, 1, 2, 3, 4])np.intersect1d(arr,arr1)#计算数组ARR A ...

  8. 家庭账本开发day09

    编写数据表格的编辑操作,大体思路和删除操作一样 点击按钮,弹出修改项目,从父窗口获取已有的值赋给 弹出的子窗口中相应的值,在子窗口中点击提交,ajax请求 servlet修改.成功后重载表格,或者up ...

  9. Python3.7 lxml引入etree

    用xml代替lxml,Python3.7中已经没有etree这个模块了 import xml.etree.ElementTree as etree from lxml import etree 这种方 ...

  10. Java基础00-集合基础15

    1. 集合基础 1.1 集合概述 1.2 ArrayList构造方法和添加方法 代码示例: 想通过指定索引添加到最后一个位置的话就填写,现有索引的+1,比如3个索引就是4.如果在指定索引的位置添加没有 ...