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


前言

最近有點事情,馬上要開學了,所以學習的腳步就慢下來了。這一篇主要是來說操作系統的系統調用的,像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_ 系統調用


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM