系统调用的实现(与errno的设置)
之前分析errno的實(shí)現(xiàn)時有講過系統(tǒng)調(diào)用的實(shí)現(xiàn), 但是寫到一半爛尾了, 于是決定重新挖個坑(- -!).
?
假設(shè)我們調(diào)用了一個open(), 從pc指向open()入口到pc執(zhí)行open()的后一條指令中間究竟發(fā)生了什么. 首先明確第一點(diǎn), 當(dāng)我們調(diào)用open()時并不是直接調(diào)用系統(tǒng)調(diào)用open, 而是調(diào)用glibc的封裝函數(shù)open(). 讓我們從頭開始一步一步分析.
讓我們來看下open()的聲明, include/fcntl.h中并未聲明該函數(shù), 但它包含了io/fcntl.h, 而后者聲明了該函數(shù).
?
手邊只有官網(wǎng)下的glibc-2.25的源碼, 沒有海思的源碼, 好在可以反匯編海思庫, 從反匯編結(jié)果來看應(yīng)該是定義了__USE_FILE_OFFSET64且定義了__REDIRECT, 走類似__libc_open64()(defined in sysdeps/unix/sysv/linux/open64.c)的接口(可能不是這個接口, 大致差不多).
1 int __libc_open64(const char *file, int oflag, ...) 2 { 3 ??? int mode = 0; 4 ??? if (__OPEN_NEEDS_MODE (oflag)) 5 ??? { 6 ??????? va_list arg; 7 ??????? va_start (arg, oflag); 8 ??????? mode = va_arg (arg, int); 9 ??????? va_end (arg); 10 ??? } 11 ??? return SYSCALL_CANCEL(open, file, oflag | O_LARGEFILE, mode); 12 }?
來看下SYSCALL_CANCEL()(defined in sysdeps/unix/sysdep.h)的實(shí)現(xiàn).
1 #define __SYSCALL_CONCAT_X(a, b) a##b 2 #define __SYSCALL_CONCAT(a, b) __SYSCALL_CONCAT_X(a, b) 3 #define __INLINE_SYSCALL_NARGS_X(a, b, c, d, e, f, g, h, n, ...) n 4 #define __INLINE_SYSCALL_NARGS(...) \ 5 ??? __INLINE_SYSCALL_NARGS_X (__VA_ARGS__, 7, 6, 5, 4, 3, 2, 1, 0, ) 6 #define __INLINE_SYSCALL_DISP(b, ...) \ 7 ??? __SYSCALL_CONCAT(b, __INLINE_SYSCALL_NARGS(__VA_ARGS__))(__VA_ARGS__) 8 #define INLINE_SYSCALL_CALL(...) \ 9 ??? __INLINE_SYSCALL_DISP(__INLINE_SYSCALL, __VA_ARGS__) 10 #define SYSCALL_CANCEL(...) \ 11 ??? ({ \ 12 ??????? long int sc_ret; \ 13 ??????? if (SINGLE_THREAD_P) \ 14 ??????????? sc_ret = INLINE_SYSCALL_CALL(__VA_ARGS__); \ 15 ??????? else \ 16 ??????? { \ 17 ??????????? int sc_cancel_oldtype = LIBC_CANCEL_ASYNC(); \ 18 ??????????? sc_ret = INLINE_SYSCALL_CALL(__VA_ARGS__); \ 19 ??????????? LIBC_CANCEL_RESET(sc_cancel_oldtype); \ 20 ??????? } \ 21 ??????? sc_ret; \ 22 ??? })?
其中SINGLE_THREAD_P()(defined in sysdeps/unix/sysv/linux/arm/sysdep-cancel.h)用于判斷是否單線程程序, 在編譯glibc時定義__ASSEMBLER__則實(shí)現(xiàn)如下:
1 #define SINGLE_THREAD_P \ 2 ??? LDST_PCREL(ldr, ip, ip, __local_multiple_threads); \ 3 ??? teq ip, #0?
LIBC_CANCEL_ASYNC()/LIBC_CANCEL_RESET()實(shí)現(xiàn)沒找到(畢竟不是一份源碼), 看nptl/cancellation.c的實(shí)現(xiàn)應(yīng)該是用于置位/清零異步取消的標(biāo)記. 實(shí)際的系統(tǒng)調(diào)用見INLINE_SYSCALL_CALL()(defined in sysdeps/unix/sysdep.h)的實(shí)現(xiàn), 該宏展開后時__INLINE_SYSCALL*(__VA_ARGS__), 其中*為參數(shù)個數(shù). __INLINE_SYSCALL*同樣是一組宏, 以__INLINE_SYSCALL3()為例.
1 #define __INLINE_SYSCALL3(name, a1, a2, a3) \ 2 ??? INLINE_SYSCALL(name, 3, a1, a2, a3) 3 INLINE_SYSCALL()(defined in sysdeps/unix/sysv/linux/arm/sysdep.h)是基于架構(gòu)實(shí)現(xiàn)的宏, 在不同平臺上有不同實(shí)現(xiàn). 4 #ifndef __ASSEMBLER__ 5 #define LOAD_ARGS_0() 6 #define ASM_ARGS_0 7 #define LOAD_ARGS_1(a1) \ 8 ??? int _a1tmp = (int)(a1); \ 9 ??? LOAD_ARGS_0() \ 10 ??? _a1 = _a1tmp; 11 #define ASM_ARGS_1??? ASM_ARGS_0, "r" (_a1) 12 #define LOAD_ARGS_2(a1, a2) \ 13 ??? int _a2tmp = (int)(a2); \ 14 ??? LOAD_ARGS_1(a1) \ 15 ??? register int _a2 asm ("a2") = _a2tmp; 16 #define ASM_ARGS_2??? ASM_ARGS_1, "r" (_a2) 17 #if defined(__thumb__) 18 #undef INTERNAL_SYSCALL_RAW 19 #define INTERNAL_SYSCALL_RAW(name, err, nr, args...) \ 20 ??? ({ \ 21 ??????? register int _a1 asm ("a1"); \ 22 ??????? int _nametmp = name; \ 23 ??????? LOAD_ARGS_##nr (args) \ 24 ??????? register int _name asm ("ip") = _nametmp; \ 25 ??????? asm volatile ("bl __libc_do_syscall" \ 26 ??????? : "=r" (_a1) \ 27 ??????? : "r" (_name) ASM_ARGS_##nr \ 28 ??????? : "memory", "lr"); \ 29 ??????? _a1; \ 30 ??? }) 31 #else /* ARM */ 32 #undef INTERNAL_SYSCALL_RAW 33 #define INTERNAL_SYSCALL_RAW(name, err, nr, args...) \ 34 ??? ({ \ 35 ??????? register int _a1 asm ("r0"), _nr asm ("r7"); \ 36 ??????? LOAD_ARGS_##nr (args) \ 37 ??????? _nr = name; \ 38 ??????? asm volatile ("swi 0x0 @ syscall " #name \ 39 ??????? : "=r" (_a1) \ 40 ??????? : "r" (_nr) ASM_ARGS_##nr \ 41 ??????? : "memory"); \ 42 ??????? _a1; \ 43 ??? }) 44 #endif 45 #undef INTERNAL_SYSCALL 46 #define INTERNAL_SYSCALL(name, err, nr, args...) \ 47 ??? INTERNAL_SYSCALL_RAW(SYS_ify(name), err, nr, args) 48 #undef INLINE_SYSCALL 49 #define INLINE_SYSCALL(name, nr, args...) \ 50 ??? ({ \ 51 ??????? unsigned int _sys_result = INTERNAL_SYSCALL (name, , nr, args); \ 52 ??????? if (__builtin_expect (INTERNAL_SYSCALL_ERROR_P (_sys_result, ), 0)) \ 53 ??????? { \ 54 ??????????? __set_errno (INTERNAL_SYSCALL_ERRNO (_sys_result, )); \ 55 ??????????? _sys_result = (unsigned int) -1; \ 56 ??????? } \ 57 ??????? (int) _sys_result; \ 58 ??? }) 59 #endif?
INTERNAL_SYSCALL_RAW()同樣有兩種實(shí)現(xiàn), 我們只關(guān)注ARM指令集的實(shí)現(xiàn). 以LOAD_ARGS_2為例, LOAD_ARGS_*是一組用于加載參數(shù)(將參數(shù)放入對應(yīng)寄存器)的宏, 其參數(shù)壓棧順序依次為a1(r0), a2(r1), a3(r2), a4(r3), v1(r4), v2(r5), v3(r6), r7記錄了參數(shù)個數(shù).
乍一看好像沒有問題? Hell No! 以上分析是基于未定義__ASSEMBLER__, 即傳統(tǒng)ABI, 對于EABI走的是另一套邏輯. 由于未定義INTERNAL_SYSCALL, 默認(rèn)使用sysdeps/unix/sysdep.h下定義.
1 #ifndef INLINE_SYSCALL 2 #define INLINE_SYSCALL(name, nr, args...) __syscall_##name(args) 3 #endif?
__syscall_##name在代碼中完全找不到, 只能猜測是腳本生成的. makefile中有調(diào)用make-syscalls.sh來生成嵌套代碼, 其使用模板是syscall-template.S, 只需修改幾個宏名字即可(這里有個疑問, 其查找的系統(tǒng)調(diào)用的模板syscalls.list里并沒有open?).
1 echo '#define SYSCALL_NAME $syscall'; 2 echo '#define SYSCALL_NARGS $nargs'; 3 echo '#define SYSCALL_SYMBOL $strong'; 4 echo '#define SYSCALL_CANCELLABLE $cancellable'; 5 echo '#define SYSCALL_NOERRNO $noerrno'; 6 echo '#define SYSCALL_ERRVAL $errval'; 7 echo '#include <syscall-template.S>';?
來看下syscall-template.S, 其中調(diào)用的T_PSEUDO*宏為PSEUDO*(defined in sysdeps/unix/sysv/linux/arm/sysdep.h)宏的封裝. 我們以帶返回值的系統(tǒng)調(diào)用為例, 分析流程.
1 #if SYSCALL_NOERRNO 2 //無錯誤返回值的系統(tǒng)調(diào)用, 不做校驗(yàn)直接返回 3 T_PSEUDO_NOERRNO(SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) 4 ret_NOERRNO 5 T_PSEUDO_END_NOERRNO(SYSCALL_SYMBOL) 6 #elif SYSCALL_ERRVAL 7 //將錯誤碼返回在結(jié)果中的系統(tǒng)調(diào)用, 不修改errno 8 T_PSEUDO_ERRVAL(SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) 9 ret_ERRVAL 10 T_PSEUDO_END_ERRVAL(SYSCALL_SYMBOL) 11 #else 12 //常見的系統(tǒng)調(diào)用, 如果有錯誤碼, 返回-1并設(shè)置errno 13 T_PSEUDO(SYSCALL_SYMBOL, SYSCALL_NAME, SYSCALL_NARGS) 14 ret 15 T_PSEUDO_END(SYSCALL_SYMBOL) 16 #endif?
可見PSEUDO()與PSEUDO_END()是成對使用的. 其中DOARGS_*是參數(shù)壓棧的宏, 可見小于4個參數(shù)時除r7無需壓棧(AAPCS要求), r7壓棧原因是AEBI要求使用r7傳遞系統(tǒng)調(diào)用號, 大于4個參數(shù)才需要壓棧. UNDOARGS_*是反作用的宏.
DO_CALL執(zhí)行完后會比較r0與-4095大小, 根據(jù)比較結(jié)果跳轉(zhuǎn). 原因是早期的系統(tǒng)調(diào)用使用負(fù)值返回錯誤狀態(tài), 但從2.1版本開始內(nèi)核的一些系統(tǒng)調(diào)用成功時也會返回負(fù)值(如lseek返回4G以上偏移), 因此glibc與linux協(xié)商使用-4095到-1作為錯誤碼, 更大的負(fù)值仍作為成功的返回值.
插入一句, 在內(nèi)核目錄include/linux/err.h中定義: #define MAX_ERRNO 4095與#define IS_ERR_VALUE(x) unlikely((x) >= (unsigned long)-MAX_ERRNO). 對于需要返回指針或錯誤碼的情況, 可以使用IS_ERR_VALUE()宏來判斷. 因?yàn)楸磉_(dá)式右側(cè)是強(qiáng)制轉(zhuǎn)換為unsigned, 能比-MAX_ERRNO大的只有負(fù)數(shù)且絕對值小于MAX_ERRNO的負(fù)數(shù).
?
SYSCALL_ERROR_HANDLER(defined in sysdeps/unix/sysv/linux/arm/sysdep.S)是錯誤處理接口, 其實(shí)現(xiàn)也比較詭異(一部分匯編包在另一個文件里).
1 ENTRY (__syscall_error) 2 ??? rsb r0, r0, $0 3 #define __syscall_error __syscall_error_1 4 #include <sysdeps/unix/arm/sysdep.S>?
sysdeps/unix/arm/sysdep.S中匯編如下, 此處先去除了無用代碼(僅分析glibc因此未定義rtld, 使用ARM指令集因此未定義__thumb__). 這段指令的作用是獲取errno在TLS中的偏移并賦值, 然后返回.
1 __syscall_error: 2 ??? mov r1, r0 /*返回值保存在r1中 */ 3 ??? GET_TLS (r2) /* 獲取tls地址, 保存在r0中 */ 4 ??? ldr r2, 1f 5 2:? ldr r2, [pc, r2] /* 獲取errno在tls中偏移 */ 6 ??? str r1, [r0, r2] /* 保存返回值 */ 7 ??? mvn r0, #0 /* 將r0設(shè)為-1 */ 8 ??? DO_RET(lr) 9 1:? .word errno(gottpoff) + (. - 2b - PC_OFS)?
讓我們看下GET_TLS(defined in sysdeps/unix/sysv/linux/arm/sysdep.h)的定義. 該宏將lr保存在傳入的TMP中, 調(diào)用GET_TLS_BODY, 返回在r0中, 如果TMP為lr本身表明無需保存lr. 獲取TLS的辦法也很簡單, 跳轉(zhuǎn)到固定地址0xFFFF0FE0(具體下文分析).
1 #define GET_TLS_BODY \ 2 ??? mov r0, #0xffff0fff; \ 3 ??? mov lr, pc; \ 4 ??? sub pc, r0, #31 5 #undef GET_TLS 6 #define GET_TLS(TMP) \ 7 ??? .ifnc TMP, lr; \ 8 ??????? mov TMP, lr; \ 9 ??????? cfi_register (lr, TMP); \ 10 ??????? GET_TLS_BODY; \ 11 ??????? mov lr, TMP; \ 12 ??????? cfi_restore (lr); \ 13 ??? .else; \ 14 ??????? GET_TLS_BODY; \ 15 ??? .endif 16 #endif?
最后來看下反匯編, 印證我們的分析(其實(shí)是對著反匯編才看懂代碼的).
1 000be680 <__open>: 2 be680:?? e51fc028??? ldr ip, [pc, #-40] ; be660 <mkdirat+0x150> 3 be684:?? e79fc00c??? ldr ip, [pc, ip] 4 be688:?? e33c0000??? teq ip, #0 5 be68c:?? e52d7004??? push {r7} ; (str r7, [sp, #-4]!) 6 be690:?? 1a000005??? bne be6ac <__open+0x2c> 7 be694:?? e3a07005??? mov r7, #5 8 be698:?? ef000000??? svc 0x00000000 9 be69c:?? e49d7004??? pop {r7} ; (ldr r7, [sp], #4) 10 be6a0:?? e3700a01??? cmn r0, #4096 ; 0x1000 11 be6a4:?? 312fff1e??? bxcc lr 12 be6a8:?? eafd661c??? b 17f20 <__syscall_error> 13 be6ac:?? e92d400f??? push {r0, r1, r2, r3, lr} 14 be6b0:?? eb007a18??? bl dcf18 <__libc_enable_asynccancel> 15 be6b4:?? e1a0c000??? mov ip, r0 16 be6b8:?? e8bd000f??? pop {r0, r1, r2, r3} 17 be6bc:?? e3a07005??? mov r7, #5 18 be6c0:?? ef000000??? svc 0x00000000 19 be6c4:?? e1a07000??? mov r7, r0 20 be6c8:?? e1a0000c??? mov r0, ip 21 be6cc:?? eb007a41??? bl dcfd8 <__libc_disable_asynccancel> 22 be6d0:?? e1a00007??? mov r0, r7 23 be6d4:?? e49de004??? pop {lr} ; (ldr lr, [sp], #4) 24 be6d8:?? e49d7004??? pop {r7} ; (ldr r7, [sp], #4) 25 be6dc:?? e3700a01??? cmn r0, #4096 ; 0x1000 26 be6e0:?? 312fff1e??? bxcc lr 27 be6e4:?? eafd660d??? b 17f20 <__syscall_error> 28 be6e8:?? e1a00000??? nop ; (mov r0, r0) 29 be6ec:?? e1a00000??? nop ; (mov r0, r0) 30 00017f20 <__syscall_error>: 31 17f20:?? e2600000??? rsb r0, r0, #0 32 00017f24 <__syscall_error_1>: 33 17f24:?? e1a0c00e??? mov ip, lr 34 17f28:?? e1a01000??? mov r1, r0 35 17f2c:?? e3e00a0f??? mvn r0, #61440 ; 0xf000 36 17f30:?? e1a0e00f??? mov lr, pc 37 17f34:?? e240f01f??? sub pc, r0, #31 38 17f38:?? e59f200c??? ldr r2, [pc, #12] ; 17f4c <__syscall_error_1+0x28> 39 17f3c:?? e79f2002??? ldr r2, [pc, r2] 40 17f40:?? e7801002??? str r1, [r0, r2] 41 17f44:?? e3e00000??? mvn r0, #0 42 17f48:?? e12fff1c??? bx ip 43 17f4c:?? 0011c108??? andseq ip, r1, r8, lsl #2?
用戶態(tài)的系統(tǒng)調(diào)用封裝暫告結(jié)束, 我們總結(jié)一下即:
1. 系統(tǒng)調(diào)用都是通過glibc封裝的(有個例外是syscall()函數(shù)會使用原生系統(tǒng)調(diào)用, 具體不分析了, 可以man syscall查看).
2. glibc封裝的作用主要是參數(shù)入棧, 設(shè)置系統(tǒng)調(diào)用號, 判斷返回值與設(shè)置errno.
3. 在設(shè)置系統(tǒng)調(diào)用號時native ABI與AEABI的實(shí)現(xiàn)不同, 前者系統(tǒng)調(diào)用號傳在swi指令中, 后者使用r7傳遞.
4. 根據(jù)不同系統(tǒng)調(diào)用類型glibc會做不同返回處理, 對于通常系統(tǒng)調(diào)用, 其結(jié)果保存在errno中(如果失敗), errno是線程安全的, 其實(shí)現(xiàn)下文詳述.
讓我們先回到swi指令, 執(zhí)行swi后跳轉(zhuǎn)系統(tǒng)異常, arch/arm/kernel/entry-armv.S中定義了異常向量表. 對于軟中斷向量表很簡單, 直接調(diào)轉(zhuǎn)vector_swi(defined in arch/arm/kernel/entry-common.S).
vector_swi()的作用是保存進(jìn)入內(nèi)核態(tài)時寄存器環(huán)境, 根據(jù)系統(tǒng)調(diào)用號查找系統(tǒng)調(diào)用入口, 跳轉(zhuǎn)執(zhí)行系統(tǒng)調(diào)用以及在返回后做錯誤處理. 其中壓棧步驟見注釋, 查找系統(tǒng)調(diào)用入口時需注意新舊abi的區(qū)別, eabi使用r7傳遞系統(tǒng)調(diào)用號而old abi使用swi的參數(shù)位傳遞系統(tǒng)調(diào)用號, 執(zhí)行系統(tǒng)調(diào)用后返回(lr)在ret_fast_syscall().
?
先來看看__sys_trace()(defined in arch/arm/kernel/entry-common.S), 該函數(shù)僅在thread_info->flags的_TIF_SYSCALL_WORK置位時才會進(jìn)入(即使用strace跟蹤系統(tǒng)調(diào)用時), 走入slow path.之所以成為slow path的原因: 在syscall_trace_enter中可能發(fā)生阻塞, 即可能發(fā)生上下文切換.
1 __sys_trace: 2 ??? /** 3 ????? 調(diào)用syscall_trace_enter, 傳遞的參數(shù)依次為pt_regs與scno 4 ???? * 5 ??? **/ 6 ??? mov r1, scno 7 ??? add r0, sp, #S_OFF 8 ??? bl syscall_trace_enter 9 ??? /** 10 ????? 修改返回地址(lr)為__sys_trace_return并將r0傳遞給scno 11 ????? r0為syscall_trace_enter返回值, 為保存的scno或-1(系統(tǒng)調(diào)用號檢查失敗或信號掛起) 12 ????? 之后流程與vector_swi一致, 即壓棧r0-r6, r4與r5, 然后查表并調(diào)用系統(tǒng)調(diào)用 13 ????? 如果scno大于NR_syscalls, 判斷scno是否為-1, 是則調(diào)用ret_slow_syscall 14 ????? 否則為未實(shí)現(xiàn)的系統(tǒng)調(diào)用, 走入2b(見上文) 15 ???? * 16 ??? **/ 17 ??? adr lr, BSYM(__sys_trace_return) 18 ??? mov scno, r0 19 ??? add r1, sp, #S_R0 + S_OFF 20 ??? cmp scno, #NR_syscalls 21 ??? ldmccia r1, {r0 - r6} 22 ??? stmccia sp, {r4, r5} 23 ??? ldrcc pc, [tbl, scno, lsl #2] 24 ??? cmp scno, #-1 25 ??? bne 2b 26 ??? add sp, sp, #S_OFF 27 ??? b ret_slow_syscall?
回頭看下syscall_trace_enter(defined in arch/arm/kernel/ptrace.c)的返回值. 有兩處地方可能返回-1, 一是secure_computing()失敗返回-1, 二是tracehook_report_syscall()中修改thread_info->syscall為-1(只有在有信號掛起時才-1).
1 static void tracehook_report_syscall(struct pt_regs *regs, enum ptrace_syscall_dir dir) 2 { 3 ??? unsigned long ip; 4 ??? /ip用于標(biāo)記syscall的進(jìn)入與退出, ip = 0為進(jìn)入, ip = 1為退出 5 ??? ip = regs->ARM_ip; 6 ??? regs->ARM_ip = dir; 7 ??? if (dir == PTRACE_SYSCALL_EXIT) 8 ??????? tracehook_report_syscall_exit(regs, 0); 9 ??? else if (tracehook_report_syscall_entry(regs)) 10 ??????? current_thread_info()->syscall = -1; 11 ??? regs->ARM_ip = ip; 12 } 13 asmlinkage int syscall_trace_enter(struct pt_regs *regs, int scno) 14 { 15 ??? current_thread_info()->syscall = scno; 16 ??? if (secure_computing(scno) == -1) 17 ??????? return -1; 18 ??? if (test_thread_flag(TIF_SYSCALL_TRACE)) 19 ??????? tracehook_report_syscall(regs, PTRACE_SYSCALL_ENTER); 20 ??? scno = current_thread_info()->syscall; 21 ??? if (test_thread_flag(TIF_SYSCALL_TRACEPOINT)) 22 ??????? trace_sys_enter(regs, scno); 23 ??? audit_syscall_entry(AUDIT_ARCH_ARM, scno, \ 24 ??????? regs->ARM_r0, regs->ARM_r1, regs->ARM_r2, regs->ARM_r3); 25 ??? return scno; 26 }?
再來看下系統(tǒng)調(diào)用表是如何定義的? sys_call_table(defined in arch/arm/kernel/entry-common.S)是一個由calls.S(arch/arm/kernel/calls.S)定義的數(shù)組.
1 #define CALL(x) .long x 2 #define ABI(native, compat) native 3 #ifdef CONFIG_AEABI 4 #define OBSOLETE(syscall) sys_ni_syscall 5 #else 6 #define OBSOLETE(syscall) syscall 7 #endif 8 .type sys_call_table, #object 9 ENTRY(sys_call_table) 10 #include "calls.S" 11 #undef ABI 12 #undef OBSOLETE?
最后來看下系統(tǒng)調(diào)用返回時的接口ret_fast_syscall(defined in arch/arm/kernel/entry-common.S)與ret_slow_syscall()(defined in arch/arm/kernel/entry-common.S).
1 fast_work_pending: 2 ??? /** 3 ????? 記錄r0, 用于傳遞系統(tǒng)調(diào)用的結(jié)果 4 ???? * 5 ??? **/ 6 ??? str r0, [sp, #S_R0+S_OFF]! 7 work_pending: 8 ??? /** 9 ????? do_work_pending()只有兩種返回值, 正常返回0或內(nèi)核異常(返回值為do_signal的返回值) 10 ????? 正常返回走no_work_pending, 內(nèi)核異常走local_restart(見上文) 11 ???? * 12 ??? **/ 13 ??? mov r0, sp 14 ??? mov r2, why 15 ??? bl do_work_pending 16 ??? cmp r0, #0 17 ??? beq no_work_pending 18 ??? movlt scno, #(__NR_restart_syscall - __NR_SYSCALL_BASE) 19 ??? ldmia sp, {r0 - r6} 20 ??? b local_restart 21 ret_fast_syscall: 22 ??? UNWIND(.fnstart) 23 ??? UNWIND(.cantunwind) 24 ??? disable_irq 25 ??? /** 26 ????? 如果thread_info->flags需要調(diào)度或信號掛起或需信號處理則走入fast_work_pending 27 ????? 否則直接恢復(fù)用戶態(tài)環(huán)境 28 ???? * 29 ??? **/ 30 ??? ldr r1, [tsk, #TI_FLAGS] 31 ??? tst r1, #_TIF_WORK_MASK 32 ??? bne fast_work_pending 33 ??? asm_trace_hardirqs_on 34 ??? /** 35 ????? arch_ret_to_user()是架構(gòu)相關(guān)代碼 36 ????? ct_user_exit()是與ct_user_exit()成對調(diào)用的上下文跟蹤的代碼 37 ???? * 38 ??? **/ 39 ??? arch_ret_to_user r1, lr 40 ??? ct_user_enter 41 ??? restore_user_regs fast = 1, offset = S_OFF 42 ??? UNWIND(.fnend) 43 ret_slow_syscall: 44 ??? disable_irq 45 ??? ldr r1, [tsk, #TI_FLAGS] 46 ??? tst r1, #_TIF_WORK_MASK 47 ??? bne work_pending 48 no_work_pending: 49 ??? asm_trace_hardirqs_on 50 ??? arch_ret_to_user r1, lr 51 ??? ct_user_enter save = 0 52 ??? restore_user_regs fast = 0, offset = 0?
看下如何從內(nèi)核態(tài)返回到用戶態(tài), 以下為未定義THUMB2_KERNEL時restore_user_regs()(defined in arch/arm/kernel/entry-header.S)的實(shí)現(xiàn).
1 .macro restore_user_regs, fast = 0, offset = 0 2 ??? ldr r1, [sp, #\offset + S_PSR] 3 ??? ldr lr, [sp, #\offset + S_PC]! 4 ??? msr spsr_cxsf, r1 5 #if defined(CONFIG_CPU_V6) 6 ??? strex r1, r2, [sp] 7 #elif defined(CONFIG_CPU_32v6K) 8 ??? clrex 9 #endif 10 ??? /** 11 ????? fast path與slow path區(qū)別在于fast path不恢復(fù)r0(返回系統(tǒng)調(diào)用結(jié)果) 12 ???? * 13 ??? **/ 14 ??? .if \fast 15 ??? ldmdb sp, {r1 - lr}^ 16 ??? .else 17 ??? ldmdb sp, {r0 - lr}^ 18 ??? .endif 19 ??? /** 20 ????? ARMv5T之前架構(gòu)在ldm指令后需要一個nop 21 ???? * 22 ??? **/ 23 ??? mov r0, r0 24 ??? add sp, sp, #S_FRAME_SIZE - S_PC 25 ??? /** 26 ????? 將spsr_svc賦值給cpsr并返回到用戶態(tài)(見ARM ref manual) 27 ???? * 28 ??? **/ 29 ??? movs pc, lr 30 .endm?
總結(jié)一下系統(tǒng)調(diào)用在內(nèi)核中的流程, 跳轉(zhuǎn)異常向量表, 保存用戶態(tài)環(huán)境, 查表執(zhí)行系統(tǒng)調(diào)用, 根據(jù)執(zhí)行結(jié)果做不同處理, 恢復(fù)用戶態(tài)環(huán)境并返回用戶態(tài).
讓我們再次回到用戶態(tài), 之前討論過errno是線程存儲的, glibc通過跳轉(zhuǎn)執(zhí)行0xffff0fe0的指令來獲取TLS數(shù)據(jù)段, 那么0xffff0fe0究竟存放了什么呢? 讓我們來看下__kuser_get_tls()(defined in arch/arm/kernel/entry-armv.S), 該接口正好存放在該地址上(0xffff0000-0xffff1000是任何進(jìn)程都會映射的地址, 因此訪問該地址并不會觸發(fā)異常), 該接口共占7條指令, 其中后4條初始化為0, 前三條指令分別將TLS地址保存在r0中, 跳轉(zhuǎn)用戶態(tài), 硬件TLS指令, 反匯編指令見下. 除了跳轉(zhuǎn)指令后沒有必要再操作協(xié)處理器以外好像沒什么問題.
1 .macro usr_ret, reg 2 #ifdef CONFIG_ARM_THUMB 3 ??? bx \reg 4 #else 5 ??? mov pc, \reg 6 #endif 7 .endm 8 __kuser_get_tls: @ 0xffff0fe0 9 ??? ldr r0, [pc, #(16 - 8)] @ read TLS, set in kuser_get_tls_init 10 ??? usr_ret lr 11 ??? mrc p15, 0, r0, c13, c0, 3 @ 0xffff0fe8 hardware TLS code 12 ??? .rep 4 13 ??? .word 0 @ 0xffff0ff0 software TLS value, then 14 ??? .endr @ pad up to __kuser_helper_version 15 __kuser_helper_version: @ 0xffff0ffc 16 ??? .word ((__kuser_helper_end - __kuser_helper_start) >> 5) 17 c053d9e0 <__kuser_get_tls>: 18 c053d9e0:?? e59f0008??? ldr r0, [pc, #8] ; c053d9f0 <__kuser_get_tls+0x10> 19 c053d9e4:?? e12fff1e??? bx lr 20 c053d9e8:?? ee1d0f70??? mrc 15, 0, r0, cr13, cr0, {3} 21 ??? ... 22 c053d9fc <__kuser_helper_version>: 23 c053d9fc:?? 00000005??? andeq r0, r0, r5?
幸好我又做了次試驗(yàn)! 不然又要打臉了. 打印結(jié)果顯示前兩條指令和反匯編內(nèi)核結(jié)果不同, 其中0xe1a0f00e是mov pc, lr指令, 與bx lr類似先不討論, 為什么0xfe0與0xfe8的指令相同?
1 int main() 2 { 3 ??? unsigned int i = 0, *p = 0xffff0fe0; 4 ??? for (i = 0; i < 8; i++) 5 ??????? printf("%p: 0x%x\n", p + i, *(p + i)); 6 } 7 #arm-hisiv400-linux-gcc test.c 8 # ./a.out? 9 0xffff0fe0: 0xee1d0f70 10 0xffff0fe4: 0xe1a0f00e 11 0xffff0fe8: 0xee1d0f70 12 0xffff0fec: 0x0 13 0xffff0ff0: 0x0 14 0xffff0ff4: 0x0 15 0xffff0ff8: 0x0 16 0xffff0ffc: 0x5?
看了下注釋找到了kuser_get_tls_init()(defined in arch/arm/kernel/traps.c), 該函數(shù)在early_trap_init()(defined in arch/arm/kernel/traps.c)中被調(diào)用(即初始化異常向量表的函數(shù)). 其中tls_emu/has_tls_reg(defined in arch/arm/include/asm/tls.h)是架構(gòu)相關(guān)宏, 分別定義了是否模擬TLS與是否使用TLS寄存器, 此處由于我們定義了CPU_32v6K, 因此不使用TLS模擬且支持TLS寄存器. 故系統(tǒng)初始化時會將0xfe8指令拷貝到0xfe0.
1 static void __init kuser_get_tls_init(unsigned long vectors) 2 { 3 ??? if (tls_emu || has_tls_reg) 4 ??????? memcpy((void *)vectors + 0xfe0, (void *)vectors + 0xfe8, 4); 5 }?
順帶一提是在定義了CPU_32v6K時設(shè)置TLS寄存器的操作set_tls(defined in arch/arm/include/asm/tls.h)正好與該指令相反. 該宏是在__switch_to(defined in arch/arm/kernel/entry-armv.S)匯編函數(shù)中調(diào)用的, 該函數(shù)我們在分析調(diào)度接口scheduled()時提到過, 是切換上下文的函數(shù), 具體可見之前的分析.
1 .macro set_tls_v6k, tp, tmp1, tmp2 2 ??? mcr p15, 0, \tp, c13, c0, 3 @ set TLS register 3 ??? mov \tmp1, #0 4 ??? mcr p15, 0, \tmp1, c13, c0, 2 @ clear user r/w TLS register 5 .endm 6 #define set_tls set_tls_v6k?
最后說下0xfe8處指令的作用, CP13是線程ID寄存器(for more detail see ARM architecture reference manual markup B4.6.35), 即專門用于存儲線程相關(guān)信息的寄存器, 自ARMv7引入.
本來想寫些關(guān)于線程跟蹤實(shí)現(xiàn)的, 結(jié)果一懶又挖坑不填了(主要是glibc看起來太消耗精力了,比內(nèi)核復(fù)雜一萬倍), 添了點(diǎn)tls的內(nèi)容濫竽充數(shù), 剩下的以后再說吧.
?
轉(zhuǎn)載于:https://www.cnblogs.com/Five100Miles/p/8878080.html
總結(jié)
以上是生活随笔為你收集整理的系统调用的实现(与errno的设置)的全部內(nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: Python3 图片(jpg、bmp、p
- 下一篇: 逻辑判断-if语句/文件目录属性判断/c