1. 程式人生 > >作業系統筆記之基礎

作業系統筆記之基礎

1.進入作業系統

神祕的開機背景後面到底發生了什麼?
開啟電源,計算機就開始工作了,那計算機怎麼工作?
馮。諾依曼儲存程式思想:把程式和資料存放在計算機儲存器中,計算機在程式的控制下一步一步執行。
設定一個程式指標PC指向指令,由PC指標從儲存器中取出指令,交給運算器和控制器,程式指標PC自動指向下一個指令。

那麼問題:開啟電源以後,計算機執行的第一條指令是什麼?
x86pc,計算機剛開啟時pc = ?
x86pc剛開機時CPU處於真實模式(定址CS:IP PC=CS左移4位+IP)
記憶體中固化了一段程式,這就是ROM BIOS(Basic Input Output System),程式固化在地址0XFFF0.開機時,CS=0XFFFF, IP=0X0000, 於是開機後PC讀到的第一條指令地址是0XFFF0,這就是BIOS的第一條指令。然後檢查RAM,鍵盤,主機板,顯示器,磁碟等硬體,然後從將磁碟0磁軌0扇區的內容讀入0X7C00處,這個0磁軌0扇區儲存的是作業系統的引導扇區,然後再設定CS=0x07c0, ip=OX0000這樣PC就變成0X07C0,接著就開始執行作業系統的引導扇區,從這裡開始就進入作業系統了,前面都還是在硬體中。

引導扇區讀入的那512個位元組程式碼(地址0X7C00處)
引導扇區是啟動裝置的第一個扇區,啟動裝置資訊被設定在CMOS中,硬碟的第一個扇區存放著開機後第一段我們能控制的程式
作業系統由此開始。

linux0.11引導扇區程式碼bootsect.s
bootsect.s最後會被彙編成機器指令,放在引導扇區。

SETUPLEN = 4                ! nr of setup-sectors
BOOTSEG  = 0x07c0           ! original address of boot-sector
INITSEG  = 0x9000           ! we move boot here - out of the way
SETUPSEG = 0x9020           ! setup starts here
SYSSEG   = 0x1000           ! system loaded at 0x10000 (65536).
ENDSEG   = SYSSEG + SYSSIZE     ! where to stop loading

! ROOT_DEV: 0x000 - same type of floppy as boot.
!       0x301 - first partition on first drive etc
ROOT_DEV = 0x306

entry _start
_start:

    mov ax,#BOOTSEG
    mov ds,ax                         //ds = 0x07c00
    mov ax,#INITSEG
    mov es,ax                        //es  = 0x9000
    mov cx,#256
    sub si,si   // si = 0
    sub di,di  // di = 0      ds : si 為 0x07c0 : 0x0000, es : di 為 0x9000 : 0x0000
    rep
    movw          
    **// 重複移動,將0x07c0:0x0000處的256個字移動到0x9000:0x0000 目的是騰出空間,後面在setup.s中會有用,避免在將system模組移動到0地址是覆蓋bootsect.s**
go: 
    mov ax,cs
    mov ds,ax
    mov es,ax!   !put stack at 0x9ff00.
    mov ss,ax
    mov sp,#0xFF00      ! arbitrary value >>512

! load the setup-sectors directly after the bootblock.
! Note that 'es' is already set up.

load_setup:
    mov dx,#0x0000      ! drive 0, head 0
    mov cx,#0x0002      ! sector 2, track 0       
    **//bootsect.s佔據第一個扇區,因此要從第二扇區讀**
    mov bx,#0x0200      ! address = 512, in INITSEG   **/ /es : bx 0x9000 : 0x0200 **
    mov ax,#0x0200+SETUPLEN ! service 2, nr of sectors **// setup扇區數量** 
    int 0x13            ! read it          **//0x13是BIOS讀磁碟扇區的中斷**
    jnc ok_load_setup       ! ok - continue
    mov dx,#0x0000
    mov ax,#0x0000      ! reset the diskette
    int 0x13              
    j   load_setup

ok_load_setup:
! Get disk drive parameters, specifically nr of sectors/track
    mov dl,#0x00
    mov ax,#0x0800      ! AH=8 is get drive parameters
    int 0x13
    mov ch,#0x00
    seg cs
    mov sectors,cx
    mov ax,#INITSEG
    mov es,ax

