Linux 系統調用

與內核通信

為了和用戶空間上運行的進程進行交互,內核提供了一組接口。透過該接口,應用程序可以訪問硬件設備和其他操作系統資源。這組接口在應用程序和內核之間扮演了使者的角色,應用程序發送各種請求,而內核負責滿足這些需求(或者讓應用程序暫時擱置)。實際上提供這組接口主要是為了保證系統穩定可靠,避免應用程序恣意妄行,惹出大麻煩。

系統調用在用戶空間進程和硬件設備之間添加了一個中間層,該層的主要作用有三個。第一,它為用戶空間提供了一種硬件的抽象接口。第二,系統調用保證了系統的穩定和安全。作為硬件設備和應用程序之間的中間人,內核可以基于權限和其他一些規則對需要的訪問進行裁決。第三,每個進程都運行在虛擬系統中,而在用戶空間和系統的其余部分提歐共這樣的一層公共接口,也是出于這種考慮,如果應用程序可以隨意訪問硬件爾內核又對此一無所知的話,幾乎就沒法實現多任務和虛擬內存,當然也不可能實現良好的穩定性和安全性。

API、POSIX和C庫

1、一般情況下,應用程序通過在用戶空間實現的應用編程接口(API)而不是直接通過系統調用來編程。一個API定義了一組應用程序使用的編程接口。它可以實現成一個系統調用,也可以通過調用多個系統調用來實現,而完全不使用任何系統調用也不存在任何問題。

2、在Unix系統中,最流行的應用程序編程接口是基于POSIX標準的。

3、Linux的系統調用作為C庫的一部分提供。C庫實現了Unix系統主要API,包括標準C庫函數和系統調用接口。

4、應用編程與系統調用無關緊要,但內核只跟系統調用打交道;庫函數及應用程序是怎么使用系統調用的,不是內核所關心的。

5、Unix接口設計有一句格言:“提供機制而不提供策略”,換句話說,Unix系統調用抽象出了用于完成某種確定目的的函數。至于這些函數怎么使用完全不用內核關心。

系統調用

系統調用(在Linux種常稱作syscalls)通常通過函數進行調用。它們通常都需要定義一個或者多個參數,而且可能產生一些副作用,例如寫某個文件或向給定的指針拷貝數據等等。系統調用還會通過一個long類型的返回值來表示成功或者錯誤。通常,用一個負的返回值來表示錯誤。返回一個0值表示成功。Unix系統調用在出現錯誤的時候,C庫會把錯誤碼寫入errno全局變量,通過調用perror()庫函數,可以把變量翻譯成用戶可以理解的錯誤字符串。

當然,系統調用最終具有一種明確的操作:例如getpid() 系統調用,根據定義它會返回當前進程的PID,內核中他的實現非常簡單:

