2025-02-26
字符设备驱动在Linux系统中是很常见的。字符设备驱动是Linux内核中用于管理字符设备的模块。字符设备以字节流形式进行数据传输,通常不支持随机访问。
常见的例子包括键盘、鼠标和串口等。驱动程序通过实现file_operations
结构体中的函数(如open、read、write等)与用户空间交互,提供设备的操作接口。
字符设备在文件系统中以设备文件形式呈现,用户通过文件I/O函数访问设备。驱动程序还需在内核中注册设备,分配主次设备号,并在/dev
目录下创建设备文件。
// simple_cdev.c
#include <linux/errno.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/minmax.h>
#include <linux/module.h>
#include <linux/uaccess.h>
#define DEV_NAME "simple_cdev"
#define BUFFER_SIZE 1024
typedef struct {
int major; // 主设备号
int minor; // 从设备号
char buffer[BUFFER_SIZE]; // 缓冲区
} simple_cdev_t;
simple_cdev_t my_cdev = {0};
static int _open(struct inode *inode, struct file *file) {
pr_info("simple_cdev_open\n");
return 0;
}
static ssize_t _read(struct file *file, char __user *userbuf, size_t size,
loff_t *offset) {
pr_info("simple_cdev_read\n");
size = min(size, BUFFER_SIZE);
unsigned long remain_len = copy_to_user(userbuf, my_cdev.buffer, size);
if (remain_len > 0) {
pr_info("copy_to_user failed\n");
return -EIO;
}
return size;
}
static ssize_t _write(struct file *file, const char __user *userbuf,
size_t size, loff_t *offset) {
pr_info("simple_cdev_write\n");
size = min(size, BUFFER_SIZE);
unsigned long remain_len = copy_from_user(my_cdev.buffer, userbuf, size);
if (remain_len > 0) {
pr_err("copy_from_user failed\n");
return -EIO;
}
return size;
}
static int _release(struct inode *inode, struct file *file) {
pr_info("simple_cdev_release\n");
return 0;
}
// 文件处理函数,每当访问设备文件,都会调用这里的回调函数
static struct file_operations fops = {
.open = _open,
.read = _read,
.write = _write,
.release = _release,
};
static int __init simple_cdev_init(void) {
pr_info("simple_cdev_init\n");
// 动态申请主设备号
my_cdev.major = register_chrdev(0, DEV_NAME, &fops);
if (my_cdev.major < 0) {
pr_err("simple_cdev register_chrdev failed!\n");
return -EIO;
}
my_cdev.minor = 0;
pr_info("simple_cdev major = %d, minor = %d\n", my_cdev.major,
my_cdev.minor);
return 0;
}
static void __exit simple_cdev_exit(void) {
pr_info("simple_cdev_exit\n");
unregister_chrdev(my_cdev.major, DEV_NAME);
}
module_init(simple_cdev_init);
module_exit(simple_cdev_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("heng30");
MODULE_VERSION("v0.0.1");
MODULE_DESCRIPTION("A simple simple_cdev kernel module");
#!/bin/sh
top-dir = $(shell pwd)
kernel-version = $(shell uname -r)
kernel-dir ?= /lib/modules/$(kernel-version)/build
obj-m = simple_cdev.o
all:
make -C $(kernel-dir) modules M=$(top-dir)
clean:
rm -f *.o *.ko *.mod *.mod.c *.order *.symvers
make -C $(kernel-dir) clean m=$(top-dir)
insmod simple_cdev.ko
[58512.143558] simple_cdev: loading out-of-tree module taints kernel.
[58512.143563] simple_cdev: module verification failed: signature and/or required key missing - tainting kernel
[58512.143981] simple_cdev_init
[58512.143984] simple_cdev major = 240, minor = 0
创建字符设备文件simple_cdev
:mknod /dev/simple-cdev c 240 0
。240
是主设备号,0
是次设备号。
向simple_cdev
文件写入内容:echo "hello" > /dev/simple-cdev
[58984.196903] simple_cdev_open
[58984.196917] simple_cdev_write
[58984.196919] simple_cdev_release
simple_cdev
文件读取内容: head -n 1 /dev/simple-cdev
[59354.674957] simple_cdev_open
[59354.674972] simple_cdev_read
[59354.674994] simple_cdev_release
rmmod simple_cdev.ko