小男孩‘自慰网亚洲一区二区,亚洲一级在线播放毛片,亚洲中文字幕av每天更新,黄aⅴ永久免费无码,91成人午夜在线精品,色网站免费在线观看,亚洲欧洲wwwww在线观看

分享

使用 Linux 系統(tǒng)調(diào)用的內(nèi)核命令

 Archangel 2007-08-06

2007 年 4 月 17 日

Linux? 系統(tǒng)調(diào)用 —— 我們每天都在使用它們。不過您清楚系統(tǒng)調(diào)用是如何在用戶空間和內(nèi)核之間執(zhí)行的嗎?本文將探究 Linux 系統(tǒng)調(diào)用接口(SCI),學習如何添加新的系統(tǒng)調(diào)用(以及實現(xiàn)這種功能的其他方法),并介紹與 SCI 有關(guān)的一些工具。

系統(tǒng)調(diào)用就是用戶空間應用程序和內(nèi)核提供的服務之間的一個接口。由于服務是在內(nèi)核中提供的,因此無法執(zhí)行直接調(diào)用;相反,您必須使用一個進程來跨越用戶空間與內(nèi)核之間的界限。在特定架構(gòu)中實現(xiàn)此功能的方法會有所不同。因此,本文將著眼于最通用的架構(gòu) —— i386。

在 本文中,我將探究 Linux SCI,演示如何向 2.6.20 內(nèi)核添加一個系統(tǒng)調(diào)用,然后從用戶空間來使用這個函數(shù)。我們還將研究在進行系統(tǒng)調(diào)用開發(fā)時非常有用的一些函數(shù),以及系統(tǒng)調(diào)用的其他選擇。最后,我們將介紹 與系統(tǒng)調(diào)用有關(guān)的一些輔助機制,比如在某個進程中跟蹤系統(tǒng)調(diào)用的使用情況。

SCI

Linux 中系統(tǒng)調(diào)用的實現(xiàn)會根據(jù)不同的架構(gòu)而有所變化,而且即使在某種給定的體架構(gòu)上也會不同。例如,早期的 x86 處理器使用了中斷機制從用戶空間遷移到內(nèi)核空間中,不過新的 IA-32 處理器則提供了一些指令對這種轉(zhuǎn)換進行優(yōu)化(使用 sysentersysexit 指令)。由于存在大量的方法,最終結(jié)果也非常復雜,因此本文將著重于接口細節(jié)的表層討論上。更詳盡的內(nèi)容請參看本文最后的 參考資料。

要對 Linux 的 SCI 進行改進,您不需要完全理解 SCI 的內(nèi)部原理,因此我將使用一個簡單的系統(tǒng)調(diào)用進程(請參看圖 1)。每個系統(tǒng)調(diào)用都是通過一個單一的入口點多路傳入內(nèi)核。eax 寄存器用來標識應當調(diào)用的某個系統(tǒng)調(diào)用,這在 C 庫中做了指定(來自用戶空間應用程序的每個調(diào)用)。當加載了系統(tǒng)的 C 庫調(diào)用索引和參數(shù)時,就會調(diào)用一個軟件中斷(0x80 中斷),它將執(zhí)行 system_call 函數(shù)(通過中斷處理程序),這個函數(shù)會按照 eax 內(nèi)容中的標識處理所有的系統(tǒng)調(diào)用。在經(jīng)過幾個簡單測試之后,使用 system_call_table 和 eax 中包含的索引來執(zhí)行真正的系統(tǒng)調(diào)用了。從系統(tǒng)調(diào)用中返回后,最終執(zhí)行 syscall_exit,并調(diào)用 resume_userspace 返回用戶空間。然后繼續(xù)在 C 庫中執(zhí)行,它將返回到用戶應用程序中。


圖 1. 使用中斷方法的系統(tǒng)調(diào)用的簡化流程
系統(tǒng)調(diào)用的簡化流程

SCI 的核心是系統(tǒng)調(diào)用多路分解表。這個表如圖 2 所示,使用 eax 中提供的索引來確定要調(diào)用該表中的哪個系統(tǒng)調(diào)用(sys_call_table)。圖中還給出了表內(nèi)容的一些樣例,以及這些內(nèi)容的位置。(有關(guān)多路分解的更多內(nèi)容,請參看側(cè)欄 “系統(tǒng)調(diào)用多路分解”)


圖 2. 系統(tǒng)調(diào)用表和各種鏈接
系統(tǒng)調(diào)用表和各種鏈接




回頁首


