我是如何學習寫一個操做系統(四):操做系統之系統調用

前言

最近有點事情,立刻要開學了,因此學習的腳步就慢下來了。這一篇主要是來講操做系統的系統調用的,像C語言的printf深刻到內部就是一個有關屏幕輸出的系統調用linux

什麼是系統調用

以前提過操做系統是對硬件的抽象,也是軟硬件之間的一層。以前好比若是咱們想要在屏幕上輸出一些字符,就須要一些指令操做,而後把數據放到顯存上。可是在有了操做系統後,就不須要這樣作,也不能這樣作了。這時候只要操做系統提供一個接口來讓咱們完成這個任務git

由操做系統實現提供的全部系統調用所構成的集合即程序接口或應用編程接口(Application Programming Interface,API)。是應用程序同系統之間的接口。github

系統調用的實現

在硬件設計上,經過區份內核態和用戶態來把內核程序和用戶程序隔離開編程

CS寄存器最低的兩位爲0便是內核態,爲3是用戶態數組

可是系統調用的代碼是處在內核態的,因此就須要提供一種方法來可以讓用戶程序進入內核態來實現系統調用緩存

在X86裏,INT指令就是硬件用來提供由用戶態進入內核態的方法,因此係統調用的實現就能夠變爲:函數

  • 由用戶程序發起一個INT指令,指明要調用的服務
  • 在操做系統裏寫出相應的中斷處理
  • 由操做系統根據用戶指明要調用的服務取執行相應的代碼

內聯彙編

稍微說一下C裏的內聯彙編,以避免以後忘記。學習

gcc的內聯彙編通常都是這個格式spa

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_ 系統調用

相關文章
相關標籤/搜索