1. 程式人生 > >ARM64核心系統呼叫詳解(基於kernel-4.9)

ARM64核心系統呼叫詳解(基於kernel-4.9)

本文以ARM64為例,介紹如何新增系統呼叫,首先來介紹一些程式碼執行流程:

首先來看異常向量表的配置,核心在arch/arm64/kernel/entry.S彙編程式碼中設定了異常向量表。

/*
 * Exception vectors.
 */
    .pushsection ".entry.text", "ax"
    .align  11
ENTRY(vectors)
    kernel_ventry   1, sync_invalid         // Synchronous EL1t
    kernel_ventry   1, irq_invalid          // IRQ EL1t
kernel_ventry 1, fiq_invalid // FIQ EL1t kernel_ventry 1, error_invalid // Error EL1t kernel_ventry 1, sync // Synchronous EL1h kernel_ventry 1, irq // IRQ EL1h kernel_ventry 1, fiq_invalid // FIQ EL1h kernel_ventry 1, error_invalid // Error EL1h
kernel_ventry 0, sync // Synchronous 64-bit EL0 kernel_ventry 0, irq // IRQ 64-bit EL0 kernel_ventry 0, fiq_invalid // FIQ 64-bit EL0 kernel_ventry 0, error_invalid // Error 64-bit EL0 #ifdef CONFIG_COMPAT kernel_ventry 0, sync_compat, 32 // Synchronous 32-bit EL0
kernel_ventry 0, irq_compat, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid_compat, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_invalid_compat, 32 // Error 32-bit EL0 #else kernel_ventry 0, sync_invalid, 32 // Synchronous 32-bit EL0 kernel_ventry 0, irq_invalid, 32 // IRQ 32-bit EL0 kernel_ventry 0, fiq_invalid, 32 // FIQ 32-bit EL0 kernel_ventry 0, error_invalid, 32 // Error 32-bit EL0 #endif END(vectors)

上面的程式碼進一步展開,即使就是設定不同mode下的異常向量表,異常可以分為4組,每組異常有4個,所以這裡一共會設定16個entry。4組異常分別對應4種情況下發生異常時的處理。上面的4組,按照順序分別對應如下4中情況:

(1)執行級別不發生切換,從ELx變化到ELx,使用SP_EL0,這種情況在Linux kernel都是不處理的,使用invalid entry。

(2)執行級別不發生切換,從ELx變化到ELx,使用SP_ELx。這種情況下在Linux中比較常見。

(3)異常需要進行級別切換來進行處理,並且使用aarch64模式處理,比如64位使用者態程式發生系統呼叫,CPU會從EL0切換到EL1,並且使用aarch64模式處理異常。

(4)異常需要進行級別切換來進行處理,並且使用aarch32模式處理。比如32位使用者態程式發生系統呼叫,CPU會從EL0切換到EL1,並且使用aarch32模式進行處理。

前面設定了異常向量表,我們來進一步檢視SVC mode的處理。當系統呼叫時CPU會切換到SVC mode,並跳轉到對應的地址去執行。

kernel中會配置兩個SVC Handler,分別對應這SVC_32/SVC_64兩種mode,32bit程式和64bit程式執行系統呼叫會跳轉到兩個不同的handler去執行。

核心在arch/arm64/kernel/entry.S彙編程式碼中設定了SVC異常entry。

64-bit執行模式解析

如下函式設定了64-bit狀態下的異常向量表設定,其中紅色部分是svc handler配置:

arch/arm64/kernel/entry.S:

    .align  6
el0_sync:
    kernel_entry 0
    mrs x25, esr_el1            // read the syndrome register
    lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
    cmp x24, #ESR_ELx_EC_SVC64      // SVC in 64-bit state
    b.eq    el0_svc
    cmp x24, #ESR_ELx_EC_DABT_LOW   // data abort in EL0
    b.eq    el0_da
    cmp x24, #ESR_ELx_EC_IABT_LOW   // instruction abort in EL0
    b.eq    el0_ia
    cmp x24, #ESR_ELx_EC_FP_ASIMD   // FP/ASIMD access
    b.eq    el0_fpsimd_acc
    cmp x24, #ESR_ELx_EC_FP_EXC64   // FP/ASIMD exception
    b.eq    el0_fpsimd_exc
    cmp x24, #ESR_ELx_EC_SYS64      // configurable trap
    b.eq    el0_sys
    cmp x24, #ESR_ELx_EC_SP_ALIGN   // stack alignment exception
    b.eq    el0_sp_pc
    cmp x24, #ESR_ELx_EC_PC_ALIGN   // pc alignment exception
    b.eq    el0_sp_pc
    cmp x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL0
    b.eq    el0_undef
    cmp x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
    b.ge    el0_dbg
    b   el0_inv

