v46.04 鸿蒙内核源码分析(特殊进程篇) | 老鼠生儿会打洞 | 百篇博客分析OpenHarmony源码

原创
2021/04/09 17:58
阅读数 1.2W

寝不尸,居不容。见齐衰者,虽狎,必变。见冕者与瞽者,虽亵,必以貌。凶服者式之。式负版者。有盛馔,必变色而作。迅雷风烈,必变 《论语》:乡党篇

在这里插入图片描述

百篇博客系列篇.本篇为:

v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞

进程管理相关篇为:

三个进程

鸿蒙有三个特殊的进程,创建顺序如下:

  • 2号进程,KProcess,为内核态根进程.启动过程中创建.
  • 0号进程,KIdle为内核态第二个进程,它是通过KProcess fork 而来的.这有点难理解.
  • 1号进程,init,为用户态根进程.由任务SystemInit创建.

鸿蒙task

  • 发现没有在图中看不到0号进程,在看完本篇之后请想想为什么?

家族式管理

  • 进程(process)是家族式管理,总体分为两大家族,用户态家族和内核态家族.
  • 用户态的进程是平民阶层,屌丝矮矬穷,干着各行各业的活,权利有限,人数众多,活动范围有限(用户空间).有关单位肯定不能随便进出.这个阶层有个共同的老祖宗g_userInitProcess (1号进程).
    g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程
    //获取用户态进程的根进程,所有用户进程都是g_processCBArray[g_userInitProcess] fork来的
    LITE_OS_SEC_TEXT UINT32 OsGetUserInitProcessID(VOID)
    {
        return g_userInitProcess;
    }
    
  • 内核态的进程是贵族阶层,管理平民阶层的,维持平民生活秩序的,拥有超级权限,能访问整个空间和所有资源,人数不多.这个阶层老祖宗是 g_kernelInitProcess(2号进程).
    g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程
    //获取内核态进程的根进程,所有内核进程都是g_processCBArray[g_kernelInitProcess] fork来的,包括g_processCBArray[g_kernelIdleProcess]进程
    LITE_OS_SEC_TEXT UINT32 OsGetKernelInitProcessID(VOID)
    {
        return g_kernelInitProcess;
    }
    
  • 两位老祖宗都不是通过fork来的,而是内核强制规定进程ID号,强制写死基因创建的.
  • 这两个阶层可以相互流动吗,有没有可能通过高考改变命运? 答案是: 绝对冇可能!!! 龙生龙,凤生凤,老鼠生儿会打洞.从老祖宗创建的那一刻起就被刻在基因里了,抹不掉了. 因为后续所有的进程都是由这两位老同志克隆(clone)来的,没得商量的继承这份基因.LosProcessCB有专门的标签来processMode区分这两个阶层.整个鸿蒙内核源码并没有提供改变命运机会的set函数.
      #define OS_KERNEL_MODE 0x0U	//内核态
      #define OS_USER_MODE   0x1U	//用户态
      STATIC INLINE BOOL OsProcessIsUserMode(const LosProcessCB *processCB)//用户模式进程
      {
          return (processCB->processMode == OS_USER_MODE);
      }
      typedef struct ProcessCB {
          // ...
          UINT16               processMode;                  /**< Kernel Mode:0; User Mode:1; */	//0位内核态,1为用户态进程
      } LosProcessCB;    
    

2号进程 KProcess

2号进程为内核态的老祖宗,是内核创建的首个进程,源码过程如下,省略了不相干的代码.