/**
 * 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只是一個宏,它定義了一個無參數的系統調用(這里數字為0),展開后的代碼如下:

asmlinkage long sys_getpid(void);

首先,必須在聲明中使用asmlinkage限定詞,這是一個編譯指令,通知編譯器僅從棧中提取該函數的參數,所有的系統調用都需要這個詞。

其次,函數返回值。為了保證32位和64位系統的兼容,系統調用在用戶空間和內核空間有不同的返回值類型,用戶空間為int,內核空間為long。

最后,系統調用應該被定義與sys_XX的形式。這是Linux種所有系統調用都應該遵守的命名規則。
(1)系統調用號
1、在Linux中,每個系統調用被賦予一個系統調用號。通過這個系統調用號可以關聯系統調用。

2、系統調用號非常重要,一旦分配就不能再有任何變更,否則編譯好的應用程序就會崩潰。

3、如果一個系統調用被刪除,它所占用的系統調用號也不允許被回收利用,否則,以前編譯過的代碼會調用此系統調用,但事實上卻調用另一個系統調用。

4、Linux中有 一個“未實現”系統調用 sys_ni_syscall(),它除了返回-ENOSYS外不做任何事,此錯誤號就是專門針對無效的系統調用而設的。

5、內核記錄了系統調用表中的所有已注冊過的系統調用的列表,存儲在sys_call_tabe中。

(2)系統調用性能
Linux系統調用比其他許多操作系統執行的要快。

系統調用處理函數

1、應用程序通知內核的機制是靠軟中斷實現的:通過引發一個異常來促使系統切換到內核態去執行異常處理程序。此時的異常處理程序實際上就是系統調用處理程序。

2、指定恰當的系統調用
1)、僅僅陷入內核空間是不夠的。必須把系統調用號一并傳給內核。
2)、在X86上,系統調用號是通過eax寄存器傳遞給內核的。在陷入內核之前,用戶空間就把相應系統調用所對應的號放入eax中。
3)、system_call函數通過將給定的系統調用號與NR_syscalls作比較來檢查其有效性。如果它大于或等于NR_syscalls,該函數就返回-ENOSYS。否則,就執行相應的系統調用。

call  *sys_call_table( , %rax,  8);

3、參數傳遞
1)、除了系統調用號之外,大部分系統調用還需要一些外部參數的輸入。再發生陷入的時候,應該把這些參數從用戶空間中傳給內核。
2)、用寄存器傳遞系統調用。在X86系統上,ebx、ecx、edx、esi和edi按順序存放前五個參數。留個或留個以上參數不常見。此外,應該用一個單獨的寄存器存放指向所有這些參數在用戶空間地址的指針。

系統調用的實現

1)、實現一個新的系統調用的第一步是決定它的用途,它要做些什么?每個系統調用應該有一個明確的用途,Linux中不提倡多用途的系統調用(一個系統調用通過傳遞不同的參數值來完成不同的工作),ioctl 就是一個典型的反例。

2)、系統調用必須仔細檢查它們所有的參數是否合法有效。最重要的一種檢查就是檢查用戶提供的指針是否有效,在接收一個用戶空間的指針之前,內核必須保證:
I、指針指向的內存區域屬于用戶空間。
II、指針指向的內存區域在進程的地址空間里。
III、如果是讀,改內核應被標記為可讀;如果是寫,改內核應被標記為可寫;如果是可執行,改內核應被標記為可執行。
3)、內核提供了兩個方法來完成必須的檢查和內核空間與用戶空間之間數據的來回拷貝。
I、copy_to_user();
II、copy_from_user();
III、如果執行失敗,這兩個函數返回的都是沒能完成拷貝的數據的字節數。如果成功,則返回0.當出現上述錯誤時,系統調用返回標準-EEAULT。
4)、檢查針對是否有合法權限。
系統允許檢查針對特定資源的特殊權限,調用者可以使用ns_capable()函數來檢查是否有權能對特定的資源進行操作。
例如:下面reboot的系統調用,第一步是判斷是否具有CAP_SYS_BOOT的權能?

SYSCALL_DEFINE4(reboot, int, magic1, int, magic2, unsigned int, cmd,
        void __user *, arg)
{
    struct pid_namespace *pid_ns = task_active_pid_ns(current);
    char buffer[256];
    int ret = 0;

    /* We only trust the superuser with rebooting the system. */
    if (!ns_capable(pid_ns->user_ns, CAP_SYS_BOOT))
        return -EPERM;

    /* For safety, we require "magic" arguments. */
    if (magic1 != LINUX_REBOOT_MAGIC1 ||
            (magic2 != LINUX_REBOOT_MAGIC2 &&
            magic2 != LINUX_REBOOT_MAGIC2A &&
            magic2 != LINUX_REBOOT_MAGIC2B &&
            magic2 != LINUX_REBOOT_MAGIC2C))
        return -EINVAL;

    /*
     * If pid namespaces are enabled and the current task is in a child
     * pid_namespace, the command is handled by reboot_pid_ns() which will
     * call do_exit().
     */
    ret = reboot_pid_ns(pid_ns, cmd);
    if (ret)
        return ret;

    /* Instead of trying to make the power_off code look like
     * halt when pm_power_off is not set do it the easy way.
     */
    if ((cmd == LINUX_REBOOT_CMD_POWER_OFF) && !pm_power_off)
        cmd = LINUX_REBOOT_CMD_HALT;

    mutex_lock(&reboot_mutex);
    switch (cmd) {
    case LINUX_REBOOT_CMD_RESTART:
        kernel_restart(NULL);
        break;

    case LINUX_REBOOT_CMD_CAD_ON:
        C_A_D = 1;
        break;

    case LINUX_REBOOT_CMD_CAD_OFF:
        C_A_D = 0;
        break;

    case LINUX_REBOOT_CMD_HALT:
        kernel_halt();
        do_exit(0);
        panic("cannot halt");

    case LINUX_REBOOT_CMD_POWER_OFF:
        kernel_power_off();
        do_exit(0);
        break;

    case LINUX_REBOOT_CMD_RESTART2:
        ret = strncpy_from_user(&buffer[0], arg, sizeof(buffer) - 1);
        if (ret < 0) {
            ret = -EFAULT;
            break;
        }
        buffer[sizeof(buffer) - 1] = '\0';

        kernel_restart(buffer);
        break;

#ifdef CONFIG_KEXEC
    case LINUX_REBOOT_CMD_KEXEC:
        ret = kernel_kexec();
        break;
#endif

#ifdef CONFIG_HIBERNATION
    case LINUX_REBOOT_CMD_SW_SUSPEND:
        ret = hibernate();
        break;
#endif

    default:
        ret = -EINVAL;
        break;
    }
    mutex_unlock(&reboot_mutex);
    return ret;
}
系統調用上下文

