一步步编写操作系统 67 系统调用的实现1-2 68
接上文:
系統調用的子功能要用eax寄存器來指定,所以咱們要看看有哪些系統調用啦,在linux系統中,系統調用是定義在/usr/include/asm/unistd.h文件中,該文件只是個統一的入口,指向了32位和64位兩種版本。在asm目錄下提供了這兩個版本,文件名分別是unistd_32.h 和unistd_64.h,這里給大家摘錄了部分32位x86平臺下的unistd_32.h文件,見圖
?
在/usr/include/asm/unistd_32.h文件中共定義了348個系統調用,哦,給大家說一下,我用的linux版本是CentOS release 6.3 (Final),不知道新版本內核中是否增加了新的系統調用功能。
我們要用的系統調用是第4號調用,即__NR_write。不要被它前面的兩個下滑線嚇到,就是個命名而已,它代表就是我們所說的write系統調用。
如果不知道某個系統調用的用法,可以用man命令來查看,方法是man 2 系統調用名。咱們執行man 2 write看看,見圖
?
man后面的數字2是表示查看System Calls方面的幫助,對于man自己的幫助信息,man命令也可以man自己,可以用man man來查看。上圖只是部分幫助信息,咱們了解這些就夠用了。write的功能是把buf指向的緩沖區中的count個字節寫入fd指向的文件描述符,執行成功后返回寫入的字節數,失敗則返回-1。
如果在c語言中調用write的話,直接代入實參就行了,這是最簡單的方式,如代碼c_syscall.c:
#include <unistd.h> int main(){write(1,"hello,world\n",4);return 0; }為了使用c標準庫中的write函數,文件開頭包含了標準頭文件unistd.h,通過該函數可以使用系統的write系統調用,該文件在磁盤上的路徑是/usr/include/unistd.h。不過在本機上測試發現不包含unistd.h,其編譯、運行都沒問題,也許這和隱式聲明有關,這里不再深究。
調用“系統調用”有兩種方式:
以上的c代碼就是用的第一種方式,不知道您是否對write函數的內部實現感興趣,其實我也沒研究過,不過萬變不離其宗,核心思想是必須與進行內核溝通才能獲得內核提供的功能。所以,write內部封裝的一定是系統調用指令,按照這種設想,下次咱們會模擬一下它的實現。
?
調用“系統調用”有兩種方式:
以上的c代碼就是用的第一種方式,不知道您是否對write函數的內部實現感興趣,其實我也沒研究過,不過萬變不離其宗,核心思想是必須與進行內核溝通才能獲得內核提供的功能。所以,write內部封裝的一定是系統調用指令,按照這種設想,一會咱們會模擬一下它的實現。
我們這里要介紹下第二種:跨過庫函數直接與系統內核通信,這樣最終的程序是不需要與任何庫文件鏈接,這是獲得系統功能效率最高的方式。
我相信,如果曾經學過匯編語言,老師都給咱們演示過第二種方式,但大多數同學還是覺得云里霧里,即使照葫蘆畫瓢完成了打印字符串的工作,也有部分同學不清楚自己在做什么,所以我在這里盡量多說一點。
前面我們已經知道了write系統調用函數的c語言使用方式,我們要用匯編代碼直接與內核通信該怎么做?我們要看看系統調用輸入參數的傳遞方式。
當輸入的參數小于等于5個時,linux是用寄存器傳遞參數。當參數個數大于5個時,把參數按照順序放入連續的內存區域,并將該區域的首地址放到ebx寄存器。這里我們只演示參數小于等于5個的情況。
eax寄存器用來存儲子功能號(寄存器eip、ebp、esp是不能使用的)。5個參數是存放在以下寄存器中,傳送參數的順序是:
好啦,理論知識夠用啦,現在趕緊實踐一把,見以下代碼syscall_write.S
1 section .data2 str_c_lib: db "c library says: hello world!", 0xa ;0xa為LF ascii碼3 str_c_lib_len equ $-str_c_lib45 str_syscall: db "syscall says: hello world!", 0xa6 str_syscall_len equ $-str_syscall78 section .text9 global _start10 _start:11 ;;;;;;;;;;;;; 方式1: 模擬c語言中系統調用庫函數write ;;;;;;;;;;;;;12 push str_c_lib_len ;按照c調用約定壓入參數13 push str_c_lib14 push 11516 call simu_write ;調用下面定義的simu_write17 add esp,12 ;回收棧空間1819 ;;;;;;;;;;;;; 方式2: 跨過庫函數,直接進行系統調用 ;;;;;;;;;;;;;20 mov eax,4 ;第4號子功能是write系統調用(不是c庫函數write)21 mov ebx, 122 mov ecx, str_syscall23 mov edx, str_syscall_len24 int 0x80 ;發起中斷,通知linux完成請求的功能。2526 ;;;;;;;;;;;;; 退出程序 ;;;;;;;;;;;27 mov eax,1 ;第1號子功能是exit28 int 0x80 ;發起中斷,通知linux完成請求的功能。2930 ;;;;;;;下面自定義的simu_write用來模擬c庫中系統調用函數write,;;;;;;這里模擬它的實現原理31 simu_write:32 push ebp ;備份ebp33 mov ebp,esp34 mov eax,4 ;第4號子功能是write系統調用(不是c庫函數write) 35 mov ebx, [ebp+8] ;第1個參數36 mov ecx, [ebp+12] ;第2個參數37 mov edx, [ebp+16] ;第3個參數38 int 0x80 ;發起中斷,通知linux完成請求的功能39 pop ebp ;恢復ebp40 ret代碼syscall_write.S中,我們演示了系統調用的兩種方式。程序開頭定義了兩種方式下打印的字符串,其中0xa為LF(LineFeed)ascii碼,這樣就會輸出一個換行符。
第11~17行是在演示方式1,模擬調用c庫函數write的方式。因為write是c庫函數,按一般的做法是,匯編程序需要與c代碼生成的目標文件鏈接才能調用c的代碼。在這個例子中我們并沒有這樣幫做,因為我想讓大家了解write函數的本質,所以,在這里為大家定義了simu_write來代替c庫函數write,用它來簡單解釋write的原理,它定義在31~40行。這里是按照c調用約定將參數從右到左依次入棧,隨后調用simu_write實現字符串打印功能。
第19~24行是在演示第2種系統調用的方式,這是最簡單直接可依賴的方式。20~24行是在eax中賦予子功能號、將參數按照順序依次寫入對應的寄存器。
第31~40行是simu_write的實現,它內部在本質上是和第2種方式一樣,都是在內部調用int指令直接和系統通信實現系統調用。此函數只是為了試圖揭開c庫函數的實現原理,良苦用心您懂的。
好啦,編譯鏈接過程如下:
nasm -f elf -o syscall_write.o syscall_write.S其中-f參數是用來指定編譯輸出的文件格式,這里需要指定為elf,目的是將來要和gcc編譯的elf格式的目標文件鏈接,所以格式必須相同。nasm輸出為目標文件,已經用-o指定文件名為syscall_write.o。
最后用ld程序將syscall_write.o鏈接成elf格式的二進制可執行文件。
ld -o syscall_write.bin syscall_write.o程序執行后的效果如圖
?
順便說一句,syscall_write.bin如果因為權限不足而無法執行時,可以用以下指令增加執行權限:
chmod u+x syscall_write.bin
本文摘自《操作系統真象還原》,請大家支持正版,多謝。
總結
以上是生活随笔為你收集整理的一步步编写操作系统 67 系统调用的实现1-2 68的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 42℃让火炉吐鲁番汗颜!河南为什么热成了
- 下一篇: 一步步编写操作系统 45 用c语言编写内