OpenHarmony设备开发 小型系统内核(LiteOS-A) 扩展组件
版本:V3.2Beta
系统调用
基本概念
OpenHarmony LiteOS-A实现了用户态与内核态的区分隔离,用户态程序不能直接访问内核资源,而系统调用则为用户态程序提供了一种访问内核资源、与内核进行交互的通道。
运行机制
如图1所示,用户程序通过调用System API(系统API,通常是系统提供的POSIX接口)进行内核资源访问与交互请求,POSIX接口内部会触发SVC/SWI异常,完成系统从用户态到内核态的切换,然后对接到内核的Syscall Handler(系统调用统一处理接口)进行参数解析,最终分发至具体的内核处理函数。
图1 系统调用示意图
Syscall Handler的具体实现在kernel/liteos_a/syscall/los_syscall.c中OsArmA32SyscallHandle函数,在进入系统软中断异常时会调用此函数,并且按照kernel/liteos_a/syscall/syscall_lookup.h中的清单进行系统调用的入参解析,执行各系统调用最终对应的内核处理函数。
说明:
- 系统调用提供基础的用户态程序与内核的交互功能,不建议开发者直接使用系统调用接口,推荐使用内核提供的对外POSIX接口,若需要新增系统调用接口,详见开发指导。
- 内核向用户态提供的系统调用接口清单详见kernel/liteos_a/syscall/syscall_lookup.h,内核相应的系统调用对接函数清单详见kernel/liteos_a/syscall/los_syscall.h。
开发指导
开发流程
新增系统调用的典型开发流程如下:
- 在LibC库中确定并添加新增的系统调用号。
- 在LibC库中新增用户态的函数接口声明及实现。
- 在内核系统调用头文件中确定并添加新增的系统调用号及对应内核处理函数的声明。
- 在内核中新增该系统调用对应的内核处理函数。
编程实例
示例代码:
- 在LibC库syscall.h.in中新增系统调用号 如下所示,其中__NR_new_syscall_sample为新增系统调用号:
...
/* 当前现有的系统调用清单 */
/* OHOS customized syscalls, not compatible with ARM EABI */
#define __NR_OHOS_BEGIN 500
#define __NR_pthread_set_detach (__NR_OHOS_BEGIN + 0)
#define __NR_pthread_join (__NR_OHOS_BEGIN + 1)
#define __NR_pthread_deatch (__NR_OHOS_BEGIN + 2)
#define __NR_create_user_thread (__NR_OHOS_BEGIN + 3)
#define __NR_processcreate (__NR_OHOS_BEGIN + 4)
#define __NR_processtart (__NR_OHOS_BEGIN + 5)
#define __NR_printf (__NR_OHOS_BEGIN + 6)
#define __NR_dumpmemory (__NR_OHOS_BEGIN + 13)
#define __NR_mkfifo (__NR_OHOS_BEGIN + 14)
#define __NR_mqclose (__NR_OHOS_BEGIN + 15)
#define __NR_realpath (__NR_OHOS_BEGIN + 16)
#define __NR_format (__NR_OHOS_BEGIN + 17)
#define __NR_shellexec (__NR_OHOS_BEGIN + 18)
#define __NR_ohoscapget (__NR_OHOS_BEGIN + 19)
#define __NR_ohoscapset (__NR_OHOS_BEGIN + 20)
#define __NR_new_syscall_sample (__NR_OHOS_BEGIN + 21) /* 新增的系统调用号 __NR_new_syscall_sample:521 */
#define __NR_syscallend (__NR_OHOS_BEGIN + 22)
...
2.在LibC库中新增用户态接口的声明与实现
#include "stdio_impl.h"
#include "syscall.h"
...
/* 新增系统调用用户态的接口实现 */
void newSyscallSample(int num)
{
printf("user mode: num = %d\n", num);
__syscall(SYS_new_syscall_sample, num);
return;
}
3.在内核系统调用头文件中新增系统调用号 如下所示,在third_party/musl/porting/liteos_a/kernel/include/bits/syscall.h文件中,__NR_new_syscall_sample为新增系统调用号。
...
/* 当前现有的系统调用清单 */
/* OHOS customized syscalls, not compatible with ARM EABI */
#define __NR_OHOS_BEGIN 500
#define __NR_pthread_set_detach (__NR_OHOS_BEGIN + 0)
#define __NR_pthread_join (__NR_OHOS_BEGIN + 1)
#define __NR_pthread_deatch (__NR_OHOS_BEGIN + 2)
#define __NR_create_user_thread (__NR_OHOS_BEGIN + 3)
#define __NR_processcreate (__NR_OHOS_BEGIN + 4)
#define __NR_processtart (__NR_OHOS_BEGIN + 5)
#define __NR_printf (__NR_OHOS_BEGIN + 6)
#define __NR_dumpmemory (__NR_OHOS_BEGIN + 13)
#define __NR_mkfifo (__NR_OHOS_BEGIN + 14)
#define __NR_mqclose (__NR_OHOS_BEGIN + 15)
#define __NR_realpath (__NR_OHOS_BEGIN + 16)
#define __NR_format (__NR_OHOS_BEGIN + 17)
#define __NR_shellexec (__NR_OHOS_BEGIN + 18)
#define __NR_ohoscapget (__NR_OHOS_BEGIN + 19)
#define __NR_ohoscapset (__NR_OHOS_BEGIN + 20)
#define __NR_new_syscall_sample (__NR_OHOS_BEGIN + 21) /* 新增的系统调用号 __NR_new_syscall_sample:521 */
#define __NR_syscallend (__NR_OHOS_BEGIN + 22)
...
在kernel/liteos_a/syscall/syscall_lookup.h中,增加一行SYSCALL_HAND_DEF(__NR_new_syscall_sample, SysNewSyscallSample, void, ARG_NUM_1):
...
/* 当前现有的系统调用清单 */
SYSCALL_HAND_DEF(__NR_chown, SysChown, int, ARG_NUM_3)
SYSCALL_HAND_DEF(__NR_chown32, SysChown, int, ARG_NUM_3)
#ifdef LOSCFG_SECURITY_CAPABILITY
SYSCALL_HAND_DEF(__NR_ohoscapget, SysCapGet, UINT32, ARG_NUM_2)
SYSCALL_HAND_DEF(__NR_ohoscapset, SysCapSet, UINT32, ARG_NUM_1)
#endif
/* 新增系统调用 */
SYSCALL_HAND_DEF(__NR_new_syscall_sample, SysNewSyscallSample, void, ARG_NUM_1)
...
4.在内核中新增内核该系统调用对应的处理函数 如下所示,在kernel/liteos_a/syscall/los_syscall.h中,SysNewSyscallSample为新增系统调用的内核处理函数声明:
...
/* 当前现有的系统调用内核处理函数声明清单 */
extern int SysClockSettime64(clockid_t clockID, const struct timespec64 *tp);
extern int SysClockGettime64(clockid_t clockID, struct timespec64 *tp);
extern int SysClockGetres64(clockid_t clockID, struct timespec64 *tp);
extern int SysClockNanoSleep64(clockid_t clk, int flags, const struct timespec64 *req, struct timespec64 *rem);
extern int SysTimerGettime64(timer_t timerID, struct itimerspec64 *value);
extern int SysTimerSettime64(timer_t timerID, int flags, const struct itimerspec64 *value, struct itimerspec64 *oldValue);
/* 新增的系统调用内核处理函数声明 */
extern void SysNewSyscallSample(int num);
...
新增的系统调用的内核处理函数实现如下:
include "los_printf.h"
...
/* 新增系统调用内核处理函数的实现 */
void SysNewSyscallSample(int num)
{
PRINTK("kernel mode: num = %d\n", num);
return;
}
结果验证:
用户态程序调用newSyscallSample(10)接口,得到输出结果如下:
/* 用户态接口与内核态接口均有输出,证明系统调用已使能 */
user mode: num = 10
kernel mode: num = 10
动态加载与链接
基本概念
OpenHarmony系统的动态加载与链接机制主要是由内核加载器以及动态链接器构成,内核加载器用于加载应用程序以及动态链接器,动态链接器用于加载应用程序所依赖的共享库,并对应用程序和共享库进行符号重定位。与静态链接相比,动态链接是将应用程序与动态库推迟到运行时再进行链接的一种机制。
动态链接的优势:
- 多个应用程序可以共享一份代码,最小加载单元为页,相对静态链接可以节约磁盘和内存空间。
- 共享库升级时,理论上将旧版本的共享库覆盖即可(共享库中的接口向下兼容),无需重新链接。
- 加载地址可以进行随机化处理,防止攻击,保证安全性。
运行机制
图1 动态加载流程
- 内核将应用程序ELF文件的PT_LOAD段信息映射至进程空间。对于ET_EXEC类型的文件,根据PT_LOAD段中p_vaddr进行固定地址映射;对于ET_DYN类型(位置无关的可执行程序,通过编译选项“-fPIE”得到)的文件,内核通过mmap接口选择base基址进行映射(load_addr = base + p_vaddr)。
- 若应用程序是静态链接的(静态链接不支持编译选项“-fPIE”),设置堆栈信息后跳转至应用程序ELF文件中e_entry指定的地址并运行;若程序是动态链接的,应用程序ELF文件中会有PT_INTERP段,保存动态链接器的路径信息(ET_DYN类型)。musl的动态链接器是libc-musl.so的一部分,libc-musl.so的入口即动态链接器的入口。内核通过mmap接口选择base基址进行映射,设置堆栈信息后跳转至base + e_entry(该e_entry为动态链接器的入口)地址并运行动态链接器。
- 动态链接器自举并查找应用程序依赖的所有共享库并对导入符号进行重定位,最后跳转至应用程序的e_entry(或base + e_entry),开始运行应用程序。
图2 程序执行流程
- 加载器与链接器调用mmap映射PT_LOAD段。
- 内核调用map_pages接口查找并映射pagecache已有的缓存。
- 程序执行时,虚拟内存区间若无具体的物理内存做映射,系统将触发缺页中断,将elf文件内容读入物理内存,并将该内存块加入pagecache。
- 将已读入文件内容的物理内存与虚拟地址区间做映射。
- 程序继续执行。
开发指导
接口说明
表1 内核加载器模块接口
功能分类 | 接口名称 | 描述 |
模块初始化 | LOS_DoExecveFile | 根据输入的参数执行指定的用户程序 |
开发流程
LOS_DoExecveFile接口一般由用户通过exec家族函数利用系统调用机制创建新的进程,内核不能直接调用该接口启动新进程。
虚拟动态共享库
基本概念
VDSO(Virtual Dynamic Shared Object,虚拟动态共享库)相对于普通的动态共享库,区别在于其so文件不保存在文件系统中,存在于系统镜像中,由内核在运行时确定并提供给应用程序,故称为虚拟动态共享库。
OpenHarmony系统通过VDSO机制实现上层用户态程序可以快速读取内核相关数据的一种通道方法,可用于实现部分系统调用的加速,也可用于实现非系统敏感数据(硬件配置、软件配置)的快速读取。
运行机制
VDSO其核心思想就是内核看护一段内存,并将这段内存映射(只读)进用户态应用程序的地址空间,应用程序通过链接vdso.so后,将某些系统调用替换为直接读取这段已映射的内存从而避免系统调用达到加速的效果。
VDSO总体可分为数据页与代码页两部分:
- 数据页提供内核映射给用户进程的内核时数据;
- 代码页提供屏蔽系统调用的主要逻辑;图1VDSO系统设计
如图1所示,当前VDSO机制有以下几个主要步骤:
① 内核初始化时进行VDSO数据页的创建;
② 内核初始化时进行VDSO代码页的创建;
③ 根据系统时钟中断不断将内核一些数据刷新进VDSO的数据页;
④ 用户进程创建时将代码页映射进用户空间;
⑤ 用户程序在动态链接时对VDSO的符号进行绑定;
⑥ 当用户程序进行特定系统调用时(例如clock_gettime(CLOCK_REALTIME_COARSE, &ts)),VDSO代码页会将其拦截;
⑦ VDSO代码页将正常系统调用转为直接读取映射好的VDSO数据页;
⑧ 从VDSO数据页中将数据传回VDSO代码页;
⑨ 将从VDSO数据页获取到的数据作为结果返回给用户程序;
说明:
- 当前VDSO机制支持LibC库clock_gettime接口的CLOCK_REALTIME_COARSE与CLOCK_MONOTONIC_COARSE功能,clock_gettime接口的使用方法详见POSIX标准。
- 用户调用C库接口clock_gettime(CLOCK_REALTIME_COARSE, &ts)或者clock_gettime(CLOCK_MONOTONIC_COARSE, &ts)即可使用VDSO机制。
- 使用VDSO机制得到的时间精度会与系统tick中断的精度保持一致,适用于对时间没有高精度要求且短时间内会高频触发clock_gettime或gettimeofday系统调用的场景,若有高精度要求,不建议采用VDSO机制。
轻量级进程间通信
基本概念
LiteIPC是OpenHarmony LiteOS-A内核提供的一种新型IPC(Inter-Process Communication,即进程间通信)机制,不同于传统的System V IPC机制,LiteIPC主要是为RPC(Remote Procedure Call,即远程过程调用)而设计的,而且是通过设备文件的方式对上层提供接口的,而非传统的API函数方式。
LiteIPC中有两个主要概念,一个是ServiceManager,另一个是Service。整个系统只能有一个ServiceManager,而Service可以有多个。
ServiceManager有两个主要功能:
- 一是负责Service的注册和注销,
- 二是负责管理Service的访问权限(只有有权限的任务(Task)可以向对应的Service发送IPC消息)。
运行机制
首先将需要接收IPC消息的任务通过ServiceManager注册成为一个Service,然后通过ServiceManager为该Service任务配置访问权限,即指定哪些任务可以向该Service任务发送IPC消息。
LiteIPC的核心思想就是在内核态为每个Service任务维护一个IPC消息队列,该消息队列通过LiteIPC设备文件向上层用户态程序分别提供代表收取IPC消息的读操作和代表发送IPC消息的写操作。
开发指导
接口说明
表1 LiteIPC模块接口(仅LiteOS-A内部使用)
功能分类 | 接口描述 |
模块初始化 | OsLiteIpcInit:初始化LiteIPC模块 |
IPC消息内存池 | - LiteIpcPoolInit:初始化进程的IPC消息内存池 - LiteIpcPoolReInit:重新初始化进程的IPC消息内存池 - LiteIpcPoolDelete:释放进程的IPC消息内存池 |
Service管理 | LiteIpcRemoveServiceHandle:删除指定的Service |
说明:
LiteIPC模块接口都只在LiteOS-A内部使用。
文章转载自:https://docs.openharmony.cn/pages/v3.2Beta/zh-cn/device-dev/kernel/kernel-small-bundles-system.md/