子曰:“回也非助我者也,于吾言無所不說。” 《論語》:先進篇
百篇部落格系列篇.本篇為:
v49.xx 鴻蒙核心源碼分析(信号消費篇) | 誰讓CPU連續四次換棧運作
程序管理相關篇為:
- v02.06 鴻蒙核心源碼分析(程序管理) | 誰在管理核心資源
- v24.03 鴻蒙核心源碼分析(程序概念) | 程序在管理哪些資源
- v45.05 鴻蒙核心源碼分析(Fork) | 一次調用,兩次傳回
- v46.05 鴻蒙核心源碼分析(特殊程序) | 老鼠生兒會打洞
- v47.02 鴻蒙核心源碼分析(程序回收) | 臨終前如何向老祖宗托孤
- v48.05 鴻蒙核心源碼分析(信号生産) | 年過半百,依然活力十足
- v49.03 鴻蒙核心源碼分析(信号消費) | 誰讓CPU連續四次換棧運作
- v71.03 鴻蒙核心源碼分析(Shell編輯) | 兩個任務,三個階段
- v72.01 鴻蒙核心源碼分析(Shell解析) | 應用窺伺核心的視窗
信号消費
本篇為信号消費篇,讀之前建議先閱讀信号生産篇,信号部分姊妹篇如下:
- v48.xx (信号生産篇) | 年過半百,依然活力十足
- v49.xx (信号消費篇) | 誰讓CPU連續四次換棧運作
本篇有相當的難度,涉及使用者棧和核心棧的兩輪切換,CPU四次換棧,寄存器改值,将圍繞下圖來說明.
解讀
- 為本篇了解友善,把圖做簡化标簽說明:
- user:使用者空間
- kernel:核心空間
- source(...):源函數
- sighandle(...):信号處理函數,
- syscall(...):系統調用,參數為系統調用号,如sigreturn,N(表任意)
- user.source():表示在使用者空間運作的源函數
- 系列篇已多次說過,使用者态的任務有兩個運作棧,一個是使用者棧,一個是核心棧.棧空間分别來自使用者空間和核心空間.兩種空間是有嚴格的位址劃分的,通過虛拟位址的大小就能判斷出是使用者空間還是核心空間.系統調用本質上是軟中斷,它使CPU執行指令的場地由使用者棧變成核心棧.怎麼變的并不複雜,就是改變(sp和cpsr寄存器的值).sp指向哪個棧就代表在哪個棧運作, 當cpu在使用者棧運作時是不能通路核心空間的,但核心态任務可以通路整個空間,而且核心态任務沒有使用者棧.
- 了解了上面的說明,再來說下正常系統調用流程是這樣的: user.source() -> kernel.syscall(N) - > user.source() ,想要回到user.source()繼續運作,就必須儲存使用者棧現場各寄存器的值.這些值儲存在核心棧中,恢複也是從核心棧恢複.
- 信号消費的過程的上圖可簡化表示為: user.source() -> kernel.syscall(N) ->user.sighandle() ->kernel.syscall(sigreturn) -> user.source() 在原本要回到user.source()的中間插入了信号處理函數的調用. 這正是本篇要通過代碼來說清楚的核心問題.
- 順着這個思路可以推到以下幾點,實際也是這麼做的:
- kernel.syscall(N) 中必須要再次儲存user.source()的上下文
,為何已經儲存了一次還要再儲存一次?sig_switch_context
- 因為第一次是儲存在核心棧中,而核心棧這部分資料會因回到使用者态user.sighandle()運作而被恢複現場出棧了.儲存現場/恢複現場是成雙出隊的好基友,注意有些文章說會把整個核心棧清空,這是不對的.
- 第二次儲存在任務結構體中,任務來源于任務池,是核心全局變量,常駐記憶體的.兩次儲存的都是user.source()運作時現場資訊,再回顧下相關的結構體.關鍵是
sig_switch_context
- kernel.syscall(N) 中必須要再次儲存user.source()的上下文
typedef struct {
// ...
sig_cb sig;//信号控制塊,用于異步通信
} LosTaskCB;
typedef struct {//信号控制塊(描述符)
sigset_t sigFlag; //不屏蔽的信号集
sigset_t sigPendFlag; //信号阻塞标簽集,記錄那些信号來過,任務依然阻塞的集合.即:這些信号不能喚醒任務
sigset_t sigprocmask; /* Signals that are blocked */ //任務屏蔽了哪些信号
sq_queue_t sigactionq; //信号捕捉隊列
LOS_DL_LIST waitList; //等待連結清單,上面挂的是等待信号到來的任務, 請查找 OsTaskWait(&sigcb->waitList, timeout, TRUE) 了解
sigset_t sigwaitmask; /* Waiting for pending signals */ //任務在等待哪些信号的到來
siginfo_t sigunbinfo; /* Signal info when task unblocked */ //任務解鎖時的信号資訊
sig_switch_context context; //信号切換上下文, 用于儲存切換現場, 比如發生系統調用時的傳回,涉及同一個任務的兩個棧進行切換
} sig_cb;
- 還必須要改變原有PC/R0/R1寄存器的值.想要執行user.sighandle(),PC寄存器就必須指向它,而R0,R1就是它的參數.
- 信号處理完成後須回到核心态,怎麼再次陷入核心态? 答案是:
,這也是個系統調用.回來後還原__NR_sigreturn
,即還原user.source()被打斷時SP/PC等寄存器的值,使其跳回到使用者棧從user.source()的被打斷處繼續執行.sig_switch_context
- 有了這三個推論,再了解下面的代碼就是吹灰之力了,涉及三個關鍵函數
,OsArmA32SyscallHandle
OsSaveSignalContext
本篇一一解讀,徹底挖透.先看信号上下文結構體OsRestorSignalContext
.sig_switch_context
sig_switch_context
//任務中斷上下文
#define TASK_IRQ_CONTEXT \
unsigned int R0; \
unsigned int R1; \
unsigned int R2; \
unsigned int R3; \
unsigned int R12; \
unsigned int USP; \
unsigned int ULR; \
unsigned int CPSR; \
unsigned int PC;
typedef struct {//信号切換上下文
TASK_IRQ_CONTEXT
unsigned int R7; //存放系統調用的ID
unsigned int count; //記錄是否儲存了信号上下文
} sig_switch_context;
- 儲存user.source()現場的結構體,
USP
代表使用者棧指針和傳回位址.ULR
-
CPSR
寄存器用于設定CPU的工作模式,CPU有7種工作模式,具體可前往翻看
v36.xx (工作模式篇) | cpu是韋小寶,有哪七個老婆?
談論的使用者态(
普通使用者)和核心态(usr
超級使用者)對應的隻是其中的兩種.二者都共用相同的寄存器.還原它就是告訴CPU核心已切到普通使用者模式運作.sys
- 其他寄存器沒有儲存的原因是系統調用不會用到它們,是以不需要儲存.
-
是在系統調用發生時用于記錄系統調用号,在信号處理過程中,R0将獲得信号編号,作為user.sighandle()的第一個參數.R7
-
記錄是否儲存了信号上下文count
OsArmA32SyscallHandle 系統調用總入口
/* The SYSCALL ID is in R7 on entry. Parameters follow in R0..R6 */
/******************************************************************
由彙編調用,見于 los_hw_exc.s / BLX OsArmA32SyscallHandle
SYSCALL是産生系統調用時觸發的信号,R7寄存器存放具體的系統調用ID,也叫系統調用号
regs:參數就是所有寄存器
注意:本函數在使用者态和核心态下都可能被調用到
//MOV R0, SP @擷取SP值,R0将作為OsArmA32SyscallHandle的參數
******************************************************************/
LITE_OS_SEC_TEXT UINT32 *OsArmA32SyscallHandle(UINT32 *regs)
{
UINT32 ret;
UINT8 nArgs;
UINTPTR handle;
UINT32 cmd = regs[REG_R7];//C7寄存器記錄了觸發了具體哪個系統調用
if (cmd >= SYS_CALL_NUM) {//系統調用的總數
PRINT_ERR("Syscall ID: error %d !!!\n", cmd);
return regs;
}
//使用者程序信号處理函數完成後的系統調用 svc 119 #__NR_sigreturn
if (cmd == __NR_sigreturn) {
OsRestorSignalContext(regs);//恢複信号上下文,回到使用者棧運作.
return regs;
}
handle = g_syscallHandle[cmd];//拿到系統調用的注冊函數,類似 SysRead
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)) {//系統調用必須有參數且參數不能大于8個
PRINT_ERR("Unsupport syscall ID: %d nArgs: %d\n", cmd, nArgs);
regs[REG_R0] = -ENOSYS;
return regs;
}
//regs[0-6] 記錄系統調用的參數,這也是由R7寄存器儲存系統調用号的原因
switch (nArgs) {//參數的個數
case ARG_NUM_0:
case ARG_NUM_1:
ret = (*(SyscallFun1)handle)(regs[REG_R0]);//執行系統調用,類似 SysUnlink(pathname);
break;
case ARG_NUM_2://如何是兩個參數的系統調用,這裡傳三個參數也沒有問題,因被調用函數不會去取用R2值
case ARG_NUM_3:
ret = (*(SyscallFun3)handle)(regs[REG_R0], regs[REG_R1], regs[REG_R2]);//類似 SysExecve(fileName, argv, envp);
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: //7個參數的情況
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);//如果有信号要處理,将改寫pc,r0,r1寄存器,改變傳回正常使用者态路徑,而先去執行信号處理程式.
/* Return the last value of curent_regs. This supports context switches on return from the exception.
* That capability is only used with the SYS_context_switch system call.
*/
return regs;//傳回寄存器的值
}
- 這是系統調用的總入口,所有的系統調用都要跑這裡要統一處理.通過系統号(儲存在R7),找到注冊函數并回調.完成系統調用過程.
-
關于系統調用可檢視
v37.xx (系統調用篇) | 系統調用到底經曆了什麼
本篇不詳細說系統調用過程,隻說跟信号相關的部分.
-
總體了解起來是被信号的儲存和還原兩個函數給包夾了.注意要在運作過程中去了解調用兩個函數的過程,對于同一個任務來說,一定是先執行OsArmA32SyscallHandle
,第二次進入OsSaveSignalContext
後再執行OsArmA32SyscallHandle
OsRestorSignalContext
- 看
,由它負責儲存user.source() 的上下文,其中改變了sp,r0/r1寄存器值,切到信号處理函數user.sighandle()運作.OsSaveSignalContext
- 在函數的開頭,碰到系統調用号
,直接恢複信号上下文就退出了,因為這是要切回user.source()繼續運作的操作.__NR_sigreturn
//使用者程序信号處理函數完成後的系統調用 svc 119 #__NR_sigreturn
if (cmd == __NR_sigreturn) {
OsRestorSignalContext(regs);//恢複信号上下文,回到使用者棧運作.
return regs;
}
OsSaveSignalContext 儲存信号上下文
有了上面的鋪墊,就不難了解這個函數的作用.
/**********************************************
産生系統調用時,也就是軟中斷時,儲存使用者棧寄存器現場資訊
改寫PC寄存器的值
**********************************************/
void OsSaveSignalContext(unsigned int *sp)
{
UINTPTR sigHandler;
UINT32 intSave;
LosTaskCB *task = NULL;
LosProcessCB *process = NULL;
sig_cb *sigcb = NULL;
unsigned long cpsr;
OS_RETURN_IF_VOID(sp == NULL);
cpsr = OS_SYSCALL_GET_CPSR(sp);//擷取系統調用時的 CPSR值
OS_RETURN_IF_VOID(((cpsr & CPSR_MASK_MODE) != CPSR_USER_MODE));//必須工作在CPU的使用者模式下,注意CPSR_USER_MODE(cpu層面)和OS_USER_MODE(系統層面)是兩碼事.
SCHEDULER_LOCK(intSave);//如有不明白前往 https://my.oschina.net/weharmony 翻看工作模式/信号分發/信号處理篇
task = OsCurrTaskGet();
process = OsCurrProcessGet();
sigcb = &task->sig;//擷取任務的信号控制塊
//1.未儲存任務上下文任務
//2.任何的信号标簽集不為空或者程序有信号要處理
if ((sigcb->context.count == 0) && ((sigcb->sigFlag != 0) || (process->sigShare != 0))) {
sigHandler = OsGetSigHandler();//擷取信号處理函數
if (sigHandler == 0) {//信号沒有注冊
sigcb->sigFlag = 0;
process->sigShare = 0;
SCHEDULER_UNLOCK(intSave);
PRINT_ERR("The signal processing function for the current process pid =%d is NULL!\n", task->processID);
return;
}
/* One pthread do the share signal */
sigcb->sigFlag |= process->sigShare;//擴充任務的信号标簽集
unsigned int signo = (unsigned int)FindFirstSetedBit(sigcb->sigFlag) + 1;
OsProcessExitCodeSignalSet(process, signo);//設定程序退出信号
sigcb->context.CPSR = cpsr; //儲存狀态寄存器
sigcb->context.PC = sp[REG_PC]; //擷取被打斷現場寄存器的值
sigcb->context.USP = sp[REG_SP];//使用者棧頂位置,以便能從核心棧切回使用者棧
sigcb->context.ULR = sp[REG_LR];//使用者棧傳回位址
sigcb->context.R0 = sp[REG_R0]; //系統調用的傳回值
sigcb->context.R1 = sp[REG_R1];
sigcb->context.R2 = sp[REG_R2];
sigcb->context.R3 = sp[REG_R3];
sigcb->context.R7 = sp[REG_R7];//為何參數不用傳R7,是因為系統調用發生時 R7始終儲存的是系統調用号.
sigcb->context.R12 = sp[REG_R12];//詳見 https://my.oschina.net/weharmony/blog/4967613
sp[REG_PC] = sigHandler;//指定信号執行函數,注意此處改變儲存任務上下文中PC寄存器的值,恢複上下文時将執行這個函數.
sp[REG_R0] = signo; //參數1,信号ID
sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //參數2
/* sig No bits 00000100 present sig No 3, but 1<< 3 = 00001000, so signo needs minus 1 */
sigcb->sigFlag ^= 1ULL << (signo - 1);
sigcb->context.count++; //代表已儲存
}
SCHEDULER_UNLOCK(intSave);
}
- 先是判斷執行條件,确實是有信号需要處理,有處理函數.自定義處理函數是由使用者程序安裝進來的,所有程序旗下的任務都共用,參數就是信号
,注意可不是系統調用号,有差別的.信号編号長這樣.signo
#define SIGHUP 1 //終端挂起或者控制程序終止
#define SIGINT 2 //鍵盤中斷(ctrl + c)
#define SIGQUIT 3 //鍵盤的退出鍵被按下
#define SIGILL 4 //非法指令
#define SIGTRAP 5 //跟蹤陷阱(trace trap),啟動程序,跟蹤代碼的執行
#define SIGABRT 6 //由abort(3)發出的退出指令
#define SIGIOT SIGABRT //abort發出的信号
#define SIGBUS 7 //總線錯誤
#define SIGFPE 8 //浮點異常
#define SIGKILL 9 //常用的指令 kill 9 123 | 不能被忽略、處理和阻塞
系統調用号長這樣,是不是看到一些很熟悉的函數.
#define __NR_restart_syscall 0
#define __NR_exit 1
#define __NR_fork 2
#define __NR_read 3
#define __NR_write 4
#define __NR_open 5
#define __NR_close 6
#define __NR_waitpid 7
#define __NR_creat 8
#define __NR_link 9
#define __NR_unlink 10
#define __NR_execve 11
#define __NR_chdir 12
#define __NR_time 13
#define __NR_mknod 14
#define __NR_chmod 15
#define __NR_lchown 16
#define __NR_break 17
- 最後是最最最關鍵的代碼,改變pc寄存器的值,此值一變,在
中恢複上下文後,cpu跳到使用者空間的代碼段 user.sighandle(R0,R1) 開始執行,即執行信号處理函數._osExceptSwiHdl
sp[REG_PC] = sigHandler;//指定信号執行函數,注意此處改變儲存任務上下文中PC寄存器的值,恢複上下文時将執行這個函數.
sp[REG_R0] = signo; //參數1,信号ID
sp[REG_R1] = (unsigned int)(UINTPTR)(sigcb->sigunbinfo.si_value.sival_ptr); //參數2
OsRestorSignalContext 恢複信号上下文
/****************************************************
恢複信号上下文,由系統調用之__NR_sigreturn産生,這是一個内部産生的系統調用.
為什麼要恢複呢?
因為系統調用的執行由任務核心态完成,使用的棧也是核心棧,CPU相關寄存器記錄的都是核心棧的内容,
而系統調用完成後,需傳回任務的使用者棧執行,這時需将CPU各寄存器回到使用者态現場
是以函數的功能就變成了還原寄存器的值
****************************************************/
void OsRestorSignalContext(unsigned int *sp)
{
LosTaskCB *task = NULL; /* Do not adjust this statement */
LosProcessCB *process = NULL;
sig_cb *sigcb = NULL;
UINT32 intSave;
SCHEDULER_LOCK(intSave);
task = OsCurrTaskGet();
sigcb = &task->sig;//擷取目前任務信号控制塊
if (sigcb->context.count != 1) {//必須之前儲存過,才能被恢複
SCHEDULER_UNLOCK(intSave);
PRINT_ERR("sig error count : %d\n", sigcb->context.count);
return;
}
process = OsCurrProcessGet();//擷取目前程序
sp[REG_PC] = sigcb->context.PC;//指令寄存器
OS_SYSCALL_SET_CPSR(sp, sigcb->context.CPSR);//重置程式狀态寄存器
sp[REG_SP] = sigcb->context.USP;//使用者棧堆棧指針, USP指的是 使用者态的堆棧,即将回到使用者棧繼續運作
sp[REG_LR] = sigcb->context.ULR;//傳回使用者棧代碼執行位置
sp[REG_R0] = sigcb->context.R0;
sp[REG_R1] = sigcb->context.R1;
sp[REG_R2] = sigcb->context.R2;
sp[REG_R3] = sigcb->context.R3;
sp[REG_R7] = sigcb->context.R7;
sp[REG_R12] = sigcb->context.R12;
sigcb->context.count--; //信号上下文的數量回到減少
process->sigShare = 0; //回到使用者态,信号共享清0
OsProcessExitCodeSignalClear(process);//清空程序退出碼
SCHEDULER_UNLOCK(intSave);
}
- 在信号處理函數完成之後,核心會觸發一個
的系統調用,又陷入核心态,回到了__NR_sigreturn
OsArmA32SyscallHandle
- 恢複的過程很簡單,把之前儲存的信号上下文恢複到核心棧sp開始位置,資料在棧中的儲存順序可檢視 用棧方式篇 ,最重要的看這幾句.
sp[REG_PC] = sigcb->context.PC;//指令寄存器
sp[REG_SP] = sigcb->context.USP;//使用者棧堆棧指針, USP指的是 使用者态的堆棧,即将回到使用者棧繼續運作
sp[REG_LR] = sigcb->context.ULR;//傳回使用者棧代碼執行位置
注意這裡還不是真正的切換上下文,隻是改變核心棧中現有的資料.這些資料将還原給寄存器.
USP
和
ULR
指向的是使用者棧的位置.一旦
PC
USP
ULR
從棧中彈出賦給寄存器.才真正完成了核心棧到使用者棧的切換.回到了user.source()繼續運作.
- 真正的切換彙編代碼如下,都已添加注釋,在儲存和恢複上下文中夾着
OsArmA32SyscallHandle
@ Description: Software interrupt exception handler
_osExceptSwiHdl: @軟中斷異常處理,注意此時已在核心棧運作
@儲存任務上下文(TaskContext) 開始... 一定要對照TaskContext來了解
SUB SP, SP, #(4 * 16) @先申請16個棧空間單元用于處理本次軟中斷
STMIA SP, {R0-R12} @TaskContext.R[GEN_REGS_NUM] STMIA從左到右執行,先放R0 .. R12
MRS R3, SPSR @讀取本模式下的SPSR值
MOV R4, LR @儲存回跳寄存器LR
AND R1, R3, #CPSR_MASK_MODE @ Interrupted mode 擷取中斷模式
CMP R1, #CPSR_USER_MODE @ User mode 是否為使用者模式
BNE OsKernelSVCHandler @ Branch if not user mode 非使用者模式下跳轉
@ 當為使用者模式時,擷取SP和LR寄出去值
@ 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 @擷取SP值,R0将作為OsArmA32SyscallHandle的參數
STMFD SP!, {R3} @ Save the CPSR 入棧儲存CPSR值 => TaskContext.regPSR
ADD R3, SP, #(4 * 17) @ Offset to pc/cpsr storage 跳到PC/CPSR存儲位置
STMFD R3!, {R4} @ Save the CPSR and r15(pc) 儲存LR寄存器 => TaskContext.PC
STMFD R3, {R13, R14}^ @ Save user mode r13(sp) and r14(lr) 從右向左 儲存 => TaskContext.LR和SP
SUB SP, SP, #4 @ => TaskContext.resved
PUSH_FPU_REGS R1 @儲存中斷模式(使用者模式)
@儲存任務上下文(TaskContext) 結束
MOV FP, #0 @ Init frame pointer
CPSIE I @開中斷,表明在系統調用期間可響應中斷
BLX OsArmA32SyscallHandle /*交給C語言處理系統調用,參數為R0,指向TaskContext的開始位置*/
CPSID I @執行後續指令前必須先關中斷
@恢複任務上下文(TaskContext) 開始
POP_FPU_REGS R1 @彈出FPU值給R1
ADD SP, SP,#4 @ 定位到儲存舊SPSR值的位置
LDMFD SP!, {R3} @ Fetch the return SPSR 彈出舊SPSR值
MSR SPSR_cxsf, R3 @ Set the return mode SPSR 恢複該模式下的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} @恢複R0-R12寄存器
LDMFD SP, {R13, R14}^ @ Restore user mode R13/R14 恢複使用者模式的R13/R14寄存器
ADD SP, SP, #(2 * 4) @定位到儲存舊PC值的位置
LDMFD SP!, {PC}^ @ Return to user 切回使用者模式運作
@恢複任務上下文(TaskContext) 結束
具體也可看這兩篇:
v42.xx (中斷切換篇) | 系統因中斷活力四射
v41.xx (任務切換篇) | 看彙編如何切換任務
百篇部落格分析.深挖核心地基
- 給鴻蒙核心源碼加注釋過程中,整理出以下文章。内容立足源碼,常以生活場景打比方盡可能多的将核心知識點置入某種場景,具有畫面感,容易了解記憶。說别人能聽得懂的話很重要! 百篇部落格絕不是百度教條式的在說一堆诘屈聱牙的概念,那沒什麼意思。更希望讓核心變得栩栩如生,倍感親切.确實有難度,自不量力,但已經出發,回頭已是不可能的了。 😛
- 與代碼有bug需不斷debug一樣,文章和注解内容會存在不少錯漏之處,請多包涵,但會反複修正,持續更新,v**.xx 代表文章序号和修改的次數,精雕細琢,言簡意赅,力求打造精品内容。
按功能子產品:
基礎工具 | 加載運作 | 程序管理 | 編譯建構 |
---|---|---|---|
雙向連結清單 位圖管理 用棧方式 定時器 原子操作 時間管理 | ELF格式 ELF解析 靜态連結 重定位 程序映像 | 程序概念 Fork 特殊程序 程序回收 信号生産 Shell編輯 Shell解析 | 編譯環境 編譯過程 環境腳本 建構工具 gn應用 忍者ninja |
程序通訊 | 記憶體管理 | 前因後果 | 任務管理 |
自旋鎖 互斥鎖 信号量 事件控制 消息隊列 | 記憶體配置設定 記憶體彙編 記憶體映射 記憶體規則 實體記憶體 | 總目錄 排程故事 記憶體主奴 源碼注釋 源碼結構 靜态站點 | 時鐘任務 任務排程 排程隊列 排程機制 線程概念 并發并行 CPU 系統調用 任務切換 |
檔案系統 | 硬體架構 | ||
檔案概念 索引節點 挂載目錄 根檔案系統 字元裝置 VFS 檔案句柄 管道檔案 | 彙編基礎 彙編傳參 工作模式 寄存器 異常接管 彙編彙總 中斷切換 中斷概念 中斷管理 |
百萬漢字注解.精讀核心源碼
四大碼倉中文注解 . 定期同步官方代碼
鴻蒙研究站( weharmonyos ) | 每天死磕一點點,原創不易,歡迎轉載,請注明出處。若能支援點贊更好,感謝每一份支援。