! Print some inane message
    mov ah,#0x03        ! read cursor pos  **//讀游標位置**
    xor bh,bh
    int 0x10          **//顯示字元的BIOS中斷** 
    mov cx,#24  **//輸出的字元數**
    mov bx,#0x0007      ! page 0, attribute 7 (normal)**//7是顯示屬性**
    mov bp,#msg1 **//bp要顯示的字元在記憶體中的位置,msgl內容在程式碼末**
    mov ax,#0x1301      ! write string, move cursor
    int 0x10     **//顯示字元的BIOS中斷**

! ok, we've written the message, now
! we want to load the system (at 0x10000)
    mov ax,#SYSSEG
    mov es,ax       ! segment of 0x010000
    call    read_it  **//讀入system模組**
    call    kill_motor

! After that we check which root-device to use. If the device is
! defined (!= 0), nothing is done and the given device is used.
! Otherwise, either /dev/PS0 (2,28) or /dev/at0 (2,8), depending
! on the number of sectors that the BIOS reports currently.
    seg cs
    mov ax,root_dev
    cmp ax,#0
    jne root_defined
    seg cs
    mov bx,sectors
    mov ax,#0x0208      ! /dev/ps0 - 1.2Mb
    cmp bx,#15
    je  root_defined
    mov ax,#0x021c      ! /dev/PS0 - 1.44Mb
    cmp bx,#18
    je  root_defined

undef_root: 
    jmp undef_root  
root_defined: 
    seg cs
    mov root_dev,ax

! after that (everyting loaded), we jump to
! the setup-routine loaded directly after
! the bootblock:
    jmpi    0,SETUPSEG
! This routine loads the system at address 0x10000, making sure
! no 64kB boundaries are crossed. We try to load it as fast as
! possible, loading whole tracks whenever we can.
!
! in:   es - starting address segment (normally 0x1000)
!
sread:  .word 1+SETUPLEN    ! sectors read of current track
head:   .word 0         ! current head
track:  .word 0         ! current track

read_it:
    mov ax,es
    test ax,#0x0fff

die:     
    jne die         ! es must be at 64kB boundary
    xor bx,bx       ! bx is starting address within segment

rp_read: 
    mov ax,es
    cmp ax,#ENDSEG      ! have we loaded all yet?
    jb ok1_read                     //ENDSEG=SYSSEG+SYSSIZE
    ret        **//system模組可能很大,要跨越磁軌
    ....**





msg1:
    .byte 13,10
    .ascii "Loading system ..."
    .byte 13,10,13,10
.org 508
root_dev:
    .word ROOT_DEV
boot_flag:
    .word 0xAA55  **//扇區的最後兩個位元組 BIOS用以識別引導扇區**

2.作業系統啟動

setup模組

start:
! ok, the read went well so we get current cursor position and save it for
! posterity.
mov ax,#INITSEG ! this is done in bootsect already, but...
mov ds,ax
mov ah,#0x03    ! read cursor pos
xor bh,bh
int 0x10        ! save it in known place, con_init fetches
mov [0],dx      ! it from 0x90000.

mov ah,#0x88
int 0x15    **//BIOS中斷,獲得實體記憶體大小**
mov [2],ax  //擴充套件記憶體大小

! now we want to move to protected mode ...
    cli         ! no interrupts allowed !

! first we move the system to it's rightful place
    mov ax,#0x0000
    cld         ! 'direction'=0, movs moves forward

do_move: 
    mov es,ax       ! destination segment
    add ax,#0x1000
    cmp ax,#0x9000
    jz  end_move
    mov ds,ax       ! source segment
    sub di,di
    sub si,si
    mov     cx,#0x8000  // 要移動的長度
    rep
    movsw  
    jmp do_move
    **//es=0x0000, ds= 0x9000.     把0X9000開始的內容移動到0地址,那麼從0地址開始就是作業系統的內容.這樣從0地址開始是作業系統的內容**

進入保護模式

end_move:
mov ax,#SETUPSEG    ! right, forgot this at first. didn't work :-)
mov ds,ax
lidt    idt_48      ! load idt with 0,0
lgdt    gdt_48      ! load gdt with whatever appropriate **//設定保護模式下的中斷和定址**