bl     main  @带LR的子程序跳转, LR = pc - 4 ,执行C层main函数
/******************************************************************************
内核入口函数,由汇编调用,见于reset_vector_up.S 和 reset_vector_mp.S
up指单核CPU, mp指多核CPU bl        main
******************************************************************************/
LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU执行,默认0号CPU 为主CPU 
{
    // ... 省略
    uwRet = OsMain();// 内核各模块初始化
}
LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID)
{
    // ... 
    ret = OsKernelInitProcess();// 创建内核态根进程
    // ...
    ret = OsSystemInit(); //中间创建了用户态根进程
}
//初始化 2号进程,即内核态进程的老祖宗
LITE_OS_SEC_TEXT_INIT UINT32 OsKernelInitProcess(VOID)
{
    LosProcessCB *processCB = NULL;
    UINT32 ret;

    ret = OsProcessInit();// 初始化进程模块全部变量,创建各循环双向链表
    if (ret != LOS_OK) {
        return ret;
    }

    processCB = OS_PCB_FROM_PID(g_kernelInitProcess);// 以PID方式得到一个进程
    ret = OsProcessCreateInit(processCB, OS_KERNEL_MODE, "KProcess", 0);// 初始化进程,最高优先级0,鸿蒙进程一共有32个优先级(0-31) 其中0-9级为内核进程,用户进程可配置的优先级有22个(10-31)
    if (ret != LOS_OK) {
        return ret;
    }

    processCB->processStatus &= ~OS_PROCESS_STATUS_INIT;// 进程初始化位 置1
    g_processGroup = processCB->group;//全局进程组指向了KProcess所在的进程组
    LOS_ListInit(&g_processGroup->groupList);// 进程组链表初始化
    OsCurrProcessSet(processCB);// 设置为当前进程
    return OsCreateIdleProcess();// 创建一个空闲状态的进程
}

解读

  • main函数在系列篇中会单独讲,请留意自行翻看,它是在开机之初在SVC模式下创建的.
  • 内核态老祖宗的名字叫 KProcess,优先级为最高 0 级,KProcess进程是长期活跃的,很多重要的任务都会跑在其之下.例如:
    • Swt_Task
    • oom_task
    • system_wq
    • tcpip_thread
    • SendToSer
    • SendToTelnet
    • eth_irq_task
    • TouchEventHandler
    • USB_GIANT_Task 此处不细讲这些任务,在其他篇幅有介绍,但光看名字也能猜个八九,请自行翻看.
  • 紧接着KProcessCLONE_FILES的方式 fork了一个 名为KIdle的子进程(0号进程).
  • 内核态的所有进程都来自2号进程这位老同志,子子孙孙,代代相传,形成一颗家族树,和人类的传承所不同的是,它们往往是白发人送黑发人,子孙进程往往都是短命鬼,老祖宗最能活,子孙都死绝了它还在,有些收尸的工作要交给它干.

0 号进程 KIdle

0号进程是内核创建的第二个进程,在OsKernelInitProcess的末尾将KProcess设为当前进程后,紧接着就fork了0号进程.为什么一定要先设置当前进程,因为fork需要一个父进程,而此时系统处于启动阶段,并没有当前进程. 是的,您没有看错.进程是操作系统为方便管理资源而衍生出来的概念,系统并不是非要进程,任务才能运行的. 开机阶段就是啥都没有,默认跑在svc模式下,默认起始地址reset_vector都是由硬件上电后规定的. 进程,线程都是跑起来后慢慢赋予的意义.OsCurrProcessSet是从软件层面赋予了此为当前进程的这个概念.KProcess是内核设置的第一个当前进程.有了它,就可以fork, fork, fork !

