首页 文章 精选 留言 我的

精选列表

搜索[openharmony],共343篇文章
优秀的个人博客,低调大师

v73.01 鸿蒙内核源码分析(注释文档篇) | 内核所有函数调用关系图 | 百篇博客分析OpenHarmony源码

百篇博客分析.本篇为: (注释文档篇) | 内核所有函数调用关系图 前因后果相关篇为: v08.03 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 v09.04 鸿蒙内核源码分析(调度故事) | 用故事说内核调度 v10.03 鸿蒙内核源码分析(内存主奴) | 皇上和奴才如何相处 v13.05 鸿蒙内核源码分析(源码注释) | 每天死磕一点点 v18.02 鸿蒙内核源码分析(源码结构) | 内核文件各自含义 v52.05 鸿蒙内核源码分析(静态站点) | 码农都不爱写注释和文档 v73.01 鸿蒙内核源码分析(注释文档) | 内核所有函数调用关系图 工欲善其事 必先利其器 本篇尝试去摸索下鸿蒙内核毛细血管级的脉络,跟踪以下几个问题. 鸿蒙有多少个结构体,结构体中每个成员变量的含义是什么? 鸿蒙main长啥样,其是如何初始化各个模块的? 鸿蒙的任意一个函数的调用和引用关系关系是怎样的? 它已成为众多鸿蒙内核阅读者必不可少的参考手册. 鸿蒙 main 函数长啥样 前往 >> 鸿蒙研究站 | 源码文档版块 点击函数跟踪. /** * @brief * 内核入口函数,由汇编调用,见于reset_vector_up.S 和 reset_vector_mp.S * up指单核CPU, mp指多核CPU bl main * @return LITE_OS_SEC_TEXT_INIT */ LITE_OS_SEC_TEXT_INIT INT32 main(VOID)//由主CPU执行,默认0号CPU 为主CPU { UINT32 uwRet; uwRet = OsMain();// 内核各模块初始化 if (uwRet != LOS_OK) { return LOS_NOK; } CPU_MAP_SET(0, OsHwIDGet());//设置CPU映射,参数0 代表0号CPU OsSchedStart();//调度开始 while (1) { __asm volatile("wfi");//WFI: wait for Interrupt 等待中断,即下一次中断发生前都在此hold住不干活 } } 结构体/宏/枚举类型 前往 >> 鸿蒙研究站 | 查看所有结构体索引 < 任意函数关系图 | 代码实现 | 注解说明 > 三位一体 模块之间关系图 任意头文件的关系图 内核协作图 前往 >> 查看内核模块协作 百篇博客分析.深挖内核地基 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 按功能模块: so tools load process 总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 注释文档 双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 ELF格式 ELF解析 静态链接 重定位 进程映像 进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 compile ipc mem task 编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja 自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换 fs hw 文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理 百万汉字注解.精读内核源码 四大码仓中文注解 . 定期同步官方代码 鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。

优秀的个人博客,低调大师

v86.01 鸿蒙内核源码分析(静态分配篇) | 很简单的一位小朋友 | 百篇博客分析 OpenHarmony 源码

