mirror of
https://github.com/DragonOS-Community/DragonOS.git
synced 2025-06-19 04:56:30 +00:00
merge main
This commit is contained in:
@ -36,3 +36,6 @@ help:
|
||||
.PHONY:
|
||||
html-multiversion:
|
||||
rm -rf ./$(BUILDDIR) && CURRENT_GIT_COMMIT_DIRTY=0 sphinx-multiversion $(SPHINXOPTS) "$(SOURCEDIR)" ./$(BUILDDIR)/html && cp -rf ./$(BUILDDIR)/html/master/* ./$(BUILDDIR)/html/
|
||||
|
||||
http_server:
|
||||
python3 -m http.server --directory $(BUILDDIR)/html
|
||||
|
@ -9,7 +9,8 @@
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
the-development-process
|
||||
how-to-contribute <https://community.dragonos.org/contributors/>
|
||||
|
||||
c-coding-style
|
||||
rust-coding-style
|
||||
conventional-commit
|
@ -1,155 +0,0 @@
|
||||
# 开发流程介绍
|
||||
|
||||
  作为一名想要参与开发的新人,您可能迫切想要了解如何参与开发,仔细阅读这篇文章将能帮助您了解整个开发流程,以及一些注意事项。
|
||||
|
||||
  注:本文参考了Linux文档中的一些思想、内容,非常感谢Linux社区的工作者们的经验!
|
||||
|
||||
## 1.概要
|
||||
|
||||
对于新人而言,参与开发的过程主要包括以下几步:
|
||||
|
||||
- **运行DragonOS**:按照文档:{ref}`构建DragonOS <_build_dragonos>`中的教程,编译DragonOS,并成功运行。在运行过程中,如果有任何的疑问,欢迎您在交流群或者BBS上进行反馈!
|
||||
- **联系Maintainer**:您可以通过邮箱<longjin@DragonOS.org>或者QQ`184829088`与龙进取得联系,或者是对应的模块的开发者进行联系(目前您可以通过发行日志上的邮箱与他们建立联系,在将来我们将编写一份“Maintainers List”以便于您能快速找到要联系的人)。
|
||||
为了节省您的时间,请简要说明:
|
||||
- 如何称呼您
|
||||
- 您目前掌握的技术能力
|
||||
- 您希望为DragonOS贡献什么功能,或者进行某个方面的开发,亦或者是想要按照社区的需要来参与新功能开发及bug的修复。
|
||||
- 如果您是来自高校/科研单位/企业/组织的代表,希望与社区开展合作研究、开发。那么,除使用QQ交流之外,还请麻烦您通过您的教师邮箱/学生邮箱/企业邮箱向<contact@DragonOS.org>发送一封相关内容的邮件!这么做的目的是为了确认您是来自您的单位,而不是网络上其他人员冒充您的身份。
|
||||
- **加入工作群**:在进一步了解,确认您愿意参与开发后,我们将邀请您加入工作群。
|
||||
- **开始开发**:正式开始代码开发工作。
|
||||
|
||||
:::{note}
|
||||
一些小功能的改进以及Bug修复并不一定需要提前与社区建立正式的联系,对于这些patch,您可以直接开发,然后在Github上进行Pull Request. 这也是可以的。
|
||||
|
||||
但是,如果您愿意的话,与Maintainer联系会是一个更好的选择。
|
||||
:::
|
||||
|
||||
## 2.开发过程是如何运作的?
|
||||
|
||||
  如今的DragonOS由于正处于开发的早期阶段,开发者数量不超过50人,因此,现在DragonOS的开发过程是通过比较松散的方式组织起来的。
|
||||
|
||||
### 2.1.版本发布周期
|
||||
|
||||
  自从2022年11月6日,DragonOS发布第一个版本以来,版本发布就被定义为15~21天发布一个更新版本。由于开发人员数量仍然较少,因此,目前这个时间是21天。我们将版本号定义为`x.y.z`的格式,每21天发布一个`z`版本. 在积累了2~3个月后,当DragonOS引入了足够的新功能,则发布一个`y`版本。请注意,我们仍未定义`x`版本的发行周期。当前,`x`版本号仍然是`0`。
|
||||
|
||||
  创建没有BUG的、具有尽可能少BUG的代码,是每个开发者的目标之一。我们希望在每个`y`版本发布时,能够修复已知的问题。然而,由于在操作系统中,影响问题的变量太多了,以至于我们只能尽全力去减少BUG,我们无法保证`y`版本不存在bug.
|
||||
|
||||
### 2.2.每个补丁的生命周期
|
||||
|
||||
  当您向DragonOS的仓库发起一次PR,那么这次PR就是一个补丁。我们会对您的补丁进行Review,以确保每个补丁都实现了一个希望在主线中进行的更改。并且,Maintainer或者感兴趣的小伙伴会对您的补丁提出修改意见。当时机合适时,您的代码将被合入主线。
|
||||
|
||||
  如果您的补丁的规模比较小,那么,它将会比较快的被合入主线。如果补丁的规模较大,或者存在一些争议,那么我们需要对其进行进一步的讨论及修改、审查,直到它符合要求。
|
||||
|
||||
  每个Patch都会经历这么一个过程(这只是一个概述,详细的描述请看后文):
|
||||
|
||||
- **设计**:在这个阶段,我们将明确,这个补丁将要实现什么功能,或者是解决什么问题,以及我们将要采用什么样的方式去完成它。通常来说,这项工作是开发者自己完成的。但是,**我们建议您,在设计了这个补丁之后,能够把您的设计方案公开,和大家讨论这项工作。** 闭门造车容易出错,在与大家沟通的过程中,则能及早的发现设计上的错误,从而节省很多的时间。
|
||||
- **代码编写**:经过了设计阶段,您已经能够明白自己要实现的到底是一个什么样的东西。您在这个阶段进行代码编写、调试。
|
||||
- **代码审查**:当完成代码编写后,您可以通过Github发起一个PR,然后您的补丁进入了代码审查阶段。在这一阶段,开发者们,或者是Maintainer会和您进行沟通交流,对您的代码进行评论,以及对发现的问题提出修改建议。
|
||||
- **合并主线**:如果一切顺利,那么代码将会被合并入主线。若该补丁在合并主线后,被发现具有较大的问题,那么它可能会被回退。重新进入前面的阶段,进行修改。
|
||||
- **长期维护**:虽然说,代码被合并之后,原来的开发人员可能会在很久一段时间后,不再理会这些代码,但是这种行为可能会给其他开发者们留下不好的印象。其实,当补丁被合并入主线后,其他开发人员会尝试继续维护这些代码,这样能够很大程度的减轻您的维护负担。但是,如果想要这些代码能够长期的被保留下来,持续的发光发热,那么离不开原始开发人员的支持(不然的话,后来的人可能难以理解、维护这些代码),这是一件很有意义的事情。
|
||||
|
||||
  对于没有参与过开源项目的同学来说,他们可能会想当然的,简单的把上述流程简化成**合并主线**这一个步骤,这样是不可取的。因为这样会导致很多问题,包括但不限于“写了代码但是效果很差”、“写的东西由于无法满足项目的需求,而不能被合并”。
|
||||
|
||||
### 2.3.开发工具
|
||||
|
||||
  从上面的描述可以看出,为了让开发人员们能高效地进行协作,那么必须使用版本控制工具来管理这个过程。目前,DragonOS使用Git来进行源代码管理。它是一个非常优秀的工具,这是不必多说的。对于每个开发者而言,Git的使用是一项必备的技能;哪怕您只是想学习DragonOS的源代码,您也需要git来获取、同步最新的代码。虽然Git的使用,对于新手来说,有些困难,但是经过一些时间的学习后,还是可以掌握的。
|
||||
|
||||
  git的官方网站为[https://git-scm.com/](https://git-scm.com/)
|
||||
|
||||
### 2.4.沟通交流
|
||||
|
||||
  DragonOS的主要开发工作是通过飞书以及bbs进行的。对于正准备参与的开发者而言,您可以加入我们的交流讨论QQ群,具体的群号可以在 {ref}`与社区建立联系 <get_contact_with_community>` 一文中找到。
|
||||
|
||||
  **何时使用即时通讯软件?** 我们在飞书上创建了工作群,为提高即时沟通的效率,这个群仅供那些真正有意愿、且正在进行或准备进行(能够保证会进行)代码开发的开发者们加入。
|
||||
|
||||
  **何时使用BBS?** 对于一些正式的、需要大家广泛参与,或者是能够帮助尚未参与开发的同学了解当前的开发进度的主题,请您在[https://bbs.DragonOS.org](https://bbs.DragonOS.org)上,使用类似于写信件一样的,正式的语言,完整地描述清楚您想表达的内容。这样有助于更多的人快速明白您要表达的是什么,也能提高整体的沟通效率。并且,bbs能够长期保存以往的帖子,这样后来者能更方便的了解“当初开发的时候,人们究竟是怎么想的”。
|
||||
|
||||
  **关于交流讨论会** 除由于法定节假日放假,或特殊情况以外,我们每周末都会召开线上交流讨论会,同步介绍每周的进展。社区成员可以在这里分享自己的方案设计或是一些操作系统相关的知识(分享的话,需要提前跟会议主持人提出,以便妥善安排)。
|
||||
|
||||
  **如何提问?** 下面这些建议能够帮助您与他人开展高效率的对话:
|
||||
|
||||
- **对于具有主题性的问题,在bbs上发帖进行讨论。** 这样能够让讨论更具有目标性。当谈论到新的主题的时候,请开一个新的帖子,并在原来的帖子中,添加对特定的子问题所在的主题的链接。
|
||||
- **请礼貌的交流。** 文明的语言能够减少不必要的冲突。技术意见上的冲突是思维的碰撞,但是如果涉及到了不文明的语言,或者在非技术层面,对他人进行攻击,这将破坏和谐讨论的氛围,这是我们反对的。如果有人试图激怒你,请忽略他的消息,别理他就好了。
|
||||
- **在提问之前,请确保您已经搜索了bbs以及互联网上的解决方案,并描述清楚您的问题的上下文情景、您的思考以及网络上的解决方案。** 一些开发人员会对“明显没有进行认真思考”的问题,表现出不耐烦的态度(因为未经思考的问题会浪费他们大量的时间)。
|
||||
- **当别人向您提问时**,请您耐心听他人的问题。如果您认为对方的问题过于简单或是未经思考,还请您为对方指个路,告诉对方,在哪些地方,使用什么样的方式搜索,能够得到对解决问题有帮助的资料。有时候,**新手需要的是一个指路人**,他会非常感谢您的!
|
||||
|
||||
### 2.5.如何入门开发?
|
||||
|
||||
  DragonOS原采用C语言进行开发,目前正在用Rust重构原有代码、开发新的模块,也就是说,除非您要进行对C语言代码的BUG修复,否则,其余的开发工作,我们都建议您通过Rust完成。因为,它能从语言层面解决那些让我们头疼的内存安全问题。从长期来看,能够提升开发效率以及软件质量。
|
||||
|
||||
  如何开发第一个补丁,是一个非常常见的问题。可以理解的是,个人开发者面对这样一个项目,常常会不知道从哪个地方开始入手。这是一件很正常的事情,因此我们建议您通过上文提到的方式,与社区建立联系,了解目前社区正在做什么,以及需要什么。
|
||||
|
||||
  对于一个新的参与者来说,我们建议您从这样一个步骤开始:
|
||||
|
||||
```text
|
||||
阅读文档,编译、运行DragonOS,并且尝试使用它目前已有的功能。
|
||||
```
|
||||
|
||||
  然后,您可以通过查看DragonOS的GitHub仓库的project面板,看看目前仍有哪些待解决的问题。可以肯定的是,永远不会缺少待解决的问题,您在解决这些问题的过程中,能够获得一些宝贵的经验。
|
||||
|
||||
## 3.早期设计
|
||||
|
||||
  对于软件开发而言,写代码永远不是第一步。在编写代码之前,进行一些必要的设计(提出架构、技术方案),是项目成功的基础工作。在新的补丁开发的早期,花费一些时间进行计划和沟通,往往能够在接下来的阶段节省更多的时间。
|
||||
|
||||
### 3.1.定义我们要解决的问题
|
||||
|
||||
  与大多数的工程项目一样,在DragonOS中进行开发,首先需要清晰的描述要解决的问题。只有精准的定义了问题,才能找到正确的解决方案。有时候,我们能很轻易的定义问题,比如“编写串口驱动程序,使得它能把屏幕上打印的字符,输出到串口”。
|
||||
|
||||
  但是,有时候,**我们容易将真正的问题与某个解决方案相混淆**,并且还没意识到这一点。
|
||||
|
||||
  在2022年10月时,我发现,在真机调试的时候,需要频繁的拔插硬盘(先连接到电脑,待数据拷贝完毕后,再连接到测试机)。我对这一过程非常的不满,因为很浪费时间。我的直觉想法是:“有没有一种设备,能够一头连接电脑,另一头连接测试机的SATA接口。从测试机的角度看来,这是一块硬盘;测试机对这块磁盘的操作,都转换为了对我的电脑上面的一个磁盘镜像文件的操作。”我的想法就是:“购买/定制一款设备,能够实现上面的这个功能,那就能解决频繁拔插硬盘的烦恼了!”然后我为了寻找这样的设备,询问了一些硬件公司,他们的开价都在2万元左右。
|
||||
|
||||
  我在上面的这个过程中,就犯了一个错误:将真正的问题与某个解决方案相混淆了。真正的问题是:“解决需要频繁的拔插硬盘”,但是,在我的思考的过程中,不知不觉间,将问题转换成了“如何实现一款具有硬盘模拟功能的设备”。后面这个问题,只是某个解决方案下,需要解决的问题,并不是我们要解决的根本问题。
|
||||
|
||||
  对于要解决的根本问题,事实上有更好的解决方案:“制作一个类似于开关一样的转换器,当数据从电脑拷贝到磁盘后,把开关拨向另一边,使得电路与测试机接通”。这个方案的成本估摸着就十几二十块钱。
|
||||
|
||||
  上面的这个故事,告诉我们的是,**在早期设计阶段,我们应当关注的是问题本身——而不是特定的解决方案**。
|
||||
|
||||
  我们需要关注系统的稳定性、长期的可维护性,解决方案必须考虑到这两点。由于系统比较复杂,因此,请您在开始编码之前,与社区的小伙伴讨论您的设计方案,以便您的方案能充分地,从全局的角度,考虑到系统的稳定性、可维护性。
|
||||
|
||||
  **因此,在开发的早期,我们应当对以下三个问题,拥有一个答案**:
|
||||
|
||||
- 要解决的本质问题是什么?
|
||||
- 这个问题会影响哪些方面/哪些用户?提出的解决方案应当解决哪些用例、场景?
|
||||
- DragonOS目前在解决该问题的方面,具有哪些不足/问题?
|
||||
|
||||
  只有考虑清楚了上面三个问题,讨论的解决方案才是有意义的。这是一个架构设计的过程,需要进行仔细的思考。尽管我们目前提倡敏捷开发,但是前期的架构设计仍然是非常重要的。
|
||||
|
||||
### 3.2.早期讨论
|
||||
|
||||
  在实施开发之前,与社区的成员们进行讨论是非常有意义的。这能够通过多种方式节省您的时间,并减少许多不必要的麻烦:
|
||||
|
||||
- DragonOS可能以您不知道、不理解的方式,已经解决了相关的问题。DragonOS里面的一些特性、功能细节不是很明显,他们不一定会被写进文档。也许这些细节只是在某个不起眼的注释里面提到了,因此您很难注意到这些。这种细节可能只有一些开发人员知道。因此,与社区沟通能够避免您进行重复的工作。
|
||||
- 您提出的解决方案中,可能会有一些东西,由于一些原因(比如方案中的一些设计会在将来造成问题、方案的架构设计具有明显缺陷),无法合入主线。
|
||||
- 其他的开发人员可能已经考虑过这个问题;他们可能有更好的解决方案,或者是更好的想法。并且,他们可能愿意帮助你一起完善你的解决方案。
|
||||
|
||||
  Linux文档中提到:闭门造车式的设计和开发,所产生的代码总会有问题,这些问题只有在发布到社区里面的时候才会暴露出来。因此,我们必须吸取前人之鉴,通过与社区开发人员进行早期讨论,从而避免大量的痛苦和额外的工作。
|
||||
|
||||
|
||||
### 3.3.在何时发布帖子?
|
||||
|
||||
  如果可能的话,在开发的早期阶段发布您的计划、设计,会是一个不错的选择。发帖的时候,您可以描述您正在解决的问题,以及已经制定的一些计划。包括但不限于:如何将设计付诸实施。您在社区内发布帖子,不仅能够帮助您获得一些有用的建议,也能帮助整个DragonOS社区提供有用的信息,使得社区沟通更高效。
|
||||
|
||||
  在这个阶段,可能您发布的帖子并不会引来很多评论,这并不一定意味着您做的不好,或者大家对您所做的工作不感兴趣。当然,也不能就此认为您的设计、想法不存在问题。可能只是因为大家比较忙,看了您的帖子之后,了解到了您的工作,但是大家并不一定有时间进行回复。(但是事实上您发布的信息对他人来说是有用的)
|
||||
|
||||
  在这种情况下,请不要气馁,您最好的做法就是,继续您的工作,并且不时地在您的帖子下分享您的工作,这样能够让社区的成员们随时了解到您的最新进展。
|
||||
|
||||
### 3.4.获得您所在的组织的支持
|
||||
|
||||
  如果您对DragonOS的开发工作,是在您的公司内完成的。那么,很显然,在您把计划、代码发布到社区论坛之前,您必须取得您的经理或上级的许可。
|
||||
|
||||
  同时,请注意,根据我们的授权许可,基于DragonOS操作系统的内核、官方开源的用户库而开发的代码,或者为DragonOS操作系统本身而开发的代码,根据开源授权许可,必须同样以GPLv2协议进行授权发布。如果您所在的组织,违背了GPLv2协议中的要求,以除GPLv2以外的协议开放源代码,或者是进行“闭源使用”,那么DragonOS社区对您的公司/组织所授予的使用DragonOS源代码的授权,将会被自动撤销。这将会面临一系列的法律问题。因此,在这个问题上,公司的管理人员、法律人员如果能越早地就公司要在DragonOS中开发的软件项目达成一致,将能促进您的公司在该项目上的进展。
|
||||
|
||||
  如果您的公司的项目/或者是您研究的项目根据您所在组织的保密规定,不能将其进行过早的披露,那也没有问题。只要您的公司能够确保这部分代码,在其编译而成的二进制产品被发布之时,按照GPLv2协议进行开源,并向开源社区贡献这部分代码即可。
|
||||
|
||||
## 4.如何正确的编写代码
|
||||
|
||||
## 5.发起Pull Request
|
||||
|
||||
## 6.后期跟进
|
||||
|
||||
## 7.另外的一些话题
|
||||
|
||||
## 8.更多信息
|
||||
|
||||
## 9.结语
|
@ -8,9 +8,7 @@
|
||||
|
||||
社区公共邮箱:contact@DragonOS.org
|
||||
|
||||
DragonOS社区负责人: 龙进
|
||||
|
||||
工作邮箱: longjin@DragonOS.org
|
||||
社区管理人员信息:https://community.dragonos.org/governance/staff-info.html
|
||||
|
||||
开发交流QQ群: 115763565
|
||||
|
||||
@ -37,16 +35,5 @@ DragonOS社区的捐赠信息将按年进行公开。赞助商、赞助者信息
|
||||
社区管理、财务及法务主体
|
||||
-------------------------
|
||||
|
||||
DragonOS社区的管理、财务及法务主体为:灵高计算机系统(广州)有限公司。
|
||||
|
||||
我们是一家开源公司,我们坚信,开源能为我国将来的IT,打下更好的基础。我们也通过其他业务创收,投入到DragonOS的研发之中。
|
||||
|
||||
公司负责DragonOS社区的运营、财务、法务事项处理工作。
|
||||
|
||||
地址:广东省广州市番禺区小谷围街广州大学城华南理工大学大学城校区
|
||||
|
||||
邮件:contact@DragonOS.org
|
||||
|
||||
官网:https://ringotek.com.cn
|
||||
|
||||
灵高是DragonOS社区为满足相关监管合规要求,成立的 **非营利性质** 的单位。详情请见:https://ringotek.com.cn
|
||||
|
||||
|
@ -30,8 +30,11 @@
|
||||
kernel/debug/index
|
||||
kernel/ktest/index
|
||||
kernel/cpu_arch/index
|
||||
kernel/container/index
|
||||
kernel/libs/index
|
||||
kernel/net/index
|
||||
kernel/trace/index
|
||||
|
||||
|
||||
|
||||
.. toctree::
|
||||
|
13
docs/kernel/container/index.rst
Normal file
13
docs/kernel/container/index.rst
Normal file
@ -0,0 +1,13 @@
|
||||
====================================
|
||||
容器化
|
||||
====================================
|
||||
|
||||
这里是DragonOS中,与容器化相关的说明文档。
|
||||
|
||||
主要包括namespace,overlayfs和cgroup
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 2
|
||||
|
||||
namespaces/index
|
||||
filesystem/unionfs/index
|
14
docs/kernel/container/namespaces/index.rst
Normal file
14
docs/kernel/container/namespaces/index.rst
Normal file
@ -0,0 +1,14 @@
|
||||
====================================
|
||||
名称空间
|
||||
====================================
|
||||
|
||||
DragonOS的namespaces目前支持pid_namespace和mnt_namespace 预计之后会继续完善
|
||||
namespace是容器化实现过程中的重要组成部分
|
||||
|
||||
由于目前os是单用户,user_namespace为全局静态
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
pid_namespace
|
||||
mnt_namespace
|
19
docs/kernel/container/namespaces/mnt_namespace.md
Normal file
19
docs/kernel/container/namespaces/mnt_namespace.md
Normal file
@ -0,0 +1,19 @@
|
||||
# 挂载名称空间
|
||||
|
||||
## 底层架构
|
||||
|
||||
pcb -> nsproxy -> mnt_namespace
|
||||
|
||||
每一个挂载文件系统都有自立独立的挂载点,表现在数据结构上是一个挂载的红黑树,每一个名称空间中挂载是独立的,所以文件系统的挂载和卸载不会影响别的
|
||||
|
||||
## 系统调用接口
|
||||
|
||||
|
||||
- clone
|
||||
- CLONE_NEWNS用于创建一个新的 MNT 命名空间。提供独立的文件系统挂载点
|
||||
- unshare
|
||||
- 使用 CLONE_NEWPID 标志调用 unshare() 后,后续创建的所有子进程都将在新的命名空间中运行。
|
||||
- setns
|
||||
- 将进程加入到指定的名称空间
|
||||
- chroot
|
||||
- 将当前进程的根目录更改为指定的路径,提供文件系统隔离。
|
21
docs/kernel/container/namespaces/pid_namespace.md
Normal file
21
docs/kernel/container/namespaces/pid_namespace.md
Normal file
@ -0,0 +1,21 @@
|
||||
# 进程名称空间
|
||||
:::{note} 本文作者:操丰毅 1553389239@qq.com
|
||||
|
||||
2024年10月30日 :::
|
||||
pid_namespace 是内核中的一种名称空间,用于实现进程隔离,允许在不同的名称空间中运行的进程有独立的pid试图
|
||||
|
||||
## 底层架构
|
||||
|
||||
pcb -> nsproxy -> pid_namespace
|
||||
- pid_namespace 内有独立的一套进程分配器,以及孤儿进程回收器,独立管理内部的pid
|
||||
- 不同进程的详细信息都存放在proc文件系统中,里面的找到对应的pid号里面的信息都在pid中,记录的是pid_namespace中的信息
|
||||
- pid_namespace等限制由ucount来控制管理
|
||||
|
||||
## 系统调用接口
|
||||
|
||||
- clone
|
||||
- CLONE_NEWPID用于创建一个新的 PID 命名空间。使用这个标志时,子进程将在新的 PID 命名空间内运行,进程 ID 从 1 开始。
|
||||
- unshare
|
||||
- 使用 CLONE_NEWPID 标志调用 unshare() 后,后续创建的所有子进程都将在新的命名空间中运行。
|
||||
- getpid
|
||||
- 在命名空间中调用 getpid() 会返回进程在当前 PID 命名空间中的进程 ID
|
@ -13,4 +13,5 @@ todo: 由于文件系统模块重构,文档暂时不可用,预计在2023年4
|
||||
vfs/index
|
||||
sysfs
|
||||
kernfs
|
||||
unionfs/index
|
||||
|
||||
|
10
docs/kernel/filesystem/unionfs/index.rst
Normal file
10
docs/kernel/filesystem/unionfs/index.rst
Normal file
@ -0,0 +1,10 @@
|
||||
====================================
|
||||
联合文件系统
|
||||
====================================
|
||||
Union Filesystem:
|
||||
OverlayFS 将多个文件系统(称为“层”)合并为一个逻辑文件系统,使用户看到一个统一的目录结构。
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
|
||||
overlayfs
|
26
docs/kernel/filesystem/unionfs/overlayfs.md
Normal file
26
docs/kernel/filesystem/unionfs/overlayfs.md
Normal file
@ -0,0 +1,26 @@
|
||||
# overlayfs
|
||||
|
||||
OverlayFs是目前使用最多的联合文件系统,原理简单方便使用,主要用于容器中
|
||||
在 Docker 中,OverlayFS 是默认的存储驱动之一。Docker 为每个容器创建一个独立的上层目录,而所有容器共享同一个下层镜像文件。这样的设计使得容器之间的资源共享更加高效,同时减少了存储需求。
|
||||
## 架构设计
|
||||
overlayfs主要有两个层,以及一个虚拟的合并层
|
||||
- Lower Layer(下层):通常是 只读 文件系统。可以包含多层。
|
||||
- Upper Layer(上层):为 可写层,所有的写操作都会在这一层上进行。
|
||||
- Merged Layer(合并层):上层和下层的逻辑视图合并后,向用户呈现的最终文件系统。
|
||||
|
||||
|
||||
## 工作原理
|
||||
- 读取操作:
|
||||
- OverlayFS 会优先从 Upper Layer 读取文件。如果文件不存在于上层,则读取 Lower Layer 中的内容。
|
||||
- 写入操作:
|
||||
- 如果一个文件位于 Lower Layer 中,并尝试写入该文件,系统会将其 copy-up 到 Upper Layer 并在上层写入。如果文件已经存在于 Upper Layer,则直接在该层写入。
|
||||
- 删除操作:
|
||||
- 当删除文件时,OverlayFS 会在上层创建一个标记为 whiteout 的条目,这会隐藏下层的文件。
|
||||
|
||||
## Copy-up
|
||||
- 写时拷贝
|
||||
当一个文件从 下层 被修改时,它会被复制到 上层(称为 copy-up)。之后的所有修改都会发生在上层的文件副本上。
|
||||
|
||||
|
||||
## 实现逻辑
|
||||
通过构建ovlInode来实现indexnode这个trait来代表上层或者下层的inode,具体的有关文件文件夹的操作都在
|
324
docs/kernel/trace/eBPF.md
Normal file
324
docs/kernel/trace/eBPF.md
Normal file
@ -0,0 +1,324 @@
|
||||
# eBPF
|
||||
|
||||
> 作者: 陈林峰
|
||||
>
|
||||
> Email: chenlinfeng25@outlook.com
|
||||
|
||||
## 概述
|
||||
|
||||
eBPF 是一项革命性的技术,起源于 Linux 内核,它可以在特权上下文中(如操作系统内核)运行沙盒程序。它用于安全有效地扩展内核的功能,而无需通过更改内核源代码或加载内核模块的方式来实现。
|
||||
|
||||
从历史上看,由于内核具有监督和控制整个系统的特权,操作系统一直是实现可观测性、安全性和网络功能的理想场所。同时,由于操作系统内核的核心地位和对稳定性和安全性的高要求,操作系统内核很难快速迭代发展。因此在传统意义上,与在操作系统本身之外实现的功能相比,操作系统级别的创新速度要慢一些。
|
||||
|
||||
eBPF 从根本上改变了这个方式。通过允许在操作系统中运行沙盒程序的方式,应用程序开发人员可以运行 eBPF 程序,以便在运行时向操作系统添加额外的功能。然后在 JIT 编译器和验证引擎的帮助下,操作系统确保它像本地编译的程序一样具备安全性和执行效率。这引发了一股基于 eBPF 的项目热潮,它们涵盖了广泛的用例,包括下一代网络实现、可观测性和安全功能等领域。
|
||||
|
||||
## eBPF In DragonOS
|
||||
|
||||
在一个新的OS上添加eBPF的支持需要了解eBPF的运行过程,通常,eBPF需要用户态工具和内核相关基础设施配合才能发挥其功能。而新的OS通常会兼容Linux上的应用程序,这可以进一步简化对用户态工具的移植工作,只要内核实现相关的系统调用和功能,就可以配合已有的工具完成eBPF的支持。
|
||||
|
||||
## eBPF的运行流程
|
||||
|
||||

|
||||
|
||||
如图所示,eBPF程序的运行过程分为三个主要步骤:
|
||||
|
||||
1. 源代码->二进制
|
||||
1. 用户可以使用python/C/Rust编写eBPF程序,并使用相关的工具链编译源代码到二进制程序
|
||||
2. 这个步骤中,用户需要合理使用helper函数丰富eBPF程序功能
|
||||
2. 加载eBPF程序
|
||||
1. 用户态的工具库会封装内核提供的系统调用接口,以简化用户的工作。用户态工具对eBPF程序经过预处理后发出系统调用,请求内核加载eBPF程序。
|
||||
1. 内核首先会对eBPF程序进行验证,检查程序的正确性和合法性,同时也会对程序做进一步的处理
|
||||
1. 内核会根据用户请求,将eBPF程序附加到内核的挂载点上(kprobe/uprobe/trace_point)
|
||||
1. 在内核运行期间,当这些挂载点被特定的事件触发, eBPF程序就会被执行
|
||||
3. 数据交互
|
||||
1. eBPF程序可以收集内核的信息,用户工具可以选择性的获取这些信息
|
||||
2. eBPF程序可以直接将信息输出到文件中,用户工具通过读取和解析文件中的内容拿到信息
|
||||
3. eBPF程序通过Map在内核和用户态之间共享和交换数据
|
||||
|
||||
|
||||
|
||||
## 用户态支持
|
||||
|
||||
用户态的eBPF工具库有很多,比如C的libbpf,python的bcc, Rust的Aya,总体来说,这些工具的处理流程都大致相同。DragonOS当前支持[Aya](https://github.com/aya-rs/aya)框架编写的eBPF程序,以Aya为例,用户态的工具的处理过程如下:
|
||||
|
||||
1. 提供eBPF使用的helper函数和Map抽象,方便实现eBPF程序
|
||||
2. 处理编译出来的eBPF程序,调用系统调用创建Map,获得对应的文件描述符
|
||||
3. 根据需要,更新Map的值(.data)
|
||||
4. 根据重定位信息,对eBPF程序的相关指令做修改
|
||||
5. 根据内核版本,对eBPF程序中的bpf to bpf call进行处理
|
||||
6. 加载eBPF程序到内核中
|
||||
7. 对系统调用封装,提供大量的函数帮助访问eBPF的信息并与内核交互
|
||||
|
||||
DragonOS对Aya 库的支持并不完整。通过对Aya库的删减,我们实现了一个较小的[tiny-aya](https://github.com/DragonOS-Community/tiny-aya)。为了确保后期对Aya的兼容,tiny-aya只对Aya中的核心工具aya做了修改**,其中一些函数被禁用,因为这些函数的所需的系统调用或者文件在DragonOS中还未实现**。
|
||||
|
||||
### Tokio
|
||||
|
||||
Aya需要使用异步运行时,通过增加一些系统调用和修复一些错误DragonOS现在已经支持基本的tokio运行时。
|
||||
|
||||
### 使用Aya创建eBPF程序
|
||||
|
||||
与Aya官方提供的[文档](https://aya-rs.dev/book/start/development/)所述,只需要根据其流程安装对应的Rust工具链,就可以按照模板创建eBPF项目。以当前实现的`syscall_ebf`为例,这个程序的功能是统计系统调用的次数,并将其存储在一个HashMap中。
|
||||
|
||||
```
|
||||
├── Cargo.toml
|
||||
├── README.md
|
||||
├── syscall_ebpf
|
||||
├── syscall_ebpf-common
|
||||
├── syscall_ebpf-ebpf
|
||||
└── xtask
|
||||
```
|
||||
|
||||
在user/app目录中,项目结构如上所示:
|
||||
|
||||
- `syscall_ebpf-ebpf`是 eBPF代码的实现目录,其会被编译到字节码
|
||||
- `syscall_ebpf-common` 是公共库,方便内核和用户态进行信息交互
|
||||
- `syscall_ebpf` 是用户态程序,其负责加载eBPF程序并获取eBPF程序产生的数据
|
||||
- `xtask` 是一个命令行工具,方便用户编译和运行用户态程序
|
||||
|
||||
为了在DragonOS中运行用户态程序,暂时还不能直接使用模板创建的项目:
|
||||
|
||||
1. 这个项目不符合DragonOS对用户程序的项目结构要求,当然这可以通过稍加修改完成
|
||||
2. 因为DragonOS对tokio运行时的支持还不是完整体,需要稍微修改一下使用方式
|
||||
|
||||
```
|
||||
#[tokio::main(flavor = "current_thread")]
|
||||
async fn main() -> Result<(), Box<dyn Error>> {
|
||||
```
|
||||
|
||||
3. 因为对Aya支持不是完整体,因此项目依赖的aya和aya-log需要换成tiny-aya中的实现。
|
||||
|
||||
```
|
||||
[dependencies]
|
||||
aya = { git = "https://github.com/DragonOS-Community/tiny-aya.git" }
|
||||
aya-log = { git = "https://github.com/DragonOS-Community/tiny-aya.git" }
|
||||
```
|
||||
|
||||
只需要稍加修改,就可以利用Aya现有的工具完成eBPF程序的实现。
|
||||
|
||||
## 内核态支持
|
||||
|
||||
内核态支持主要为三个部分:
|
||||
|
||||
1. kprobe实现:位于目录`kernel/crates/kprobe`
|
||||
2. rbpf运行时:位于目录`kernel/crates/rbpf`
|
||||
3. 系统调用支持
|
||||
4. helper函数支持
|
||||
|
||||
### rbpf
|
||||
|
||||
由于rbpf之前只是用于运行一些简单的eBPF程序,其需要通过一些修改才能运行更复杂的程序。
|
||||
|
||||
1. 增加bpf to bpf call 的支持:通过增加新的栈抽象和保存和恢复必要的寄存器数据
|
||||
2. 关闭内部不必要的内存检查,这通常由内核的验证器完成
|
||||
3. 增加带所有权的数据结构避免生命周期的限制
|
||||
|
||||
|
||||
|
||||
### 系统调用
|
||||
|
||||
eBPF相关的系统调用都集中在`bpf()` 上,通过参数cmd来进一步区分功能,目前对其支持如下:
|
||||
|
||||
```rust
|
||||
pub fn bpf(cmd: bpf_cmd, attr: &bpf_attr) -> Result<usize> {
|
||||
let res = match cmd {
|
||||
// Map related commands
|
||||
bpf_cmd::BPF_MAP_CREATE => map::bpf_map_create(attr),
|
||||
bpf_cmd::BPF_MAP_UPDATE_ELEM => map::bpf_map_update_elem(attr),
|
||||
bpf_cmd::BPF_MAP_LOOKUP_ELEM => map::bpf_lookup_elem(attr),
|
||||
bpf_cmd::BPF_MAP_GET_NEXT_KEY => map::bpf_map_get_next_key(attr),
|
||||
bpf_cmd::BPF_MAP_DELETE_ELEM => map::bpf_map_delete_elem(attr),
|
||||
bpf_cmd::BPF_MAP_LOOKUP_AND_DELETE_ELEM => map::bpf_map_lookup_and_delete_elem(attr),
|
||||
bpf_cmd::BPF_MAP_LOOKUP_BATCH => map::bpf_map_lookup_batch(attr),
|
||||
bpf_cmd::BPF_MAP_FREEZE => map::bpf_map_freeze(attr),
|
||||
// Program related commands
|
||||
bpf_cmd::BPF_PROG_LOAD => prog::bpf_prog_load(attr),
|
||||
// Object creation commands
|
||||
bpf_cmd::BPF_BTF_LOAD => {
|
||||
error!("bpf cmd {:?} not implemented", cmd);
|
||||
return Err(SystemError::ENOSYS);
|
||||
}
|
||||
ty => {
|
||||
unimplemented!("bpf cmd {:?} not implemented", ty)
|
||||
}
|
||||
};
|
||||
res
|
||||
}
|
||||
```
|
||||
|
||||
其中对创建Map命令会再次细分,以确定具体的Map类型,目前我们对通用的Map基本添加了支持:
|
||||
|
||||
```rust
|
||||
bpf_map_type::BPF_MAP_TYPE_ARRAY
|
||||
bpf_map_type::BPF_MAP_TYPE_PERCPU_ARRAY
|
||||
bpf_map_type::BPF_MAP_TYPE_PERF_EVENT_ARRAY
|
||||
bpf_map_type::BPF_MAP_TYPE_HASH
|
||||
bpf_map_type::BPF_MAP_TYPE_PERCPU_HASH
|
||||
bpf_map_type::BPF_MAP_TYPE_QUEUE
|
||||
bpf_map_type::BPF_MAP_TYPE_STACK
|
||||
bpf_map_type::BPF_MAP_TYPE_LRU_HASH
|
||||
bpf_map_type::BPF_MAP_TYPE_LRU_PERCPU_HASH
|
||||
|
||||
bpf_map_type::BPF_MAP_TYPE_CPUMAP
|
||||
| bpf_map_type::BPF_MAP_TYPE_DEVMAP
|
||||
| bpf_map_type::BPF_MAP_TYPE_DEVMAP_HASH => {
|
||||
error!("bpf map type {:?} not implemented", map_meta.map_type);
|
||||
Err(SystemError::EINVAL)?
|
||||
}
|
||||
```
|
||||
|
||||
所有的Map都会实现定义好的接口,这个接口参考Linux的实现定义:
|
||||
|
||||
```rust
|
||||
pub trait BpfMapCommonOps: Send + Sync + Debug + CastFromSync {
|
||||
/// Lookup an element in the map.
|
||||
///
|
||||
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_lookup_elem/
|
||||
fn lookup_elem(&mut self, _key: &[u8]) -> Result<Option<&[u8]>> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Update an element in the map.
|
||||
///
|
||||
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_update_elem/
|
||||
fn update_elem(&mut self, _key: &[u8], _value: &[u8], _flags: u64) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Delete an element from the map.
|
||||
///
|
||||
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_map_delete_elem/
|
||||
fn delete_elem(&mut self, _key: &[u8]) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// For each element in map, call callback_fn function with map,
|
||||
/// callback_ctx and other map-specific parameters.
|
||||
///
|
||||
/// See https://ebpf-docs.dylanreimerink.nl/linux/helper-function/bpf_for_each_map_elem/
|
||||
fn for_each_elem(&mut self, _cb: BpfCallBackFn, _ctx: *const u8, _flags: u64) -> Result<u32> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Look up an element with the given key in the map referred to by the file descriptor fd,
|
||||
/// and if found, delete the element.
|
||||
fn lookup_and_delete_elem(&mut self, _key: &[u8], _value: &mut [u8]) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// perform a lookup in percpu map for an entry associated to key on cpu.
|
||||
fn lookup_percpu_elem(&mut self, _key: &[u8], cpu: u32) -> Result<Option<&[u8]>> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Get the next key in the map. If key is None, get the first key.
|
||||
///
|
||||
/// Called from syscall
|
||||
fn get_next_key(&self, _key: Option<&[u8]>, _next_key: &mut [u8]) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Push an element value in map.
|
||||
fn push_elem(&mut self, _value: &[u8], _flags: u64) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Pop an element value from map.
|
||||
fn pop_elem(&mut self, _value: &mut [u8]) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Peek an element value from map.
|
||||
fn peek_elem(&self, _value: &mut [u8]) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Freeze the map.
|
||||
///
|
||||
/// It's useful for .rodata maps.
|
||||
fn freeze(&self) -> Result<()> {
|
||||
Err(SystemError::ENOSYS)
|
||||
}
|
||||
/// Get the first value pointer.
|
||||
fn first_value_ptr(&self) -> *const u8 {
|
||||
panic!("value_ptr not implemented")
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
联通eBPF和kprobe的系统调用是[`perf_event_open`](https://man7.org/linux/man-pages/man2/perf_event_open.2.html),这个系统调用在Linux中非常复杂,因此Dragon中并没有按照Linux进行实现,目前只支持其中两个功能:
|
||||
|
||||
|
||||
|
||||
```rust
|
||||
match args.type_ {
|
||||
// Kprobe
|
||||
// See /sys/bus/event_source/devices/kprobe/type
|
||||
perf_type_id::PERF_TYPE_MAX => {
|
||||
let kprobe_event = kprobe::perf_event_open_kprobe(args);
|
||||
Box::new(kprobe_event)
|
||||
}
|
||||
perf_type_id::PERF_TYPE_SOFTWARE => {
|
||||
// For bpf prog output
|
||||
assert_eq!(args.config, perf_sw_ids::PERF_COUNT_SW_BPF_OUTPUT);
|
||||
assert_eq!(
|
||||
args.sample_type,
|
||||
Some(perf_event_sample_format::PERF_SAMPLE_RAW)
|
||||
);
|
||||
let bpf_event = bpf::perf_event_open_bpf(args);
|
||||
Box::new(bpf_event)
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- 其中一个`PERF_TYPE_SOFTWARE`是用来创建软件定义的事件,`PERF_COUNT_SW_BPF_OUTPUT` 确保这个事件用来采集bpf的输出。
|
||||
- `PERF_TYPE_MAX` 通常指示创建kprobe/uprobe事件,也就是用户程序使用kprobe的途径之一,用户程序可以将eBPF程序绑定在这个事件上
|
||||
|
||||
同样的,perf不同的事件也实现定义的接口:
|
||||
|
||||
```rust
|
||||
pub trait PerfEventOps: Send + Sync + Debug + CastFromSync + CastFrom {
|
||||
fn mmap(&self, _start: usize, _len: usize, _offset: usize) -> Result<()> {
|
||||
panic!("mmap not implemented for PerfEvent");
|
||||
}
|
||||
fn set_bpf_prog(&self, _bpf_prog: Arc<File>) -> Result<()> {
|
||||
panic!("set_bpf_prog not implemented for PerfEvent");
|
||||
}
|
||||
fn enable(&self) -> Result<()> {
|
||||
panic!("enable not implemented");
|
||||
}
|
||||
fn disable(&self) -> Result<()> {
|
||||
panic!("disable not implemented");
|
||||
}
|
||||
fn readable(&self) -> bool {
|
||||
panic!("readable not implemented");
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
这个接口目前并不稳定。
|
||||
|
||||
### helper函数支持
|
||||
|
||||
用户态工具通过系统调用和内核进行通信,完成eBPF数据的设置、交换。在内核中,eBPF程序的运行也需要内核的帮助,单独的eBPF程序并没有什么太大的用处,因此其会调用内核提供的`helper` 函数完成对内核资源的访问。
|
||||
|
||||
目前已经支持的大多数`helper` 函数是与Map操作相关:
|
||||
|
||||
```rust
|
||||
/// Initialize the helper functions.
|
||||
pub fn init_helper_functions() {
|
||||
let mut map = BTreeMap::new();
|
||||
unsafe {
|
||||
// Map helpers::Generic map helpers
|
||||
map.insert(1, define_func!(raw_map_lookup_elem));
|
||||
map.insert(2, define_func!(raw_map_update_elem));
|
||||
map.insert(3, define_func!(raw_map_delete_elem));
|
||||
map.insert(164, define_func!(raw_map_for_each_elem));
|
||||
map.insert(195, define_func!(raw_map_lookup_percpu_elem));
|
||||
// map.insert(93,define_func!(raw_bpf_spin_lock);
|
||||
// map.insert(94,define_func!(raw_bpf_spin_unlock);
|
||||
// Map helpers::Perf event array helpers
|
||||
map.insert(25, define_func!(raw_perf_event_output));
|
||||
// Probe and trace helpers::Memory helpers
|
||||
map.insert(4, define_func!(raw_bpf_probe_read));
|
||||
// Print helpers
|
||||
map.insert(6, define_func!(trace_printf));
|
||||
|
||||
// Map helpers::Queue and stack helpers
|
||||
map.insert(87, define_func!(raw_map_push_elem));
|
||||
map.insert(88, define_func!(raw_map_pop_elem));
|
||||
map.insert(89, define_func!(raw_map_peek_elem));
|
||||
}
|
||||
BPF_HELPER_FUN_SET.init(map);
|
||||
}
|
||||
```
|
||||
|
BIN
docs/kernel/trace/ebpf_flow.png
Normal file
BIN
docs/kernel/trace/ebpf_flow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 65 KiB |
11
docs/kernel/trace/index.rst
Normal file
11
docs/kernel/trace/index.rst
Normal file
@ -0,0 +1,11 @@
|
||||
内核跟踪机制
|
||||
====================================
|
||||
|
||||
内核跟踪机制由很多功能构成, 比如kprobe/uprobe/tracepoint/ftrace等, 以及用于扩展内核可观测性的eBPF,内核当前支持kprobe和eBPF, 本章将介绍这两种机制。
|
||||
|
||||
.. toctree::
|
||||
:maxdepth: 1
|
||||
:caption: 目录
|
||||
|
||||
eBPF
|
||||
kprobe
|
57
docs/kernel/trace/kprobe.md
Normal file
57
docs/kernel/trace/kprobe.md
Normal file
@ -0,0 +1,57 @@
|
||||
# kprobe
|
||||
|
||||
> 作者: 陈林峰
|
||||
>
|
||||
> Email: chenlinfeng25@outlook.com
|
||||
|
||||
## 概述
|
||||
|
||||
Linux kprobes调试技术是内核开发者们专门为了便于跟踪内核函数执行状态所设计的一种轻量级内核调试技术。利用kprobes技术,内核开发人员可以在内核的绝大多数指定函数中动态的插入探测点来收集所需的调试状态信息而基本不影响内核原有的执行流程。
|
||||
|
||||
kprobes技术依赖硬件架构相关的支持,主要包括CPU的异常处理和单步调试机制,前者用于让程序的执行流程陷入到用户注册的回调函数中去,而后者则用于单步执行被探测点指令。需要注意的是,在一些架构上硬件并不支持单步调试机制,这可以通过一些软件模拟的方法解决(比如riscv)。
|
||||
|
||||
|
||||
|
||||
## kprobe工作流程
|
||||
|
||||
<img src="./kprobe_flow.png" style="zoom: 67%;" alt="xxx"/>
|
||||
|
||||
|
||||
|
||||
1. 注册kprobe后,注册的每一个kprobe对应一个kprobe结构体,该结构中记录着探测点的位置,以及该探测点本来对应的指令。
|
||||
2. 探测点的位置被替换成了一条异常的指令,这样当CPU执行到探测点位置时会陷入到异常态,在x86_64上指令是int3(如果kprobe经过优化后,指令是jmp)
|
||||
3. 当执行到异常指令时,系统换检查是否是kprobe 安装的异常,如果是,就执行kprobe的pre_handler,然后利用CPU提供的单步调试(single-step)功能,设置好相应的寄存器,将下一条指令设置为插入点处本来的指令,从异常态返回;
|
||||
4. 再次陷入异常态。上一步骤中设置了single-step相关的寄存器,所以原指令刚一执行,便会再次陷入异常态,此时将single-step清除,并且执行post_handler,然后从异常态安全返回.
|
||||
5. 当卸载kprobe时,探测点原来的指令会被恢复回去。
|
||||
|
||||
|
||||
|
||||
内核目前对x86和riscv64都进行了支持,由于 riscv64 没有单步执行模式,因此我们使用 break 异常来进行模拟,在保存探测点指令时,我们会额外填充一条 break 指令,这样就可以使得在riscv64架构上,在执行完原指令后,会再次触发break陷入异常。
|
||||
|
||||
## kprobe的接口
|
||||
|
||||
```rust
|
||||
pub fn register_kprobe(kprobe_info: KprobeInfo) -> Result<LockKprobe, SystemError>;
|
||||
pub fn unregister_kprobe(kprobe: LockKprobe) -> Result<(), SystemError>;
|
||||
|
||||
impl KprobeBasic {
|
||||
pub fn call_pre_handler(&self, trap_frame: &dyn ProbeArgs)
|
||||
pub fn call_post_handler(&self, trap_frame: &dyn ProbeArgs)
|
||||
pub fn call_fault_handler(&self, trap_frame: &dyn ProbeArgs)
|
||||
pub fn call_event_callback(&self, trap_frame: &dyn ProbeArgs)
|
||||
pub fn update_event_callback(&mut self, callback: Box<dyn CallBackFunc>)
|
||||
pub fn disable(&mut self)
|
||||
pub fn enable(&mut self)
|
||||
pub fn is_enabled(&self) -> bool
|
||||
pub fn symbol(&self) -> Option<&str>
|
||||
}
|
||||
```
|
||||
|
||||
- `call_pre_handler` 在探测点指令被执行前调用用户定义的回调函数
|
||||
- `call_post_handler` 在单步执行完探测点指令后调用用户定义的回调函数
|
||||
- `call_fault_handler` 在调用前两种回调函数发生失败时调用
|
||||
- `call_event_callback` 用于调用eBPF相关的回调函数,通常与`call_post_handler` 一样在单步执行探测点指令会调用
|
||||
- `update_event_callback`用于运行过程中更新回调函数
|
||||
- `disable` 和 `enable` 用于动态关闭kprobe,在`disable`调用后,kprobe被触发时不执行回调函数
|
||||
- `symbol` 返回探测点的函数名称
|
||||
|
BIN
docs/kernel/trace/kprobe_flow.png
Normal file
BIN
docs/kernel/trace/kprobe_flow.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 46 KiB |
Reference in New Issue
Block a user