<meter id="pryje"><nav id="pryje"><delect id="pryje"></delect></nav></meter>
          <label id="pryje"></label>

          新聞中心

          EEPW首頁 > 嵌入式系統(tǒng) > 設(shè)計(jì)應(yīng)用 > ARM Linux系統(tǒng)調(diào)用的原理

          ARM Linux系統(tǒng)調(diào)用的原理

          作者: 時(shí)間:2016-11-09 來源:網(wǎng)絡(luò) 收藏
          操作系統(tǒng)為在用戶態(tài)運(yùn)行的進(jìn)程與硬件設(shè)備進(jìn)行交互提供了一組接口。在應(yīng)用程序和硬件之間設(shè)置一個(gè)額外層具有很多優(yōu)點(diǎn)。首先,這使得編程更加容易,把 用戶從學(xué)習(xí)硬件設(shè)備的低級(jí)編程特性中解放出來。其次,這極大地提高了系統(tǒng)的安全性,因?yàn)閮?nèi)核在試圖滿足某個(gè)請(qǐng)求之前在接口級(jí)就可以檢查這種請(qǐng)求的正確性。 最后,更重要的是這些接口使得程序具有可移植性,因?yàn)橹灰獌?nèi)核所提供的一組接口相同,那么在任一內(nèi)核之上就可以正確地編譯和執(zhí)行程序。

          本文引用地址:http://www.ex-cimer.com/article/201611/318016.htm

          Unix系統(tǒng)通過向內(nèi)核發(fā)出系統(tǒng)調(diào)用(systemcall)實(shí)現(xiàn)了用戶態(tài)進(jìn)程和硬件設(shè)備之間的大部分接口。系統(tǒng)調(diào)用是操作系統(tǒng)提供的服務(wù),用戶程序通過各種系統(tǒng)調(diào)用,來引用內(nèi)核提供的各種服務(wù),系統(tǒng)調(diào)用的執(zhí)行讓用戶程序陷入內(nèi)核,該陷入動(dòng)作由swi軟中斷完成。

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

          大部分封裝例程返回一個(gè)整數(shù),其值的含義依賴于相應(yīng)的系統(tǒng)調(diào)用。返回-1通常表示內(nèi)核不能滿足進(jìn)程的請(qǐng)求。系統(tǒng)調(diào)用處理程序的失敗可能是由無效參數(shù) 引起的,也可能是因?yàn)槿狈捎觅Y源,或硬件出了問題等等。在libc庫中定義的errno變量包含特定的出錯(cuò)碼,每個(gè)出錯(cuò)碼定義為一個(gè)常量宏。

          當(dāng)用戶態(tài)的進(jìn)程調(diào)用一個(gè)系統(tǒng)調(diào)用時(shí),CPU切換到內(nèi)核態(tài)并開始執(zhí)行一個(gè)內(nèi)核函數(shù)。因?yàn)閮?nèi)核實(shí)現(xiàn)了很多不同的系統(tǒng)調(diào)用,因此進(jìn)程必須傳遞一個(gè)名為系統(tǒng) 調(diào)用號(hào)(system call number)的參數(shù)來識(shí)別所需的系統(tǒng)調(diào)用。所有的系統(tǒng)調(diào)用核都返回一個(gè)整數(shù)值。這些返回值與封裝例程返回值的約定是不同的。在內(nèi)中,整數(shù)或0表示系統(tǒng)調(diào) 用成功結(jié)束,而負(fù)數(shù)表示一個(gè)出錯(cuò)條件。在后一種情況下,這個(gè)值就是存放在errno變量中必須返回給應(yīng)用程序的負(fù)出錯(cuò)碼。

          ARM Linux系統(tǒng)利用SWI指令來從用戶空間進(jìn)入內(nèi)核空間,還是先讓我們了解下這個(gè)SWI指令吧。SWI指令用于產(chǎn)生軟件中斷,從而實(shí)現(xiàn)從用戶模式到管理模 式的變換,CPSR保存到管理模式的SPSR,執(zhí)行轉(zhuǎn)移到SWI向量。在其他模式下也可使用SWI指令,處理器同樣地切換到管理模式。指令格式如下:

          SWI{cond} immed_24

          其中:

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

          使用SWI指令時(shí),通常使用以下兩種方法進(jìn)行參數(shù)傳遞,SWI異常處理程序可以提供相關(guān)的服務(wù),這兩種方法均是用戶軟件協(xié)定。

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

          MOV R0,#34

          SWI 12

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

          MOV R0, #12

          MOV R1, #34

          SWI 0

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

          由用戶空間進(jìn)入系統(tǒng)調(diào)用

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

          #define open open64

          open實(shí)際上只是open64的一個(gè)別名而已。

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

          extern __typeof(open64) __libc_open64;

          extern __typeof(open) __libc_open;

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

          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又調(diào)用了__libc_open函數(shù),這個(gè)函數(shù)在文件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)

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

          __syscall_open在同一個(gè)文件中定義:

          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));

          }

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

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

          {

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

          }

          INLINE_SYSCALL為同一個(gè)文件中定義的宏:

          #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)

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

          上面的SYS_ify(name)宏,是用來獲得系統(tǒng)調(diào)用號(hào)的。

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

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

          #define __NR___syscall_open __NR_open

          __NR_open在內(nèi)核代碼的頭文件中有定義。

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

          同時(shí)這兩種調(diào)用方式的系統(tǒng)調(diào)用號(hào)也是存在這區(qū)別的,在內(nèi)核的文件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)

          ……

          接下來來看操作系統(tǒng)對(duì)系統(tǒng)調(diào)用的處理。我們回到ARMLinux的異常向量表,因?yàn)楫?dāng)執(zhí)行swi時(shí),會(huì)從異常向量表中取例程的地址從而跳轉(zhuǎn)到相應(yīng)的處理程序中。在文件arch/arm/kernel/entry-armv.S中我們看到SWI異常向量:

          W(ldr) pc,.LCvswi + stubs_offset

          而.LCvswi在同一個(gè)文件中定義為:

          .LCvswi:

          .word vector_swi

          也就是最終會(huì)執(zhí)行例程vector_swi來完成對(duì)系統(tǒng)調(diào)用的處理,接下來我們來看下在arch/arm/kernel/entry-common.S中定義的vector_swi例程(刪去一些和我們的示例平臺(tái)無關(guān)的代碼):

          .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指令的功能是反轉(zhuǎn)字中的字節(jié)序

          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

          // 獲得線程對(duì)象的基地址。

          get_thread_infotsk

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

          // tbl .req r8 @syscall table pointer,

          // 用來存放系統(tǒng)調(diào)用表的指針,系統(tǒng)調(diào)用表在后面調(diào)用

          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是一個(gè)宏,在arch/arm/kernel/entry-header.S中定義:

          .macro zero_fp

          #ifdef CONFIG_FRAME_POINTER

          mov fp, #0

          #endif

          .endm

          而fp位寄存器r11。

          像每一個(gè)異常處理程序一樣,要做的第一件事當(dāng)然就是保護(hù)現(xiàn)場(chǎng)了。緊接著是獲得系統(tǒng)調(diào)用的系統(tǒng)調(diào)用號(hào)。然后以系統(tǒng)調(diào)用號(hào)作為索引來查找系統(tǒng)調(diào)用表,如果系統(tǒng)調(diào)用號(hào)正常的話,就會(huì)調(diào)用相應(yīng)的處理例程來處理,就是上面的那個(gè)ldrcc pc, [tbl, scno, lsl #2]語句,然后通過例程ret_fast_syscall來返回。

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

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

          第四、兩個(gè)都沒有配置,系統(tǒng)默認(rèn)會(huì)只允許old ABI方式,但是不存在old_syscall_table,最終會(huì)通過sys_call_table 完成函數(shù)調(diào)用

          系統(tǒng)會(huì)根據(jù)ABI的不同而將相應(yīng)的系統(tǒng)調(diào)用表的基地址加載進(jìn)tbl寄存器,也就是r8寄存器。接下來來看系統(tǒng)調(diào)用表,如前面所說的那樣,有兩個(gè),同樣都在文件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

          另外一個(gè)為:

          #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

          這樣看來貌似兩個(gè)系統(tǒng)調(diào)用表是完全一樣的。這里預(yù)處理指令include的獨(dú)特用法也挺有意思,系統(tǒng)調(diào)用表的內(nèi)容就是整個(gè)arch/arm/kernel/calls.S文件的內(nèi)容(由于太長(zhǎng),這里就不全部列出了):

          /* 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()是個(gè)宏,它同樣在文件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已經(jīng)被包含了一次,只不過在這里,不是為了建立系統(tǒng)調(diào)用表,而僅僅是為了獲得系統(tǒng)的系統(tǒng)調(diào)用的數(shù)量,并保存在宏NR_syscalls中。在SWI向量中,我們也看到,是使用了這個(gè)宏的。

          最后再羅嗦一點(diǎn),如果用sys_open來搜的話,是搜不到系統(tǒng)調(diào)用open的定義的,系統(tǒng)調(diào)用函數(shù)都是用宏來定義的,比如對(duì)于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 }

          繼續(xù)回到vector_swi,如果系統(tǒng)調(diào)用號(hào)不正確,則會(huì)調(diào)用arm_syscall函數(shù)來進(jìn)行處理,這個(gè)函數(shù)定義如下:

          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 }

          這個(gè)函數(shù)處理所有的辨別不出來的系統(tǒng)調(diào)用。系統(tǒng)調(diào)用號(hào)正確也好不正確也好,最終都是通過ret_fast_syscall例程來返回,因?yàn)槲覀兛吹?,在進(jìn)入系統(tǒng)調(diào)用處理函數(shù)之前,先加載了符號(hào)ret_fast_syscall進(jìn)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)

          對(duì)于我們的平臺(tái)來說,上面的arch_ret_to_user為空。restore_user_regs宏用于恢復(fù)現(xiàn)場(chǎng)并返回,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

          添加新的系統(tǒng)調(diào)用

          第一、打開arch/arm/kernel/calls.S,在最后添加系統(tǒng)調(diào)用的函數(shù)原型的指針,例如:

          CALL(sys_set_senda)

          補(bǔ)充說明一點(diǎn)關(guān)于NR_syscalls的東西,這個(gè)常量表示系統(tǒng)調(diào)用的總的個(gè)數(shù),在較新版本的內(nèi)核中,文件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

          相當(dāng)?shù)那擅睿皇菃??在系統(tǒng)調(diào)用表中每添加一個(gè)系統(tǒng)調(diào)用,NR_syscalls就自動(dòng)增加一。在這個(gè)地方先求出NR_syscalls,然后重新定義CALL(x)宏,這樣也可以不影響文件后面系統(tǒng)調(diào)用表的建立。

          第二、打開include/asm-arm/unistd.h,添加系統(tǒng)調(diào)用號(hào)的宏,感覺這步可以省略,因?yàn)檫@個(gè)地方定義的系統(tǒng)調(diào)用號(hào)主要是個(gè)C庫,比如uClibc、Glibc用的。例如:

          #define__NR_plan_set_senda(__NR_SYSCALL_BASE+365)

          為了向后兼容,系統(tǒng)調(diào)用只能增加而不能減少,這里的編號(hào)添加時(shí),也必須按順序來。否則會(huì)導(dǎo)致核心運(yùn)行錯(cuò)誤。

          第三,實(shí)例化該系統(tǒng)調(diào)用,即編寫新添加系統(tǒng)調(diào)用的實(shí)現(xiàn)例如:

          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添加函數(shù)聲明

          asmlinkagelong sys_set_senda(int iset);

          第五、在應(yīng)用程序中調(diào)用該系統(tǒng)調(diào)用,可以參考uClibc的實(shí)現(xiàn)。

          第六、結(jié)束。

          參考文檔:

          [精華] arm Linux 2.6高版本中的系統(tǒng)調(diào)用方式

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

          ARMLinux下添加新的系統(tǒng)調(diào)用



          關(guān)鍵詞: ARMLinux系統(tǒng)調(diào)

          評(píng)論


          技術(shù)專區(qū)

          關(guān)閉
          看屁屁www成人影院,亚洲人妻成人图片,亚洲精品成人午夜在线,日韩在线 欧美成人 (function(){ var bp = document.createElement('script'); var curProtocol = window.location.protocol.split(':')[0]; if (curProtocol === 'https') { bp.src = 'https://zz.bdstatic.com/linksubmit/push.js'; } else { bp.src = 'http://push.zhanzhang.baidu.com/push.js'; } var s = document.getElementsByTagName("script")[0]; s.parentNode.insertBefore(bp, s); })();