1. 程式人生 > >作業系統基礎之系統呼叫

作業系統基礎之系統呼叫

1.使用者態和核心態

使用者程式是如何呼叫核心程式的呢?考慮實現下面的一個whoami的系統呼叫:
這裡寫圖片描述
在核心中100地址處有一個使用者“lizhijun”,whoami函式的功能是要打印出這個使用者名稱,那可以直接打印出100地址處的內容嗎?答案當然是否定的,因為使用者程式不能隨意的訪問核心程式,核心態可以訪問任何資料,使用者態卻不能訪問核心資料,這是一種處理器的硬體設計所決定的,如下圖:
這裡寫圖片描述
判斷一段程式能不能訪問另一段是根據程式的特權級來判斷的,DPL 是指目標程式的特權級(核心態程式碼DPL存在於GDT表中,在head.s已經初始化好),CPL指當前程式的特權級,當前程式執行在什麼態,使用CS的最低兩位來表示的,0表示核心態,3表示使用者態,當DPL大於CPL時,當前程式可訪問目標程式。

2.主動進入核心的方法

首先硬體提供了主動進入核心的方法,對於intel x86而言,就是中斷指令,通過中斷指令將CS的CPL改為0,從而進入核心,這是使用者程式呼叫核心程式碼的唯一方法。
這裡寫圖片描述

2.1系統呼叫的實現

以c語言中printf函式為例,printf函式實際上是呼叫了核心的write函式,庫函式write最終通過巨集展開成包含指令int 0x80的程式碼,從而進入核心執行核心的write函式:
這裡寫圖片描述
那麼具體流程是怎樣的呢?當呼叫庫函式write時,執行下面的程式碼:
這裡寫圖片描述
展開後的程式碼如下:

int write(int fd, const char *buf, off_t count)
{  long   _res;\
  _ _asm__ volatile("int 0x80":"=a"(_res):" "(__NR_write), "b"(long(fd)),"c"((long)(*buf)), "d"((long)(count))); if(_res>=0) return (int)_res; error = -_res; return -1;}

程式碼是內嵌彙編程式碼,一共3條語句,分別用:隔開,第一條是中斷指令,第二條是輸出,是指把_res的內容付給eax暫存器,第三條是輸入,是指把三個引數和_NR_write的值分別付給ebx 、ecx、edx和eax暫存器,首先執行第三條語句,然後執行中斷指令,最後執行輸出指令,然後再執行c語言的return語句。總的來說就是將系統呼叫號(NR_write)置給eax,然後呼叫中斷指令進入核心。
那麼呼叫中斷又是怎麼進入核心呢?首先呼叫中斷時需要查詢idt表,早在系統初始化時就已經初始化好了idt表,初始化函式(set_system_gate(0x80,&system_call);)做了如下操作:
這裡寫圖片描述


將0x80號中斷表的表項賦值:DPL賦值為3,&system_call賦給處理函式入口偏移,段選擇符賦為0x0008,即CS=8,IP = &system_call,(通過CS=8找到gdt表中的核心程式碼段,同jmpi 0,8的那個用法,而且8的最後一位為0,所以CPL被置為0),然後跳轉到核心區域執行。
接下來就呼叫system_call函式:
這裡寫圖片描述
該函式首先做了一些鋪設操作,然後將資料段暫存器賦值為0x10,現在資料指標和程式碼指標都指向了核心區域,因此接下來就真正進入核心操作,執行call _sys_call_table(,%eax,4)指令,_sys_call_table是一個系統呼叫表的起始地址,通過地址和eax的值找到要呼叫的系統呼叫的真正地址,即_sys_call_table+4*%eax,eax中是之前存入的NR_write(4),找到表中的第4個元素:
這裡寫圖片描述
確實為sys_write,因此接下來就呼叫sys_write,此函式就是真正要做寫操作的函數了。
總結一下,就是如下圖:
這裡寫圖片描述
當從第一個框圖到第二框圖時,此時CPL=3,在初始化時,又將int 0x80指令的DPL做成了3,因此可以調到執行int 0x80指令。