d驱动进入林操

原文
原文章内容太多,仅节选了方法结论.

通过成功把内核Linux模块移植D语言,来证明在Linux内核中使用现代语言的可行性.移植了virtio框架的virtio_net网络驱动.

设计和实现依赖指定D语言技术,以改进Linux内核驱动.由于D语言提供的安全优势,性能成本可忽略不计.
提供了一种内核Linux移植模块到D语言的方法.通过成功移植证明,该方法可用来移植其他Linux内核模块.

使用D移植和增强内核模块的方法

A.在Linux内核中引入D代码
有两种方法可向Linux内核添加新功能:
(1),直接静态链接目标文件到内核核心
(2),按可加载模块编译代码,并按需链接到内核中.

一般经验是按可加载模块添加新功能.优点是保持内核代码尽量干净,且更易维护.此外,还允许更大程度上定制,因为可按需加载和卸载必要功能.此外,使可信计算基础(TCB)保持较小,并降低了整体对入侵的敏感性,从而提高了安全性.

无论必须构建哪种类型模块,内核构建系统都假定源文件是用C编写的.因此,无法成功编译另一种语言编写的源文件,并且生成将失败.对D语言也一样.同时,模块入口和出口点函数必须用C语言,以便内核可访问它们.总之,移植模块D语言需要:
1,用D编写相应源码
2,按C接口函数提供模块入口
3,更新构建系统文件,来链接新模块

第二个要求,必须在内核D编写的模块间实现C接口.此C接口应仅包含入口函数和无法移植D的函数宏和函数绑定.接口表明新功能至少需要两个源码文件:一个是C格式,另一个是D格式.因此,必须相应编写Linux内核构建文件中的指令,来满足第三个要求.

内核构建系统假定它正在处理C源文件,并试相应构建目标文件.幸好,构建系统按依赖项(a)接受预构建的对象二进制文件,它(a)与它构建的目标文件链接来创建内核模块.

这是通过从module-file.o更改依赖项名module-file.o_shipped来完成的.要链接D目标文件到内核模块,必须先编译D源文件,并使其名字带有.o_shipped后缀.

源文件可由带-betterC开关的D编译器编译.可选择使用基于LLVM(LDC)编译器和基于GCC(GDC)编译器.独立编译后,把它们传递到内核构建系统,并与其他C对象链接在一起.

B.移植内核模块

移植内核模块,包括测试基准测试,有5个步骤:
1,移植模块内使用的数据结构.确保每个新移植构大小布局与原始构的相同.
2,一次一个函数的移植模块实现.每新移植函数后检查模块功能.
3,执行第一组基准:求值模块行为.比较模块D和C版本.
4,在代码中,引入D惯用构造和功能.添加检查边界,用元编程替换宏和转换,添加@safe,@trusted和其他有用的功能.
5,执行第二组基准:求值添加的惯用代码效果.比较模块的惯用D粗略的D版本.比较模块的D和C版本.

第一步,移植数据结构最复杂.在内核模块中,一些在模块代码中定义,而其他构则来自不同的头文件.为了可生成可从C程序传递和接收的对象,D编译器(与其他编译器一样)必须知道这些C构内存布局.表明要移植它们到D.
可用是编译器包装器dpp来移植,它解析扩展叫.dpp的D源文件,并原位扩展遇见的#include指令,翻译所有CC++符号为D,然后传递结果给D编译器".但是,头文件中的高级分支或递归包含可能会导致不能用dpp.
此时,有两种选择:
(1)手动移植数据结构
(2)使dppLinux内核头文件一起使用.
这里选择了前者.
无论移植方法如何,移植到D的每个新构的大小和布局都应与C中原始构的相同.可比较D字段偏移与C对应项偏移来轻松检测大小或布局不匹配.

在D中,可用.offsetof字段属性取字段的偏移.在Linux内核中,可用offsetof(TYPE,MEMBER)宏来取它.

要考虑区别是空构大小:空构C核大小为0,而在D中,该构大小为1字节.可用D强大的编译时自省来解决该问题.此外,应该考虑D语言不隐式支持位域事实.但是,使用std.bitmanip.bitfields库类型可实现相同的功能.

在移植实现时,必须使用extern(C)链接属性注解从C调用的D函数.该属性指示链接器使用C命名和调用约定,而不是D的.在D标头声明用C实现的函数时,也必须同样注解.

在D中,不变的全局变量放在(TLS)线本存储中,而在C中,它们放在全局存储中.要实现函数奇偶校验,必须使用__gshared属性注解D全局变量.
此外,在D中,const限定符是可传递的,表明以递归方式应用限定符到类型的每个子组件.