本篇关键词:池头、池体、节头、节块 内存管理相关篇为: v31.02 鸿蒙内核源码分析(内存规则) | 内存管理到底在管什么 v32.04 鸿蒙内核源码分析(物理内存) | 真实的可不一定精彩 v33.04 鸿蒙内核源码分析(内存概念) | RAM & ROM & Flash v34.03 鸿蒙内核源码分析(虚实映射) | 映射是伟大的发明 v35.02 鸿蒙内核源码分析(页表管理) | 映射关系保存在哪 v36.03 鸿蒙内核源码分析(静态分配) | 很简单的一位小朋友 v37.01 鸿蒙内核源码分析(TLFS算法) | 图表解读TLFS原理 v38.01 鸿蒙内核源码分析(内存池管理) | 如何高效切割合并内存块 v39.04 鸿蒙内核源码分析(原子操作) | 谁在守护指令执行的完整性 v40.01 鸿蒙内核源码分析(圆整对齐) | 正在制作中 ... 静态分配 相比动态分配,静态内存池的分配就是个小弟弟,非常的简单,两个结构体 + 一张图 就能说明白。 typedef struct {//静态内存池信息结构体 UINT32 uwBlkSize; /**< Block size | 块大小*/ UINT32 uwBlkNum; /**< Block number | 块数量*/ UINT32 uwBlkCnt; /**< The number of allocated blocks | 已经被分配的块数量*/ LOS_MEMBOX_NODE stFreeList; /**< Free list | 空闲链表*/ } LOS_MEMBOX_INFO; typedef struct tagMEMBOX_NODE { //内存池中空闲节点的结构,是个单向的链表 struct tagMEMBOX_NODE *pstNext; /**< Free node's pointer to the next node in a memory pool | 指向内存池中下一个空闲节点的指针*/ } LOS_MEMBOX_NODE; 下图来源于官网 解读 静态内存池在概念上由 池头 和 池体 两部分组成,池体由众多节块组成,节块由 节头和 节体 两部分组成 在数据结构上表现为 LOS_MEMBOX_INFO(池头) + [LOS_MEMBOX_NODE(节头) + data(节体)] + ... + [LOS_MEMBOX_NODE(节头) + data(节体)] ,在虚拟地址上它们是连在一起的。 池头 记录总信息,包括 节块大小,总节块数量,已分配节块数量,空闲节块链表表头,stFreeList将所有空闲节块链接到一起,分配内存根本不需要遍历,stFreeList指向的下一个不为null代表还有空闲节块。 节头只有一个指向下一个空闲链表的pstNext指针,简单但足以。 静态分配的优缺点是很明显的,总结下: 负责管理的结构体简单,会占用很少的空间,这点优于动态分配。 分配速度最快,一步到位。 缺点是浪费严重,僵硬不灵活,很计划经济,给每一个家庭每月口粮就这么多,高矮胖瘦都不会管。 因代码量不大,但很精彩,看这种代码是种享受,本篇详细列出静态内存代码层面的实现,关键处已添加注释。 初始化 ///初始化一个静态内存池,根据入参设定其起始地址、总大小及每个内存块大小 LITE_OS_SEC_TEXT_INIT UINT32 LOS_MemboxInit(VOID *pool, UINT32 poolSize, UINT32 blkSize) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool;//在内存起始处放置控制头 LOS_MEMBOX_NODE *node = NULL; //... UINT32 index; UINT32 intSave; MEMBOX_LOCK(intSave); boxInfo->uwBlkSize = LOS_MEMBOX_ALIGNED(blkSize + OS_MEMBOX_NODE_HEAD_SIZE); //节块总大小(节头+节体) boxInfo->uwBlkNum = (poolSize - sizeof(LOS_MEMBOX_INFO)) / boxInfo->uwBlkSize;//总节块数量 boxInfo->uwBlkCnt = 0; //已分配的数量 if (boxInfo->uwBlkNum == 0) {//只有0块的情况 MEMBOX_UNLOCK(intSave); return LOS_NOK; } node = (LOS_MEMBOX_NODE *)(boxInfo + 1);//去除池头,找到第一个节块位置 boxInfo->stFreeList.pstNext = node;//池头空闲链表指向第一个节块 for (index = 0; index < boxInfo->uwBlkNum - 1; ++index) {//切割节块,挂入空闲链表 node->pstNext = OS_MEMBOX_NEXT(node, boxInfo->uwBlkSize);//按块大小切割好,统一由pstNext指向 node = node->pstNext;//node存储了下一个节点的地址信息 } node->pstNext = NULL;//最后一个为null MEMBOX_UNLOCK(intSave); return LOS_OK; } 申请 ///从指定的静态内存池中申请一块静态内存块,整个内核源码只有 OsSwtmrScan中用到了静态内存. LITE_OS_SEC_TEXT VOID *LOS_MemboxAlloc(VOID *pool) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; LOS_MEMBOX_NODE *node = NULL; LOS_MEMBOX_NODE *nodeTmp = NULL; UINT32 intSave; if (pool == NULL) { return NULL; } MEMBOX_LOCK(intSave); node = &(boxInfo->stFreeList);//拿到空闲单链表 if (node->pstNext != NULL) {//不需要遍历链表,因为这是空闲链表 nodeTmp = node->pstNext;//先记录要使用的节点 node->pstNext = nodeTmp->pstNext;//不再空闲了,把节点摘出去了. OS_MEMBOX_SET_MAGIC(nodeTmp);//为已使用的节块设置魔法数字 boxInfo->uwBlkCnt++;//已使用块数增加 } MEMBOX_UNLOCK(intSave); return (nodeTmp == NULL) ? NULL : OS_MEMBOX_USER_ADDR(nodeTmp);//返回可用的虚拟地址 } 释放 /// 释放指定的一块静态内存块 LITE_OS_SEC_TEXT UINT32 LOS_MemboxFree(VOID *pool, VOID *box) { LOS_MEMBOX_INFO *boxInfo = (LOS_MEMBOX_INFO *)pool; UINT32 ret = LOS_NOK; UINT32 intSave; if ((pool == NULL) || (box == NULL)) { return LOS_NOK; } MEMBOX_LOCK(intSave); do { LOS_MEMBOX_NODE *node = OS_MEMBOX_NODE_ADDR(box);//通过节体获取节块首地址 if (OsCheckBoxMem(boxInfo, node) != LOS_OK) { break; } node->pstNext = boxInfo->stFreeList.pstNext;//节块指向空闲链表表头 boxInfo->stFreeList.pstNext = node;//空闲链表表头反指向它,意味节块排到第一,下次申请将首个分配它 boxInfo->uwBlkCnt--;//已经使用的内存块减一 ret = LOS_OK; } while (0);//将被编译时优化 MEMBOX_UNLOCK(intSave); return ret; } 使用 鸿蒙内核目前只有软时钟处理使用了静态内存池,直接上代码 ///软时钟初始化 ,注意函数在多CPU情况下会执行多次 STATIC UINT32 SwtmrBaseInit(VOID) { UINT32 ret; UINT32 size = sizeof(SWTMR_CTRL_S) * LOSCFG_BASE_CORE_SWTMR_LIMIT; SWTMR_CTRL_S *swtmr = (SWTMR_CTRL_S *)LOS_MemAlloc(m_aucSysMem0, size); /* system resident resource */ if (swtmr == NULL) { return LOS_ERRNO_SWTMR_NO_MEMORY; } (VOID)memset_s(swtmr, size, 0, size);//清0 g_swtmrCBArray = swtmr;//软时钟 LOS_ListInit(&g_swtmrFreeList);//初始化空闲链表 for (UINT16 index = 0; index < LOSCFG_BASE_CORE_SWTMR_LIMIT; index++, swtmr++) { swtmr->usTimerID = index;//按顺序赋值 LOS_ListTailInsert(&g_swtmrFreeList, &swtmr->stSortList.sortLinkNode);//通过sortLinkNode将节点挂到空闲链表 } //想要用静态内存池管理,就必须要使用LOS_MEMBOX_SIZE来计算申请的内存大小,因为需要点前缀内存承载头部信息. size = LOS_MEMBOX_SIZE(sizeof(SwtmrHandlerItem), OS_SWTMR_HANDLE_QUEUE_SIZE);//规划一片内存区域作为软时钟处理函数的静态内存池。 g_swtmrHandlerPool = (UINT8 *)LOS_MemAlloc(m_aucSysMem1, size); /* system resident resource */ if (g_swtmrHandlerPool == NULL) { return LOS_ERRNO_SWTMR_NO_MEMORY; } ret = LOS_MemboxInit(g_swtmrHandlerPool, size, sizeof(SwtmrHandlerItem)); if (ret != LOS_OK) { return LOS_ERRNO_SWTMR_HANDLER_POOL_NO_MEM; } for (UINT16 index = 0; index < LOSCFG_KERNEL_CORE_NUM; index++) { SwtmrRunQue *srq = &g_swtmrRunQue[index]; /* The linked list of all cores must be initialized at core 0 startup for load balancing */ OsSortLinkInit(&srq->swtmrSortLink); LOS_ListInit(&srq->swtmrHandlerQueue); srq->swtmrTask = NULL; } SwtmrDebugDataInit(); return LOS_OK; } typedef VOID (*SWTMR_PROC_FUNC)(UINTPTR arg); //函数指针, 赋值给 SWTMR_CTRL_S->pfnHandler,回调处理 typedef struct {//处理软件定时器超时的回调函数的结构体 SWTMR_PROC_FUNC handler; /**< Callback function that handles software timer timeout */ //处理软件定时器超时的回调函数 UINTPTR arg; /**< Parameter passed in when the callback function that handles software timer timeout is called */ //调用处理软件计时器超时的回调函数时传入的参数 LOS_DL_LIST node; #ifdef LOSCFG_SWTMR_DEBUG UINT32 swtmrID; #endif } SwtmrHandlerItem; 关于软定时器可以查看系列相关篇,请想想为何软件定时器会使用静态内存。 百文说内核 | 抓住主脉络 百文相当于摸出内核的肌肉和器官系统,让人开始丰满有立体感,因是直接从注释源码起步,在加注释过程中,每每有心得处就整理,慢慢形成了以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切。 与代码需不断debug一样,文章内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 百文在 < 鸿蒙研究站 | 开源中国 | 博客园 | 51cto | csdn | 知乎 | 掘金 > 站点发布,鸿蒙研究站 | weharmonyos 中回复 百文 可方便阅读。 按功能模块: 基础知识 进程管理 任务管理 内存管理 双向链表 内核概念 源码结构 地址空间 计时单位 优雅的宏 钩子框架 位图管理 POSIX main函数 调度故事 进程控制块 进程空间 线性区 红黑树 进程管理 Fork进程 进程回收 Shell编辑 Shell解析 任务控制块 并发并行 就绪队列 调度机制 任务管理 用栈方式 软件定时器 控制台 远程登录 协议栈 内存规则 物理内存 内存概念 虚实映射 页表管理 静态分配 TLFS算法 内存池管理 原子操作 圆整对齐 通讯机制 文件系统 硬件架构 内核汇编 通讯总览 自旋锁 互斥锁 快锁使用 快锁实现 读写锁 信号量 事件机制 信号生产 信号消费 消息队列 消息封装 消息映射 共享内存 文件概念 文件故事 索引节点 VFS 文件句柄 根文件系统 挂载机制 管道文件 文件映射 写时拷贝 芯片模式 ARM架构 指令集 协处理器 工作模式 寄存器 多核管理 中断概念 中断管理 编码方式 汇编基础 汇编传参 链接脚本 开机启动 进程切换 任务切换 中断切换 异常接管 缺页中断 编译运行 调测工具 编译过程 编译构建 GN语法 忍者无敌 ELF格式 ELF解析 静态链接 重定位 动态链接 进程映像 应用启动 系统调用 VDSO 模块监控 日志跟踪 系统安全 测试用例 百万注源码 | 处处扣细节 百万汉字注解内核目的是要看清楚其毛细血管,细胞结构,等于在拿放大镜看内核。内核并不神秘,带着问题去源码中找答案是很容易上瘾的,你会发现很多文章对一些问题的解读是错误的,或者说不深刻难以自圆其说,你会慢慢形成自己新的解读,而新的解读又会碰到新的问题,如此层层递进,滚滚向前,拿着放大镜根本不愿意放手。 < gitee | github | coding | gitcode > 四大码仓推送 | 同步官方源码,鸿蒙研究站 | weharmonyos 中回复 百万 可方便阅读。 据说喜欢点赞分享的,后来都成了大神。:)

优秀的个人博客,低调大师

鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 百篇博客分析OpenHarmony源码 | v71.01

子曰:“我非生而知之者,好古,敏以求之者也。” 《论语》:述而篇 百篇博客系列篇.本篇为: v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 51 .c .h .o 进程管理相关篇为: v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o v46.xx 鸿蒙内核源码分析(特殊进程篇) | 老鼠生儿会打洞 | 51 .c .h .o v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o v71.xx 鸿蒙内核源码分析(Shell编辑篇) | 两个任务,三个阶段 | 51 .c .h .o v72.xx 鸿蒙内核源码分析(Shell解析篇) | 应用窥伺内核的窗口 | 51 .c .h .o 系列篇从内核视角用一句话概括shell的底层实现为:两个任务,三个阶段。其本质是独立进程,因而划到进程管理模块。每次创建shell进程都会再创建两个任务。 客户端任务(ShellEntry): 负责接受来自终端(控制台)敲入的一个个字符,字符按VT规范组装成一句句的命令。 服务端任务(ShellTask): 对命令进行解析并执行,将结果输出到控制台。 而按命令生命周期可分三个阶段. 编辑: 鸿蒙在这个部分实现了一个简单的编辑器功能,处理控制台输入的每个字符,主要包括了对控制字符 例如 <ESC>,\t,\b,\n,\r,四个方向键0x41 ~ 0x44 的处理。 解析: 对编辑后的字符串进行解析,解析出命令项和参数项,找到对应的命令项执行函数。 执行: 命令可通过静态和动态两种方式注册到内核,解析出具体命令后在注册表中找到对应函数回调。将结果输出到控制台。 编辑部分由客户端任务完成,后两个部分由服务端任务完成,命令全局注册由内核完成。 本篇主要说 客户端任务 和 编辑过程 服务端任务 和 解析/执行过程 已在(Shell解析篇)中说明,请自行翻看. 什么是 Shell 从用户视角看,shell是用户窥视和操作内核的一个窗口,内核并非铁板一块,对应用层开了两个窗口,一个是系统调用,一个就是shell,由内核提供实现函数,由用户提供参数执行。区别是 shell是由独立的任务去完成,可通过将shell命令序列化编写成独立的,简单的shell程序,所以shell也是一门脚本语言,系统调用是依附于应用程序的任务去完成,能做的有限。通过shell窗口能看到 cpu的运行情况,内存的消耗情况,网络的链接状态等等。 鸿蒙 Shell 代码在哪 与shell对应的概念是kernel,在鸿蒙内核,这两部分代码是分开放的,shell代码在 查看 shell 代码 ,目录结构如下. ├─include │ dmesg.h │ dmesg_pri.h │ shcmd.h │ shcmdparse.h │ shell.h │ shell_lk.h │ shell_pri.h │ shmsg.h │ show.h │ └─src ├─base │ shcmd.c │ shcmdparse.c │ shell_lk.c │ shmsg.c │ show.c │ └─cmds date_shellcmd.c dmesg.c hwi_shellcmd.c shell_shellcmd.c watch_shellcmd.c Shell 控制块 跟进程,任务一样,每个概念的背后需要一个主结构体来的支撑,shell的主结构体就是ShellCB,掌握它就可以将shell拿捏的死死的,搞不懂这个结构体就读不懂shell的内核实现.所以在上面花再多功夫也不为过. typedef struct { UINT32 consoleID; //控制台ID UINT32 shellTaskHandle; //shell服务端任务ID UINT32 shellEntryHandle; //shell客户端任务ID VOID *cmdKeyLink; //待处理的shell命令链表 VOID *cmdHistoryKeyLink;//已处理的历史记录链表,去重,10个 VOID *cmdMaskKeyLink; //主要用于方向键上下遍历历史命令 UINT32 shellBufOffset; //buf偏移量 UINT32 shellKeyType; //按键类型 EVENT_CB_S shellEvent; //事件类型触发 pthread_mutex_t keyMutex; //按键互斥量 pthread_mutex_t historyMutex; //历史记录互斥量 CHAR shellBuf[SHOW_MAX_LEN]; //shell命令buf,接受键盘的输入,需要对输入字符解析. CHAR shellWorkingDirectory[PATH_MAX];//shell的工作目录 } ShellCB; //一个shell命令的结构体,命令有长有短,鸿蒙采用了可变数组的方式实现 typedef struct { UINT32 count; //字符数量 LOS_DL_LIST list; //双向链表 CHAR cmdString[0]; //字符串,可变数组的一种实现方式. } CmdKeyLink; enum { STAT_NOMAL_KEY, //普通的按键 STAT_ESC_KEY, //<ESC>键在VT控制规范中时控制的起始键 STAT_MULTI_KEY //组合键 }; 解读 鸿蒙支持两种方式在控制台输入Shell命令,关于控制台请自行翻看控制台篇. 在串口工具中直接输入Shell命令 CONSOLE_SERIAL。 在telnet工具中输入Shell命令 CONSOLE_TELNET。 shellTaskHandle和shellEntryHandle编辑/处理shell命令的两个任务ID,本篇重点说后一个. cmdKeyLink,cmdHistoryKeyLink,cmdMaskKeyLink是三个类型为CmdKeyLink的结构体,本质是双向链表,对应编辑shell命令过程中的三个功能. cmdKeyLink 待执行的命令链表 cmdHistoryKeyLink 存储命令历史记录的,即: history命令显示的内容 cmdMaskKeyLink 记录按上下方向键输出的内容,这个有点难理解,自行在shell中按上下方向键自行体验 shellBufOffset和shellBuf是成对出现的,其中存放的就是用户敲入处理后的字符. keyMutex和historyMutex为操作链表所需的互斥锁,内核用的最多的就是这类锁. shellEvent用于任务之间的通讯,比如. SHELL_CMD_PARSE_EVENT:编辑完成了通知解析任务开始执行 CONSOLE_SHELL_KEY_EVENT:收到来自控制台的CTRL + C信号产生的事件. shellKeyType 按键的类型,分三种 普通,<ESC>键,组合键 shellWorkingDirectory 工作区就不用说了,从哪个目录进入shell的 创建 Shell //shell进程的入口函数 int main(int argc, char **argv) { //... g_shellCB = shellCB;//全局变量,说明鸿蒙同时只支持一个shell进程 return OsShellCreateTask(shellCB);//初始化两个任务 } //创建shell任务 STATIC UINT32 OsShellCreateTask(ShellCB *shellCB) { UINT32 ret = ShellTaskInit(shellCB);//执行shell命令的任务初始化 if (ret != LOS_OK) { return ret; } return ShellEntryInit(shellCB);//通过控制台接收shell命令的任务初始化 } //进入shell客户端任务初始化,这个任务负责编辑命令,处理命令产生的过程,例如如何处理方向键,退格键,回车键等 LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntryInit(ShellCB *shellCB) { UINT32 ret; CHAR *name = NULL; TSK_INIT_PARAM_S initParam = {0}; if (shellCB->consoleID == CONSOLE_SERIAL) { name = SERIAL_ENTRY_TASK_NAME; } else if (shellCB->consoleID == CONSOLE_TELNET) { name = TELNET_ENTRY_TASK_NAME; } else { return LOS_NOK; } initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellEntry;//任务入口函数 initParam.usTaskPrio = 9; /* 9:shell task priority */ initParam.auwArgs[0] = (UINTPTR)shellCB; initParam.uwStackSize = 0x1000; initParam.pcName = name; initParam.uwResved = LOS_TASK_STATUS_DETACHED; ret = LOS_TaskCreate(&shellCB->shellEntryHandle, &initParam);//创建任务 #ifdef LOSCFG_PLATFORM_CONSOLE (VOID)ConsoleTaskReg((INT32)shellCB->consoleID, shellCB->shellEntryHandle);//将任务注册到控制台 #endif return ret; } 解读 main为shell进程的主任务,每个进程都会创建一个默认的线程(任务),这个任务的入口函数就是大家熟知的main函数,不清楚的自行翻看任务管理各篇有详细的说明. 由main任务再创建两个任务,即本篇开头说的两个任务,本篇重点说其中的一个 ShellEntry,任务优先级为9,算是较高优先级. 指定内核栈大小为0x1000 = 4K ,因任务只负责编辑处理控制台输入的字符,命令的执行在其他任务,所以4K的内核空间足够使用. ShellEntry为入口函数,这个函数的实现为本篇的重点 ShellEntry | 编辑过程 LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntry(UINTPTR param) { CHAR ch; INT32 n = 0; ShellCB *shellCB = (ShellCB *)param; CONSOLE_CB *consoleCB = OsGetConsoleByID((INT32)shellCB->consoleID);//获取控制台 if (consoleCB == NULL) { PRINT_ERR("Shell task init error!\n"); return 1; } (VOID)memset_s(shellCB->shellBuf, SHOW_MAX_LEN, 0, SHOW_MAX_LEN);//重置shell命令buf while (1) { #ifdef LOSCFG_PLATFORM_CONSOLE if (!IsConsoleOccupied(consoleCB)) {//控制台是否被占用 #endif /* is console ready for shell ? */ n = read(consoleCB->fd, &ch, 1);//从控制台读取一个字符内容,字符一个个处理 if (n == 1) {//如果能读到一个字符 ShellCmdLineParse(ch, (pf_OUTPUT)dprintf, shellCB); } if (is_nonblock(consoleCB)) {//在非阻塞模式下暂停 50ms LOS_Msleep(50); /* 50: 50MS for sleep */ } #ifdef LOSCFG_PLATFORM_CONSOLE } #endif } } //对命令行内容解析 LITE_OS_SEC_TEXT_MINOR VOID ShellCmdLineParse(CHAR c, pf_OUTPUT outputFunc, ShellCB *shellCB) { const CHAR ch = c; INT32 ret; //不是回车键和字符串结束,且偏移量为0 if ((shellCB->shellBufOffset == 0) && (ch != '\n') && (ch != '\0')) { (VOID)memset_s(shellCB->shellBuf, SHOW_MAX_LEN, 0, SHOW_MAX_LEN);//重置buf } //遇到回车或换行 if ((ch == '\r') || (ch == '\n')) { if (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1)) { shellCB->shellBuf[shellCB->shellBufOffset] = '\0';//字符串结束 } shellCB->shellBufOffset = 0; (VOID)pthread_mutex_lock(&shellCB->keyMutex); OsShellCmdPush(shellCB->shellBuf, shellCB->cmdKeyLink);//解析回车或换行 (VOID)pthread_mutex_unlock(&shellCB->keyMutex); ShellNotify(shellCB);//通知任务解析shell命令 return; } else if ((ch == '\b') || (ch == 0x7F)) { /* backspace or delete(0x7F) */ //遇到删除键 if ((shellCB->shellBufOffset > 0) && (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1))) { shellCB->shellBuf[shellCB->shellBufOffset - 1] = '\0';//填充`\0` shellCB->shellBufOffset--;//buf减少 outputFunc("\b \b");//回调入参函数 } return; } else if (ch == 0x09) { /* 0x09: tab *///遇到tab键 if ((shellCB->shellBufOffset > 0) && (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1))) { ret = OsTabCompletion(shellCB->shellBuf, &shellCB->shellBufOffset);//解析tab键 if (ret > 1) { outputFunc("OHOS # %s", shellCB->shellBuf);//回调入参函数 } } return; } /* parse the up/down/right/left key */ ret = ShellCmdLineCheckUDRL(ch, shellCB);//解析上下左右键 if (ret == LOS_OK) { return; } if ((ch != '\n') && (ch != '\0')) {//普通的字符的处理 if (shellCB->shellBufOffset < (SHOW_MAX_LEN - 1)) {//buf范围 shellCB->shellBuf[shellCB->shellBufOffset] = ch;//直接加入 } else { shellCB->shellBuf[SHOW_MAX_LEN - 1] = '\0';//加入字符串结束符 } shellCB->shellBufOffset++;//偏移量增加 outputFunc("%c", ch);//向终端输出字符 } shellCB->shellKeyType = STAT_NOMAL_KEY;//普通字符 } 解读 ShellEntry内部是个死循环,不断的读取控制台输入的每个字符,注意是按字符处理. 处理四个方向,换行回车,tab,backspace,delete,esc 等控制键,相当于重新认识了下Ascii表.可以把shell终端理解为一个简单的编辑器. 按回车键 表示完成前面的输入,进入解析执行阶段. 按方向键 要显示上/下一个命令的内容,一直按就一直显示上上/下下命令. 按tab键 是要补齐命令的内容,目前鸿蒙支持如下命令: arp cat cd chgrp chmod chown cp cpup date dhclient dmesg dns format free help hwi ifconfig ipdebug kill log ls lsfd memcheck mkdir mount netstat oom partinfo partition ping ping6 pwd reset rm rmdir sem statfs su swtmr sync systeminfo task telnet test tftp touch umount uname watch writeproc 例如:当在控制台按下 ch和 tab键后会输出以下三个 chgrp chmod chown 内容,这些功能对使用者而已看似再平常不过,但都需要内核一一实现. shellBuf存储编辑结果,当按下回车键时,将结果保存并交付给下一个阶段使用. 鸿蒙内核源码分析.总目录 v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o 关注不迷路.代码即人生 QQ群:790015635 | 入群密码: 666 原创不易,欢迎转载,但请注明出处.

优秀的个人博客,低调大师

鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 百篇博客分析OpenHarmony源码 | v63.01

百篇博客系列篇.本篇为: v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 51 .c .h .o 文件系统相关篇为: v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51 .c .h .o 本篇讲一个大型图书馆的管理方案,来说清楚计算机文件系统是如何管理的.如果读懂了这个方案,就基本了解了文件系统最底层的运行机制. 如何建图书馆 假如给你一个100*100米,高10米的场地用于建图书馆,放置全世界的图书档案,有以下几个运营要求: 图书有大有小,差异巨大,比如一本大英百科全书,估计有几百万页,同时也有只有薄薄一页的一封电报. 图书不是一次性给齐,后续不断有新增的,修改的,删除的,比如第一次给的24史不全只有12本,后面陆陆续续补上了. 书的规模是千万级的.必须要在最短的时间内找到书,新书也要在最短的时间内找到地方搁置. 每一次对书的操作都要记录下来,比如何时入库,何时修改,何时被借阅了. 权限验证,不同的人对书有不同的权限,比如有的人可以在书上涂鸦,但有的人只能看. 图书馆必须安全,不能因为局部失火导致整个图书馆不能正常运营. 可以存放目录信息,每个人都可以建自己的书籍目录,要求这些目录信息也能被保存. 让借书,还书,拿目录信息变简单,每个人只凭条形码操作. 请问如果是你会如何设计这个图书馆?并让它即安全又高效的运行. 小易的解决方案 有个叫小易的小伙子提出了一种解决方案: 全仓库建大小相同的格子,这种格子统一叫单元格.甭管是什么内容最后都是放到格子里,若每个格子按 0.250.250.25 米算, 整个场地可建设成 640万个单元格.单元格有唯一且统一的编号.从0一直编到640万-1. 因为单元格太多,管理非常复杂,所以将场地分成大A区,大B区,大C区,.....N区,比如分成8大区,每个区分配80万个单元格. 大A区编号[0 - 799999],...依次类推. 每个大区又划分成统计区,目录区,图书区. 统计区是描述整个图书馆和各分区的信息数据,占用1000个单元格 目录区是为管理本分区图书而产生的信息,占19000个单元格,分成三块: 索引表块(占18900个单元格):小易规定后续将用一页纸来记录书的索引信息,并把这页纸叫索引页,将索引页装订成一本索引表书,这本书有连续的统一的页编号(也叫条形码). 像大英百科全书这样有几百万页的一本书,不管后续在图书区里占用多少单元格,但其在索引表书中就是一张纸,这张纸有固定格式,记录书的名称,权限,修改时间等等信息. 索引页位图块(占10个单元格):记录索引页的使用情况,0|1代表未使用|已使用. 图书区单元格位图块(:占90个单元格):记录数据区单元格的使用情况,0|1代表未使用|已使用. 图书区里放的是真正要管理的书籍,按单元格的容量来放,大点的书会分成多个单元格来存放.共占78万个单元格. 将以上信息简化成树形图表示如下: └─图书馆 => 共 640万个单元格,平均被分成八大战区 ├─大A区 => 80万单元格 │ ├─图书区 => 78万个单元格 │ ├─目录区 => 19000个单元格 │ │ ├─图书区单元格位图块 => 90个单元格 │ │ ├─索引表块 => 18900 个单元格 │ │ │ ├─A文件索引页 => 信息登记表,占一页纸,描述一本书的名称,权限,时间 │ │ │ ├─... │ │ │ └─B目录索引页 │ │ └─索引页位图块 => 10个单元格 │ └─统计区 (1000) └─大B区 ├─图书区 ├─目录区 │ ├─图书区单元格位图块 │ ├─索引表块 │ │ ├─A文件索引页 │ │ ├─... │ │ └─B目录索引页 │ └─索引页位图块 └─统计区 ... 请务必理解这些概念关系,在脑海中形成脑图,这是后续理解整个文件系统如何运作和管理的最关键底层逻辑.以下一一展开说明这些概念. 单元格 因为需求是图书大小没有限制,差别极大,有的书大到上千万页,有的小到只有一页.是个开放问题,需要在空间和时间上进行取舍,不想浪费时间就得浪费空间.没有边界就不可能做特殊处理,这需要标准化的统一管理.如何标准化? 答案是: 建相同的格子 至于是大格子还是小格子可以灵活,但必须是一样尺寸的.这里建 0.25*0.25*0.25米的标准格,可放1000页(1K页)书的,统一按页数放,放满1000页就换个格子放剩下的. 格子是书的关系是(N:1)的关系,即一本书可以分多个格子放,但一个格子中不能放两本不一样的书. 没错,其中就会存在浪费的问题,但浪费就是浪费了. 表现如下: 如果一本书只有一页,那也要占一个格子,等于浪费掉999页的空间,注意不再往里面放其他书,否则管理会非常的麻烦. 而99999页的大英百科,将被分成100份,最后的999页也占个格子,等于浪费掉一页的空间. 当然,如果已知该图书馆将放置的基本都是10K页厚的书就可以按10K页的格子来建设,这样格子就少了,节省了查找的时间. 但如果基本都是10页的书就可以按10页来划格子,节省了空间但换来更多的格子.总之要在时间和空间上取一个平衡.你想如果将10000页的书放在10页单元格的图书馆中意味着要将书本切成1000份存放,后续维护是非常的耗时的. 统计区 统计区用于统计整个图书馆和各大区的全局信息,这种信息非常的重要,被使用频率极高,就像问我们国家有多少人口一样,马上就要答出来,而不是让各省逐级汇报下加起来再回复你. 整个图书馆的全局信息包括: 战区数: 8 战区名称: A区,B区 ... 单元格的总数: 640W 已用单元格: 330W 剩余单元格: 310W 战区单元格范围: A区(0 ~ 799999),B区(800000 ~ ...) .... 索引页编号:A区(0~99999),B区(100000 ~ ...) 图书馆的创建时间:1921年 xx月 图书馆的名称: 世界图书馆 历史大事件: 1949年.... 1956年.... 列表描述各大分区基本信息 A分区 基本信息... B分区 基本信息... C分区 基本信息... 整个图书馆全局信息为何不单独于各分区放置,而要在每个分区都保存一份呢? 原因是因为防止数据被损坏,这么重要的数据只有一份如果哪天图书馆着火了放置那部分单元格被火烧掉了,那就歇菜了,整个图书馆都不能正常运作,所以用多份存档的方式是为了数据的安全.烧了东边还有西边撒,虽然会牺牲空间,但图书馆的安全健壮性却上了台阶.各分区的基本信息会有描述,但对分区更详细的信息会在分区的全局信息块中描述.这相当于广东省总人口会在中央登记下,但具体下属市,区,街道办的人口数据广东省自己维护了. 各分区全局信息包括 战区名称: A区 战区范围: (0 ~ 799999) 单元格的总数: 80W 已用单元格: 20W 剩余单元格: 60W 数据块总数:78W 索引块总数:19000 .... 目录区 这个区和统计区一样,是因为要高效的管理图书区而衍生出来的区.目录区和图书区所分配单元格数量是在图书馆开业那天就定下来了,填满图书区单元格的真正的图书,而谁也不知道会有些什么图书要进进出出,图书大小决定了单元格的使用情况,每本书会占用一张索引页,所以最后一定会出现两种情况: 索引页用完了,但图书区还有格子没被填满.这种情况出现在大量的小而多图书,因为多占用的索引页就会多,因为小占用的图书区单元格就会少. 反之,另一种情况是索引页没用完,但图书区没单元格了.这种情况出现在大量的大而少图书,因为大占用图书区单元格就会多,因为少索引页就会少. 这就是为什么有时看着明明磁盘还有很大空间,但就是存储进去的原因所在.因为该分区小文件太多了,试着删除那些很小而不用的文件试试. 索引表 完全可以把索引表看成是一本书,书的内容是一页一页的索引页,每一页记录一本书的索引信息,1000页就可以记录1000本书的索引信息,这本书也是要装到格子里的.装这本书的单元格叫索引表块. 如果按1000万本书计算 就会生成1000万张索引页, 每个格子1000页,那么 1000万/1000 = 1万 ,也就是说 索引表这本书,需要1万个单元格来存放. 索引页也有全局唯一且统一的编号,注意这个编号和单元格的编号是两码事,这是很多人搞不明白文件系统的关键所在,二者编号范围在统计区有保存. 大分区中三个分区(统计区,目录区,图书区)的排列格式是固定的.,所以很容易计算出某个分区下索引区块的开始和结束位置.这里假定索引表块在分区相对位置的第3000个单元格开始. 如何借书 此时外部人员可凭条形码来取书.流程如下: 技术部的屌丝小王拿着5300这个数字(条形码)来取书. 管理员需先锁定这个条形码在哪个大分区,在统计区一查发现在大A区,索引页编号:A区(0~99999) 管理员立即计算索引页存放的格子位置(3000+5300/1000)=3006号格子,搬梯子手机拍照第300页的内容.内容如下: 名称: 编程珠玑 大小: 14284页 占用图书区总格子数: 15 单元格大小: 1000页 属于普通文件 条形码: 5300 捆绑数: 2人 权限: (0644/-rw-r-----) 用户ID: ( 10/ 小张) 组ID: (2/ 技术部) 访问时间: 2021-07-24 02:07:21.683190622 -0700 修改书籍时间: 2021-07-21 00:17:34.733766830 -0700 修改索引时间: 2021-07-29 01:20:14.314343117 -0700 图书区存放位置:12,32,45,....980 管理员先验权限和所属,表上已经说的很清楚,这本书主人是技术部的小张. 小张本人rw-对这本书可以读,可以修改. 技术部同学r--对这本书只能读,屌丝小王属于技术部,所以可以借,但不能在上面涂鸦. 非技术部同学---说明没有任何权限. 索引表上清晰的记录了书本的名称,总页数,格子的大小,条形码. 数据块号: 15 代表编程珠玑放在大A区图书区的15个格子里,12,32,45 ....给出了格子的编号,编程珠玑是按编号顺序存放的. 有了图书区存放位置管理员再依次把书拷贝一份出来,按顺序叠放好,形成完整的编程珠玑交给屌丝小王,同时将借阅数: 2人 改成3人,把访问时间改成当前时间. 两个位图块 如果屌丝小王也想像小张一样,将爱书论程序员的自我修养捐给图书馆,流程又是怎样的呢? 很明显需要增加两部分内容: 索引页 , 论程序员的自我修养 将在索引表中有张单独的索引页记录信息,这需要一张干净的索引页. 图书区单元格, 实际存放论程序员的自我修养内容的单元格,占用多少单元格取决于书的大小.这需要一批干净的单元格. 注意虽然索引页编号是按顺序编的,一个号接一个号的在索引表这本书里.但是图书馆运营久了有些书是会被销毁的,例如: 条形码为200的delphi程序设计这本书太老太久没人借站着位置就被销毁了,但擦涂的是第200索引页上的记录, 第200页这张纸还是存在的,一直在 199 - 201 页之间. 擦洗干净了谁管你以前是干什么的, 又可用于记录新书的索引信息.那么如何能快速的知道哪些索引页和图书区单元格没有被使用呢? 答案是: 索引页位图块 和 图书区单元格位图块 可以把索引页位图看成是一本书,书的内容是一页一页的位图页,存放这本书的单元格叫 索引页位图块. 将 位图页 画成 100*100的如下格子 010101011010110101010101110101010101101011 101011010101010110101101011010101010101010 110101010101101011010110101010110101101011 010110101101010101011001110101101011010110 101011010101010110101101011010101010101010 110101010101101011010110101010110101101011 011110101101010101010101110101101011010110 010110101101010101011001110101101011010110 101011010101010110101101011010101010101010 .... 每一位代表一个索引页的使用情况,上面说了1000万本图书对应就会有1000万张索引页,而一张位图页能标识100100=1万张索引页的使用情况. 总的计算公式就是: 1000万索引页/(100100)=1千张位图页/1000=1个索引页位图块 也就是说只需要一个格子就能装下1000万的索引占用情况. 同样的道理适用于 图书区单元格位图块,它记录的是图书区单元格的使用情况.位图是最简单最高效的记录两种状态是否变更的方法. 如何捐书 有了以上的基础,小王捐书的流程就简单了. 从索引页位图中找一个没有被使用0的索引页,同时在页中标记为 1,比如 条形码为9527的可用 根据书本的大小来计算需要多少个数据块来存放 论程序员的自我修养,因为程序眼屌丝的书都很厚,例如 9888页,一个单元格放1000页,就需要10个单元格. 再从数据块位图中找到没有被使用0的10个单元格,同时在页中标记为 1,比如数据块编号3,89,765,...,因不断的变动,很大概率是找不到连续的单元格. 这时就可以创建属于论程序员的自我修养这本书的索引页了.如下 名称: 论程序员的自我修养 大小: 9888页 数据块号: 10 单元格大小: 1000页 属于普通文件 条形码: 9527 硬链接数: 1人 权限: (0644/-rw-r-----) 用户ID: ( 4/ 屌丝小王) 组ID: (2/ 技术部) 访问时间: 2021-08-04 03:07:21.683190622 -0700 修改书籍时间: 2021-08-04 00:45:34.733766830 -0700 修改索引时间: 2021-08-04 01:20:14.314343117 -0700 数据块位置:3,89,765,... 目录项 能否用小易的方案记录以下这种信息关系? ├── 金庸小说全集 (条形码:322 ) │ ├── 射雕英雄传 (条形码:1245 ) │ ├── 神雕侠侣 (条形码:23456 ) │ ├── 鹿鼎记 (条形码:34567 ) 其实也是可以的金庸小说全集虽看似一个目录,但他们在索引区没有太多的区别,金庸小说全集目录同样有一张索引页,内容如下: 名称: 金庸小说全集/ 大小: 1页 数据块号: 1 单元格大小: 1000页 目录 条形码: 322 捆绑数: 17 权限: (0755/drwxr-xr-x) 用户ID: ( 10/ 小张) 组ID: (2/ 技术部) Access: 2021-08-03 22:11:49.021942010 -0700 Modify: 2021-07-23 18:53:38.656550199 -0700 Change: 2021-07-23 18:53:38.656550199 -0700 数据块位置:15 唯一的差别是索引页中挑明了它是个目录.这个等于告诉了管理员如何取数据块的数据.目录中的内容如射雕英雄传的索引信息并不在金庸小说全集的索引页中体现.因为索引页的大小是有限的,不能承载太多的内容,不确定的因素都移到了数据块区. 数据块位置:15中存的是 射雕英雄传等的条形码,1245,23456,34567,有了条形码就能找到索引页,找到索引页就找到了一切 映射关系 小易的方案基本是文件系统的底层实现.理解了这套方案对后续基于源码理解鸿蒙文件系统的实现会变得简单, 图书馆系统和计算机文件系统概念映射关系如下 小易方案 - > ext 文件系统 大A区 - > 块组(group) 统计区 - > 超级块(super block) 分区描述列表 - > 块组描述符(GDT) 索引表块 - > 索引表(index table) 索引页 - > 索引节点(inode) 条形码 - > 索引编号(inode.id) 图书区位图块 - > 数据块位图(Blocks bitmap) 索引页位图块 - > 索引位图(inode bitmap) 单元格 - > 逻辑块(Blocks) 目录区 - > 索引块(inode Blocks) 图书区 - > 数据块(date Blocks) 百篇博客.往期回顾 在加注过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 :P 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,.xx代表修改的次数,精雕细琢,言简意赅,力求打造精品内容。 v63.xx 鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 51 .c .h .o v62.xx 鸿蒙内核源码分析(文件概念篇) | 为什么说一切皆是文件 | 51 .c .h .o v61.xx 鸿蒙内核源码分析(忍者ninja篇) | 都忍者了能不快吗 | 51 .c .h .o v60.xx 鸿蒙内核源码分析(gn应用篇) | gn语法及在鸿蒙的使用 | 51 .c .h .o v59.xx 鸿蒙内核源码分析(构建工具篇) | 顺瓜摸藤调试鸿蒙构建过程 | 51 .c .h .o v58.xx 鸿蒙内核源码分析(环境脚本篇) | 编译鸿蒙原来如此简单 | 51 .c .h .o v57.xx 鸿蒙内核源码分析(编译过程篇) | 简单案例窥视GCC编译全过程 | 51 .c .h .o v56.xx 鸿蒙内核源码分析(进程映像篇) | ELF是如何被加载运行的? | 51 .c .h .o v55.xx 鸿蒙内核源码分析(重定位篇) | 与国际接轨的对外部发言人 | 51 .c .h .o v54.xx 鸿蒙内核源码分析(静态链接篇) | 完整小项目看透静态链接过程 | 51 .c .h .o v53.xx 鸿蒙内核源码分析(ELF解析篇) | 你要忘了她姐俩你就不是银 | 51 .c .h .o v52.xx 鸿蒙内核源码分析(静态站点篇) | 五一哪也没去就干了这事 | 51 .c .h .o v51.xx 鸿蒙内核源码分析(ELF格式篇) | 应用程序入口并不是main | 51 .c .h .o v50.xx 鸿蒙内核源码分析(编译环境篇) | docker编译鸿蒙真的很香 | 51 .c .h .o v49.xx 鸿蒙内核源码分析(信号消费篇) | 谁让CPU连续四次换栈运行 | 51 .c .h .o v48.xx 鸿蒙内核源码分析(信号生产篇) | 年过半百,依然活力十足 | 51 .c .h .o v47.xx 鸿蒙内核源码分析(进程回收篇) | 临终前如何向老祖宗托孤 | 51 .c .h .o v46.xx 鸿蒙内核源码分析(特殊进程篇) | 龙生龙凤生凤老鼠生儿会打洞 | 51 .c .h .o v45.xx 鸿蒙内核源码分析(Fork篇) | 一次调用,两次返回 | 51 .c .h .o v44.xx 鸿蒙内核源码分析(中断管理篇) | 江湖从此不再怕中断 | 51 .c .h .o v43.xx 鸿蒙内核源码分析(中断概念篇) | 海公公的日常工作 | 51 .c .h .o v42.xx 鸿蒙内核源码分析(中断切换篇) | 系统因中断活力四射 | 51 .c .h .o v41.xx 鸿蒙内核源码分析(任务切换篇) | 看汇编如何切换任务 | 51 .c .h .o v40.xx 鸿蒙内核源码分析(汇编汇总篇) | 汇编可爱如邻家女孩 | 51 .c .h .o v39.xx 鸿蒙内核源码分析(异常接管篇) | 社会很单纯,复杂的是人 | 51 .c .h .o v38.xx 鸿蒙内核源码分析(寄存器篇) | 小强乃宇宙最忙存储器 | 51 .c .h .o v37.xx 鸿蒙内核源码分析(系统调用篇) | 开发者永远的口头禅 | 51 .c .h .o v36.xx 鸿蒙内核源码分析(工作模式篇) | CPU是韦小宝,七个老婆 | 51 .c .h .o v35.xx 鸿蒙内核源码分析(时间管理篇) | 谁是内核基本时间单位 | 51 .c .h .o v34.xx 鸿蒙内核源码分析(原子操作篇) | 谁在为原子操作保驾护航 | 51 .c .h .o v33.xx 鸿蒙内核源码分析(消息队列篇) | 进程间如何异步传递大数据 | 51 .c .h .o v32.xx 鸿蒙内核源码分析(CPU篇) | 整个内核就是一个死循环 | 51 .c .h .o v31.xx 鸿蒙内核源码分析(定时器篇) | 哪个任务的优先级最高 | 51 .c .h .o v30.xx 鸿蒙内核源码分析(事件控制篇) | 任务间多对多的同步方案 | 51 .c .h .o v29.xx 鸿蒙内核源码分析(信号量篇) | 谁在负责解决任务的同步 | 51 .c .h .o v28.xx 鸿蒙内核源码分析(进程通讯篇) | 九种进程间通讯方式速揽 | 51 .c .h .o v27.xx 鸿蒙内核源码分析(互斥锁篇) | 比自旋锁丰满的互斥锁 | 51 .c .h .o v26.xx 鸿蒙内核源码分析(自旋锁篇) | 自旋锁当立贞节牌坊 | 51 .c .h .o v25.xx 鸿蒙内核源码分析(并发并行篇) | 听过无数遍的两个概念 | 51 .c .h .o v24.xx 鸿蒙内核源码分析(进程概念篇) | 进程在管理哪些资源 | 51 .c .h .o v23.xx 鸿蒙内核源码分析(汇编传参篇) | 如何传递复杂的参数 | 51 .c .h .o v22.xx 鸿蒙内核源码分析(汇编基础篇) | CPU在哪里打卡上班 | 51 .c .h .o v21.xx 鸿蒙内核源码分析(线程概念篇) | 是谁在不断的折腾CPU | 51 .c .h .o v20.xx 鸿蒙内核源码分析(用栈方式篇) | 程序运行场地由谁提供 | 51 .c .h .o v19.xx 鸿蒙内核源码分析(位图管理篇) | 谁能一分钱分两半花 | 51 .c .h .o v18.xx 鸿蒙内核源码分析(源码结构篇) | 内核每个文件的含义 | 51 .c .h .o v17.xx 鸿蒙内核源码分析(物理内存篇) | 怎么管理物理内存 | 51 .c .h .o v16.xx 鸿蒙内核源码分析(内存规则篇) | 内存管理到底在管什么 | 51 .c .h .o v15.xx 鸿蒙内核源码分析(内存映射篇) | 虚拟内存虚在哪里 | 51 .c .h .o v14.xx 鸿蒙内核源码分析(内存汇编篇) | 谁是虚拟内存实现的基础 | 51 .c .h .o v13.xx 鸿蒙内核源码分析(源码注释篇) | 鸿蒙必定成功,也必然成功 | 51 .c .h .o v12.xx 鸿蒙内核源码分析(内存管理篇) | 虚拟内存全景图是怎样的 | 51 .c .h .o v11.xx 鸿蒙内核源码分析(内存分配篇) | 内存有哪些分配方式 | 51 .c .h .o v10.xx 鸿蒙内核源码分析(内存主奴篇) | 皇上和奴才如何相处 | 51 .c .h .o v09.xx 鸿蒙内核源码分析(调度故事篇) | 用故事说内核调度过程 | 51 .c .h .o v08.xx 鸿蒙内核源码分析(总目录) | 百万汉字注解 百篇博客分析 | 51 .c .h .o v07.xx 鸿蒙内核源码分析(调度机制篇) | 任务是如何被调度执行的 | 51 .c .h .o v06.xx 鸿蒙内核源码分析(调度队列篇) | 内核有多少个调度队列 | 51 .c .h .o v05.xx 鸿蒙内核源码分析(任务管理篇) | 任务池是如何管理的 | 51 .c .h .o v04.xx 鸿蒙内核源码分析(任务调度篇) | 任务是内核调度的单元 | 51 .c .h .o v03.xx 鸿蒙内核源码分析(时钟任务篇) | 触发调度谁的贡献最大 | 51 .c .h .o v02.xx 鸿蒙内核源码分析(进程管理篇) | 谁在管理内核资源 | 51 .c .h .o v01.xx 鸿蒙内核源码分析(双向链表篇) | 谁是内核最重要结构体 | 51 .c .h .o 关于 51 .c .h .o 看系列篇文章会常看到 51 .c .h .o,希望这对大家阅读不会造成影响. 分别对应以下四个站点的首个字符,感谢这些站点一直以来对系列篇的支持和推荐,尤其是 oschina gitee ,很喜欢它的界面风格,简洁大方,让人感觉到开源的伟大! 51cto csdn harmony oschina 而巧合的是.c .h .o是C语言的头/源/目标文件,这就很有意思了,冥冥之中似有天数,将这四个宝贝以这种方式融合在一起. 51 .c .h .o , 我要CHO ,嗯嗯,hin 顺口 : ) 百万汉字注解.百篇博客分析 百万汉字注解 >> 精读鸿蒙源码,中文注解分析, 深挖地基工程,大脑永久记忆,四大码仓每日同步更新< gitee | github | csdn | coding > 百篇博客分析 >> 故事说内核,问答式导读,生活式比喻,表格化说明,图形化展示,主流站点定期更新中< 51cto | csdn | harmony | osc > 关注不迷路.代码即人生 热爱是所有的理由和答案 - turing 原创不易,欢迎转载,但麻烦请注明出处.

优秀的个人博客,低调大师

v74.01 鸿蒙内核源码分析(控制台篇) | 一个让很多人模糊的概念 | 百篇博客分析OpenHarmony源码

百篇博客分析.本篇为: (控制台篇) | 一个让很多人模糊的概念 文件系统相关篇为: v62.02 鸿蒙内核源码分析(文件概念) | 为什么说一切皆是文件 v63.04 鸿蒙内核源码分析(文件系统) | 用图书管理说文件系统 v64.06 鸿蒙内核源码分析(索引节点) | 谁是文件系统最重要的概念 v65.05 鸿蒙内核源码分析(挂载目录) | 为何文件系统需要挂载 v66.07 鸿蒙内核源码分析(根文件系统) | 谁先挂到/谁就是根总 v67.03 鸿蒙内核源码分析(字符设备) | 绝大多数设备都是这类 v68.02 鸿蒙内核源码分析(VFS) | 文件系统是个大家庭 v69.04 鸿蒙内核源码分析(文件句柄) | 你为什么叫句柄 v70.05 鸿蒙内核源码分析(管道文件) | 如何降低数据流动成本 v74.01 鸿蒙内核源码分析(控制台) | 一个让很多人模糊的概念 本篇尝试讲明白控制台实现以及Shell如何依赖控制台工作.涉及源码部分只列出关键代码. 详细代码前往 >> 中文注解鸿蒙内核源码 查看 Shell | 控制台模型 下图为看完鸿蒙内核Shell和控制台源码后整理的模型图 模型说明 模型涉及四个任务, 两个在用户空间,两个在内核空间.用户空间的在系列篇Shell部分中已有详细说明,请前往查看. SystemInit任务是在内核OsMain中创建的系统初始化任务,其中初始化了根文件系统,串口,控制台等内核模块 在控制台模块中创建SendToSer任务,这是一个负责将控制台结果输出到终端的任务. 结构体CONSOLE_CB,CirBufSendCB承载了控制台的实现过程. 代码实现 每个模块都有一个核心结构体,控制台则是 结构体 | CONSOLE_CB /** * @brief 控制台控制块(描述符) */ typedef struct { UINT32 consoleID; ///< 控制台ID 例如 : 1 | 串口 , 2 | 远程登录 UINT32 consoleType; ///< 控制台类型 UINT32 consoleSem; ///< 控制台信号量 UINT32 consoleMask; ///< 控制台掩码 struct Vnode *devVnode; ///< 索引节点 CHAR *name; ///< 名称 例如: /dev/console1 INT32 fd; ///< 系统文件句柄, 由内核分配 UINT32 refCount; ///< 引用次数,用于判断控制台是否被占用 UINT32 shellEntryId; ///< 负责接受来自终端信息的 "ShellEntry"任务,这个值在运行过程中可能会被换掉,它始终指向当前正在运行的shell客户端 INT32 pgrpId; ///< 进程组ID BOOL isNonBlock; ///< 是否无锁方式 #ifdef LOSCFG_SHELL VOID *shellHandle; ///< shell句柄,本质是 shell控制块 ShellCB #endif UINT32 sendTaskID; ///< 创建任务通过事件接收数据, 见于OsConsoleBufInit /*--以下为 一家子 start---------*/ CirBufSendCB *cirBufSendCB; ///< 循环缓冲发送控制块 UINT8 fifo[CONSOLE_FIFO_SIZE]; ///< 控制台缓冲区大小 1K UINT32 fifoOut; ///< 对fifo的标记,输出位置 UINT32 fifoIn; ///< 对fifo的标记,输入位置 UINT32 currentLen; ///< 当前fifo位置 /*---以上为 一家子 end------- https://man7.org/linux/man-pages/man3/tcflow.3.html */ struct termios consoleTermios; ///< 行规程 } CONSOLE_CB; 解析 创建控制台的过程是给CONSOLE_CB赋值的过程,如下 STATIC CONSOLE_CB *OsConsoleCreate(UINT32 consoleID, const CHAR *deviceName) { INT32 ret; CONSOLE_CB *consoleCB = OsConsoleCBInit(consoleID);//初始化控制台 ret = (INT32)OsConsoleBufInit(consoleCB);//控制台buf初始化,创建 ConsoleSendTask 任务 ret = (INT32)LOS_SemCreate(1, &consoleCB->consoleSem);//创建控制台信号量 ret = OsConsoleDevInit(consoleCB, deviceName);//控制台设备初始化,注意这步要在 OsConsoleFileInit 的前面. ret = OsConsoleFileInit(consoleCB); //为 /dev/console(n|1:2)分配fd(3) OsConsoleTermiosInit(consoleCB, deviceName);//控制台行规程初始化 return consoleCB; } Shell是用户空间进程, 负责解析和执行用户输入的命令. 但前提是得先拿到用户的输入数据. 不管数据是从串口进来,还是远程登录进来,必须得先经过内核, 而控制台的作用就是帮你拿到数据再交给shell处理, shell再将要显示的处理结果通过控制台返回给终端用户, 那数据怎么传给shell呢? 很显然用户进程只能通过系统调用 read(fd,...)来读取内核数据, 因为应用程序的视角是只认fd.通用的办法是通过文件路径来打开文件来获取fd. 还有一种办法是内核先打开文件,获取fd后,用户任务通过捆绑的方式获取fd,而shell和console之间正是通过这种方式勾搭在一块的.具体在创建ShellEntry任务时将自己与控制台进行捆绑.看源码实现 ///进入shell客户端任务初始化,这个任务负责编辑命令,处理命令产生的过程,例如如何处理方向键,退格键,回车键等 LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntryInit(ShellCB *shellCB) { UINT32 ret; CHAR *name = NULL; TSK_INIT_PARAM_S initParam = {0}; if (shellCB->consoleID == CONSOLE_SERIAL) { name = SERIAL_ENTRY_TASK_NAME; } initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ShellEntry;//任务入口函数 initParam.usTaskPrio = 9; /* 9:shell task priority */ initParam.auwArgs[0] = (UINTPTR)shellCB; initParam.uwStackSize = 0x1000; initParam.pcName = name; //任务名称 initParam.uwResved = LOS_TASK_STATUS_DETACHED; ret = LOS_TaskCreate(&shellCB->shellEntryHandle, &initParam);//创建shell任务 #ifdef LOSCFG_PLATFORM_CONSOLE (VOID)ConsoleTaskReg((INT32)shellCB->consoleID, shellCB->shellEntryHandle);//将shell捆绑到控制台 #endif return ret; } ConsoleTaskReg将 shellCB和consoleCB捆绑在一块,二者可以相互查找.ShellEntry任务个人更愿意称之为shell的客户端任务,用死循环不断一个字符一个字符的读取用户的输入,为何要单字符 读取可翻看系列篇的Shell编辑篇,简单的说是因为要处理控制字符(如:删除,回车==) LITE_OS_SEC_TEXT_MINOR UINT32 ShellEntry(UINTPTR param) { CHAR ch; INT32 n = 0; ShellCB *shellCB = (ShellCB *)param; CONSOLE_CB *consoleCB = OsGetConsoleByID((INT32)shellCB->consoleID);//获取绑定的控制台,目的是从控制台读数据 (VOID)memset_s(shellCB->shellBuf, SHOW_MAX_LEN, 0, SHOW_MAX_LEN);//重置shell命令buf while (1) { n = read(consoleCB->fd, &ch, 1);//系统调用,从控制台读取一个字符内容,字符一个个处理 if (n == 1) {//如果能读到一个字符 ShellCmdLineParse(ch, (pf_OUTPUT)dprintf, shellCB); } } } read函数的consoleCB->fd是个虚拟字符设备文件 如:/dev/console1,对文件的操作由g_consoleDevOps实现.read最终会调用ConsoleRead,再往下会调用到UART_Read /*! console device driver function structure | 控制台设备驱动程序,统一的vfs接口的实现 */ STATIC const struct file_operations_vfs g_consoleDevOps = { .open = ConsoleOpen, /* open */ .close = ConsoleClose, /* close */ .read = ConsoleRead, /* read */ .write = ConsoleWrite, /* write */ .seek = NULL, .ioctl = ConsoleIoctl, .mmap = NULL, #ifndef CONFIG_DISABLE_POLL .poll = ConsolePoll, #endif }; fifo用于termios(行规程)的规范模式,输入数据基于行进行处理。在用户输入一个行结束符(回车符、EOF等)之前,系统调用read()读不到用户输入的任何字符。除了EOF之外的行结束符(回车符等),与普通字符一样会被read()读到缓冲区fifo中。在规范模式中,可以进行行编辑,而且一次调用read()最多只能读取一行数据。如果read()请求读取的数据字节少于当前行可读取的字节,则read()只读取被请求的字节数,剩下的字节下次再读。详细内容见系列篇之 行规程篇 CirBufSendCB是专用于SendToSer任务的结构体,任务之间通过事件相互驱动,控制台通知SendToSer将数据发送给终端 /** * @brief 发送环形buf控制块,通过事件发送 */ typedef struct { CirBuf cirBufCB; /* Circular buffer CB | 循环缓冲控制块 */ EVENT_CB_S sendEvent; /* Inform telnet send task | 例如: 给SendToSer任务发送事件*/ } CirBufSendCB; 发送数据给终端的任务 | ConsoleSendTask ConsoleSendTask只干一件事,将数据发送给串口或远程登录,任务优先级与shell同级,为9,它由系统初始化任务SystemInit创建, 具体可翻看系列篇之内核启动篇 /// 控制台缓存初始化,创建一个 发送任务 STATIC UINT32 OsConsoleBufInit(CONSOLE_CB *consoleCB) { UINT32 ret; TSK_INIT_PARAM_S initParam = {0}; consoleCB->cirBufSendCB = ConsoleCirBufCreate();//创建控制台 if (consoleCB->cirBufSendCB == NULL) { return LOS_NOK; } initParam.pfnTaskEntry = (TSK_ENTRY_FUNC)ConsoleSendTask;//控制台发送任务入口函数 initParam.usTaskPrio = SHELL_TASK_PRIORITY; //优先级9 initParam.auwArgs[0] = (UINTPTR)consoleCB; //入口函数的参数 initParam.uwStackSize = LOSCFG_BASE_CORE_TSK_DEFAULT_STACK_SIZE; //16K if (consoleCB->consoleID == CONSOLE_SERIAL) {//控制台的两种方式 initParam.pcName = "SendToSer"; //任务名称(发送数据到串口) } else { initParam.pcName = "SendToTelnet";//任务名称(发送数据到远程登录) } initParam.uwResved = LOS_TASK_STATUS_DETACHED; //使用任务分离模式 ret = LOS_TaskCreate(&consoleCB->sendTaskID, &initParam);//创建task 并加入就绪队列,申请立即调度 if (ret != LOS_OK) { //创建失败处理 ConsoleCirBufDelete(consoleCB->cirBufSendCB);//释放循环buf consoleCB->cirBufSendCB = NULL;//置NULL return LOS_NOK; }//永久等待读取 CONSOLE_SEND_TASK_RUNNING 事件,CONSOLE_SEND_TASK_RUNNING 由 ConsoleSendTask 发出. (VOID)LOS_EventRead(&consoleCB->cirBufSendCB->sendEvent, CONSOLE_SEND_TASK_RUNNING, LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER); // ... 读取到 CONSOLE_SEND_TASK_RUNNING 事件才会往下执行 return LOS_OK; } 任务的入口函数ConsoleSendTask实现也很简单,此处全部贴出来,死循环等待事件的发送.说到死循环多说两句,不要被while (1)吓倒,认为内核会卡死在这里玩不下去,那是应用程序员看待死循环的视角,其实在内核当等待的事件没有到来的时,这个任务并不会往下执行,而是处于挂起状态,当事件到来时才会切换回来继续往下走,那如何知道事件到来了呢? 可翻看系列篇之事件控制篇 STATIC UINT32 ConsoleSendTask(UINTPTR param) { CONSOLE_CB *consoleCB = (CONSOLE_CB *)param; CirBufSendCB *cirBufSendCB = consoleCB->cirBufSendCB; CirBuf *cirBufCB = &cirBufSendCB->cirBufCB; UINT32 ret, size; UINT32 intSave; CHAR *buf = NULL; (VOID)LOS_EventWrite(&cirBufSendCB->sendEvent, CONSOLE_SEND_TASK_RUNNING);//发送一个控制台任务正在运行的事件 while (1) {//读取 CONSOLE_CIRBUF_EVENT | CONSOLE_SEND_TASK_EXIT 这两个事件 ret = LOS_EventRead(&cirBufSendCB->sendEvent, CONSOLE_CIRBUF_EVENT | CONSOLE_SEND_TASK_EXIT, LOS_WAITMODE_OR | LOS_WAITMODE_CLR, LOS_WAIT_FOREVER);//读取循环buf或任务退出的事件 if (ret == CONSOLE_CIRBUF_EVENT) {//控制台循环buf事件发生 size = LOS_CirBufUsedSize(cirBufCB);//循环buf使用大小 if (size == 0) { continue; } buf = (CHAR *)LOS_MemAlloc(m_aucSysMem1, size + 1);//分配接收cirbuf的内存 if (buf == NULL) { continue; } (VOID)memset_s(buf, size + 1, 0, size + 1);//清0 LOS_CirBufLock(cirBufCB, &intSave); (VOID)LOS_CirBufRead(cirBufCB, buf, size);//读取循环cirBufCB至 buf LOS_CirBufUnlock(cirBufCB, intSave); (VOID)WriteToTerminal(consoleCB, buf, size);//将buf数据写到控制台终端设备 (VOID)LOS_MemFree(m_aucSysMem1, buf);//清除buf } else if (ret == CONSOLE_SEND_TASK_EXIT) {//收到任务退出的事件, 由 OsConsoleBufDeinit 发出事件. break;//退出循环 } } ConsoleCirBufDelete(cirBufSendCB);//删除循环buf,归还内存 return LOS_OK; } 上面提到了控制台和终端,是经常容易搞混的又变得越来越模糊两个概念,简单说明下. 传统的控制台和终端 控制台(console)和终端(terminal)有什么区别? 看张古老的图 这个不陌生吧,实现中虽很少看到,可电影里可没少出现. 据说是NASA航天飞机控制台,满满的科技感. 这就是控制台.早期控制台其实是给系统管理人员使用的.因为机器很大,价格很贵,不可能让每个人都拥有一个真正物理上属于自己的计算机,但是只让一个人用那其他人怎么办? 效率太低,就出现了多用户多任务计算机,让一台计算机多个人同时登录使用的情况, 给每个人面前放个简单设备(只有键盘和屏幕)连接到主机上,如图所示 这个就叫终端 ,注意别看那么大,长得很像一体机,但其实它只是一台显示器.这是给普通用户使用,权限也有限,核心功能权限还是在操作控制台的系统管理员手上. 综上所述,用图表列出二者早期差异 区别 终端(terminal) 控制台(console) 设备属性 外挂的附加设备 自带的基本设备 数量 多个 一个 主机信任度 低 高 输出内容 主机处理的信息 主机核心/自身信息 操作员 普通用户 管理员 现在的控制台和终端 由于时代的发展计算机的硬件越来越便宜,现在都是一个人独占一台计算机(个人电脑),已经不再需要传统意义上的硬件终端。现在终端和控制台都由硬件概念,逐渐演化成了软件的概念。终端和控制台的界限也慢慢模糊了,复杂了,甚至控制台也变成了终端, 现在要怎么理解它们,推荐一篇文章,请自行前往搜看. << 彻底理解Linux的各种终端类型以及概念 >> 本篇内容与图中右上角的/dev/console那部分相关. 从鸿蒙内核视角来看,控制台和终端还是有很大差别的. 百篇博客分析.深挖内核地基 给鸿蒙内核源码加注释过程中,整理出以下文章。内容立足源码,常以生活场景打比方尽可能多的将内核知识点置入某种场景,具有画面感,容易理解记忆。说别人能听得懂的话很重要! 百篇博客绝不是百度教条式的在说一堆诘屈聱牙的概念,那没什么意思。更希望让内核变得栩栩如生,倍感亲切.确实有难度,自不量力,但已经出发,回头已是不可能的了。 😛 与代码有bug需不断debug一样,文章和注解内容会存在不少错漏之处,请多包涵,但会反复修正,持续更新,v**.xx 代表文章序号和修改的次数,精雕细琢,言简意赅,力求打造精品内容。 按功能模块: 前因后果 基础工具 加载运行 进程管理 总目录 调度故事 内存主奴 源码注释 源码结构 静态站点 注释文档 双向链表 位图管理 用栈方式 定时器 原子操作 时间管理 ELF格式 ELF解析 静态链接 重定位 进程映像 进程管理 进程概念 Fork 特殊进程 进程回收 信号生产 信号消费 Shell编辑 Shell解析 编译构建 进程通讯 内存管理 任务管理 编译环境 编译过程 环境脚本 构建工具 gn应用 忍者ninja 自旋锁 互斥锁 进程通讯 信号量 事件控制 消息队列 内存分配 内存管理 内存汇编 内存映射 内存规则 物理内存 时钟任务 任务调度 任务管理 调度队列 调度机制 线程概念 并发并行 CPU 系统调用 任务切换 文件系统 硬件架构 文件概念 文件系统 索引节点 挂载目录 根文件系统 字符设备 VFS 文件句柄 管道文件 控制台 汇编基础 汇编传参 工作模式 寄存器 异常接管 汇编汇总 中断切换 中断概念 中断管理 百万汉字注解.精读内核源码 四大码仓中文注解 . 定期同步官方代码 鸿蒙研究站( weharmonyos ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。

资源下载

更多资源
Mario

Mario

马里奥是站在游戏界顶峰的超人气多面角色。马里奥靠吃蘑菇成长,特征是大鼻子、头戴帽子、身穿背带裤,还留着胡子。与他的双胞胎兄弟路易基一起,长年担任任天堂的招牌角色。

腾讯云软件源

腾讯云软件源

为解决软件依赖安装时官方源访问速度慢的问题,腾讯云为一些软件搭建了缓存服务。您可以通过使用腾讯云软件源站来提升依赖包的安装速度。为了方便用户自由搭建服务架构,目前腾讯云软件源站支持公网访问和内网访问。

Nacos

Nacos

Nacos /nɑ:kəʊs/ 是 Dynamic Naming and Configuration Service 的首字母简称,一个易于构建 AI Agent 应用的动态服务发现、配置管理和AI智能体管理平台。Nacos 致力于帮助您发现、配置和管理微服务及AI智能体应用。Nacos 提供了一组简单易用的特性集,帮助您快速实现动态服务发现、服务配置、服务元数据、流量管理。Nacos 帮助您更敏捷和容易地构建、交付和管理微服务平台。

Spring

Spring

Spring框架(Spring Framework)是由Rod Johnson于2002年提出的开源Java企业级应用框架,旨在通过使用JavaBean替代传统EJB实现方式降低企业级编程开发的复杂性。该框架基于简单性、可测试性和松耦合性设计理念,提供核心容器、应用上下文、数据访问集成等模块,支持整合Hibernate、Struts等第三方框架,其适用范围不仅限于服务器端开发,绝大多数Java应用均可从中受益。

用户登录
用户注册