ucore操做系統學習筆記(一) ucore lab1系統啓動流程分析

1、ucore操做系統介紹

  操做系統做爲一個基礎系統軟件,對下控制硬件(cpu、內存、磁盤網卡等外設),屏蔽了底層複雜多樣的硬件差別;對上則提供封裝良好的應用程序接口,簡化應用程序開發者的使用難度。站在應用程序開發人員的角度來看,平常開發中常見的各類關於併發、I/O、程序通訊的問題等都和操做系統相關,所以必定程度上了解底層的操做系統工做原理是有必要的。javascript

  另外一方面,因爲操做系統自身功能的複雜性,總體設計通常會有一個好的模塊化架構;操做系統做爲基礎服務,對性能效率的要求也很高,底層會用到許多關於數據結構和算法相關的知識。若是仔細的研究一個操做系統的源碼,既能夠學習設計一個複雜軟件的架構知識,又能夠看到偏理論的數據結構和算法知識是如何被運用在實際場景中的,更深入的體會不一樣數據結構、算法在特定場景下的性能差別。html

  然而對於初學者而言,學習操做系統並非一件輕鬆的事情。操做系統理論的學習過於抽象,每每看了就忘。而主流商業操做系統動輒十萬、百萬級的內核源碼也令想要一窺究竟的普通人望而卻步。對於一個已經迭代發展至關一段時間的系統,我的認爲好的學習方法不是從最新的,相對複雜的版本開始瞭解,而是從最初始的,較爲簡單的版本起步,研究其是如何一步步優化、迭代至現在的這個版本。通過無數人迭代、優化的最新版本linux內核當然無比複雜,但90年代早期發佈的版本卻簡單太多,更容易理解和學習,在掌握了相對簡單的早期版本後,能下降後續學習更復雜的版本的難度。java

  對於操做系統的學習而言,有很多大牛都出版了關於如何實現一個簡易版操做系統的書籍,例如《Orange'S:一個操做系統的實現》《30天自制操做系統》等等。很多大學也開始對操做系統的課程進行改革,再也不是枯燥的灌輸理論知識點,而是嘗試着讓學生親自動手實現一個demo操做系統,加深對知識內容的理解。其中麻省理工大學的公開課MIT 6.828是出品較早,久負盛名的。node

  本系列博客的主角,是由清華大學出品的操做系統網上公開課,其中的實驗課程就須要學生經過一個個的迭代實驗,逐步實現一個名爲ucore的操做系統。其實驗指導書上對ucore os的評價是"麻雀雖小,五臟俱全",很是適合操做系統初學者進行學習。linux

ucore項目github倉庫連接:https://github.com/chyyuu/os_kernel_lab (master分支)git

ucore實驗指導書連接:https://chyyuu.gitbooks.io/ucore_os_docs/content/程序員

ucore公開課視頻連接(學堂在線):https://www.xuetangx.com/course/THU08091000267github

2、學習ucore所須要的準備知識

  工欲善其事,必先利其器。操做系統做爲一門綜合性的課程,須要掌握必定的前置基礎知識才能順利的完成ucore操做系統的學習。算法

1.C語言  

  ucore內核中絕大多數的功能都是使用C語言實現的,掌握C語言是學習ucore的基礎。除了熟練掌握C語言的基礎語法知識以外,最好能對宏、指針有必定了解。spring

  推薦學習書籍:《C primer》

2.x86彙編語言(32位)

  ucore內核是運行在80386這一32位x86架構的cpu之上的。雖然ucore的內核主要是由C語言實現的,但因爲操做系統是貼近底層,與硬件有頻繁交互的系統程序。在ucore中,CPU加電後的內核引導程序以及中斷、與特定硬件交互時的部分都須要經過x86彙編來實現。

  若是是彙編語言的初學者,強烈建議先學習8086彙編語言,創建一個對底層CPU工做原理的基本知識結構後,再學習更爲複雜的32位彙編。  

  須要注意的一點是,ucore中的x86彙編代碼是以AT&T格式編寫的,和Intel格式的x86彙編雖然邏輯等價,但寫法上有較大差別。對於經過Intel格式入門彙編的初學者來講,須要稍微適應一下。

  推薦學習書籍:《彙編語言》王爽著(8086彙編)、《X86彙編從實模式到保護模式》(80386彙編)

3.80386CPU工做原理

  彙編語言對應的是機器碼,其中的不少功能都與CPU硬件緊密關聯。80386的分頁、中斷,特權級等功能在ucore操做系統的實現中扮演了重要的角色。若是對80386的工做原理了解不夠,在閱讀ucore與之相關的源碼時會有困難。

  推薦學習書籍:《X86彙編從實模式到保護模式》、《intel 80386參考手冊》