! Well, now's the time to actually move into protected mode. To make
! things as simple as possible, we do no register set-up or anything,
! we let the gnu-compiled 32-bit programs do that. We just jump to
! absolute address 0x00000, in 32-bit protected mode.

    mov ax,#0x0001  ! protected mode (PE) bit
    lmsw    ax      ! This is it!
    jmpi    0,8     ! jmp offset 0 of segment 8 (cs)
**//jmpi 0,8很重要。在這裡定址方式發生了改變,由於之前的定址模式(cs左移4位+ip的方式,cs 和ip都是16位暫存器,最大定址空間20位,即1M)定址空間太少。接下來要切換到32位模式(保護模式)。 這個時候定址模式是查表**

這裡寫圖片描述

gdt:

    .word   0,0,0,0     ! dummy
    .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word   0x0000      ! base address=0
    .word   0x9A00      ! code read/exec
    .word   0x00C0      ! granularity=4096, 386

    .word   0x07FF      ! 8Mb - limit=2047 (2048*4096=8Mb)
    .word   0x0000      ! base address=0
    .word   0x9200      ! data read/write
    .word   0x00C0      ! granularity=4096, 386

這裡寫圖片描述

關於jmpi 0, 8:
gdt表中0x00c09A00000007FFF, 表示 地址將跳轉到0地址,0地址是system模組的地址開始處,到此setup模組的工作完成。
setup模組先後完成了讀取硬體引數,把system移動到0地址處,設定為保護模式,最後再把程式指標移動到0地址(這時已是system)處。

跳到system模組執行
system中的第一部分程式碼head.s

startup_32:
    movl $0x10,%eax
    mov %ax,%ds
    mov %ax,%es
    mov %ax,%fs
    mov %ax,%gs     //指向gdt的0x10項(資料段)
    lss stack_start,%esp //設定系統棧
    call setup_idt    //初始化idt表
    call setup_gdt    //初始化gdt表
    movl $0x10,%eax        # reload all the segment registers
    mov %ax,%ds     # after changing gdt. CS was already
    mov %ax,%es     # reloaded in 'setup_gdt'
    mov %ax,%fs
    mov %ax,%gs
    lss stack_start,%esp
    xorl %eax,%eax  

1:
    incl %eax       # check that A20 really IS enabled
    movl %eax,0x000000  # loop forever if it isn't
    cmpl %eax,0x100000
    je 1b
    movl %cr0,%eax      # check math chip
    andl $0x80000011,%eax  # Save PG,PE,ET
    orl $2,%eax        # set MP
    movl %eax,%cr0
    call check_x87
    jmp after_page_tables

設定了頁表之後after_page_tables

after_page_tables:
    pushl $0       # These are the parameters to main :-)
    pushl $0
    pushl $0
    pushl $L6      # return address for main, if it decides to.
    pushl $main
    jmp setup_paging   //設定頁表   結束後跳轉到main
    L6:  jmp L6         # main should never return here, but
                # just in case, we know what happens.

這裡寫圖片描述

接下來跳轉到main中,這就進入作業系統了。

3.作業系統介面

上層軟體如何與核心交流
這裡寫圖片描述

4.系統呼叫是如何實現的

不可以直接訪問核心的程式碼:核心中有許多重要的資料,比如root使用者的相關資料等,故不能隨意訪問。

如何實現不能隨意訪問核心程式碼?:這需要硬體來實現,比如有一種處理器的硬體設計
這裡寫圖片描述
把記憶體分為不同的區域, 通過段暫存器來區分程式屬於哪個段(核心段 or 使用者段)
DPL: destination privilege level 要訪問的目標區域的特權級
CPL: current privilege level 當前特權級
每次訪問時,都要檢查特權級。0是核心態,3是使用者態。只有DPL >= CPL時才能夠執行。
核心中的程式碼的特權級會被初始化為0,而使用者的程式碼的特權級是3

那通過怎樣的機制才能訪問核心程式碼?
仍然是硬體來實現,對於x86那就是中斷指令int。設計的某些中斷能進入核心。
int指令將使CS中的CPL改成0,那麼此時就能進入核心。
這裡寫圖片描述
規定int 0x80為系統呼叫中斷。 

總結一下:

這裡寫圖片描述 
應用程式如何呼叫系統呼叫:
這裡寫圖片描述