放原子开源基金会(简称“基金会”)于 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架构参考手册中,可以找到中断向量表的说明:
可以看到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源码相对路径)。
关于HLS IP无法编译解决方案
瑞晶预计提前转进30纳米制程
科氏力质量流量计安装要求
金属镀层在汽车连接器中的作用
PHY6230—高性价比的低功耗高性能蓝牙5.2芯片
openharmony源码静态分析
霍金:应从研究提升AI能力转移到最大化的社会效益上面
短期内NAND闪存需求的不如人意 并不影响市场的长期向好态势
HDMI标准闲谈:VGA接口是高清吗?
Fraunhofer IIS 荣获2012年NAB创新大奖
2023 AI芯片行业报告
区块链底层技术创新 培养区块链人才
数字电路和模拟电路的工作各有何特点?
怎样彻底关掉手机通知栏的通知
断路器失灵保护时间定值如何整定?
简述LED车灯七大优势
如何成长为一名Java高级程序员
AMD将在11月6日公布产品技术路线图
智能电视普及率达89%_彩电行业面临的新一轮洗牌
智慧港口解决方案:数据传输满足智慧远程操作