本篇说清楚系统调用

读本篇之前建议先读鸿蒙内核源码分析(总目录)工作模式篇.

本篇通过一张图和七段代码详细说明系统调用的整个过程,代码一捅到底,直到汇编层再也捅不下去. 先看图,这里的模式可以理解为空间,因为模式不同运行的栈空间就不一样.

过程解读

● 在应用层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

intmain(void)

{

charmqname[NAMESIZE], msgrv1[BUFFER], msgrv2[BUFFER];

const char*msgptr1 ="test message1";

const char*msgptr2 ="test message2 with differnet length";

mqd_t mqdes;

intprio1 = 1, prio2 = 2;

struct timespec ts;

struct mq_attr attr;

intunresolved = 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");

returnPTS_PASS;

}

2. mq_open 发起系统调用

mqd_t mq_open(constchar*name,intflags, ...)

{

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

}

returnsyscall(SYS_mq_open,name, flags, mode, attr);

}

解读

● SYS_mq_open 是真正的系统调用函数,对应一个系统调用号__NR_mq_open,通过宏SYSCALL_HAND_DEF将SysMqOpen注册到g_syscallHandle中.

staticUINTPTR g_syscallHandle[SYS_CALL_NUM] = {0}; //系统调用入口函数注册

staticUINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};//保存系统调用对应的参数数量

#define SYSCALL_HAND_DEF(id, fun, rType, nArg)                                             \

if ((id)

g_syscallHandle[(id)] = (UINTPTR)(fun);                                            \

g_syscallNArgs[(id) / NARG_PER_BYTE] |= ((id) & 1) ? (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));

}

staticinline 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"); \

returnx0; \

} 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

ANDR1, R3, #CPSR_MASK_MODE                          @ Interrupted mode 获取中断模式

CMP     R1, #CPSR_USER_MODE                              @ Usermode    是否为用户模式

BNE     OsKernelSVCHandler                               @ Branch if notusermode 非用户模式下跳转

@ 当为用户模式时,获取SP和LR寄出去值

@ we enter fromusermode, we need get thevaluesofUSERmode r13(sp)andr14(lr).

@ stmia with^ willreturntheusermode registers (provided that r15isnotinthe register list).

MOV     R0, SP                                           @获取SP值,R0将作为OsArmA32SyscallHandle的参数

STMFD   SP!, {R3}                                        @ Save the CPSR 入栈保存CPSR值

ADDR3, SP, #(4 * 17)                                @ Offsettopc/cpsr storage 跳到PC/CPSR存储位置

STMFD   R3!, {R4}                                        @ Save the CPSR andr15(pc) 保存LR寄存器

STMFD   R3, {R13, R14}^                                  @ Save usermode r13(sp)andr14(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

ADDSP, SP,#4                                        @ 定位到保存旧SPSR值的位置

LDMFD   SP!, {R3}                                        @ FetchthereturnSPSR 弹出旧SPSR值

MSR     SPSR_cxsf, R3                                    @ Setthereturnmode SPSR 恢复该模式下的SPSR值

@ we are leaving tousermode, we needtorestore thevaluesofUSERmode r13(sp)andr14(lr).

@ ldmia with^ willreturntheusermode registers (provided that r15isnotinthe register list)

LDMFD   SP!, {R0-R12}                                    @恢复R0-R12寄存器

LDMFD   SP, {R13, R14}^                                  @ Restore usermode R13/R14 恢复用户模式的R13/R14寄存器

ADDSP, SP, #(2 * 4)                                 @定位到保存旧PC值的位置

LDMFD   SP!, {PC}^                                       @ Returntouser切回用户模式运行

解读

● 运行到此处,已经切到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];// R7寄存器记录了系统调用号

if (cmd >= SYS_CALL_NUM) {//系统调用的总数

PRINT_ERR("Syscall ID: error %d !!!\n", cmd);

returnregs;

}

