Linux模块(1) - 加载与卸载

本文详细介绍了如何在Linux内核中编写和注册一个简单的平台驱动。从定义模块信息、驱动框架、设备实体到模块源码的编写,一步步展示了驱动的创建过程。在实践中,作者遇到了设备树以外的设备加载警告和缺少release()函数的问题,并给出了修复方法。最后,文章提到了模块的编译和测试步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天想要把一份驱动放到Linux内核下运行,所以开启一段Linux模块的编写历程,之前接触过一些相关的知识,但多停留在纸面上,今天实战一次。目前手上有一份工作正常的内核源码和一块工作正常的板卡,开发环境都是就绪的。

1. 模块信息

MODULE_AUTHOR("xflm");  // 作者
MODULE_DESCRIPTION("mac test"); // 简介
MODULE_LICENSE("Dual MIT/GPL"); // 版权,我随便找了一个写上

2. 驱动框架

  1. 先定义一个驱动实体struct platform_driver mac_platform_driver
    1. struct platform_driver mac_platform_driver定义在include/linux/platform_device.h
    struct platform_driver {  // 平台驱动抽象模型
    	int (*probe)(struct platform_device *);  /* 设备探测,设备和驱动匹配后自动调用,必须提供 */
    	int (*remove)(struct platform_device *);  /* 设备移除,设备卸载时自动调用,必须提供 */
    	int (*shutdown)(struct platform_device *);  /* 关机 */
    	int (*suspend)(struct platform_device *pm_message_t state);  /* 挂起,电源关闭 */
    	int (*resume)(struct platform_device *);  /* 恢复,电源打开 */
    	struct device_driver driver;  /* 设备驱动抽象模型 */
    	const struct platfoem_device_id *id_table;  /* 平台设备ID,指向一个以全0结尾的结构体数组 */
    	bool prevent_deferred_probe;
    }
    // include/linux/device.h
    struct device_driver {
    	const char *name;  /* 设备驱动名称 */
    	struct bus_type *bus;  /* 设备所属的总线类型 */
    	struct module *owner;  /* 模块的拥有者,通常为宏`THIS_MODULE` */
    	const char *mod_name;  /* 模块内部使用 */
    	...
    	const struct of_device_id *of_match_table;  /* 设备树中匹配的设备名称,指向一个以全0结尾的结构体数组 */
    	...
    }
    
    1. platform_match()定义在drivers/base/platform.c,定义了设备和驱动匹配的过程,匹配成功返回1
    static int platform_match(struct device *dev, struct device_driver *drv)
    {
    	struct platform_device *pdev = to_platform_device(dev);
    	struct platform_driver *pdrv = to_platform_driver(drv);
    	
    	/* 1. 若设备驱动之前匹配过驱动(比如注册了多个相同的设备,则他们的驱动应当相同),则直接比较`struct device_driver->name`。 */
    	if (pdev->driver-override)
    		return !strcmp(pdev->driver_override, drv->name);
    	/* 2. 匹配设备树(OF)中注册的设备 。*/
    	if (of_driver_match_device(dev, drv))
    		return 1;
    	/* 3. 匹配ACPI中注册的设备。 */
    	if (acpi_driver_match_device(dev, drv))
    		return 1;
    	/* 4. 匹配`struct platform_driver->id_table`。 */
    	if (pdrv->id_table)
    		return platform_match_id(pdrv->id_table, pdev) != NULL;
    	/* 5. 匹配`struct platform_device->name` 和`struct device_driver->name` */
    	return (strcmp(pdev->name, drv->name) == 0);
    }
    
    1. 填充驱动实体mac_platform_driver
    static int mac_probe(struct platform_device *mac_platform_device) { printk("%s\n", __func__); return 0; }
    static int mac_remove(struct platform_device *mac_platform_device) { printk("%s\n", __func__); return 0; }
    static struct platform_driver mac_paltform_driver = {
    	.probe = mac_probe,
    	.remove = mac_remove,
    	.driver = {
    		.name = "mac";  /* 根据`platform_match()`,定义该值即可匹配成功。 */
    	},
    }
    
  2. 驱动注册。
    1. 可以使用module_platform_driver()注册,module_platform_driver()是个宏定义,在include/linux/device.h中,展开后如下。
    static int __init __driver_init(void)
    {
    	return __platform_driver_register(&mac_platform_driver, THIS_MODULE);
    }
    module_init(__driver_init);
    static void __exit __driver_exit(void)
    {
    	__platform_driver_unregister(&mac_platform_driver, THIS_MODULE);
    }
    module_exit(__driver_exit);
    
    1. 由于不打算修改设备树,所以注册驱动时还需要使用platform_device_register()注册个设备,module_init()定义了模块加载时的入口函数,故需要将platform_device_register()函数写到__driver_init()中,也就不能使用module_platform_driver()了。
    static int __init mac_module_init(void)
    {
    	int ret;
    	
    	ret = platform_device_register(&mac_platform_device);  // device和driver可以先可以后,没有必然联系
    	if (!ret)
    		ret = platform_driver_register(&mac_paltfoem_driver);
    	return ret;
    }
    module_init(mac_module_init);
    
    static void __exit mac_module_exit(void)
    {
    	platform_driver_unregister(&mac_platform_driver);  // device和driver可以先可以后,没有必然联系
    	platform_device_unregister(&mac_platform_device);
    }
    module_exit(mac_module_exit);
    
  3. 定义一个设备实体struct platform_device mac_platform_device
    1. struct platform_device定义在include/linux/platform_device.h中。
    struct platform_device {
    	const char *name;  // 设备名称
    	int id;  // 设备编号,多个相同的设备编号为0 1 2...
    	struct device dev;  // 设备基本数据
    	...
    }
    // include/linux/device.h
    struct device {
    	...
    	void (*release)(struct device *dev);  // 设备释放时自动调用,必须提供,这个第一次测试未关心,通过打印日志看到的
    	...
    }
    
    1. 填充设备实体mac_platform_device
    static void mac_release(struct device *dev) { printk("%s\n", __func__); }
    struct platform_device mac_platform_device = {
    	.name = "mac",  // 必须和`mac_platform_driver.driver.name`相同
    	.id = 0,  // 只定义一个,定义多个需要使用不同的id,或者使能`mac_platform_device.id_auto`
    	.dev = {
    		.release = mac_release,  // 这个第一次测试未关心,通过打印日志看到的
    	},
    }
    

