Linux Kernel Development—-系統調用

來源:互聯網
上載者:User
文章目錄
  • 系統調用與使用者編程介面API
  • getpid直接系統調用與C庫調用樣本
  • 系統調用的實現
  • getpid系統調用的實現樣本
  • 系統調用的效能問題
  • 使用systemtap追蹤進程的系統調用次數

什麼是系統調用

系統調用是使用者空間與核心空間之間互動的介面,使用者空間不能直接存取核心空間,而必須通過系統調用才可訪問,這是為了保證核心空間的穩定性和安全性。

系統調用與使用者編程介面API

使用者空間的程式通常不直接使用系統調用,而是通過API間接調用系統調用。API封裝了系統調用,但不是每一個API介面都會用到系統調用。API和系統調用並沒有嚴格對應關係,一個API可能恰好只對應一個系統調用,比如read()API和read()系統調用;一個API也可能由多個系統調用實現;有時候,一個API的功能可能並不需要核心提供的服務,那麼此時這個API也就不需要任何的系統調用,比如abs()。另外,一個系統調用可能還被多個不同的API內部調用。

Linux的使用者編程介面遵循了在Unix世界中最流行的應用編程介面標準——POSIX標準,這套標準定義了一系列API。在Linux中(Unix也如此),這些API主要是通過C庫(libc)實現的,它除了定義的一些標準的C函數外,一個很重要的任務就是提供了一套封裝常式(wrapper routine)將系統調用在使用者空間封裝後供使用者編程使用。

不過封裝並非必須的,如果你願意直接調用,Linux核心也提供了一個syscall()函數來實現調用

getpid直接系統調用與C庫調用樣本

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

int main(void) {
long ID1, ID2;
/*-----------------------------*/
/* 直接系統調用*/
/* SYS_getpid (func no. is 20) */
/*-----------------------------*/
ID1 = syscall(SYS_getpid);
printf ("syscall(SYS_getpid)=%ld\n", ID1);
/*-----------------------------*/
/* 使用"libc"封裝的系統調用 */
/* SYS_getpid (Func No. is 20) */
/*-----------------------------*/
ID2 = getpid();
printf ("getpid()=%ld\n", ID2);
return(0);
}

系統調用的實現

Linux中實現系統調用利用了0x86體繫結構中的軟體中斷。軟體中斷和我們常說的中斷(硬體中斷)不同之處在於——它是通過軟體指令觸發而並非外設引發的中斷,也就是說,又是編程人員開發出的一種異常,具體的講就是調用int $0x80彙編指令,這條彙編指令將產生向量為128的編程異常。

之所以系統調用需要藉助異常來實現,是因為當使用者態的進程調用一個系統調用時,CPU便被切換到核心態執行核心功能,而進入核心——進入高特權層級——必須經過系統的門機制,這裡的異常實際上就是通過系統門陷入核心(除了int 0x80外使用者空間還可以通過int3——向量3、into——向量4 、bound——向量5等異常指令進入核心,而其他異常無法被使用者空間程式利用,都是由系統使用的)。

我們更詳細地解釋一下這個過程。int $0x80指令的目的是產生一個編號為128的編程異常,這個編程異常對應的是中斷描述符表IDT中的第128項——也就是對應的系統門描述符。門描述符中含有一個預設的核心空間地址,它指向了系統調用處理常式:system_call()(別和系統調用服務程式混淆,這個程式在entry.S檔案中用組合語言編寫)。

很顯然,所有的系統調用都會統一地轉到這個地址,但Linux一共有2、3百個系統調用都從這裡進入核心後又該如何派發到它們到各自的服務程式去呢?別發昏,解決這個問題的方法非常簡單:首先Linux為每個系統調用都進行了編號(0—NR_syscall),同時在核心中儲存了一張系統調用表,該表中儲存了系統調用編號和其對應的服務常式,因此在系統調入通過系統門陷入核心前,需要把系統調用號一併傳入核心,在x86上,這個傳遞動作是通過在執行int 0x80前把調用號裝入eax寄存器實現的。這樣系統調用處理常式一旦運行,就可以從eax中得到資料,然後再去系統調用表中尋找相應服務常式了。

除了需要傳遞系統調用號以外,許多系統調用還需要傳遞一些參數到核心,比如sys_write(unsigned int fd, const char * buf, size_t count)調用就需要傳遞檔案描述符fd、要寫入的內容buf、以及寫入位元組數count等幾個內容到核心。碰到這種情況,Linux會有6個寄存器可被用來傳遞這些參數:eax (存放系統調用號)、 ebx、ecx、edx、esi及edi來存放這些額外的參數(以字母遞增的順序)。具體做法是在system_call( )中使用SAVE_ALL宏把這些寄存器的值儲存在核心態堆棧中。

