Linux内核字符设备驱动详解
在Linux操作系统的世界中,一切皆文件,无论是硬件设备还是普通文件,都可以通过统一的接口进行访问,本文将详细探讨Linux内核中的字符设备驱动程序,特别是cdev结构体的使用,通过本文,您将了解字符设备的基础知识、cdev结构体的定义和操作,以及如何在内核中实现一个简单的字符设备驱动。
一、字符设备简介
字符设备是Linux系统中的三大类设备之一(另外两类是块设备和网络设备),它们面向流数据,以字节为单位进行顺序读取或写入操作,常见的字符设备包括鼠标、键盘、串口等,与块设备不同,字符设备通常不能随机访问数据,而是按照顺序读取或写入。
二、cdev结构体解析
在Linux内核中,struct cdev
结构体用于描述一个字符设备,该结构体定义在<linux/cdev.h>
头文件中,其定义如下:
struct cdev { struct kobject kobj; // 内嵌的内核对象 struct module *owner; // 拥有该字符设备的模块指针 const struct file_operations *ops; // 默认文件操作接口 struct list_head list; // 用于链表管理 dev_t dev; // 设备号 unsigned int count; // 次设备号的数量 };
1.kobject kobj
kobject
是一个内核对象,用于表示驱动程序中的设备,它包含一些基本的成员函数指针和引用计数等。
2.module *owner
指向拥有该字符设备结构体的模块的指针,这是为了确保设备在模块卸载时能够被正确释放。
3.file_operations *ops
这是一个函数指针数组,包含了字符设备驱动提供给虚拟文件系统的接口函数,如open
、read
、write
等。
4.list_head list
用于将所有注册的字符设备连接成一个链表,方便管理和遍历。
5.dev_t dev
字符设备的设备号,由主设备号和次设备号组成,主设备号标识设备类型,而次设备号区分同一类型的多个设备。
6.unsigned int count
隶属于同一主设备号的次设备号的数量。
三、cdev结构体的操作函数
1.cdev_init()
该函数用于初始化一个cdev结构体,并建立cdev与file_operations之间的连接。
void cdev_init(struct cdev *cdev, const struct file_operations *fops) { memset(cdev, 0, sizeof(*cdev)); INIT_LIST_HEAD(&cdev->list); kobject_init(&cdev->kobj, &ktype_cdev_default); cdev->ops = fops; }
2.cdev_alloc()
动态申请一个cdev结构体内存,并进行初步的初始化。
struct cdev *cdev_alloc(void) { struct cdev *p = kzalloc(sizeof(struct cdev), GFP_KERNEL); if (p) { INIT_LIST_HEAD(&p->list); kobject_init(&p->kobj, &ktype_cdev_dynamic); } return p; }
3.cdev_add()
向系统添加一个cdev结构体,完成字符设备的注册。
int cdev_add(struct cdev *p, dev_t dev, unsigned count) { int ret; p->dev = dev; p->count = count; ret = kobject_add(&p->kobj, &char_ktype, NULL, "%s", devname(dev)); if (ret < 0) return ret; return 0; }
4.cdev_del()
从系统中删除一个cdev结构体,注销字符设备。
void cdev_del(struct cdev *p) { kobject_put(&p->kobj); }
四、字符设备驱动模型
字符设备驱动模型主要包括以下几个部分:
设备号分配:静态或动态分配设备号。
cdev结构体初始化:使用cdev_init()
函数初始化cdev结构体。
file_operations实现:编写具体的设备操作函数,并将函数指针赋值给file_operations结构体。
设备注册与注销:使用cdev_add()
和cdev_del()
函数向系统注册和注销字符设备。
用户空间交互:用户程序通过设备文件访问字符设备,触发内核中的设备操作函数。
五、示例代码
以下是一个简单的字符设备驱动示例,展示了如何使用cdev结构体实现一个基本的字符设备驱动。
#include <linux/init.h> #include <linux/module.h> #include <linux/cdev.h> #include <linux/fs.h> #include <linux/slab.h> #include <linux/uaccess.h> #define MYDEVICE_NAME "mychardevice" #define CLASS_NAME "mycharclass" static int majorNumber; static struct class *charClass; static struct cdev myCdev; static char msg[80] = "Hello, Linux kernel!"; static int deviceOpened = 0; static int messageLen = 0; // 定义file_operations结构体 static struct file_operations fops = { .owner = THIS_MODULE, .open = deviceOpen, .release = deviceRelease, .read = deviceRead, .write = deviceWrite, }; // 打开设备函数 int deviceOpen(struct inode *inode, struct file *file) { if (deviceOpened) return -EBUSY; deviceOpened++; try_module_get(THIS_MODULE); printk(KERN_INFO "Device opened "); return 0; } // 释放设备函数 int deviceRelease(struct inode *inode, struct file *file) { deviceOpened--; module_put(THIS_MODULE); printk(KERN_INFO "Device released "); return 0; } // 读取设备函数 ssize_t deviceRead(struct file *filp, char __user *buffer, size_t len, loff_t *offset) { int error_count = 0; printk(KERN_INFO "Device read request "); if (*offset == 0) { error_count = copy_to_user(buffer, msg, strlen(msg)); *offset = strlen(msg); } else { error_count = -EFAULT; // Offset not supported yet. } return error_count ? error_count : strlen(msg); } // 写入设备函数 ssize_t deviceWrite(struct file *filp, const char __user *buff, size_t len, loff_t *offset) { int error_count = 0; printk(KERN_INFO "Device write request "); if (len > sizeof(msg)) len = sizeof(msg); if (copy_from_user(msg, buff, len)) { printk(KERN_ALERT "Failed to copy from user space "); error_count = -EFAULT; // Failed to copy from user space. } else { msg[len] = '