Linux字符设备开发详细指南
目录
概述
字符设备是Linux内核中最基本的设备类型之一,与块设备不同,字符设备以字符流的方式处理数据,不支持随机访问。常见的字符设备包括串口、键盘、鼠标等。
字符设备特点
- 按字符流顺序访问
- 不支持随机访问(无寻道操作)
- 通常用于数据传输量较小的设备
- 实现相对简单
字符设备基础概念
设备号
Linux使用设备号来标识设备,包括:
- 主设备号(Major Number):标识设备驱动程序
- 次设备号(Minor Number):标识具体的设备实例
c
// 设备号类型定义
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
// 从dev_t提取主次设备号
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
设备文件
字符设备通过设备文件暴露给用户空间,通常位于/dev目录下:
bash
# 创建设备节点
mknod /dev/mydevice c 240 0
# 参数说明:c表示字符设备,240是主设备号,0是次设备号
内核模块基础
模块基本结构
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/kernel.h>
static int __init my_module_init(void)
{
printk(KERN_INFO "Module loaded successfully\n");
return 0;
}
static void __exit my_module_exit(void)
{
printk(KERN_INFO "Module unloaded\n");
}
module_init(my_module_init);
module_exit(my_module_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
模块编译
创建Makefile:
makefile
obj-m += my_device.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
字符设备驱动架构
核心数据结构
file_operations结构体
file_operations是字符设备驱动的核心,定义了设备支持的操作:
c
#include <linux/fs.h>
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 *);
ssize_t (*read_iter) (struct kiocb *, struct iov_iter *);
ssize_t (*write_iter) (struct kiocb *, struct iov_iter *);
int (*iterate) (struct file *, struct dir_context *);
unsigned int (*poll) (struct file *, struct poll_table_struct *);
long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);
int (*mmap) (struct file *, struct vm_area_struct *);
int (*mremap)(struct file *, struct vm_area_struct *);
int (*open) (struct inode *, struct file *);
int (*flush) (struct file *, fl_owner_t id);
int (*release) (struct inode *, struct file *);
int (*fsync) (struct file *, loff_t, loff_t, int datasync);
int (*aio_fsync) (struct kiocb *, int datasync);
int (*fasync) (int, struct file *, int);
int (*lock) (struct file *, int, struct file_lock *);
ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int);
unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long);
int (*check_flags)(int);
int (*flock) (struct file *, int, struct file_lock *);
ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int);
ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
int (*setlease)(struct file *, long, struct file_lock **, void **);
long (*fallocate)(struct file *, int, loff_t, loff_t);
void (*show_fdinfo)(struct seq_file *, struct file *);
};
设备数据结构
定义设备私有数据结构:
c
struct my_device_data {
struct cdev cdev;
char buffer[BUFFER_SIZE];
int data_size;
struct semaphore sem;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
struct class *dev_class;
struct device *dev;
};
设备注册与注销
分配设备号
c
#include <linux/fs.h>
#define DEVICE_NAME "my_char_device"
#define MAJOR_NUM 240 // 选择一个未使用的主设备号
#define MINOR_NUM 0
#define DEVICE_COUNT 1
static dev_t dev_num;
static struct cdev my_cdev;
static int __init device_init(void)
{
int result;
// 方法1:静态分配设备号
dev_num = MKDEV(MAJOR_NUM, MINOR_NUM);
result = register_chrdev_region(dev_num, DEVICE_COUNT, DEVICE_NAME);
// 方法2:动态分配设备号(推荐)
// result = alloc_chrdev_region(&dev_num, MINOR_NUM, DEVICE_COUNT, DEVICE_NAME);
if (result < 0) {
printk(KERN_WARNING "Can't register device number\n");
return result;
}
return 0;
}
初始化并添加字符设备
c
static int __init device_init(void)
{
int result;
struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
};
// 初始化字符设备
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
// 添加字符设备到内核
result = cdev_add(&my_cdev, dev_num, DEVICE_COUNT);
if (result) {
printk(KERN_WARNING "Error adding character device\n");
unregister_chrdev_region(dev_num, DEVICE_COUNT);
return result;
}
return 0;
}
创建设备文件(自动)
c
#include <linux/device.h>
static struct class *my_class;
static int __init device_init(void)
{
// ... 前面的初始化代码 ...
// 创建设备类
my_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(my_class)) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, DEVICE_COUNT);
return PTR_ERR(my_class);
}
// 创建设备文件
device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME);
return 0;
}
static void __exit device_exit(void)
{
device_destroy(my_class, dev_num);
class_destroy(my_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev_num, DEVICE_COUNT);
}
基本文件操作实现
open和release操作
c
static int device_open(struct inode *inode, struct file *filp)
{
struct my_device_data *dev;
// 获取设备数据
dev = container_of(inode->i_cdev, struct my_device_data, cdev);
// 保存设备数据到file结构
filp->private_data = dev;
// 增加模块引用计数
try_module_get(THIS_MODULE);
printk(KERN_INFO "Device opened\n");
return 0;
}
static int device_release(struct inode *inode, struct file *filp)
{
// 减少模块引用计数
module_put(THIS_MODULE);
printk(KERN_INFO "Device closed\n");
return 0;
}
read操作
c
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
ssize_t retval = 0;
// 获取信号量
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
// 等待数据可用
while (dev->data_size == 0) {
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->read_queue, dev->data_size > 0))
return -ERESTARTSYS;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
// 计算可读数据量
if (count > dev->data_size)
count = dev->data_size;
// 复制数据到用户空间
if (copy_to_user(buf, dev->buffer, count)) {
retval = -EFAULT;
goto out;
}
// 移动缓冲区数据
memcpy(dev->buffer, dev->buffer + count, dev->data_size - count);
dev->data_size -= count;
retval = count;
// 唤醒等待写入的进程
wake_up_interruptible(&dev->write_queue);
out:
up(&dev->sem);
return retval;
}
write操作
c
static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
ssize_t retval = -ENOMEM;
// 获取信号量
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
// 等待缓冲区空间
while (dev->data_size == BUFFER_SIZE) {
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->write_queue, dev->data_size < BUFFER_SIZE))
return -ERESTARTSYS;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
// 计算可写入数据量
if (count > BUFFER_SIZE - dev->data_size)
count = BUFFER_SIZE - dev->data_size;
// 从用户空间复制数据
if (copy_from_user(dev->buffer + dev->data_size, buf, count)) {
retval = -EFAULT;
goto out;
}
dev->data_size += count;
retval = count;
// 唤醒等待读取的进程
wake_up_interruptible(&dev->read_queue);
out:
up(&dev->sem);
return retval;
}
高级特性
llseek操作
c
static loff_t device_llseek(struct file *filp, loff_t offset, int whence)
{
struct my_device_data *dev = filp->private_data;
loff_t new_pos;
switch (whence) {
case SEEK_SET:
new_pos = offset;
break;
case SEEK_CUR:
new_pos = filp->f_pos + offset;
break;
case SEEK_END:
new_pos = dev->data_size + offset;
break;
default:
return -EINVAL;
}
if (new_pos < 0)
return -EINVAL;
filp->f_pos = new_pos;
return new_pos;
}
poll/select支持
c
static unsigned int device_poll(struct file *filp, struct poll_table_struct *wait)
{
struct my_device_data *dev = filp->private_data;
unsigned int mask = 0;
down(&dev->sem);
poll_wait(filp, &dev->read_queue, wait);
poll_wait(filp, &dev->write_queue, wait);
if (dev->data_size > 0)
mask |= POLLIN | POLLRDNORM;
if (dev->data_size < BUFFER_SIZE)
mask |= POLLOUT | POLLWRNORM;
up(&dev->sem);
return mask;
}
ioctl系统调用
ioctl命令定义
c
// 使用_IO系列宏定义ioctl命令
#include <linux/ioctl.h>
#define IOCTL_MAGIC 'k'
#define IOCTL_GET_BUFFER_SIZE _IOR(IOCTL_MAGIC, 1, int)
#define IOCTL_SET_BUFFER_SIZE _IOW(IOCTL_MAGIC, 2, int)
#define IOCTL_CLEAR_BUFFER _IO(IOCTL_MAGIC, 3)
#define IOCTL_GET_DEVICE_INFO _IOR(IOCTL_MAGIC, 4, struct device_info)
struct device_info {
int major;
int minor;
int buffer_size;
int data_size;
};
ioctl实现
c
static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
struct my_device_data *dev = filp->private_data;
int err = 0;
int retval = 0;
// 检查命令有效性
if (_IOC_TYPE(cmd) != IOCTL_MAGIC)
return -ENOTTY;
if (_IOC_NR(cmd) > IOCTL_MAXNR)
return -ENOTTY;
// 检查访问权限
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok((void __user *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
switch (cmd) {
case IOCTL_GET_BUFFER_SIZE:
retval = __put_user(BUFFER_SIZE, (int __user *)arg);
break;
case IOCTL_SET_BUFFER_SIZE:
// 这里应该重新分配缓冲区,简化处理
retval = -EINVAL;
break;
case IOCTL_CLEAR_BUFFER:
down(&dev->sem);
dev->data_size = 0;
up(&dev->sem);
wake_up_interruptible(&dev->write_queue);
break;
case IOCTL_GET_DEVICE_INFO:
{
struct device_info info;
down(&dev->sem);
info.major = MAJOR(dev_num);
info.minor = MINOR(dev_num);
info.buffer_size = BUFFER_SIZE;
info.data_size = dev->data_size;
up(&dev->sem);
if (copy_to_user((void __user *)arg, &info, sizeof(info)))
retval = -EFAULT;
}
break;
default:
retval = -ENOTTY;
}
return retval;
}
阻塞与非阻塞操作
等待队列
c
#include <linux/wait.h>
// 初始化等待队列
static int __init device_init(void)
{
// ... 其他初始化 ...
init_waitqueue_head(&dev.read_queue);
init_waitqueue_head(&dev.write_queue);
// ...
}
阻塞读取示例
c
static ssize_t device_read_blocking(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
// 等待条件满足
wait_event_interruptible(dev->read_queue, device_has_data(dev));
// 读取数据
return device_read_data(dev, buf, count);
}
非阻塞读取示例
c
static ssize_t device_read_nonblocking(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
// 检查是否有数据可用
if (!device_has_data(dev))
return -EAGAIN;
// 读取数据
return device_read_data(dev, buf, count);
}
综合实现
c
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
if (filp->f_flags & O_NONBLOCK) {
return device_read_nonblocking(filp, buf, count, f_pos);
} else {
return device_read_blocking(filp, buf, count, f_pos);
}
}
并发控制
信号量使用
c
#include <linux/semaphore.h>
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_device_data *dev = filp->private_data;
ssize_t retval = 0;
// 获取信号量
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
// 临界区代码
// ... 读取操作 ...
// 释放信号量
up(&dev->sem);
return retval;
}
自旋锁使用
c
#include <linux/spinlock.h>
typedef struct {
char data[100];
int size;
spinlock_t lock;
} shared_data_t;
static shared_data_t shared_data;
static void update_data(char *new_data, int size)
{
unsigned long flags;
// 获取自旋锁
spin_lock_irqsave(&shared_data.lock, flags);
// 临界区代码
memcpy(shared_data.data, new_data, size);
shared_data.size = size;
// 释放自旋锁
spin_unlock_irqrestore(&shared_data.lock, flags);
}
读写锁
c
#include <linux/rwlock.h>
typedef struct {
char data[100];
int size;
rwlock_t lock;
} rw_data_t;
static rw_data_t rw_data;
static ssize_t read_data(char __user *buf, size_t count)
{
unsigned long flags;
ssize_t retval;
// 获取读锁
read_lock_irqsave(&rw_data.lock, flags);
// 读取操作(多个读者可以同时访问)
retval = copy_to_user(buf, rw_data.data, min(count, rw_data.size));
// 释放读锁
read_unlock_irqrestore(&rw_data.lock, flags);
return retval;
}
static ssize_t write_data(const char __user *buf, size_t count)
{
unsigned long flags;
// 获取写锁
write_lock_irqsave(&rw_data.lock, flags);
// 写入操作(独占访问)
copy_from_user(rw_data.data, buf, count);
rw_data.size = count;
// 释放写锁
write_unlock_irqrestore(&rw_data.lock, flags);
return count;
}
中断处理
注册中断处理程序
c
#include <linux/interrupt.h>
#include <linux/gpio.h>
#define GPIO_IRQ_PIN 17
#define IRQ_NUMBER gpio_to_irq(GPIO_IRQ_PIN)
static irqreturn_t my_interrupt_handler(int irq, void *dev_id)
{
struct my_device_data *dev = dev_id;
// 处理中断
printk(KERN_INFO "Interrupt received\n");
// 唤醒等待的进程
wake_up_interruptible(&dev->read_queue);
return IRQ_HANDLED;
}
static int __init device_init(void)
{
int result;
// 申请GPIO
result = gpio_request(GPIO_IRQ_PIN, "my_device_irq");
if (result) {
printk(KERN_WARNING "Failed to request GPIO\n");
return result;
}
// 设置GPIO方向
result = gpio_direction_input(GPIO_IRQ_PIN);
if (result) {
gpio_free(GPIO_IRQ_PIN);
return result;
}
// 注册中断处理程序
result = request_irq(IRQ_NUMBER, my_interrupt_handler,
IRQF_TRIGGER_RISING, "my_device", &dev);
if (result) {
gpio_free(GPIO_IRQ_PIN);
return result;
}
return 0;
}
static void __exit device_exit(void)
{
free_irq(IRQ_NUMBER, &dev);
gpio_free(GPIO_IRQ_PIN);
}
中断驱动的I/O
c
typedef struct {
char buffer[BUFFER_SIZE];
int head;
int tail;
int count;
spinlock_t lock;
wait_queue_head_t queue;
} circular_buffer_t;
static circular_buffer_t cb;
static irqreturn_t interrupt_handler(int irq, void *dev_id)
{
unsigned long flags;
char data;
// 读取硬件数据(示例)
data = read_hardware_data();
// 保存到循环缓冲区
spin_lock_irqsave(&cb.lock, flags);
if (cb.count < BUFFER_SIZE) {
cb.buffer[cb.head] = data;
cb.head = (cb.head + 1) % BUFFER_SIZE;
cb.count++;
wake_up_interruptible(&cb.queue);
}
spin_unlock_irqrestore(&cb.lock, flags);
return IRQ_HANDLED;
}
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
unsigned long flags;
int i;
char temp_buf[BUFFER_SIZE];
// 等待数据可用
wait_event_interruptible(cb.queue, cb.count > 0);
spin_lock_irqsave(&cb.lock, flags);
// 从循环缓冲区读取数据
for (i = 0; i < count && cb.count > 0; i++) {
temp_buf[i] = cb.buffer[cb.tail];
cb.tail = (cb.tail + 1) % BUFFER_SIZE;
cb.count--;
}
spin_unlock_irqrestore(&cb.lock, flags);
// 复制到用户空间
return copy_to_user(buf, temp_buf, i) ? -EFAULT : i;
}
内存管理
kmalloc和kfree
c
#include <linux/slab.h>
typedef struct {
char *buffer;
int size;
} dynamic_buffer_t;
static int allocate_buffer(dynamic_buffer_t *buf, int size)
{
// 分配内存
buf->buffer = kmalloc(size, GFP_KERNEL);
if (!buf->buffer) {
printk(KERN_WARNING "Failed to allocate memory\n");
return -ENOMEM;
}
buf->size = size;
return 0;
}
static void free_buffer(dynamic_buffer_t *buf)
{
if (buf->buffer) {
kfree(buf->buffer);
buf->buffer = NULL;
buf->size = 0;
}
}
vmalloc和vfree
c
#include <linux/vmalloc.h>
static int *large_array;
static int __init init_large_array(void)
{
// 分配大内存块(可能不连续)
large_array = vmalloc(SIZE_LARGE_ARRAY * sizeof(int));
if (!large_array) {
printk(KERN_WARNING "Failed to allocate large array\n");
return -ENOMEM;
}
// 初始化数组
memset(large_array, 0, SIZE_LARGE_ARRAY * sizeof(int));
return 0;
}
static void __exit cleanup_large_array(void)
{
if (large_array) {
vfree(large_array);
large_array = NULL;
}
}
内存池
c
#include <linux/mempool.h>
#define MIN_POOL_ELEMENTS 16
#define MAX_POOL_ELEMENTS 32
static mempool_t *my_mempool;
static kmem_cache_t *my_cache;
static void *mempool_alloc_function(gfp_t gfp_mask, void *pool_data)
{
return kmalloc(sizeof(my_data_t), gfp_mask);
}
static void mempool_free_function(void *element, void *pool_data)
{
kfree(element);
}
static int __init init_mempool(void)
{
// 创建slab缓存
my_cache = kmem_cache_create("my_cache", sizeof(my_data_t), 0,
SLAB_HWCACHE_ALIGN, NULL);
if (!my_cache)
return -ENOMEM;
// 创建内存池
my_mempool = mempool_create(MIN_POOL_ELEMENTS, mempool_alloc_function,
mempool_free_function, NULL);
if (!my_mempool) {
kmem_cache_destroy(my_cache);
return -ENOMEM;
}
return 0;
}
static void __exit cleanup_mempool(void)
{
if (my_mempool) {
mempool_destroy(my_mempool);
my_mempool = NULL;
}
if (my_cache) {
kmem_cache_destroy(my_cache);
my_cache = NULL;
}
}
调试技巧
使用printk
c
// 不同级别的打印信息
printk(KERN_EMERG "Emergency message\n");
printk(KERN_ALERT "Alert message\n");
printk(KERN_CRIT "Critical message\n");
printk(KERN_ERR "Error message\n");
printk(KERN_WARNING "Warning message\n");
printk(KERN_NOTICE "Notice message\n");
printk(KERN_INFO "Info message\n");
printk(KERN_DEBUG "Debug message\n");
动态调试
c
// 使用动态调试
#define pr_fmt(fmt) KBUILD_MODNAME ": " fmt
#include <linux/dynamic_debug.h>
// 在代码中使用动态调试宏
pr_debug("Debug information: value=%d\n", value);
// 启用动态调试
echo 'module my_device +p' > /sys/kernel/debug/dynamic_debug/control
使用proc文件系统
c
#include <linux/proc_fs.h>
#include <linux/seq_file.h>
static int device_proc_show(struct seq_file *m, void *v)
{
struct my_device_data *dev = &global_device_data;
seq_printf(m, "Device Information:\n");
seq_printf(m, "Major number: %d\n", MAJOR(dev_num));
seq_printf(m, "Minor number: %d\n", MINOR(dev_num));
seq_printf(m, "Buffer size: %d\n", BUFFER_SIZE);
seq_printf(m, "Current data size: %d\n", dev->data_size);
return 0;
}
static int device_proc_open(struct inode *inode, struct file *file)
{
return single_open(file, device_proc_show, NULL);
}
static const struct file_operations device_proc_fops = {
.owner = THIS_MODULE,
.open = device_proc_open,
.read = seq_read,
.llseek = seq_lseek,
.release = single_release,
};
static int __init create_proc_entry(void)
{
proc_create("my_device_info", 0, NULL, &device_proc_fops);
return 0;
}
static void __exit remove_proc_entry(void)
{
remove_proc_entry("my_device_info", NULL);
}
使用debugfs
c
#include <linux/debugfs.h>
static struct dentry *debug_dir;
static struct dentry *debug_file;
static int debug_open(struct inode *inode, struct file *file)
{
return single_open(file, debug_show, inode->i_private);
}
static ssize_t debug_write(struct file *file, const char __user *buf, size_t count, loff_t *ppos)
{
// 处理调试命令
char command[100];
if (count > sizeof(command) - 1)
return -EINVAL;
if (copy_from_user(command, buf, count))
return -EFAULT;
command[count] = '\0';
// 解析并执行调试命令
process_debug_command(command);
return count;
}
static const struct file_operations debug_fops = {
.owner = THIS_MODULE,
.open = debug_open,
.read = seq_read,
.write = debug_write,
.llseek = seq_lseek,
.release = single_release,
};
static int __init create_debug_entries(void)
{
debug_dir = debugfs_create_dir("my_device", NULL);
if (!debug_dir)
return -ENOMEM;
debug_file = debugfs_create_file("debug", 0644, debug_dir, NULL, &debug_fops);
if (!debug_file) {
debugfs_remove(debug_dir);
return -ENOMEM;
}
return 0;
}
static void __exit remove_debug_entries(void)
{
debugfs_remove(debug_file);
debugfs_remove(debug_dir);
}
完整示例
完整的字符设备驱动
c
#include <linux/init.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include <linux/uaccess.h>
#include <linux/semaphore.h>
#include <linux/wait.h>
#include <linux/sched.h>
#define DEVICE_NAME "my_char_dev"
#define BUFFER_SIZE 1024
#define MAJOR_NUM 240
// 设备数据结构
struct my_char_device {
struct cdev cdev;
char buffer[BUFFER_SIZE];
int data_size;
struct semaphore sem;
wait_queue_head_t read_queue;
wait_queue_head_t write_queue;
struct class *dev_class;
struct device *dev;
};
// 全局变量
static dev_t dev_num;
static struct my_char_device my_device;
// 文件操作函数声明
static int device_open(struct inode *, struct file *);
static int device_release(struct inode *, struct file *);
static ssize_t device_read(struct file *, char __user *, size_t, loff_t *);
static ssize_t device_write(struct file *, const char __user *, size_t, loff_t *);
// 文件操作结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
};
// open函数
static int device_open(struct inode *inode, struct file *filp)
{
struct my_char_device *dev;
dev = container_of(inode->i_cdev, struct my_char_device, cdev);
filp->private_data = dev;
try_module_get(THIS_MODULE);
printk(KERN_INFO "Device opened\n");
return 0;
}
// release函数
static int device_release(struct inode *inode, struct file *filp)
{
module_put(THIS_MODULE);
printk(KERN_INFO "Device closed\n");
return 0;
}
// read函数
static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
struct my_char_device *dev = filp->private_data;
ssize_t retval = 0;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
while (dev->data_size == 0) {
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->read_queue, dev->data_size > 0))
return -ERESTARTSYS;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
if (count > dev->data_size)
count = dev->data_size;
if (copy_to_user(buf, dev->buffer, count)) {
retval = -EFAULT;
goto out;
}
memcpy(dev->buffer, dev->buffer + count, dev->data_size - count);
dev->data_size -= count;
retval = count;
wake_up_interruptible(&dev->write_queue);
out:
up(&dev->sem);
return retval;
}
// write函数
static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
struct my_char_device *dev = filp->private_data;
ssize_t retval = -ENOMEM;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
while (dev->data_size == BUFFER_SIZE) {
up(&dev->sem);
if (filp->f_flags & O_NONBLOCK)
return -EAGAIN;
if (wait_event_interruptible(dev->write_queue, dev->data_size < BUFFER_SIZE))
return -ERESTARTSYS;
if (down_interruptible(&dev->sem))
return -ERESTARTSYS;
}
if (count > BUFFER_SIZE - dev->data_size)
count = BUFFER_SIZE - dev->data_size;
if (copy_from_user(dev->buffer + dev->data_size, buf, count)) {
retval = -EFAULT;
goto out;
}
dev->data_size += count;
retval = count;
wake_up_interruptible(&dev->read_queue);
out:
up(&dev->sem);
return retval;
}
// 模块初始化函数
static int __init char_device_init(void)
{
int result;
// 注册设备号
dev_num = MKDEV(MAJOR_NUM, 0);
result = register_chrdev_region(dev_num, 1, DEVICE_NAME);
if (result < 0) {
printk(KERN_WARNING "Can't register device number\n");
return result;
}
// 初始化字符设备
cdev_init(&my_device.cdev, &fops);
my_device.cdev.owner = THIS_MODULE;
// 添加字符设备到内核
result = cdev_add(&my_device.cdev, dev_num, 1);
if (result) {
printk(KERN_WARNING "Error adding character device\n");
goto fail_cdev_add;
}
// 创建设备类
my_device.dev_class = class_create(THIS_MODULE, DEVICE_NAME);
if (IS_ERR(my_device.dev_class)) {
result = PTR_ERR(my_device.dev_class);
goto fail_class_create;
}
// 创建设备文件
my_device.dev = device_create(my_device.dev_class, NULL, dev_num, NULL, DEVICE_NAME);
if (IS_ERR(my_device.dev)) {
result = PTR_ERR(my_device.dev);
goto fail_device_create;
}
// 初始化同步机制
sema_init(&my_device.sem, 1);
init_waitqueue_head(&my_device.read_queue);
init_waitqueue_head(&my_device.write_queue);
my_device.data_size = 0;
printk(KERN_INFO "Character device registered successfully\n");
return 0;
fail_device_create:
class_destroy(my_device.dev_class);
fail_class_create:
cdev_del(&my_device.cdev);
fail_cdev_add:
unregister_chrdev_region(dev_num, 1);
return result;
}
// 模块退出函数
static void __exit char_device_exit(void)
{
device_destroy(my_device.dev_class, dev_num);
class_destroy(my_device.dev_class);
cdev_del(&my_device.cdev);
unregister_chrdev_region(dev_num, 1);
printk(KERN_INFO "Character device unregistered\n");
}
module_init(char_device_init);
module_exit(char_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A complete character device driver");
MODULE_VERSION("1.0");
Makefile
makefile
obj-m += my_char_device.o
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
all:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
$(MAKE) -C $(KERNELDIR) M=$(PWD) clean
install:
sudo insmod my_char_device.ko
uninstall:
sudo rmmod my_char_device
test:
echo "Hello, device!" > /dev/my_char_dev
cat /dev/my_char_dev
测试程序
c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#define DEVICE_PATH "/dev/my_char_dev"
int main(int argc, char *argv[])
{
int fd;
char buffer[256];
ssize_t bytes;
// 打开设备
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return 1;
}
printf("Device opened successfully\n");
// 写入数据
if (argc > 1) {
bytes = write(fd, argv[1], strlen(argv[1]));
printf("Wrote %zd bytes to device\n", bytes);
}
// 读取数据
bytes = read(fd, buffer, sizeof(buffer) - 1);
if (bytes > 0) {
buffer[bytes] = '\0';
printf("Read %zd bytes from device: %s\n", bytes, buffer);
}
// 关闭设备
close(fd);
return 0;
}
最佳实践
1. 错误处理
- 始终检查返回值
- 使用适当的错误码
- 提供有意义的错误信息
2. 内存管理
- 使用适当的内存分配函数
- 检查分配结果
- 确保释放所有分配的内存
3. 同步机制
- 使用适当的同步原语
- 避免死锁
- 最小化临界区
4. 可移植性
- 使用内核API而不是直接访问硬件
- 避免架构相关的代码
- 遵循内核编码风格
5. 调试和测试
- 添加足够的调试信息
- 创建测试程序
- 使用内核调试工具
6. 文档和维护
- 添加详细的注释
- 创建使用文档
- 维护变更日志
7. 安全性
- 验证用户输入
- 检查权限
- 避免缓冲区溢出
8. 性能优化
- 最小化系统调用开销
- 使用高效的数据结构
- 考虑使用DMA(如适用)
总结
Linux字符设备驱动开发是内核编程的重要组成部分。通过理解核心概念、掌握基本架构、实现必要的功能,并遵循最佳实践,可以开发出高质量、可靠的字符设备驱动程序。
关键点回顾:
- 理解字符设备与块设备的区别
- 掌握设备注册和文件操作实现
- 熟练使用同步机制处理并发
- 正确处理阻塞和非阻塞操作
- 实现必要的ioctl命令
- 注重错误处理和调试
- 遵循内核编程规范
随着经验的积累,可以进一步探索更高级的主题,如内存映射、DMA操作、电源管理等,以开发更复杂和功能丰富的设备驱动程序。