添加一個 Linux 系統(tǒng)調(diào)用

系統(tǒng)調(diào)用多路分解

有些系統(tǒng)調(diào)用會由內(nèi)核進一步進行多路分解。例如,BSD(Berkeley Software Distribution)socket 調(diào)用(socket、bind、 connect 等)都與一個單獨的系統(tǒng)調(diào)用索引(__NR_socketcall)關(guān)聯(lián)在一起,不過在內(nèi)核中會進行多路分解,通過另外一個參數(shù)進入適當?shù)恼{(diào)用。請參看 ./linux/net/socket.c 中的 sys_socketcall 函數(shù)。

添加一個新系統(tǒng)調(diào)用主要是一些程序性的操作,但應該注意幾件事情。本節(jié)將介紹幾個系統(tǒng)調(diào)用的構(gòu)造,從而展示它們的實現(xiàn)和用戶空間應用程序?qū)λ鼈兊氖褂谩?/p>

向內(nèi)核中添加新系統(tǒng)調(diào)用,需要執(zhí)行 3 個基本步驟:

  1. 添加新函數(shù)。
  2. 更新頭文件。
  3. 針對這個新函數(shù)更新系統(tǒng)調(diào)用表。

注意: 這個過程忽略了用戶空間的需求,我將稍后介紹。

最常見的情況是,您會為自己的函數(shù)創(chuàng)建一個新文件。不過,為了簡單起見,我將自己的新函數(shù)添加到現(xiàn)有的源文件中。清單 1 所示的前兩個函數(shù),是系統(tǒng)調(diào)用的簡單示例。清單 2 提供了一個使用指針參數(shù)的稍微復雜的函數(shù)。


清單 1. 系統(tǒng)調(diào)用示例的簡單內(nèi)核函數(shù)
                    asmlinkage long sys_getjiffies( void )
                    {
                    return (long)get_jiffies_64();
                    }
                    asmlinkage long sys_diffjiffies( long ujiffies )
                    {
                    return (long)get_jiffies_64() - ujiffies;
                    }
                    

在清單 1 中,我們?yōu)檫M行 jiffies 監(jiān)視提供了兩個函數(shù)。(有關(guān) jiffies 的更多信息,請參看側(cè)欄 “Kernel jiffies”)。第一個函數(shù)會返回當前 jiffy,而第二個函數(shù)則返回當前值與所傳遞進來的值之間的差值。注意 asmlinkage 修飾符的使用。這個宏(在 linux/include/asm-i386/linkage.h 中定義)告訴編譯器將傳遞棧中的所有函數(shù)參數(shù)。


清單 2. 系統(tǒng)調(diào)用示例的最后內(nèi)核函數(shù)
                    asmlinkage long sys_pdiffjiffies( long ujiffies,
                    long __user *presult )
                    {
                    long cur_jiffies = (long)get_jiffies_64();
                    long result;
                    int  err = 0;
                    if (presult) {
                    result = cur_jiffies - ujiffies;
                    err = put_user( result, presult );
                    }
                    return err ? -EFAULT : 0;
                    }
                    

內(nèi)核 jiffies

Linux 內(nèi)核具有一個名為 jiffies 的全局變量,它代表從機器啟動時算起的時間滴答數(shù)。這個變量最初被初始化為 0,每次時鐘中斷時都會加 1。您可以使用 get_jiffies_64 函數(shù)來讀取 jiffies 的值,然后使用 jiffies_to_msecs 將其換算成毫秒或使用 jiffies_to_usecs 將其換算成微秒。jiffies 的全局定義和相關(guān)函數(shù)是在 ./linux/include/linux/jiffies.h 中提供的。

清單 2 給出了第三個函數(shù)。這個函數(shù)使用了兩個參數(shù):一個 long 類型,以及一個指向被定義為 __userlong 的指針。__user 宏簡單告訴編譯器(通過 noderef)不應該解除這個指針的引用(因為在當前地址空間中它是沒有意義的)。這個函數(shù)會計算這兩個 jiffies 值之間的差值,然后通過一個用戶空間指針將結(jié)果提供給用戶。put_user 函數(shù)將結(jié)果值放入 presult 所指定的用戶空間位置。如果在這個操作過程中出現(xiàn)錯誤,將立即返回,您也可以通知用戶空間調(diào)用者。

對于步驟 2 來說,我對頭文件進行了更新:在系統(tǒng)調(diào)用表中為這幾個新函數(shù)安排空間。對于本例來說,我使用新系統(tǒng)調(diào)用號更新了 linux/include/asm/unistd.h 頭文件。更新如清單 3 中的黑體所示。


