0
  • 聊天消息
  • 系统消息
  • 评论与回复
登录后你可以
  • 下载海量资料
  • 学习在线课程
  • 观看技术视频
  • 写文章/发帖/加入社区
会员中心
创作中心

完善资料让更多小伙伴认识你,还能领取20积分哦,立即完善>

3天内不再提示

openharmony源码静态分析

姚小熊27 来源:整合自博客园 作者:整合自博客园 2021-06-23 15:24 次阅读

放原子开源基金会(简称“基金会”)于 2020 年 9 月接受华为捐赠的智能终端操作系统基础能力相关代码,随后进行开源,并根据命名规则为该开源项目命名为 OpenAtom OpenHarmony(简称“OpenHarmony”)。

OpenHarmony是自主研发、不兼容安卓的全领域下一代开源操作系统。OpenHarmony内核主要包括进程和线程调度、内存管理、IPC机制、timer管理等内核基本功能。

#ifndef __scc

#define __scc(X) ((long) (X)) // 转为long类型

typedef long syscall_arg_t;

#endif

#define __syscall1(n,a) __syscall1(n,__scc(a))

#define __syscall2(n,a,b) __syscall2(n,__scc(a),__scc(b))

#define __syscall3(n,a,b,c) __syscall3(n,__scc(a),__scc(b),__scc(c)) //

继续搜索发现有多出匹配,我们关注arch/arm目录下的文件,因为ARM Cortext A7是Armv7-A指令集的32位CPU(如果是Armv8-A指令集的64位CPU则对应arch/aarch64下的文件):

static inline long __syscall3(long n, long a, long b, long c)

{

register long r7 __ASM____R7__ = n;

register long r0 __asm__(“r0”) = a;

register long r1 __asm__(“r1”) = b;

register long r2 __asm__(“r2”) = c;

__asm_syscall(R7_OPERAND, “0”(r0), “r”(r1), “r”(r2));

}

这段代码中还有三个宏,__ASM____R7__、__asm_syscall和R7_OPERAND:

#ifdef __thumb__

#define __ASM____R7__

#define __asm_syscall(。。.) do { \

__asm__ __volatile__ ( “mov %1,r7 ; mov r7,%2 ; svc 0 ; mov r7,%1” \

: “=r”(r0), “=&r”((int){0}) : __VA_ARGS__ : “memory”); \

return r0; \

} while (0)

#else // __thumb__

#define __ASM____R7__ __asm__(“r7”)

#define __asm_syscall(。。.) do { \

__asm__ __volatile__ ( “svc 0” \

: “=r”(r0) : __VA_ARGS__ : “memory”); \

return r0; \

} while (0)

#endif // __thumb__

#ifdef __thumb2__

#define R7_OPERAND “rI”(r7)

#else

#define R7_OPERAND “r”(r7)

#endif

它们有两个实现版,分别对应于编译器THUMB选项的开启和关闭。这两种选项条件下的代码流程基本一致,以下仅以未开启THUMB选项为例进行分析。这两个宏展开后的__syscall3函数内容为:

static inline long __syscall3(long n, long a, long b, long c)

{

register long r7 __asm__(“r7”) = n; // 系统调用号

register long r0 __asm__(“r0”) = a; // 参数0

register long r1 __asm__(“r1”) = b; // 参数1

register long r2 __asm__(“r2”) = c; // 参数2

do { \

__asm__ __volatile__ ( “svc 0” \

: “=r”(r0) : “r”(r7), “0”(r0), “r”(r1), “r”(r2) : “memory”); \

return r0; \

} while (0);

}

这里最后的一个内嵌汇编比较复杂,它符合如下格式(具体细节可以查阅gcc内嵌汇编文档的扩展汇编说明):

asm asm-qualifiers ( AssemblerTemplate

: OutputOperands

[ : InputOperands

[ : Clobbers ] ])

