天天看點

《Linux核心設計與實作》讀書筆記(五)- 系統調用

主要内容:

  1. 什麼是系統調用
  2. Linux上的系統調用實作原理
  3. 一個簡單的系統調用的實作

1. 什麼是系統調用

簡單來說,系統調用就是使用者程式和硬體裝置之間的橋梁。

使用者程式在需要的時候,通過系統調用來使用硬體裝置。

系統調用的存在,有以下重要的意義:

1)使用者程式通過系統調用來使用硬體,而不用關心具體的硬體裝置,這樣大大簡化了使用者程式的開發。

    比如:使用者程式通過write()系統調用就可以将資料寫入檔案,而不必關心檔案是在磁盤上還是軟碟上,或者其他存儲上。

2)系統調用使得使用者程式有更好的可移植性。

    隻要作業系統提供的系統調用接口相同,使用者程式就可在不用修改的情況下,從一個系統遷移到另一個作業系統。

3)系統調用使得核心能更好的管理使用者程式,增強了系統的穩定性。

    因為系統調用是核心實作的,核心通過系統調用來控制開放什麼功能及什麼權限給使用者程式。

    這樣可以避免使用者程式不正确的使用硬體裝置,進而破壞了其他程式。

4)系統調用有效的分離了使用者程式和核心的開發。

    使用者程式隻需關心系統調用API,通過這些API來開發自己的應用,不用關心API的具體實作。

    核心則隻要關心系統調用API的實作,而不必管它們是被如何調用的。

使用者程式,系統調用,核心,硬體裝置的調用關系如下圖:

《Linux核心設計與實作》讀書筆記(五)- 系統調用

2. Linux上的系統調用實作原理

要想實作系統調用,主要實作以下幾個方面:

  1. 通知核心調用一個哪個系統調用
  2. 使用者程式把系統調用的參數傳遞給核心
  3. 使用者程式擷取核心傳回的系統調用傳回值

下面看看Linux是如何實作上面3個功能的。

2.1 通知核心調用一個哪個系統調用

每個系統調用都有一個系統調用号,系統調用發生時,核心就是根據傳入的系統調用号來知道是哪個系統調用的。

在x86架構中,使用者空間将系統調用号是放在eax中的,系統調用處理程式通過eax取得系統調用号。

系統調用号定義在核心代碼:arch/alpha/include/asm/unistd.h 中,可以看出linux的系統調用不是很多。

2.2 使用者程式把系統調用的參數傳遞給核心

系統調用的參數也是通過寄存器傳給核心的,在x86系統上,系統調用的前5個參數放在ebx,ecx,edx,esi和edi中,如果參數多的話,還需要用個單獨的寄存器存放指向所有參數在使用者空間位址的指針。

一般的系統調用都是通過C庫(最常用的是glibc庫)來通路的,Linux核心提供一個從使用者程式直接通路系統調用的方法。

參見核心代碼:arch/cris/include/arch-v10/arch/unistd.h

裡面定義了6個宏,分别可以調用參數個數為0~6的系統調用

_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)      

超過6個參數的系統調用很罕見,是以這裡隻定義了6個。

2.3 使用者程式擷取核心傳回的系統調用傳回值

擷取系統調用的傳回值也是通過寄存器,在x86系統上,傳回值放在eax中。

3. 一個簡單的系統調用的實作

了解了Linux上系統調用的原理,下面就可以自己來實作一個簡單的系統調用。

3.1 環境準備

為了不破壞現有系統,我是用虛拟機來實驗的。

主機:fedora16 x86_64系統 + kvm(一種虛拟技術,就像virtualbox,vmware等)

虛拟機: 也是安裝fedora16 x86_64系統(通過virt-manager很容易安裝一個系統)

下載下傳核心源碼:www.kernel.org  下載下傳最新的就行

3.2 修改核心源碼中的相應檔案

主要修改以下檔案:

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個函數:sys_foo和sys_bar

如果是在x86_64的核心中增加一個系統調用,隻需修改 arch/x86/include/asm/unistd_64.h,比如sys_bar。

修改内容參見下面的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 編譯核心

#cd linux-3.2.28
#make menuconfig  (選擇要編譯參數,如果不熟悉核心編譯,用預設選項即可)
#make all  (這一步真的時間很長......)
#make modules_install
#make install  (這一步會把新的核心加到啟動項中)
#reboot  (重新開機系統進入新的核心)      

3.4 編寫調用的系統調用的代碼

#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;
}      

編譯運作上面的代碼:

#gcc test.c -o test
#./test      

運作結果如下:

result foo is 1112223334444555
Success
result bar is 1234567890
Success      

繼續閱讀