清單 3. 更新 unistd.h 文件為新系統(tǒng)調(diào)用安排空間
                    #define __NR_getcpu		318
                    #define __NR_epoll_pwait	319
                    #define __NR_getjiffies		320
                    #define __NR_diffjiffies	321
                    #define __NR_pdiffjiffies	322
                    #define NR_syscalls	323
                    

現(xiàn) 在已經(jīng)有了自己的內(nèi)核系統(tǒng)調(diào)用,以及表示這些系統(tǒng)調(diào)用的編號。接下來需要做的是要在這些編號(表索引)和函數(shù)本身之間建立一種對等關(guān)系。這就是第 3 個步驟,更新系統(tǒng)調(diào)用表。如清單 4 所示,我將為這個新函數(shù)更新 linux/arch/i386/kernel/syscall_table.S 文件,它會填充清單 3 顯示的特定索引。


清單 4. 使用新函數(shù)更新系統(tǒng)調(diào)用表
                    .long sys_getcpu
                    .long sys_epoll_pwait
                    .long sys_getjiffies		/* 320 */
                    .long sys_diffjiffies
                    .long sys_pdiffjiffies
                    

注意: 這個表的大小是由符號常量 NR_syscalls 定義的。

現(xiàn)在,我們已經(jīng)完成了對內(nèi)核的更新。接下來必須對內(nèi)核重新進行編譯,并在測試用戶空間應用程序之前使引導使用的新映像變?yōu)榭捎谩?/p>

對用戶內(nèi)存進行讀寫

Linux 內(nèi)核提供了幾個函數(shù),可以用來將系統(tǒng)調(diào)用參數(shù)移動到用戶空間中,或從中移出。方法包括一些基本類型的簡單函數(shù)(例如 get_userput_user)。要移動一塊兒數(shù)據(jù)(如結(jié)構(gòu)或數(shù)組),您可以使用另外一組函數(shù): copy_from_usercopy_to_user??梢允褂脤iT的調(diào)用移動以 null 結(jié)尾的字符串: strncpy_from_userstrlen_from_user。您也可以通過調(diào)用 access_ok 來測試用戶空間指針是否有效。這些函數(shù)都是在 linux/include/asm/uaccess.h 中定義的。

您可以使用 access_ok 宏來驗證給定操作的用戶空間指針。這個函數(shù)有 3 個參數(shù),分別是訪問類型(VERIFY_READVERIFY_WRITE),指向用戶空間內(nèi)存塊的指針,以及塊的大小(單位為字節(jié))。如果成功,這個函數(shù)就返回 0:

int access_ok( type, address, size );
                    

要在內(nèi)核和用戶空間移動一些簡單類型(例如 int 或 long 類型),可以使用 get_userput_user 輕松地實現(xiàn)。這兩個宏都包含一個值以及一個指向變量的指針。get_user 函數(shù)將用戶空間地址(ptr)指定的值移動到所指定的內(nèi)核變量(var)中。 put_user 函數(shù)則將內(nèi)核變量(var)指定的值移動到用戶空間地址(ptr)。 如果成功,這兩個函數(shù)都返回 0:

int get_user( var, ptr );
                    int put_user( var, ptr );
                    

要移動更大的對象,例如結(jié)構(gòu)或數(shù)組,您可以使用 copy_from_usercopy_to_user 函數(shù)。這些函數(shù)將在用戶空間和內(nèi)核之間移動完整的數(shù)據(jù)塊。 copy_from_user 函數(shù)會將一塊數(shù)據(jù)從用戶空間移動到內(nèi)核空間,copy_to_user 則會將一塊數(shù)據(jù)從內(nèi)核空間移動到用戶空間:

unsigned long copy_from_user( void *to, const void __user *from, unsigned long n );
                    unsigned long copy_to_user( void *to, const void __user *from, unsigned long n );
                    

最后,您可以使用 strncpy_from_user 函數(shù)將一個以 NULL 結(jié)尾的字符串從用戶空間移動到內(nèi)核空間中。在調(diào)用這個函數(shù)之前,您可以通過調(diào)用 strlen_user 宏來獲得用戶空間字符串的大?。?/p>

long strncpy_from_user( char *dst, const char __user *src, long count );
                    strlen_user( str );
                    

