Linux內核設計與實現---系統調用

系統調用

  • 1 API、POSIX和C庫
  • 2 系統調用
    • 系統調用號
  • 3 系統調用處理程序
    • 指定恰當的系統調用
    • 參數傳遞
  • 4 系統調用的實現
      • 參數驗證
  • 5 系統調用上下文
    • 綁定一個系統調用的最后步驟
    • 從用戶空間訪問系統調用
    • 為什么不通過系統調用的方式實現

1 API、POSIX和C庫

API:應用編程接口。一個API定義了一組應用程序使用的編程接口。它們可以實現成一個系統調用,也可以通過調用多個系統調用來實現,而完全不使用任何系統調用也不存在問題。在Unix世界中,最流行的應用編程接口是基于POSIX標準的。Linux是與POSIX兼容的。

Linux的系統調用像大多數Unix一樣,作為C庫的一部分。C庫實現了Unix系統的主要API,包括標準C庫函數和系統調用。

Unix的系統調用抽象出了完成某種確定目的的函數。至于這些函數怎么用完全不需要內核去關心,提供機制(需要提供什么功能)而不是策略(怎樣實現這些功能)。

2 系統調用

系統調用通常通過函數進行調用,在Linux中常稱作syscalls。系統調用還會通過一個long類型的返回值來表示成功或者失敗,使用long類型是為了與64位的硬件體系結構保持兼容。

函數聲明中如果用asmlinkage限定詞,表示通知編譯器僅從棧中提取函數的參數,所有的系統調用都需要這個限定詞。Linux所有的系統調用都應該遵守的命名規則:系統調用getpid()在內核中被定義為sys_getpid(),要在系統調用加上sys_。

系統調用號

Linux中,每個系統調用都被賦予一個系統調用號。這樣,通過這個獨一無二的號就可以關聯系統調用。當用戶空間的進程執行一個系統調用的時候,這個系統調用號就被用來指明到底是要執行那個系統調用,進程不會提及系統調用的名稱。

系統調用號一旦分配就不能再有任何變更,否則編譯好的應用程序就會崩潰。此外,如果一個系統調用被刪除,它所占的系統調用號也不允許被回收利用,否則,以前編譯過的代碼再調用這個系統調用,實際上調用的就是另一個系統調用了。Linux有一個系統調用sys_ni_syscall(),它除了返回ENOSYS外不做任何其他的工作,這個錯誤號就是專門針對無效的系統調用而設的。

內核記錄了系統調用表中的所有已被注冊過的系統調用的列表,存儲在sys_call_table中。它與結構體系有關,需要將系統調用分別注冊到每個需要支持的體系結構去,一般定義在entry.s中。2.6.10版本的位置在arch/結構體系/kernel/entry.S。這個表中為每一個有效的系統調用指定了唯一的系統調用號。
m32r體系結構:
在這里插入圖片描述

3 系統調用處理程序

用戶空間的程序無法直接執行內核代碼,他們不能直接調用內核空間中的函數,所以,應用程序應該以某種方式通知系統,告訴內核自己需要執行一個系統調用,希望系統切換到內核態,通知內核的機制是靠軟中斷實現的:通過一個異常來促使系統切換到內核態去執行異常處理程序。此時的異常處理程序就是系統調用程序。x86系統上的軟中斷是int 0x80指令產生的。這條指令會觸發一個異常導致系統切換到內核態并執行第128號異常處理程序,而該程序正是系統調用處理程序,叫system_call()。x86處理器增加了一條叫做sysenter的指令。與int指令相比,這條指令提供了更快、更專業的陷入內核執行系統調用的方式。

指定恰當的系統調用

因為所有的系統調用陷入內核方式都一樣,因此必須把系統調用號一并傳給內核,告訴內核去執行什么系統調用。在x86上,系統調用號是通過eax寄存器傳遞給內核的。在陷入內核之前,用戶空間就把相應系統調用所對應的號放入eax中。這樣系統調用處理程序一旦運行,就可以從eax中得到數據。

system_call()函數通過將給定的系統調用號與NR_syscalls做比較來檢查其有效性。如果大于等于NR_syscalls,該函數返回ENOSYS。否則,就執行相應的系統調用。

call *sys_call_table(,%eax,4)