getpid系統調用的實現樣本

系統調用表中arch/x86/kernel/syscall_table_32.S,sys_getpid在sys_call_table中的位置就是其系統調用號,這兒是20

.long sys_getpid /* 20 */

在支援的體繫結構(如x86)中定義系統調用號:

arch/x86/include/asm/unistd_64.h:這兒的系統調用號不需要和上面系統調用表中的一致

#define __NR_getpid 39
__SYSCALL(__NR_getpid, sys_getpid)

在kernel/timer.c中實現該系統調用:注意,這兒的名字getpid會在SYSCALL_DEFINE0中拼接成sys_getpid
/**
* sys_getpid - return the thread group id of the current process
*
* Note, despite the name, this returns the tgid not the pid. The tgid and
* the pid are identical unless CLONE_THREAD was specified on clone() in
* which case the tgid is the same in all threads of the same group.
*
* This is SMP safe as current->tgid does not change.
*/
SYSCALL_DEFINE0(getpid)
{
return task_tgid_vnr(current);
}

上面SYSCALL_DEFINE0定義在include/linux/syscalls.h中:
#define SYSCALL_DEFINE0(sname) \
SYSCALL_TRACE_ENTER_EVENT(_##sname); \
SYSCALL_TRACE_EXIT_EVENT(_##sname); \
static struct syscall_metadata __used \
__syscall_meta__##sname = { \
.name = "sys_"#sname, \
.syscall_nr = -1, /* Filled in at boot */ \
.nb_args = 0, \
.enter_event = &event_enter__##sname, \
.exit_event = &event_exit__##sname, \
.enter_fields = LIST_HEAD_INIT(__syscall_meta__##sname.enter_fields), \
}; \
static struct syscall_metadata __used \
__attribute__((section("__syscalls_metadata"))) \
*__p_syscall_meta_##sname = &__syscall_meta__##sname; \
asmlinkage long sys_##sname(void)
#else
#define SYSCALL_DEFINE0(name) asmlinkage long sys_##name(void)
#endif

根據上面的定義,其實很容易在核心中添加一個系統調用,其方法見http://edsionte.com/techblog/archives/2086。

系統調用的效能問題

系統調用需要從使用者空間陷入核心空間,處理完後,又需要返回使用者空間。其中除了系統調用服務常式的實際耗時外,陷入/返回過程和系統調用處理常式(查系統調用表、儲存/恢複使用者現場)也需要花費一些時間,這些時間加起來就是一個系統調用的響應速度。系統調用不比別的使用者程式,它對效能要求很苛刻,因為它需要陷入核心執行,所以和其他核心程式一樣要求代碼簡潔、執行迅速。幸好Linux具有令人難以置信的環境切換速度,使得其進出核心都被最佳化得簡潔高效;同時所有Linux系統調用處理常式和每個系統調用本身也都非常簡潔。

絕大多數情況下,Linux系統調用的效能是可以接受的,但是對於一些對效能要求非常高的應用來說,它們雖然希望利用系統調用的服務,但卻希望加快響應速度,避免陷入/返回和系統調用處理常式帶來的花銷,因此採用由核心直接調用系統調用服務常式,最好的例子就HTTPD——它為了避免上述開銷,從核心調用socket等系統調用服務常式。

使用systemtap追蹤進程的系統調用次數

global syscalllist

probe begin {
printf("System Call Monitoring Started (10 seconds)...\n")
}

probe syscall.*
{
syscalllist[pid(), execname(), name]++
}

probe timer.ms(10000) {
foreach ( [pid, procname, name] in syscalllist ) {
printf("%s[%d] calls %s %d times\n", procname,
pid,
name,
syscalllist[pid, procname, name] )
}
exit()
}

輸出樣本:
# stap system_call_count.stp
System Call Monitoring Started (10 seconds)...
stapio[2328] calls fcntl 82 times
stapio[2328] calls read 92 times
stapio[2328] calls nanosleep 40 times
mongod[2099] calls select 1978 times
mongod[2099] calls gettimeofday 400 times
mongod[2099] calls nanosleep 298 times
compiz[8661] calls read 253 times
compiz[8661] calls clock_gettime 2466 times
compiz[8661] calls poll 555 times
compiz[8661] calls writev 168 times
Xorg[2242] calls clock_gettime 733 times
Xorg[2242] calls setitimer 340 times
Xorg[2242] calls read 340 times
Xorg[2242] calls writev 177 times
Xorg[2242] calls select 170 times
compiz[8661] calls recvfrom 1187 times
redis-server[3600] calls gettimeofday 400 times
redis-server[3600] calls epoll_wait 100 times

本文參考:

http://edsionte.com/techblog/archives/2071
http://www.kerneltravel.net/journal/iv/syscall.htm

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.