//创建一个名叫"KIdle"的0号进程,给CPU空闲的时候使用
STATIC UINT32 OsCreateIdleProcess(VOID)
{
    UINT32 ret;
    CHAR *idleName = "Idle";
    LosProcessCB *idleProcess = NULL;
    Percpu *perCpu = OsPercpuGet();
    UINT32 *idleTaskID = &perCpu->idleTaskID;//得到CPU的idle task

    ret = OsCreateResourceFreeTask();// 创建一个资源回收任务,优先级为5 用于回收进程退出时的各种资源
    if (ret != LOS_OK) {
        return ret;
    }
	//创建一个名叫"KIdle"的进程,并创建一个idle task,CPU空闲的时候就待在 idle task中等待被唤醒
    ret = LOS_Fork(CLONE_FILES, "KIdle", (TSK_ENTRY_FUNC)OsIdleTask, LOSCFG_BASE_CORE_TSK_IDLE_STACK_SIZE);
    if (ret < 0) {//内核进程的fork并不会一次调用,返回两次,此子进程执行的开始位置是参数OsIdleTask
        return LOS_NOK;
    }
    g_kernelIdleProcess = (UINT32)ret;//返回 0号进程

    idleProcess = OS_PCB_FROM_PID(g_kernelIdleProcess);//通过ID拿到进程实体
    *idleTaskID = idleProcess->threadGroupID;//绑定CPU的IdleTask,或者说改变CPU现有的idle任务
    OS_TCB_FROM_TID(*idleTaskID)->taskStatus |= OS_TASK_FLAG_SYSTEM_TASK;//设定Idle task 为一个系统任务
#if (LOSCFG_KERNEL_SMP == YES)
    OS_TCB_FROM_TID(*idleTaskID)->cpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//多核CPU的任务指定,防止乱串了,注意多核才会有并行处理
#endif
    (VOID)memset_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, 0, OS_TCB_NAME_LEN);//task 名字先清0
    (VOID)memcpy_s(OS_TCB_FROM_TID(*idleTaskID)->taskName, OS_TCB_NAME_LEN, idleName, strlen(idleName));//task 名字叫 idle
    return LOS_OK;
}