4.C語言機器實現底層(與x86彙編的關聯)

  在ucore中常常會出現c和彙編代碼互相調用的地方。要想理解其工做原理,須要去理解C語言編譯後生成的底層機器指令(彙編),統一的站在彙編語言的角度來思考。你須要瞭解C中的結構體、數組等數據結構在內存中的是如何排布的,C中的指針操做是如何被轉換成各類內存尋址指令的,C中的函數調用與返回過程當中,因爲參數壓棧出棧等等棧上數據的是如何變化的等等。

  其實C中的指針等比較難理解的概念,在有了必定的彙編語言基礎後會理解的更加透徹。C中的指針和結構體使得程序員沒必要再去思考彙編層面中繁瑣的內存訪問偏移量計算,通通的交由編譯器處理,C程序員的腦力獲得解放,可以站在更高的抽象層面去思考更復雜的業務問題。

  有了C語言和彙編的基礎後,能夠經過編寫簡單的C程序,查看其反彙編代碼來進行相關的學習(經過32位的編譯器)。

  推薦學習書籍:《深刻理解計算機系統》(Computer Systems A Programmer's perspective  CSAPP)

5.基本的數據結構知識

  ucore中所涉及到的通用數據結構並很少,只須要對雙向鏈表和哈希表有必定了解便可。

  雖然在後續的實驗中參考linux的實現引入了紅黑樹等複雜數據結構優化一些算法的實現,但並不涉及核心流程,若是不是學有餘力,在ucore的學習過程當中當作一個黑盒子去看待就行。

  推薦學習公開課視頻: 清華大學出品的數據結構公開課(鄧俊輝)

  初學者在學習ucore的過程當中碰到的一個很大的困難就是lab1做爲最初始的一個實驗,爲了搭建起一個能實際運行的系統,一會兒引入了不少內容。這裏面既有生成img鏡像的功能,也有bootloader加載內核的功能,還有許多與硬件交互的代碼邏輯,這些信息鋪天蓋地的涌來,容易勸退初學者。當時的我就差點被勸退了,但因爲本身強烈的好奇心以及實驗指導書首頁的提醒:「lab1和lab2比較困難,有些同窗因爲畏難而止步與此,很惋惜。經過lab1和lab2後,對計算機原理中的中斷、段頁表機制、特權級等的理解會更深刻,等會有等同於打通了任督二脈,後面的實驗將一片坦途。」,最終仍是堅持了下來。實際的感受也確實如此,若是能理解lab一、lab2中諸多硬件相關的知識和C內核實現中不少巧妙但晦澀的指針、宏的用法,後續的實驗將簡單不少。

  在整個ucore的學習過程當中,除了公開課的視頻和資料外,網上不少關於ucore學習的博客也給了我很大幫助,因此我也但願能經過博客分享本身的學習心得,幫助到更多對操做系統、ucore感興趣的人。若是實驗中碰到不懂的地方,多經過關鍵字去搜索相關資料以及網上關於ucore學習的博客可以起到事半功倍的做用。

3、ucore操做系統lab1 系統加載啓動過程分析

  下面進入正題,開始分析ucore在實驗課程lab1中的內容:ucore系統加載啓動過程的分析。

  ucore的lab1項目結構從總體來看,按照執行的流程順序分爲三部分:img磁盤映像的生成引導內核的bootloader程序ucore內核的初始化

ucore的img磁盤映像生成

  ucore總體是一個makefile項目。經過make指令,解析項目中的makefile文件後會生成一個ucore.img磁盤映像。(lab1的實驗課視頻演示中能夠詳細的看到構建的全過程)

  這個磁盤映像主要由兩大部分組成:位於第一個扇區即引導扇區的ucore bootloader程序,以及第二個扇區開始日後的ucore kernel內核程序。

  80386CPU在加電啓動之初,會執行固化在BIOS中的程序。BIOS因爲容量有限,自身不提供加載完整操做系統內核的功能,而是約定好會讀取磁盤中第一個扇區(引導扇區)中的內容,將其加載至內存地址空間0x7c00處,在加載完畢後,令CS:IP指向0x7c00,跳轉執行引導扇區中的引導程序的第一條指令。爲了不所加載的磁盤引導扇區是一個無效扇區(可能引導扇區中的內容就是空的或是亂碼),要求512字節大小的扇區在其最後兩字節必須是0x55AA(其他的空餘空間能夠用0填充),不然沒法經過BIOS的校驗,引導失敗。

  ucore的makefile文件中,將項目中位於boot文件夾下的程序放入了ucore的第一個扇區,在makefile的"#create bootblock"註釋開頭的段中有所體現。其中調用了/tool/sign.c來生成寫入一個合法的引導扇區。

  因爲項目中的makefile文件中有很多複雜腳本,若是對makefile工做原理不熟悉,在ucore的學習中能夠下降要求,大體瞭解一下每一部分的代碼大概在幹什麼便可,沒必要強求理解每一行,避免在學習之初就產生太強的挫敗感。

若是想對經過makefile是如何一步步完整的生成磁盤映像感興趣,能夠參考如下內容:

  1. lab1項目目錄下的report.md實驗報告示例

  2. https://www.jianshu.com/p/2f95d38afa1d  其中對lab1中makefile的分析很是詳細

ucore的bootloader引導程序

  當BIOS加載完引導扇區的內容至內存後,CPU便會跳轉到0x7c00執行命令,此時CPU的控制權便交給了ucore的引導程序bootloader。引導程序主體由boot文件夾下的bootasm.S和bootasm.c共同組成,其中bootasm.S因爲構建時靠前,是先執行的。

令CPU進入保護模式

  bootasm.S的主要工做就是令80386從加電時默認的實模式切換到32位保護模式,經過代碼的註釋能夠看到,因爲一些歷史緣由要令80386正確的進入保護模式仍是有點小麻煩的(並非簡單的調整一個開關位就行)。在《X86彙編語言 從實模式到保護模式》一書中對此有更加詳細的介紹。

  在經過彙編指令完成80386從實模式至保護模式的切換後,經過call bootmain指令,跳轉至bootmain.c中的bootmain函數完成引導內核的工做。

bootasm.S:

  1 #include <asm.h>
  2 
  3 # Start the CPU: switch to 32-bit protected mode, jump into C.
  4 # The BIOS loads this code from the first sector of the hard disk into
  5 # memory at physical address 0x7c00 and starts executing in real mode
  6 # with %cs=0 %ip=7c00.
  7 
  8 # 80386CPU爲了兼容8086程序,最開始啓動時是以16位的實模式進行工做的
  9 # 生成img磁盤映像時,bootasm.S中的引導代碼將會被放在引導扇區
 10 # 80386CPU加電啓動後,會執行BIOS中的默認引導程序,BIOS引導程序會將引導扇區中(第一個磁盤塊)的內容讀入內存,並放置在0x7C00(16位)/0x00007c00(32位)處
 11 # 隨後CPU會跳轉到0x7c00處開始第一條指令的執行,即bootasm.S的第一條指令(start:)
 12 
 13 .set PROT_MODE_CSEG,        0x8                     # kernel code segment selector
 14 .set PROT_MODE_DSEG,        0x10                    # kernel data segment selector
 15 .set CR0_PE_ON,             0x1                     # protected mode enable flag
 16 
 17 # start address should be 0:7c00, in real mode, the beginning address of the running bootloader
 18 .globl start
 19 start:
 20 .code16                                             # Assemble for 16-bit mode
 21     cli                                             # Disable interrupts
 22     cld                                             # String operations increment
 23 
 24     # Set up the important data segment registers (DS, ES, SS).
 25     xorw %ax, %ax                                   # Segment number zero
 26     movw %ax, %ds                                   # -> Data Segment
 27     movw %ax, %es                                   # -> Extra Segment
 28     movw %ax, %ss                                   # -> Stack Segment
 29 
 30     # Enable A20:
 31     #  For backwards compatibility with the earliest PCs, physical
 32     #  address line 20 is tied low, so that addresses higher than
 33     #  1MB wrap around to zero by default. This code undoes this.
 34 
 35     # 爲了進入32位保護模式,必須先開啓A20(第21位內存訪問線),不然在32位尋址模式下給出的內存地址第21位始終爲0,形成錯誤
 36     # 爲何須要特地開啓A20總線?
 37     # 在早期的8086CPU中,內存總線是20位的,由高16位的段基址和低16位的段內偏移共同構成一個20位的內存地址
 38     # 但事實上在段基址和段內偏移比較大的狀況下,其實際得出的結果是超過了20位的(例如0xFFFF段基址 <<< 4 + 0xFFFF段內偏移 > 0xFFFFF),出現了溢出
 39     # 而8086中對這種溢出是兼容的,這種溢出在8086上會體現爲繞回0x00000低端
 40     # 「程序員,你是知道的,他們喜歡鑽研,更喜歡利用硬件的某些特性來展現本身的技術,很難說在當年有多少程序在依賴這個迴繞特性工做着」
 41     # 摘自《X86彙編語言 從實模式到保護模式》 11.5 關於第21條地址線A20的問題
 42     # 到了更新版的80286時代,24位的內存總線,若是不默認關閉A20總線,那麼就沒法兼容使用迴繞特性的8086程序了
 43     # 而80386做爲80286的後一代,也繼承了80286這一特性
 44 seta20.1:
 45     inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
 46     testb $0x2, %al
 47     jnz seta20.1
 48 
 49     movb $0xd1, %al                                 # 0xd1 -> port 0x64
 50     outb %al, $0x64                                 # 0xd1 means: write data to 8042’s P2 port
 51 
 52 seta20.2:
 53     inb $0x64, %al                                  # Wait for not busy(8042 input buffer empty).
 54     testb $0x2, %al
 55     jnz seta20.2
 56 
 57     movb $0xdf, %al                                 # 0xdf -> port 0x60
 58     outb %al, $0x60                                 # 0xdf = 11011111, means set P2’s A20 bit(the 1 bit) to 1
 59 
 60     # Switch from real to protected mode, using a bootstrap GDT
 61     # and segment translation that makes virtual addresses
 62     # identical to physical addresses, so that the
 63     # effective memory map does not change during the switch.
 64     # 設置GDT,修改CRO寄存器中的保護模式容許位,進入保護模式
 65     lgdt gdtdesc
 66     movl %cr0, %eax
 67     orl $CR0_PE_ON, %eax
 68     movl %eax, %cr0
 69 
 70     # Jump to next instruction, but in 32-bit code segment.
 71     # Switches processor into 32-bit mode.
 72     # 經過一個遠跳轉指令指向protcseg處的指令,令CPU清空以前在實模式下保存在流水線中的指令(當前處於保護模式下執行實模式的指令會出現各類問題)
 73     ljmp $PROT_MODE_CSEG, $protcseg
 74 
 75 # 下面的都是X86-32的彙編程序
 76 .code32                                             # Assemble for 32-bit mode
 77 protcseg:
 78     # Set up the protected-mode data segment registers
 79     # 跳轉至保護模式後,須要刷新數據段寄存器(由於引入了特權級保護,避免數據段寄存器以前的值不對而出現漏洞)
 80     movw $PROT_MODE_DSEG, %ax                       # Our data segment selector
 81     movw %ax, %ds                                   # -> DS: Data Segment
 82     movw %ax, %es                                   # -> ES: Extra Segment
 83     movw %ax, %fs                                   # -> FS
 84     movw %ax, %gs                                   # -> GS
 85     movw %ax, %ss                                   # -> SS: Stack Segment
 86 
 87     # Set up the stack pointer and call into C. The stack region is from 0--start(0x7c00)
 88     # 設置棧段寄存器 棧基址0x0,棧頂指針指向start段所在位置(0x7c00)
 89     movl $0x0, %ebp
 90     movl $start, %esp
 91     # 調用跳轉至bootmain.c中的bootmain函數,完成內核的引導
 92     call bootmain
 93 
 94     # If bootmain returns (it shouldn’t), loop.
 95     # 自旋死循環(但若是引導程序和內核實現正確,bootmain函數將永遠不會返回並執行至此。由於操做系統內核自己就是經過自旋循環常駐內存的)
 96 spin:
 97     jmp spin
 98 
 99 # Bootstrap GDT
100 .p2align 2                                          # force 4 byte alignment
101 # SEG_ASM是位於asm.h中的宏,用於構造GDT中的段描述符
102 # 按照GDT的約定,第一個爲NULL段。ucore採用的是平坦內存模型,因此代碼段和數據段在內核中均只存在一個。
103 gdt:
104     SEG_NULLASM                                     # null seg
105     SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)           # code seg for bootloader and kernel
106     SEG_ASM(STA_W, 0x0, 0xffffffff)                 # data seg for bootloader and kernel
107 
108 gdtdesc:
109     .word 0x17                                      # sizeof(gdt) - 1
110     .long gdt                                       # address gdt

bootloader引導加載內核

  bootloader引導程序是位於設備的第一個扇區,即引導扇區的,而ucore的內核程序則是從第二個磁盤扇區開始日後存放的。bootmain.c的任務就是將kernel內核部分從磁盤中讀出並載入內存,並將程序的控制流轉移至指定的內核入口處。

  ucore的內核文件在生成磁盤映像時是以ELF(Executable and linking format)格式保存的。ELF文件是Unix/Linux下通用的一種可執行文件,對於ELF的詳細介紹在《深刻理解計算機系統》的"連接"一章中有較爲詳細的介紹。

  要想完全的理解ELF格式的文件是如何被編譯器、連接器等工具生成的,須要對編譯原理相關的知識進行系統的學習,難度很大。所以在ucore的學習過程當中,若是不是很瞭解ELF,能夠簡單的理解爲ELF的文件頭中標識了一個可執行程序中包含了哪些部分,好比代碼段、數據段(只讀數據段、可讀寫數據段)、棧段等等,分別存儲在哪裏;並指明瞭須要爲這些段分配多少內存空間、須要被加載到內存的什麼地址(虛擬地址)等。

  ucore內核生成ELF文件的關鍵配置在/tools/kernel.ld中,能夠清楚的看到內核加載的.text代碼段基址爲0x100000,後面緊跟着各類類型的數據段等。

kernel.ld:

/* Simple linker script for the JOS kernel.
   See the GNU ld 'info' manual ("info ld") to learn the syntax. */

OUTPUT_FORMAT("elf32-i386", "elf32-i386", "elf32-i386")
OUTPUT_ARCH(i386)
ENTRY(kern_init)

SECTIONS {
    /* Load the kernel at this address: "." means the current address */
    . = 0x100000;

    .text : {
        *(.text .stub .text.* .gnu.linkonce.t.*)
    }

    PROVIDE(etext = .);    /* Define the 'etext' symbol to this value */

    .rodata : {
        *(.rodata .rodata.* .gnu.linkonce.r.*)
    }

    /* Include debugging information in kernel memory */
    .stab : {
        PROVIDE(__STAB_BEGIN__ = .);
        *(.stab);
        PROVIDE(__STAB_END__ = .);
        BYTE(0)        /* Force the linker to allocate space
                   for this section */
    }

    .stabstr : {
        PROVIDE(__STABSTR_BEGIN__ = .);
        *(.stabstr);
        PROVIDE(__STABSTR_END__ = .);
        BYTE(0)        /* Force the linker to allocate space
                   for this section */
    }

    /* Adjust the address for the data segment to the next page */
    . = ALIGN(0x1000);

    /* The data segment */
    .data : {
        *(.data)
    }

    PROVIDE(edata = .);

    .bss : {
        *(.bss)
    }

    PROVIDE(end = .);

    /DISCARD/ : {
        *(.eh_frame .note.GNU-stack)
    }
}

  在libs/elf.h中定義了兩個ELF相關的結構體,elfhdrproghdr,用於映射讀取出來的內核ELF頭信息。

elf.h:

#ifndef __LIBS_ELF_H__
#define __LIBS_ELF_H__

#include <defs.h>

#define ELF_MAGIC    0x464C457FU            // "\x7FELF" in little endian

/* file header */
struct elfhdr {
    uint32_t e_magic;     // must equal ELF_MAGIC
    uint8_t e_elf[12];
    uint16_t e_type;      // 1=relocatable, 2=executable, 3=shared object, 4=core image
    uint16_t e_machine;   // 3=x86, 4=68K, etc.
    uint32_t e_version;   // file version, always 1
    uint32_t e_entry;     // entry point if executable
    uint32_t e_phoff;     // file position of program header or 0
    uint32_t e_shoff;     // file position of section header or 0
    uint32_t e_flags;     // architecture-specific flags, usually 0
    uint16_t e_ehsize;    // size of this elf header
    uint16_t e_phentsize; // size of an entry in program header
    uint16_t e_phnum;     // number of entries in program header or 0
    uint16_t e_shentsize; // size of an entry in section header
    uint16_t e_shnum;     // number of entries in section header or 0
    uint16_t e_shstrndx;  // section number that contains section name strings
};

