1. 程式人生 > >我是如何學習寫一個作業系統(四):作業系統之系統呼叫

我是如何學習寫一個作業系統(四):作業系統之系統呼叫

前言

最近有點事情,馬上要開學了,所以學習的腳步就慢下來了。這一篇主要是來說作業系統的系統呼叫的,像C語言的printf深入到內部就是一個有關螢幕輸出的系統呼叫

什麼是系統呼叫

之前提過作業系統是對硬體的抽象,也是軟硬體之間的一層。之前比如如果我們想要在螢幕上輸出一些字元,就需要一些指令操作,然後把資料放到視訊記憶體上。但是在有了作業系統後,就不需要這樣做,也不能這樣做了。這時候只要作業系統提供一個介面來讓我們完成這個任務

由作業系統實現提供的所有系統呼叫所構成的集合即程式介面或應用程式設計介面(Application Programming Interface,API)。是應用程式同系統之間的介面。

系統呼叫的實現

在硬體設計上,通過區分核心態和使用者態來把核心程式和使用者程式隔離開

CS暫存器最低的兩位為0即是核心態,為3是使用者態

但是系統呼叫的程式碼是處在核心態的,所以就需要提供一種方法來能夠讓使用者程式進入核心態來實現系統呼叫

在X86裡,INT指令就是硬體用來提供由使用者態進入核心態的方法,所以系統呼叫的實現就可以變為:

  • 由使用者程式發起一個INT指令,指明要呼叫的服務
  • 在作業系統裡寫出相應的中斷處理
  • 由作業系統根據使用者指明要呼叫的服務取執行相應的程式碼

內聯彙編

稍微說一下C裡的內聯彙編,以免之後忘記。

gcc的內聯彙編一般都是這個格式

asm ( 彙編指令
    : 輸出運算元     // 非必需
    : 輸入運算元     // 非必需
    : 其他被汙染的暫存器 // 非必需
    );
  1. 第一部分就是彙編指令

  2. 第二部分是輸出運算元,都是 "=?"(var) 的形式, var可以是任意記憶體變數(輸出結果會存到這個變數中), ?一般是下面這些識別符號 (表示內聯彙編中用什麼來代理這個運算元):

    a,b,c,d,S,D 分別代表 eax,ebx,ecx,edx,esi,edi 暫存器
    r 上面的暫存器的任意一個(誰閒著就用誰)
    m 記憶體
    i 立即數(常量,只用於輸入運算元)
    g 暫存器、記憶體、立即數 都行
    在彙編中用%序號來代表這些輸入/輸出運算元,序號從0開始。為了與運算元區分開來,暫存器用兩個%引出,如:%%eax

  3. 第三部分是是輸入運算元,都是 "?"(var) 的形式, ? 除了可以是上面的那些識別符號,還可以是輸出運算元的序號,表示用 var 來初始化該輸出運算元,上面的程式中 %0 和 %1 就是一個東西,初始化為 1(a的值)。

  4. 第四部分標出那些在彙編程式碼中修改了的、 又沒有在輸入/輸出列表中列出的暫存器, 這樣 gcc 就不會擅自使用這些"危險的"暫存器。 還可以用 "memory" 表示在內聯彙編中修改了記憶體, 之前快取在暫存器中的記憶體變數需要重新讀取。

    參考連結

Linux0.11裡對系統呼叫的程式碼實現

設定中斷

  • 首先需要對IDT設定中斷呼叫的處理函式
#define set_system_gate(n,addr) \
    _set_gate(&idt[n],15,3,addr)

#define _set_gate(gate_addr,type,dpl,addr) \
__asm__ ("movw %%dx,%%ax\n\t" \
    "movw %0,%%dx\n\t" \
    "movl %%eax,%1\n\t" \
    "movl %%edx,%2" \
    : \
    : "i" ((short) (0x8000+(dpl<<13)+(type<<8))), \
    "o" (*((char *) (gate_addr))), \
    "o" (*(4+(char *) (gate_addr))), \
    "d" ((char *) (addr)),"a" (0x00080000))

set_system_gate(0x80,&system_call);

實現中斷函式

  • sys_call_table[]是一個指標陣列,定義在include/linux/sys.h中,該指標陣列中設定了所有72個系統呼叫C處理函式地址。
system_call:
    cmpl $nr_system_calls-1,%eax
    ja bad_sys_call
    push %ds
    push %es
    push %fs
    pushl %edx
    pushl %ecx      # push %ebx,%ecx,%edx as parameters
    pushl %ebx      # to the system call
    movl $0x10,%edx     # set up ds,es to kernel space
    mov %dx,%ds
    mov %dx,%es
    movl $0x17,%edx     # fs points to local data space
    mov %dx,%fs
    call sys_call_table(,%eax,4)
    pushl %eax
    movl current,%eax
    cmpl $0,state(%eax)     # state
    jne reschedule
    cmpl $0,counter(%eax)       # counter
    je reschedule

提供介面

  • 在linux嚮應用程式提供系統呼叫介面write
  • _syscall3的本質上是一個巨集
_syscall3(int,write,int,fd,const char *,buf,off_t,count)
#define _syscall3(type,name,atype,a,btype,b,ctype,c) \
type name(atype a,btype b,ctype c) \
{ \
long __res; \
__asm__ volatile ("int $0x80" \
    : "=a" (__res) \
    : "0" (__NR_##name),"b" ((long)(a)),"c" ((long)(b)),"d" ((long)(c))); \
if (__res>=0) \
    return (type) __res; \
errno=-__res; \
return -1; \
}

小結

這樣對於一個系統呼叫就會變成

printf 使用者呼叫

int 0x80 庫函式的實現


進入核心


system_call 中斷呼叫

sys_ 系統呼叫