一、字符设备驱动概念
1. 什么是字符设备驱动?
字符设备是 Linux 驱动中最基本的一类设备驱动,按字节流进行读写操作,数据读写有先后顺序。常见的字符设备包括LED灯、按键、IIC、SPI、LCD等。字符设备驱动就是为这些设备编写的驱动程序。
2. Linux应用程序如何调用驱动程序
在 Linux 中,一切皆为文件,驱动加载成功后,会在 /dev
目录下生成一个相应的文件,应用程序通过操作该文件即可实现对硬件的操作。例如 /dev/led
是一个 LED 灯的驱动文件,应用程序可以使用 open
函数打开文件 /dev/led
,使用 close
函数关闭文件 /dev/led
。要点亮或关闭 LED,可以使用 write
函数向该驱动写入数据;要获取 LED 状态,可以使用 read
函数从驱动读取状态。
3. 系统调用与驱动函数
应用程序运行在用户空间,驱动运行在内核空间,用户空间不能直接操作内核空间。因此,通过系统调用的方式实现用户空间与内核空间的交互。每个系统调用在驱动中都有对应的驱动函数。驱动程序中的 file_operations
结构体定义了这些操作函数:
struct file_operations {
struct module *owner;
loff_t (*llseek) (struct file *, loff_t, int);
ssize_t (*read) (struct file *, char __user *, size_t, loff_t *);
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *);
int (*open) (struct inode *, struct file *);
int (*release) (struct inode *, struct file *);
// 其他函数省略
};
4. file_operations
结构体常用函数
owner
: 指向拥有该结构体的模块的指针,一般设置为THIS_MODULE
。read
: 用于读取设备文件。write
: 用于向设备文件写入数据。open
: 用于打开设备文件。release
: 用于释放(关闭)设备文件。
二、字符设备驱动开发步骤
1. 驱动模块的加载和卸载
Linux 驱动有两种运行方式:编译进内核或编译成模块。模块的加载和卸载注册函数如下:
module_init(xxx_init); // 注册模块加载函数
module_exit(xxx_exit); // 注册模块卸载函数
驱动模块加载和卸载模板:
static int __init xxx_init(void) {
// 入口函数的具体内容
return 0;
}
static void __exit xxx_deinit(void) {
// 出口函数的具体内容
}
module_init(xxx_init);
module_exit(xxx_deinit);
2. 添加LICENSE和作者信息
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Author Name");
3. 示例程序
3.1 编写 hello_driver.c
#include <linux/module.h>
static int __init hello_driver_init(void) {
printk("hello_driver_init\n");
return 0;
}
static void __exit hello_driver_cleanup(void) {
printk("hello_driver_cleanup\n");
}
module_init(hello_driver_init);
module_exit(hello_driver_cleanup);
MODULE_LICENSE("GPL");
3.2 编写 Makefile
KERNELDIR := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := hello_driver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
3.3 编译、加载、卸载模块
make
sudo insmod hello_driver.ko
lsmod | grep hello_driver
dmesg | grep hello_driver
sudo rmmod hello_driver
4. 字符设备注册与注销
字符设备的注册和注销函数原型:
static inline int register_chrdev(unsigned int major, const char *name, const struct file_operations *fops);
static inline void unregister_chrdev(unsigned int major, const char *name);
设备号的定义和操作:
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma, mi) (((ma) << MINORBITS) | (mi))
5. 内核空间与用户空间数据交互
unsigned long copy_to_user(void *dst, const void *src, unsigned long len);
unsigned long copy_from_user(void *to, const void *from, unsigned long n);
6. 示例程序:注册字符设备
6.1 hello_driver.c
内容
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#define CHRDEVBASE_MAJOR 200
static char kernel_buffer[1024];
static int hello_open(struct inode *inode, struct file *file) {
printk("hello_open\n");
return 0;
}
static int hello_release(struct inode *inode, struct file *file) {
printk("hello_release\n");
return 0;
}
static ssize_t hello_read(struct file *file, char __user *buffer, size_t size, loff_t *ppos) {
printk("hello_read: size=%zu\n", size);
copy_to_user(buffer, kernel_buffer, size);
return size;
}
static ssize_t hello_write(struct file *file, const char __user *buffer, size_t size, loff_t *ppos) {
printk("hello_write: size=%zu\n", size);
copy_from_user(kernel_buffer, buffer, size);
return size;
}
static const struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static int __init hello_init(void) {
int ret = register_chrdev(CHRDEVBASE_MAJOR, "hello", &hello_fops);
if (ret < 0) {
printk("register_chrdev failed\n");
return ret;
}
printk("hello_init\n");
return 0;
}
static void __exit hello_cleanup(void) {
unregister_chrdev(CHRDEVBASE_MAJOR, "hello");
printk("hello_cleanup\n");
}
module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");
6.2 test_app.c
内容
#include <stdio.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define BUFFER_SIZE 1024
int main(int argc, char *argv[]) {
if (argc != 3) {
fprintf(stderr, "Usage: %s <device> <read/write>\n", argv[0]);
return 1;
}
int fd = open(argv[1], O_RDWR);
if (fd < 0) {
perror("open");
return 1;
}
char buffer[BUFFER_SIZE];
if (strcmp(argv[2], "read") == 0) {
ssize_t ret = read(fd, buffer, BUFFER_SIZE);
if (ret < 0) {
perror("read");
close(fd);
return 1;
}
printf("Read from kernel: %s\n", buffer);
} else if (strcmp(argv[2], "write") == 0) {
printf("Enter data to write: ");
fgets(buffer, BUFFER_SIZE, stdin);
ssize_t ret = write(fd, buffer, strlen(buffer));
if (ret < 0) {
perror("write");
close(fd);
return 1;
}
} else {
fprintf(stderr, "Invalid operation: %s\n", argv[2]);
}
close(fd);
return 0;
}
6.3 Makefile
内容
KERNELDIR := /lib/modules/$(shell uname -r)/build
CURRENT_PATH := $(shell pwd)
obj-m := hello_driver.o
build: kernel_modules
kernel_modules:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) modules
gcc -o test_app test_app.c
clean:
$(MAKE) -C $(KERNELDIR) M=$(CURRENT_PATH) clean
rm -f test_app
7. 自动创建设备节点
7.1 创建和删除类
struct class *class_create(struct module *owner, const char *name);
void class_destroy(struct class *cls);
7.2 创建设备
struct device *device_create(struct class *class, struct device *parent, dev_t devt, void *drvdata, const char *fmt, ...);
void device_destroy(struct class *class, dev_t devt);
8. 示例程序:自动创建设备节点
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
dev_t hello_devid;
struct cdev hello_cdev;
static struct class *hello_class;
static char kernel_buffer[1024];
static int hello_open(struct inode *inode, struct file *file) {
printk("hello_open\n");
return 0;
}
static int hello_release(struct inode *inode, struct file *file) {
printk("hello_release\n");
return 0;
}
static ssize_t hello_read(struct file *file, char __user *buffer, size_t size, loff_t *ppos) {
printk("hello_read: size=%zu\n", size);
copy_to_user(buffer, kernel_buffer, size);
return size;
}
static ssize_t hello_write(struct file *file, const char __user *buffer, size_t size, loff_t *ppos) {
printk("hello_write: size=%zu\n", size);
copy_from_user(kernel_buffer, buffer, size);
return size;
}
static const struct file_operations hello_fops = {
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
.read = hello_read,
.write = hello_write,
};
static int __init hello_init(void) {
int ret;
printk("hello_init\n");
ret = alloc_chrdev_region(&hello_devid, 0, 1, "hello");
if (ret < 0) {
printk("alloc_chrdev_region failed\n");
return ret;
}
cdev_init(&hello_cdev, &hello_fops);
ret = cdev_add(&hello_cdev, hello_devid, 1);
if (ret < 0) {
unregister_chrdev_region(hello_devid, 1);
printk("cdev_add failed\n");
return ret;
}
hello_class = class_create(THIS_MODULE, "hello_class");
device_create(hello_class, NULL, hello_devid, NULL, "hello"); // /dev/hello
return 0;
}
static void __exit hello_cleanup(void) {
printk("hello_cleanup\n");
device_destroy(hello_class, hello_devid);
class_destroy(hello_class);
cdev_del(&hello_cdev);
unregister_chrdev_region(hello_devid, 1);
}
module_init(hello_init);
module_exit(hello_cleanup);
MODULE_LICENSE("GPL");
总结
这篇博文详细介绍了 Linux 字符设备驱动开发的基本概念、步骤和示例程序。希望通过实践操作,大家能够更好地理解和掌握字符设备驱动开发的核心知识和技巧。