从机器上电到运行OS发生了什么?

在电脑主板上有一个Flash块,存放了BIOS的可执行代码。它是ROM,断电不会丢掉数据。在机器上电的时候,CPU要求内存控制器从0地址读取数据(程序第一条指令)的时候,内存控制器去主板上的BIOS所在ROM读取数据,此时CPU运行着BIOS。这里BIOS主要做了以下3个任务:

  1. 检测存在的硬件,并测试其是否正常工作。
  2. 初始化显卡、显存,检验视频信号和同步信号,对显示器接口进行测试。
  3. 根据配置选择某个外存(U盘、CD-ROM、硬盘这些)作为启动,将其第一个扇区(BootLoader默认在存储器的第一个扇区)加载到内存上某固定区段,然后设置CPU的CS:IP寄存器指向这个内存区域的起点。此时CPU运行着BootLoader。

在JOS实验中, BootLoader的源代码是boot/boot.S和boot/main.c。经过编译链接得到ELF格式的二进制文件obj/boot/boot。这便是存放在0号扇区里的BootLoader。

BootLoader会完成两个主要功能:

  1. BootLoader将处理器从实模式转换为保护模式。
  2. BootLoader使用x86特定的IO指令直接访问IDE磁盘设备寄存器,从外存加载内核(也就是OS)到内存上,并设置CPU的CS:IP寄存器指向这个内存区域的起点,此时CPU正式开始运行操作系统。

Questions

At what point does the processor start executing 32-bit code? What exactly causes the switch from 16- to 32-bit mode?

在boot/boot.S中,计算机首先工作于16bit工作模式(实模式),当运行完 " ljmp $PROT_MODE_CSEG, $protcseg "语句后,正式进入32位工作模式(保护模式)。

What is the last instruction of the boot loader executed, and what is the first instruction of the kernel it just loaded?

  • bootmain子程序的最后一条语句((void (*)(void)) (ELFHDR->e_entry))();,即跳转到操作系统内核程序的起始指令处。
  • 第一条指令位于/kern/entry.S。为第一句movw $0x1234, 0x472

How does the boot loader decide how many sectors it must read in order to fetch the entire kernel from disk? Where does it find this information?

  • 操作系统文件中的Program Header Table存储了操作系统一共有哪些段,每个段有多少扇区等信息。每个表项对应操作系统一个段。找到这个表后即可确定操作系统内核占用了多少个扇区。
  • 操作系统内核映像文件的ELF头部信息记录了这个表的存储位置。

BootLoader加载操作系统内核的详细过程

在JOS实验中,操作系统内核最后编译得到的是一个二进制映像文件obj/kern/kernel,这个文件就是UNIX标准下的ELF格式文件。

在JOS实验中,可以简单地认为obj/kern/kernel由三部分组成:

  • 带有加载信息的文件头
  • 程序段表
  • 几个程序段

大致如下图所示:

这里使用objdump -x obj/kern/kernel查看JOS内核的程序段表和所有段信息:

VMA即链接地址,这个段希望被存放到的逻辑地址。

LMA即加载地址,这个段被加载到内存中后所在的物理地址。

BootLoader首先将ELF的header从外存加载到内存上,

然后根据程序段表依次将需要加载的程序段从外存加载到内存上。

最后将CPU的CS:IP设置成操作系统内核的入口位置,操作系统内核正式启动。

内核准备就绪

在JOS实验中,JOS内核的入口点的源代码是/kern/entry.S的39行,从39行到77行这部分先开启了paging,后初始化了堆栈。然后转移到C语言写的i386_init。

内核的内存机制

内核的设计者希望为用户提供尽量大的内存空间,但是RAM的物理空间大小就那么大,怎么办,段页内存机制。

前面在读程序段表的时候有两个属性,VMA和LMA。LMA是提供给BootLoader的,BootLoader根据LMA将内核的段们加载到内存的指定位置(就是段的LMA)。 VMA是提供给内核看的。

从软件视角(内核的安排设计)的内存(虚拟内存)上看,kernel被加载到高位地址空间上,低位地址空间留给上层应用使用.堆栈内存就在这里.

在JOS实验中,我们使用GDB的si从内核入口0x10000C开始调试,会碰到一条指令movl %eax, %cr0,这条指令打开paging,从而支持虚拟地址。

Problems / 动手实现printf格式化输出到屏幕

  1. Explain the interface between printf.c and console.c. Specifically, what function does console.c export? How is this function used by printf.c?