3. 模块源码

#include <linux/module.h>
#include <linux/platform_device.h>

static int mac_probe(struct platform_device *mac_platform_device)
{
	printk("%s\n", __func__);
	return 0;
}
static int mac_remove(struct platform_device *mac_platform_device)
{
	printk("%s\n", __func__);
	return 0;
}
static struct platform_driver mac_paltform_driver = {
	.probe = mac_probe,
	.remove = mac_remove,
	.driver = {
		.name = "mac";  /* 根据`platform_match()`,定义该值即可匹配成功。 */
	},
}
static void mac_release(struct device *dev)
{
	printk("%s\n", __func__);
}
struct platform_device mac_platform_device = {
	.name = "mac",  // 必须和`mac_platform_driver.driver.name`相同
	.id = 0,  // 只定义一个,定义多个需要使用不同的id,或者使能`mac_platform_device.id_auto`
	.dev = {
		.release = mac_release,
	},
}
static int __init mac_module_init(void)
{
	int ret;

	ret = platform_device_register(&mac_platform_device);  // device和driver可以先可以后
	if (!ret)
		ret = platform_driver_register(&mac_paltfoem_driver);
	return ret;
}
module_init(mac_module_init);

static void __exit mac_module_exit(void)
{
	platform_driver_unregister(&mac_platform_driver);  // device和driver可以先可以后
	platform_device_unregister(&mac_platform_device);
}
module_exit(mac_module_exit);

MODULE_AUTHOR("xflm");  // 作者
MODULE_DESCRIPTION("mac test"); // 简介
MODULE_LICENSE("Dual MIT/GPL"); // 版权,我随便找了一个写上

4. 模块编译

  1. 编写Makefile文件。
# CPU架构
export ARCH = csky
# 交叉工具链
export CROSS_COMPILE = /opt/linux-glibc/csky-avbiv2-linux-
# 内核源码的编译目录
KSRC = /home/xflm/workspace/csky/kernel/build
# 编译模块
obj-m += mac.o
# make缺省目标,即编译模块
modules:
	$(MAKE) -C $(KSRC) M=`pwd` modules
# make其他目标,比如make clean
%:
$(MAKE) -C $(KSRC) M=`pwd` $@
  1. 编译,下载(略),并运行。
# 编译
$ make
# 插入模块,执行了`mac_probe()`但是报了错误警告`加载了个设备树以外的设备,污染了内核`
$ insmod mac.ko
[18745.095224] mac: loading out-of-tree module taints kernel.
[18745.104503] mac_probe
# 查看模块插入情况,插入成功
$ lsmod
	tainted: G
mac 999 0 - Live 0xc017e000 (o)
# 卸载模块,执行了`mac_remove()`但是报了错误警告`缺少release()`函数,内核仍工作正常
$ rmmod mac.ko
[19907.272900] mac_remove
[19907.275568] -------[ cut here ]------
...
[19907.296775] Device 'mac.0' doed not have a release() function, it is broken and must be fixed.
# 查看模块卸载情况,卸载成功
$ lsmod
	tainted: G
  1. 修复了release()函数问题后测试。
# 插入模块,执行了`mac_probe()`但是没报错误警告,之前的警告只会报一次
$ insmod mac.ko
[22407.680126] mac_probe
# 查看模块插入情况,插入成功
$ lsmod
	tainted: G
mac 1063 0 - Live 0xc018a000 (o)
# 卸载模块,执行了`mac_remove()`但是报了错误警告`缺少release()`函数,内核仍工作正常
$ rmmod mac.ko
[22415.180496] mac_remove
[22415.183102] mac_release
...
[19907.296775] Device 'mac.0' doed not have a release() function, it is broken and must be fixed.
# 查看模块卸载情况,卸载成功
$ lsmod
	tainted: G

今天先到这。下一篇Linux模块(2) - 创建设备节点

5. 参考

module_platform_driver()
Linux Platform驱动模型(二) _驱动方法
Linux驱动–of_match_ptr 宏定义

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值