這些函數(shù)為內(nèi)核和用戶空間之間的內(nèi)存移動提供了基本功能。實際上還可以使用另外一些函數(shù)(例如減少執(zhí)行檢查數(shù)量的函數(shù))。您可以在 uaccess.h 中找到這些函數(shù)。





回頁首


使用系統(tǒng)調(diào)用

現(xiàn)在內(nèi)核已經(jīng)使用新系統(tǒng)調(diào)用完成更新了,接下來看一下從用戶空間應用程序中使用這些系統(tǒng)調(diào)用需要執(zhí)行的操作。使用新的內(nèi)核系統(tǒng)調(diào)用有兩種方法。第一種方法非常方便(但是在產(chǎn)品代碼中您可能并不希望使用),第二種方法是傳統(tǒng)方法,需要多做一些工作。

使用第一種方法,您可以通過 syscall 函數(shù)調(diào)用由其索引所標識的新函數(shù)。使用 syscall 函數(shù),您可以通過指定它的調(diào)用索引和一組參數(shù)來調(diào)用系統(tǒng)調(diào)用。例如,清單 5 顯示的簡單應用程序就使用其索引調(diào)用了 sys_getjiffies。


清單 5. 使用 syscall 調(diào)用系統(tǒng)調(diào)用
                    #include <linux/unistd.h>
                    #include <sys/syscall.h>
                    #define __NR_getjiffies		320
                    int main()
                    {
                    long jiffies;
                    jiffies = syscall( __NR_getjiffies );
                    printf( "Current jiffies is %lx\n", jiffies );
                    return 0;
                    }
                    

正如您所見,syscall 函數(shù)使用了系統(tǒng)調(diào)用表中使用的索引作為第一個參數(shù)。如果還有其他參數(shù)需要傳遞,可以加在調(diào)用索引之后。大部分系統(tǒng)調(diào)用都包括了一個 SYS_ 符號常量來指定自己到 __NR_ 索引的映射。例如,使用 syscall 調(diào)用 __NR_getpid 索引:

                syscall( SYS_getpid )
                    

syscall 函數(shù)特定于架構(gòu),使用一種機制將控制權(quán)交給內(nèi)核。其參數(shù)是基于 __NR 索引與 /usr/include/bits/syscall.h 提供的 SYS_ 符號之間的映射(在編譯 libc 時定義)。永遠都不要直接引用這個文件;而是要使用 /usr/include/sys/syscall.h 文件。

傳統(tǒng)的方法要求我們創(chuàng)建函數(shù)調(diào)用,這些函數(shù)調(diào)用必須匹配內(nèi)核中的系統(tǒng)調(diào)用索引(這樣就可以調(diào)用正確的內(nèi)核服務),而且參數(shù)也必須匹配。Linux 提供了一組宏來提供這種功能。_syscallN 宏是在 /usr/include/linux/unistd.h 中定義的,格式如下:

                _syscall0( ret-type, func-name )
                    _syscall1( ret-type, func-name, arg1-type, arg1-name )
                    _syscall2( ret-type, func-name, arg1-type, arg1-name, arg2-type, arg2-name )
                    

用戶空間和 __NR 常量

注意清單 6 中提供了 __NR 符號常量。您可以在 /usr/include/asm/unistd.h 中找到它們(對于標準系統(tǒng)調(diào)用來說)。

_syscall 宏最多可定義 6 個參數(shù)(不過此處只顯示了 3 個)。

現(xiàn)在,讓我們來看一下如何使用 _syscall 宏來使新系統(tǒng)調(diào)用對于用戶空間可見。清單 6 顯示的應用程序使用了 _syscall 宏定義的所有系統(tǒng)調(diào)用。


清單 6. 將 _syscall 宏 用于用戶空間應用程序開發(fā)
                    #include <stdio.h>
                    #include <linux/unistd.h>
                    #include <sys/syscall.h>
                    #define __NR_getjiffies		320
                    #define __NR_diffjiffies	321
                    #define __NR_pdiffjiffies	322
                    _syscall0( long, getjiffies );
                    _syscall1( long, diffjiffies, long, ujiffies );
                    _syscall2( long, pdiffjiffies, long, ujiffies, long*, presult );
                    int main()
                    {
                    long jifs, result;
                    int err;
                    jifs = getjiffies();
                    printf( "difference is %lx\n", diffjiffies(jifs) );
                    err = pdiffjiffies( jifs, &result );
                    if (!err) {
                    printf( "difference is %lx\n", result );
                    } else {
                    printf( "error\n" );
                    }
                    return 0;
                    }
                    

