1. 程式人生 > >系統呼叫執行過程分析

系統呼叫執行過程分析

#####################################
作者:張卓
原創作品轉載請註明出處:《Linux作業系統分析》MOOC課程 http://www.xuetangx.com/courses/course-v1:ustcX+USTC001+_/about
#####################################

一 系統呼叫概述

作業系統為使用者態程序與硬體裝置進行互動提供了一組介面-系統呼叫
  ● 把使用者從底層的硬體程式設計中解放出來
  ● 極大的提高了系統的安全性
  ● 使用使用者程式具有可移植性

作業系統提供的API和系統呼叫的關係
應用程式設計介面(application program interace,API)和系統呼叫是不同的
  ● API只是一個函式定義
  ● 系統呼叫通過軟中斷向核心發生一個明確的請求
Libc庫定義的一些API引用了封裝例程(wrapper routine,唯一目的就是釋出系統呼叫)
  ● 一般每個系統呼叫對應一個封裝例程
  ● 庫再用這些例程定義出給使用者的API
不是每個API都對應一個特定的系統呼叫
  ● API可能直接提供使用者態的服務,如一些數學函式
  ● 一個單獨的API可能呼叫幾個系統呼叫
  ● 不同的API可能呼叫同一個系統呼叫
返回值
  ● 大部分封裝例程返回一個整數,其值的含義依賴於相應的系統呼叫
  ● -1在大多數的情況下表示核心不能滿足程序的請求
  ● Libc中定義的errno變數包含特定的出錯碼
 

二 系統呼叫的執行過程分析

在 Linux 平臺下有兩種方式來使用系統呼叫:利用封裝後的 C 庫(libc)或者通過彙編直接呼叫。其中通過組合語言來直接呼叫系統呼叫,是最高效地使用 Linux 核心服務的方法,因為最終生成的程式不需要與任何庫進行連結,而是直接和核心通訊。
下面我將藉助實驗樓的環境詳細分析使用庫函式API和C程式碼中嵌入彙編程式碼兩種方式使用同一個系統呼叫的過程。
 1 #include <stdio.h>                                                  
  2 #include <stdlib.h>
  3 #include <sys/types.h>
  4 #include <fcntl.h>
  5 
  6 int main(int argc, const char *argv[])
  7 {
  8     int fd = 0;
  9     int ret = 0;
 10     char buf[100] = {0};
 11     char *filename = "1569.txt";
 12 
 13     fd = open(filename, O_RDONLY);
 14     if( 0 > fd){
 15         perror("Failed to open");
 16         exit(1);
 17     }
 18 
 19     ret = read(fd, buf, 50);
 20     if(ret < 0){
 21         perror("Failed to read");
 22         close(fd);
 23         exit(1);
 24     }                                                               
 25     printf("%s\n",buf);                                             
 26                                                                     
 27     return 0;                                                       
 28 }            
這簡單的程式用庫函式的方式使用了兩個系統呼叫:5 和 3
通過簡單的編譯執行,可以得到如下的結果:

接下來,我們用GCC內嵌彙編的方式來執行系統呼叫:
 1 #include <stdio.h>
 2 #include <stdlib.h>
 3 #include <sys/types.h>
 4 #include <fcntl.h>
 5
 6 int main(int argc, const char *argv[])
 7 {
 8     int fd = 0;
 9     int ret = 0;
10     char buf[100] = {0};
11     char *filename = "1569.txt";
12
13     //fd = open("1569.txt", O_RDONLY);
14     asm volatile(
15         "movl %1,%%ebx\n\t"   /* 用ebx儲存第一個引數 */
16         "movl %2,%%ecx\n\t"   /* 用ecx儲存第二個引數 */
17         "movl $0x5,%%eax\n\t" /* 用eax傳遞系統呼叫號,5表示sys_open */
18         "int $0x80\n\t"       /* 系統通過 int $0x80這條指令產生一個軟中斷,執行系統呼叫 */
19         "movl %%eax, %0\n\t"  /* 和其它函式一樣,返回值也是儲存在eax暫存器之中 */
20         :"=m"(fd)
21         :"b"(filename),"c"(O_RDONLY)
22         );
23     if( 0 > fd){
24         perror("Failed to open");
25         exit(1);
26     }
27
28     //ret = read(fd, buf, 50);
29     asm volatile(
30         "movl %1,%%ebx;"
31         "movl %2,%%ecx;"
32         "movl %3,%%edx;"
33         "movl $0x3,%%eax;"
34         "int $0x80;"
35         "movl %%eax, %0;"
36         :"=m"(ret)
37         :"b"(fd),"c"(buf),"d"(50)
38         );
39     if(ret < 0){
40         perror("Failed to read");
41         close(fd);
42         exit(1);
43     }
44     printf("%s\n",buf);
45
46     return 0;
47 }
48
和 DOS 一樣,Linux 下的系統呼叫也是通過中斷(int 0x80)來實現的。在執行 int 80 指令時,暫存器 eax 中存放的是系統呼叫的功能號,而傳給系統呼叫的引數則必須按順序放到暫存器 ebx,ecx,edx,esi,edi 中,當系統呼叫完成之後,返回值可以在暫存器 eax 中獲得。所有的系統呼叫功能號都可以在檔案 /usr/include/bits/syscall.h 中找到。
通過下面的命令編譯,然後執行:
gcc read_asm.c -o read_asm -m32
可以看到執行結果和上面的一樣:

三 總結

從上面實驗的內容我們得出,系統呼叫的執行過程,可以用下面的一個圖表示出來:

使用者程序通過呼叫系統提供的API,在API中執行類似與上面內嵌彙編的程式;進而執行system call 處理程式,在system call處理程式中執行真正的系統提供的服務程式,執行完畢返回退出。系統呼叫號將API和system call 緊密聯絡在一起。在這個過程中涉及到了,系統從使用者態切換到核心態執行程式的過程,從而可以說明系統呼叫也是一種特殊的中斷。中斷向量0x80對應system call。


本部落格參考:Linux 組合語言開發指南
系統呼叫列表參見http://codelab.shiyanlou.com/xref/linux-3.18.6/arch/x86/syscalls/syscall_32.tbl