解读

  • 看过fork篇的可能发现了一个参数, KIdle被创建的方式和通过系统调用创建的方式不一样,一个用的是CLONE_FILES,一个是 CLONE_SIGHAND 具体的创建方式如下:
      #define CLONE_VM       0x00000100	//子进程与父进程运行于相同的内存空间
      #define CLONE_FS       0x00000200	//子进程与父进程共享相同的文件系统,包括root、当前目录、umask
      #define CLONE_FILES    0x00000400	//子进程与父进程共享相同的文件描述符(file descriptor)表
      #define CLONE_SIGHAND  0x00000800	//子进程与父进程共享相同的信号处理(signal handler)表
      #define CLONE_PTRACE   0x00002000	//若父进程被trace,子进程也被trace
      #define CLONE_VFORK    0x00004000	//父进程被挂起,直至子进程释放虚拟内存资源
      #define CLONE_PARENT   0x00008000	//创建的子进程的父进程是调用者的父进程,新进程与创建它的进程成了“兄弟”而不是“父子”
      #define CLONE_THREAD   0x00010000	//Linux 2.4中增加以支持POSIX线程标准,子进程与父进程共享相同的线程群
    
  • KIdle创建了一个名为Idle的任务,任务的入口函数为OsIdleTask,这是个空闲任务,啥也不干的.专门用来给cpu休息的,cpu空闲时就待在这个任务里等活干.
      LITE_OS_SEC_TEXT WEAK VOID OsIdleTask(VOID)
      {
          while (1) {//只有一个死循环
      #ifdef LOSCFG_KERNEL_TICKLESS //低功耗模式开关, idle task 中关闭tick
              if (OsTickIrqFlagGet()) {
                  OsTickIrqFlagSet(0);
                  OsTicklessStart();
              }
      #endif
              Wfi();//WFI指令:arm core 立即进入low-power standby state,进入休眠模式,等待中断.
          }
      }
    
  • fork 内核态进程和fork用户态进程有个地方会不一样,就是SP寄存器的值.fork用户态的进程一次调用两次返回(父子进程各一次),返回的位置一样(是因为拷贝了父进程陷入内核时的上下文).所以可以通过返回值来判断是父还是子返回.这个在fork篇中有详细的描述.请自行翻看. 但fork内核态进程虽也有两次返回,但是返回的位置却不一样,子进程的返回位置是由内核指定的,例如:Idle任务的入口函数为OsIdleTask.详见代码:
    //任务初始化时拷贝任务信息
      STATIC VOID OsInitCopyTaskParam(LosProcessCB *childProcessCB, const CHAR *name, UINTPTR entry, UINT32 size,
                                      TSK_INIT_PARAM_S *childPara)
      {
          LosTaskCB *mainThread = NULL;
          UINT32 intSave;
    
          SCHEDULER_LOCK(intSave);
          mainThread = OsCurrTaskGet();//获取当前task,注意变量名从这里也可以看出 thread 和 task 是一个概念,只是内核常说task,上层应用说thread ,概念的映射.
    
          if (OsProcessIsUserMode(childProcessCB)) {//用户态进程
              childPara->pfnTaskEntry = mainThread->taskEntry;//拷贝当前任务入口地址
              childPara->uwStackSize = mainThread->stackSize;	//栈空间大小
              childPara->userParam.userArea = mainThread->userArea;		//用户态栈区栈顶位置
              childPara->userParam.userMapBase = mainThread->userMapBase;	//用户态栈底
              childPara->userParam.userMapSize = mainThread->userMapSize;	//用户态栈大小
          } else {//注意内核态进程创建任务的入口由外界指定,例如 OsCreateIdleProcess 指定了OsIdleTask
              childPara->pfnTaskEntry = (TSK_ENTRY_FUNC)entry;//参数(sp)为内核态入口地址
              childPara->uwStackSize = size;//参数(size)为内核态栈大小
          }
          childPara->pcName = (CHAR *)name;					//拷贝进程名字
          childPara->policy = mainThread->policy;				//拷贝调度模式
          childPara->usTaskPrio = mainThread->priority;		//拷贝优先级
          childPara->processID = childProcessCB->processID;	//拷贝进程ID
          if (mainThread->taskStatus & OS_TASK_FLAG_PTHREAD_JOIN) {
              childPara->uwResved = OS_TASK_FLAG_PTHREAD_JOIN;
          } else if (mainThread->taskStatus & OS_TASK_FLAG_DETACHED) {
              childPara->uwResved = OS_TASK_FLAG_DETACHED;
          }
    
          SCHEDULER_UNLOCK(intSave);
      }
    
  • 结论是创建0号进程中的OsCreateIdleProcess调用LOS_Fork后只会有一次返回.而且返回值为0,因为 g_freeProcess中0号进程还没有被分配.详见代码,注意看最后的注释:
    //进程模块初始化,被编译放在代码段 .init 中
      LITE_OS_SEC_TEXT_INIT UINT32 OsProcessInit(VOID)
      {
          UINT32 index;
          UINT32 size;
    
          g_processMaxNum = LOSCFG_BASE_CORE_PROCESS_LIMIT;//默认支持64个进程
          size = g_processMaxNum * sizeof(LosProcessCB);//算出总大小
    
          g_processCBArray = (LosProcessCB *)LOS_MemAlloc(m_aucSysMem1, size);// 进程池,占用内核堆,内存池分配 
          if (g_processCBArray == NULL) {
              return LOS_NOK;
          }
          (VOID)memset_s(g_processCBArray, size, 0, size);//安全方式重置清0
    
          LOS_ListInit(&g_freeProcess);//进程空闲链表初始化,创建一个进程时从g_freeProcess中申请一个进程描述符使用
          LOS_ListInit(&g_processRecyleList);//进程回收链表初始化,回收完成后进入g_freeProcess等待再次被申请使用
    
          for (index = 0; index < g_processMaxNum; index++) {//进程池循环创建
              g_processCBArray[index].processID = index;//进程ID[0-g_processMaxNum-1]赋值
              g_processCBArray[index].processStatus = OS_PROCESS_FLAG_UNUSED;// 默认都是白纸一张,贴上未使用标签
              LOS_ListTailInsert(&g_freeProcess, &g_processCBArray[index].pendList);//注意g_freeProcess挂的是pendList节点,所以使用要通过OS_PCB_FROM_PENDLIST找到进程实体.
          }
    
          g_userInitProcess = 1; /* 1: The root process ID of the user-mode process is fixed at 1 *///用户态的根进程
          LOS_ListDelete(&g_processCBArray[g_userInitProcess].pendList);// 将1号进程从空闲链表上摘出去
    
          g_kernelInitProcess = 2; /* 2: The root process ID of the kernel-mode process is fixed at 2 *///内核态的根进程
          LOS_ListDelete(&g_processCBArray[g_kernelInitProcess].pendList);// 将2号进程从空闲链表上摘出去
    
          //注意:这波骚操作之后,g_freeProcess链表上还有,0,3,4,...g_processMaxNum-1号进程.创建进程是从g_freeProcess上申请
          //即下次申请到的将是0号进程,而 OsCreateIdleProcess 将占有0号进程.
    
          return LOS_OK;
      }
    