/* program section header */
struct proghdr {
    uint32_t p_type;   // loadable code or data, dynamic linking info,etc.
    uint32_t p_offset; // file offset of segment
    uint32_t p_va;     // virtual address to map segment
    uint32_t p_pa;     // physical address, not used
    uint32_t p_filesz; // size of segment in file
    uint32_t p_memsz;  // size of segment in memory (bigger if contains bss)
    uint32_t p_flags;  // read/write/execute bits
    uint32_t p_align;  // required alignment, invariably hardware page size
};

#endif /* !__LIBS_ELF_H__ */

bootmain.c:

#include <defs.h>
#include <x86.h>
#include <elf.h>

/* *********************************************************************
 * This a dirt simple boot loader, whose sole job is to boot
 * an ELF kernel image from the first IDE hard disk.
 *
 * DISK LAYOUT
 *  * This program(bootasm.S and bootmain.c) is the bootloader.
 *    It should be stored in the first sector of the disk.
 *         這個程序(bootasm.S and bootmain.c)是引導加載器程序,應該被保存在磁盤的第一個扇區
 *
 *  * The 2nd sector onward holds the kernel image.
 *         第二個扇區日後保存着內核映像
 *
 *  * The kernel image must be in ELF format.
 *         內核映像必須必須是ELF格式的
 *
 * BOOT UP STEPS
 *  * when the CPU boots it loads the BIOS into memory and executes it
 *
 *  * the BIOS intializes devices, sets of the interrupt routines, and
 *    reads the first sector of the boot device(e.g., hard-drive)
 *    into memory and jumps to it.
 *
 *  * Assuming this boot loader is stored in the first sector of the
 *    hard-drive, this code takes over...
 *
 *  * control starts in bootasm.S -- which sets up protected mode,
 *    and a stack so C code then run, then calls bootmain()
 *
 *  * bootmain() in this file takes over, reads in the kernel and jumps to it.
 * */
unsigned int    SECTSIZE  =      512 ;  // 一個磁盤扇區的大小爲512字節
struct elfhdr * ELFHDR    =      ((struct elfhdr *)0x10000) ;     // scratch space

/* waitdisk - wait for disk ready */
static void
waitdisk(void) {
    // 讀數據,當0x1f7不爲忙狀態時,能夠讀
    while ((inb(0x1F7) & 0xC0) != 0x40)
        /* do nothing */;
}

/* readsect - read a single sector at @secno into @dst */
// 讀取一個單獨的扇區(由@secno指定)到@dst指針指向的內存中
static void
readsect(void *dst, uint32_t secno) {
    // https://chyyuu.gitbooks.io/ucore_os_docs/content/lab1/lab1_3_2_3_dist_accessing.html
    // 實驗指導書lab1中的對ide硬盤的訪問中有詳細介紹

    // wait for disk to be ready
    waitdisk();

    // 磁盤讀取參數設置
    outb(0x1F2, 1);                         // count = 1
    outb(0x1F3, secno & 0xFF);
    outb(0x1F4, (secno >> 8) & 0xFF);
    outb(0x1F5, (secno >> 16) & 0xFF);
    outb(0x1F6, ((secno >> 24) & 0xF) | 0xE0);
    outb(0x1F7, 0x20);                      // cmd 0x20 - read sectors

    // wait for disk to be ready
    waitdisk();

    // read a sector
    insl(0x1F0, dst, SECTSIZE / 4);
}

/* *
 * readseg - read @count bytes at @offset from kernel into virtual address @va,
 * might copy more than asked.
 * */
static void
readseg(uintptr_t va, uint32_t count, uint32_t offset) {
    uintptr_t end_va = va + count;

    // round down to sector boundary
    va -= offset % SECTSIZE;

    // translate from bytes to sectors; kernel starts at sector 1
    // 計算出須要讀取的磁盤扇區號,因爲第1個扇區被bootloader佔據,kernel內核從第二個扇區開始(下標爲1),因此扇區號須要增長1
    uint32_t secno = (offset / SECTSIZE) + 1;

    // If this is too slow, we could read lots of sectors at a time.
    // We'd write more to memory than asked, but it doesn't matter --
    // we load in increasing order.
    // 循環往復,經過va指針的自增,一個一個扇區的循環讀取數據寫入va指向的內存區域
    for (; va < end_va; va += SECTSIZE, secno ++) {
        readsect((void *)va, secno);
    }
}

/* bootmain - the entry of bootloader */
void
bootmain(void) {
    // read the 1st page off disk
    // 從硬盤中讀取出內核文件ELF文件頭數據,存入ELFHDR指針指向的內存區域 (大小爲8個扇區)
    readseg((uintptr_t)ELFHDR, SECTSIZE * 8, 0);

    // is this a valid ELF? 校驗讀取出來的ELF文件頭的魔數值是否正確
    if (ELFHDR->e_magic != ELF_MAGIC) {
        goto bad;
    }

    struct proghdr *ph, *eph;

    // load each program segment (ignores ph flags)
    ph = (struct proghdr *)((uintptr_t)ELFHDR + ELFHDR->e_phoff); // 根據elf文件頭,得到程序段的起始
    eph = ph + ELFHDR->e_phnum; // 程序段起始指針(*ph)指針偏移程序段數目(ELFHDR->e_phnum) = 最後一段程序的頭部
    for (; ph < eph; ph ++) {
        // 循環往復,將各個程序段的內容讀取至指定的內存位置(ph->p_va)
        readseg(ph->p_va & 0xFFFFFF, ph->p_memsz, ph->p_offset);
    }

    // call the entry point from the ELF header
    // note: does not return
    // 經過函數指針的方式,跳轉至ELFHDR->e_entry指定的程序初始執行入口(即內核入口)
    // 在makefile的配置中,ELFHDR->e_entry指向的是kern/init/init.c中的kern_init函數 (kernel.ld中的ENTRY(kern_init))
    ((void (*)(void))(ELFHDR->e_entry & 0xFFFFFF))();

bad:
    // 跳轉至內核以後,不該該返回
    outw(0x8A00, 0x8A00);
    outw(0x8A00, 0x8E00);

    /* do nothing */
    while (1);
}

ucore內核結構分析

  在bootloader將ucore的kernel內核完整的加載至內存,並經過ELF文件頭中指定的entry入口跳轉至內核入口,即/kern/init.c中的kern_init函數。

  kern_init函數是內核的總控函數,內核中的各個組成部分都在kern_init函數中完成初始化。

內核總控函數kern_init 

  總控函數一方面負責初始化與各類硬件的交互(例如與顯卡、中斷控制器、定時器等),另外一方面初始化各類內核功能(好比初始化物理內存管理器、中斷描述符表IDT等),以後便經過一個自旋死循環令操做系統常駐內存,經過監聽各類中斷提供操做系統服務。

init.c(主體部分):

#include <defs.h>
#include <stdio.h>
#include <string.h>
#include <console.h>
#include <kdebug.h>
#include <picirq.h>
#include <trap.h>
#include <clock.h>
#include <intr.h>
#include <pmm.h>
#include <kmonitor.h>
void kern_init(void) __attribute__((noreturn));
void grade_backtrace(void);
static void lab1_switch_test(void);

/**
 * 內核入口 總控函數
 * */
void
kern_init(void){
    extern char edata[], end[];
    memset(edata, 0, end - edata);

    // 初始化控制檯(控制顯卡交互),只有設置好了對顯卡的控制後,std_out輸出的信息(例如cprintf)才能顯示在控制檯中
    cons_init();                // init the console

    const char *message = "(THU.CST) os is loading ...";
    cprintf("%s\n\n", message);

    print_kerninfo();

    grade_backtrace();

    // 初始化物理內存管理器
    pmm_init();                 // init physical memory management

    // 初始化中斷控制器
    pic_init();                 // init interrupt controller
    // 初始化中斷描述符表
    idt_init();                 // init interrupt descriptor table

    // 初始化定時芯片
    clock_init();               // init clock interrupt
    // 開中斷
    intr_enable();              // enable irq interrupt

    //LAB1: CAHLLENGE 1 If you try to do it, uncomment lab1_switch_test()
    // user/kernel mode switch test
    lab1_switch_test();

    /* do nothing */
    // 陷入死循環,避免內核程序退出。經過監聽中斷事件進行服務
    while (1);
}

  從kern_init函數的代碼中能夠看出,其依次完成了以下的幾個主要工做:

  1. cons_init  初始化控制檯(控制顯卡交互)

  2. pmm_init  初始化物理內存管理器(lab1中裏面暫時只是完成了GDT的從新設置,比較簡單。而在lab2的物理內存管理的實現中,pmm_init才成爲主角)

  3. pic_init 初始化中斷控制器(內部經過與8259A中斷控制器芯片進行交互,令ucore可以接收到來自硬件的各類中斷請求)

  4. idt_init 初始化中斷描述符表(在下面的中斷機制一節中詳細介紹)

  5. clock_init 初始化定時器(進行8253定時器的相關設置,將其設置爲10ms發起一次時鐘中斷)

  6. intr_enable 完成了內核結構的初始化後,開啓中斷,至此ucore內核正式開始運行

  在kern_init的內核初始化過程當中,涉及到的與顯卡、定時器等硬件交互的地方,要想深刻理解其工做原理,除了仔細閱讀ucore的代碼外,還需經過硬件手冊等資料熟悉不一樣硬件提供的交互接口,限於篇幅就再也不展開了。我的認爲這一部份內容並不屬於ucore的核心,若是不是特別感興趣,能夠將其暫時視爲一個黑盒子,理解大體工做原理便可。

ucore的中斷工做機制

  ucore在lab1中實現的一個很是重要的功能,就是創建了一個能夠工做的中斷服務框架。能夠說操做系統的工做是離不開硬件提供的中斷機制的。

  下面分析ucore的中斷機制是如何工做的。

