Char Device Driver

本文深入解析字符设备驱动的实现机制,包括字符驱动模型、设备号分配及文件系统访问流程。介绍核心数据结构如cdev,并解释如何通过kobj_map关联设备与驱动。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >



Char Device Driver

相关数据结构:

struct cdev {
        struct kobject kobj;
        struct module *owner;
        const struct file_operations *ops;
        struct list_head list;
        dev_t dev;
        unsigned int count;
};


struct kobj_map {
        struct probe {
                struct probe *next;
                dev_t dev;
                unsigned long range;
                struct module *owner;
                kobj_probe_t *get;
                int (*lock)(dev_t, void *);
                void *data;
        } *probes[255];
        struct mutex *lock;
};


static struct char_device_struct {
        struct char_device_struct *next;
        unsigned int major;
        unsigned int baseminor;
        int minorct;
        char name[64];
        struct file_operations *fops;
        struct cdev *cdev;                /* will die */
} *chrdevs[CHRDEV_MAJOR_HASH_SIZE];

#define CHRDEV_MAJOR_HASH_SIZE        255


下面本文通过一下三个方面以及他们的关联来描述字符设备驱动:
1. 字符驱动模型
2. 字符设备的设备号
3. 文件系统中对字符设备文件的访问


1. 字符驱动模型

每个字符驱动由一个 cdev 结构来表示.

在设备驱动模型(device driver model)中, 使用 (kobject mapping domain) 来记录字符设备驱动.
这是由 struct kobj_map 结构来表示的. 它内嵌了255个struct probe指针数组
kobj_map由全局变量 cdev_map 引用: static struct kobj_map *cdev_map;


kobj_map.gif
2008-08-26 15:45 上传
下载附件 (6.77 KB)


相关函数说明:

cdev_alloc() 用来创建一个cdev的对象

cdev_add() 用来将cdev对象添加到驱动模型中,其主要是通过kobj_map()来实现的.
        kobj_map() 会创建一个probe对象,然后将其插入cdev_map中的某一项中,并关联probe->data 指向 cdev
        
struct kobject *kobj_lookup(struct kobj_map *domain, dev_t dev, int *index)
根据设备号,在cdev_map中查找其cdev对象内嵌的kobject. (probe->data->kobj),返回的是cdev的kobject




2. 字符设备的设备号

字符设备的主,次设备号的分配:

全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct char_device_struct的元素.
每一个对应一个相应的主设备号.

如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中.
这样,通过chrdevs数组,我们就可以知道分配了哪些设备号.

char_device_struct.gif
2008-08-26 15:45 上传
下载附件 (5.8 KB)


相关函数:
register_chrdev_region( ) 分配指定的设备号范围
alloc_chrdev_region( ) 动态分配设备范围
他们都主要是通过调用函数__register_chrdev_region() 来实现的
要注意,这两个函数仅仅是注册设备号! 如果要和cdev关联起来,还要调用cdev_add()


register_chrdev( ) 申请指定的设备号,并且将其注册到字符设备驱动模型中.
它所做的事情为:
1. 注册设备号, 通过调用 __register_chrdev_region() 来实现
2. 分配一个cdev, 通过调用 cdev_alloc() 来实现
3. 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
4. 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要.




3. 文件系统中对字符设备文件的访问

对于一个字符设备文件, 其inode->i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备文件没有被打开.

由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表

cdev_file.gif
2008-08-26 15:45 上传
下载附件 (4.88 KB)


首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open.
(最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open = chrdev_open. 这一系列的调用过程,本文暂不讨论)

int chrdev_open(struct inode * inode, struct file * filp)