汇编模板为:“svc 0”, 输出参数部分为:“=r”(r0),输出寄存器为r0输入参数部分为:“r”(r7), “0”(r0), “r”(r1), “r”(r2),输入寄存器为r7,r0,r1,r2,(“0”的含义是,这个输入寄存器必须和输出寄存器第0个位置一样) Clobber部分为:“memory”

这里我们只需要记住:系统调用号存放在r7寄存器,参数存放在r0,r1,r2,返回值最终会存放在r0中;

SVC指令,ARM Cortex A7手册 的解释为:

The SVC instruction causes a Supervisor Call exception. This provides a mechanism for unprivileged software to make a call to the operating system, or other system component that is accessible only at PL1.

翻译过来就是说

SVC指令会触发一个“特权调用”异常。这为非特权软件调用操作系统或其他只能在PL1级别访问的系统组件提供了一种机制。

详细的指令说明在

到这里,我们分析了鸿蒙系统上应用程序如何进入内核态,主要分析的是musl libc的实现。

liteos-a内核的系统调用实现分析

既然SVC能够触发一个异常,那么我们就要看看liteos-a内核是如何处理这个异常的。

ARM Cortex A7中断向量表

在ARM架构参考手册中,可以找到中断向量表的说明:

f4fa71d9e8dfffc1b1e7c3efcb4001e0.png

可以看到SVC中断向量的便宜地址是0x08,我们可以在kernel/liteos_a/arch/arm/arm/src/startup目录的reset_vector_mp.S文件和reset_vector_up.S文件中找到相关汇编代码:

__exception_handlers:

/*

*Assumption: ROM code has these vectors at the hardware reset address.

*A simple jump removes any address-space dependencies [i.e. safer]

*/

b reset_vector

b _osExceptUndefInstrHdl

b _osExceptSwiHdl

b _osExceptPrefetchAbortHdl

b _osExceptDataAbortHdl

b _osExceptAddrAbortHdl

b OsIrqHandler

b _osExceptFiqHdl

PS:kernel/liteos_a/arch/arm/arm/src/startup目录有两个文件reset_vector_mp.S文件和reset_vector_up.S文件分别对应多核和单核编译选项:

ifeq ($(LOSCFG_KERNEL_SMP), y)

LOCAL_SRCS += src/startup/reset_vector_mp.S

else

LOCAL_SRCS += src/startup/reset_vector_up.S

endif

SVC中断处理函数

上面的汇编代码中可以看到,_osExceptSwiHdl函数就是SVC异常处理函数,具体实现在kernel/liteos_a/arch/arm/arm/src/los_hw_exc.S文件中:

@ Description: Software interrupt exception handler

_osExceptSwiHdl:

SUB SP, SP, #(4 * 16) @ 栈增长

STMIA SP, {R0-R12} @ 保存R0-R12寄存器到栈上

MRS R3, SPSR @ 移动SPSR寄存器的值到R3

MOV R4, LR

AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode

CMP R1, #CPSR_USER_MODE @ User mode

BNE OsKernelSVCHandler @ Branch if not user mode

@ 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

STMFD SP!, {R3} @ Save the CPSR

ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage

STMFD R3!, {R4} @ Save the CPSR and r15(pc)

STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr)

SUB SP, SP, #4

PUSH_FPU_REGS R1

MOV FP, #0 @ Init frame pointer

CPSIE I @ Interrupt Enable

BLX OsArmA32SyscallHandle

CPSID I @ Interrupt Disable

POP_FPU_REGS R1

ADD SP, SP,#4

LDMFD SP!, {R3} @ Fetch the return SPSR

MSR SPSR_cxsf, R3 @ Set the return mode 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}

LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14

ADD SP, SP, #(2 * 4)

LDMFD SP!, {PC}^ @ Return to user

这段代码的注释较为清楚,可以看到,内核模式会继续调用OsKernelSVCHandler,用户模式会继续调用OsArmA32SyscallHandle函数;

OsArmA32SyscallHandle函数

我们这里分析的流程是从用户模式进入的,所以调用的是OsArmA32SyscallHandle,它的实现位于kernel/liteos_a/syscall/los_syscall.c文件:

/* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */

LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)