80386中斷工做機制介紹

  前面提到過學習ucore的一個前提是對80386CPU的硬件工做原理有必定了解,這裏先回顧一下80386的中斷工做機制。

  1. 在80386中,爲了更好的支持對中斷服務例程的特權級保護,使用中斷描述符表代替了8086中的中斷向量表。和8086中斷向量表被固定在低位內存不同,80386CPU經過中斷描述符表寄存器IDTR來定位中斷描述符表IDT的位置,這給了操做系統的設計者必定的自主權。

  2. 80386在執行完每條指令後,都會檢查當前是否存在中斷請求。若是沒有發現中斷請求,則接着執行後續指令;若是發現存在中斷請求,則會根據中斷信號中給出的中斷類型碼,從中斷描述符表中查找到對應的中斷描述符,在中斷描述符中記錄了對應的中斷服務例程的入口。

  3. 隨後,CPU硬件會打斷當前控制流,在棧上壓入CS、EIP、EFLAGS等寄存器的內容(用於中斷服務例程的返回),跳轉到對應的中斷服務例程入口,進行中斷請求的處理。當中斷服務返回時,經過以前壓入棧中的CS,EIP等返回到以前被中斷請求打斷的控制流中,恢復現場,繼續運行。

  因爲80386中斷工做機制相對比較複雜,限於篇幅這裏的流程介紹省略了很多細節。若是對這一塊內容不熟悉的話須要經過實驗指導書或是有關資料進行學習,或者參考我以前寫的博客 80386學習(四) 80386中斷,裏面對此有較爲詳細的介紹。

ucore中斷功能的組成部分

  ucore的中斷工做機制大體能夠分爲如下幾個部分:

  1. IDT中斷描述符表的創建

  2. 中斷棧幀的生成

  3. 接收到中斷棧幀,經過對應的中斷服務例程進行處理

  4. 中斷服務例程處理完畢,中斷返回

IDT中斷描述符表的創建

  在ucore中,對於中斷描述符表IDT的初始化,是在kern_init總控函數中經過idt_init函數進行的。

 idt_init函數:

/* *
 * Interrupt descriptor table:
 *
 * Must be built at run time because shifted function addresses can't
 * be represented in relocation records.
 * */
static struct gatedesc idt[256] = {{0}};

static struct pseudodesc idt_pd = {
    sizeof(idt) - 1, (uintptr_t)idt
};

/* idt_init - initialize IDT to each of the entry points in kern/trap/vectors.S */
void
idt_init(void) {
     /* LAB1 YOUR CODE : STEP 2 */
     /* (1) Where are the entry addrs of each Interrupt Service Routine (ISR)?
      *     All ISR's entry addrs are stored in __vectors. where is uintptr_t __vectors[] ?
      *     __vectors[] is in kern/trap/vector.S which is produced by tools/vector.c
      *     (try "make" command in lab1, then you will find vector.S in kern/trap DIR)
      *     You can use  "extern uintptr_t __vectors[];" to define this extern variable which will be used later.
      * (2) Now you should setup the entries of ISR in Interrupt Description Table (IDT).
      *     Can you see idt[256] in this file? Yes, it's IDT! you can use SETGATE macro to setup each item of IDT
      * (3) After setup the contents of IDT, you will let CPU know where is the IDT by using 'lidt' instruction.
      *     You don't know the meaning of this instruction? just google it! and check the libs/x86.h to know more.
      *     Notice: the argument of lidt is idt_pd. try to find it!
      */
    extern uintptr_t __vectors[];
    int i;
    // 首先經過tools/vector.c經過程序生成/kern/trap/verctor.S,並在加載內核時對以前已經聲明的全局變量__vectors進行總體的賦值
    // __vectors數組中的每一項對應於中斷描述符的中斷服務例程的入口地址,在SETGATE宏的使用中能夠體現出來
    // 將__vectors數組中每一項關於中斷描述符的描述設置到下標相同的idt中,經過宏SETGATE構造出最終的中斷描述符結構
    for (i = 0; i < sizeof(idt) / sizeof(struct gatedesc); i ++) {
        // 遍歷idt數組,將其中的內容(中斷描述符)設置進IDT中斷描述符表中(默認的DPL特權級都是內核態DPL_KERNEL=0)
        SETGATE(idt[i], 0, GD_KTEXT, __vectors[i], DPL_KERNEL);
    }
    // set for switch from user to kernel
    // 用戶態與內核態的互相轉化是經過中斷實現的,單獨爲其一箇中斷描述符
    // 因爲須要容許用戶態的程序訪問使用該中斷,DPL特權級爲用戶態DPL_USER=3
    SETGATE(idt[T_SWITCH_TOK], 0, GD_KTEXT, __vectors[T_SWITCH_TOK], DPL_USER);
    // load the IDT 令IDTR中斷描述符表寄存器指向idt_pd,加載IDT
    // idt_pd結構體中的前16位爲描述符表的界限,pd_base指向以前完成了賦值操做的idt數組的起始位置
    lidt(&idt_pd);
}

  經過上述代碼的註釋能夠發現,在idt_init函數中,經過構建項目時自動生成的中斷描述符元信息數組__vectors,在一個循環中,經過SETGATE宏,將idt[i]中的每一項都賦值了一箇中斷描述符。 能夠看到中斷描述符和gatedesc門描述符結構體的對應關係。(C中結構體的字段在內存中排布的順序是按照定義的順序,從低位到高位的)

中斷門示意圖:

  

gatedesc結構體:

/* Gate descriptors for interrupts and traps */
struct gatedesc {
    unsigned gd_off_15_0 : 16;        // low 16 bits of offset in segment
    unsigned gd_ss : 16;            // segment selector
    unsigned gd_args : 5;            // # args, 0 for interrupt/trap gates
    unsigned gd_rsv1 : 3;            // reserved(should be zero I guess)
    unsigned gd_type : 4;            // type(STS_{TG,IG32,TG32})
    unsigned gd_s : 1;                // must be 0 (system)
    unsigned gd_dpl : 2;            // descriptor(meaning new) privilege level
    unsigned gd_p : 1;                // Present
    unsigned gd_off_31_16 : 16;        // high bits of offset in segment
};

SETGATE宏:

/* *
 * Set up a normal interrupt/trap gate descriptor
 *   - istrap: 1 for a trap (= exception) gate, 0 for an interrupt gate
 *   - sel: Code segment selector for interrupt/trap handler
 *   - off: Offset in code segment for interrupt/trap handler
 *   - dpl: Descriptor Privilege Level - the privilege level required
 *          for software to invoke this interrupt/trap gate explicitly
 *          using an int instruction.
 * */
#define SETGATE(gate, istrap, sel, off, dpl) {            \
    (gate).gd_off_15_0 = (uint32_t)(off) & 0xffff;        \
    (gate).gd_ss = (sel);                                \
    (gate).gd_args = 0;                                    \
    (gate).gd_rsv1 = 0;                                    \
    (gate).gd_type = (istrap) ? STS_TG32 : STS_IG32;    \
    (gate).gd_s = 0;                                    \
    (gate).gd_dpl = (dpl);                                \
    (gate).gd_p = 1;                                    \
    (gate).gd_off_31_16 = (uint32_t)(off) >> 16;        \
}

  最終構建出了一個48位的結構體pseudodesc,前16位標識着中斷描述符表的大小(pd_lim),後32位標識着中斷描述符表IDT的基址(pd_base)。

  若是熟悉80386中斷機制的話,就會發現這一結構與IDTR寄存器所須要的結構一致。在idt_init函數的最後,經過lidt函數執行彙編指令lidt,完成了對IDTR寄存器的賦值。至此,ucore的中斷描述符表設置完成。

/* Pseudo-descriptors used for LGDT, LLDT(not used) and LIDT instructions. */
struct pseudodesc {
    uint16_t pd_lim;        // Limit
    uint32_t pd_base;        // Base address
} __attribute__ ((packed));

中斷棧幀的生成

  下面接着分析,中斷描述符表裏到底存放了什麼數據結構,在ucore的中斷服務功能的創建中是如何發揮做用的?

  打開以前用於構造中斷描述符數組,爲vertors賦值的/kern/trap/vector.S,能夠看到其中的每一項的中斷服務例程的代碼都同樣。有的項首先push了一個0,有的沒有(下面會介紹爲何會有這種差別)。接下來將下標push壓入棧中,便統一jmp跳轉到了__alltraps處了。

vector.S:(很長,幾乎都是腳本生成的模板代碼)

# handler
.text
.globl __alltraps
.globl vector0
vector0:
  pushl $0
  pushl $0
  jmp __alltraps
.globl vector1
vector1:
  pushl $0
  pushl $1
  jmp __alltraps
.globl vector2
vector2:
  pushl $0
  pushl $2
  jmp __alltraps
.globl vector3
vector3:
  pushl $0
  pushl $3
  jmp __alltraps
.globl vector4
vector4:
  pushl $0
  pushl $4
  jmp __alltraps
.globl vector5
vector5:
  pushl $0
  pushl $5
  jmp __alltraps
.globl vector6
vector6:
  pushl $0
  pushl $6
  jmp __alltraps
.globl vector7
vector7:
  pushl $0
  pushl $7
  jmp __alltraps
.globl vector8
vector8:
  pushl $8
  jmp __alltraps
.globl vector9
vector9:
  pushl $9
  jmp __alltraps
.globl vector10
vector10:
  pushl $10
  jmp __alltraps
.globl vector11
vector11:
  pushl $11
  jmp __alltraps
.globl vector12
vector12:
  pushl $12
  jmp __alltraps
.globl vector13
vector13:
  pushl $13
  jmp __alltraps
.globl vector14
vector14:
  pushl $14
  jmp __alltraps
.globl vector15
vector15:
  pushl $0
  pushl $15
  jmp __alltraps
.globl vector16
vector16:
  pushl $0
  pushl $16
  jmp __alltraps
.globl vector17
vector17:
  pushl $17
  jmp __alltraps
.globl vector18
vector18:
  pushl $0
  pushl $18
  jmp __alltraps
.globl vector19
vector19:
  pushl $0
  pushl $19
  jmp __alltraps
.globl vector20
vector20:
  pushl $0
  pushl $20
  jmp __alltraps
.globl vector21
vector21:
  pushl $0
  pushl $21
  jmp __alltraps
.globl vector22
vector22:
  pushl $0
  pushl $22
  jmp __alltraps
.globl vector23
vector23:
  pushl $0
  pushl $23
  jmp __alltraps
.globl vector24
vector24:
  pushl $0
  pushl $24
  jmp __alltraps
.globl vector25
vector25:
  pushl $0
  pushl $25
  jmp __alltraps
.globl vector26
vector26:
  pushl $0
  pushl $26
  jmp __alltraps
.globl vector27
vector27:
  pushl $0
  pushl $27
  jmp __alltraps
.globl vector28
vector28:
  pushl $0
  pushl $28
  jmp __alltraps
.globl vector29
vector29:
  pushl $0
  pushl $29
  jmp __alltraps