系統調用運行在進程上下文,所以可以休眠,可以被搶占,所以要保證該系統調用時可重入的。
1、綁定一個系統調用的最后步驟
1)、把系統調用注冊成一個正式的系統調用:
I、首先,在系統調用表的最后加入一個表項。
II、對于所支持的各種體系結構,系統調用號都必須定義于<asm/unistd.h>中。
III、系統調用必須被編譯進內核映像(不能編譯成模塊)。

2、從用戶空間訪問系統調用
1)、用戶程序通過包含標準頭文件和C庫連接,就可以使用系統調用。
2)、Linux本身提供一個宏,用于直接對系統調用進行訪問。
以Android系統一個reboot系統調用為例,應用程序調用reboot系統調用的方法如下:

ret = syscall(__NR_reboot, LINUX_REBOOT_MAGIC1, LINUX_REBOOT_MAGIC2,
                           LINUX_REBOOT_CMD_RESTART2, arg);

系統調用號的宏定義:位于文件/bionic/libc/kernel/uapi/asm-arm/asm/unistd.h
其中:

#define __NR_reboot 142

匯編定義相關函數的中斷調用過程:syscall位于文件/bionic/libc/arch-arm64/bionic/syscall.S,內容如下:

ENTRY(syscall)
    /* Move syscall No. from x0 to x8 */
    mov     x8, x0
    /* Move syscall parameters from x1 thru x6 to x0 thru x5 */
    mov     x0, x1
    mov     x1, x2
    mov     x2, x3
    mov     x3, x4
    mov     x4, x5
    mov     x5, x6
    svc     #0

    /* check if syscall returned successfully */
    cmn     x0, #(MAX_ERRNO + 1)
    cneg    x0, x0, hi
    b.hi    __set_errno_internal

    ret
END(syscall)

3、不通過系統調用的方式實現的原因。
盡量不要自己添加系統調用,有很多其他方法可以替代:
1)實現一個設備節點,對設備進行read,write操作,使用ioctl對特定的設置進行操作。
2)把增加的信息作為一個文件放在sysfs中。

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,702評論 6 534
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,615評論 3 419
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,606評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,044評論 1 314
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,826評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,227評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,307評論 3 442
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,447評論 0 289
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,992評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,807評論 3 355
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,001評論 1 370
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,550評論 5 361
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,243評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,667評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,930評論 1 287
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,709評論 3 393
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,996評論 2 374

推薦閱讀更多精彩內容

  • 一、為什么會有系統調用? 用戶進程(如瀏覽器)免不了要使用系統資源,如打開文件讀取內容等操作,但是計算機的資源由操...
    開發者共讀閱讀 329評論 0 0
  • 計算機系統漫游 代碼從文本到可執行文件的過程(c語言示例):預處理階段,處理 #inlcude , #defin...
    willdimagine閱讀 3,608評論 0 5
  • 原來自己多年來一直使用的庫函數竟有如此復雜的機制。這個機制的設計者思考的如此深入,屏蔽了底層硬件的差異,也是費勁心...
    athorn閱讀 1,167評論 0 1
  • 漫友支持!每日一畫! 《鐵拐戒之二》寬48高68厘米
    漫悟閱讀 263評論 0 4
  • 又是一個春暖花開的新年,和家人們踏著濃濃的年味向云南旅游目的地出發了,一切都是意外的驚喜,只為記錄這美好的發...
    靜靜成長說閱讀 254評論 0 3