目录
1. 确定主设备号
主设备号用于唯一标识一个设备类型。在Linux中,设备号由主设备号和次设备号组成。你可以通过alloc_chrdev_region
函数来动态分配主设备号,或者使用IOCTL
命令来请求一个特定的主设备号。例如:
int ret = alloc_chrdev_region(&dev, 0, 1, "my_device");
这里的dev
是一个dev_t
类型的变量,用来存储分配的设备号。
2. 定义自己的file_operation
结构体
file_operation
结构体包含了设备支持的文件操作函数指针,如open
、read
、write
等。例如:
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.open = my_open,
.release = my_release,
.read = my_read,
.write = my_write,
.poll = my_poll,
};
3. 实现对应的open
、read
、write
等函数
这些函数需要根据你的设备特性来实现,例如:
当实现字符设备驱动中的open
、read
、write
等函数时,你需要考虑设备的特性和操作系统的API。下面是一些示例,展示了如何实现这些函数:
-
open
函数:这个函数在文件被打开时调用,可以用于初始化设备资源、设置设备状态等。static int my_device_open(struct inode *inode, struct file *filp) { // 获取设备的私有数据 struct my_device *dev = container_of(inode->i_cdev, struct my_device, cdev); // 初始化设备,例如设置设备状态为可用 dev->is_open = 1; // 你可以在此处分配资源,例如DMA缓冲区 dev->buffer = dma_alloc_coherent(...); // 如果设备正在使用中,可以返回-EBUSY错误 if (atomic_read(&dev->users) > MAX_USERS) { dev->is_open = 0; return -EBUSY; } // 增加设备使用计数 atomic_inc(&dev->users); return 0; }
-
read
函数:这个函数用于从设备读取数据。static ssize_t my_device_read(struct file *filp, char __user *buf, size_t count, loff_t *ppos) { struct my_device *dev = filp->private_data; // 从设备读取数据到缓冲区 int bytes_read = my_device_read_from_hw(dev, dev->buffer, count); // 将数据从缓冲区复制到用户空间 if (bytes_read > 0 && copy_to_user(buf, dev->buffer, bytes_read)) { return -EFAULT; } // 更新读取位置 *ppos += bytes_read; return bytes_read; }
-
write
函数:这个函数用于向设备写入数据。static ssize_t my_device_write(struct file *filp, const char __user *buf, size_t count, loff_t *ppos) { struct my_device *dev = filp->private_data; // 从用户空间复制数据到缓冲区 if (copy_from_user(dev->buffer, buf, count)) { return -EFAULT; } // 将数据写入设备 int bytes_written = my_device_write_to_hw(dev, dev->buffer, count); // 更新写入位置 *ppos += bytes_written; return bytes_written; }
-
release
函数:这个函数在文件被关闭时调用,用于释放设备资源。static int my_device_release(struct inode *inode, struct file *filp) { struct my_device *dev = filp->private_data; // 减少设备使用计数 atomic_dec(&dev->users); // 如果没有用户,释放资源 if (atomic_read(&dev->users) == 0) { dev->is_open = 0; dma_free_coherent(...); } return 0; }
在这些示例中,my_device
是一个结构体,包含了设备的私有数据,例如DMA缓冲区、设备状态等。my_device_read_from_hw
和my_device_write_to_hw
是用于与硬件交互的函数,你需要根据具体的设备来实现它们。
4. 把file_operations
结构体告诉内核
在注册设备时,你需要提供device_operations
字段,这个字段实际上是指向file_operations
的指针。例如:
static struct cdev my_cdev;
cdev_init(&my_cdev, &my_fops);
5. 入口函数和出口函数
通常使用module_init
和module_exit
宏来指定你的入口和出口函数,例如:
static int __init my_driver_init(void) {
// 注册设备
return 0;
}
static void __exit my_driver_exit(void) {
// 清理资源
}
module_init(my_driver_init);
module_exit(my_driver_exit);
6. 提供设备信息和创建设备节点
你可以使用class_create
和device_create
函数来创建设备类别和设备节点,例如:
struct class *my_class;
struct device *my_device;
my_class = class_create(THIS_MODULE, "my_class");
my_device = device_create(my_class, NULL, dev, NULL, "my_device");
完成以上步骤后,你的字符设备驱动程序就基本完成了,可以进行编译和加载测试了。