.globl vector30
vector30:
  pushl $0
  pushl $30
  jmp __alltraps
.globl vector31
vector31:
  pushl $0
  pushl $31
  jmp __alltraps
.globl vector32
vector32:
  pushl $0
  pushl $32
  jmp __alltraps
.globl vector33
vector33:
  pushl $0
  pushl $33
  jmp __alltraps
.globl vector34
vector34:
  pushl $0
  pushl $34
  jmp __alltraps
.globl vector35
vector35:
  pushl $0
  pushl $35
  jmp __alltraps
.globl vector36
vector36:
  pushl $0
  pushl $36
  jmp __alltraps
.globl vector37
vector37:
  pushl $0
  pushl $37
  jmp __alltraps
.globl vector38
vector38:
  pushl $0
  pushl $38
  jmp __alltraps
.globl vector39
vector39:
  pushl $0
  pushl $39
  jmp __alltraps
.globl vector40
vector40:
  pushl $0
  pushl $40
  jmp __alltraps
.globl vector41
vector41:
  pushl $0
  pushl $41
  jmp __alltraps
.globl vector42
vector42:
  pushl $0
  pushl $42
  jmp __alltraps
.globl vector43
vector43:
  pushl $0
  pushl $43
  jmp __alltraps
.globl vector44
vector44:
  pushl $0
  pushl $44
  jmp __alltraps
.globl vector45
vector45:
  pushl $0
  pushl $45
  jmp __alltraps
.globl vector46
vector46:
  pushl $0
  pushl $46
  jmp __alltraps
.globl vector47
vector47:
  pushl $0
  pushl $47
  jmp __alltraps
.globl vector48
vector48:
  pushl $0
  pushl $48
  jmp __alltraps
.globl vector49
vector49:
  pushl $0
  pushl $49
  jmp __alltraps
.globl vector50
vector50:
  pushl $0
  pushl $50
  jmp __alltraps
.globl vector51
vector51:
  pushl $0
  pushl $51
  jmp __alltraps
.globl vector52
vector52:
  pushl $0
  pushl $52
  jmp __alltraps
.globl vector53
vector53:
  pushl $0
  pushl $53
  jmp __alltraps
.globl vector54
vector54:
  pushl $0
  pushl $54
  jmp __alltraps
.globl vector55
vector55:
  pushl $0
  pushl $55
  jmp __alltraps
.globl vector56
vector56:
  pushl $0
  pushl $56
  jmp __alltraps
.globl vector57
vector57:
  pushl $0
  pushl $57
  jmp __alltraps
.globl vector58
vector58:
  pushl $0
  pushl $58
  jmp __alltraps
.globl vector59
vector59:
  pushl $0
  pushl $59
  jmp __alltraps
.globl vector60
vector60:
  pushl $0
  pushl $60
  jmp __alltraps
.globl vector61
vector61:
  pushl $0
  pushl $61
  jmp __alltraps
.globl vector62
vector62:
  pushl $0
  pushl $62
  jmp __alltraps
.globl vector63
vector63:
  pushl $0
  pushl $63
  jmp __alltraps
.globl vector64
vector64:
  pushl $0
  pushl $64
  jmp __alltraps
.globl vector65
vector65:
  pushl $0
  pushl $65
  jmp __alltraps
.globl vector66
vector66:
  pushl $0
  pushl $66
  jmp __alltraps
.globl vector67
vector67:
  pushl $0
  pushl $67
  jmp __alltraps
.globl vector68
vector68:
  pushl $0
  pushl $68
  jmp __alltraps
.globl vector69
vector69:
  pushl $0
  pushl $69
  jmp __alltraps
.globl vector70
vector70:
  pushl $0
  pushl $70
  jmp __alltraps
.globl vector71
vector71:
  pushl $0
  pushl $71
  jmp __alltraps
.globl vector72
vector72:
  pushl $0
  pushl $72
  jmp __alltraps
.globl vector73
vector73:
  pushl $0
  pushl $73
  jmp __alltraps
.globl vector74
vector74:
  pushl $0
  pushl $74
  jmp __alltraps
.globl vector75
vector75:
  pushl $0
  pushl $75
  jmp __alltraps
.globl vector76
vector76:
  pushl $0
  pushl $76
  jmp __alltraps
.globl vector77
vector77:
  pushl $0
  pushl $77
  jmp __alltraps
.globl vector78
vector78:
  pushl $0
  pushl $78
  jmp __alltraps
.globl vector79
vector79:
  pushl $0
  pushl $79
  jmp __alltraps
.globl vector80
vector80:
  pushl $0
  pushl $80
  jmp __alltraps
.globl vector81
vector81:
  pushl $0
  pushl $81
  jmp __alltraps
.globl vector82
vector82:
  pushl $0
  pushl $82
  jmp __alltraps
.globl vector83
vector83:
  pushl $0
  pushl $83
  jmp __alltraps
.globl vector84
vector84:
  pushl $0
  pushl $84
  jmp __alltraps
.globl vector85
vector85:
  pushl $0
  pushl $85
  jmp __alltraps
.globl vector86
vector86:
  pushl $0
  pushl $86
  jmp __alltraps
.globl vector87
vector87:
  pushl $0
  pushl $87
  jmp __alltraps
.globl vector88
vector88:
  pushl $0
  pushl $88
  jmp __alltraps
.globl vector89
vector89:
  pushl $0
  pushl $89
  jmp __alltraps
.globl vector90
vector90:
  pushl $0
  pushl $90
  jmp __alltraps
.globl vector91
vector91:
  pushl $0
  pushl $91
  jmp __alltraps
.globl vector92
vector92:
  pushl $0
  pushl $92
  jmp __alltraps
.globl vector93
vector93:
  pushl $0
  pushl $93
  jmp __alltraps
.globl vector94
vector94:
  pushl $0
  pushl $94
  jmp __alltraps
.globl vector95
vector95:
  pushl $0
  pushl $95
  jmp __alltraps
.globl vector96
vector96:
  pushl $0
  pushl $96
  jmp __alltraps
.globl vector97
vector97:
  pushl $0
  pushl $97
  jmp __alltraps
.globl vector98
vector98:
  pushl $0
  pushl $98
  jmp __alltraps
.globl vector99
vector99:
  pushl $0
  pushl $99
  jmp __alltraps
.globl vector100
vector100:
  pushl $0
  pushl $100
  jmp __alltraps
.globl vector101
vector101:
  pushl $0
  pushl $101
  jmp __alltraps
.globl vector102
vector102:
  pushl $0
  pushl $102
  jmp __alltraps
.globl vector103
vector103:
  pushl $0
  pushl $103
  jmp __alltraps
.globl vector104
vector104:
  pushl $0
  pushl $104
  jmp __alltraps
.globl vector105
vector105:
  pushl $0
  pushl $105
  jmp __alltraps
.globl vector106
vector106:
  pushl $0
  pushl $106
  jmp __alltraps
.globl vector107
vector107:
  pushl $0
  pushl $107
  jmp __alltraps
.globl vector108
vector108:
  pushl $0
  pushl $108
  jmp __alltraps
.globl vector109
vector109:
  pushl $0
  pushl $109
  jmp __alltraps
.globl vector110
vector110:
  pushl $0
  pushl $110
  jmp __alltraps
.globl vector111
vector111:
  pushl $0
  pushl $111
  jmp __alltraps
.globl vector112
vector112:
  pushl $0
  pushl $112
  jmp __alltraps
.globl vector113
vector113:
  pushl $0
  pushl $113
  jmp __alltraps
.globl vector114
vector114:
  pushl $0
  pushl $114
  jmp __alltraps
.globl vector115
vector115:
  pushl $0
  pushl $115
  jmp __alltraps
.globl vector116
vector116:
  pushl $0
  pushl $116
  jmp __alltraps
.globl vector117
vector117:
  pushl $0
  pushl $117
  jmp __alltraps
.globl vector118
vector118:
  pushl $0
  pushl $118
  jmp __alltraps
.globl vector119
vector119:
  pushl $0
  pushl $119
  jmp __alltraps
.globl vector120
vector120:
  pushl $0
  pushl $120
  jmp __alltraps
.globl vector121
vector121:
  pushl $0
  pushl $121
  jmp __alltraps
.globl vector122
vector122:
  pushl $0
  pushl $122
  jmp __alltraps
.globl vector123
vector123:
  pushl $0
  pushl $123
  jmp __alltraps
.globl vector124
vector124:
  pushl $0
  pushl $124
  jmp __alltraps
.globl vector125
vector125:
  pushl $0
  pushl $125
  jmp __alltraps
.globl vector126
vector126:
  pushl $0
  pushl $126
  jmp __alltraps
.globl vector127
vector127:
  pushl $0
  pushl $127
  jmp __alltraps
.globl vector128
vector128:
  pushl $0
  pushl $128
  jmp __alltraps
.globl vector129
vector129:
  pushl $0
  pushl $129
  jmp __alltraps
.globl vector130
vector130:
  pushl $0
  pushl $130
  jmp __alltraps
.globl vector131
vector131:
  pushl $0
  pushl $131
  jmp __alltraps
.globl vector132
vector132:
  pushl $0
  pushl $132
  jmp __alltraps
.globl vector133
vector133:
  pushl $0
  pushl $133
  jmp __alltraps
.globl vector134
vector134:
  pushl $0
  pushl $134
  jmp __alltraps
.globl vector135
vector135:
  pushl $0
  pushl $135
  jmp __alltraps
.globl vector136
vector136:
  pushl $0
  pushl $136
  jmp __alltraps
.globl vector137
vector137:
  pushl $0
  pushl $137
  jmp __alltraps
.globl vector138
vector138:
  pushl $0
  pushl $138
  jmp __alltraps
.globl vector139
vector139:
  pushl $0
  pushl $139
  jmp __alltraps
.globl vector140
vector140:
  pushl $0
  pushl $140
  jmp __alltraps
.globl vector141
vector141:
  pushl $0
  pushl $141
  jmp __alltraps
.globl vector142
vector142:
  pushl $0
  pushl $142
  jmp __alltraps
.globl vector143
vector143:
  pushl $0
  pushl $143
  jmp __alltraps
.globl vector144
vector144:
  pushl $0
  pushl $144
  jmp __alltraps
.globl vector145
vector145:
  pushl $0
  pushl $145
  jmp __alltraps
.globl vector146
vector146:
  pushl $0
  pushl $146
  jmp __alltraps
.globl vector147
vector147:
  pushl $0
  pushl $147
  jmp __alltraps
.globl vector148
vector148:
  pushl $0
  pushl $148
  jmp __alltraps
.globl vector149
vector149:
  pushl $0
  pushl $149
  jmp __alltraps
