天天看點

Linux系統調用

    1. 方法之四:以功能為中心,各個擊破 

        從功能上看,整個Linux系統可看作有一下幾個部分組成:

      1. 程序管理機制部分;
      2. 記憶體管理機制部分; 
      3. 檔案系統部分; 
      4. 硬體驅動部分; 
      5. 系統調用部分等;

在這五個功能部件中,系統調用是使用者程式或操作調用核心所提供的功能的接口;也是分析Linux核心源碼幾個很好的入口點之一。

 http://www.yesky.com/20010813/192117_3.shtml

與系統調用相關的内容主要有:系統調用總控程式,系統調用向量表sys_call_table,以及各系統調用服務程式。

    1. 保護模式下的初始化過程中,設定并初始化idt,共256個入口,服務程式均為ignore_int, 該服務程式僅列印“Unknown interruptn”。(源碼參見/Arch/i386/KERNEL/head.S檔案;相關内容可參見 保護模式下的初始化部分)
    2. 在系統初始化完成後運作的第一個核心程式start_kernel中,通過調用 trap_init函數,把各自陷和中斷服務程式的入口位址設定到 idt 表中;同時,此函數還通過調用函數set_system_gate 把系統調用總控程式的入口位址挂在中斷0x80上。其中:
  • start_kernel的原型為void __init start_kernel(void) ,其源碼在檔案 init/main.c中;
  • trap_init函數的原型為void __init trap_init(void),定義在arch/i386/kernel/traps.c 中
  • 函數set_system_gate同樣定義在arch/i386/kernel/traps.c 中,調用原型為set_system_gate(SYSCALL_VECTOR,&system_call);
  • 其中,SYSCALL_VECTOR是定義在 linux/arch/i386/kernel/irq.h中的一個常量0x80;
  • 而 system_call 即為系統調用總控程式的入口位址;中斷總控程式用彙編語言定義在arch/i386/kernel/entry.S中。
  • 系統調用向量表sys_call_table, 是一個含有NR_syscalls=256個單元的數組。它的每個單元存放着一個系統調用服務程式的入口位址。該數組定義在/arch/i386/kernel/entry.S中;而NR_syscalls則是一個等于256的宏,定義在include/linux/sys.h中。
  • 各系統調用服務程式則分别定義在各個子產品的相應檔案中;例如asmlinkage int sys_time(int * tloc)就定義在kerneltime.c中;另外,在kernelsys.c中也有不少服務程式;

系統調用個數:

從2.4的190個到2.6的300多個

Linux系統調用

eax是寄存器

可見,系統調用的進入課分為“使用者程式 系統調用總控程式”和“系統調用總控程式各個服務程式”兩部分

III、系統調用總控程式(system_call)

系統調用總控程式(system_call)可參見arch/i386/kernel/entry.S其執行流程如下圖:

Linux系統調用

由以上的分析可知,增加系統調用由于下兩種方法: 

i.編一個新的服務例程,将它的入口位址加入到sys_call_table的某一項,隻要該項的原服務例程是sys_ni_syscall,并且是sys_ni_syscall的作用屬于第三種的項,也即Nr 137, Nr 188, Nr 189。       

我覺得,推薦下面這種:

ii.直接增加:

編一個新的服務例程; 

在sys_call_table中添加一個新項, 并把的新增加的服務例程的入口位址加到sys_call_table表中的新項中; 

把增加的 sys_call_table 表項所對應的向量, 在include/asm-386/unistd.h 中進行必要申明,以供使用者程序和其他系統程序查詢或調用。 

由于在标準的c語言庫中沒有新系統調用的承接段,是以,在測試程式中,除了要#include ,還要申明如下 _syscall1(int,additionSysCall,int, num)。      

 下面将對第ii種情況列舉一個我曾經實作過了的一個增加系統調用的執行個體: 

1.)在kernel/sys.c中增加新的系統服務例程如下: 

asmlinkage int sys_addtotal(int numdata) 

{ 

int i=0,enddata=0; 

while(i<=numdata) 

enddata+=i++; 

return enddata; 

}       

該函數有一個 int 型入口參數 numdata , 并傳回從 0 到 numdata 的累加值; 當然也可以把系統服務例程放在一個自己定義的檔案或其他檔案中,隻是要在相應檔案中作必要的說明; 

2.)把 asmlinkage int sys_addtotal( int) 的入口位址加到sys_call_table表中: 

arch/i386/kernel/entry.S 中的最後幾行源代碼修改前為: 

... ... 

.long SYMBOL_NAME(sys_sendfile) 

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */ 

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */ 

.long SYMBOL_NAME(sys_vfork) /* 190 */ 

.rept NR_syscalls-190 

.long SYMBOL_NAME(sys_ni_syscall) 

.endr 

修改後為: ... ... 

.long SYMBOL_NAME(sys_sendfile) 

.long SYMBOL_NAME(sys_ni_syscall) /* streams1 */ 

.long SYMBOL_NAME(sys_ni_syscall) /* streams2 */ 

.long SYMBOL_NAME(sys_vfork) /* 190 */ 

/* add by I */ 