原语数据类型的等价性也可能有问题.

内核模块中,并非所有使用或实现功能都值得移植.某些宏就是如此,它们反之调用其他宏等,并且深深植根于内核代码中.
使用扩展了标准C语言GCC功能的某些内核函数同样,且可能无法在D编译器中实现.
可创建C绑定(仅调用其他函数的函数),来避免移植这些宏或函数,可向D对象公开并应在模块的C接口创建这些绑定.

新移植函数之后,应运行功能测试包.

加强安全

如下是D语言提供的一些安全增强功能.用来在D中实现和构建新实现的内核模块.

1)变量
初化为类型的默认值,从而消除初化错误.
2)隐式转换
禁止指针转换为其它类型指针.D需要显式转换,来转换不同类型指针.
D中禁止使用C隐式开关降落行为.D还使用final switch语句,其中不必也不应有default(默认语句无用时很有用),在枚举类型时特别有用,因为在case语句中强制使用的所有枚举成员.

3)静态数组,默认,检查边界.
4)切片
通过引用和长度信息指定数组的一部分.用来检查动态分配数组边界.请注意,要了解动态分配数组的初始大小.
5)模板
模板编程可替代的C空针宏定义,从而启用类型系统检查.
6)安全函数

(用@safe注解),静态验证未定义行为.在安全函数中,不能破坏类型系统的转换或指针算术.

域,中域中域函数参数,确保参数不会出域,不会超过匹配参数生命期,并且即使通过指针间接寻址也能正确跟踪.
7)信任函数
(用@trusted注解),提供与安全函数相同的保证,但必须人工检查.
8)安全函数
只能调用其他安全函数和信任函数.

结论

在本文中,提出了一种使用D语言提高Linux内核模块安全性的方法.选择了virtio_net作为目标驱动,这是Linux内核中一个中等大小且积极维护的组件.用D语言移植了该驱动,并强调了与原始C驱动功能和性能等价,并讨论了安全优势.详细阐述了一种可对其他类型驱动达到相同目的的方法.

添加到驱动安全功能表明,D语言可在内核模块中提升安全,检查数组边界和编译时多态性是最重要的.

重要的是要注意内核内部不安全事实.尽管可用使用语言不同机制提高开发人员编写的代码的安全性,但某一时刻,开发人员被迫执行不安全的操作.
这些可能来自同底层硬件细节交互的需要,或需要与内核API交互.大多数内核API核心都使用原始指针;因此,即使安全代码,可能实现了合理对象生命期算法,强制传递原始指针内核,赌使所有安全注和假设无效.
尽管如此,有两个强有力的论据可使用安全语言:
1)受益于30年的开发和错误修复,内核非常稳定和健壮.
2)内核API明确定义了由谁,内核或驱动来释放分配的资源.
另一个重要观察结果是,语言必须可遵守Linux内核中实现的约束和设计模式.正如LinusTorvalds所说,内核需要的超过语言需求.因此,认为D语言非常适合,因为,证明它很容易的对接,C和内核基础设施.

可根据模块实现的独立程度改进内核安全性.模块使用外部特征越多,可行的安全增强就越少.

virtio_net驱动的性能求值表明,该驱动的D版本与原始C变体相比,几乎没有增加开销.可持续添加安全功能,不会引入开销,因此,认为性能结果令人鼓舞.

由于创建的方法,相信其他驱动可合理的努力移植D.由于与C语言的相似性,对驱动开发人员,习惯D语言影响最小.这与Rust语言形成鲜明对比,后者的语法和功能,与C语言非常不同.
相信,在Linux内核中添加安全语言的兴趣日益浓厚,这是向前迈出的一大步,因为它为内核开发人员提供了替代方案和灵活性,使他们可根据自己的需求和目标取得适当的平衡.

通过这些方法,可用本文中描述方法移植更多驱动.稍后,这可扩展到Linux内核中的整个内置组件和子系统.用受欢迎的类似C的语言,为整个内核安全性,几乎无成本的带来急需改进.

posted @   zjh6  阅读(31)  评论(0编辑  收藏  举报  
相关博文:
阅读排行:
· 分享一个免费、快速、无限量使用的满血 DeepSeek R1 模型,支持深度思考和联网搜索!
· 基于 Docker 搭建 FRP 内网穿透开源项目(很简单哒)
· ollama系列01:轻松3步本地部署deepseek,普通电脑可用
· 25岁的心里话
· 按钮权限的设计及实现
点击右上角即可分享
微信分享提示