.globl vector150
vector150:
  pushl $0
  pushl $150
  jmp __alltraps
.globl vector151
vector151:
  pushl $0
  pushl $151
  jmp __alltraps
.globl vector152
vector152:
  pushl $0
  pushl $152
  jmp __alltraps
.globl vector153
vector153:
  pushl $0
  pushl $153
  jmp __alltraps
.globl vector154
vector154:
  pushl $0
  pushl $154
  jmp __alltraps
.globl vector155
vector155:
  pushl $0
  pushl $155
  jmp __alltraps
.globl vector156
vector156:
  pushl $0
  pushl $156
  jmp __alltraps
.globl vector157
vector157:
  pushl $0
  pushl $157
  jmp __alltraps
.globl vector158
vector158:
  pushl $0
  pushl $158
  jmp __alltraps
.globl vector159
vector159:
  pushl $0
  pushl $159
  jmp __alltraps
.globl vector160
vector160:
  pushl $0
  pushl $160
  jmp __alltraps
.globl vector161
vector161:
  pushl $0
  pushl $161
  jmp __alltraps
.globl vector162
vector162:
  pushl $0
  pushl $162
  jmp __alltraps
.globl vector163
vector163:
  pushl $0
  pushl $163
  jmp __alltraps
.globl vector164
vector164:
  pushl $0
  pushl $164
  jmp __alltraps
.globl vector165
vector165:
  pushl $0
  pushl $165
  jmp __alltraps
.globl vector166
vector166:
  pushl $0
  pushl $166
  jmp __alltraps
.globl vector167
vector167:
  pushl $0
  pushl $167
  jmp __alltraps
.globl vector168
vector168:
  pushl $0
  pushl $168
  jmp __alltraps
.globl vector169
vector169:
  pushl $0
  pushl $169
  jmp __alltraps
.globl vector170
vector170:
  pushl $0
  pushl $170
  jmp __alltraps
.globl vector171
vector171:
  pushl $0
  pushl $171
  jmp __alltraps
.globl vector172
vector172:
  pushl $0
  pushl $172
  jmp __alltraps
.globl vector173
vector173:
  pushl $0
  pushl $173
  jmp __alltraps
.globl vector174
vector174:
  pushl $0
  pushl $174
  jmp __alltraps
.globl vector175
vector175:
  pushl $0
  pushl $175
  jmp __alltraps
.globl vector176
vector176:
  pushl $0
  pushl $176
  jmp __alltraps
.globl vector177
vector177:
  pushl $0
  pushl $177
  jmp __alltraps
.globl vector178
vector178:
  pushl $0
  pushl $178
  jmp __alltraps
.globl vector179
vector179:
  pushl $0
  pushl $179
  jmp __alltraps
.globl vector180
vector180:
  pushl $0
  pushl $180
  jmp __alltraps
.globl vector181
vector181:
  pushl $0
  pushl $181
  jmp __alltraps
.globl vector182
vector182:
  pushl $0
  pushl $182
  jmp __alltraps
.globl vector183
vector183:
  pushl $0
  pushl $183
  jmp __alltraps
.globl vector184
vector184:
  pushl $0
  pushl $184
  jmp __alltraps
.globl vector185
vector185:
  pushl $0
  pushl $185
  jmp __alltraps
.globl vector186
vector186:
  pushl $0
  pushl $186
  jmp __alltraps
.globl vector187
vector187:
  pushl $0
  pushl $187
  jmp __alltraps
.globl vector188
vector188:
  pushl $0
  pushl $188
  jmp __alltraps
.globl vector189
vector189:
  pushl $0
  pushl $189
  jmp __alltraps
.globl vector190
vector190:
  pushl $0
  pushl $190
  jmp __alltraps
.globl vector191
vector191:
  pushl $0
  pushl $191
  jmp __alltraps
.globl vector192
vector192:
  pushl $0
  pushl $192
  jmp __alltraps
.globl vector193
vector193:
  pushl $0
  pushl $193
  jmp __alltraps
.globl vector194
vector194:
  pushl $0
  pushl $194
  jmp __alltraps
.globl vector195
vector195:
  pushl $0
  pushl $195
  jmp __alltraps
.globl vector196
vector196:
  pushl $0
  pushl $196
  jmp __alltraps
.globl vector197
vector197:
  pushl $0
  pushl $197
  jmp __alltraps
.globl vector198
vector198:
  pushl $0
  pushl $198
  jmp __alltraps
.globl vector199
vector199:
  pushl $0
  pushl $199
  jmp __alltraps
.globl vector200
vector200:
  pushl $0
  pushl $200
  jmp __alltraps
.globl vector201
vector201:
  pushl $0
  pushl $201
  jmp __alltraps
.globl vector202
vector202:
  pushl $0
  pushl $202
  jmp __alltraps
.globl vector203
vector203:
  pushl $0
  pushl $203
  jmp __alltraps
.globl vector204
vector204:
  pushl $0
  pushl $204
  jmp __alltraps
.globl vector205
vector205:
  pushl $0
  pushl $205
  jmp __alltraps
.globl vector206
vector206:
  pushl $0
  pushl $206
  jmp __alltraps
.globl vector207
vector207:
  pushl $0
  pushl $207
  jmp __alltraps
.globl vector208
vector208:
  pushl $0
  pushl $208
  jmp __alltraps
.globl vector209
vector209:
  pushl $0
  pushl $209
  jmp __alltraps
.globl vector210
vector210:
  pushl $0
  pushl $210
  jmp __alltraps
.globl vector211
vector211:
  pushl $0
  pushl $211
  jmp __alltraps
.globl vector212
vector212:
  pushl $0
  pushl $212
  jmp __alltraps
.globl vector213
vector213:
  pushl $0
  pushl $213
  jmp __alltraps
.globl vector214
vector214:
  pushl $0
  pushl $214
  jmp __alltraps
.globl vector215
vector215:
  pushl $0
  pushl $215
  jmp __alltraps
.globl vector216
vector216:
  pushl $0
  pushl $216
  jmp __alltraps
.globl vector217
vector217:
  pushl $0
  pushl $217
  jmp __alltraps
.globl vector218
vector218:
  pushl $0
  pushl $218
  jmp __alltraps
.globl vector219
vector219:
  pushl $0
  pushl $219
  jmp __alltraps
.globl vector220
vector220:
  pushl $0
  pushl $220
  jmp __alltraps
.globl vector221
vector221:
  pushl $0
  pushl $221
  jmp __alltraps
.globl vector222
vector222:
  pushl $0
  pushl $222
  jmp __alltraps
.globl vector223
vector223:
  pushl $0
  pushl $223
  jmp __alltraps
.globl vector224
vector224:
  pushl $0
  pushl $224
  jmp __alltraps
.globl vector225
vector225:
  pushl $0
  pushl $225
  jmp __alltraps
.globl vector226
vector226:
  pushl $0
  pushl $226
  jmp __alltraps
.globl vector227
vector227:
  pushl $0
  pushl $227
  jmp __alltraps
.globl vector228
vector228:
  pushl $0
  pushl $228
  jmp __alltraps
.globl vector229
vector229:
  pushl $0
  pushl $229
  jmp __alltraps
.globl vector230
vector230:
  pushl $0
  pushl $230
  jmp __alltraps
.globl vector231
vector231:
  pushl $0
  pushl $231
  jmp __alltraps
.globl vector232
vector232:
  pushl $0
  pushl $232
  jmp __alltraps
.globl vector233
vector233:
  pushl $0
  pushl $233
  jmp __alltraps
.globl vector234
vector234:
  pushl $0
  pushl $234
  jmp __alltraps
.globl vector235
vector235:
  pushl $0
  pushl $235
  jmp __alltraps
.globl vector236
vector236:
  pushl $0
  pushl $236
  jmp __alltraps
.globl vector237
vector237:
  pushl $0
  pushl $237
  jmp __alltraps
.globl vector238
vector238:
  pushl $0
  pushl $238
  jmp __alltraps
.globl vector239
vector239:
  pushl $0
  pushl $239
  jmp __alltraps
.globl vector240
vector240:
  pushl $0
  pushl $240
  jmp __alltraps
.globl vector241
vector241:
  pushl $0
  pushl $241
  jmp __alltraps
.globl vector242
vector242:
  pushl $0
  pushl $242
  jmp __alltraps
.globl vector243
vector243:
  pushl $0
  pushl $243
  jmp __alltraps
.globl vector244
vector244:
  pushl $0
  pushl $244
  jmp __alltraps
.globl vector245
vector245:
  pushl $0
  pushl $245
  jmp __alltraps
.globl vector246
vector246:
  pushl $0
  pushl $246
  jmp __alltraps
.globl vector247
vector247:
  pushl $0
  pushl $247
  jmp __alltraps
.globl vector248
vector248:
  pushl $0
  pushl $248
  jmp __alltraps
.globl vector249
vector249:
  pushl $0
  pushl $249
  jmp __alltraps
.globl vector250
vector250:
  pushl $0
  pushl $250
  jmp __alltraps
.globl vector251
vector251:
  pushl $0
  pushl $251
  jmp __alltraps
.globl vector252
vector252:
  pushl $0
  pushl $252
  jmp __alltraps
.globl vector253
vector253:
  pushl $0
  pushl $253
  jmp __alltraps
.globl vector254
vector254:
  pushl $0
  pushl $254
  jmp __alltraps
.globl vector255
vector255:
  pushl $0
  pushl $255
  jmp __alltraps