注意 __NR 索引在這個應用程序中是必需的,因為 _syscall 宏使用了 func-name 來構(gòu)造 __NR 索引(getjiffies -> __NR_getjiffies)。其結(jié)果是您可以使用它們的名字來調(diào)用內(nèi)核函數(shù),就像其他任何系統(tǒng)調(diào)用一樣。





回頁首


用戶/內(nèi)核交互的其他選擇

系 統(tǒng)調(diào)用是請求內(nèi)核中服務的一種有效方法。使用這種方法的最大問題就是它是一個標準接口,很難將新的系統(tǒng)調(diào)用增加到內(nèi)核中,因此可以通過其他方法來實現(xiàn)類似 服務。如果您無意將自己的系統(tǒng)調(diào)用加入公共的 Linux 內(nèi)核中,那么系統(tǒng)調(diào)用就是將內(nèi)核服務提供給用戶空間的一種方便而且有效的方法。

讓您的服務對用戶空間可見的另外一種方法是通過 /proc 文件系統(tǒng)。/proc 文件系統(tǒng)是一個虛擬文件系統(tǒng),您可以通過它來向用戶提供一個目錄和文件,然后通過文件系統(tǒng)接口(讀、寫等)在內(nèi)核中為新服務提供一個接口。





回頁首


使用 strace 跟蹤系統(tǒng)調(diào)用

Linux 內(nèi)核提供了一種非常有用的方法來跟蹤某個進程所調(diào)用的系統(tǒng)調(diào)用(以及該進程所接收到的信號)。這個工具就是 strace,它可以在命令行中執(zhí)行,使用希望跟蹤的應用程序作為參數(shù)。例如,如果您希望了解在執(zhí)行 date 命令時都執(zhí)行了哪些系統(tǒng)調(diào)用,可以鍵入下面的命令:

strace date
                    

結(jié)果會產(chǎn)生大量信息,顯示在執(zhí)行 date 命令過程中所執(zhí)行的各個系統(tǒng)調(diào)用。您會看到加載共享庫、映射內(nèi)存,最后跟蹤到的是在標準輸出中生成日期信息:

...
                    write(1, "Fri Feb  9 23:06:41 MST 2007\n", 29Fri Feb  9 23:06:41 MST 2007) = 29
                    munmap(0xb747a000, 4096)	= 0
                    exit_group(0)			= ?
                    $
                    

當當前系統(tǒng)調(diào)用請求具有一個名為 syscall_trace 的特定字段集(它導致 do_syscall_trace 函數(shù)的調(diào)用)時,將在內(nèi)核中完成跟蹤。您還可以看到跟蹤調(diào)用是 ./linux/arch/i386/kernel/entry.S 中系統(tǒng)調(diào)用請求的一部分(請參看 syscall_trace_entry)。





回頁首


結(jié)束語

分享這篇文章……

digg 將本文提交到 Digg
del. 發(fā)布到 del.
Slashdot 提交到 Slashdot!

系統(tǒng)調(diào)用是穿越用戶空間和內(nèi)核空間,請求內(nèi)核空間服務的一種有效方法。不過對這種方法的控制也很嚴格,更簡單的方式是增加一個新的 /proc 文件系統(tǒng)項來提供用戶/內(nèi)核間的交互。不過當速度因素非常重要時,系統(tǒng)調(diào)用則是使應用程序獲得最佳性能的理想方法。請參看 參考資料 的內(nèi)容進一步了解 SCI。



參考資料

學習


關(guān)于作者

M. tim jones

M. Tim Jones 是一名嵌入式軟件工程師,他是 GNU/Linux Application Programming、AI Application Programming 以及 BSD Sockets Programming from a Multilanguage Perspective 等書的作者。他的工程背景非常廣泛,從同步宇宙飛船的內(nèi)核開發(fā)到嵌入式架構(gòu)設(shè)計,再到網(wǎng)絡(luò)協(xié)議的開發(fā)。Tim 是位于科羅拉多州 Longmont 的 Emulex Corp. 的一名顧問工程師。

    本站是提供個人知識管理的網(wǎng)絡(luò)存儲空間,所有內(nèi)容均由用戶發(fā)布,不代表本站觀點。請注意甄別內(nèi)容中的聯(lián)系方式、誘導購買等信息,謹防詐騙。如發(fā)現(xiàn)有害或侵權(quán)內(nèi)容,請點擊一鍵舉報。
    轉(zhuǎn)藏 分享 獻花(0

    0條評論

    發(fā)表

    請遵守用戶 評論公約

    類似文章 更多