
Linux内核驱动开发---789it.top/14260/Linux内核驱动开发---
Linux 内核驱动开发:精通 file_operations 结构体
课程概述
本课程将深入讲解 Linux 内核驱动开发中的核心概念 - file_operations
结构体,帮助您掌握设备文件与用户空间交互的关键技术。
1. file_operations 结构体基础
1.1 什么是 file_operations?
file_operations
是 Linux 内核中定义的一个关键数据结构,它包含了指向各种文件操作函数的指针。当用户空间程序对设备文件进行系统调用时,内核会调用相应的函数指针。
c
// 主要结构定义(Linux 5.x+ 版本)
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 *);
// ... 其他成员
};
1.2 结构体成员详解
成员 | 描述 | 对应系统调用 |
---|---|---|
open |
打开设备文件 | open() |
release |
关闭设备文件 | close() |
read |
从设备读取数据 | read() |
write |
向设备写入数据 | write() |
llseek |
改变文件位置 | lseek() |
unlocked_ioctl |
设备控制命令 | ioctl() |
mmap |
内存映射 | mmap() |
poll |
轮询设备状态 | select() , poll() |
2. 实战:实现一个简单的字符设备驱动
2.1 基础驱动框架
c
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#define DEVICE_NAME "my_device"
#define CLASS_NAME "my_class"
static int major_number;
static struct class* dev_class = NULL;
static struct cdev my_cdev;
// 设备缓冲区
static char device_buffer[256];
static int buffer_index = 0;
// file_operations 函数实现
static int device_open(struct inode *inode, struct file *file)
{
printk(KERN_INFO "MyDevice: Device opened\n");
return 0;
}
static int device_release(struct inode *inode, struct file *file)
{
printk(KERN_INFO "MyDevice: Device closed\n");
return 0;
}
static ssize_t device_read(struct file *filp, char __user *buf,
size_t len, loff_t *offset)
{
int bytes_to_copy;
int retval;
// 计算要拷贝的字节数
bytes_to_copy = min(len, (size_t)buffer_index);
if (bytes_to_copy == 0) {
return 0; // EOF
}
// 将数据从内核空间拷贝到用户空间
retval = copy_to_user(buf, device_buffer, bytes_to_copy);
if (retval) {
return -EFAULT; // 拷贝失败
}
// 更新偏移量
*offset += bytes_to_copy;
printk(KERN_INFO "MyDevice: Read %d bytes\n", bytes_to_copy);
return bytes_to_copy;
}
static ssize_t device_write(struct file *filp, const char __user *buf,
size_t len, loff_t *offset)
{
int bytes_to_copy;
int retval;
// 确保不超出缓冲区大小
bytes_to_copy = min(len, sizeof(device_buffer) - 1);
if (bytes_to_copy == 0) {
return -ENOMEM; // 缓冲区满
}
// 从用户空间拷贝数据到内核空间
retval = copy_from_user(device_buffer, buf, bytes_to_copy);
if (retval) {
return -EFAULT;
}
device_buffer[bytes_to_copy] = '\0'; // 添加字符串结束符
buffer_index = bytes_to_copy;
printk(KERN_INFO "MyDevice: Write %d bytes: %s\n", bytes_to_copy, device_buffer);
return bytes_to_copy;
}
// 定义 file_operations 结构体
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
};
// 模块初始化
static int __init my_device_init(void)
{
int retval;
dev_t dev = 0;
printk(KERN_INFO "MyDevice: Initializing device driver\n");
// 动态分配主设备号
retval = alloc_chrdev_region(&dev, 0, 1, DEVICE_NAME);
if (retval < 0) {
printk(KERN_ALERT "MyDevice: Failed to allocate device number\n");
return retval;
}
major_number = MAJOR(dev);
// 初始化 cdev 结构
cdev_init(&my_cdev, &fops);
my_cdev.owner = THIS_MODULE;
// 添加字符设备到系统
retval = cdev_add(&my_cdev, dev, 1);
if (retval < 0) {
unregister_chrdev_region(dev, 1);
printk(KERN_ALERT "MyDevice: Failed to add cdev\n");
return retval;
}
// 创建设备类
dev_class = class_create(THIS_MODULE, CLASS_NAME);
if (IS_ERR(dev_class)) {
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_ALERT "MyDevice: Failed to create class\n");
return PTR_ERR(dev_class);
}
// 创建设备文件
device_create(dev_class, NULL, dev, NULL, DEVICE_NAME);
printk(KERN_INFO "MyDevice: Device driver loaded successfully\n");
printk(KERN_INFO "MyDevice: Major number is %d\n", major_number);
return 0;
}
// 模块退出
static void __exit my_device_exit(void)
{
dev_t dev = MKDEV(major_number, 0);
// 清理资源
device_destroy(dev_class, dev);
class_destroy(dev_class);
cdev_del(&my_cdev);
unregister_chrdev_region(dev, 1);
printk(KERN_INFO "MyDevice: Device driver unloaded\n");
}
module_init(my_device_init);
module_exit(my_device_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Your Name");
MODULE_DESCRIPTION("A simple character device driver");
2.2 测试应用程序
c
// test_device.c - 用户空间测试程序
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
#define DEVICE_PATH "/dev/my_device"
int main()
{
int fd;
char buffer[256];
ssize_t ret;
// 打开设备
fd = open(DEVICE_PATH, O_RDWR);
if (fd < 0) {
perror("Failed to open device");
return -1;
}
// 写入数据
char *message = "Hello, Linux Driver!";
ret = write(fd, message, strlen(message));
if (ret < 0) {
perror("Failed to write to device");
close(fd);
return -1;
}
printf("Written %zd bytes to device\n", ret);
// 读取数据
ret = read(fd, buffer, sizeof(buffer) - 1);
if (ret < 0) {
perror("Failed to read from device");
close(fd);
return -1;
}
buffer[ret] = '\0'; // 添加字符串结束符
printf("Read %zd bytes from device: %s\n", ret, buffer);
// 关闭设备
close(fd);
return 0;
}
3. 高级功能实现
3.1 ioctl 命令实现
c
#include <linux/ioctl.h>
// 定义 ioctl 命令
#define MYDEVICE_IOCTL_MAGIC 'k'
#define MYDEVICE_RESET _IO(MYDEVICE_IOCTL_MAGIC, 0)
#define MYDEVICE_GET_SIZE _IOR(MYDEVICE_IOCTL_MAGIC, 1, int)
#define MYDEVICE_SET_SIZE _IOW(MYDEVICE_IOCTL_MAGIC, 2, int)
static long device_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int retval = 0;
// 检查命令类型和权限
if (_IOC_TYPE(cmd) != MYDEVICE_IOCTL_MAGIC) {
return -ENOTTY; // 不是我们的命令
}
if (_IOC_NR(cmd) > 2) {
return -ENOTTY; // 命令编号无效
}
switch (cmd) {
case MYDEVICE_RESET:
buffer_index = 0;
memset(device_buffer, 0, sizeof(device_buffer));
printk(KERN_INFO "MyDevice: Buffer reset\n");
break;
case MYDEVICE_GET_SIZE:
retval = put_user(buffer_index, (int __user *)arg);
if (retval) {
return -EFAULT;
}
printk(KERN_INFO "MyDevice: Get size: %d\n", buffer_index);
break;
case MYDEVICE_SET_SIZE:
// 实际应用中需要更复杂的边界检查
retval = get_user(buffer_index, (int __user *)arg);
if (retval) {
return -EFAULT;
}
printk(KERN_INFO "MyDevice: Set size to: %d\n", buffer_index);
break;
default:
return -ENOTTY;
}
return retval;
}
// 更新 file_operations
static struct file_operations fops = {
.owner = THIS_MODULE,
.open = device_open,
.release = device_release,
.read = device_read,
.write = device_write,
.unlocked_ioctl = device_ioctl,
};
3.2 poll 操作实现
c
#include <linux/poll.h>
static unsigned int device_poll(struct file *filp, poll_table *wait)
{
unsigned int mask = 0;
// 将等待队列添加到 poll_table
poll_wait(filp, &my_wait_queue, wait);
// 检查设备状态
if (buffer_index > 0) {
mask |= POLLIN | POLLRDNORM; // 可读
}
if (buffer_index < sizeof(device_buffer) - 1) {
mask |= POLLOUT | POLLWRNORM; // 可写
}
return mask;
}
// 更新 file_operations
static struct file_operations fops = {
// ... 其他成员
.poll = device_poll,
};
4. 最佳实践和注意事项
4.1 错误处理要点
c
// 正确的错误处理示例
static ssize_t safe_device_read(struct file *filp, char __user *buf,
size_t len, loff_t *offset)
{
// 参数验证
if (!buf) {
return -EINVAL;
}
if (len == 0) {
return 0;
}
// 边界检查
if (*offset >= buffer_index) {
return 0; // EOF
}
// 实际读取操作...
}
4.2 并发控制
c
#include <linux/mutex.h>
static DEFINE_MUTEX(device_mutex);
static ssize_t thread_safe_write(struct file *filp, const char __user *buf,
size_t len, loff_t *offset)
{
int retval;
// 获取互斥锁
if (mutex_lock_interruptible(&device_mutex)) {
return -ERESTARTSYS;
}
// 临界区操作
retval = copy_from_user(device_buffer, buf,
min(len, sizeof(device_buffer) - 1));
if (!retval) {
// 成功处理
buffer_index = min(len, sizeof(device_buffer) - 1);
device_buffer[buffer_index] = '\0';
}
// 释放互斥锁
mutex_unlock(&device_mutex);
return retval ? -EFAULT : len;
}
5. 调试技巧
5.1 使用 printk 调试
c
// 不同级别的调试信息
#define DEBUG_LEVEL 3
#if DEBUG_LEVEL >= 1
#define dbg_info(fmt, args...) printk(KERN_INFO "MyDevice: " fmt, ##args)
#else
#define dbg_info(fmt, args...)
#endif
#if DEBUG_LEVEL >= 2
#define dbg_debug(fmt, args...) printk(KERN_DEBUG "MyDevice: " fmt, ##args)
#else
#define dbg_debug(fmt, args...)
#endif
5.2 动态调试
c
// 在模块参数中控制调试级别
static int debug_level = 1;
module_param(debug_level, int, S_IRUGO | S_IWUSR);
// 条件调试输出
if (debug_level > 0) {
printk(KERN_DEBUG "MyDevice: Debug information\n");
}
总结
通过本课程的学习,您应该能够:
- 理解
file_operations
结构体的作用和重要性 - 实现 完整的字符设备驱动程序
- 掌握 用户空间与内核空间的数据交换
- 应用 高级功能如 ioctl、poll 等
- 遵循 内核驱动开发的最佳实践
继续深入学习建议:
- 研究内核源码中的实际驱动实现
- 学习设备树(Device Tree)的使用
- 掌握中断处理和 DMA 操作
- 了解内核同步机制和内存管理
这个框架为您提供了 Linux 内核驱动开发的核心知识,特别是 file_operations
结构体的深入理解。实际开发中请根据具体需求和内核版本进行适当调整。