乡下人产国偷v产偷v自拍,国产午夜片在线观看,婷婷成人亚洲综合国产麻豆,久久综合给合久久狠狠狠9

  • <output id="e9wm2"></output>
    <s id="e9wm2"><nobr id="e9wm2"><ins id="e9wm2"></ins></nobr></s>

    • 分享

      使用 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 有關的一些工具。

      系統(tǒng)調(diào)用就是用戶空間應用程序和內(nèi)核提供的服務之間的一個接口。由于服務是在內(nèi)核中提供的,因此無法執(zhí)行直接調(diào)用;相反,您必須使用一個進程來跨越用戶空間與內(nèi)核之間的界限。在特定架構中實現(xiàn)此功能的方法會有所不同。因此,本文將著眼于最通用的架構 —— 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)用有關的一些輔助機制,比如在某個進程中跟蹤系統(tǒng)調(diào)用的使用情況。

      SCI

      Linux 中系統(tǒng)調(diào)用的實現(xiàn)會根據(jù)不同的架構而有所變化,而且即使在某種給定的體架構上也會不同。例如,早期的 x86 處理器使用了中斷機制從用戶空間遷移到內(nèi)核空間中,不過新的 IA-32 處理器則提供了一些指令對這種轉換進行優(yōu)化(使用 sysentersysexit 指令)。由于存在大量的方法,最終結果也非常復雜,因此本文將著重于接口細節(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)容的位置。(有關多路分解的更多內(nèi)容,請參看側欄 “系統(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)用(socketbind、 connect 等)都與一個單獨的系統(tǒng)調(diào)用索引(__NR_socketcall)關聯(lián)在一起,不過在內(nèi)核中會進行多路分解,通過另外一個參數(shù)進入適當?shù)恼{(diào)用。請參看 ./linux/net/socket.c 中的 sys_socketcall 函數(shù)。

      添加一個新系統(tǒng)調(diào)用主要是一些程序性的操作,但應該注意幾件事情。本節(jié)將介紹幾個系統(tǒng)調(diào)用的構造,從而展示它們的實現(xiàn)和用戶空間應用程序對它們的使用。

      向內(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ù)。(有關 jiffies 的更多信息,請參看側欄 “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 的全局定義和相關函數(shù)是在 ./linux/include/linux/jiffies.h 中提供的。

      清單 2 給出了第三個函數(shù)。這個函數(shù)使用了兩個參數(shù):一個 long 類型,以及一個指向被定義為 __userlong 的指針。__user 宏簡單告訴編譯器(通過 noderef)不應該解除這個指針的引用(因為在當前地址空間中它是沒有意義的)。這個函數(shù)會計算這兩個 jiffies 值之間的差值,然后通過一個用戶空間指針將結果提供給用戶。put_user 函數(shù)將結果值放入 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ù)本身之間建立一種對等關系。這就是第 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ù)(如結構或數(shù)組),您可以使用另外一組函數(shù): copy_from_usercopy_to_user。可以使用專門的調(diào)用移動以 null 結尾的字符串: 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 );
                          

      要移動更大的對象,例如結構或數(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 結尾的字符串從用戶空間移動到內(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ù)特定于架構,使用一種機制將控制權交給內(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 來構造 __NR 索引(getjiffies -> __NR_getjiffies)。其結果是您可以使用它們的名字來調(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
                          

      結果會產(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)。





      回頁首


      結束語

      分享這篇文章……

      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。



      參考資料

      學習


      關于作者

      M. tim jones

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

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

        0條評論

        發(fā)表

        請遵守用戶 評論公約

        類似文章 更多