新聞中心

        EEPW首頁 > 嵌入式系統 > 設計應用 > ARM Linux系統調用的原理

        ARM Linux系統調用的原理

        作者: 時間:2016-11-09 來源:網絡 收藏
        操作系統為在用戶態運行的進程與硬件設備進行交互提供了一組接口。在應用程序和硬件之間設置一個額外層具有很多優點。首先,這使得編程更加容易,把 用戶從學習硬件設備的低級編程特性中解放出來。其次,這極大地提高了系統的安全性,因為內核在試圖滿足某個請求之前在接口級就可以檢查這種請求的正確性。 最后,更重要的是這些接口使得程序具有可移植性,因為只要內核所提供的一組接口相同,那么在任一內核之上就可以正確地編譯和執行程序。

        本文引用地址:http://www.104case.com/article/201611/318016.htm

        Unix系統通過向內核發出系統調用(systemcall)實現了用戶態進程和硬件設備之間的大部分接口。系統調用是操作系統提供的服務,用戶程序通過各種系統調用,來引用內核提供的各種服務,系統調用的執行讓用戶程序陷入內核,該陷入動作由swi軟中斷完成。

        應用編程接口(API)與系統調用的不同在于,前者只是一個函數定義,說明了如何獲得一個給定的服務,而后者是通過軟件中斷向內核發出的一個明確的 請求。POSIX標準針對API,而不針對系統調用。Unix系統給程序員提供了很多API庫函數。libc的標準c庫所定義的一些API引用了封裝例程 (wrapper routine)(其唯一目的就是發布系統調用)。通常情況下,每個系統調用對應一個封裝例程,而封裝例程定義了應用程序使用的API。反之則不然,一個 API沒必要對應一個特定的系統調用。從編程者的觀點看,API和系統調用之間的差別是沒有關系的:唯一相關的事情就是函數名、參數類型及返回代碼的含 義。然而,從內核設計者的觀點看,這種差別確實有關系,因為系統調用屬于內核,而用戶態的庫函數不屬于內核。

        大部分封裝例程返回一個整數,其值的含義依賴于相應的系統調用。返回-1通常表示內核不能滿足進程的請求。系統調用處理程序的失敗可能是由無效參數 引起的,也可能是因為缺乏可用資源,或硬件出了問題等等。在libc庫中定義的errno變量包含特定的出錯碼,每個出錯碼定義為一個常量宏。

        當用戶態的進程調用一個系統調用時,CPU切換到內核態并開始執行一個內核函數。因為內核實現了很多不同的系統調用,因此進程必須傳遞一個名為系統 調用號(system call number)的參數來識別所需的系統調用。所有的系統調用核都返回一個整數值。這些返回值與封裝例程返回值的約定是不同的。在內中,整數或0表示系統調 用成功結束,而負數表示一個出錯條件。在后一種情況下,這個值就是存放在errno變量中必須返回給應用程序的負出錯碼。

        ARM Linux系統利用SWI指令來從用戶空間進入內核空間,還是先讓我們了解下這個SWI指令吧。SWI指令用于產生軟件中斷,從而實現從用戶模式到管理模 式的變換,CPSR保存到管理模式的SPSR,執行轉移到SWI向量。在其他模式下也可使用SWI指令,處理器同樣地切換到管理模式。指令格式如下:

        SWI{cond} immed_24

        其中:

        immed_24 24位立即數,值為從0——16215之間的整數。

        使用SWI指令時,通常使用以下兩種方法進行參數傳遞,SWI異常處理程序可以提供相關的服務,這兩種方法均是用戶軟件協定。

        1)、指令中24位的立即數指定了用戶請求的服務類型,參數通過通用寄存器傳遞。SWI異常處理程序要通過讀取引起軟件中斷的SWI指令,以取得24為立即數。如:

        MOV R0,#34

        SWI 12

        2)、指令中的24位立即數被忽略,用戶請求的服務類型由寄存器R0的值決定,參數通過其他的通用寄存器傳遞。如:

        MOV R0, #12

        MOV R1, #34

        SWI 0

        在SWI異常處理程序中,取出SWI立即數的步驟為:首先確定引起軟件中斷的SWI指令是ARM指令還是Thumb指令,這可通過對SPSR訪問得到;然后取得該SWI指令的地址,這可通過訪問LR寄存器得到;接著讀出指令,分解出立即數(低24位)。

        由用戶空間進入系統調用

        通常情況下,我們寫的用戶空間應用程序都是通過封裝的C lib來調用系統調用的。以0.9.30版uClibc中的open為例,來追蹤一下這個封裝的函數是如何一步一步的調用系統調用的。在include/fcntl.h中有定義:

        #define open open64

        open實際上只是open64的一個別名而已。

        在libc/sysdeps/linux/common/open64.c中可以看到:

        extern __typeof(open64) __libc_open64;

        extern __typeof(open) __libc_open;

        可見open64也只不過是__libc_open64的別名,而__libc_open64函數在同一個文件中定義:

        libc_hidden_proto(__libc_open64)

        int __libc_open64 (const char *file,int oflag, ...)

        {

        mode_t mode = 0;

        if (oflag & O_CREAT)

        {

        va_listarg;

        va_start(arg, oflag);

        mode= va_arg (arg, mode_t);

        va_end(arg);

        }

        return __libc_open(file, oflag O_LARGEFILE, mode);

        }

        libc_hidden_def(__libc_open64)

        最終__libc_open64又調用了__libc_open函數,這個函數在文件libc/sysdeps/linux/common/open.c中定義:

        libc_hidden_proto(__libc_open)

        int __libc_open(const char *file, intoflag, ...)

        {

        mode_tmode = 0;

        if(oflag & O_CREAT) {

        va_listarg;

        va_start(arg, oflag);

        mode= va_arg (arg, mode_t);

        va_end (arg);

        }

        return__syscall_open(file, oflag, mode);

        }

        libc_hidden_def(__libc_open)

        這個函數,也是僅僅根據打開標志oflag的值,來判斷是否有第三個參數,若由,則獲得其值。之后,便用獲得的參數來調用__syscall_open(file,oflag, mode)。

        __syscall_open在同一個文件中定義:

        static __inline__ _syscall3(int,__syscall_open, const char *, file,

        int,flags, __kernel_mode_t, mode)

        在文件libc/sysdeps/linux/arm/bits/syscalls.h文件中可以看到:

        #undef _syscall3

        #define_syscall3(type,name,type1,arg1,type2,arg2,type3,arg3)

        type name(type1 arg1,type2 arg2,type3arg3)

        {

        return (type) (INLINE_SYSCALL(name, 3,arg1, arg2, arg3));

        }

        這個宏實際上完成定義一個函數的工作,宏的第一個參數是函數的返回值類型,第二個參數是函數名,之后的參數就如同它們的參數名所表明的那樣,分別是函數的參數類型及參數名。__syscall_open實際上為:

        int __syscall_open (const char * file,intflags, __kernel_mode_t mode)

        {

        return (int) (INLINE_SYSCALL(__syscall_open,3, file, flags, mode));

        }

        INLINE_SYSCALL為同一個文件中定義的宏:

        #undef INLINE_SYSCALL

        #define INLINE_SYSCALL(name, nr,args...)

        ({ unsigned int _inline_sys_result = INTERNAL_SYSCALL (name, , nr,args);

        if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_inline_sys_result, ),0))

        {

        __set_errno (INTERNAL_SYSCALL_ERRNO(_inline_sys_result, ));

        _inline_sys_result = (unsigned int) -1;

        }

        (int) _inline_sys_result; })

        INLINE_SYSCALL宏中最值得注意的是INTERNAL_SYSCALL,其定義如下:

        #undef INTERNAL_SYSCALL

        #if !defined(__thumb__)

        #if defined(__ARM_EABI__)

        #define INTERNAL_SYSCALL(name, err, nr,args...)

        ({unsigned int __sys_result;

        {

        register int _a1 __asm__ ("r0"), _nr __asm__ ("r7");

        LOAD_ARGS_##nr (args)

        _nr = SYS_ify(name);

        __asm__ __volatile__ ("swi 0x0 @ syscall " #name

        : "=r" (_a1)

        : "r" (_nr) ASM_ARGS_##nr

        : "memory");

        __sys_result = _a1;

        }

        (int) __sys_result; })

        #else /* defined(__ARM_EABI__) */

        #define INTERNAL_SYSCALL(name, err, nr,args...)

        ({ unsigned int __sys_result;

        {

        register int _a1 __asm__ ("a1");

        LOAD_ARGS_##nr (args)

        __asm__ __volatile__ ("swi %1 @ syscall " #name

        : "=r" (_a1)

        : "i" (SYS_ify(name))ASM_ARGS_##nr

        : "memory");

        __sys_result = _a1;

        }

        (int) __sys_result; })

        #endif

        這里也將同文件中的LOAD_ARGS宏的定義貼出來:

        #define LOAD_ARGS_0()

        #define ASM_ARGS_0

        #define LOAD_ARGS_1(a1)

        _a1 = (int) (a1);

        LOAD_ARGS_0 ()

        #define ASM_ARGS_1 ASM_ARGS_0, "r" (_a1)

        #define LOAD_ARGS_2(a1, a2)

        register int _a2 __asm__ ("a2") = (int) (a2);

        LOAD_ARGS_1 (a1)

        #define ASM_ARGS_2 ASM_ARGS_1, "r" (_a2)

        #define LOAD_ARGS_3(a1, a2, a3)

        register int _a3 __asm__ ("a3") = (int) (a3);

        LOAD_ARGS_2 (a1, a2)

        這幾個宏用來在寄存器中加載相應的參數,參數傳遞的方式和普通的C函數也沒有什么太大的區別,同樣都是將參數列表中的參數依次放入寄存器r0、r1、r2、r3…中。

        上面的SYS_ify(name)宏,是用來獲得系統調用號的。

        #define SYS_ify(syscall_name) (__NR_##syscall_name)

        也就是__NR___syscall_open,在libc/sysdeps/linux/common/open.c中可以看到這個宏的定義:

        #define __NR___syscall_open __NR_open

        __NR_open在內核代碼的頭文件中有定義。

        在這里我們忽略定義__thumb__的情況,而假設我們編譯出來的庫函數使用的都是ARM指令集。在上面的代碼中,我們看到,根據是否定義宏__ARM_EABI__,INTERNAL_SYSCALL會被展開為兩種不同的版本。關于這一點,與應用二進制接口ABI有關,不同的ABI,則會有不同的傳遞系統調用號的方法。對于比較新的EABI,則在r7寄存器保存系統調用號,通過swi 0x0來陷入內核。否則,通過swi指令的24位立即數參數來傳遞系統調用號。后面還會有內核中關于這個問題的更詳細的說明。

        同時這兩種調用方式的系統調用號也是存在這區別的,在內核的文件arch/arm/inclue/asm/unistd.h中可以看到:

        #define __NR_OABI_SYSCALL_BASE 0x900

        #if defined(__thumb__) defined(__ARM_EABI__)

        #define __NR_SYSCALL_BASE 0

        #else

        #define __NR_SYSCALL_BASE __NR_OABI_SYSCALL_BASE

        #endif

        /*

        * This file contains the system call numbers.

        */

        #define __NR_restart_syscall (__NR_SYSCALL_BASE+ 0)

        #define __NR_exit (__NR_SYSCALL_BASE+ 1)

        #define __NR_fork (__NR_SYSCALL_BASE+ 2)

        #define __NR_read (__NR_SYSCALL_BASE+ 3)

        #define __NR_write (__NR_SYSCALL_BASE+ 4)

        #define __NR_open (__NR_SYSCALL_BASE+ 5)

        ……

        接下來來看操作系統對系統調用的處理。我們回到ARMLinux的異常向量表,因為當執行swi時,會從異常向量表中取例程的地址從而跳轉到相應的處理程序中。在文件arch/arm/kernel/entry-armv.S中我們看到SWI異常向量:

        W(ldr) pc,.LCvswi + stubs_offset

        而.LCvswi在同一個文件中定義為:

        .LCvswi:

        .word vector_swi

        也就是最終會執行例程vector_swi來完成對系統調用的處理,接下來我們來看下在arch/arm/kernel/entry-common.S中定義的vector_swi例程(刪去一些和我們的示例平臺無關的代碼):

        .align 5

        ENTRY(vector_swi)

        sub sp, sp, #S_FRAME_SIZE

        stmia sp, {r0 - r12} @Calling r0 - r12

        ARM( add r8, sp, #S_PC )

        ARM( stmdb r8, {sp, lr}^ ) @ Calling sp, lr

        mrs r8, spsr @called from non-FIQ mode, so ok.

        str lr, [sp, #S_PC] @ Save calling PC

        str r8, [sp, #S_PSR] @ Save CPSR

        str r0, [sp, #S_OLD_R0] @ Save OLD_R0

        zero_fp

        /*Get the system call number. */

        #if defined(CONFIG_OABI_COMPAT)

        /*

        * If we have CONFIG_OABI_COMPAT then we needto look at the swi

        * value to determine if it is an EABI or anold ABI call.

        */

        ldr r10, [lr, #-4] @ get SWI instruction

        #ifdef CONFIG_CPU_ENDIAN_BE8

        //rev指令的功能是反轉字中的字節序

        rev r10, r10 @little endian instruction

        #endif

        #elif defined(CONFIG_AEABI)

        #else

        /*Legacy ABI only. */

        ldr scno, [lr, #-4] @ get SWI instruction

        #endif

        #ifdef CONFIG_ALIGNMENT_TRAP

        ldr ip, __cr_alignment

        ldr ip, [ip]

        mcr p15, 0, ip, c1, c0 @ update control register

        #endif

        enable_irq

        // tsk 是寄存器r9的別名,在arch/arm/kernel/entry-header.S中定義:// tsk .req r9 @current thread_info

        // 獲得線程對象的基地址。

        get_thread_infotsk

        // tbl是r8寄存器的別名,在arch/arm/kernel/entry-header.S中定義:

        // tbl .req r8 @syscall table pointer,

        // 用來存放系統調用表的指針,系統調用表在后面調用

        adr tbl, sys_call_table @ load syscall table pointer

        ldr ip, [tsk, #TI_FLAGS] @ check for syscall tracing

        #if defined(CONFIG_OABI_COMPAT)

        /*

        * If the swi argument is zero, this is an EABIcall and we do nothing.

        *

        * If this is an old ABI call, get the syscallnumber into scno and

        * get the old ABI syscall table address.

        */

        bics r10, r10, #0xff

        eorne scno, r10, #__NR_OABI_SYSCALL_BASE

        ldrne tbl, =sys_oabi_call_table

        #elif !defined(CONFIG_AEABI)

        // scno是寄存器r7的別名

        bic scno, scno, #0xff @ mask off SWI op-code

        eor scno, scno, #__NR_SYSCALL_BASE @ check OS number

        #endif

        stmdb sp!, {r4, r5} @push fifth and sixth args

        tst ip, #_TIF_SYSCALL_TRACE @ are we tracing syscalls?

        bne __sys_trace

        cmp scno, #NR_syscalls @ check upper syscall limit

        adr lr, BSYM(ret_fast_syscall) @ return address

        ldrcc pc, [tbl, scno, lsl #2] @ call sys_* routine

        add r1, sp, #S_OFF

        // why也是r8寄存器的別名

        2: mov why, #0 @no longer a real syscall

        cmp scno, #(__ARM_NR_BASE - __NR_SYSCALL_BASE)

        eor r0, scno, #__NR_SYSCALL_BASE @ put OS number back

        bcs arm_syscall

        b sys_ni_syscall @not private func

        ENDPROC(vector_swi)

        上面的zero_fp是一個宏,在arch/arm/kernel/entry-header.S中定義:

        .macro zero_fp

        #ifdef CONFIG_FRAME_POINTER

        mov fp, #0

        #endif

        .endm

        而fp位寄存器r11。

        像每一個異常處理程序一樣,要做的第一件事當然就是保護現場了。緊接著是獲得系統調用的系統調用號。然后以系統調用號作為索引來查找系統調用表,如果系統調用號正常的話,就會調用相應的處理例程來處理,就是上面的那個ldrcc pc, [tbl, scno, lsl #2]語句,然后通過例程ret_fast_syscall來返回。

        在這個地方我們接著來討論ABI的問題。現在,我們首先來看兩個宏,一個是CONFIG_OABI_COMPAT 意思是說與old ABI兼容,另一個是CONFIG_AEABI 意思是說指定現在的方式為EABI。這兩個宏可以同時配置,也可以都不配,也可以配置任何一種。我們來看一下內核是怎么處理這一問題的。我們知 道,sys_call_table 在內核中是個跳轉表,這個表中存儲的是一系列的函數指針,這些指針就是系統調用函數的指針,如(sys_open)。內核是根據一個系統調用號(對于 EABI來說為系統調用表的索引)找到實際該調用內核哪個函數,然后通過運行該函數完成系統調用的。

        首先,對于old ABI,內核給出的處理是為它建立一個單獨的system call table,叫sys_oabi_call_table。這樣,兼容方式下就會有兩個system call table, 以old ABI方式的系統調用會執行old_syscall_table表中的系統調用函數,EABI方式的系統調用會用sys_call_table中的函數指 針。
        配置無外乎以下4中:
        第一、兩個宏都配置行為就是上面說的那樣。
        第二、只配置CONFIG_OABI_COMPAT,那么以oldABI方式調用的會用sys_oabi_call_table,以EABI方式調用的用sys_call_table,和1實質上是相同的。只是情況1更加明確。
        第三、只配置CONFIG_AEABI系統中不存在sys_oabi_call_table,對old ABI方式調用不兼容。只能 以EABI方式調用,用sys_call_table。

        第四、兩個都沒有配置,系統默認會只允許old ABI方式,但是不存在old_syscall_table,最終會通過sys_call_table 完成函數調用

        系統會根據ABI的不同而將相應的系統調用表的基地址加載進tbl寄存器,也就是r8寄存器。接下來來看系統調用表,如前面所說的那樣,有兩個,同樣都在文件arch/arm/kernel/entry-armv.S中:

        #define ABI(native, compat) native

        #ifdef CONFIG_AEABI

        #define OBSOLETE(syscall)sys_ni_syscall

        #else

        #define OBSOLETE(syscall) syscall

        #endif

        .type sys_call_table, #object

        ENTRY(sys_call_table)

        #include "calls.S"

        #undef ABI

        #undef OBSOLETE

        另外一個為:

        #define ABI(native, compat) compat

        #define OBSOLETE(syscall) syscall

        .type sys_oabi_call_table, #object

        ENTRY(sys_oabi_call_table)

        #include "calls.S"

        #undef ABI

        #undef OBSOLETE

        這樣看來貌似兩個系統調用表是完全一樣的。這里預處理指令include的獨特用法也挺有意思,系統調用表的內容就是整個arch/arm/kernel/calls.S文件的內容(由于太長,這里就不全部列出了):

        /* 0 */ CALL(sys_restart_syscall)

        CALL(sys_exit)

        CALL(sys_fork_wrapper)

        CALL(sys_read)

        CALL(sys_write)

        /* 5 */ CALL(sys_open)

        CALL(sys_close)

        ……

        上面的CALL()是個宏,它同樣在文件arch/arm/kernel/entry-armv.S中定義:

        #define CALL(x) .equNR_syscalls,NR_syscalls+1

        #include "calls.S"

        #undef CALL

        #define CALL(x) .long x

        在定義宏CALL()的地方,我們看到calls.S已經被包含了一次,只不過在這里,不是為了建立系統調用表,而僅僅是為了獲得系統的系統調用的數量,并保存在宏NR_syscalls中。在SWI向量中,我們也看到,是使用了這個宏的。

        最后再羅嗦一點,如果用sys_open來搜的話,是搜不到系統調用open的定義的,系統調用函數都是用宏來定義的,比如對于open,有這樣的定義:

        fs/open.c

        1066 SYSCALL_DEFINE3(open, const char__user *, filename, int, flags, int, mode)

        1067 {

        1068 long ret;

        1069

        1070 if (force_o_largefile())

        1071 flags = O_LARGEFILE;

        1072

        1073 ret = do_sys_open(AT_FDCWD, filename,flags, mode);

        1074 /* avoid REGPARM breakage on x86: */

        1075 asmlinkage_protect(3, ret, filename,flags, mode);

        1076 return ret;

        1077 }

        繼續回到vector_swi,如果系統調用號不正確,則會調用arm_syscall函數來進行處理,這個函數定義如下:

        arch/arm/kernel/traps.c

        465 #define NR(x) ((__ARM_NR_##x) -__ARM_NR_BASE)

        466 asmlinkage int arm_syscall(int no,struct pt_regs *regs)

        467 {

        468struct thread_info *thread = current_thread_info();

        469siginfo_t info;

        470

        471if ((no >> 16) != (__ARM_NR_BASE>> 16))

        472 return bad_syscall(no, regs);

        473

        474switch (no & 0xffff) {

        475case 0: /* branch through 0 */

        476 info.si_signo = SIGSEGV;

        477 info.si_errno = 0;

        478 info.si_code = SEGV_MAPERR;

        479 info.si_addr = NULL;

        480

        481 arm_notify_die("branch throughzero", regs, &info, 0, 0);

        482 return 0;

        483

        484case NR(breakpoint): /* SWI BREAK_POINT */

        485 regs->ARM_pc -= thumb_mode(regs) ? 2: 4;

        486 ptrace_break(current, regs);

        487 return regs->ARM_r0;

        488

        489/*

        490* Flush a region from virtual address r0 to virtual address r1

        491 * _exclusive_. There is no alignment requirement on eitheraddress;

        492* user space does not need to know the hardware cache layout.

        493*

        494* r2 contains flags. It shouldALWAYS be passed as ZERO until it

        495* is defined to be something else.For now we ignore it, but may

        496* the fires of hell burn in your belly if you break this rule. ;)

        497*

        498* (at a later date, we may want to allow this call to not flush

        499* various aspects of the cache.Passing will guarantee that

        500* everything necessary gets flushed to maintain consistency in

        501* the specified region).

        502*/

        503case NR(cacheflush):

        504 do_cache_op(regs->ARM_r0,regs->ARM_r1, regs->ARM_r2);

        505 return 0;

        506

        507case NR(usr26):

        508 if (!(elf_hwcap & HWCAP_26BIT))

        509 break;

        510 regs->ARM_cpsr &= ~MODE32_BIT;

        511 return regs->ARM_r0;

        512

        513case NR(usr32):

        514 if (!(elf_hwcap & HWCAP_26BIT))

        515 break;

        516 regs->ARM_cpsr = MODE32_BIT;

        517 return regs->ARM_r0;

        518

        519case NR(set_tls):

        520 thread->tp_value = regs->ARM_r0;

        521 #if defined(CONFIG_HAS_TLS_REG)

        522 asm ("mcr p15, 0, %0, c13, c0,3" : : "r" (regs->ARM_r0) );

        523 #elif !defined(CONFIG_TLS_REG_EMUL)

        524 /*

        525 * User space must never try to accessthis directly.

        526 * Expect your app to break eventuallyif you do so.

        527 * The user helper at 0xffff0fe0 mustbe used instead.

        528 * (see entry-armv.S for details)

        529 */

        530 *((unsigned int *)0xffff0ff0) =regs->ARM_r0;

        531 #endif

        532 return 0;

        533

        534 #ifdef CONFIG_NEEDS_SYSCALL_FOR_CMPXCHG

        535/*

        536* Atomically store r1 in*r2 if *r2 is equal to r0 for user space.

        537* Return zero in r0 if *MEM was changed or non-zero if no exchange

        538* happened. Also set the user Cflag accordingly.

        539* If access permissions have to be fixed up then non-zero is

        540* returned and the operation has to be re-attempted.

        541*

        542* *NOTE*: This is a ghost syscall private to the kernel. Only the

        543* __kuser_cmpxchg code in entry-armv.S should be aware of its

        544* existence. Dont ever use thisfrom user code.

        545*/

        546case NR(cmpxchg):

        547for (;;) {

        548extern void do_DataAbort(unsigned long addr, unsigned int fsr,

        549 struct pt_regs*regs);

        550unsigned long val;

        551unsigned long addr = regs->ARM_r2;

        552struct mm_struct *mm = current->mm;

        553pgd_t *pgd; pmd_t *pmd; pte_t *pte;

        554spinlock_t *ptl;

        556regs->ARM_cpsr &= ~PSR_C_BIT;

        557down_read(&mm->mmap_sem);

        558pgd = pgd_offset(mm, addr);

        559if (!pgd_present(*pgd))

        560goto bad_access;

        561pmd = pmd_offset(pgd, addr);

        562if (!pmd_present(*pmd))

        563 goto bad_access;

        564pte = pte_offset_map_lock(mm, pmd, addr, &ptl);

        565if (!pte_present(*pte) !pte_dirty(*pte)) {

        566 pte_unmap_unlock(pte, ptl);

        567 goto bad_access;

        568}

        569val = *(unsigned long *)addr;

        570val -= regs->ARM_r0;

        571if (val == 0) {

        572 *(unsigned long *)addr =regs->ARM_r1;

        573 regs->ARM_cpsr = PSR_C_BIT;

        574}

        575pte_unmap_unlock(pte, ptl);

        576up_read(&mm->mmap_sem);

        577return val;

        578

        579bad_access:

        580up_read(&mm->mmap_sem);

        581/* simulate a write access fault */

        582do_DataAbort(addr, 15 + (1 << 11), regs);

        583}

        584 #endif

        585

        586default:

        587 /* Calls 9f00xx..9f07ff are defined to return -ENOSYS

        588 if not implemented, rather thanraising SIGILL. This

        589 way the calling program can gracefullydetermine whether

        590 a feature is supported.*/

        591 if ((no & 0xffff) <= 0x7ff)

        592 return -ENOSYS;

        593 break;

        594}

        595 #ifdef CONFIG_DEBUG_USER

        596/*

        597* experience shows that these seem to indicate that

        598* something catastrophic has happened

        599*/

        600if (user_debug & UDBG_SYSCALL) {

        601 printk("[%d] %s: arm syscall%dn",

        602task_pid_nr(current),current->comm, no);

        603 dump_instr("", regs);

        604 if (user_mode(regs)) {

        605 __show_regs(regs);

        606 c_backtrace(regs->ARM_fp, processor_mode(regs));

        607 }

        608}

        609 #endif

        610info.si_signo = SIGILL;

        611info.si_errno = 0;

        612info.si_code = ILL_ILLTRP;

        613info.si_addr = (void __user*)instruction_pointer(regs) -

        614 (thumb_mode(regs) ? 2 : 4);

        615

        616arm_notify_die("Oops - bad syscall(2)", regs, &info, no,0);

        617return 0;

        618 }

        這個函數處理所有的辨別不出來的系統調用。系統調用號正確也好不正確也好,最終都是通過ret_fast_syscall例程來返回,因為我們看到,在進入系統調用處理函數之前,先加載了符號ret_fast_syscall進lr寄存器。ret_fast_syscall定義如下:

        arch/arm/kernel/entry-common.S

        ret_fast_syscall:

        UNWIND(.fnstart )

        UNWIND(.cantunwind )

        disable_irq @ disable interrupts

        ldr r1, [tsk, #TI_FLAGS]

        tst r1, #_TIF_WORK_MASK

        bne fast_work_pending

        /*perform architecture specific actions before user return */

        arch_ret_to_userr1, lr

        restore_user_regsfast = 1, offset = S_OFF

        UNWIND(.fnend )

        fast_work_pending:

        str r0, [sp, #S_R0+S_OFF]! @ returned r0

        work_pending:

        tst r1, #_TIF_NEED_RESCHED

        bne work_resched

        tst r1, #_TIF_SIGPENDING_TIF_NOTIFY_RESUME

        beq no_work_pending

        mov r0, sp @regs

        mov r2, why @syscall

        bl do_notify_resume

        b ret_slow_syscall @ Check work again

        work_resched:

        bl schedule

        /*

        * "slow" syscall return path. "why" tells us if this was a realsyscall.

        */

        ENTRY(ret_to_user)

        ret_slow_syscall:

        disable_irq @ disable interrupts

        ldr r1, [tsk, #TI_FLAGS]

        tst r1, #_TIF_WORK_MASK

        bne work_pending

        no_work_pending:

        /*perform architecture specific actions before user return */

        arch_ret_to_userr1, lr

        restore_user_regsfast = 0, offset = 0

        ENDPROC(ret_to_user)

        對于我們的平臺來說,上面的arch_ret_to_user為空。restore_user_regs宏用于恢復現場并返回,restore_user_regs宏定義如下:

        arch/arm/kernel/entry-header.S

        .macro restore_user_regs, fast = 0, offset = 0

        ldr r1, [sp, #offset + S_PSR] @ get calling cpsr

        ldr lr, [sp, #offset + S_PC]! @ get pc

        msr spsr_cxsf, r1 @save in spsr_svc

        #if defined(CONFIG_CPU_32v6K)

        clrex @ clear the exclusive monitor

        #elif defined (CONFIG_CPU_V6)

        strex r1, r2, [sp] @clear the exclusive monitor

        #endif

        .if fast

        ldmdb sp, {r1 - lr}^ @get calling r1 - lr

        .else

        ldmdb sp, {r0 - lr}^ @get calling r0 - lr

        .endif

        mov r0, r0 @ARMv5T and earlier require a nop

        @after ldm {}^

        add sp, sp, #S_FRAME_SIZE - S_PC

        movs pc, lr @return & move spsr_svc into cpsr

        .endm

        添加新的系統調用

        第一、打開arch/arm/kernel/calls.S,在最后添加系統調用的函數原型的指針,例如:

        CALL(sys_set_senda)

        補充說明一點關于NR_syscalls的東西,這個常量表示系統調用的總的個數,在較新版本的內核中,文件arch/arm/kernel/entry-common.S中可以找到:

        .equ NR_syscalls,0

        #define CALL(x).equ NR_syscalls,NR_syscalls+1

        #include"calls.S"

        #undef CALL

        #define CALL(x).long x

        相當的巧妙,不是嗎?在系統調用表中每添加一個系統調用,NR_syscalls就自動增加一。在這個地方先求出NR_syscalls,然后重新定義CALL(x)宏,這樣也可以不影響文件后面系統調用表的建立。

        第二、打開include/asm-arm/unistd.h,添加系統調用號的宏,感覺這步可以省略,因為這個地方定義的系統調用號主要是個C庫,比如uClibc、Glibc用的。例如:

        #define__NR_plan_set_senda(__NR_SYSCALL_BASE+365)

        為了向后兼容,系統調用只能增加而不能減少,這里的編號添加時,也必須按順序來。否則會導致核心運行錯誤。

        第三,實例化該系統調用,即編寫新添加系統調用的實現例如:

        SYSCALL_DEFINE1(set_senda, int,iset)

        {

        if(iset)

        UART_PUT_CR(&at91_port[2],AT91C_US_SENDA);

        else

        UART_PUT_CR(&at91_port[2],AT91C_US_RSTSTA);

        return 0;

        }

        第四、打開include/linux/syscalls.h添加函數聲明

        asmlinkagelong sys_set_senda(int iset);

        第五、在應用程序中調用該系統調用,可以參考uClibc的實現。

        第六、結束。

        參考文檔:

        [精華] arm Linux 2.6高版本中的系統調用方式

        http://www.unixresources.net/linux/clf/linuxK/archive/00/00/67/92/679297.html

        ARMLinux下添加新的系統調用



        關鍵詞: ARMLinux系統調

        評論


        技術專區

        關閉
        主站蜘蛛池模板: 福建省| 广德县| 定安县| 介休市| 临沭县| 乌拉特前旗| 津市市| 长泰县| 通道| 丽水市| 白河县| 周宁县| 阳东县| 唐山市| 芜湖市| 登封市| 谷城县| 道孚县| 兴隆县| 嫩江县| 土默特右旗| 屏边| 施甸县| 丰都县| 易门县| 南充市| 龙南县| 珲春市| 东辽县| 沽源县| 津市市| 孙吴县| 成都市| 乐清市| 海城市| 买车| 镇平县| 巨鹿县| 邓州市| 比如县| 任丘市|