linux内核开发

内核编程常常看起来像是黑魔法,而在亚瑟 C 克拉克的眼中,它八成就是了。Linux内核和它的用户空间是大不相同的:抛开漫不经心,你必须小心翼翼,因为你编程中的一个bug就会影响到整个系统。浮点运算做起来可不容易,堆栈固定而狭小,而你写的代码总是异步的,因此你需要想想并发会导致什么。而除了所有这一切之外,Linux内核只是一个很大的、很复杂的C程序,它对每个人开放,任何人都去读它、学习它并改进它,而你也可以是其中之一。

img

学习内核编程的最简单的方式也许就是写个内核模块:一段可以动态加载进内核的代码。模块所能做的事是有限的——例如,他们不能在类似进程描述符这样的公共数据结构中增减字段(LCTT译注:可能会破坏整个内核及系统的功能)。但是,在其它方面,他们是成熟的内核级的代码,可以在需要时随时编译进内核(这样就可以摒弃所有的限制了)。完全可以在Linux源代码树以外来开发并编译一个模块(这并不奇怪,它称为树外开发),如果你只是想稍微玩玩,而并不想提交修改以包含到主线内核中去,这样的方式是很方便的。

在本教程中,我们将开发一个简单的内核模块用以创建一个/dev/reverse设备。写入该设备的字符串将以相反字序的方式读回(“Hello World”读成“World Hello”)。这是一个很受欢迎的程序员面试难题,当你利用自己的能力在内核级别实现这个功能时,可以使你得到一些加分。在开始前,有一句忠告:你的模块中的一个bug就会导致系统崩溃(虽然可能性不大,但还是有可能的)和数据丢失。在开始前,请确保你已经将重要数据备份,或者,采用一种更好的方式,在虚拟机中进行试验。

尽可能不要用root身份

默认情况下,/dev/reverse只有root可以使用,因此你只能使用sudo来运行你的测试程序。要解决该限制,可以创建一个包含以下内容的/lib/udev/rules.d/99-reverse.rules文件:

SUBSYSTEM=="misc", KERNEL=="reverse", MODE="0666"

别忘了重新插入模块。让非root用户访问设备节点往往不是一个好主意,但是在开发其间却是十分有用的。这并不是说以root身份运行二进制测试文件也不是个好主意。

模块的构造

由于大多数的Linux内核模块是用C写的(除了底层的特定于体系结构的部分),所以推荐你将你的模块以单一文件形式保存(例如,reverse.c)。我们已经把完整的源代码放在GitHub上——这里我们将看其中的一些片段。开始时,我们先要包含一些常见的文件头,并用预定义的宏来描述模块:

#include <linux/init.h>#include <linux/kernel.h>#include <linux/module.h> MODULE_LICENSE("GPL");MODULE_AUTHOR("Valentine Sinitsyn <[email protected]>");MODULE_DESCRIPTION("In-kernel phrase reverser");

这里一切都直接明了,除了MODULE_LICENSE():它不仅仅是一个标记。内核坚定地支持GPL兼容代码,因此如果你把许可证设置为其它非GPL兼容的(如,“Proprietary”[专利]),某些特定的内核功能将在你的模块中不可用。

什么时候不该写内核模块

内核编程很有趣,但是在现实项目中写(尤其是调试)内核代码要求特定的技巧。通常来讲,在没有其它方式可以解决你的问题时,你才应该在内核级别解决它。以下情形中,可能你在用户空间中解决它更好:

  • 你要开发一个USB驱动 —— 请查看libusb
  • 你要开发一个文件系统 —— 试试FUSE
  • 你在扩展Netfilter —— 那么libnetfilter_queue对你有所帮助。

通常,内核里面代码的性能会更好,但是对于许多项目而言,这点性能丢失并不严重。

由于内核编程总是异步的,没有一个main()函数来让Linux顺序执行你的模块。取而代之的是,你要为各种事件提供回调函数,像这个:

static int __init reverse_init(void){    printk(KERN_INFO "reverse device has been registered\n");    return 0;} static void __exit reverse_exit(void){    printk(KERN_INFO "reverse device has been unregistered\n");} module_init(reverse_init);module_exit(reverse_exit);

这里,我们定义的函数被称为模块的插入和删除。只有第一个的插入函数是必要的。目前,它们只是打印消息到内核环缓冲区(可以在用户空间通过dmesg命令访问);KERN_INFO是日志级别(注意,没有逗号)。__init__exit是属性 —— 联结到函数(或者变量)的元数据片。属性在用户空间的C代码中是很罕见的,但是内核中却很普遍。所有标记为__init的,会在初始化后释放内存以供重用(还记得那条过去内核的那条“Freeing unused kernel memory…[释放未使用的内核内存……]”信息吗?)。__exit表明,当代码被静态构建进内核时,该函数可以安全地优化了,不需要清理收尾。最后,module_init()module_exit()这两个宏将reverse_init()reverse_exit()函数设置成为我们模块的生命周期回调函数。实际的函数名称并不重要,你可以称它们为init()exit(),或者start()stop(),你想叫什么就叫什

  • 0
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值