LAB1 啓動操作系統

從機器上電到運行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由三部分組成:

  • 帶有加載信息的文件頭
  • 程序段表
  • 幾個程序段

大致如下圖所示:
avatar

這裏使用objdump -x obj/kern/kernel查看JOS內核的程序段表和所有段信息:
avatar

VMA即鏈接地址,這個段希望被存放到的邏輯地址。

LMA即加載地址,這個段被加載到內存中後所在的物理地址。

BootLoader首先將ELF的header從外存加載到內存上,
然後根據程序段表依次將需要加載的程序段從外存加載到內存上。
最後將CPU的CS:IP設置成操作系統內核的入口位置,操作系統內核正式啓動。

內核準備就緒

在JOS實驗中,JOS內核的入口點的源代碼是/kern/entry.S的39行,從39行到77行這部分先開啓了paging,後初始化了堆棧。然後轉移到C語言寫的i386_init。

內核的內存機制

內核的設計者希望爲用戶提供儘量大的內存空間,但是RAM的物理空間大小就那麼大,怎麼辦,段頁內存機制。

avatar

前面在讀程序段表的時候有兩個屬性,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

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章