鴻蒙內(nèi)核源碼分析(系統(tǒng)調(diào)用篇) | 圖解系統(tǒng)調(diào)用全貌
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)
本篇說清楚系統(tǒng)調(diào)用
讀本篇之前建議先讀鴻蒙內(nèi)核源碼分析(總目錄)工作模式篇.
本篇通過一張圖和七段代碼詳細(xì)說明系統(tǒng)調(diào)用的整個過程,代碼一捅到底,直到匯編層再也捅不下去. 先看圖,這里的模式可以理解為空間,因為模式不同運行的棧空間就不一樣.
過程解讀
● 在應(yīng)用層main中使用系統(tǒng)調(diào)用mq_open(posix標(biāo)準(zhǔn)接口)
● mq_open被封裝在庫中,這里直接看庫里的代碼.
● mq_open中調(diào)用syscall,將參數(shù)傳給寄出器 R7,R0~R6
● SVC 0 完成用戶模式到內(nèi)核模式(SVC)的切換
● _osExceptSwiHdl運行在svc模式下.
● PC寄存器直接指向_osExceptSwiHdl處取指令.
● _osExceptSwiHdl是匯編代碼,先保存用戶模式現(xiàn)場(R0~R12寄存器),并調(diào)用OsArmA32SyscallHandle完成系統(tǒng)調(diào)用
● OsArmA32SyscallHandle中通過系統(tǒng)調(diào)用號(保存在R7寄存器)查詢對應(yīng)的注冊函數(shù)SYS_mq_open
● SYS_mq_open是本次系統(tǒng)調(diào)用的實現(xiàn)函數(shù),完成后return回到OsArmA32SyscallHandle
● OsArmA32SyscallHandle再return回到_osExceptSwiHdl
● _osExceptSwiHdl恢復(fù)用戶模式現(xiàn)場(R0~R12寄存器)
● 從內(nèi)核模式(SVC)切回到用戶模式,PC寄存器也切回用戶現(xiàn)場.
● 由此完成整個系統(tǒng)調(diào)用全過程
七段追蹤代碼,逐個分析
1.應(yīng)用程序 main
- int main(void)
- {
- char mqname[NAMESIZE], msgrv1[BUFFER], msgrv2[BUFFER];
- const char *msgptr1 = "test message1";
- const char *msgptr2 = "test message2 with differnet length";
- mqd_t mqdes;
- int prio1 = 1, prio2 = 2;
- struct timespec ts;
- struct mq_attr attr;
- int unresolved = 0, failure = 0;
- sprintf(mqname, "/" FUNCTION "_" TEST "_%d", getpid());
- attr.mq_msgsize = BUFFER;
- attr.mq_maxmsg = BUFFER;
- mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr);
- if (mqdes == (mqd_t)-1) {
- perror(ERROR_PREFIX "mq_open");
- unresolved = 1;
- }
- if (mq_send(mqdes, msgptr1, strlen(msgptr1), prio1) != 0) {
- perror(ERROR_PREFIX "mq_send");
- unresolved = 1;
- }
- printf("Test PASSED\n");
- return PTS_PASS;
- }
2. mq_open 發(fā)起系統(tǒng)調(diào)用
- mqd_t mq_open(const char *name, int flags, ...)
- {
- mode_t mode = 0;
- struct mq_attr *attr = 0;
- if (*name == '/') name++;
- if (flags & O_CREAT) {
- va_list ap;
- va_start(ap, flags);
- mode = va_arg(ap, mode_t);
- attr = va_arg(ap, struct mq_attr *);
- va_end(ap);
- }
- return syscall(SYS_mq_open, name, flags, mode, attr);
- }
解讀
● SYS_mq_open 是真正的系統(tǒng)調(diào)用函數(shù),對應(yīng)一個系統(tǒng)調(diào)用號__NR_mq_open,通過宏SYSCALL_HAND_DEF將SysMqOpen注冊到g_syscallHandle中.
- static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系統(tǒng)調(diào)用入口函數(shù)注冊
- static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系統(tǒng)調(diào)用對應(yīng)的參數(shù)數(shù)量
- #define SYSCALL_HAND_DEF(id, fun, rType, nArg) \
- if ((id) < SYS_CALL_NUM) { \
- g_syscallHandle[(id)] = (UINTPTR)(fun); \
- g_syscallNArgs[(id) / NARG_PER_BYTE] |= ((id) & 1) ? (nArg) << NARG_BITS : (nArg); \
- } \
- #include "syscall_lookup.h"
- #undef SYSCALL_HAND_DEF
- SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)
● g_syscallNArgs為注冊函數(shù)的參數(shù)個數(shù),也會一塊記錄下來.
● 四個參數(shù)為 SYS_mq_open的四個參數(shù),將保存在R0~R3寄存器中
3. syscall
- long syscall(long n, ...)
- {
- va_list ap;
- syscall_arg_t a,b,c,d,e,f;
- va_start(ap, n);
- a=va_arg(ap, syscall_arg_t);
- b=va_arg(ap, syscall_arg_t);
- c=va_arg(ap, syscall_arg_t);
- d=va_arg(ap, syscall_arg_t);
- e=va_arg(ap, syscall_arg_t);
- f=va_arg(ap, syscall_arg_t);//最多6個參數(shù)
- va_end(ap);
- return __syscall_ret(__syscall(n,a,b,c,d,e,f));
- }
- static inline long __syscall4(long n, long a, long b, long c, long d)
- {
- register long a7 __asm__("a7") = n; //系統(tǒng)調(diào)用號 R7寄存器
- register long a0 __asm__("a0") = a; //R0
- register long a1 __asm__("a1") = b; //R1
- register long a2 __asm__("a2") = c; //R2
- register long a3 __asm__("a3") = d; //R3
- __asm_syscall("r"(a7), "0"(a0), "r"(a1), "r"(a2), "r"(a3))
- }
解讀
● 可變參數(shù)實現(xiàn)所有系統(tǒng)調(diào)用的參數(shù)的管理,可以看出,在鴻蒙內(nèi)核中系統(tǒng)調(diào)用的參數(shù)最多不能大于6個
● R7寄存器保存了系統(tǒng)調(diào)用號,R0~R5保存具體每個參數(shù)
● 可變參數(shù)的具體實現(xiàn)后續(xù)有其余篇幅詳細(xì)介紹,敬請關(guān)注.
4. svc 0
- //切到SVC模式
- #define __asm_syscall(...) do { \
- __asm__ __volatile__ ( "svc 0" \
- : "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \
- return x0; \
- } while (0)
- b reset_vector @開機(jī)代碼
- b _osExceptUndefInstrHdl @異常處理之CPU碰到不認(rèn)識的指令
- b _osExceptSwiHdl @異常處理之:軟中斷
- b _osExceptPrefetchAbortHdl @異常處理之:取指異常
- b _osExceptDataAbortHdl @異常處理之:數(shù)據(jù)異常
- b _osExceptAddrAbortHdl @異常處理之:地址異常
- b OsIrqHandler @異常處理之:硬中斷
- b _osExceptFiqHdl @異常處理之:快中斷
解讀
● svc 全稱是 SuperVisor Call,完成工作模式的切換.不管之前是7個模式中的哪個模式,統(tǒng)一都切到SVC管理模式
● 而軟中斷對應(yīng)的處理函數(shù)為 _osExceptSwiHdl,即PC寄存器將跳到_osExceptSwiHdl執(zhí)行
5. _osExceptSwiHdl
- @ Description: Software interrupt exception handler
- _osExceptSwiHdl: @軟中斷異常處理
- SUB SP, SP, #(4 * 16) @先申請16個??臻g用于處理本次軟中斷
- STMIA SP, {R0-R12} @保存R0-R12寄存器值
- MRS R3, SPSR @讀取本模式下的SPSR值
- MOV R4, LR @保存回跳寄存器LR
- AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 獲取中斷模式
- CMP R1, #CPSR_USER_MODE @ User mode 是否為用戶模式
- BNE OsKernelSVCHandler @ Branch if not user mode 非用戶模式下跳轉(zhuǎn)
- @ 當(dāng)為用戶模式時,獲取SP和LR寄出去值
- @ we enter from user mode, we need get the values of USER mode r13(sp) and r14(lr).
- @ stmia with ^ will return the user mode registers (provided that r15 is not in the register list).
- MOV R0, SP @獲取SP值,R0將作為OsArmA32SyscallHandle的參數(shù)
- STMFD SP!, {R3} @ Save the CPSR 入棧保存CPSR值
- ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存儲位置
- STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器
- STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 保存用戶模式下的SP和LR寄存器
- SUB SP, SP, #4
- PUSH_FPU_REGS R1 @保存中斷模式(用戶模式模式)
- MOV FP, #0 @ Init frame pointer
- CPSIE I @開中斷,表明在系統(tǒng)調(diào)用期間可響應(yīng)中斷
- BLX OsArmA32SyscallHandle /*交給C語言處理系統(tǒng)調(diào)用*/
- CPSID I @執(zhí)行后續(xù)指令前必須先關(guān)中斷
- POP_FPU_REGS R1 @彈出FP值給R1
- ADD SP, SP,#4 @ 定位到保存舊SPSR值的位置
- LDMFD SP!, {R3} @ Fetch the return SPSR 彈出舊SPSR值
- MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢復(fù)該模式下的SPSR值
- @ we are leaving to user mode, we need to restore the values of USER mode r13(sp) and r14(lr).
- @ ldmia with ^ will return the user mode registers (provided that r15 is not in the register list)
- LDMFD SP!, {R0-R12} @恢復(fù)R0-R12寄存器
- LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢復(fù)用戶模式的R13/R14寄存器
- ADD SP, SP, #(2 * 4) @定位到保存舊PC值的位置
- LDMFD SP!, {PC}^ @ Return to user 切回用戶模式運行
解讀
● 運行到此處,已經(jīng)切到SVC的棧運行,所以先保存上一個模式的現(xiàn)場
● 獲取中斷模式,軟中斷的來源可不一定是用戶模式,完全有可能是SVC本身,比如系統(tǒng)調(diào)用中又發(fā)生系統(tǒng)調(diào)用.就變成了從SVC模式切到SVC的模式
● MOV R0, SP ;sp將作為參數(shù)傳遞給OsArmA32SyscallHandle
● 調(diào)用 OsArmA32SyscallHandle 這是所有系統(tǒng)調(diào)用的統(tǒng)一入口
● 注意看OsArmA32SyscallHandle的參數(shù) UINT32 *regs
6. OsArmA32SyscallHandle
- LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)
- {
- UINT32 ret;
- UINT8 nArgs;
- UINTPTR handle;
- UINT32 cmd = regs[REG_R7];// R7寄存器記錄了系統(tǒng)調(diào)用號
- if (cmd >= SYS_CALL_NUM) {//系統(tǒng)調(diào)用的總數(shù)
- PRINT_ERR("Syscall ID: error %d !!!\n", cmd);
- return regs;
- }
- if (cmd == __NR_sigreturn) {//此時運行在內(nèi)核棧,程序返回的調(diào)用,從內(nèi)核態(tài)返回用戶態(tài)時觸發(fā)
- OsRestorSignalContext(regs);//恢復(fù)信號上下文,執(zhí)行完函數(shù)后,切到了用戶棧
- return regs;
- }
- handle = g_syscallHandle[cmd];//拿到系統(tǒng)調(diào)用的注冊函數(shù),即 SYS_mq_open
- nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */
- nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//獲取參數(shù)個數(shù)
- if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系統(tǒng)調(diào)用必須有參數(shù)且參數(shù)不能大于8個
- PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs);
- regs[REG_R0] = -ENOSYS;
- return regs;
- }
- //regs[0-6] 記錄系統(tǒng)調(diào)用的參數(shù),這也是由 R7 寄存器保存系統(tǒng)調(diào)用號的原因
- switch (nArgs) {//參數(shù)的個數(shù)
- case ARG_NUM_0:
- case ARG_NUM_1:
- ret = (*(SyscallFun1)handle)(regs[REG_R0]);//執(zhí)行系統(tǒng)調(diào)用,類似 SysUnlink(pathname);
- break;
- case ARG_NUM_2://@note_thinking 如何是兩個參數(shù)的系統(tǒng)調(diào)用,這里傳的確是三個參數(shù),任務(wù)棧中會出現(xiàn)怎樣的情況呢?
- case ARG_NUM_3:
- ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//類似 SysExecve(fileName, argv, envp);
- break;
- case ARG_NUM_4:
- case ARG_NUM_5:
- ret = (*(SyscallFun5)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
- regs[REG_R4]);
- break;
- default: //7個參數(shù)的情況
- ret = (*(SyscallFun7)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2], regs[REG_R3],
- regs[REG_R4], regs[REG_R5], regs[REG_R6]);
- }
- regs[REG_R0] = ret;//R0保存系統(tǒng)調(diào)用返回值
- OsSaveSignalContext(regs);//保存用戶?,F(xiàn)場
- /* Return the last value of curent_regs. This supports context switches on return from the exception.
- * That capability is only used with theSYS_context_switch system call.
- */
- return regs;//返回寄存器的值
- }
解讀
● 參數(shù)是regs對應(yīng)的就是R0~Rn
● R7保存的是系統(tǒng)調(diào)用號,R0~R3保存的是 SysMqOpen的四個參數(shù)
● g_syscallHandle[cmd]就能查詢到 SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)注冊時對應(yīng)的 SysMqOpen函數(shù)
● *(SyscallFun5)handle此時就是SysMqOpen
● 注意看 SysMqOpen 的參數(shù)是最開始的 main函數(shù)中的 mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr); 由此完成了真正系統(tǒng)調(diào)用的過程
7. SysMqOpen
- mqd_t SysMqOpen(const char *mqName, int openFlag, mode_t mode, struct mq_attr *attr)
- {
- mqd_t ret;
- int retValue;
- char kMqName[PATH_MAX + 1] = { 0 };
- retValue = LOS_StrncpyFromUser(kMqName, mqName, PATH_MAX);
- if (retValue < 0) {
- return retValue;
- }
- ret = mq_open(kMqName, openFlag, mode, attr);//一個消息隊列可以有多個進(jìn)程向它讀寫消息
- if (ret == -1) {
- return (mqd_t)-get_errno();
- }
- return ret;
- }
解讀
● 此處的mq_open和main函數(shù)的mq_open其實是兩個函數(shù)體實現(xiàn).一個是給應(yīng)用層的調(diào)用,一個是內(nèi)核層使用,只是名字一樣而已.
● SysMqOpen是返回到 OsArmA32SyscallHandle regs[REG_R0] = ret;
● OsArmA32SyscallHandle再返回到 _osExceptSwiHdl
● _osExceptSwiHdl后面的代碼是用于恢復(fù)用戶模式現(xiàn)場和SPSR,PC 等寄存器.
以上為鴻蒙系統(tǒng)調(diào)用的整個過程.
關(guān)于寄存器(R0~R15)在每種模式下的使用方式,寄存器篇中已詳細(xì)說明,請前往查看.
51CTO和華為官方合作共建的鴻蒙技術(shù)社區(qū)