从新手村开始,手把手带你入门梳理内核代码
作者:姜亚华(@二如公子 ),《精通 Linux 内核——智能设备开发核心技术》的作者,一直从事与 Linux 内核和 Linux 编程相关的工作,研究内核代码十多年,对多数模块的细节如数家珍。曾负责华为手机 Touch、Sensor 的驱动和软件优化(包括 Mate、荣耀等系列),以及 Intel 安卓平台 Camera 和 Sensor 的驱动开发(包括 Baytrail、Cherrytrail、Cherrytrail CR、Sofia 等)。现负责 DMA、Interrupt、Semaphore 等模块的优化与验证(包括 Vega、Navi 系列和多款 APU 产品)。
往期回顾:
在上一期内容中,我们介绍了从 JVM 到内核的编译原理,告诉大家应用和系统工程师如何接触到内核。本文将从一个简单的底层硬件模块入手,一步步教大家如何梳理内核代码。适合精力集中在内核,不太需要关心用户空间的工程师,比如驱动工程师、嵌入式工程师等,以及想往这方面学习发展的朋友。
初探内核
Ubuntu (lsb_release -a) | Distributor ID: Ubuntu Description: Ubuntu 19.10 Release: 19.10 |
Linux (uname -a) | Linux yahua 5.5.5 #1 SMP … x86_64 x86_64 x86_64 GNU/Linux |
在往期的访谈中,我们讨论过如何阅读内核代码,在这里按照之前讨论的思路详细扩展下。
在 drivers/input/keyboard 下面的文件是键盘驱动,我们选择 lm8333.c 吧(没什么特殊理由,其他的也可以)。
找到 module_init,xxx_init,module_xxx,这些就是模块(驱动也是一种模块)的入口(进阶点 1,系统的启动过程),lm8333.c 内对应的是 module_i2c_driver(lm8333_driver),注册 driver。
lm8333_driver 定义如下:
static struct i2c_driver lm8333_driver = { .driver = { .name = "lm8333", }, .probe = lm8333_probe, .remove = lm8333_remove, .id_table = lm8333_id, };
驱动和设备匹配后,会回调 probe(进阶点 2,Linux Device Driver,LDD),也就是 lm8333_probe,它的关键代码如下:
static int lm8333_probe(struct i2c_client *client, const struct i2c_device_id *id) //1 { const struct lm8333_platform_data *pdata = dev_get_platdata(&client->dev); struct lm8333 *lm8333; struct input_dev *input; lm8333 = kzalloc(sizeof(*lm8333), GFP_KERNEL); //7 input = input_allocate_device(); //8 lm8333->client = client; //10 lm8333->input = input; //11 input->name = client->name; //13 input->dev.parent = &client->dev; //14 input->id.bustype = BUS_I2C; //15 input_set_capability(input, EV_MSC, MSC_SCAN); //16 err = matrix_keypad_build_keymap(pdata->matrix_data, …, input); //18 if (pdata->debounce_time) { err = lm8333_write8(lm8333, LM8333_DEBOUNCE, pdata->debounce_time / 3); //22 } if (pdata->active_time) { err = lm8333_write8(lm8333, LM8333_ACTIVE, pdata->active_time / 3); //27 } err = request_threaded_irq(client->irq, NULL, lm8333_irq_thread, IRQF_TRIGGER_FALLING | IRQF_ONESHOT, "lm8333", lm8333); //32 err = input_register_device(input); //34 i2c_set_clientdata(client, lm8333); //36 return 0; }
probe 的任务是驱动的初始化和设置,初学阶段,并不需要每一行代码都深入学习,可以先尝试将代码分类,以 lm8333_probe 为例。
第 1 行,函数的参数类型是固定的,背后是 LDD。
第 7 行,申请内存,背后是内存管理。暂且把它当成c语言的 malloc 也无妨。
第 8/13~16/18/34,input 相关,背后是 input 子系统。
第 22/27 行,写寄存器,背后是 i2c 总线。
第 32 行,request_threaded_irq,背后是中断处理。
这些背后的机制每一个都是一个进阶点。
初始化完毕,中断产生后,会调用 request_threaded_irq 时传递的 lm8333_irq_thread,继续梳理它的逻辑。
static irqreturn_t lm8333_irq_thread(int irq, void *data) { struct lm8333 *lm8333 = data; u8 status = lm8333_read8(lm8333, LM8333_READ_INT); if (!status) return IRQ_NONE; if (status & LM8333_ERROR_IRQ) { //省略 } if (status & LM8333_KEYPAD_IRQ) lm8333_key_handler(lm8333); return IRQ_HANDLED; }
可以看到 lm8333_irq_thread 先读寄存器来判断产生中断的原因,是 ERROR 还是 KEYPAD,如果是后者,调用 lm8333_key_handler。
static void lm8333_key_handler(struct lm8333 *lm8333) { struct input_dev *input = lm8333->input; u8 keys[LM8333_FIFO_TRANSFER_SIZE]; u8 code, pressed; int i, ret; ret = lm8333_read_block(lm8333, LM8333_FIFO_READ, LM8333_FIFO_TRANSFER_SIZE, keys); for (i = 0; i < LM8333_FIFO_TRANSFER_SIZE && keys[i]; i++) { pressed = keys[i] & 0x80; code = keys[i] & 0x7f; input_event(input, EV_MSC, MSC_SCAN, code); input_report_key(input, lm8333->keycodes[code], pressed); } input_sync(input); }
lm8333_key_handler 读寄存器,然后根据寄存器的值判断实际的按键,调用 input_report_key 报告数据。
好了,lm8333.c 的逻辑我们清楚了:初始化、设置中断、读取数据并 report。
我们从 lm8333 的硬件角度看看,它是一个比较简单的芯片,datasheet(数据手册,下载地址)也并不复杂,摘取其中一段。
n ACCESS.bus (I2C-compatible) communication interface to the hostn Four general purpose host programmable I/O pins with two optional (slow) external Interruptsn 16 byte FIFO buffer to store key pressed and key released eventsn Host programmable active time and debounce time
兼容 i2c 总线,支持中断,16 字节的 buffer,主机可编程有效时间和去抖时间。
再看看寄存器(这个文档称之为 command)表。
CMD | Data Bits | Description |
0x20 FIFO_READ | 128 | Read an event from the FIFO. Maximum 14 event codes stored in the FIFO. MSB = 1: key pressed. MSB = 0: key released. |
0x22 DEBOUNCE | 8 | Default is 10 ms. Valid range 1255. Time ~ n x 4 ms |
… | … | … |
再看看代码里出现的 i2c 读写的地址 LM8333_DEBOUNCE(0x22)和 LM8333_FIFO_READ(0x20)这些,这个表就是依据。
驱动做的事情可以分为两个方面,一方面是处理芯片本身的逻辑,比如中断、i2c、寄存器和时序等;另一方面是系统方面的,驱动和设备匹配、中断处理、数据传递(报告)等。
lm8333 比较简单,但复杂的芯片多如牛毛,所以驱动工程师也可以分为两类,一类比较专注于芯片本身的逻辑,另一类游到内核的大海中去了。
复杂的芯片本身就是一个完整的系统,成千上万的寄存器,错综复杂的模块,能将这些弄清楚也是有很大挑战的。除此之外,复杂的芯片很多都有配套的软件架构,比如 ISP(Camera)相关的 V4L2(Video For Linux 2),GPU 相关的 DRM(Direct Rendering Manager)。
很明显,芯片本身的逻辑并不是本文的重点,我们更关心如何游到内核。
再看 lm8333.c,大概清楚它的主要流程后,我们基本就算脱离新手村了,就像网游一样可以进入到下一阶副本了。回忆一下,在新手村,我们只需要识别出哪些函数属于其他模块,了解它们的基本原理,熟悉本身模块的逻辑即可。
进入第二个阶段,最好先从与日常工作关系最密切的模块入手。比如 lm8333,连接在 i2c 总线上,获取数据后通过 input 子系统 report,就可以从 i2c 和 input 入手。
学习 i2c 的过程中,还要解决 i2c 总线和 lm8333 的关系,这就涉及到 LDD。
深入 input 子系统的过程中,如果你对用户空间得到数据的过程感兴趣,就涉及到文件系统、poll/epoll 等。
当然了,在这个阶段,最好还是把文件系统这些复杂的模块当作黑盒。小碎步前进,不断有收获。
稍微复杂些的驱动可能还会有电源管理、工作队列和等待队列等机制,也可以在这个阶段内梳理它们的原理,至于它们背后的进程管理这些也可以先放放。
有了这一身装备,应付副本里的小 BOSS 也绰绰有余了,相比新手村那会也更有成就感,可以仗剑天涯了。
第三个阶段就是解决之前遗留的疑问了,将内存管理、文件系统和进程管理等一一拿下,比如 lm8333_probe 调用的 kzalloc、input 子系统涉及的 sysfs 文件系统、工作队列和中断处理相关的进程调度,一步步深入挖掘。
在之前的问答活动里我曾说过,“我已经把自己看过的代码的截图放在随书资料中了,算是一小段捷径吧。这些截图里面,某函数、它调用的函数等函数调用关系使用红线标示(如下图),内容包括内存管理、文件系统和进程管理三大模块。”
这些截图是随书资料,但并不是光盘那种。想要获取资料的朋友欢迎在下面评论留言,或者邮件(linux_kernel_os@163.com)联系我,有任何疑问也可以找我共同探讨。
往期回顾:

低调大师中文资讯倾力打造互联网数据资讯、行业资源、电子商务、移动互联网、网络营销平台。
持续更新报道IT业界、互联网、市场资讯、驱动更新,是最及时权威的产业资讯及硬件资讯报道平台。
转载内容版权归作者及来源网站所有,本站原创内容转载请注明来源。
- 上一篇
LLVM Clang 11 预编译头新选项将带来大幅构建性能提升
科技外媒phoronix 跟踪了 LLVM/Clang 11 源码更新中的一个构建新特性,如果将 clang-cl 驱动用于 MSVC 或通过其它方式使用预编译头(Pre-Compiled Headers,PCH)特性,则可以使用新选项来显著加快构建时间。 这一特性最早提交于去年 10 月,今年 4 月又被调整后 commit,并进入 LLVM 11 RC。主要原理是添加选项以实例化 PCH 中已经存在的模板。添加 -fpch-instantiate-templates,在生成预编译头时已经实例化模板,而不是在每次使用预编译头时都实例化。 默认情况下仅为 clang-cl 启用此功能。MSVC 通过使用空的 .cpp 文件进行编译来创建 PCH,这意味着在构建 PCH 时会实例化模板,因此 .h 需要自包含,否则可能导致问题:test/PCH/pch-instantiate-templates-forward-decl.cpp 无法编译。 提出这一改进的开发者测试了多次,表示这可以节省 20-30% 的构建时间。 具体可以查看:https://reviews.llvm.org/rGa4...
- 下一篇
当前云计算的业务思路有哪些
首先,云计算的业务思路一定要与云计算的服务模式相结合,不同的服务模式会孵化出不同的业务思路,同时团队自身的资源整合能力和技术研发能力,也在很大程度上会影响云计算的业务思路。 传统的云计算服务模式可以分为三种,分别是IaaS、PaaS和SaaS,随着大数据、人工智能和区块链等技术的发展,在传统的服务模式上,又衍生出了很多新的服务模式,包括数据服务、决策服务、价值体系服务等等,从大的发展趋势来看,云计算未来的业务思路会更多偏向于行业资源的整合和利用,这一点在工业互联网时代会有更加明显的体现。 当前不同规模的企业往往会致力于不同领域的云计算服务,比如大型企业比较热衷于IaaS和PaaS,随着IaaS的附加值逐渐降低,当前大型企业更注重PaaS的研发,同时基于PaaS也可以向不同领域进行垂直发展,价值挖掘空间还是非常大的。从当前的发展趋势来看,基于PaaS可以做全栈云和智能云,也可以嫁接大数据来提升服务的附加值,当然这个过程需要具有较强的行业背景。 对于大量的中小企业和行业创业者来说,以SaaS为切入点是比较现实的选择,SaaS本身的机会非常多,而且SaaS服务本身就具有多样性,可以进行行业定...
相关文章
文章评论
共有0条评论来说两句吧...
文章二维码
点击排行
推荐阅读
最新文章
- CentOS6,CentOS7官方镜像安装Oracle11G
- Hadoop3单机部署,实现最简伪集群
- SpringBoot2初体验,简单认识spring boot2并且搭建基础工程
- Springboot2将连接池hikari替换为druid,体验最强大的数据库连接池
- Windows10,CentOS7,CentOS8安装Nodejs环境
- Linux系统CentOS6、CentOS7手动修改IP地址
- CentOS7设置SWAP分区,小内存服务器的救世主
- CentOS6,7,8上安装Nginx,支持https2.0的开启
- CentOS7,CentOS8安装Elasticsearch6.8.6
- SpringBoot2整合Redis,开启缓存,提高访问速度