.long SYMBOL_NAME(sys_addtotal) 

.rept NR_syscalls-191 

.long SYMBOL_NAME(sys_ni_syscall) 

.endr       

3.) 把增加的 sys_call_table 表項所對應的向量,在include/asm-386/unistd.h 中進行必要申明,以供使用者程序和其他系統程序查詢或調用: 

增加後的部分 /usr/src/linux/include/asm-386/unistd.h 檔案如下: 

... ... 

#define __NR_sendfile 187 

#define __NR_getpmsg 188 

#define __NR_putpmsg 189 

#define __NR_vfork 190 

/* add by I */ 

#define __NR_addtotal 191       

4.測試程式(test.c)如下: 

#include <unistd.h>

#include <stdio.h>

_syscall1(int,addtotal,int, num) 

main() 

{ 

int i,j; 

do 

printf("Please input a numbern"); 

while(scanf("%d",&i)==EOF); 

if((j=addtotal(i))==-1) 

printf("Error occurred in syscall-addtotal();n"); 

printf("Total from 0 to %d is %d n",i,j); 

}       

對修改後的新的核心進行編譯,并引導它作為新的作業系統,運作幾個程式後可以發現一切正常;在新的系統下對測試程式進行編譯(*注:由于原核心并未提供此系統調用,是以隻有在編譯後的新核心下,此測試程式才能可能被編譯通過),運作情況如下: 

$gcc -o test test.c 

$./test 

Please input a number 

36 

Total from 0 to 36 is 666       

在上面的論述中,一共列舉了兩個核心分析的入口、和三種分析源碼的方法:

以程式流程為線索,一線串珠;
以資料結構為基點,觸類旁通;
以功能為中心,各個擊破。
三種方法各有特點,适合于分析不同部分的代碼: 

以程式流程為線索,适合于分析系統的初始化過程:系統引導、實模式下的初始化、保護模式下的初始化三個部分,和分析應用程式的執行流程:從程式的裝載,到運作,一直到程式的退出。而流程圖則是這種分析方法最合适的表達工具。 

以資料結構為基點、觸類旁通,這種方法是分析作業系統源碼最常用的和最主要的方法。對分析程序管理,裝置管理,記憶體管理等等都是很有效的。 

以功能為中心、各個擊破,是把整個系統分成幾個相對獨立的功能子產品,然後分别對各個功能進行分析。這樣帶來的一個好處就是,每次隻以一個功能為中心,涉及到其他部分的内容,可以看作是其它功能提供的服務,而無需急着追究這種服務的實作細節;這樣,在很大程度上減輕了分析的複雜度。 

三種方法,各有其長,隻要合理的綜合運用這些方法,相信對減輕分析的複雜度還是有所幫組的。      

以下是一個360doc的文檔,不能拷貝,隻能截圖。

Linux系統調用

 說一下我的了解。Linux使用兩級保護機制:0級供核心使用,3級供使用者程式使用。庫子程式(除了其中系統調用的部分)是回到了使用者态的,隻有系統調用是核心态的。核心态、使用者态的知識,在另一篇文章中再詳細讨論。

Linux系統調用
Linux系統調用
Linux系統調用
Linux系統調用

注意标準C庫的作用:

Linux系統調用

注意:

Linux系統調用

是以,一般不會直接調用系統調用的,都是調用标準C庫,或者更加抽象的庫子程式。

雖然理論上,

對于簡單的操作,我們可以直接調用系統調用來通路資源,如“人”,對于複雜操作,我們借助于庫函數來實作,如“仁”。顯然,這樣的庫函數依據不同的标準也可以有不同的實作版本,如ISO C 标準庫,POSIX标準庫等。

但是實際上,

誰也不想去寫複雜的系統調用映射,處理寄存器操作,等等。

如果真的想調用,有下面三種方法:

http://www.linuxidc.com/Linux/2014-12/110238.htm

系統調用(System Call)是作業系統為在使用者态運作的程序與硬體裝置(如CPU、磁盤、列印機等)進行互動提供的一組接口。當使用者程序需要發生系統調用時,CPU 通過軟中斷切換到核心态開始執行核心系統調用函數。下面介紹Linux 下三種發生系統調用的方法:

1. 通過 glibc 提供的庫函數

glibc 是 Linux 下使用的開源的标準 C 庫,它是 GNU 釋出的 libc 庫,即運作時庫。glibc 為程式員提供豐富的 API(Application Programming Interface),除了例如字元串處理、數學運算等使用者态服務之外,最重要的是封裝了作業系統提供的系統服務,即系統調用的封裝。

那麼glibc提供的系統調用API與核心特定的系統調用之間的關系是什麼呢?

通常情況,每個特定的系統調用對應了至少一個 glibc 封裝的庫函數,如系統提供的打開檔案系統調用 sys_open 對應的是 glibc 中的 open 函數;

其次,glibc 一個單獨的 API 可能調用多個系統調用,如 glibc 提供的 printf 函數就會調用如 sys_open、sys_mmap、sys_write、sys_close 等等系統調用;

