Linux驱动开发 ---- 4.4_驱动 API 及常用操作
目录
在 Linux 驱动开发中,有一系列的 API 用于注册和管理硬件设备、驱动程序以及与用户空间交互的设备文件。以下是常见的驱动 API 及其操作的详细讲解和示例。
1. platform_driver_register
作用:注册一个平台驱动程序,使其能够与平台设备进行交互。
操作步骤:
- 你首先需要定义一个
platform_driver
结构体,通常包含驱动的probe
和remove
函数。 - 使用
platform_driver_register()
函数将驱动注册到内核中。
#include <linux/platform_device.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int my_driver_probe(struct platform_device *pdev) {
pr_info("Driver probed\n");
return 0;
}
static int my_driver_remove(struct platform_device *pdev) {
pr_info("Driver removed\n");
return 0;
}
static struct platform_driver my_driver = {
.probe = my_driver_probe,
.remove = my_driver_remove,
.driver = {
.name = "my_platform_driver",
.owner = THIS_MODULE,
},
};
static int __init my_driver_init(void) {
return platform_driver_register(&my_driver);
}
static void __exit my_driver_exit(void) {
platform_driver_unregister(&my_driver);
}
module_init(my_driver_init);
module_exit(my_driver_exit);
MODULE_LICENSE("GPL");
2. platform_device_register
作用:注册一个平台设备,使设备能够被驱动程序识别并与硬件交互。
操作步骤:
- 使用
platform_device_register()
注册一个平台设备。
#include <linux/platform_device.h>
static struct platform_device *pdev;
static int __init my_device_init(void) {
pdev = platform_device_register_simple("my_platform_device", -1, NULL, 0);
if (IS_ERR(pdev)) {
pr_err("Failed to register platform device\n");
return PTR_ERR(pdev);
}
return 0;
}
static void __exit my_device_exit(void) {
platform_device_unregister(pdev);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
3. device_create
作用:在 /dev
目录下创建设备文件,通常用于字符设备。
操作步骤:
- 使用
device_create()
创建设备文件,通常在驱动的初始化过程中完成。
#include <linux/device.h>
#include <linux/fs.h>
#include <linux/cdev.h>
static struct class *my_class;
static struct device *my_device;
static int __init my_device_init(void) {
// 创建设备类
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
// 创建设备文件
my_device = device_create(my_class, NULL, 0, NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_class);
return PTR_ERR(my_device);
}
return 0;
}
static void __exit my_device_exit(void) {
device_destroy(my_class, 0);
class_destroy(my_class);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
4. class_create
作用:创建一个设备类,该类用于管理一组设备并生成 /sys/class/
下的设备节点。
操作步骤:
- 使用
class_create()
创建一个设备类,该类的设备可以通过device_create()
在/dev
下生成设备文件。
#include <linux/device.h>
static struct class *my_class;
static int __init my_class_init(void) {
// 创建设备类
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
return 0;
}
static void __exit my_class_exit(void) {
class_destroy(my_class);
}
module_init(my_class_init);
module_exit(my_class_exit);
MODULE_LICENSE("GPL");
5. sysfs_create_file
作用:在 sysfs
文件系统中创建设备属性文件,以便用户空间能够通过读取和写入这些文件与内核交互。
操作步骤:
- 定义设备属性并实现
show
和store
函数。 - 使用
sysfs_create_file()
将属性添加到sysfs
中。
#include <linux/sysfs.h>
#include <linux/device.h>
static ssize_t myattribute_show(struct device *dev, struct device_attribute *attr, char *buf) {
return sprintf(buf, "42\n"); // 返回 "42" 字符串
}
static struct device_attribute dev_attr_myattribute = {
.attr = { .name = "myattribute", .mode = S_IRUGO },
.show = myattribute_show,
};
static struct class *my_class;
static struct device *my_device;
static int __init my_device_init(void) {
my_class = class_create(THIS_MODULE, "my_class");
if (IS_ERR(my_class)) {
pr_err("Failed to create class\n");
return PTR_ERR(my_class);
}
my_device = device_create(my_class, NULL, 0, NULL, "my_device");
if (IS_ERR(my_device)) {
pr_err("Failed to create device\n");
class_destroy(my_class);
return PTR_ERR(my_device);
}
// 在 sysfs 中创建属性文件
sysfs_create_file(&my_device->kobj, &dev_attr_myattribute.attr);
return 0;
}
static void __exit my_device_exit(void) {
sysfs_remove_file(&my_device->kobj, &dev_attr_myattribute.attr);
device_destroy(my_class, 0);
class_destroy(my_class);
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
6. alloc_chrdev_region
和 cdev_add
作用:用于字符设备的注册,alloc_chrdev_region()
用于动态分配一个设备号,cdev_add()
用于将字符设备与内核注册。
操作步骤:
- 使用
alloc_chrdev_region()
分配设备号。 - 使用
cdev_init()
初始化字符设备结构体。 - 使用
cdev_add()
注册字符设备。
#include <linux/fs.h>
#include <linux/cdev.h>
static dev_t dev_num;
static struct cdev my_cdev;
static int __init my_cdev_init(void) {
// 分配设备号
if (alloc_chrdev_region(&dev_num, 0, 1, "my_cdev") < 0) {
pr_err("Failed to allocate chrdev region\n");
return -1;
}
// 初始化字符设备结构体
cdev_init(&my_cdev, &fops);
// 注册字符设备
if (cdev_add(&my_cdev, dev_num, 1) < 0) {
pr_err("Failed to add cdev\n");
unregister_chrdev_region(dev_num, 1);
return -1;
}
return 0;
}
static void __exit my_cdev_exit(void) {
// 注销字符设备
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, 1);
}
module_init(my_cdev_init);
module_exit(my_cdev_exit);
MODULE_LICENSE("GPL");
上述的代码确实需要通过 Makefile
进行编译,因为它们涉及到内核模块的编写,并且内核模块需要通过编译生成 .ko
文件(内核模块文件),然后才能加载到 Linux 内核中。
编译及使用步骤概述
-
编写内核模块代码:你已经有了相应的内核模块代码,例如
platform_driver_register
、device_create
等示例代码。 -
编写 Makefile:为了编译这些内核模块,你需要编写一个
Makefile
,它告诉编译器如何编译源代码并将其链接成内核模块(.ko
文件)。
Makefile 示例
假设你将内核模块的代码保存在一个文件中,例如 my_driver.c
,那么你需要创建一个 Makefile
来编译这个模块。以下是一个简单的 Makefile
示例:
obj-m += my_driver.o
# 使用内核的构建系统来构建模块
all:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) modules
# 删除构建文件
clean:
make -C /lib/modules/$(shell uname -r)/build M=$(PWD) clean
Makefile 解释
-
obj-m += my_driver.o
:这行告诉内核构建系统,编译的是my_driver.c
文件,并生成my_driver.o
对象文件。 -
all
目标:这行指令告诉make
使用当前正在运行的内核版本进行编译:-C /lib/modules/$(shell uname -r)/build
:指定使用当前内核版本的构建目录。M=$(PWD)
:指定当前目录作为模块源代码的位置。modules
:告诉内核构建系统编译模块。
-
clean
目标:清理构建文件,删除编译产生的临时文件。
编译和加载模块
-
编译模块:
在包含my_driver.c
和Makefile
的目录中,运行以下命令来编译内核模块:make
编译完成后,你会得到一个
.ko
文件,例如my_driver.ko
。 -
加载模块:
使用insmod
命令将编译好的内核模块加载到内核中:sudo insmod my_driver.ko
-
查看内核日志:
你可以使用dmesg
命令查看内核日志,确认模块是否加载成功:dmesg | tail
-
卸载模块:
使用rmmod
命令卸载内核模块:sudo rmmod my_driver
-
清理构建文件:
如果你想清理编译生成的临时文件,可以运行:make clean
小结
- 编写内核模块代码:你已经编写了驱动代码。
- 编写 Makefile:你需要创建一个
Makefile
来编译和构建内核模块。 - 编译和加载模块:使用
make
编译模块,使用insmod
加载模块。 - 卸载模块:使用
rmmod
卸载模块,清理构建文件。
通过这些步骤,你可以成功地编译并加载内核模块。
总结
platform_driver_register
和platform_device_register
:注册平台驱动和平台设备,分别用于将驱动与设备关联。device_create
和class_create
:在/dev
中创建设备文件,并通过设备类管理设备。sysfs_create_file
:在sysfs
文件系统中创建设备属性,允许用户空间通过文件与内核交互。alloc_chrdev_region
和cdev_add
:为字符设备分配设备号并注册字符设备。
这些 API 结合使用,可以实现驱动的注册、设备文件的创建、与用户空间的交互等功能,是 Linux 驱动开发中的基础操作。