Linux设备驱动prebe过程(二)
上周整理了非热插拔设备驱动的probe过程,留了个小尾巴,这周先补上,共2个遗留课题:
- 热插拔设备驱动probe过程
- 以ko方式加载的driver如何实现probe
Linux设备驱动probe系列文章:
1. 热插拔设备驱动probe过程
链接如下:
Linux设备驱动probe过程(三)
2. 以ko方式加载driver的probe过程
将 Linux 驱动程序编译成内核模块(.ko 文件)有一些优势和适用场景:
- 动态加载和卸载:
内核模块可以在系统运行时动态加载和卸载,而不需要重新编译和重新启动整个内核。这使得系统管理更加灵活,可以根据需要添加或删除驱动程序,而不会影响系统的稳定性和运行。 - 节省内存:将驱动程序编译成内核模块可以节省内存,因为内核模块只在需要时加载到内存中。这对于系统资源受限的嵌入式系统或嵌入式设备尤为重要。
- 快速开发和调试:
内核模块的开发和调试相对于直接将驱动程序编译到内核中更为方便。您可以在不影响系统稳定性的情况下,快速加载、卸载和调试驱动程序。 - 模块化设计:将驱动程序编译成内核模块可以促使您采用模块化设计的思路,将功能模块化并独立开发,有助于提高代码的可维护性和可重用性。
- 定制化:使用内核模块可以实现对内核功能的定制化,您可以根据需要仅加载必要的模块,从而减小内核的体积,并降低系统的攻击面。
- 保密性:有些解决方案厂商会把一些知识产权保密性更高的模块以ko等方式对客户发布,而不release源码。如果客户需要源码来参考,需要支付额外的费用。
然而,将驱动程序编译到内核中也有其优势,例如性能方面可能会更好,因为编译到内核中的驱动程序不需要动态加载,但这通常会导致内核体积增加并且需要重新编译内核才能生效。因此,根据具体的应用场景和需求,选择将驱动程序编译成内核模块或编译到内核中是有所取舍的。
2.1 用户侧实现 - insmod
一般用户侧会在启动脚本、运行脚本和代码中进行ko的加载,最为常见的就是insmod命令。当下大部分Linux系统上,insmod命令通常为一个软链接:
$ ls -l /sbin/insmod lrwxrwxrwx 1 root root 9 Jul 28 2020
/sbin/insmod -> /bin/kmod
接下来看下它的实现,如何实现用户态到内核态的跳转,根据封装程度,拆成3个步骤来开:
- insmod命令到kmod_module_insert_module
- kmod_module_insert_module到syscall
- syscall到内核__do_sys_init_module
2.1.1 从执行insmod命令到kmod_module_insert_module
参考kmode代码:https://git.kernel.org/cgit/utils/kernel/kmod/kmod.git
这个阶段主要为加载ko做准备。
// insmod入口
int main(int argc, char *argv[])
{
int err;
/* program_invocation_short_name 为正在运行程序的文件名
比如: busybox, init, mkdir.
若insmod为软链接:/sbin/insmod -> /bin/kmod*
streq函数比较两个字符串,相等返回1
*/
if (streq(program_invocation_short_name, "kmod"))
err = handle_kmod_commands(argc, argv);
else
err = handle_kmod_compat_commands(argc, argv);
return err;
}
static int handle_kmod_commands(int argc, char *argv[])
{
...
for (i = 0, err = -EINVAL; i < ARRAY_SIZE(kmod_cmds); i++) {
if (streq(kmod_cmds[i]->name, cmd)) {
err = kmod_cmds[i]->cmd(--argc, ++argv);
break;
}
}
...
}
// 跳一些步骤,来到 do_insmod:
const struct kmod_cmd kmod_cmd_compat_insmod = {
.name = "insmod",
.cmd = do_insmod,
.help = "compat insmod command",
};
static int do_insmod(int argc, char *argv[])
{
...
// 1. 创建一个modules上下文ctx, 包含refcount, log配置, linux modules绝对路径..
ctx = kmod_new(NULL, &null_config);
...
// 2. 创建ko上下文mod, 递增ref, 提取hashkey ..
err = kmod_module_new_from_path(ctx, filename, &mod);
...
// 3. 将ko加载到kernel
err = kmod_module_insert_module(mod, flags, opts);
...
kmod_module_unref(mod);
...
kmod_unref(ctx);
...
}
- program_invocation_short_name实现获取程序名(拓展阅读)
2.1.2 kmod_module_insert_module到syscall
之后流程就是开始加载ko了,可以通过"-f"配置flag决定是否开展ko校验:
- KMOD_INSERT_FORCE_VERMAGIC: ignore kernel version magic;
- KMOD_INSERT_FORCE_MODVERSION: ignore symbol version hashes.
KMOD_EXPORT int kmod_module_insert_module(struct kmod_module *mod,
unsigned int flags,
const char *options)
{
int err;
const void *mem;
off_t size;
struct kmod_elf *elf;
const char *path;
const char *args = options ? options : "";
...
// 1. 获取ko路径
path = kmod_module_get_path(mod);
..