《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書(shū)筆記(五)- 系統(tǒng)調(diào)用
主要內(nèi)容:
- 什么是系統(tǒng)調(diào)用
- Linux上的系統(tǒng)調(diào)用實(shí)現(xiàn)原理
- 一個(gè)簡(jiǎn)單的系統(tǒng)調(diào)用的實(shí)現(xiàn)
1. 什么是系統(tǒng)調(diào)用
簡(jiǎn)單來(lái)說(shuō),系統(tǒng)調(diào)用就是用戶程序和硬件設(shè)備之間的橋梁。
用戶程序在需要的時(shí)候,通過(guò)系統(tǒng)調(diào)用來(lái)使用硬件設(shè)備。
系統(tǒng)調(diào)用的存在,有以下重要的意義:
1)用戶程序通過(guò)系統(tǒng)調(diào)用來(lái)使用硬件,而不用關(guān)心具體的硬件設(shè)備,這樣大大簡(jiǎn)化了用戶程序的開(kāi)發(fā)。
比如:用戶程序通過(guò)write()系統(tǒng)調(diào)用就可以將數(shù)據(jù)寫(xiě)入文件,而不必關(guān)心文件是在磁盤(pán)上還是軟盤(pán)上,或者其他存儲(chǔ)上。
2)系統(tǒng)調(diào)用使得用戶程序有更好的可移植性。
只要操作系統(tǒng)提供的系統(tǒng)調(diào)用接口相同,用戶程序就可在不用修改的情況下,從一個(gè)系統(tǒng)遷移到另一個(gè)操作系統(tǒng)。
3)系統(tǒng)調(diào)用使得內(nèi)核能更好的管理用戶程序,增強(qiáng)了系統(tǒng)的穩(wěn)定性。
因?yàn)橄到y(tǒng)調(diào)用是內(nèi)核實(shí)現(xiàn)的,內(nèi)核通過(guò)系統(tǒng)調(diào)用來(lái)控制開(kāi)放什么功能及什么權(quán)限給用戶程序。
這樣可以避免用戶程序不正確的使用硬件設(shè)備,從而破壞了其他程序。
4)系統(tǒng)調(diào)用有效的分離了用戶程序和內(nèi)核的開(kāi)發(fā)。
用戶程序只需關(guān)心系統(tǒng)調(diào)用API,通過(guò)這些API來(lái)開(kāi)發(fā)自己的應(yīng)用,不用關(guān)心API的具體實(shí)現(xiàn)。
內(nèi)核則只要關(guān)心系統(tǒng)調(diào)用API的實(shí)現(xiàn),而不必管它們是被如何調(diào)用的。
用戶程序,系統(tǒng)調(diào)用,內(nèi)核,硬件設(shè)備的調(diào)用關(guān)系如下圖:
2. Linux上的系統(tǒng)調(diào)用實(shí)現(xiàn)原理
要想實(shí)現(xiàn)系統(tǒng)調(diào)用,主要實(shí)現(xiàn)以下幾個(gè)方面:
- 通知內(nèi)核調(diào)用一個(gè)哪個(gè)系統(tǒng)調(diào)用
- 用戶程序把系統(tǒng)調(diào)用的參數(shù)傳遞給內(nèi)核
- 用戶程序獲取內(nèi)核返回的系統(tǒng)調(diào)用返回值
下面看看Linux是如何實(shí)現(xiàn)上面3個(gè)功能的。
2.1 通知內(nèi)核調(diào)用一個(gè)哪個(gè)系統(tǒng)調(diào)用
每個(gè)系統(tǒng)調(diào)用都有一個(gè)系統(tǒng)調(diào)用號(hào),系統(tǒng)調(diào)用發(fā)生時(shí),內(nèi)核就是根據(jù)傳入的系統(tǒng)調(diào)用號(hào)來(lái)知道是哪個(gè)系統(tǒng)調(diào)用的。
在x86架構(gòu)中,用戶空間將系統(tǒng)調(diào)用號(hào)是放在eax中的,系統(tǒng)調(diào)用處理程序通過(guò)eax取得系統(tǒng)調(diào)用號(hào)。
系統(tǒng)調(diào)用號(hào)定義在內(nèi)核代碼:arch/alpha/include/asm/unistd.h 中,可以看出linux的系統(tǒng)調(diào)用不是很多。
2.2 用戶程序把系統(tǒng)調(diào)用的參數(shù)傳遞給內(nèi)核
系統(tǒng)調(diào)用的參數(shù)也是通過(guò)寄存器傳給內(nèi)核的,在x86系統(tǒng)上,系統(tǒng)調(diào)用的前5個(gè)參數(shù)放在ebx,ecx,edx,esi和edi中,如果參數(shù)多的話,還需要用個(gè)單獨(dú)的寄存器存放指向所有參數(shù)在用戶空間地址的指針。
一般的系統(tǒng)調(diào)用都是通過(guò)C庫(kù)(最常用的是glibc庫(kù))來(lái)訪問(wèn)的,Linux內(nèi)核提供一個(gè)從用戶程序直接訪問(wèn)系統(tǒng)調(diào)用的方法。
參見(jiàn)內(nèi)核代碼:arch/cris/include/arch-v10/arch/unistd.h
里面定義了6個(gè)宏,分別可以調(diào)用參數(shù)個(gè)數(shù)為0~6的系統(tǒng)調(diào)用
_syscall0(type,name)
_syscall1(type,name,type1,arg1)
_syscall2(type,name,type1,arg1,type2,arg2)
_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)
_syscall4(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4)
_syscall5(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5)
_syscall6(type,name,type1,arg1,type2,arg2,type3,arg3,type4,arg4,type5,arg5,type6,arg6)
超過(guò)6個(gè)參數(shù)的系統(tǒng)調(diào)用很罕見(jiàn),所以這里只定義了6個(gè)。
2.3 用戶程序獲取內(nèi)核返回的系統(tǒng)調(diào)用返回值
獲取系統(tǒng)調(diào)用的返回值也是通過(guò)寄存器,在x86系統(tǒng)上,返回值放在eax中。
3. 一個(gè)簡(jiǎn)單的系統(tǒng)調(diào)用的實(shí)現(xiàn)
了解了Linux上系統(tǒng)調(diào)用的原理,下面就可以自己來(lái)實(shí)現(xiàn)一個(gè)簡(jiǎn)單的系統(tǒng)調(diào)用。
3.1 環(huán)境準(zhǔn)備
為了不破壞現(xiàn)有系統(tǒng),我是用虛擬機(jī)來(lái)實(shí)驗(yàn)的。
主機(jī):fedora16 x86_64系統(tǒng) + kvm(一種虛擬技術(shù),就像virtualbox,vmware等)
虛擬機(jī): 也是安裝fedora16 x86_64系統(tǒng)(通過(guò)virt-manager很容易安裝一個(gè)系統(tǒng))
下載內(nèi)核源碼:www.kernel.org 下載最新的就行
3.2 修改內(nèi)核源碼中的相應(yīng)文件
主要修改以下文件:
arch/x86/ia32/ia32entry.S
arch/x86/include/asm/unistd_32.h
arch/x86/include/asm/unistd_64.h
arch/x86/kernel/syscall_table_32.S
include/asm-generic/unistd.h
include/linux/syscalls.h
kernel/sys.c
我在sys.c中追加了2個(gè)函數(shù):sys_foo和sys_bar
如果是在x86_64的內(nèi)核中增加一個(gè)系統(tǒng)調(diào)用,只需修改 arch/x86/include/asm/unistd_64.h,比如sys_bar。
修改內(nèi)容參見(jiàn)下面的diff文件:
diff -r new/arch/x86/ia32/ia32entry.S old/arch/x86/ia32/ia32entry.S 855d854 < .quad sys_foo diff -r new/arch/x86/include/asm/unistd_32.h old/arch/x86/include/asm/unistd_32.h 357d356 < #define __NR_foo 349 361c360 < #define NR_syscalls 350 --- > #define NR_syscalls 349 diff -r new/arch/x86/include/asm/unistd_64.h old/arch/x86/include/asm/unistd_64.h 689,692d688 < #define __NR_foo 312 < __SYSCALL(__NR_foo, sys_foo) < #define __NR_bar 313 < __SYSCALL(__NR_bar, sys_bar) diff -r new/arch/x86/kernel/syscall_table_32.S old/arch/x86/kernel/syscall_table_32.S 351d350 < .long sys_foo diff -r new/include/asm-generic/unistd.h old/include/asm-generic/unistd.h 694,695d693 < #define __NR_foo 272 < __SYSCALL(__NR_foo, sys_foo) 698c696 < #define __NR_syscalls 273 --- > #define __NR_syscalls 272 diff -r new/kernel/sys.c old/kernel/sys.c 1920,1928d1919 < < asmlinkage long sys_foo(void) < { < return 1112223334444555; < } < asmlinkage long sys_bar(void) < { < return 1234567890; < }
3.3 編譯內(nèi)核
#cd linux-3.2.28 #make menuconfig (選擇要編譯參數(shù),如果不熟悉內(nèi)核編譯,用默認(rèn)選項(xiàng)即可) #make all (這一步真的時(shí)間很長(zhǎng)......) #make modules_install #make install (這一步會(huì)把新的內(nèi)核加到啟動(dòng)項(xiàng)中) #reboot (重啟系統(tǒng)進(jìn)入新的內(nèi)核)
3.4 編寫(xiě)調(diào)用的系統(tǒng)調(diào)用的代碼
#include <unistd.h> #include <sys/syscall.h> #include <string.h> #include <stdio.h> #include <errno.h> #define __NR_foo 312 #define __NR_bar 313 int main() { printf ("result foo is %ld\n", syscall(__NR_foo)); printf("%s\n", strerror(errno)); printf ("result bar is %ld\n", syscall(__NR_bar)); printf("%s\n", strerror(errno)); return 0; }
編譯運(yùn)行上面的代碼:
#gcc test.c -o test #./test
運(yùn)行結(jié)果如下:
result foo is 1112223334444555 Success result bar is 1234567890 Success


浙公網(wǎng)安備 33010602011771號(hào)