1号进程 init

1号进程为用户态的老祖宗.创建过程如下, 省略了不相干的代码.

LITE_OS_SEC_TEXT_INIT INT32 OsMain(VOID)
{
    // ... 
    ret = OsKernelInitProcess();// 创建内核态根进程
    // ...
    ret = OsSystemInit(); //中间创建了用户态根进程
}
UINT32 OsSystemInit(VOID)
{
    //..
    ret = OsSystemInitTaskCreate();//创建了一个系统任务,
}

STATIC UINT32 OsSystemInitTaskCreate(VOID)
{
    UINT32 taskID;
    TSK_INIT_PARAM_S sysTask;

    (VOID)memset_s(&sysTask, sizeof(TSK_INIT_PARAM_S), 0, sizeof(TSK_INIT_PARAM_S));
    sysTask.pfnTaskEntry = (TSK_ENTRY_FUNC)SystemInit;//任务的入口函数,这个函数实现由外部提供
    sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K
    sysTask.pcName = "SystemInit";//任务的名称
    sysTask.usTaskPrio = LOSCFG_BASE_CORE_TSK_DEFAULT_PRIO;// 内核默认优先级为10 
    sysTask.uwResved = LOS_TASK_STATUS_DETACHED;//任务分离模式
#if (LOSCFG_KERNEL_SMP == YES)
    sysTask.usCpuAffiMask = CPUID_TO_AFFI_MASK(ArchCurrCpuid());//cpu 亲和性设置,记录执行过任务的CPU,尽量确保由同一个CPU完成任务周期
#endif
    return LOS_TaskCreate(&taskID, &sysTask);//创建任务并加入就绪队列,并立即参与调度
}
//SystemInit的实现由由外部提供 比如..\vendor\hi3516dv300\module_init\src\system_init.c
void SystemInit(void)
{
    // ...
    if (OsUserInitProcess()) {//创建用户态进程的老祖宗
        PRINT_ERR("Create user init process faialed!\n");
        return;
    }
}
//用户态根进程的创建过程
LITE_OS_SEC_TEXT_INIT UINT32 OsUserInitProcess(VOID)
{
    INT32 ret;
    UINT32 size;
    TSK_INIT_PARAM_S param = { 0 };
    VOID *stack = NULL;
    VOID *userText = NULL;
    CHAR *userInitTextStart = (CHAR *)&__user_init_entry;//代码区开始位置 ,对应 LITE_USER_SEC_ENTRY
    CHAR *userInitBssStart = (CHAR *)&__user_init_bss;// 未初始化数据区(BSS)。在运行时改变其值 对应 LITE_USER_SEC_BSS
    CHAR *userInitEnd = (CHAR *)&__user_init_end;// 结束地址
    UINT32 initBssSize = userInitEnd - userInitBssStart;
    UINT32 initSize = userInitEnd - userInitTextStart;

    LosProcessCB *processCB = OS_PCB_FROM_PID(g_userInitProcess);//"Init进程的优先级是 28"
    ret = OsProcessCreateInit(processCB, OS_USER_MODE, "Init", OS_PROCESS_USERINIT_PRIORITY);// 初始化用户进程,它将是所有应用程序的父进程
    if (ret != LOS_OK) {
        return ret;
    }

    userText = LOS_PhysPagesAllocContiguous(initSize >> PAGE_SHIFT);// 分配连续的物理页
    if (userText == NULL) {
        ret = LOS_NOK;
        goto ERROR;
    }

    (VOID)memcpy_s(userText, initSize, (VOID *)&__user_init_load_addr, initSize);// 安全copy 经加载器load的结果 __user_init_load_addr -> userText
    ret = LOS_VaddrToPaddrMmap(processCB->vmSpace, (VADDR_T)(UINTPTR)userInitTextStart, LOS_PaddrQuery(userText),
                               initSize, VM_MAP_REGION_FLAG_PERM_READ | VM_MAP_REGION_FLAG_PERM_WRITE |
                               VM_MAP_REGION_FLAG_PERM_EXECUTE | VM_MAP_REGION_FLAG_PERM_USER);// 虚拟地址与物理地址的映射
    if (ret < 0) {
        goto ERROR;
    }

    (VOID)memset_s((VOID *)((UINTPTR)userText + userInitBssStart - userInitTextStart), initBssSize, 0, initBssSize);// 除了代码段,其余都清0

    stack = OsUserInitStackAlloc(g_userInitProcess, &size);//分配任务在用户态下的运行栈,大小为1M
    if (stack == NULL) {
        PRINTK("user init process malloc user stack failed!\n");
        ret = LOS_NOK;
        goto ERROR;
    }

    param.pfnTaskEntry = (TSK_ENTRY_FUNC)userInitTextStart;// 从代码区开始执行,也就是应用程序main 函数的位置
    param.userParam.userSP = (UINTPTR)stack + size;// 用户态栈底
    param.userParam.userMapBase = (UINTPTR)stack;// 用户态栈顶
    param.userParam.userMapSize = size;// 用户态栈大小
    param.uwResved = OS_TASK_FLAG_PTHREAD_JOIN;// 可结合的(joinable)能够被其他线程收回其资源和杀死
    ret = OsUserInitProcessStart(g_userInitProcess, &param);// 创建一个任务,来运行main函数
    if (ret != LOS_OK) {
        (VOID)OsUnMMap(processCB->vmSpace, param.userParam.userMapBase, param.userParam.userMapSize);
        goto ERROR;
    }

    return LOS_OK;

ERROR:
    (VOID)LOS_PhysPagesFreeContiguous(userText, initSize >> PAGE_SHIFT);//释放物理内存块
    OsDeInitPCB(processCB);//删除PCB块
    return ret;
}

