本篇通過一張圖和七段代碼詳細(xì)說明系統(tǒng)調(diào)用的整個(gè)過程,代碼一捅到底,直到匯編層再也捅不下去. 先看圖,這里的模式可以理解為空間,因?yàn)槟J讲煌\(yùn)行的棧空間就不一樣.

過程解讀
在應(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運(yùn)行在svc模式下.
PC寄存器直接指向_osExceptSwiHdl處取指令.
_osExceptSwiHdl是匯編代碼,先保存用戶模式現(xiàn)場(chǎng)(R0~R12寄存器),并調(diào)用OsArmA32SyscallHandle完成系統(tǒng)調(diào)用
OsArmA32SyscallHandle中通過系統(tǒng)調(diào)用號(hào)(保存在R7寄存器)查詢對(duì)應(yīng)的注冊(cè)函數(shù)SYS_mq_open
SYS_mq_open是本次系統(tǒng)調(diào)用的實(shí)現(xiàn)函數(shù),完成后return回到OsArmA32SyscallHandle
OsArmA32SyscallHandle再return回到_osExceptSwiHdl
_osExceptSwiHdl恢復(fù)用戶模式現(xiàn)場(chǎng)(R0~R12寄存器)
從內(nèi)核模式(SVC)切回到用戶模式,PC寄存器也切回用戶現(xiàn)場(chǎng).
由此完成整個(gè)系統(tǒng)調(diào)用全過程
七段追蹤代碼,逐個(gè)分析
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ù),對(duì)應(yīng)一個(gè)系統(tǒng)調(diào)用號(hào)__NR_mq_open,通過宏SYSCALL_HAND_DEF將SysMqOpen注冊(cè)到g_syscallHandle中.
static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系統(tǒng)調(diào)用入口函數(shù)注冊(cè)
static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系統(tǒng)調(diào)用對(duì)應(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為注冊(cè)函數(shù)的參數(shù)個(gè)數(shù),也會(huì)一塊記錄下來.
四個(gè)參數(shù)為 SYS_mq_open的四個(gè)參數(shù),后續(xù)將保存在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個(gè)參數(shù)
va_end(ap);
return __syscall_ret(__syscall(n,a,b,c,d,e,f));
}
//4個(gè)參數(shù)的系統(tǒng)調(diào)用時(shí)底層處理
static inline long __syscall4(long n, long a, long b, long c, long d)
{
register long a7 __asm__("a7") = n; //將系統(tǒng)調(diào)用號(hà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ù)實(shí)現(xiàn)所有系統(tǒng)調(diào)用的參數(shù)的管理,可以看出,在鴻蒙內(nèi)核中系統(tǒng)調(diào)用的參數(shù)最多不能大于6個(gè)
R7寄存器保存了系統(tǒng)調(diào)用號(hào),R0~R5保存具體每個(gè)參數(shù)
可變參數(shù)的具體實(shí)現(xiàn)后續(xù)有其余篇幅詳細(xì)介紹,敬請(qǐng)關(guān)注.
4. svc 0
//切到SVC模式
#define __asm_syscall(...) do { \
__asm__ __volatile__ ( "svc 0" \
: "=r"(x0) : __VA_ARGS__ : "memory", "cc"); \
return x0; \
} while (0)
看不太懂的沒關(guān)系,這里我們只需要記住:系統(tǒng)調(diào)用號(hào)存放在r7寄存器,參數(shù)存放在r0,r1,r2寄存器中,返回值最終會(huì)存放在寄存器r0中


