首页 文章 精选 留言 我的

精选列表

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

鸿蒙内核源码分析(文件系统篇) | 用图书管理说文件系统 | 百篇博客分析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 ) | 每天死磕一点点,原创不易,欢迎转载,请注明出处。若能支持点赞则更佳,感谢每一份支持。

资源下载

更多资源
优质分享App

优质分享App

近一个月的开发和优化,本站点的第一个app全新上线。该app采用极致压缩,本体才4.36MB。系统里面做了大量数据访问、缓存优化。方便用户在手机上查看文章。后续会推出HarmonyOS的适配版本。

腾讯云软件源

腾讯云软件源

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

Nacos

Nacos

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

Sublime Text

Sublime Text

Sublime Text具有漂亮的用户界面和强大的功能,例如代码缩略图,Python的插件,代码段等。还可自定义键绑定,菜单和工具栏。Sublime Text 的主要功能包括:拼写检查,书签,完整的 Python API , Goto 功能,即时项目切换,多选择,多窗口等等。Sublime Text 是一个跨平台的编辑器,同时支持Windows、Linux、Mac OS X等操作系统。

用户登录
用户注册