放原子開源基金會(簡稱“基金會”)于 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源碼相對路徑)。
責任編輯:YYX
-
開源
+關注
關注
3文章
3371瀏覽量
42583 -
鴻蒙系統
+關注
關注
183文章
2636瀏覽量
66470 -
OpenHarmony
+關注
關注
25文章
3729瀏覽量
16408
發布評論請先 登錄
相關推薦
評論