b reset_vector @開機(jī)代碼
b _osExceptUndefInstrHdl @異常處理之CPU碰到不認(rèn)識(shí)的指令
b _osExceptSwiHdl @異常處理之:軟中斷
b _osExceptPrefetchAbortHdl @異常處理之:取指異常
b _osExceptDataAbortHdl @異常處理之:數(shù)據(jù)異常
b _osExceptAddrAbortHdl @異常處理之:地址異常
b OsIrqHandler @異常處理之:硬中斷
b _osExceptFiqHdl @異常處理之:快中斷
解讀
svc全稱是 SuperVisor Call,完成工作模式的切換.不管之前是7個(gè)模式中的哪個(gè)模式,統(tǒng)一都切到SVC管理模式.但你也許會(huì)好奇,ARM軟中斷不是用SWI嗎,這里怎么變成了SVC了,請(qǐng)看下面一段話,是從ARM官網(wǎng)翻譯的:
SVC
超級(jí)用戶調(diào)用。
語法
SVC{cond} #immed
其中:
cond
是一個(gè)可選的條件代碼(請(qǐng)參閱條件執(zhí)行)。
immed
是一個(gè)表達(dá)式,其取值為以下范圍內(nèi)的一個(gè)整數(shù):
在 ARM 指令中為 0 到 224–1(24 位值)
在 16 位 Thumb 指令中為 0-255(8 位值)。
用法
SVC 指令會(huì)引發(fā)一個(gè)異常。 這意味著處理器模式會(huì)更改為超級(jí)用戶模式,CPSR 會(huì)保存到超級(jí)用戶模式 SPSR,并且執(zhí)行會(huì)跳轉(zhuǎn)到 SVC 向量(請(qǐng)參閱《開發(fā)指南》中的第 6 章 處理處理器異常)。
處理器會(huì)忽略 immed。 但異常處理程序會(huì)獲取它,借以確定所請(qǐng)求的服務(wù)。
Note
作為 ARM 匯編語言開發(fā)成果的一部分,SWI 指令已重命名為 SVC。 在此版本的 RVCT 中,SWI 指令反匯編為 SVC,并提供注釋以指明這是以前的 SWI。
條件標(biāo)記
此指令不更改標(biāo)記。
體系結(jié)構(gòu)
此 ARM 指令可用于所有版本的 ARM 體系結(jié)構(gòu)。
而軟中斷對(duì)應(yīng)的處理函數(shù)為_osExceptSwiHdl,即PC寄存器將跳到_osExceptSwiHdl執(zhí)行
5. _osExceptSwiHdl
@ Description: Software interrupt exception handler
_osExceptSwiHdl: @軟中斷異常處理
@保存任務(wù)上下文(TaskContext) 開始... 一定要對(duì)照TaskContext來理解
SUB SP, SP, #(4 * 16) @先申請(qǐng)16個(gè)??臻g用于處理本次軟中斷
STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA從左到右執(zhí)行,先放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)為用戶模式時(shí),獲取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值 => TaskContext.regPSR
ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存儲(chǔ)位置
STMFD R3!, {R4} @ Save the CPSR and r15(pc) 保存LR寄存器 => TaskContext.PC
STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 從右向左 保存 => TaskContext.LR和SP
SUB SP, SP, #4 @ => TaskContext.resved
PUSH_FPU_REGS R1 @保存中斷模式(用戶模式模式)
@保存任務(wù)上下文(TaskContext) 結(jié)束
MOV FP, #0 @ Init frame pointer
CPSIE I @開中斷,表明在系統(tǒng)調(diào)用期間可響應(yīng)中斷
BLX OsArmA32SyscallHandle /*交給C語言處理系統(tǒng)調(diào)用,參數(shù)為R0,指向TaskContext的開始位置*/
CPSID I @執(zhí)行后續(xù)指令前必須先關(guān)中斷
@恢復(fù)任務(wù)上下文(TaskContext) 開始
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 切回用戶模式運(yùn)行
@恢復(fù)任務(wù)上下文(TaskContext) 結(jié)束
OsKernelSVCHandler:@主要目的是保存ExcContext中除(R0~R12)的其他寄存器
ADD R0, SP, #(4 * 16) @跳轉(zhuǎn)到保存PC,LR,SP的位置,此時(shí)R0位置剛好是SP的位置
MOV R5, R0 @由R5記錄SP位置,因?yàn)镽0要暫時(shí)充當(dāng)SP寄存器來使用
STMFD R0!, {R4} @ Store PC => ExcContext.PC
STMFD R0!, {R4} @ 相當(dāng)于保存了=> ExcContext.LR
STMFD R0!, {R5} @ 相當(dāng)于保存了=> ExcContext.SP
STMFD SP!, {R3} @ Push task`s CPSR (i.e. exception SPSR). =>ExcContext.regPSR
SUB SP, SP, #(4 * 2) @ user sp and lr => =>ExcContext.USP,ULR
MOV R0, #OS_EXCEPT_SWI @ Set exception ID to OS_EXCEPT_SWI.
@ 設(shè)置異常ID為軟中斷
B _osExceptionSwi @ Branch to global exception handler.
@ 跳到全局異常處理
解讀
運(yùn)行到此處,已經(jīng)切到SVC的棧運(yùn)行,所以先保存上一個(gè)模式的現(xiàn)場(chǎng)
獲取中斷模式,軟中斷的來源可不一定是用戶模式,完全有可能是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
/* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */ /****************************************************************** 由匯編調(diào)用,見于 los_hw_exc.s / BLX OsArmA32SyscallHandle SYSCALL是產(chǎn)生系統(tǒng)調(diào)用時(shí)觸發(fā)的信號(hào),R7寄存器存放具體的系統(tǒng)調(diào)用ID,也叫系統(tǒng)調(diào)用號(hào) regs:參數(shù)就是所有寄存器 注意:本函數(shù)在用戶態(tài)和內(nèi)核態(tài)下都可能被調(diào)用到 //MOV R0, SP @獲取SP值,R0將作為OsArmA32SyscallHandle的參數(shù) ******************************************************************/ LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器記錄了觸發(fā)了具體哪個(gè)系統(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) {//收到 __NR_sigreturn 信號(hào) OsRestorSignalContext(regs);//恢復(fù)信號(hào)上下文 return regs; } handle = g_syscallHandle[cmd];//拿到系統(tǒng)調(diào)用的注冊(cè)函數(shù),類似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//獲取參數(shù)個(gè)數(shù) if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系統(tǒng)調(diào)用必須有參數(shù)且參數(shù)不能大于8個(gè) 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)用號(hào)的原因 switch (nArgs) {//參數(shù)的個(gè)數(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://如何是兩個(gè)參數(shù)的系統(tǒng)調(diào)用,這里傳三個(gè)參數(shù)也沒有問題,因被調(diào)用函數(shù)不會(huì)去取用R2值 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個(gè)參數(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);//保存信號(hào)上下文現(xiàn)場(chǎng) /* 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對(duì)應(yīng)的就是R0~Rn
R7保存的是系統(tǒng)調(diào)用號(hào),R0~R3保存的是SysMqOpen的四個(gè)參數(shù)
g_syscallHandle[cmd]就能查詢到SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)注冊(cè)時(shí)對(duì)應(yīng)的SysMqOpen函數(shù)
*(SyscallFun5)handle此時(shí)就是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);//一個(gè)消息隊(duì)列可以有多個(gè)進(jìn)程向它讀寫消息
if (ret == -1) {
return (mqd_t)-get_errno();
}
return ret;
}
解讀
此處的mq_open和main函數(shù)的mq_open其實(shí)是兩個(gè)函數(shù)體實(shí)現(xiàn).一個(gè)是給應(yīng)用層的調(diào)用,一個(gè)是內(nèi)核層使用,只是名字一樣而已.
SysMqOpen是返回到OsArmA32SyscallHandleregs[REG_R0] = ret;
OsArmA32SyscallHandle再返回到_osExceptSwiHdl
_osExceptSwiHdl后面的代碼是用于恢復(fù)用戶模式現(xiàn)場(chǎng)和SPSR,PC等寄存器.
以上為鴻蒙系統(tǒng)調(diào)用的整個(gè)過程.
編輯:hfy
-
鴻蒙系統(tǒng)
+關(guān)注
關(guān)注
183文章
2642瀏覽量
69314
發(fā)布評(píng)論請(qǐng)先 登錄
湘鄉(xiāng)市工程BIM系統(tǒng)全過程建筑咨詢軟件
講述PCB設(shè)計(jì)全過程操作
CPU制造全過程
水利工程全過程造價(jià)管理關(guān)鍵因素分析
雙系統(tǒng)XP、win7 雙系統(tǒng)安裝全過程圖解
DIY安裝導(dǎo)航+可視倒車+行車記錄+GPS跟蹤全過程
鴻蒙系統(tǒng)逐步跟蹤系統(tǒng)調(diào)用實(shí)現(xiàn)全過程解析
醫(yī)療廢物全過程在線監(jiān)測(cè)系統(tǒng)讓醫(yī)廢監(jiān)管智慧化
用C語言開發(fā)DSP系統(tǒng)全過程
用C語言開發(fā)DSP系統(tǒng)的全過程講解

鴻蒙系統(tǒng)逐步跟蹤系統(tǒng)調(diào)用實(shí)現(xiàn)全過程
評(píng)論