欢迎来到军工软件开发人才培养基地——学到牛牛

Linux字符设备驱动高级封装

时间:2024-05-06 07:01:10 来源:学到牛牛

​在Linux驱动中,cdev和file_operations是紧密关联的,它们在字符设备驱动中扮演重要角色。以下是它们之间的关系:

1.cdev结构体:在Linux内核中,字符设备由cdev结构体来描述。cdev包含了设备号(通过dev_t成员)和指向file_operations结构体的指针等重要信息。设备号是用于在系统中唯一标识字符设备的。

2.file_operations结构体:file_operations结构体定义了一系列指向函数的指针,这些函数实现了字符设备驱动提供给VFS(Virtual File System,虚拟文件系统)的接口。这些函数包括常见的open()、read()、write()等。当用户空间程序通过系统调用访问字符设备时,这些函数会被调用。

cdev结构体中的file_operations指针将字符设备与具体的操作函数关联起来。这意味着,当一个字符设备被访问时,内核知道应该调用哪些函数来处理这个访问。这种关联是通过在驱动初始化时,将file_operations结构体的地址赋值给cdev结构体的相应成员来实现的。

在字符设备驱动的模块加载函数中,通常会使用register_chrdev_region()或alloc_chrdev_region()来获取设备号,并使用cdev_init()来初始化cdev结构体并建立与file_operations的关联。然后,通过cdev_add()将cdev添加到系统中,完成驱动的注册。在模块卸载函数中,则通过cdev_del()和unregister_chrdev_region()来注销设备和释放设备号。

总的来说,cdev和file_operations在Linux字符设备驱动中共同工作,使得用户空间程序能够通过系统调用访问和控制字符设备。cdev提供了设备的描述和标识,而file_operations定义了设备的行为和操作。

那么如果需要注册多个设备,最简单的思路是多个cdev和多个file_operations对象,并将他们一一对应。此思路在同一驱动代码下支持多个设备驱动和多个驱动代码支持多个设备驱动完成没有差别。但是我们发现所有设备的file_operations全部是一致的,我们能否将所有的cdev和同一个file_operations进行绑定,但是又能够在读写上面区分你具体是哪一个cdev,这便是设备抽象以及面向对象的思路。

示例代码如下:

#include <linux/kernel.h>

#include <linux/init.h>

#include <linux/module.h>

#include <linux/sched.h>

#include <linux/cdev.h>

#include <asm/uaccess.h>

 

 

MODULE_LICENSE("GPL");

 

struct xdndev{ // 抽象一个dev对象,包含cdev以及该设备资源

struct cdev dev;

char buffer[20];

// dev_t devid;

};

 

struct xdndev devs[2]; // 注册两个设备

 

 

ssize_t xread(struct file *fp, char __user *buf, size_t len, loff_t *off)

{

// 通过fp指针找到传过来的xdndev是谁,对应访问谁的设备资源

struct xdndev *dptr = container_of( fp->private_data, struct xdndev, dev );

// copy_to_user

// len = len > 20 ? 20 : len;

copy_to_user( buf, dptr->buffer, len );

return 0;

}

 

ssize_t xwrite(struct file *fp, const char __user *buf, size_t len, loff_t *off)

{

// 通过fp指针找到传过来的xdndev是谁,对应访问谁的设备资源

struct xdndev *dptr = container_of( fp->private_data, struct xdndev, dev );

// copy_from_user

copy_from_user( dptr->buffer, buf, len );

printk("buf = %s\n", buf );

return len;

}

 

int xopen (struct inode *no, struct file *fp)

{

printk("xopen = %p, %x\n", no->i_cdev, no->i_rdev);

// 两个设备在底层的inode节点不一样,并通过struct file* fp进行传参

fp->private_data = no->i_cdev;

return 0;

}

 

int xrelease (struct inode *no, struct file *fp)

{

printk("xrelease\n");

return 0;

}

 

struct file_operations xops = {

.open = xopen,

.release = xrelease,

.read = xread,

.write = xwrite,

};

 

 

 

int xuedaon_init( void )

{

cdev_init( &devs[0].dev, &xops ); // 两个设备对应同一个xops

cdev_add( &devs[0].dev, MKDEV(99,100), 1 );

cdev_init( &devs[1].dev, &xops ); // 两个设备对应同一个xops

cdev_add( &devs[1].dev, MKDEV(99,101), 1 );

return 0;

}

 

void xuedaon_exit( void )

{

cdev_del( &devs[0].dev );

cdev_del( &devs[1].dev );

printk("exit\n");

}

 

module_init( xuedaon_init );

module_exit( xuedaon_exit );

以上代码能够以面向对象的思维实现多个设备对应同一个file_operations的调用,该封装方式在linux内核代码中随处可见。

文中所涉及的宏container_of参考前面的“内核链表”相关文档。