if (cmd == __NR_sigreturn) {//此时运行在内核栈,程序返回的调用,从内核态返回用户态时触发

OsRestorSignalContext(regs);//恢复信号上下文,执行完函数后,切到了用户栈

returnregs;

}

handle = g_syscallHandle[cmd];//拿到系统调用的注册函数,即 SYS_mq_open

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;

returnregs;

}

//regs[0-6] 记录系统调用的参数,这也是由 R7 寄存器保存系统调用号的原因

switch (nArgs) {//参数的个数

caseARG_NUM_0:

caseARG_NUM_1:

ret = (*(SyscallFun1)handle)(regs[REG_R0]);//执行系统调用,类似 SysUnlink(pathname);

break;

caseARG_NUM_2://@note_thinking 如何是两个参数的系统调用,这里传的确是三个参数,任务栈中会出现怎样的情况呢?

caseARG_NUM_3:

ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//类似 SysExecve(fileName, argv, envp);

break;

caseARG_NUM_4:

caseARG_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);//保存用户栈现场

/* Returnthelastvalueofcurent_regs.  This supports context switchesonreturnfromthe exception.

* That capability isonlyusedwiththeSYS_context_switch system call.

*/

returnregs;//返回寄存器的值

}

解读

● 参数是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(constchar*mqName,intopenFlag, mode_t mode, struct mq_attr *attr)

{

mqd_t ret;

intretValue;

charkMqName[PATH_MAX + 1] = { 0 };

retValue = LOS_StrncpyFromUser(kMqName, mqName, PATH_MAX);

if (retValue

returnretValue;

}

ret = mq_open(kMqName, openFlag, mode, attr);//一个消息队列可以有多个进程向它读写消息

if (ret == -1) {

return(mqd_t)-get_errno();

}

returnret;

}

解读

● 此处的mq_open和main函数的mq_open其实是两个函数体实现.一个是给应用层的调用,一个是内核层使用,只是名字一样而已.

● SysMqOpen是返回到 OsArmA32SyscallHandle regs[REG_R0] = ret;

● OsArmA32SyscallHandle再返回到 _osExceptSwiHdl

● _osExceptSwiHdl后面的代码是用于恢复用户模式现场和SPSR,PC 等寄存器.

以上为鸿蒙系统调用的整个过程.

关于寄存器(R0~R15)在每种模式下的使用方式,寄存器篇中已详细说明,请前往查看.

【编辑推荐】

【责任编辑:jianghua TEL:(010)68476606】

点赞 0