console.c中实现了一些基础显示函数,供外部使用. printf.c中的cprintf()实现依赖于vcprintf()的实现,vcprintf()的实现依赖于putch()的实现,putch()的实现依赖于console.c提供的cputchar().

  1. Explain the following from console.c:
1      if (crt_pos >= CRT_SIZE) {
2 int i;
3 memmove(crt_buf, crt_buf + CRT_COLS, (CRT_SIZE - CRT_COLS) * sizeof(uint16_t));
4 for (i = CRT_SIZE - CRT_COLS; i < CRT_SIZE; i++)
5 crt_buf[i] = 0x0700 | ' ';
6 crt_pos -= CRT_COLS;
7 }

考虑上下文的变量声明,

变量crt_buf: 一个字符数组缓冲区,里面存放着要显示到屏幕上的字符.

变量crt_pos: 当前最后一个字符显示在屏幕上的位置.

给出的代码是cga_putc的中间部分,cga_putc的上部分是根据字符值int c来判断到底要显示成什么样子. cpga_putc的下部分则把决定要显示的字符显示到屏幕指定位置.

  1. For the following questions you might wish to consult the notes for Lecture 2. These notes cover GCC's calling convention on the x86.

    Trace the execution of the following code step-by-step:
int x = 1, y = 3, z = 4;
cprintf("x %d, y %x, z %d\n", x, y, z);
  • In the call to cprintf(), to what does fmt point? To what does ap point?
  • List (in order of execution) each call to cons_putc, va_arg, and vcprintf. For cons_putc, list its argument as well. For va_arg, list what ap points to before and after the call. For vcprintf list the values of its two arguments.
  1. Run the following code.
    unsigned int i = 0x00646c72;
cprintf("H%x Wo%s", 57616, &i);

What is the output? Explain how this output is arrived at in the step-by-step manner of the previous exercise. Here's an ASCII table that maps bytes to characters.

The output depends on that fact that the x86 is little-endian. If the x86 were instead big-endian what would you set i to in order to yield the same output? Would you need to change 57616 to a different value?

Here's a description of little- and big-endian and a more whimsical description.

(PASS)

  1. In the following code, what is going to be printed after 'y='? (note: the answer is not a specific value.) Why does this happen?

    cprintf("x=%d y=%d", 3);

(PASS)

  1. Let's say that GCC changed its calling convention so that it pushed arguments on the stack in declaration order, so that the last argument is pushed last. How would you have to change cprintf or its interface so that it would still be possible to pass it a variable number of arguments?

The stack

kernel从哪条指令开始初始化堆栈?

kern/entry.S中,

call i386_init指令前的这两句:

  movl    $0x0,%ebp            # nuke frame pointer
  movl $(bootstacktop),%esp

JOS堆栈位于内存的什么位置?

kern/entry.S中的这几句初始化了JOS内核的分页机制:

1   movl    $(RELOC(entry_pgdir)), %eax
2   movl %eax, %cr3
3   movl %cr0, %eax
4   orl $(CR0_PE|CR0_PG|CR0_WP), %eax
5   movl %eax, %cr0

第1、2句,把entry_pgdir页表的起始地址送入%eax寄存器和%cr3寄存器

第3、4、5句,修改cr0寄存器的值,把cr0的PE位,PG位, WP位都置位1。其中PE位是启用保护标识位,如果被置1代表将会运行在保护模式下。PG位是分页标识位,如果这一位被置1,则代表开启了分页机制。WP位是写保护标识,如果被置位为1,则处理器会禁止超级用户程序向用户级只读页面执行写操作。

紧接着的下面这两句

1     mov    $relocated, %eax
2 jmp *%eax

把当前运行程序的地址空间提高到[0xf0000000-0xf0400000]范围内。

然后

1     movl    $0x0,%ebp            # nuke frame pointer
2 movl $(bootstacktop),%esp
3 call i386_init

在entry.S的末尾还定义了一个值,bootstack。注意,在数据段中定义栈顶bootstacktop之前,首先分配了KSTKSIZE这么多的存储空间,专门用于堆栈,这个KSTKSIZE = 8 * PGSIZE = 8 * 4096 = 32KB。所以用于堆栈的地址空间为 0xf0108000-0xf0110000,其中栈顶指针指向0xf0110000. 那么这个堆栈实际坐落在内存的 0x00108000-0x00110000物理地址空间中。

Reference

