本文对鸿蒙OS内核进行了简单的分析,涉及鸿蒙OS内核的架构,鸿蒙OS内核的三大核心模块(进程管理,虚拟内存,文件系统)。
首先,作者先阐述几个关键的名词。
操作系统(Operating System): 操作系统的功能是负责管理各种硬件设备,同时对底层硬件设备进行抽象,为上层软件提供高效的系统接口。操作系统设计和实现的优劣直接决定了系统栈的各个方面,比如性能,可靠性,甚至安全性。操作系统的设计实现是计算机系统研究最古老最困难的方向之一。因为底层设备的复杂性,操作系统实现的代码量巨大。从系统架构上可以将操作系统分为三类:宏内核(Monolithic Kernel)操作系统,微内核(Microkernel)操作系统,外内核(Exokernel)操作系统。
内核(kernel): 几乎所有的处理器厂商都将处理器的执行状态分为了两个级别:特权级(priviledge)和非特权级(non-priviledge)。处理器只有在特权级时才可以执行一些特权级的指令和访问特殊的寄存器。特权级和非特权级的划分是为了防止不可信的用户和应用程序破坏系统的状态和数据。特权级又称为ring 0。在特权级执行的代码称为内核。所以,内核实际上是一段在处理器特权级执行的代码。只不过代码量越写越大,最后变成了软件。
操作系统与内核的关系。狭义上讲,操作系统指的就是内核。比如Linux内核,所有的操作系统代码都在核心态执行,这样的操作系统称为宏内核操作系统。但是随着操作系统技术的不断发展,操作系统本身的架构也在不断的演进。一些操作系统为了追求架构设计的简洁和系统的安全性,将部分操作系统模块的代码从内核态剥离出来,放到用户态执行(比如Minix操作系统)。这样内核只包括了少量的核心功能模块,在极大的程度上简化了内核的设计和实现。而剥离到用户态的操作系统代码可以以服务进程的方式去运行,或者以库的形式存在(Library OS),链接到其他的内核上去。
鸿蒙OS内核包括了liteos-a内核和liteos-m内核。liteos-m内核是最早面向物联网设备开发的,内存要求小于128MB。而liteos-a针对的是资源较为丰富的嵌入式设备,内存可以达到4GB。本文分析的是liteos-a内核。
鸿蒙OS内核的架构
鸿蒙OS内核最主要的特性之一就是微内核的操作系统内核架构设计。那么如何分析操作系统设计采用的内核架构呢?一个简单直接的方法就是观察操作系统内核接口的设计。操作系统内核接口又称为系统调用(System Call)。操作系统内核接口的设计体现了操作系统内核本身设计的复杂度。比如Linux内核作为宏内核的代表,系统调用数量在325个。几乎全部的操作系统功能实现都在内核中。Windows内核的系统调用数量据说有上千个。而微内核的代表的操作系统内核Minix,最早其系统调用的数量仅仅为2个。内核对上层软件只提供了消息传递的服务功能。下面通过鸿蒙OS内核的系统调用实现来简单分析一下鸿蒙OS内核的架构。
鸿蒙OS运行库采用了第三方的musl libc库。在musl libc库中,系统调用的实现是应用程序在寄存器中设置好系统调用号和系统调用参数后,执行svc指令陷入到内核,如下面代码所示:
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);
}
liteos-a内核在函数OsArmA32SyscallHandle中处理这个中断。函数OsArmA32SyscallHandle根据系统调用号执行预先设置好的系统调用服务例程。
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 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;
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;
}
liteos-a内核中的全部系统调用都定义在文件syscall/los_syscall.h中。通过观察这个文件可以发现liteos-a类似于Linux内核,定义了大量的系统调用,并且这些系统调用全部都实现在内核态当中。
syscall/los_syscall.h
extern unsigned int SysGetGroupId(void);
extern unsigned int SysGetTid(void);
extern void SysSchedYield(int type);
extern int SysSchedGetScheduler(int id, int flag);
extern int SysSchedSetScheduler(int id, int policy, int prio, int flag);
extern int SysSchedGetParam(int id, int flag);
extern int SysSchedSetParam(int id, unsigned int prio, int flag);
extern int SysSetProcessPriority(int which, int who, unsigned int prio);
extern int SysGetProcessPriority(int which, int who);
extern int SysSchedGetPriorityMin(int policy);
....
相比于传统的微内核,liteos-a内核稍显复杂。微内核最主要的代表Minix操作系统,其系统接口设计简洁到只有两个系统调用:发送消息send和接收消息receive。任何的系统调用请求都用消息封装好,然后在用户态的服务进程之间互相传递。内核只提供了三个基本的功能:时钟中断,进程调度和消息传递。
笔者统计了一下鸿蒙OS内核的代码量,如下表所示:
liteos-a内核
NuttX虚拟文件系统
lwIP网络协议栈
69265
16056
89485
其中liteos-a内核将近7万行,NuttX虚拟文件系统1.6万行左右,lwIP网络协议栈近9万行。这十几万行的代码包含了操作系统最核心的几个功能模块(进程管理,虚拟内存,文件系统,网络传输),这些功能模块全部运行在内核态。目前liteos-a内核的实现在宏内核和微内核之间更偏向于宏内核。或许后期鸿蒙OS还会对其内核架构有进一步的调整。
鸿蒙OS内核还有另外一个重要特点:安全性。鸿蒙OS内核的安全性可以通过其内核架构来保证。微内核架构因为其较小的内核设计实现,相比于宏内核庞大的功能实现,其大大减少了攻击面的大小。另外,微内核的架构也可以方便后期系统整体的形式化验证。鸿蒙OS内可能会借助于形式化技术来保证其内核设计实现的正确性。目前被验证系统的代码量与形式化技术验证软件系统需要的代码量比例大概在1:20左右。即一行c语言代码需要二十行形式化验证代码去验证。微内核简单的内核设计和极小的内核代码量无疑会大大减轻后期操作系统形式化验证的代码量。目前鸿蒙OS内核在开发时代码注释写的非常规范。如下图所示,猜测也是为了后期系统形式化验证时接口规约的方便。注释比代码长,囧。
/**
* @ingroup los_sys
* @brief Obtain the number of cycles in one second.
*
* @par Description:
* This API is used to obtain the number of cycles in one second.
* @attention
*
*
None*
*
* @param None
*
* @retval UINT32 Number of cycles obtained in one second.
* @par Dependency:
*
- los_sys.h: the header file that contains the API declaration.
* @see None
*/
extern UINT32 LOS_CyclePerTickGet(VOID);
总结一下鸿蒙OS内核架构的设计:
优点: 微内核的设计简化了操作系统内核设计实现的复杂度,提高了系统的安全性和可靠性,减轻了后期形式化验证的工作量,:)。
缺点: 目前liteos-a内核的设计实现更偏向于宏内核而不是微内核,囧z。
鸿蒙OS内核的进程管理
liteos-a内核同时支持了进程和线程的实现。进程描述符为LosProcessCB,线程描述符为LosTaskCB。
进程描述符LosProcessCB包含了进程的所有信息:进程号,进程调度状态,进程信号状态,虚拟内存信息,文件系统状态,以及进程的权能信息。所有的进程描述符LosProcessCB的指针保存在一个全局的数组g_processCBArray中。通过processID进行索引,可以获得对应的LosProcessCB结构。系统中的所有进程通过childrenList,exitChildList,siblingList组织成一个树状的结构。capability字段是进程的权能信息,用来实现主体对客体资源的访问控制。
typedef struct ProcessCB {
CHAR processName[OS_PCB_NAME_LEN]; /**< Process name */
UINT32 processID; /**< process ID = leader thread ID */
UINT16 processStatus; /**< [15:4] process Status; [3:0] The number of threads currently
running in the process */
UINT16 priority; /**< process priority */
UINT16 policy; /**< process policy */
UINT16 timeSlice; /**< Remaining time slice */
UINT16 consoleID; /**< The console id of task belongs */
UINT16 processMode; /**< Kernel Mode:0; User Mode:1; */
UINT32 parentProcessID; /**< Parent process ID */
UINT32 exitCode; /**< process exit status */
LOS_DL_LIST pendList; /**< Block list to which the process belongs */
LOS_DL_LIST childrenList; /**< my children process list */
LOS_DL_LIST exitChildList; /**< my exit children process list */
LOS_DL_LIST siblingList; /**< linkage in my parent's children list */
ProcessGroup *group; /**< Process group to which a process belongs */
LOS_DL_LIST subordinateGroupList; /**< linkage in my group list */
UINT32 threadGroupID; /**< Which thread group , is the main thread ID of the process */
UINT32 threadScheduleMap; /**< The scheduling bitmap table for the thread group of the
process */
LOS_DL_LIST threadSiblingList; /**< List of threads under this process */
LOS_DL_LIST threadPriQueueList[OS_PRIORITY_QUEUE_NUM]; /**< The process's thread group schedules the
priority hash table */
volatile UINT32 threadNumber; /**< Number of threads alive under this process */
UINT32 threadCount; /**< Total number of threads created under this process */
LOS_DL_LIST waitList; /**< The process holds the waitLits to support wait/waitpid */
#if (LOSCFG_KERNEL_SMP == YES)
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */
#endif
UINTPTR sigHandler; /**< signal handler */
sigset_t sigShare; /**< signal share bit */
#if (LOSCFG_KERNEL_LITEIPC == YES)
ProcIpcInfo ipcInfo; /**< memory pool for lite ipc */
#endif
LosVmSpace *vmSpace; /**< VMM space for processes */
#ifdef LOSCFG_FS_VFS
struct files_struct *files; /**< Files held by the process */
#endif
timer_t timerID; /**< iTimer */
#ifdef LOSCFG_SECURITY_CAPABILITY
User *user;
UINT32 capability;
#endif
#ifdef LOSCFG_SECURITY_VID
TimerIdMap timerIdMap;
#endif
#ifdef LOSCFG_DRIVERS_TZDRIVER
struct file *execFile; /**< Exec bin of the process */
#endif
mode_t umask;
} LosProcessCB;
与进程管理相关的系统调用包括fork,exit,wait等。这里看一下fork系统调用的实现。liteos-a内核中fork系统调用由SysFork函数实现,而SysFork函数主要由OsCopyProcess函数实现。
STATIC INT32 OsCopyProcess(UINT32 flags, const CHAR *name, UINTPTR sp, UINT32 size)
{
UINT32 intSave, ret, processID;
LosProcessCB *run = OsCurrProcessGet();
/* 分配一个新的进程描述符 */
LosProcessCB *child = OsGetFreePCB();
if (child == NULL) {
return -LOS_EAGAIN;
}
processID = child->processID;
/* 初始化进程描述符 */
ret = OsForkInitPCB(flags, child, name, sp, size);
if (ret != LOS_OK) {
goto ERROR_INIT;
}
/* 将父进程进程描述符中的信息拷贝到子进程描述符中,包括虚拟内存,文件,信号,权能等 */
ret = OsCopyProcessResources(flags, child, run);
if (ret != LOS_OK) {
goto ERROR_TASK;
}
/* 设置新进程的状态并加入到调度队列中,等待被调度运行 */
ret = OsChildSetProcessGroupAndSched(child, run);
if (ret != LOS_OK) {
goto ERROR_TASK;
}
LOS_MpSchedule(OS_MP_CPU_ALL);
if (OS_SCHEDULER_ACTIVE) {
LOS_Schedule();
}
return processID;
ERROR_TASK:
SCHEDULER_LOCK(intSave);
(VOID)OsTaskDeleteUnsafe(OS_TCB_FROM_TID(child->threadGroupID), OS_PRO_EXIT_OK, intSave);
ERROR_INIT:
OsDeInitPCB(child);
return -ret;
}
同一个进程的所有线程共享内核中的状态,比如打开文件,地址空间等。liteos-a中所有同一个进程的所有线程共享一个LosProcessCB结构体,进程描述符中的threadSiblingList表示该进程所有的线程。线程描述符为LosTaskCB中processID字段表示所属的进程。LosTaskCB结构其余的字段表示线程的信息,比如线程栈,线程运行的处理器等信息。
typedef struct {
VOID *stackPointer; /**< Task stack pointer */
UINT16 taskStatus; /**< Task status */
UINT16 priority; /**< Task priority */
UINT16 policy;
UINT16 timeSlice; /**< Remaining time slice */
UINT32 stackSize; /**< Task stack size */
UINTPTR topOfStack; /**< Task stack top */
UINT32 taskID; /**< Task ID */
TSK_ENTRY_FUNC taskEntry; /**< Task entrance function */
VOID *joinRetval; /**< pthread adaption */
VOID *taskSem; /**< Task-held semaphore */
VOID *taskMux; /**< Task-held mutex */
VOID *taskEvent; /**< Task-held event */
UINTPTR args[4]; /**< Parameter, of which the maximum number is 4 */
CHAR taskName[OS_TCB_NAME_LEN]; /**< Task name */
LOS_DL_LIST pendList; /**< Task pend node */
LOS_DL_LIST threadList; /**< thread list */
SortLinkList sortList; /**< Task sortlink node */
UINT32 eventMask; /**< Event mask */
UINT32 eventMode; /**< Event mode */
UINT32 priBitMap; /**< BitMap for recording the change of task priority,
the priority can not be greater than 31 */
INT32 errorNo; /**< Error Num */
UINT32 signal; /**< Task signal */
sig_cb sig;
#if (LOSCFG_KERNEL_SMP == YES)
UINT16 currCpu; /**< CPU core number of this task is running on */
UINT16 lastCpu; /**< CPU core number of this task is running on last time */
UINT16 cpuAffiMask; /**< CPU affinity mask, support up to 16 cores */
UINT32 timerCpu; /**< CPU core number of this task is delayed or pended */
#if (LOSCFG_KERNEL_SMP_TASK_SYNC == YES)
UINT32 syncSignal; /**< Synchronization for signal handling */
#endif
#if (LOSCFG_KERNEL_SMP_LOCKDEP == YES)
LockDep lockDep;
#endif
#if (LOSCFG_KERNEL_SCHED_STATISTICS == YES)
SchedStat schedStat; /**< Schedule statistics */
#endif
#endif
UINTPTR userArea;
UINTPTR userMapBase;
UINT32 userMapSize; /**< user thread stack size ,real size : userMapSize + USER_STACK_MIN_SIZE */
UINT32 processID; /**< Which belong process */
FutexNode futex;
LOS_DL_LIST joinList; /**< join list */
LOS_DL_LIST lockList; /**< Hold the lock list */
UINT32 waitID; /**< Wait for the PID or GID of the child process */
UINT16 waitFlag; /**< The type of child process that is waiting, belonging to a group or parent,
a specific child process, or any child process */
#if (LOSCFG_KERNEL_LITEIPC == YES)
UINT32 ipcStatus;
LOS_DL_LIST msgListHead;
BOOL accessMap[LOSCFG_BASE_CORE_TSK_LIMIT];
#endif
} LosTaskCB;
在鸿蒙OS的运行库中的pthread_create函数里,可以发现在创建线程时调用了__thread_clone函数。而__thread_clone函数则调用了SYS_creat_user_thread系统调用。代码路径third_party_musl/src/thread/pthread_create.c
int __pthread_create(pthread_t *restrict res, const pthread_attr_t *restrict attrp, void *(*entry)(void *), void *restrict arg)
{
...
ret = __thread_clone((c11 ? start_c11 : start), flags, new, stack);
...
}
int __thread_clone(int (*func)(void *), int flags, struct pthread *thread, unsigned char *sp)
{
int ret;
bool join_flag = false;
struct user_param param;
if (thread->detach_state == DT_JOINABLE) {
join_flag = true;
}
param.user_area = TP_ADJ(thread);
param.user_sp = sp;
param.map_base = thread->map_base;
param.map_size = thread->map_size;
ret = __syscall(SYS_creat_user_thread , func, ¶m, join_flag);
if (ret < 0) {
return ret;
}
thread->tid = (unsigned long)ret;
return 0;
}
内核在创建线程的函数OsCreateUserTask中,仅仅需要分配初始化一个LosTaskCB结构体。因此,在litos-a内核中,调度的实体是进程LosProcessCB,而不是线程。同一个进程创建出的多个线程依附在创建的进程上去运行。
LITE_OS_SEC_TEXT_INIT INT32 OsCreateUserTask(UINT32 processID, TSK_INIT_PARAM_S *initParam)
{
....
if (processID == OS_INVALID_VALUE) {
SCHEDULER_LOCK(intSave);
processCB = OsCurrProcessGet(); /* 获得当前运行的进程processCB */
initParam->processID = processCB->processID;
initParam->consoleID = processCB->consoleID;
SCHEDULER_UNLOCK(intSave);
} else {
processCB = OS_PCB_FROM_PID(processID);
if (!(processCB->processStatus & (OS_PROCESS_STATUS_INIT | OS_PROCESS_STATUS_RUNNING))) {
return OS_INVALID_VALUE;
}
initParam->processID = processID;
initParam->consoleID = 0;
}
ret = LOS_TaskCreateOnly(&taskID, initParam); /* 分配LosTaskCB */
if (ret != LOS_OK) {
return OS_INVALID_VALUE;
}
return taskID;
}
小结: liteos-a内核本身不直接支持线程实现,将线程的信息和进程信息分离开。内核中调度管理的实体是进程,但是调度器中包含了线程的调度信息,比如调度的优先级。liteos-a内核不同于Linux内核,在Linux内核中采用的NPTL线程库中,每个线程在内核都有一个相应的进程控制块。另外,liteos-a内核进程管理的其他部分实现遵循传统的操作系统内核中的进程管理实现,包括进程调度,进程的创建回收,进程的组织,信号传递等。
鸿蒙OS内核的虚拟内存
虚拟内存包括了虚拟地址空间和页表树这两个主要的结构。虚拟地址空间被划分为若干的线性区。liteos-a内核中进程内存描述符结构为LosVmSpace,线性区描述符结构为VmMapRegion。进程所有的线性区描述符用两种数据结构组织:链表和红黑树。链表头regions,红黑树根节点regionRbTree。node表示全局的g_vmSpaceList链表节点。进程/线程在操纵虚拟地址空间时必须先获取互斥量regionMux。下面的一些字段描述符了进程地址空间的基本信息,比如堆的开始和结束地址,代码段的开始和结束地址。archMmu字段表示了页表的信息。
typedef struct VmSpace {
LOS_DL_LIST node; /**< vm space dl list */
LOS_DL_LIST regions; /**< region dl list */
LosRbTree regionRbTree; /**< region red-black tree root */
LosMux regionMux; /**< region list mutex lock */
VADDR_T base; /**< vm space base addr */
UINT32 size; /**< vm space size */
VADDR_T heapBase; /**< vm space heap base address */
VADDR_T heapNow; /**< vm space heap base now */
LosVmMapRegion *heap; /**< heap region */
VADDR_T mapBase; /**< vm space mapping area base */
UINT32 mapSize; /**< vm space mapping area size */
LosArchMmu archMmu; /**< vm mapping physical memory */
#ifdef LOSCFG_DRIVERS_TZDRIVER
VADDR_T codeStart; /**< user process code area start */
VADDR_T codeEnd; /**< user process code area end */
#endif
} LosVmSpace;
每个线性区描述符结构为VmMapRegion。该结构体描述了线性区的基本信息,包括线性区的开始和结束LosVmMapRange,线性区的访问权限protectFlags,线性区的类型regionType。根据线性区映射类型的不同,分为三类:文件映射,匿名映射,特殊设备映射,即unTypeData。
struct VmMapRegion {
LosRbNode rbNode; /**< region red-black tree node */
LosVmSpace *space;
LOS_DL_LIST node; /**< region dl list */
LosVmMapRange range; /**< region address range */
VM_OFFSET_T pgOff; /**< region page offset to file */
UINT32 regionFlags; /**< region flags: cow, user_wired */
UINT32 shmid; /**< shmid about shared region */
UINT8 protectFlags; /**< vm region protect flags: PROT_READ, PROT_WRITE, */
UINT8 forkFlags; /**< vm space fork flags: COPY, ZERO, */
UINT8 regionType; /**< vm region type: ANON, FILE, DEV */
union {
struct VmRegionFile {
unsigned int fileMagic;
struct file *file;
const LosVmFileOps *vmFOps;
} rf;
struct VmRegionAnon {
LOS_DL_LIST node; /**< region LosVmPage list */
} ra;
struct VmRegionDev {
LOS_DL_LIST node; /**< region LosVmPage list */
const LosVmFileOps *vmFOps;
} rd;
} unTypeData;
};
liteos-a内核中与虚拟内存相关的系统调用有mmap,munmap,brk,mprotect等。这里以mmap为例阐述一下liteos-a中虚拟内存管理的实现。mmap系统调用主要由函数LOS_MMap实现。函数LOS_MMap首先会进行参数的检查,然后对齐映射开始的地址和大小。接着判断一下是否是文件映射,如果是获取file结构体指针。下面获取保护虚拟地址空间的互斥量regionMux,因为线程共享地址空间,可能同时修改管理线性区的红黑树,所以要保证互斥访问。然后在进程的虚拟地址空间中查找一块未被使用过的区间,分配一个VmMapRegion结构体,将VmMapRegion结构体插入到红黑树中,这里并没有插入到链表中,可能实现还不完全,囧。最后根据线性区的类型设置标志,返回映射的开始地址。当进程/线程真正访问该线性区时,会触发缺页异常。内核在缺页异常里面会根据线性区信息来检查缺页地址的合法性,然后更新页表树结构。这里就不阐述了。
VADDR_T LOS_MMap(VADDR_T vaddr, size_t len, unsigned prot, unsigned long flags, int fd, unsigned long pgoff)
{
STATUS_T status;
VADDR_T resultVaddr;
UINT32 regionFlags;
LosVmMapRegion *newRegion = NULL;
struct file *filep = NULL;
LosVmSpace *vmSpace = OsCurrProcessGet()->vmSpace;
vaddr = ROUNDUP(vaddr, PAGE_SIZE);
len = ROUNDUP(len, PAGE_SIZE);
STATUS_T checkRst = OsCheckMMapParams(vaddr, prot, flags, len, pgoff);
if (checkRst != LOS_OK) {
return checkRst;
}
if (LOS_IsNamedMapping(flags)) {
status = fs_getfilep(fd, &filep);/* 判断一下是否是文件映射,如果是获取file结构体指针 */
if (status < 0) {
return -EBADF;
}
}
/* 获取保护虚拟地址空间的互斥量regionMux */
(VOID)LOS_MuxAcquire(&vmSpace->regionMux);
/* user mode calls mmap to release heap physical memory without releasing heap virtual space */
status = OsUserHeapFree(vmSpace, vaddr, len);
if (status == LOS_OK) {
resultVaddr = vaddr;
goto MMAP_DONE;
}
regionFlags = OsCvtProtFlagsToRegionFlags(prot, flags);
/* 分配一个VmMapRegion结构体 */
newRegion = LOS_RegionAlloc(vmSpace, vaddr, len, regionFlags, pgoff);
if (newRegion == NULL) {
resultVaddr = (VADDR_T)-ENOMEM;
goto MMAP_DONE;
}
newRegion->regionFlags |= VM_MAP_REGION_FLAG_MMAP;
resultVaddr = newRegion->range.base;
/* 根据线性区的类型设置标志 */
if (LOS_IsNamedMapping(flags)) {
status = OsNamedMMap(filep, newRegion);
} else {
status = OsAnonMMap(newRegion);
}
if (status != LOS_OK) {
LOS_RbDelNode(&vmSpace->regionRbTree, &newRegion->rbNode);
LOS_RegionFree(vmSpace, newRegion);
resultVaddr = (VADDR_T)-ENOMEM;
goto MMAP_DONE;
}
MMAP_DONE:
(VOID)LOS_MuxRelease(&vmSpace->regionMux);
return resultVaddr;
}
小结: liteos-a内核中虚拟内存管理类似于传统操作系统内核中的实现(比如Linux内核),无论是管理线性区的数据结构(红黑树+链表),实现的代码逻辑,地址空间管理的同步机制等。但是缺少相应的性能优化措施,比如线性区的缓存,相同权限的连续地址线性区的合并等。另外,目前虚拟内存系统调用实现还不完全,比如缺少madvise系统调用的实现。
鸿蒙OS内核的文件系统
liteos-a内核中文件系统包含了虚拟文件系统和具体文件系统实现。虚拟文件系统采用了第三方的NuttX系统,具体文件系统目前支持FAT,JFFS2,NFS,RamFS四种文件系统。liteos-a内核中文件系统的系统调用调用还是比较全面的,基本包含了所有常用的文件系统系统调用。进程描述符中的files_struct结构体描述了文件描述符的状态,包括文件描述符表等信息。其余的虚拟文件系统中涉及到的结构在NuttX系统中实现。所以,liteos-a内核只负责管理文件描述符信息。
struct files_struct {
int count;
struct fd_table_s *fdt;
unsigned int file_lock;
unsigned int next_fd;
#ifdef VFS_USING_WORKDIR
spinlock_t workdir_lock;
char workdir[PATH_MAX];
#endif
};
Linux内核中的虚拟文件系统包含了四大对象:超级块super_block,索引节点inode,目录项dentry,文件对象file。NuttX虚拟文件系统简化了虚拟文件系统的实现,文件系统的元数据用fsmap_t结构体描述,没有目录项dentry结构。
struct fsmap_t
{
const char *fs_filesystemtype;
const struct mountpt_operations *fs_mops;
const BOOL is_mtd_support;
const BOOL is_bdfs;
};
索引节点inode保存了文件的元数据。在NuttX中的虚拟文件系统中,所有文件的inode会按照目录的层级组织成树的结构,树节点的指针是i_peer和i_child。inode结构体采用了文件的完整路径名进行索引,路径名字符串保存在i_name中。索引节点所有文件系统注册的操作函数保存在inode_ops_u结构体中。
struct inode
{
FAR struct inode *i_peer; /* Link to same level inode */
FAR struct inode *i_child; /* Link to lower level inode */
int16_t i_crefs; /* References to inode */
uint16_t i_flags; /* Flags for inode */
unsigned long mountflags; /* Flags for mount */
union inode_ops_u u; /* Inode operations */
#ifdef LOSCFG_FILE_MODE
unsigned int i_uid;
unsigned int i_gid;
mode_t i_mode; /* Access mode flags */
#endif
FAR void *i_private; /* Per inode driver private data */
MOUNT_STATE e_status;
char i_name[1]; /* Name of inode (variable) */
};
最后,每一个打开的文件都由一个file结构体来描述。
struct file
{
unsigned int f_magicnum; /* file magic number */
int f_oflags; /* Open mode flags */
FAR struct inode *f_inode; /* Driver interface */
loff_t f_pos; /* File position */
unsigned long f_refcount; /* reference count */
char *f_path; /* File fullpath */
void *f_priv; /* Per file driver private data */
const char *f_relpath; /* realpath */
struct page_mapping *f_mapping; /* mapping file to memory */
void *f_dir; /* DIR struct for iterate the directory if open a directory */
};
liteos-a内核中任何文件系统的元数据操作必须进行路径名查找操作。文件路径名查找操作会将用户给定的文件路径名转化成文件的索引节点inode。NuttX中的虚拟文件系统采用了完整的文件路径名进行查找操作。inode_find根据给定的文件路径名按照从上到下的顺序查找所有inode组织的树结构,在树的每一层按照从左向右的顺序,依次匹配树中的每个inode。该inode树结构由一个信号量来保证互斥访问。这里存在两个性能问题。第一,树查找操作没有采用优化措施,导致查找效率不高。第二,整个系统中只有这一个inode树结构,多进程并发访问时会造成非常严重的锁竞争问题,导致系统可扩展性能下降。
作为对比,Linux内核对文件路径名查找操作进行了很多的优化。Linux内核中的虚拟文件系统采用了dentry结构组织成目录树,并且采用了哈希表以及RCU等多种方法来提高文件路径名查找的效率和可扩展性。
int inode_find(FAR struct inode_search_s *desc)
{
int ret;
FAR const char *path = desc->path;
FAR const char **relpath = &(desc->relpath);
FAR struct inode **peer = &(desc->peer);
FAR struct inode **parent = &(desc->parent);
/* Find the node matching the path. If found, increment the count of
* references on the node.
*/
inode_semtake();
desc->node = inode_search(&path, peer, parent, relpath);
if (desc->node != NULL)
{
/* Found it */
FAR struct inode *node = desc->node;
/* Increment the reference count on the inode */
node->i_crefs++;
ret = 0;
}
else
{
ret = -1;
}
inode_semgive();
return ret;
}
完成文件路径名的查找操作之后,NuttX中的虚拟文件系统根据inode结构体中的inode_ops_u查找到对应的操作函数,调用具体文件系统注册的操作函数,进行具体文件系统的操作。这里不再阐述。
小结: liteos-a文件系统只实现了文件描述符的管理,其余虚拟文件系统的实现借助与第三方的NuttX系统,并且在实现层移植了若干其他文件系统(比如FAT)。NuttX系统中虚拟文件系统存在很多的性能问题,比如文件路径名查找的可扩展性和效率都不太好。未来liteos-a内核可以自己实现了一个虚拟文件系统,而不是借用NuttX系统中的虚拟文件系统,毕竟NuttX系统中的虚拟文件系统实现并不是太好。
总结
这次鸿蒙OS开源的内核功能上还是比较完整的。本文篇幅有限,只是分析了部分的内核架构和实现。liteos-a内核麻雀虽小五脏俱全。基本的操作系统核心功能都有,并且代码写的很规范。只是函数名变量名为啥要用驼峰命名法,有种在写Java代码的感觉,一不小心写个class出来。后期liteos-a内核的开发应该着重在内核性能的优化,安全性的提高等方面。
本文参与了「解读鸿蒙源码」技术征文,欢迎正在阅读的你也加入。