el0_svc的實現如下:

/*
 * SVC handler.
 */
    .align  6
el0_svc:
    adrp    stbl, sys_call_table        // load syscall table pointer
    uxtw    scno, w8            // syscall number in w8
    mov sc_nr, #__NR_syscalls
el0_svc_naked:                  // compat entry point
    stp x0, scno, [sp, #S_ORIG_X0]  // save the original x0 and syscall number
    enable_dbg_and_irq
    ct_user_exit 1
    ldr x16, [tsk, #TSK_TI_FLAGS]   // check for syscall hooks
    tst x16, #_TIF_SYSCALL_WORK
    b.ne    __sys_trace
    cmp     scno, sc_nr                     // check upper syscall limit
    b.hs    ni_sys
    ldr x16, [stbl, scno, lsl #3]   // address in the syscall table
    blr x16             // call sys_* routine
    b   ret_fast_syscall
ni_sys:
    mov x0, sp
    bl  do_ni_syscall
    b   ret_fast_syscall
ENDPROC(el0_svc)

可以看到它會去查詢sys_call_table這個陣列並找到對應的系統呼叫函式去執行,注意其中有一個關鍵函式do_ni_syscall,(no implement syscall),當系統呼叫遇到一些限制或者問題時會跳轉到該函式去執行。

sys_call_table的定義在如下檔案中:

arch/arm64/kernel/sys.c:
/*
 * The sys_call_table array must be 4K aligned to be accessible from
 * kernel/entry.S.
 */
void * const sys_call_table[__NR_syscalls] __aligned(4096) = {
    [0 ... __NR_syscalls - 1] = sys_ni_syscall,
#include <asm/unistd.h>
};

這個陣列在建立時首先會把所有的陣列成員設定為sys_ni_syscall,而後根據asm/unistd.h中的內容做進一步初始化。其實最終該標頭檔案會把include/uapi/asm-generic//unistd.h包含進來,也就是這個標頭檔案會是最終定義陣列的地方。

......
__SYSCALL(__NR_epoll_wait, sys_epoll_wait)
#define __NR_ustat 1070
__SYSCALL(__NR_ustat, sys_ustat)
#define __NR_vfork 1071
__SYSCALL(__NR_vfork, sys_vfork)
#define __NR_oldwait4 1072
__SYSCALL(__NR_oldwait4, sys_wait4)
#define __NR_recv 1073
__SYSCALL(__NR_recv, sys_recv)
#define __NR_send 1074
__SYSCALL(__NR_send, sys_send)
#define __NR_bdflush 1075
__SYSCALL(__NR_bdflush, sys_bdflush)
#define __NR_umount 1076
__SYSCALL(__NR_umount, sys_oldumount)
#define __ARCH_WANT_SYS_OLDUMOUNT
#define __NR_uselib 1077
__SYSCALL(__NR_uselib, sys_uselib)
#define __NR__sysctl 1078
__SYSCALL(__NR__sysctl, sys_sysctl)
#define __NR_fork 1079
#ifdef CONFIG_MMU
__SYSCALL(__NR_fork, sys_fork)
#else
__SYSCALL(__NR_fork, sys_ni_syscall)
#endif /* CONFIG_MMU */
......

32-bit執行模式解析

如下函式設定了32-bit狀態下的異常向量表設定,其中紅色部分是svc handler配置:

arch/arm64/kernel/entry.S

 #ifdef CONFIG_COMPAT
     .align  6
 el0_sync_compat:
     kernel_entry 0, 32
     mrs x25, esr_el1            // read the syndrome register
     lsr x24, x25, #ESR_ELx_EC_SHIFT // exception class
     cmp x24, #ESR_ELx_EC_SVC32      // SVC in 32-bit state
     b.eq    el0_svc_compat
     cmp x24, #ESR_ELx_EC_DABT_LOW   // data abort in EL0
     b.eq    el0_da
     cmp x24, #ESR_ELx_EC_IABT_LOW   // instruction abort in EL0
     b.eq    el0_ia
     cmp x24, #ESR_ELx_EC_FP_ASIMD   // FP/ASIMD access
     b.eq    el0_fpsimd_acc
     cmp x24, #ESR_ELx_EC_FP_EXC32   // FP/ASIMD exception
     b.eq    el0_fpsimd_exc
     cmp x24, #ESR_ELx_EC_PC_ALIGN   // pc alignment exception
     b.eq    el0_sp_pc
     cmp x24, #ESR_ELx_EC_UNKNOWN    // unknown exception in EL0
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_CP15_32    // CP15 MRC/MCR trap
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_CP15_64    // CP15 MRRC/MCRR trap
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_CP14_MR    // CP14 MRC/MCR trap
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_CP14_LS    // CP14 LDC/STC trap
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_CP14_64    // CP14 MRRC/MCRR trap
     b.eq    el0_undef
     cmp x24, #ESR_ELx_EC_BREAKPT_LOW    // debug exception in EL0
     b.ge    el0_dbg
     b   el0_inv
 el0_svc_compat:
     /*
      * AArch32 syscall handling
      */
     adrp    stbl, compat_sys_call_table // load compat syscall table pointer
     uxtw    scno, w7            // syscall number in w7 (r7)
     mov     sc_nr, #__NR_compat_syscalls
     b   el0_svc_naked

     .align  6
 el0_irq_compat:
     kernel_entry 0, 32
     b   el0_irq_naked
 #endif

el0_svc_compat的實現如下:

el0_svc_compat:
    /*
     * AArch32 syscall handling
     */
    adrp    stbl, compat_sys_call_table // load compat syscall table pointer
    uxtw    scno, w7            // syscall number in w7 (r7)
    mov     sc_nr, #__NR_compat_syscalls
    b   el0_svc_naked
    .align  6
el0_irq_compat:
    kernel_entry 0, 32
    b   el0_irq_naked

可以看到它會去查詢compat_sys_call_table這個陣列並找到對應的系統呼叫函式去執行,compat_sys_call_table的定義在如下檔案中:

arch/arm64/kernel/sys32.c:
 /*
  * The sys_call_table array must be 4K aligned to be accessible from
  * kernel/entry.S.
  */
 void * const compat_sys_call_table[__NR_compat_syscalls] __aligned(4096) = {
     [0 ... __NR_compat_syscalls - 1] = sys_ni_syscall,
 #include <asm/unistd32.h>
 };

這個陣列在建立時首先會把所有的陣列成員設定為sys_ni_syscall,而後根據asm/unistd32.h中的內容做進一步初始化。其實最終該標頭檔案會把arch/arm64/include/asm/unistd32.h包含進來,也就是這個標頭檔案會是最終定義函式陣列的地方。

arch/arm64/include/asm/unistd32.h:
......
 __SYSCALL(__NR_process_vm_writev, compat_sys_process_vm_writev)
 #define __NR_kcmp 378
 __SYSCALL(__NR_kcmp, sys_kcmp)
 #define __NR_finit_module 379
 __SYSCALL(__NR_finit_module, sys_finit_module)
 #define __NR_sched_setattr 380
 __SYSCALL(__NR_sched_setattr, sys_sched_setattr)
 #define __NR_sched_getattr 381
 __SYSCALL(__NR_sched_getattr, sys_sched_getattr)
 #define __NR_renameat2 382
 __SYSCALL(__NR_renameat2, sys_renameat2)
 #define __NR_seccomp 383
 __SYSCALL(__NR_seccomp, sys_seccomp)
 #define __NR_getrandom 384
 __SYSCALL(__NR_getrandom, sys_getrandom)
 #define __NR_memfd_create 385
 __SYSCALL(__NR_memfd_create, sys_memfd_create)
 #define __NR_bpf 386
 __SYSCALL(__NR_bpf, sys_bpf)
 #define __NR_execveat 387
 __SYSCALL(__NR_execveat, compat_sys_execveat)
 #define __NR_userfaultfd 388
 __SYSCALL(__NR_userfaultfd, sys_userfaultfd)
 #define __NR_membarrier 389
 __SYSCALL(__NR_membarrier, sys_membarrier)
 #define __NR_mlock2 390
 __SYSCALL(__NR_mlock2, sys_mlock2)
 #define __NR_copy_file_range 391
 __SYSCALL(__NR_copy_file_range, sys_copy_file_range)
 #define __NR_preadv2 392
 __SYSCALL(__NR_preadv2, compat_sys_preadv2)
 #define __NR_pwritev2 393
 __SYSCALL(__NR_pwritev2, compat_sys_pwritev2)
......

最後來看一下do_ni_syscall,核心中沒有意義的系統呼叫號都會執行到該函式上面:

asmlinkage long do_ni_syscall(struct pt_regs *regs)
{
#ifdef CONFIG_COMPAT
    long ret;
    if (is_compat_task()) {
        ret = compat_arm_syscall(regs);
        if (ret != -ENOSYS)
            return ret;
    }
#endif
    if (show_unhandled_signals_ratelimited()) {
        pr_info("%s[%d]: syscall %d\n", current->comm
            task_pid_nr(current), (int)regs->syscallno);
        dump_instr("", regs);
        if (user_mode(regs))     
            __show_regs(regs);
    } 
    return sys_ni_syscall();
}

本文基於kernel-4.9版本,原創文章,轉載請標註。

相關推薦

ARM64核心系統呼叫基於kernel-4.9

本文以ARM64為例,介紹如何新增系統呼叫,首先來介紹一些程式碼執行流程: 首先來看異常向量表的配置,核心在arch/arm64/kernel/entry.S彙編程式碼中設定了異常向量表。 /* * Exception vectors. */

核心中訪問空指標基於kernel-4.9

在C語言中,我們定義了NULL來表示空指標,空指標是一個特殊的指標,它其實就是0指標,*p = NULL和*p=0是等價的寫法。空指標是一個未賦值的指標,毫無意義的指標,如果訪問到該地址,那麼程式會出錯。 如果在Linux應用程式中訪問NULL指標: 會收到

Linux系統呼叫如何從使用者空間進入核心空間

系統呼叫概述         計算機系統的各種硬體資源是有限的,在現代多工作業系統上同時執行的多個程序都需要訪問這些資源,為了更好的管理這些資源程序是不允許直接操作的,所有對這些資源的訪問都必須有作業系統控制。也就是說作業系統是使用這些資源的唯一入口,而這個入口就是作業系

linux核心剖析---Linux系統呼叫實現機制分析

本文介紹了系統呼叫的一些實現細節。首先分析了系統呼叫的意義,它們與庫函式和應用程式介面(API)有怎樣的關係。然後,我們考察了Linux核心如何實現系統呼叫,以及執行系統呼叫的連鎖反應:陷入核心,傳遞系統呼叫號和引數,執行正確的系統呼叫函式,並把返回值帶回使用者空間。最後

制作自己的Setup.exe-程序打包基於Visual Studio 2015

忘記 圖片 安裝文件 for int .com create rtc gis 序言   第一次打包程序,新手,遂作筆記如下,以供自己忘記細節時翻看,也供同樣新手或有需要者以為參考。不敢班門弄斧,大神若是誤入還請莫要見笑。   以下所述基於Visual Studio 2015

MapReduce編程模型基於Windows平臺Eclipse

lib read 找到 lin @override ext logs 設置 otf 本文基於Windows平臺Eclipse,以使用MapReduce編程模型統計文本文件中相同單詞的個數來詳述了整個編程流程及需要註意的地方。不當之處還請留言指出。 前期準備 hadoop集群

Intel硬編碼:不定長指令、ModR/M與SIB基於P6微架構

Intel硬編碼(一):Opcode Map、定長指令與指令字首 我們在Opcode Map中提到定長指令的索引方式,也分析了比較常見的一些定長指令,接著我們就要進行不定長指令的分析了。所謂不定長指得是SIB部分、Displcement、Immediate三部

十三linux檔案系統基於ext2檔案系統

  我們知道,一個磁碟可以劃分成多個分割槽,每個分割槽必須先用格式化工具(例如某種mkfs命令)格式化成某種格式的檔案系統,然後才能儲存檔案,格式化的過程會在磁碟上寫一些管理儲存佈局的資訊。下圖是一個磁碟分割槽格式化成ext2檔案系統後的儲存佈局:

Vue.js 運行環境搭建基於windows的手把手安裝教學及vue、node基礎知識普及

頁面 沒有 全能 服務器程序 重載 帶來 size 耐心 編程   Vue.js 是一套構建用戶界面的漸進式框架。他自身不是一個全能框架——只聚焦於視圖層。因此它非常容易學習,非常容易與其它庫或已有項目整合。在與相關工具和支持庫一起使用時,Vue.j

操作系統Lab1 boot|kern/debug

segment frame gin tabs program its 內容 AC 描述 總體 : boot kern libs tools boot asm.h bootmain.c bootasm.S asm.h 匯編頭文件 SEG_NULLASM 定義一個空段描述符 S

Flume NG高可用叢集搭建基於flume-1.7.0

1、Flume NG簡述 Flume NG是一個分散式,高可用,可靠的系統,它能將不同的海量資料收集,移動並存儲到一個數據儲存系統中。輕量,配置簡單,適用於各種日誌收集,並支援 Failover和負載均衡。並且它擁有非常豐富的元件。Flume NG採用的是三層架構:Agent層,Collecto

python中呼叫R語言通過rpy2 進行互動安裝配置R_USER、R_HOME配置

python中呼叫R語言通過rpy2 進行詳解 文章目錄 python中呼叫R語言通過rpy2 進行詳解 1.R語言的安裝: 2.Rpy2工具的安裝和配置 3.pycharm中使用R語言配置

非對稱加密過程基於RSA非對稱加密演算法實現

1、非對稱加密過程:         假如現實世界中存在A和B進行通訊,為了實現在非安全的通訊通道上實現資訊的保密性、完整性、可用性(即資訊保安的三個性質),A和B約定使用非對稱加密通道進行通訊,具體過程如下:   說明:         國內目前使用雙證書體系,即

Linux系統安裝VMware虛擬機器安裝ubuntu為例

目錄 前言:因為我們的系統大多數用的是Window系統,理論上可以存在雙系統的,可以把Linux系統也安裝在我們的驅動盤上,但是為了簡化這些安裝步驟,我們可以直接在我們的Window系統搭載一個虛擬機器,然後再虛擬機器裡安裝Linux系統。 1.安裝虛擬機器V

Rabbitmq基於go語言

參考文件 RMQ的安裝和埠 手動安裝太麻煩,請自行百度。這裡只給出一種基於docker安裝的簡單形式。 docker run -d --hostname my-rabbit --name rmq -p 15672:15672 -p 5

差分約束系統轉化為最短路

差分約束系統中: 如果求未知數的最大值,那麼按小於等於建圖後求最短路即可。(因為求最短路是由無窮向下約束而得到的,所以得到的一定是最大值)。 如果求未知數的最小值,那麼按小於等於建圖後求最長路即可。 注意所有資料的關係,不能漏掉關係,還有與附加源點的關係。

中科院中文分詞在java中呼叫ICTCLAS2013版

在中文分詞的時候,現在流行的有很多,下面主要介紹中科院中文分詞,現在中科院地址是http://ictclas.nlpir.org/ 首先也是開始呼叫這個介面,呼叫成功後覺得應該共享出來,讓更多人去使用。 然後主要是介紹一下怎麼用使用漢語分詞系統怎麼去呼叫。必須先在上面那個網

EasyPR--中文開源車牌識別系統 開發1

  在上篇文件中作者已經簡單的介紹了EasyPR,現在在本文件中詳細的介紹EasyPR的開發過程。   正如淘寶誕生於一個購買來的LAMP系統,EasyPR也有它誕生的原型,起源於CSDN的taotao1233的一個部落格,博主以讀書筆記的形式記述了通過閱讀“Mastering OpenCV”這本書完成的一

Vue.js 執行環境搭建基於windows的手把手安裝教學及vue、node基礎知識普及

Vue.js 是一套構建使用者介面的漸進式框架。他自身不是一個全能框架——只聚焦於檢視層。因此它非常容易學習,非常容易與其它庫或已有專案整合。在與相關工具和支援庫一起使用時,Vue.js 也能完美地驅動複雜的單頁應用。   在配置環境之前呢,有些基礎的東西還是要和大家普及一

Linux下基於vsftpd的FTP檔案傳輸協議附完整流程命令

理論概述 基本概念 FTP 是File Transfer Protocol(檔案傳輸協議)的英文簡稱,而中文簡稱為“文傳協議”。用於Internet上的控制檔案的雙向傳輸。同時,它也是一個應用程式(Application)。基於不同的作業系統有不同的FTP應