# vector table
.data
.globl __vectors
__vectors:
  .long vector0
  .long vector1
  .long vector2
  .long vector3
  .long vector4
  .long vector5
  .long vector6
  .long vector7
  .long vector8
  .long vector9
  .long vector10
  .long vector11
  .long vector12
  .long vector13
  .long vector14
  .long vector15
  .long vector16
  .long vector17
  .long vector18
  .long vector19
  .long vector20
  .long vector21
  .long vector22
  .long vector23
  .long vector24
  .long vector25
  .long vector26
  .long vector27
  .long vector28
  .long vector29
  .long vector30
  .long vector31
  .long vector32
  .long vector33
  .long vector34
  .long vector35
  .long vector36
  .long vector37
  .long vector38
  .long vector39
  .long vector40
  .long vector41
  .long vector42
  .long vector43
  .long vector44
  .long vector45
  .long vector46
  .long vector47
  .long vector48
  .long vector49
  .long vector50
  .long vector51
  .long vector52
  .long vector53
  .long vector54
  .long vector55
  .long vector56
  .long vector57
  .long vector58
  .long vector59
  .long vector60
  .long vector61
  .long vector62
  .long vector63
  .long vector64
  .long vector65
  .long vector66
  .long vector67
  .long vector68
  .long vector69
  .long vector70
  .long vector71
  .long vector72
  .long vector73
  .long vector74
  .long vector75
  .long vector76
  .long vector77
  .long vector78
  .long vector79
  .long vector80
  .long vector81
  .long vector82
  .long vector83
  .long vector84
  .long vector85
  .long vector86
  .long vector87
  .long vector88
  .long vector89
  .long vector90
  .long vector91
  .long vector92
  .long vector93
  .long vector94
  .long vector95
  .long vector96
  .long vector97
  .long vector98
  .long vector99
  .long vector100
  .long vector101
  .long vector102
  .long vector103
  .long vector104
  .long vector105
  .long vector106
  .long vector107
  .long vector108
  .long vector109
  .long vector110
  .long vector111
  .long vector112
  .long vector113
  .long vector114
  .long vector115
  .long vector116
  .long vector117
  .long vector118
  .long vector119
  .long vector120
  .long vector121
  .long vector122
  .long vector123
  .long vector124
  .long vector125
  .long vector126
  .long vector127
  .long vector128
  .long vector129
  .long vector130
  .long vector131
  .long vector132
  .long vector133
  .long vector134
  .long vector135
  .long vector136
  .long vector137
  .long vector138
  .long vector139
  .long vector140
  .long vector141
  .long vector142
  .long vector143
  .long vector144
  .long vector145
  .long vector146
  .long vector147
  .long vector148
  .long vector149
  .long vector150
  .long vector151
  .long vector152
  .long vector153
  .long vector154
  .long vector155
  .long vector156
  .long vector157
  .long vector158
  .long vector159
  .long vector160
  .long vector161
  .long vector162
  .long vector163
  .long vector164
  .long vector165
  .long vector166
  .long vector167
  .long vector168
  .long vector169
  .long vector170
  .long vector171
  .long vector172
  .long vector173
  .long vector174
  .long vector175
  .long vector176
  .long vector177
  .long vector178
  .long vector179
  .long vector180
  .long vector181
  .long vector182
  .long vector183
  .long vector184
  .long vector185
  .long vector186
  .long vector187
  .long vector188
  .long vector189
  .long vector190
  .long vector191
  .long vector192
  .long vector193
  .long vector194
  .long vector195
  .long vector196
  .long vector197
  .long vector198
  .long vector199
  .long vector200
  .long vector201
  .long vector202
  .long vector203
  .long vector204
  .long vector205
  .long vector206
  .long vector207
  .long vector208
  .long vector209
  .long vector210
  .long vector211
  .long vector212
  .long vector213
  .long vector214
  .long vector215
  .long vector216
  .long vector217
  .long vector218
  .long vector219
  .long vector220
  .long vector221
  .long vector222
  .long vector223
  .long vector224
  .long vector225
  .long vector226
  .long vector227
  .long vector228
  .long vector229
  .long vector230
  .long vector231
  .long vector232
  .long vector233
  .long vector234
  .long vector235
  .long vector236
  .long vector237
  .long vector238
  .long vector239
  .long vector240
  .long vector241
  .long vector242
  .long vector243
  .long vector244
  .long vector245
  .long vector246
  .long vector247
  .long vector248
  .long vector249
  .long vector250
  .long vector251
  .long vector252
  .long vector253
  .long vector254
  .long vector255
View Code

   __alltraps是定義在同一目錄下即/kern/trap/trapentry.S中的。

  在__alltraps中,按照順序將當前的各個經常使用的寄存器的值壓入了棧中,隨後將ds、es等數據段寄存器載入了內核的數據段選擇子(這是由於中斷可能來自用戶態,而中斷服務例程必須在內核態運行以擁有全部資源的訪問權限,避免內核中的中斷服務例程因爲特權級不夠,訪問數據時出現問題)。

  隨後棧中壓入esp的值,便經過call trap跳轉到了內核的中斷服務分發函數trap中。trap函數位於/kern/trap/trap.c中。

trapentry.S:

#include <memlayout.h>

# vectors.S sends all traps here.
.text
.globl __alltraps
__alltraps:
    # push registers to build a trap frame
    # therefore make the stack look like a struct trapframe
    pushl %ds
    pushl %es
    pushl %fs
    pushl %gs
    pushal

    # load GD_KDATA into %ds and %es to set up data segments for kernel
    movl $GD_KDATA, %eax
    movw %ax, %ds
    movw %ax, %es

    # push %esp to pass a pointer to the trapframe as an argument to trap()
    pushl %esp

    # call trap(tf), where tf=%esp
    call trap

    # pop the pushed stack pointer
    popl %esp

    # return falls through to trapret...
.globl __trapret
__trapret:
    # restore registers from stack
    popal

    # restore %ds, %es, %fs and %gs
    popl %gs
    popl %fs
    popl %es
    popl %ds

    # get rid of the trap number and error code
    addl $0x8, %esp
    iret

trap函數入口:

/* *
 * trap - handles or dispatches an exception/interrupt. if and when trap() returns,
 * the code in kern/trap/trapentry.S restores the old CPU state saved in the
 * trapframe and then uses the iret instruction to return from the exception.
 * */
void
trap(struct trapframe *tf) {
    // dispatch based on what type of trap occurred
    trap_dispatch(tf);
}

  trap函數的參數是trapframe結構體。仔細觀察能夠看到,trapframe中字段屬性的定義和trapentry.S中入棧的順序是相反的。

  1. 第一個字段pushregs保存了pushal壓入棧中的數據,後續的tf_gs + tf_padding0兩個字段是由於pushl會壓入一個32位的數據,而gs數據段寄存器自己保存的段選擇子是16位的,須要一個16位的padding空閒字段合併起來與之對應。後面的fs、es、ds原理同樣。

  2. 以後的tf_trapno屬性對應着跳轉至__alltraps以前所壓入棧中的中斷向量號,tf_err對應的是在上面的pushl $0,即錯誤號。根據註釋能夠看到,包括tf_err在內的中斷錯誤號都是x86CPU硬件在中斷髮生時自動壓入棧中的。但並非全部的硬件中斷都會被自動壓入錯誤號(須要去閱讀硬件手冊才能知道具體細節),爲了可以以一個統一的接口去處理全部的中斷請求,在vertor.S中對於沒有錯誤號的中斷請求默認加上了pushl $0,壓入一個默認的錯誤號0;對於CPU硬件會壓入錯誤號的中斷向量則沒有進行默認處理,例如vertor八、vertor9等等。

  3. 發生中斷時,x86CPU會默認按照順序依次壓入eflags、cs和eip用於中斷後的現場恢復。對應的是tf_eip、tf_cs + tf_padding4以及tf_eflags。而當發生了CPL特權級的變化時,x86CPU硬件會發生不一樣特權級棧的切換,所以還會先依次壓入切換特權級前的ss棧段寄存器和esp棧頂指針的值入棧,便於中斷返回後回到對應的特權級中。一個很典型的例子就是,當用戶程序執行系統調用時(系統調用是經過中斷機制實現的,在ucore的lab5中實現了這一功能),會從用戶的CPL特權級ring3切換到內核的特權級ring0,系統調用的服務例程是在分配好的內核棧中執行的。

/* registers as pushed by pushal */
struct pushregs {
    uint32_t reg_edi;
    uint32_t reg_esi;
    uint32_t reg_ebp;
    uint32_t reg_oesp;            /* Useless */
    uint32_t reg_ebx;
    uint32_t reg_edx;
    uint32_t reg_ecx;
    uint32_t reg_eax;
};

struct trapframe {
    struct pushregs tf_regs;
    uint16_t tf_gs;
    uint16_t tf_padding0;
    uint16_t tf_fs;
    uint16_t tf_padding1;
    uint16_t tf_es;
    uint16_t tf_padding2;
    uint16_t tf_ds;
    uint16_t tf_padding3;
    uint32_t tf_trapno;
    /* below here defined by x86 hardware */
    uint32_t tf_err;
    uintptr_t tf_eip;
    uint16_t tf_cs;
    uint16_t tf_padding4;
    uint32_t tf_eflags;
    /* below here only when crossing rings, such as from user to kernel */
    uintptr_t tf_esp;
    uint16_t tf_ss;
    uint16_t tf_padding5;
} __attribute__((packed));

爲何tramframe中斷棧幀的結構屬性的定義順序會和入棧時的順序相反?

  要理解這個須要對C語言編譯後的底層機器代碼模型有必定了解。

  1.C語言結構體定義的內存排布是按照字段定義順序,從內存的低位到高位延伸的。

  2.棧在壓入數據時,棧頂指針是遞減的,由高位往低位延伸的。

  3.trap函數的參數trapframe指針指向的是當前棧頂,相對處於低位,而對所包含的字段則是在這個指針的基礎上向高位偏移對應的N個字節來訪問的。要想在C中經過一個結構體映射出棧上的內存數據便於後續的訪問,那麼必須以和入棧順序相反的順序來定義結構體。棧幀結構體trapframe定義最後的__attribute__((packed))指的是強制令C編譯器使用緊湊模式處理該結構體,避免編譯器在字段的處理上進行額外的內存對齊操做,致使訪問時最後生成的內存地址訪問偏移量計算錯誤。

  這裏須要注意的一點是,當處理沒有發生特權級切換的中斷時,trapframe對應的最後三個字段是不存在於棧上的,此時若是經過tf_ss等屬性訪問時,會越界訪問到本來在棧上不相關的數據。因此在訪問這幾個字段時,必須先判斷是否發生了特權級的變化,避免損壞棧上數據,令程序出錯甚至崩潰。