{

UINT32 ret;

UINT8 nArgs;

UINTPTR handle;

UINT32 cmd = regs[REG_R7];

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]; // 得到实际系统调用处理函数

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

PRINT_ERR(“Unsupport syscall ID: %d nArgs: %d\n”, cmd, nArgs);

regs[REG_R0] = -ENOSYS;

return regs;

}

switch (nArgs) { // 以下各个case是实际函数调用

case ARG_NUM_0:

case ARG_NUM_1:

ret = (*(SyscallFun1)handle)(regs[REG_R0]);

break;

case ARG_NUM_2:

case ARG_NUM_3:

ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);

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:

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;

}

这个函数中用到了个全局数组g_syscallHandle和g_syscallNArgs,它们的定义以及初始化函数也在同一个文件中:

static UINTPTR g_syscallHandle[SYS_CALL_NUM] = {0};

static UINT8 g_syscallNArgs[(SYS_CALL_NUM + 1) / NARG_PER_BYTE] = {0};

void SyscallHandleInit(void)

{

#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宏的对齐格式我做了一点调整。

从g_syscallNArgs成员赋值以及定义的地方,能看出它的每个UINT8成员被用来存放两个系统调用的参数个数,从而实现更少的内存占用;

syscall_lookup.h文件和los_syscall.c位于同一目录,它记录了系统调用函数对照表,我们仅节取一部分:

SYSCALL_HAND_DEF(__NR_read, SysRead, ssize_t, ARG_NUM_3)

SYSCALL_HAND_DEF(__NR_write, SysWrite, ssize_t, ARG_NUM_3) //

看到这里,write系统调用的内核函数终于找到了——SysWrite。

到此,我们已经知道了liteos-a的系统调用机制是如何实现的。

liteos-a内核SysWrite的实现

SysWrite函数的实现位于kernel/liteos_a/syscall/fs_syscall.c文件:

ssize_t SysWrite(int fd, const void *buf, size_t nbytes)

{

int ret;

if (nbytes == 0) {

return 0;

}

if (!LOS_IsUserAddressRange((vaddr_t)(UINTPTR)buf, nbytes)) {

return -EFAULT;

}

/* Process fd convert to system global fd */

fd = GetAssociatedSystemFd(fd);

ret = write(fd, buf, nbytes); //

它又调用了write?但是这一次是内核空间的write,不再是 musl libc,经过一番搜索,我们可以找到另一个文件third_party/NuttX/fs/vfs/fs_write.c中的write:

ssize_t write(int fd, FAR const void *buf, size_t nbytes) {

#if CONFIG_NFILE_DESCRIPTORS 》 0

FAR struct file *filep;

if ((unsigned int)fd 》= CONFIG_NFILE_DESCRIPTORS)

#endif

{ /* Write to a socket descriptor is equivalent to send with flags == 0 */

#if defined(LOSCFG_NET_LWIP_SACK)

FAR const void *bufbak = buf;

ssize_t ret;

if (LOS_IsUserAddress((VADDR_T)(uintptr_t)buf)) {

if (buf != NULL && nbytes 》 0) {

buf = malloc(nbytes);

if (buf == NULL) { /* 省略 错误处理 代码 */ }

if (LOS_ArchCopyFromUser((void*)buf, bufbak, nbytes) != 0) {/* 省略 */}

}

}

ret = send(fd, buf, nbytes, 0); // 这个分支是处理socket fd的

if (buf != bufbak) {

free((void*)buf);

}

return ret;

#else

set_errno(EBADF);

return VFS_ERROR;

#endif

}

#if CONFIG_NFILE_DESCRIPTORS 》 0

/* The descriptor is in the right range to be a file descriptor.。。 write

* to the file.

*/

if (fd 《= STDERR_FILENO && fd 》= STDIN_FILENO) { /* fd : [0,2] */

fd = ConsoleUpdateFd();

if (fd 《 0) {

set_errno(EBADF);

return VFS_ERROR;

}

}

int ret = fs_getfilep(fd, &filep);

if (ret 《 0) {

/* The errno value has already been set */

return VFS_ERROR;

}

if (filep-》f_oflags & O_DIRECTORY) {

set_errno(EBADF);

return VFS_ERROR;

}

if (filep-》f_oflags & O_APPEND) {

if (file_seek64(filep, 0, SEEK_END) == -1) {

return VFS_ERROR;

}

}

/* Perform the write operation using the file descriptor as an index */

return file_write(filep, buf, nbytes);

#endif

}

找到这段代码,我们知道了:

liteos-a的vfs是在NuttX基础上实现的,NuttX是一个开源RTOS项目;

liteos-a的TCP/IP协议栈是基于lwip的,lwip也是一个开源项目;

这段代码中的write分为两个分支,socket fd调用lwip的send,另一个分支调用file_write;

至于,file_write如何调用到存储设备驱动程序,则是更底层的实现了,本文不在继续分析。

补充说明

本文内容均是基于鸿蒙系统开源项目OpenHarmony源码静态分析所整理,没有进行实际的运行环境调试,实际执行过程可能有所差异,希望发现错误的读者及时指正。文中所有路径均为整个openharmony源码树上的相对路径(而非liteos源码相对路径)。

责任编辑:YYX

声明:本文内容及配图由入驻作者撰写或者入驻合作网站授权转载。文章观点仅代表作者本人,不代表电子发烧友网立场。文章及其配图仅供工程师学习之用,如有内容侵权或者其他违规问题,请联系本站处理。 举报投诉
  • 开源
    +关注

    关注

    3

    文章

    3210

    浏览量

    42293
  • 鸿蒙系统
    +关注

    关注

    183

    文章

    2633

    浏览量

    66122
  • OpenHarmony
    +关注

    关注

    25

    文章

    3629

    浏览量

    16031
收藏 人收藏

    评论

    相关推荐

    【开源鸿蒙】使用QEMU运行OpenHarmony轻量系统

    本文将会介绍如何从源码安装QEMU 6.2.0,以及如何使用QEMU运行OpenHarmony轻量系统。通过本文,你将会对QEMU和OpenHarmony轻量系统又一个初步的认知,并对如何使用QEMU又一个初步的理解和体会。
    的头像 发表于 09-14 08:51 373次阅读
    【开源鸿蒙】使用QEMU运行<b class='flag-5'>OpenHarmony</b>轻量系统

    鸿蒙OpenHarmony南向/北向快速开发教程-迅为RK3568开发板

    优化开发流程-配置远程访问环境 P8_优化开发流程-编译源码和烧写镜像 P9_OpenHarmony源码目录介绍 P10_整体移植方案介绍 P11_编译目标分析 P12_编译框架基本概
    发表于 07-23 10:44

    开源鸿蒙 编译OpenHarmony轻量系统QEMU RISC-V版本

    本文将介绍如何为QEMU RISC-V虚拟平台构建OpenHarmony轻量系统。得益于QEMU的CPU指令集模拟执行能力,该方法可以在没有开发板的情况下调试和运行OpenHarmony系统源码。本文介绍的该方法,可以用于
    的头像 发表于 07-15 10:36 936次阅读
    开源鸿蒙 编译<b class='flag-5'>OpenHarmony</b>轻量系统QEMU RISC-V版本

    OpenHarmony之开机优化

    一丶环境信息 源码版本:OpenHarmony-4.1-Release 板子型号:dayu200(RK3568) 二丶Bootchart工具 在开机优化时,我们需要借助Bootchart工具,当前
    发表于 07-01 16:39

    如何在OpenHarmony设置静态IP?

    介绍本文适用于所有RK3566/RK3568/RK3588平台产品在OpenHarmony系统上设置静态IP。本文以PurplePiOH开发板为例,在OpenHarmony系统上进行设置。触觉智能
    的头像 发表于 05-12 08:32 580次阅读
    如何在<b class='flag-5'>OpenHarmony</b>设置<b class='flag-5'>静态</b>IP?

    鸿蒙OpenHarmony【标准系统 编译】(基于RK3568开发板)

    OpenHarmony支持hb和build.sh两种编译方式。此处介绍hb方式,build.sh脚本编译方式请参考[使用build.sh脚本编译源码]。
    的头像 发表于 05-08 17:37 843次阅读
    鸿蒙<b class='flag-5'>OpenHarmony</b>【标准系统 编译】(基于RK3568开发板)

    HarmonyOS开发:【基于命令行(获取源码)】

    在Ubuntu环境下通过以下步骤获取OpenHarmony源码
    的头像 发表于 04-25 22:08 355次阅读
    HarmonyOS开发:【基于命令行(获取<b class='flag-5'>源码</b>)】

    鸿蒙OpenHarmony【创建工程并获取源码

    在通过DevEco Device Tool创建OpenHarmony工程时,可自动下载相应版本的OpenHarmony源码
    的头像 发表于 04-19 21:40 324次阅读
    鸿蒙<b class='flag-5'>OpenHarmony</b>【创建工程并获取<b class='flag-5'>源码</b>】

    鸿蒙OpenHarmony【搭建Ubuntu环境】

    在嵌入式开发中,很多开发者习惯于使用Windows进行代码的编辑,比如使用Windows的Visual Studio Code进行OpenHarmony代码的开发。但当前阶段,大部分的开发板源码还不
    的头像 发表于 04-19 16:53 1219次阅读
    鸿蒙<b class='flag-5'>OpenHarmony</b>【搭建Ubuntu环境】

    鸿蒙OpenHarmony【搭建Windows环境】

    在嵌入式开发中,很多开发者习惯于使用Windows进行代码的编辑,比如使用Windows的Visual Studio Code进行OpenHarmony代码的开发。但当前阶段,大部分的开发板源码还不
    的头像 发表于 04-19 15:42 556次阅读
    鸿蒙<b class='flag-5'>OpenHarmony</b>【搭建Windows环境】

    OpenHarmony开发学习:【源码下载和编译】

    本文介绍了如何下载鸿蒙系统源码,如何一次性配置可以编译三个目标平台(`Hi3516`,`Hi3518`和`Hi3861`)的编译环境,以及如何将源码编译为三个目标平台的二进制文件。
    的头像 发表于 04-14 09:36 844次阅读
    <b class='flag-5'>OpenHarmony</b>开发学习:【<b class='flag-5'>源码</b>下载和编译】

    OpenHarmony内核编程实战

    编程入门[Hello,OpenHarmony]在正式开始之前,对于刚接触OpenHarmony的伙伴们,面对大篇幅的源码可能无从下手,不知道怎么去编码写程序,下面用一个简单的例子带伙伴们入门。▍任务
    的头像 发表于 03-27 08:31 684次阅读
    <b class='flag-5'>OpenHarmony</b>内核编程实战

    鸿蒙开发学习:【OpenHarmony HAR】

    OpenHarmony js/ts三方库使用的是OpenHarmony静态共享包,即HAR(Harmony Archive),可以包含js/ts代码、c++库、资源和配置文件。通过HAR,可以实现
    的头像 发表于 03-18 16:27 649次阅读

    报名启动|OpenHarmony源码转换器—多线程特性转换赛题

    点击蓝字 ╳ 关注我们 开源项目 OpenHarmony 是每个人的 OpenHarmony 原文标题:报名启动|OpenHarmony源码转换器—多线程特性转换赛题 文章出处:【微信
    的头像 发表于 12-29 16:15 639次阅读
    报名启动|<b class='flag-5'>OpenHarmony</b><b class='flag-5'>源码</b>转换器—多线程特性转换赛题

    epoll源码分析

    个函数进行源码分析源码来源 由于epoll的实现内嵌在内核中,直接查看内核源码的话会有一些无关代码影响阅读。为此在GitHub上写的简化版TCP/IP协议栈,里面实现了epoll逻
    的头像 发表于 11-13 11:49 951次阅读
    epoll<b class='flag-5'>源码</b><b class='flag-5'>分析</b>