我是如何學習寫一個作業系統(四):作業系統之系統調用
- 2019 年 10 月 3 日
- 筆記
前言
最近有點事情,馬上要開學了,所以學習的腳步就慢下來了。這一篇主要是來說作業系統的系統調用的,像C語言的printf深入到內部就是一個有關螢幕輸出的系統調用
什麼是系統調用
之前提過作業系統是對硬體的抽象,也是軟硬體之間的一層。之前比如如果我們想要在螢幕上輸出一些字元,就需要一些指令操作,然後把數據放到顯示記憶體上。但是在有了作業系統後,就不需要這樣做,也不能這樣做了。這時候只要作業系統提供一個介面來讓我們完成這個任務
由作業系統實現提供的所有系統調用所構成的集合即程式介面或應用編程介面(Application Programming Interface,API)。是應用程式同系統之間的介面。
系統調用的實現
在硬體設計上,通過區分內核態和用戶態來把內核程式和用戶程式隔離開
CS暫存器最低的兩位為0即是內核態,為3是用戶態
但是系統調用的程式碼是處在內核態的,所以就需要提供一種方法來能夠讓用戶程式進入內核態來實現系統調用
在X86里,INT指令就是硬體用來提供由用戶態進入內核態的方法,所以系統調用的實現就可以變為:
- 由用戶程式發起一個INT指令,指明要調用的服務
- 在作業系統里寫出相應的中斷處理
- 由作業系統根據用戶指明要調用的服務取執行相應的程式碼
內聯彙編
稍微說一下C里的內聯彙編,以免之後忘記。
gcc的內聯彙編一般都是這個格式
asm ( 彙編指令 : 輸出操作數 // 非必需 : 輸入操作數 // 非必需 : 其他被污染的暫存器 // 非必需 );
-
第一部分就是彙編指令
-
第二部分是輸出操作數,都是 "=?"(var) 的形式, var可以是任意記憶體變數(輸出結果會存到這個變數中), ?一般是下面這些標識符 (表示內聯彙編中用什麼來代理這個操作數):
a,b,c,d,S,D 分別代表 eax,ebx,ecx,edx,esi,edi 暫存器
r 上面的暫存器的任意一個(誰閑著就用誰)
m 記憶體
i 立即數(常量,只用於輸入操作數)
g 暫存器、記憶體、立即數 都行
在彙編中用%序號來代表這些輸入/輸出操作數,序號從0開始。為了與操作數區分開來,暫存器用兩個%引出,如:%%eax -
第三部分是是輸入操作數,都是 "?"(var) 的形式, ? 除了可以是上面的那些標識符,還可以是輸出操作數的序號,表示用 var 來初始化該輸出操作數,上面的程式中 %0 和 %1 就是一個東西,初始化為 1(a的值)。
-
第四部分標出那些在彙編程式碼中修改了的、 又沒有在輸入/輸出列表中列出的暫存器, 這樣 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,%%axnt" "movw %0,%%dxnt" "movl %%eax,%1nt" "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_ 系統調用