由于系統調用表中的表項是32位(4字節)類型存放的,所以內核需要將給定的系統調用號乘以4,然后用所得的結構在該表中查詢其位置。
在這里插入圖片描述

參數傳遞

除了系統調用號外,大部分系統調用都還需要一些外部的參數輸入。所以,在發送異常的時候,應該把這些參數從用戶空間傳給內核。最簡單的辦法就是像傳遞系統調用號一樣:把這些參數存放到寄存器里。在x86系統上,ebx、ecx、edx、edi和esi按照順序存放前五個參數。需要6個或6個以上參數的,應該用一個單獨的寄存器存放指向所有這些參數在用戶空間地址的指針。

用戶空間返回值也通過寄存器傳遞,在x86系統上,它存放在eax寄存器中。

4 系統調用的實現

實現一個新的系統調用的第一步是決定它的用途。它要做什么?每個系統調用都應該有一個明確的用途。在Linux中不提倡采用多用途的系統調用(一個系統調用根據參數來選擇完成不同的工作)。

新系統調用的參數、返回值和錯誤碼又該是什么?系統調用的接口應該簡潔,參數盡可能的少。

設計接口的時候要盡量為將來多做考慮。

當寫一個系統調用的時候,要時刻注意可移植性和健壯性,不但要考慮當前,還要為將來做打算。

參數驗證

系統調用必須仔細檢查它們所有的參數是否合法有效。系統調用在內核空間執行,如果任由用戶將不合法的輸入傳遞給內核,那么系統的安全和穩定將面臨巨大的考驗。

內核提供了兩個方法來完成必須的檢查和內核空間與用戶空間之間數據的來回拷貝。為了向用戶空間寫入數據,內核提供了copy_to_uesr(),它需要三個參數。第一個參數是進程空間中的目的內存地址。第二個是內核空間內的源地址。最后一個參數是需要拷貝的數據長度。為了從用戶空間讀取數據,內核提供了copy_from_user()。該函數把第二個參數指定的位置上的數據拷貝到第一個參數指定的位置上,拷貝的數據長度由第三個參數決定。

注意,內核無論何時都不能輕率地接受來自用戶空間的指針!

5 系統調用上下文

在執行系統調用的時候處于進程上下文。current指針指向當前任務,即引發系統調用的那個進程。
在進程上下文中,內核可以休眠并且可以被搶占。當系統調用返回的時候,控制權仍然在system_call()中,它最終負責切換到用戶空間并讓用戶進程繼續執行下去。

綁定一個系統調用的最后步驟

當編寫完一個系統調用后,把它注冊成一個正式的系統調用是件瑣碎的工作:

  • 首先,在系統調用表的最后加入一個表項。每種支持該系統調用的硬件體系都必須做這樣的工作。從0開始算起,系統調用在該表中的位置就是它的系統調用號。如第10個系統調用分配到的系統調用號為9。
  • 對于所支持的各種體系結構,系統調用號都必須定義于include/asm/unistd.h中。
  • 系統調用必須被編譯進內核映像(不能編譯成模塊)。這只要把它放進kernel/下的一個相關文件就行了。

我們通過一個虛構的系統調用foo()來仔細觀察這些步驟。首先,我們把sys_foo加入到系統調用表中去。對于大多數體系結構中,該表位于entry.s中,形式如下:
請添加圖片描述
我們把新的系統調用加到這個表的末尾:

.long sys_foo

雖然沒有明確地指定編號,但我們加入這個系統調用被次序分配給了285這個系統調用號。對于每種需要支持的體系結構,我們都必須將自己的系統調用加入到其系統調用表中去。每種體系結構不需要對應相同的系統調用號。系統調用號是專屬體系結構ABI(應用程序二進制結構)的部分。

接下來,我們把系統調用號加入到include/asm/unistd.h中,它的格式如下
請添加圖片描述
然后,我們在該列表中加入下面這行:

#define _NR_foo 			285

同時#define NR_syscalls 285這個要改成#define NR_syscalls 286。因為系統調用處理程序system_call()函數通過將給定的系統調用號與NR_syscalls做比較來檢查其有效性。如果大于等于NR_syscalls,該函數返回ENOSYS。否則,就執行相應的系統調用。

最后,我們來實現foo()系統調用。無論何種配置,該系統調用都必須編譯到核心的內核映像中去,所以我們把它放在kernel/sys.c文件中。再次編譯內核就可以了。