鸿蒙系统源代码解析,鸿蒙内核源码分析(系统调用篇) | 图解系统调用全貌相关推荐

  1. 鸿蒙内核代码 行,鸿蒙内核源码分析(CPU篇) | 内核是如何描述CPU的 ? | 祝新的一年牛气冲天 ! | v36.01...

    本篇说清楚CPU 读本篇之前建议先读鸿蒙内核源码分析(总目录)进程/线程篇.指令是稳定的,但指令序列是变化的,只有这样计算机才能够实现用计算来解决一切问题这个目标.计算是稳定的,但计算的数据是多变的, ...

  2. 鸿蒙内核 cpu兼容,鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 祝新的一年牛气冲天 ! | v32.04...

    本篇说清楚CPU 读本篇之前建议先读鸿蒙内核源码分析(总目录)进程/线程篇. 指令是稳定的,但指令序列是变化的,只有这样计算机才能够实现用计算来解决一切问题这个目标.计算是稳定的,但计算的数据是多变的 ...

  3. 鸿蒙内核源码分析表,鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 ? | 开篇致敬鸿蒙内核开发者 | v1.10...

    谁是鸿蒙内核最重要的结构体? 答案一定是: LOS_DL_LIST(双向链表),它长这样.typedef struct LOS_DL_LIST {//双向链表,内核最重要结构体 struct LOS_ ...

  4. 高通Q888内核源码分析--概述篇

    基于小米开源代码Linux Kernel5.4.61, github地址为https://github.com/MiCode/Xiaomi_Kernel_OpenSource.git star-r-o ...

  5. 鸿蒙内核源码分析系列 | 读懂HarmonyOS内核源代码!

    本系列从HarmonyOS架构层视角整理成文, 并用生活场景及讲故事的方式试图去解构内核,一窥究竟.帮助你读懂并快速理解鸿蒙操作系统源码. 1.鸿蒙内核源码分析(调度机制篇) 2.鸿蒙内核源码分析(进 ...

  6. v06.03 鸿蒙内核源码分析(调度队列) | 内核调度也需要排队 | 百篇博客分析HarmonyOS源码

    子曰:"君子食无求饱,居无求安,敏于事而慎于言,就有道而正焉,可谓好学也已."<论语>:学而篇 百篇博客系列篇.本篇为: v06.xx 鸿蒙内核源码分析(调度队列篇) ...

  7. v45.05 鸿蒙内核源码分析(Fork) | 一次调用 两次返回 | 百篇博客分析HarmonyOS源码

    孔子于乡党,恂恂如也,似不能言者.其在宗庙朝廷,便便言,唯谨尔. <论语>:乡党篇 百篇博客系列篇.本篇为: v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用 两次返回 进程管理 ...

  8. v34.04 鸿蒙内核源码分析(原子操作) | 谁在为完整性保驾护航 | 百篇博客分析HarmonyOS源码

    子曰:"吾未见好德如好色者也." <论语>:子罕篇 百篇博客系列篇.本篇为: v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为完整性保驾护航 基础工具相关篇为: ...

  9. v69.04 鸿蒙内核源码分析(文件句柄) | 你为什么叫句柄 | 百篇博客分析OpenHarmony源码

    子曰:"诵诗三百,授之以政,不达:使于四方,不能专对:虽多,亦奚以为?" <论语>:子路篇 百篇博客系列篇.本篇为: v69.xx 鸿蒙内核源码分析(文件句柄篇) | ...

最新文章

  1. 回归ASP,学习ADO,熟悉VBScript
  2. oracle数据库的高可用r,Oracle高可用之dataguard
  3. Revising Aggregations - The Sum Function(集合函数-sum)
  4. 机器学习之过拟合与欠拟合以及偏差-方差分解
  5. 后端:414 Request-URI Too Large解决方案
  6. 中国大学MOOC-陈越、何钦铭-数据结构-2019春期末考试(题目+部分解答)
  7. 我的2013年总结以及未来7年预算
  8. html搜索框代码_解放双手 | 10行Python代码实现一款网页自动化工具
  9. 《软件需求十步走》阅读笔记一
  10. 电视盒子_刷机固件_免费合集分享
  11. 安卓手机抓包方法归纳总结
  12. 一种由视频和音频共同驱动的说话人脸合成方法简介
  13. CSDN日报190221——被裁的第50天,我终于拿到心仪公司Offer
  14. 论文阅读:Permutation Matters: Anisotropic Convolutional Layer for Learning on Point Clouds
  15. Qt实现 基于ffmpeg拉流播放视频
  16. 长尾理论,长尾示意图,读书笔记
  17. URLConnection HttpURLConnection 网络请求
  18. XML文件简介和解析
  19. 【CV】SiamFC:用于目标跟踪的全卷积孪生网络
  20. linux5 vnc,centos 5.5 配置vnc,开启linux远程桌面教程(完整正确版)

热门文章

  1. STM32+Air202+Air530+HXDZ-30102-ACC心率血氧GPS采集上传到阿里云
  2. 51单片机与STM32的区别(为何51单片机IO引脚的驱动能力弱)
  3. 十款浏览器插件,让你拥有更好的浏览器体验
  4. 操作系统:磁盘的移臂调度算法
  5. 阿里云最新最全扩容方法
  6. hive笔记(与上一偏 《hadoop集群搭建》结合)
  7. MAC上安装Ubantu双系统
  8. 集线器、路由器、交换机区别
  9. paper 94:视觉领域博客资源1之中国部分
  10. vtk中画几何图形存储为vtk文件并在窗口显示