解读

  • 从代码中可以看出用户态的老祖宗创建过程有点意思,首先它的源头和内核态老祖宗一样都在OsMain.
  • 通过创建一个分离模式,优先级为10的系统任务 SystemInit,来完成.任务的入口函数 SystemInit()的实现由平台集成商来指定. 本篇采用了hi3516dv300的实现.也就是说用户态祖宗的创建是在 sysTask.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE;//16K 栈中完成的.这个任务归属于内核进程KProcess.
  • 用户态老祖宗的名字叫 Init,优先级为28级.
  • 用户态的每个进程有独立的虚拟进程空间vmSpace,拥有独立的内存映射表(L1,L2表),申请的内存需要重新映射,映射过程在内存系列篇中有详细的说明.
  • init创建了一个任务,任务的入口地址为 __user_init_entry,由编译器指定.
  • 用户态进程是指应有程序运行的进程,通过动态加载ELF文件的方式启动.具体加载流程系列篇有讲解,不细说.用户态进程运行在用户空间,但通过系统调用可陷入内核空间.具体看这张图: 在这里插入图片描述

百篇博客分析.深挖内核地基

  • 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛
  • 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。

按功能模块:

基础工具 加载运行 进程管理 编译构建
双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 ELF格式 ELF解析 静态链接 重定位 进程映像 进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja
进程通讯 内存管理 前因后果 任务管理
自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换
文件系统 硬件架构
文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理

百万汉字注解.精读内核源码

WeHarmony/kernel_liteos_a_note

鸿蒙研究站 | 每天死磕一点点,原创不易,欢迎转载,但请注明出处。

展开阅读全文
加载中
点击引领话题📣 发布并加入讨论🔥
打赏
0 评论
3 收藏
2
分享
返回顶部
顶部