鸿蒙内核源码分析(系统调用篇) | 一撸到底,全面解剖系统调用实现过程 | 中文注解HarmonyOS源码 | v37.02
百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >
本篇说清楚系统调用
读本篇之前建议先读鸿蒙内核源码分析(总目录)工作模式篇.
本篇通过一张图和七段代码详细说明系统调用的整个过程,代码一捅到底,直到汇编层再也捅不下去. 先看图,这里的模式可以理解为空间,因为模式不同运行的栈空间就不一样.
过程解读
- 在应用层
main
中使用系统调用mq_open
(posix标准接口) mq_open
被封装在库中,这里直接看库里的代码.mq_open
中调用syscall
,将参数传给寄出器R7,R0~R6
SVC 0
完成用户模式到内核模式(SVC)的切换_osExceptSwiHdl
运行在svc模式下.- PC寄存器直接指向
_osExceptSwiHdl
处取指令. _osExceptSwiHdl
是汇编代码,先保存用户模式现场(R0~R12寄存器),并调用OsArmA32SyscallHandle
完成系统调用OsArmA32SyscallHandle
中通过系统调用号(保存在R7寄存器)查询对应的注册函数SYS_mq_open
SYS_mq_open
是本次系统调用的实现函数,完成后return回到OsArmA32SyscallHandle
OsArmA32SyscallHandle
再return回到_osExceptSwiHdl
_osExceptSwiHdl
恢复用户模式现场(R0~R12寄存器)- 从内核模式(SVC)切回到用户模式,PC寄存器也切回用户现场.
- 由此完成整个系统调用全过程
七段追踪代码,逐个分析
1.应用程序 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 发起系统调用
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
是真正的系统调用函数,对应一个系统调用号__NR_mq_open
,通过宏SYSCALL_HAND_DE
F将SysMqOpen
注册到g_syscallHandle
中.
static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系统调用入口函数注册 static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系统调用对应的参数数量 #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
为注册函数的参数个数,也会一块记录下来.- 四个参数为 SYS_mq_open的四个参数,后续将保存在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个参数 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; //系统调用号 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)) }
解读
- 可变参数实现所有系统调用的参数的管理,可以看出,在鸿蒙内核中系统调用的参数最多不能大于6个
- R7寄存器保存了系统调用号,R0~R5保存具体每个参数
- 可变参数的具体实现后续有其余篇幅详细介绍,敬请关注.
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 @开机代码 b _osExceptUndefInstrHdl @异常处理之CPU碰到不认识的指令 b _osExceptSwiHdl @异常处理之:软中断 b _osExceptPrefetchAbortHdl @异常处理之:取指异常 b _osExceptDataAbortHdl @异常处理之:数据异常 b _osExceptAddrAbortHdl @异常处理之:地址异常 b OsIrqHandler @异常处理之:硬中断 b _osExceptFiqHdl @异常处理之:快中断
解读
svc
全称是 SuperVisor Call,完成工作模式的切换.不管之前是7个模式中的哪个模式,统一都切到SVC管理模式.- 而软中断对应的处理函数为
_osExceptSwiHdl
,即PC寄存器将跳到_osExceptSwiHdl
执行
5. _osExceptSwiHdl
@ Description: Software interrupt exception handler _osExceptSwiHdl: @软中断异常处理 SUB SP, SP, #(4 * 16) @先申请16个栈空间用于处理本次软中断 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 非用户模式下跳转 @ 当为用户模式时,获取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的参数 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 @开中断,表明在系统调用期间可响应中断 BLX OsArmA32SyscallHandle /*交给C语言处理系统调用*/ CPSID I @执行后续指令前必须先关中断 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 恢复该模式下的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} @恢复R0-R12寄存器 LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢复用户模式的R13/R14寄存器 ADD SP, SP, #(2 * 4) @定位到保存旧PC值的位置 LDMFD SP!, {PC}^ @ Return to user 切回用户模式运行
解读
- 运行到此处,已经切到SVC的栈运行,所以先保存上一个模式的现场
- 获取中断模式,软中断的来源可不一定是用户模式,完全有可能是SVC本身,比如系统调用中又发生系统调用.就变成了从SVC模式切到SVC的模式
MOV R0, SP
;sp将作为参数传递给OsArmA32SyscallHandle
- 调用
OsArmA32SyscallHandle
这是所有系统调用的统一入口 - 注意看
OsArmA32SyscallHandle
的参数UINT32 *regs
6. OsArmA32SyscallHandle
LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs) { UINT32 ret; UINT8 nArgs; UINTPTR handle; UINT32 cmd = regs[REG_R7];//C7寄存器记录了触发了具体哪个系统调用 if (cmd >= SYS_CALL_NUM) {//系统调用的总数 PRINT_ERR("Syscall ID: error %d !!!\n", cmd); return regs; } if (cmd == __NR_sigreturn) {//此时运行在内核栈,程序返回的调用,从内核态返回用户态时触发 OsRestorSignalContext(regs);//恢复信号上下文,执行完函数后,切到了用户栈 return regs; } handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,类似 SysRead nArgs = g_syscallNArgs[cmd / NARG_PER_BYTE]; /* 4bit per nargs */ nArgs = (cmd & 1) ? (nArgs >> NARG_BITS) : (nArgs & NARG_MASK);//获取参数个数 if ((handle == 0) || (nArgs > ARG_NUM_7)) {//系统调用必须有参数且参数不能大于8个 PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs); regs[REG_R0] = -ENOSYS; return regs; } //regs[0-6] 记录系统调用的参数,这也是由R7寄存器保存系统调用号的原因 switch (nArgs) {//参数的个数 case ARG_NUM_0: case ARG_NUM_1: ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname); break; case ARG_NUM_2://@note_thinking 如何是两个参数的系统调用,这里传的确是三个参数,任务栈中会出现怎样的情况呢? 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个参数的情况 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保存系统调用返回值 OsSaveSignalContext(regs);//保存用户栈现场 /* 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;//返回寄存器的值 }
解读
- 参数是
regs
对应的就是R0~Rn - R7保存的是系统调用号,R0~R3保存的是
SysMqOpen
的四个参数 g_syscallHandle[cmd]
就能查询到SYSCALL_HAND_DEF(__NR_mq_open, SysMqOpen, mqd_t, ARG_NUM_4)
注册时对应的SysMqOpen
函数*(SyscallFun5)handle
此时就是SysMqOpen
- 注意看 SysMqOpen 的参数是最开始的
main
函数中的mqdes = mq_open(mqname, O_CREAT | O_RDWR, S_IRUSR | S_IWUSR, &attr);
由此完成了真正系统调用的过程
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);//一个消息队列可以有多个进程向它读写消息 if (ret == -1) { return (mqd_t)-get_errno(); } return ret; }
解读
- 此处的
mq_open
和main函数的mq_open
其实是两个函数体实现.一个是给应用层的调用,一个是内核层使用,只是名字一样而已. SysMqOpen
是返回到OsArmA32SyscallHandle
regs[REG_R0] = ret;
OsArmA32SyscallHandle
再返回到_osExceptSwiHdl
_osExceptSwiHdl
后面的代码是用于恢复用户模式现场和SPSR
,PC
等寄存器.
以上为鸿蒙系统调用的整个过程.
关于寄存器(R0~R15)在每种模式下的使用方式,后续将由其他篇详细说明,敬请关注.
喜欢就大方 点赞+关注+收藏 吧
各大站点搜 "鸿蒙内核源码分析" ,快速找到组织.
百万汉字注解 >> 精读内核源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding >
百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,多站点每日同步更新< oschina | csdn | weharmony >

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
RBAC模型整合数据权限
在项目实际开发中我们不光要控制一个用户能访问哪些资源,还需要控制用户只能访问资源中的某部分数据。 控制一个用户能访问哪些资源我们有很成熟的权限管理模型即RBAC,但是控制用户只能访问某部分资源(即我们常说的数据权限)使用RBAC模型是不够的,本文我们尝试在RBAC模型的基础上融入数据权限的管理控制。 首先让我们先看下RBAC模型。 RBAC模型 RBAC是Role-BasedAccess Control的英文缩写,意思是基于角色的访问控制。 RBAC事先会在系统中定义出不同的角色,不同的角色拥有不同的权限,一个角色实际上就是一组权限的集合。而系统的所有用户都会被分配到不同的角色中,一个用户可能拥有多个角色。使用RBAC可以极大地简化权限的管理。 RBAC模型还可以细分为RBAC0,RBAC1,RBAC2,RBAC3。这里我们不讨论他们之间的差异,感兴趣的同学可以自行研究,我们主要聚焦于常见的RBAC0模型上。 如下图就是一个经典RBAC0模型的数据库设计。 RBAC0 在RBAC模型下,系统只会验证用户A是否属于角色RoleX,而不会判断用户A是否能访问只属于用户B的数据DataB。这...
- 下一篇
.NET Core中的Worker Service
当你想到ASP.NET Core时,可能会想到Web应用程序后端代码,包括MVC和WebAPI。MVC视图和Razor页面还允许使用后端代码生成带有HTML元素的前端UI。全新的Blazor更进一步,允许使用WebAssembly在Web浏览器中运行客户端.NET代码。最后,我们现在有了一个Worker Service应用程序的模板。 这是在ASP.NET Core早期预览中引入的。虽然项目模板最初列在Web模板下,但此后在向导中重新定位了一个级别。这是在.NET Core中创建长时间运行的跨平台服务的好方法。本文介绍Windows操作系统。 Worker Service项目 在VisualStudio 2019中创建新的Worker Service项目的最快方法是使用最新模板。还可以使用适当的DotNetCLI命令。 启动VisualStudio并选择Worker Service模板,如下所示: 要使用命令行,使用以下命令,-o是一个可选标志,用于为项目提供输出文件夹名: dotnet new worker -o myproject 程序和后台服务 Program.cs类包含Main...
相关文章
文章评论
共有0条评论来说两句吧...