從用戶空間訪問系統調用

Linux提供了一組宏,用于直接對系統調用進行訪問。它會設置好寄存器并調用陷入指令。這些宏是_syscalln(),其中n的范圍是0到6,代表需要傳遞給系統調用的參數個數。舉個例子,open系統調用的 定義為:

long open(const char *filename,int flags,int mode);

直接調用該系統調用的宏形式為:

_syscall3(long,open,const char *,filename,int flags,int mode)

這樣程序就可直接使用open。對于每個宏,都有2+2*n個參數,第一個參數是返回值類型。第二個參數是系統調用的名稱。

為什么不通過系統調用的方式實現

建立一個新的系統調用非常容易,但卻絕不倡導。

建立一個新的系統調用的好處:

  • 系統調用創建容易且使用方便
  • Linux系統調用的高性能顯而易見

問題是:

  • 你需要一個系統調用號,而這需要在一個內核處于開發版本的時候由官方分配給你
  • 系統調用被加入穩定內核后就被固化了
  • 需要將系統調用分別注冊的每個需要的體系結構去
  • 在腳本中不容易調用系統調用,也不能從文件系統直接訪問系統調用

本文來自互聯網用戶投稿,該文觀點僅代表作者本人,不代表本站立場。本站僅提供信息存儲空間服務,不擁有所有權,不承擔相關法律責任。
如若轉載,請注明出處:http://www.pswp.cn/news/379671.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/379671.shtml
英文地址,請注明出處:http://en.pswp.cn/news/379671.shtml

如若內容造成侵權/違法違規/事實不符,請聯系多彩編程網進行投訴反饋email:809451989@qq.com,一經查實,立即刪除!

相關文章

內核編譯配置選項含義

Linux 2.6.19.x 內核編譯配置選項簡介 作者:金步國 版權聲明 本文作者是一位自由軟件愛好者,所以本文雖然不是軟件,但是本著 GPL 的精神發布。任何人都可以自由使用、轉載、復制和再分發,但必須保留作者署名,亦不得對聲…

js編碼處理(轉)

js編碼處理(轉) 1. 使用 JS 中的 encodeURIComponent 或 encodeURI 方法。 說明: encodeURIComponent(String) 對傳遞參數進行設置。不編碼字符有 71 個: ! , , ( , ) , * , - &#…

手動去設置HTTP響應行、響應頭、響應體

①手動去設置HTTP響應行中的狀態碼,這里用到了response的setStatus(int sc);這個方法 package com.itheima.line;import java.io.IOException; import javax.servlet.ServletException; import javax.servlet.http.HttpServlet; import javax.servlet.http.HttpSer…

Java SecurityManager checkListen()方法與示例