另外,多個 API 也可能隻對應同一個系統調用,如glibc 下實作的 malloc、calloc、free 等函數用來配置設定和釋放記憶體,都利用了核心的 sys_brk 的系統調用。      

代碼如下:

#include <sys/types.h>
#include <sys/stat.h>
#include <errno.h>
#include <stdio.h>

int main()
{
        int rc;

        rc = chmod("/etc/passwd", 0444);
        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod success!\n");
        return 0;
}      

2. 使用 syscall 直接調用

有點不足是,如果 glibc 沒有封裝某個核心提供的系統調用時,我就沒辦法通過上面的方法來調用該系統調用。如我自己通過編譯核心增加了一個系統調用,這時 glibc 不可能有你新增系統調用的封裝 API,此時我們可以利用 glibc 提供的syscall 函數直接調用。該函數定義在 unistd.h 頭檔案中,函數原型如下:

long int syscall (long int sysno, ...)

sysno 是系統調用号,每個系統調用都有唯一的系統調用号來辨別。在 sys/syscall.h 中有所有可能的系統調用号的宏定義。

... 為剩餘可變長的參數,為系統調用所帶的參數,根據系統調用的不同,可帶0~5個不等的參數,如果超過特定系統調用能帶的參數,多餘的參數被忽略。

傳回值 該函數傳回值為特定系統調用的傳回值,在系統調用成功之後你可以将該傳回值轉化為特定的類型,如果系統調用失敗則傳回 -1,錯誤代碼存放在 errno 中。      
#include <stdio.h>
#include <unistd.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        int rc;
        rc = syscall(SYS_chmod, "/etc/passwd", 0444);

        if (rc == -1)
                fprintf(stderr, "chmod failed, errno = %d\n", errno);
        else
                printf("chmod succeess!\n");
        return 0;
}      

3. 通過 int 指令陷入

如果我們知道系統調用的整個過程的話,應該就能知道使用者态程式通過軟中斷指令int 0x80 來陷入核心态(在Intel Pentium II 又引入了sysenter指令),參數的傳遞是通過寄存器,eax 傳遞的是系統調用号,ebx、ecx、edx、esi和edi 來依次傳遞最多五個參數,當系統調用傳回時,傳回值存放在 eax 中。

仍然以上面的修改檔案屬性為例,将調用系統調用那段寫成内聯彙編代碼:

#include <stdio.h>
#include <sys/types.h>
#include <sys/syscall.h>
#include <errno.h>

int main()
{
        long rc;
        char *file_name = "/etc/passwd";
        unsigned short mode = 0444;

        asm(
                "int $0x80"
                : "=a" (rc)
                : "0" (SYS_chmod), "b" ((long)file_name), "c" ((long)mode)
        );

        if ((unsigned long)rc >= (unsigned long)-132) {
                errno = -rc;
                rc = -1;
        }

        if (rc == -1)
                fprintf(stderr, "chmode failed, errno = %d\n", errno);
        else
                printf("success!\n");

        return 0;
}      

如果 eax 寄存器存放的傳回值(存放在變量 rc 中)在 -1~-132 之間,就必須要解釋為出錯碼(在/usr/include/asm-generic/errno.h 檔案中定義的最大出錯碼為 132),這時,将錯誤碼寫入 errno 中,置系統調用傳回值為 -1;否則傳回的是 eax 中的值。

上面程式在 32位Linux下以普通使用者權限編譯運作結果與前面兩個相同!

Linux系統調用清單(其實指的是glibc庫函數的清單)

http://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html

這個清單以man pages第2節,即系統調用節為藍本。

其中有一些函數的作用完全相同,隻是參數不同。(可能很多熟悉C++朋友馬上就能聯想起函數重載,但是别忘了Linux核心是用C語言寫的,是以隻能取成不同的函數名)。還有一些函數已經過時,被新的更好的函數所代替了(gcc在連結這些函數時會發出警告),但因為相容的原因還保留着,這些函數我會在前面标上“*”号以示差別。

(各給一個示例,具體的看原文吧,對加強對Linux系統的了解,很有好處!)

一、程序控制:

fork 建立一個新程序

clone 按指定條件建立子程序

exit 中止程序

_exit 立即中止目前程序

二、檔案系統控制

1、檔案讀寫操作

fcntl 檔案控制

open 打開檔案

2、檔案系統操作

access 确定檔案的可存取性

link 建立連結

三、系統控制

ioctl I/O總控制函數

四、記憶體管理

brk 改變資料段空間的配置設定(malloc, calloc 等庫函數,都是用的這個系統調用)

mmap 映射虛拟記憶體頁

五、網絡管理

getdomainname 取域名

六、socket控制

socket 建立socket

七、使用者管理

getuid 擷取使用者辨別号

八、程序間通信

ipc 程序間通信總控制調用

1、信号

sigaction 設定對指定信号的處理方法

signal 參見signal

kill 向程序或程序組發信号

2、消息

msgctl 消息控制操作

3、管道

pipe 建立管道

4、信号量

semctl 信号量控制

5、共享記憶體

shmctl 控制共享記憶體

參考資料

Linux man pages

Advanced Programming in the UNIX Environment, W. Richard Stevens, 1993

繼續閱讀