LAB1 启动操作系统的更多相关文章

  1. LinuxMint18配置Grub2默认启动操作系统

    ---恢复内容开始--- 之前电脑里面装了太多系统太乱了,刚好假期回家有一些空闲时间于是开始了重装计划. 现在重新弄好了,有两个系统,一个是Windows10,另一个是LinuxMint18,但是我平 ...

  2. 关于U盘启动操作系统《30天自制操作系统》

    原本的启动是从img启动的,并且这个img是用FAT12文件系统进行格式化的(详细去搜索FAT12文件格式,这里给大家推荐一篇http://www.doc88.com/p-646605198560.h ...

  3. 清华大学OS操作系统实验lab1练习知识点汇总

    lab1知识点汇总 还是有很多问题,但是我觉得我需要在查看更多资料后回来再理解,学这个也学了一周了,看了大量的资料...还是它们自己的80386手册和lab的指导手册觉得最准确,现在我就把这部分知识做 ...

  4. 自制操作系统 (三) 从启动区执行操作系统并进入C世界

    qq:992591601 欢迎交流 2016.04.03 2016.05.31 2016.06.29 这一章是有些复杂的,我不太懂作者为什么要把这么多内容都放进一天. 1读入了十个柱面 2从启动区执行 ...

  5. Linux操作系统之更改启动菜单的背景图实战案例

    Linux操作系统之更改启动菜单的背景图实战案例 作者:尹正杰 版权声明:原创作品,谢绝转载!否则将追究法律责任. 一.制作图像并上传到服务器 1>.使用window 10操作系统自带的画图工具 ...

  6. [OS] 操作系统课程(三)

    工具 源码阅读:understand 源码文档自动生成工具:Doxygen 编译环境:gcc 运行环境:x86机器或QEMU 调试工具:QEMU+(GDB or IDE) IDE:Eclipse-CD ...

  7. Linux启动报错missing operating system

    用UltraISO制作了一个Red Hat Enterprise Linux Server release 5.7系统的U盘启动盘,然后在一台PC上安装,由于安装过程中在干别的事情,有些选项没有细看. ...

  8. 计算机启动boot

    原创博文:转载请标明出处:http://www.cnblogs.com/zxouxuewei 零.boot的含义 先问一个问题,"启动"用英语怎么说? 回答是boot.可是,boo ...

  9. DIY操作系统(一)

    先说几句题外话: 回想第一次看到<30天自制操作系统>这本书时,就被这快餐般的标题深深吸引了,我无法想象如此复杂有内涵的内容能在30天就弄出来,直到我花了一个多月看到这本书的第9天时,我放 ...

随机推荐

  1. 2016 最新的 树莓派3 Raspberry Pi 3 上手评测 图解教程 新手必看!(VNC 安装,启动,关闭)

    1.png . 官方教程: INSTALLING OPERATING SYSTEM IMAGES: https://www.raspberrypi.org/documentation/installa ...

  2. React 组件之间通信 All in One

    React 组件之间通信 All in One 组件间通信 1. 父子组件之间通信 props 2. 兄弟组件之间通信 3. 跨多层级的组件之间通信 Context API https://react ...

  3. MySQL 8.x

    MySQL 8.x SQL & NoSQL $ mysql --version # mysql Ver 8.0.21 for osx10.15 on x86_64 (Homebrew) # M ...

  4. JavaScript Learning Paths(ES5/ES6/ES-Next)

    JavaScript Learning Paths(ES5/ES6/ES-Next) JavaScript Expert refs https://developer.mozilla.org/en-U ...

  5. text image & 思维脑图 & xmind

    text image & 思维脑图 text-image 图片读 rgb 值->灰度值->字符; rgb->灰度值的公式 google 一下就有,灰度值传字符部分,从灰度值从 ...

  6. node mailer & email bot

    node mailer & email bot email https://nodemailer.com/about/ https://github.com/nodemailer/nodema ...

  7. how to open a terminal in finder folder of macOS

    how to open a terminal in finder folder of macOS shit service demo refs https://lifehacker.com/launc ...

  8. RocketMq灰皮书(二)------本地部署启动MQ

    RocketMq灰皮书(二)------本地部署启动MQ Windows10本地部署RocketMQ 在上一篇文章中,我们对rocket的几个基本概念进行了介绍,也了解了业内几大消息中间件的区别.在本 ...

  9. HQYJ嵌入式学习笔记——C语言复习day2

    1.计算机的数值表示 数值类型和非数值类型 二进制 0,1 (0b1001) 八进制 0~7   (0146) 十进制 0~9 十六进制 0~f (0x3f) 八进制转二进制-->一位八进制数换 ...

  10. 谈一下hashMap中put是如何实现的?

    源码: Hash(key):计算出key的hash值. put方法详解: 1.如果table数组为null或者table数组的长度为0,则调用resize()方法扩容并返回table数组.数组的长度为 ...