SecurityManager類的checkListen()方法 (SecurityManager Class checkListen() method) checkListen() method is available in java.lang package. checkListen()方法在java.lang包中可用。 checkListen() method invokes checkPermission with the given SocketPermission(&q…

基本的二分查找、尋找第一個和最后一個數的二分查找

二分查找1 二分查找的框架2 尋找一個數(基本的二分搜索)3 尋找左側邊界的二分搜索4 尋找右側邊界的二分查找5 合并二分查找場景:有序數組尋找一個數、尋找左側邊界(有序數組第一個等目標數的下標)、尋找右側邊界&#…

PostgreSQL 中的遞歸查詢 與oracle 的比較

PostgreSQL 中的遞歸查詢,2種方法: 1、用with decursive WITH RECURSIVE d AS (SELECT d1.id,d1.parent_id,d1.caption FROM course_types d1 where d1.dr 0 and d1.idtypeId union ALL SELECT d2.id,d2.parent_id,d2.caption FROM course_types d2, d …

教你如何玩轉GitHub

使用GitHub ①目的:借助GitHub托管項目代碼 基本概念: ①倉庫(Repository): 用來存放項目代碼,每個項目對應一個倉庫,多個開源項目對應多個倉庫 ②收藏(Star): 收藏項目,方便下次查看 ③…

Java SecurityManager checkDelete()方法與示例

SecurityManager類的checkDelete()方法 (SecurityManager Class checkDelete() method) checkDelete() method is available in java.lang package. checkDelete()方法在java.lang包中可用。 checkDelete() method calls checkPermission with FilePermission(filename,"d…

jQuery中的treeview插件

jQuery做樹狀結構真的很簡單,下面做一個最簡單的示例: 在html文件中引用: <link rel"stylesheet" href"../jquery.treeview.css" /> <link rel"stylesheet" href"../red-treeview.css" /> <link rel"styles…

Linux內核設計與實現---中斷和中斷處理程序

中斷和中斷處理程序1 中斷異常2 中斷處理程序上半部與下半部的對比3 注冊中斷處理程序釋放中斷處理程序4 編寫中斷處理程序重入和中斷處理程序共享的中斷處理程序中斷處理程序實例5 中斷上下文6 中斷處理機制的實現7 中斷控制禁止和激活中斷禁止指定中斷線中斷系統的狀態8 總結…

asp.net中的窗體身份驗證(最簡單篇)

在創建網站中&#xff0c;常常會使用到身份驗證。asp.net中內置了幾種身份驗證的方式&#xff0c;如Windows、Froms、Passport等。這幾種身份驗證的方式各有不同。一般來說&#xff0c;網站的身份驗證方式都會經過以下幾個步驟&#xff1a; 1、輸入用戶名和密碼&#xff0c;單擊…

bat文件調用dos命令 (dos淘金)

ECHO命令是大家都熟悉的DOS批處理命令的一條子命令&#xff0c;但它的一些功能和用法也許你并不是全都知道&#xff0c;不信你瞧&#xff1a; 1&#xff0e; 作為控制批處理命令在執行時是否顯示命令行自身的開關 格式&#xff1a;ECHO [ON|OFF] 如果想關閉“ECHO OFF”命令…

response細節點

一、 1&#xff09;、response獲得的流不需要手動關閉&#xff0c;Tomcat容器會幫你自動關閉 2&#xff09;、getWriter和getOutputStream不能同時調用 //error package com.itheima.content;import java.io.IOException; import javax.servlet.ServletException; import java…

Java RandomAccessFile writeBytes()方法與示例

RandomAccessFile類writeBytes()方法 (RandomAccessFile Class writeBytes() method) writeBytes() method is available in java.io package. writeBytes()方法在java.io包中可用。 writeBytes() method is used to write the sequence of bytes (i.e. string) to the file. E…

linux內核設計與實現---下半部和推后執行的工作

下半部和推后執行的工作1 下半部為什么要用下半部下半部的環境內核定時器2 軟中斷軟中斷的實現軟中斷處理程序執行軟中斷使用軟中斷3 tasklettasklet的實現使用taskletksoftirqd4 工作隊列工作隊列的實現工作、工作隊列和工作者線程之間的關系使用工作隊列5 下半部機制的選擇6 …

Jquery對復選框的操作

<from> 你的愛好是?<br/> <input type"checkbox" name"items" value"籃球" />籃球 <input type"checkbox" name"items" value"乒乓球" />乒乓球 <input type"checkbox" na…

HttpServletRequest(request的一些API)

一、request的運行流程 首先&#xff0c;自己寫一個web工程&#xff0c;也就是建一個工程&#xff1b;當把該web工程發布到Tomcat服務器當中&#xff0c;可以讓外界訪問&#xff0c;這就成了一個web應用。 在客戶端輸入一個網站&#xff0c;是web應用資源的地址URL&#xff0c…

DCI:James O. Coplien和Trygve Reenskau提出的新架構方法

http://www.infoq.com/cn/news/2009/05/dci-coplien-reenskau 轉載于:https://www.cnblogs.com/yelinpalace/archive/2009/06/13/1502573.html

Java ObjectStreamField getOffset()方法與示例

ObjectStreamField類的getOffset()方法 (ObjectStreamField Class getOffset() method) getOffset() method is available in java.io package. getOffset()方法在java.io包中可用。 getOffset() method is used to get the offset of this ObjectStreamField field. getOffse…

Mac VSCode配置C語言環境(可以調試)

Mac VSCode配置C語言環境c_cpp_properties.jsontasks.jsonlaunch.json新建一個文件夾&#xff0c;用vscode&#xff0c;然后再新建一個test.c文件。 #include <stdio.h>int main(void) {int a1,b1;int cab;printf("%d\n",c);return 0; }這篇文章說怎么配置c_c…