中斷服務例程進行處理

  如今分析當中斷髮生時,trap函數接收到trapframe中斷幀參數後是如何進行中斷服務處理的。

  在lab1中,trap函數只是簡單的調用了同一文件中的trap_dispatch函數,在trap_dispatch中經過對tf->tf_trapno,即中斷向量號進行判斷,將控制流轉移至中斷向量號對應的中斷服務例程中。(這裏trap函數只是簡單的調用trap_dispatch,是由於後續的lab中,會在trap函數中在中斷處理服務開始先後加入許多邏輯,預先將對中斷請求的分發邏輯抽取了出來)

  好比第一個case塊即是用於處理時鐘中斷的。在lab1中,經過一個被volatile關鍵字修飾的ticks全局變量,在每次時鐘中斷時累加1,當次數每達到TICK_NUM時(默認100,對應的是10ms一次的時鐘中斷),便打印一段話。體如今lab1實驗中即是,ucore內核啓動完成後,控制檯每秒鐘週期性的打印出"100ticks"。

  能夠看到,雖然80386CPU的硬件設計者但願操做系統的設計者直接在中斷描述符中設置對應的中斷服務例程的入口地址,但ucore卻沒有充分利用這一特性,而是選擇了在中斷服務例程的入口處簡單壓入幾個數據後將中斷服務的控制流程統一的指向了__alltraps,最後經過trap_dispatch函數進行分發。這樣的設計雖然在性能上可能有微小的損失,可是卻使得ucore的中斷服務實現更加靈活、可控。(做爲一個java後端程序員,這一設計令我想到了springmvc框架基於下層servlet的封裝機制:經過一個/*的servlet得到全部請求的控制權,再由框架靈活封裝各類參數,最後將參數和控制權交給對應的controller方法進行處理)

trap_dispatch函數:

/* trap_dispatch - dispatch based on what type of trap occurred */
static void
trap_dispatch(struct trapframe *tf) {
    char c;

    switch (tf->tf_trapno) {
    case IRQ_OFFSET + IRQ_TIMER:
        /* LAB1 YOUR CODE : STEP 3 */
        /* handle the timer interrupt */
        /* (1) After a timer interrupt, you should record this event using a global variable (increase it), such as ticks in kern/driver/clock.c
         * (2) Every TICK_NUM cycle, you can print some info using a funciton, such as print_ticks().
         * (3) Too Simple? Yes, I think so!
         */
        ticks ++;
        if (ticks % TICK_NUM == 0) {
            print_ticks();
        }
        break;
    case IRQ_OFFSET + IRQ_COM1:
        c = cons_getc();
        cprintf("serial [%03d] %c\n", c, c);
        break;
    case IRQ_OFFSET + IRQ_KBD:
        c = cons_getc();
        cprintf("kbd [%03d] %c\n", c, c);
        break;
    //LAB1 CHALLENGE 1 : YOUR CODE you should modify below codes.
    case T_SWITCH_TOU:
        if (tf->tf_cs != USER_CS) {
            switchk2u = *tf;
            switchk2u.tf_cs = USER_CS;
            switchk2u.tf_ds = switchk2u.tf_es = switchk2u.tf_ss = USER_DS;
            switchk2u.tf_esp = (uint32_t)tf + sizeof(struct trapframe) - 8;
        
            // set eflags, make sure ucore can use io under user mode.
            // if CPL > IOPL, then cpu will generate a general protection.
            switchk2u.tf_eflags |= FL_IOPL_MASK;
        
            // set temporary stack
            // then iret will jump to the right stack
            *((uint32_t *)tf - 1) = (uint32_t)&switchk2u;
        }
        break;
    case T_SWITCH_TOK:
        if (tf->tf_cs != KERNEL_CS) {
            tf->tf_cs = KERNEL_CS;
            tf->tf_ds = tf->tf_es = KERNEL_DS;
            tf->tf_eflags &= ~FL_IOPL_MASK;
            switchu2k = (struct trapframe *)(tf->tf_esp - (sizeof(struct trapframe) - 8));
            memmove(switchu2k, tf, sizeof(struct trapframe) - 8);
            *((uint32_t *)tf - 1) = (uint32_t)switchu2k;
        }
        break;
    case IRQ_OFFSET + IRQ_IDE1:
    case IRQ_OFFSET + IRQ_IDE2:
        /* do nothing */
        break;
    default:
        // in kernel, it must be a mistake
        if ((tf->tf_cs & 3) == 0) {
            print_trapframe(tf);
            panic("unexpected trap in kernel.\n");
        }
    }
}  

中斷返回

  中斷服務例程用於處理突發性的中斷事件,通常來講都是短小精悍的服務代碼,會很快的執行完畢並返回。下面接着分析ucore在中斷返回時的處理機制。

  在trap函數返回後,代碼的控制流回到了trapentry.S中,即CPU指令指向call trap的下一條指令。爲了保證中斷服務返回後以前被中斷程序上下文的正確性,須要將執行call trap以前的壓入的數據一一彈出還原。

  1. 按照相反的順序彈出、還原各個經常使用寄存器的值(popl esp、popal、popl gs/fs/es/ds)。

  2. 經過addl $0x8, %esp,以直接上移棧頂指針的方式,略過以前壓入的中斷號tf_trapno和錯誤碼tf_err。

  3. 執行iret指令,iret指令會將以前硬件自動壓入的eip、cs、eflags按照順序彈出。當CPU發現彈出時的cs值和當前cs值不一致,則認定這次中斷髮生了特權級的變化。此時CPU會接着彈出以前壓入了的esp、ss寄存器的值,令其返回到中斷髮生前對應的特權級棧中繼續執行。

  CPU認爲只有以前發生特權級變化時纔會額外壓入ss、esp,因此中斷返回時若是發現彈出的cs與當前cs不一致時,除了恢復以前棧上的cs(也恢復了CPL),同時會額外的彈出esp、ss。

  這特權級一機制在lab1的挑戰練習lab1 challenge1中被利用了起來,挑戰練習1須要模擬出內核態轉化至用戶態,再從用戶態再轉換回內核態的過程。

lab1挑戰練習1實現原理分析

  在kern_init總控函數中,最後經過lab1_switch_test來實現這一過程。

  lab1_switch_to_user函數中,經過內聯彙編執行了int命令,觸發了一個軟中斷,中斷號爲T_SWITCH_TOU。控制流最終會指向trap_dispatch函數中對應的case塊中,在其中經過修改當前中斷棧幀中的cs代碼段寄存器、ds、es、ss等數據段寄存器的值,使得中斷棧幀上的CS的段選擇子的值爲用戶態。這樣在中斷返回時,便"欺騙"了CPU,使得CPU在中斷返回後將當前的特權級由內核態切換到了用戶態。在後續的實驗中,例如經過系統調用加載並運行一個用戶態應用程序,就是經過這一"欺騙"機制巧妙地實現特權級的切換。

  lab1_switch_to_kernel函數中,一樣經過內聯彙編執行int命令觸發軟中斷,中斷號爲T_SWITCH_TOK。控制流最終指向trap_dispatch函數中對應的case塊中,經過設置cs代碼段寄存器的值爲內核代碼段、ds、es設置爲內核數據段來實現中斷返回後,令CPU再從用戶態回到內核態。

static void
lab1_switch_test(void) {
    lab1_print_cur_status();
    cprintf("+++ switch to  user  mode +++\n");
    lab1_switch_to_user();
    lab1_print_cur_status();
    cprintf("+++ switch to kernel mode +++\n");
    lab1_switch_to_kernel();
    lab1_print_cur_status();
}

static void
lab1_switch_to_user(void) {
    //LAB1 CHALLENGE 1 : TODO
    asm volatile (
        "sub $0x8, %%esp \n"
        "int %0 \n"
        "movl %%ebp, %%esp"
        : 
        : "i"(T_SWITCH_TOU)
    );
}

static void
lab1_switch_to_kernel(void) {
    //LAB1 CHALLENGE 1 :  TODO
    asm volatile (
        "int %0 \n"
        "movl %%ebp, %%esp \n"
        : 
        : "i"(T_SWITCH_TOK)
    );
}

總結

  從年初接觸ucore到如今完成學習,經過博客總結心得已通過去了大半年。學習ucore就像攀爬一座高山同樣,最初的我因爲彙編、C等基礎的前置知識掌握的不牢靠,致使往往想研究ucore源碼時都由於看不懂代碼而宣告失敗,所以我下定決心將彙編和C從新學習了一遍。雖然最初的動機是爲了更好的學習ucore,但在學習過程當中我卻收穫頗豐,領略了爬山途中的好風景。一方面使我對計算機底層的運行機制創建起了一個大體的知識框架,理解了CPU的運行機制、中斷等硬件的工做原理(主要仍是單核CPU的工做原理)。另外一方面,隨着對彙編、C語言的進一步學習,也慢慢的理解了《黑客與畫家》中對於編程語言抽象能力的見解,爲何計算機硬件不斷髮展,抽象程度更高但性能較低的編程語言會變得愈來愈流行。

  正如C語言最初做爲一種"高級彙編語言"而出現,其提供的數組、結構體、指針等機制簡化了彙編中使人頭疼的訪問數據時的地址偏移問題。同時C還提供了標準庫,在絕大多數場景下可以屏蔽掉不一樣硬件、操做系統平臺的差別,使其作到一次編寫,處處編譯。而C++做爲C的高級版本,提供了面向對象的編程機制,由編譯器提供多態等諸多語法糖,由編譯器來自動完成以前須要C程序員經過函數指針集合等方式手動實現的面向對象邏輯。java做爲C++的後繼者,認爲C++爲了兼容C依然保留了太多應用程序開發時不須要的底層功能,便將包括指針、goto在內的許多機制都隱藏起來了,不讓程序員直接接觸,經過jvm在絕大多數場景下屏蔽了不一樣操做系統平臺的差別。而Lisp語言的抽象程度則更高,正同《程序員的吶喊》中所說的:「Lisp僞裝操做系統不存在」。若是不考慮在當前馮.諾依曼架構機器上的運行效率,LISP倡導的就是肆無忌憚的進行函數遞歸而沒必要擔憂棧溢出,爲了使函數調用無反作用能夠任意的copy數據,而沒必要擔憂內存不足和垃圾回收的負擔,最重要的是程序的可讀性、可維護性,怎麼方便人思考怎麼來,不太關心空間、時間性能。隨着機器性能的不斷提高,將來的編程語言實現中也許真的能夠用kv Map徹底的替代數組,甚至用丘奇數來替代整數以追求數學上極致簡約的美?Orz

  任意編程語言的內容主要分爲兩部分,一是基礎語法,另外一部分則是在基於的特定平臺上功能的封裝。例如javascript由ECMA語法和對其工做平臺瀏覽器相關功能的封裝組成,而java、nodejs等通用編程語言則是由語法和對操做系統功能的封裝。做爲一個以java做爲平常開發語言的我來講,學習ucore讓我對java中諸如併發同步、BIO/NIO等機制有了進一步的理解,解開了很多對於jvm底層與操做系統交互機制的困惑。總而言之,學習操做系統仍是能學到不少知識的,而ucore os網上公開課就是一個很好的學習方式。

  這是我ucore學習系列博客的第一篇博客,將來會不斷的更新後續實驗的學習心得,博客中會嘗試着儘可能將初學者可能碰到的各類疑惑一一解答。但願能幫助到對操做系統、ucore os感興趣的人。

  這篇博客的完整代碼在個人github上:https://github.com/1399852153/ucore_os_lab (fork自官方倉庫)中的lab1_answer,存在許多不足之處,還請多多指教。

相關文章
相關標籤/搜索