chrdev_open()所做的事情可以概括如下:
1. 根据设备号(inode->i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject.
2. 设置inode->i_cdev , 指向找到的cdev.
3. 将inode添加到cdev->list的链表中.
4. 使用cdev的ops 设置file对象的f_op
5. 如果ops中定义了open方法,则调用该open方法
6. 返回.

执行完chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read, write等操作,就会执行cdev的相应操作.
/* * 安装模块(带参数) * sudo insmod char_dev.ko cap=1 * * 查看设备节点 * ls -l /dev/my_char_dev * * 测试写入设备 * echo "Hello Kernel World" | sudo tee /dev/my_char_dev * * 查看内核日志(需要root权限) * sudo dmesg | grep my_char_dev * * 卸载模块 * sudo rmmod char_dev */ #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/ctype.h> #include <linux/mutex.h> #define DEVICE_NAME "my_char_dev" #define MAX_BUF_LEN 1024 /* 模块参数 */ static int cap = 0; module_param(cap, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(cap, "Convert to uppercase if set to 1 (default=0)"); /* 共享数据结构 */ static struct { char buffer[MAX_BUF_LEN]; // 数据缓冲区 size_t data_len; // 有效数据长度 struct mutex lock; // 互斥锁 } dev_data; static dev_t dev_num; static struct cdev my_cdev; static struct class *my_class; static struct device *my_device; /* 设备打开函数 */ static int device_open(struct inode *inode, struct file *file) { return 0; } /* 设备释放函数 */ static int device_release(struct inode *inode, struct file *file) { return 0; } /* 设备写入函数 */ static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { size_t len = (count > MAX_BUF_LEN - 1) ? MAX_BUF_LEN - 1 : count; int i = 0; // 加锁保护共享数据 mutex_lock(&dev_data.lock); /* 从用户空间复制数据 */ if (copy_from_user(dev_data.buffer, buf, len)) { mutex_unlock(&dev_data.lock); return -EFAULT; } dev_data.buffer[len] = '\0'; // 确保字符串终止 dev_data.data_len = len; // 记录有效长度 /* 根据cap参数进行大小写转换 */ if (cap) { for (i = 0; dev_data.buffer[i]; i++) { dev_data.buffer[i] = toupper(dev_data.buffer[i]); } } printk(KERN_INFO "my_char_dev: Received: %s\n", dev_data.buffer); mutex_unlock(&dev_data.lock); // 解锁 return len; } /* 设备读取函数 */ static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { ssize_t retval = 0; // 加锁保护共享数据 mutex_lock(&dev_data.lock); if (*f_pos >= dev_data.data_len) { mutex_unlock(&dev_data.lock); return 0; // 文件结尾 } // 计算剩余可读数据量 size_t remaining = dev_data.data_len - *f_pos; size_t to_copy = (count < remaining) ? count : remaining; // 复制数据到用户空间 if (copy_to_user(buf, dev_data.buffer + *f_pos, to_copy)) { mutex_unlock(&dev_data.lock); return -EFAULT; } *f_pos += to_copy; // 更新文件位置 retval = to_copy; mutex_unlock(&dev_data.lock); // 解锁 return retval; } /* 定义设备支持的操作 */ static const struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .write = device_write, .read = device_read, // 添加read支持 }; /* 模块初始化函数 */ static int __init char_dev_init(void) { /* 初始化共享数据 */ mutex_init(&dev_data.lock); // 初始化互斥锁 dev_data.data_len = 0; dev_data.buffer[0] = '\0'; /* 动态分配设备号 */ if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) return -1; /* 初始化字符设备 */ cdev_init(&my_cdev, &fops); if (cdev_add(&my_cdev, dev_num, 1) < 0) goto err_cdev; /* 创建设备类 */ my_class = class_create(THIS_MODULE, "my_char_class"); if (IS_ERR(my_class)) goto err_class; /* 创建设备节点 */ my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(my_device)) goto err_device; /* 设置设备权限(所有用户可读写) */ device_create_file(my_device, &dev_attr_cap); // 可选:添加cap属性文件 printk(KERN_INFO "my_char_dev: Module loaded, cap=%d\n", cap); return 0; /* 错误处理 */ err_device: class_destroy(my_class); err_class: cdev_del(&my_cdev); err_cdev: unregister_chrdev_region(dev_num, 1); return -1; } /* 模块退出函数 */ static void __exit char_dev_exit(void) { device_destroy(my_class, dev_num); class_destroy(my_class); cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); mutex_destroy(&dev_data.lock); // 销毁互斥锁 printk(KERN_INFO "my_char_dev: Module unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Advanced char device driver with case conversion"); 这段代码make以后发生以下错误: xyc@xyc-virtual-machine:~/VSCode/embedded-linux/char_device$ make make -C /lib/modules/4.15.0-213-generic/build M=/home/xyc/VSCode/embedded-linux/char_device modules make[1]: Entering directory '/usr/src/linux-headers-4.15.0-213-generic' CC [M] /home/xyc/VSCode/embedded-linux/char_device/char_dev.o /home/xyc/VSCode/embedded-linux/char_device/char_dev.c: In function ‘device_read’: /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:103:5: warning: ISO C90 forbids mixed declarations and code [-Wdeclaration-after-statement] size_t remaining = dev_data.data_len - *f_pos; ^~~~~~ /home/xyc/VSCode/embedded-linux/char_device/char_dev.c: In function ‘char_dev_init’: /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:156:36: error: ‘dev_attr_cap’ undeclared (first use in this function); did you mean ‘setattr_copy’? device_create_file(my_device, &dev_attr_cap); // 可选:添加cap属性文件 ^~~~~~~~~~~~ setattr_copy /home/xyc/VSCode/embedded-linux/char_device/char_dev.c:156:36: note: each undeclared identifier is reported only once for each function it appears in scripts/Makefile.build:340: recipe for target '/home/xyc/VSCode/embedded-linux/char_device/char_dev.o' failed make[2]: *** [/home/xyc/VSCode/embedded-linux/char_device/char_dev.o] Error 1 Makefile:1596: recipe for target '_module_/home/xyc/VSCode/embedded-linux/char_device' failed make[1]: *** [_module_/home/xyc/VSCode/embedded-linux/char_device] Error 2 make[1]: Leaving directory '/usr/src/linux-headers-4.15.0-213-generic' Makefile:5: recipe for target 'all' failed make: *** [all] Error 2 这是什么问题
最新发布
08-08
<think>我们有两个错误需要解决: 1. 在函数`device_read`中,ISO C90不允许在代码之后声明变量(即变量声明必须在函数开头)。 2. 在函数`char_dev_init`中,`dev_attr_cap`未声明。 解决方案: 1. 将`device_read`函数中的变量声明移动到函数的开头(所有可执行语句之前)。 2. 我们使用了`device_create_file`来添加一个属性文件,但是`dev_attr_cap`并没有定义。这通常是通过`DEVICE_ATTR`宏来定义的。但是,我们这里并没有定义这个属性。我们可以选择删除这行,或者实现这个属性。 根据代码,我们有一个模块参数`cap`,我们可能希望通过sysfs来在运行时修改它。因此,我们可以实现一个设备属性来允许用户空间读写`cap`。 但是,由于时间关系,我们先采用简单的方法:删除这行。因为题目中并没有要求通过sysfs修改cap,而且模块参数本身就已经可以通过sysfs在/sys/module/<module_name>/parameters/下访问(需要模块参数权限支持)。所以,我们可以删除`device_create_file`那一行。 另一种方法是实现`dev_attr_cap`,这需要定义show和store函数,并使用`DEVICE_ATTR`宏来定义属性。 我们先采用删除的方法,因为这样最简单。如果确实需要设备属性,我们可以后续添加。 修改步骤: 1. 在`device_read`函数中,将变量声明移到函数开始处。 2. 注释掉或删除`char_dev_init`函数中的`device_create_file`调用。 修改后的代码: 在`device_read`函数中: 将变量`remaining`和`to_copy`的声明放在函数开头(即`mutex_lock`之前)。 在`char_dev_init`函数中: 删除行:`device_create_file(my_device, &dev_attr_cap);` 重新编译。 如果后续需要添加`cap`的设备属性,可以这样实现: 步骤: a. 包含头文件:`#include <linux/sysfs.h>` b. 定义show和store函数: static ssize_t cap_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", cap); } static ssize_t cap_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int value; if (kstrtoint(buf, 10, &value) < 0) return -EINVAL; cap = value ? 1 : 0; // 确保是0或1 return count; } c. 使用宏定义属性:`static DEVICE_ATTR_RW(cap);` // 这会生成一个名为`dev_attr_cap`的结构体 d. 在模块初始化中,使用`device_create_file`添加属性。 e. 在模块退出时,使用`device_remove_file`移除属性。 但根据问题,我们先采用删除的方法。 修改后的代码部分如下:</think>您的代码在编译时遇到两个主要错误,我将详细解释原因并提供完整解决方案: ### 问题分析 1. **ISO C90 警告**: ```c warning: ISO C90 forbids mixed declarations and code ``` - **原因**:在 `device_read` 函数中,您在可执行语句 (`mutex_lock`) 之后声明了变量 `remaining` 和 `to_copy`。C90 标准要求所有变量声明必须在函数开头(在任何可执行语句之前)。 2. **未定义符号错误**: ```c error: 'dev_attr_cap' undeclared ``` - **原因**:您尝试使用 `device_create_file()` 创建 sysfs 属性文件,但没有定义对应的 `dev_attr_cap` 结构体。这个结构体需要通过 `DEVICE_ATTR` 宏生成。 ### 解决方案 #### 1. 修复 C90 声明问题 将 `device_read` 函数中的变量声明移动到函数开头: ```c static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { ssize_t retval = 0; size_t remaining, to_copy; // 声明提前到函数开头 mutex_lock(&dev_data.lock); if (*f_pos >= dev_data.data_len) { mutex_unlock(&dev_data.lock); return 0; } remaining = dev_data.data_len - *f_pos; to_copy = (count < remaining) ? count : remaining; if (copy_to_user(buf, dev_data.buffer + *f_pos, to_copy)) { mutex_unlock(&dev_data.lock); return -EFAULT; } *f_pos += to_copy; retval = to_copy; mutex_unlock(&dev_data.lock); return retval; } ``` #### 2. 修复 dev_attr_cap 未定义问题 有两种解决方案: ##### 方案 A:移除属性文件创建(推荐) ```c static int __init char_dev_init(void) { // ... 其他代码不变 ... /* 创建设备节点 */ my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(my_device)) goto err_device; // 删除下面这行 // device_create_file(my_device, &dev_attr_cap); // ... 其他代码不变 ... } ``` ##### 方案 B:完整实现 sysfs 属性(可选) ```c #include <linux/sysfs.h> // 添加头文件 // 定义属性操作函数 static ssize_t cap_show(struct device *dev, struct device_attribute *attr, char *buf) { return sprintf(buf, "%d\n", cap); } static ssize_t cap_store(struct device *dev, struct device_attribute *attr, const char *buf, size_t count) { int val; if (kstrtoint(buf, 0, &val) < 0) return -EINVAL; cap = val ? 1 : 0; // 确保只能是 0/1 return count; } // 使用宏定义属性 static DEVICE_ATTR_RW(cap); // 自动生成 dev_attr_cap ``` 然后在模块初始化和退出函数中添加: ```c static int __init char_dev_init(void) { // ... 其他代码 ... device_create_file(my_device, &dev_attr_cap); // 保留这行 // ... 其他代码 ... } static void __exit char_dev_exit(void) { device_remove_file(my_device, &dev_attr_cap); // 添加清理 // ... 其他代码 ... } ``` ### 完整修复代码(方案 A) 以下是采用方案 A 的完整修复代码: ```c #include <linux/module.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/ctype.h> #include <linux/mutex.h> #define DEVICE_NAME "my_char_dev" #define MAX_BUF_LEN 1024 /* 模块参数 */ static int cap = 0; module_param(cap, int, S_IRUGO | S_IWUSR); MODULE_PARM_DESC(cap, "Convert to uppercase if set to 1 (default=0)"); /* 共享数据结构 */ static struct { char buffer[MAX_BUF_LEN]; size_t data_len; struct mutex lock; } dev_data; static dev_t dev_num; static struct cdev my_cdev; static struct class *my_class; static struct device *my_device; /* 设备打开函数 */ static int device_open(struct inode *inode, struct file *file) { return 0; } /* 设备释放函数 */ static int device_release(struct inode *inode, struct file *file) { return 0; } /* 设备写入函数 */ static ssize_t device_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos) { size_t len = (count > MAX_BUF_LEN - 1) ? MAX_BUF_LEN - 1 : count; int i = 0; mutex_lock(&dev_data.lock); if (copy_from_user(dev_data.buffer, buf, len)) { mutex_unlock(&dev_data.lock); return -EFAULT; } dev_data.buffer[len] = '\0'; dev_data.data_len = len; if (cap) { for (i = 0; dev_data.buffer[i]; i++) { dev_data.buffer[i] = toupper(dev_data.buffer[i]); } } printk(KERN_INFO "my_char_dev: Received: %s\n", dev_data.buffer); mutex_unlock(&dev_data.lock); return len; } /* 设备读取函数(已修复) */ static ssize_t device_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos) { ssize_t retval = 0; size_t remaining, to_copy; // 声明提前到函数开头 mutex_lock(&dev_data.lock); if (*f_pos >= dev_data.data_len) { mutex_unlock(&dev_data.lock); return 0; } remaining = dev_data.data_len - *f_pos; to_copy = (count < remaining) ? count : remaining; if (copy_to_user(buf, dev_data.buffer + *f_pos, to_copy)) { mutex_unlock(&dev_data.lock); return -EFAULT; } *f_pos += to_copy; retval = to_copy; mutex_unlock(&dev_data.lock); return retval; } /* 文件操作结构体 */ static const struct file_operations fops = { .owner = THIS_MODULE, .open = device_open, .release = device_release, .write = device_write, .read = device_read, }; /* 模块初始化函数(已修复) */ static int __init char_dev_init(void) { mutex_init(&dev_data.lock); dev_data.data_len = 0; dev_data.buffer[0] = '\0'; if (alloc_chrdev_region(&dev_num, 0, 1, DEVICE_NAME) < 0) return -1; cdev_init(&my_cdev, &fops); if (cdev_add(&my_cdev, dev_num, 1) < 0) goto err_cdev; my_class = class_create(THIS_MODULE, "my_char_class"); if (IS_ERR(my_class)) goto err_class; my_device = device_create(my_class, NULL, dev_num, NULL, DEVICE_NAME); if (IS_ERR(my_device)) goto err_device; printk(KERN_INFO "my_char_dev: Module loaded, cap=%d\n", cap); return 0; err_device: class_destroy(my_class); err_class: cdev_del(&my_cdev); err_cdev: unregister_chrdev_region(dev_num, 1); return -1; } /* 模块退出函数 */ static void __exit char_dev_exit(void) { device_destroy(my_class, dev_num); class_destroy(my_class); cdev_del(&my_cdev); unregister_chrdev_region(dev_num, 1); mutex_destroy(&dev_data.lock); printk(KERN_INFO "my_char_dev: Module unloaded\n"); } module_init(char_dev_init); module_exit(char_dev_exit); MODULE_LICENSE("GPL"); MODULE_AUTHOR("Your Name"); MODULE_DESCRIPTION("Advanced char device driver"); ``` ### 测试步骤 1. **编译模块**: ```bash make ``` 2. **加载模块**: ```bash sudo insmod char_dev.ko cap=1 ``` 3. **写入测试**: ```bash echo "Test Message" | sudo tee /dev/my_char_dev ``` 4. **读取测试**: ```bash sudo cat /dev/my_char_dev # 应输出大写的 "TEST MESSAGE" ``` 5. **查看日志**: ```bash dmesg | tail -n 5 # 应显示 "my_char_dev: Received: TEST MESSAGE" ``` 6. **卸载模块**: ```bash sudo rmmod char_dev ``` ### 关键修复点说明 1. **C90 兼容性**:将变量声明移到函数开头,符合 C90 标准 2. **属性文件问题**:移除了未实现的 `dev_attr_cap` 相关代码 3. **互斥锁使用**:保留了正确的互斥锁保护,确保并发安全 4. **内核 API 使用**:保留了正确的字符设备注册/注销流程
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值