主要内容来自《Linux设备驱动程序》
第三章:字符设备驱动程序
主设备和次设备号
对字符设备的访问是通过文件系统内的设备名称进行的。那些名称被称为特殊文件、设备文件,或者简单称之为文件系统树的节点,通常位于/dev目录。
ls -l命令输出:第一列中的“c”表示字符设备、“b”表示块设备。
主设备号:标识设备对应的驱动程序。现在Linux内核允许多个驱动程序共享主设备号,但大多数设备仍然按照“一个主设备号对应一个驱动程序”的原则组织。
次设备号:由内核使用,用于正确确定设备文件所指的设备。
设备编号的内部表示
在内核中,dev_t类型(<linux/types.h>中定义)用来保存设备编号。
dev_t是一个32位的数,其中12位用来表示主设备号,其余20位用来表示设备号。
//查看了源码,有两处定义了dev_t。虽然不知道两者的区别,但是可以看到的确是32位。
typedef unsigned long u_long;
typedef u_long dev_t;
typedef __u32 __kernel_dev_t;
typedef __kernel_dev_t dev_t;
几个宏函数:
MAJOR(dev_t dev); /*获得dev_t的主设备号*/
MINOR(dev_t dev); /*获得dev_t的次设备号*/
MKDEV(int major, int minor); /*将主设备号和次设备号转换成dev_t类型*/
//下面是源码中宏函数的定义
#define MINORBITS 20
#define MINORMASK ((1U << MINORBITS) - 1)
#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
分配和释放设备编号
int register_chrdev_region(dev_t first, unsigned int count, char *name);//如果提前知道所需要的设备编号,则这个函数会工作的很好。但还是建议使用下面的动态分配。
参数:first是要分配的设备编号范围的起始值,first的次设备号经常被置为0;count是所请求的连续设备号的个数;name是和该编号范围关联的设备名称,它将出现在/proc/devices和sysfs中。
返回值:返回值为0表示分配成功,在错误情况下,将返回一个负的错误码,并且不能使用所请求的编号区域。
int alloc_chrdev_region(dev_t *dev, unsigned int firstminor, unsigned int count, char *name);
参数:dev是仅用于输出的参数,在成功完成调用后将保存已分配范围的第一个编号;firstminor是要使用的被请求的第一个设备号,通常为0;其它参数同上。
返回值:同上。
//LDD3中说到作为驱动程序作者,有如下选择:可以简单选定一个尚未使用的编号,或者通过动态方式分配主设备号。前者当驱动被广泛使用时可能造成冲突和麻烦,所以建议使用后者。
//动态分配的缺点是:由于分配的主设备号不能保证始终一致,所以无法预先创建设备节点。向上面说的一旦分配了设备号,可以从/proc/devices中读取得到。
//书上有一个脚本,不做深入理解,但是有一条指令:mknod filename type major minor/*创建设备节点*/分别表示:设备文件名,设备文件类型,主设备号,次设备号。
//*****分配-->释放*****以上是两种不同的注册设备号的方法,在不使用这些设备号时都要释放它们。函数如下:
void unregister_chrdev_region(dev_t first, unsigned int count);
//LDD3上有一句对以上工作的总结:上面的函数为驱动程序的使用分配设备编号,但是它们并没有告诉内核关于拿来这些编号要做什么工作。在用户空间程序可访问上述设备编号之前,驱动程序需要将设备编号和内部函数连接起来,这些内部函数用来实现设备的操作。
一些重要的数据结构(文件操作、file结构、inode结构)
文件操作:字符驱动和内核的接口
前面已经为自己保留了一些设备编号,但尚未将任何驱动程序操作连接到这些编号。file_operation结构就是用来建立这种连接的。定义在<fs.h>中。
按照惯例,file_oprations结构或者指向这类结构的指针称为fops。file_oprations的声明使用标准C的标记化结构初始化语法,这使得定义发生变化时更具可移植性,并且使得代码更加紧凑且易读。
file结构 //源码908行声明
每个打开的文件(内部由一个file结构表示)和一组函数关联(通过包含指向一个file_oprations结构的f_op字段)
file结构代表一个打开的文件(它并不仅仅限定于设备驱动程序,系统中每个打开的文件在内核空间都有一个对应的file结构)。它由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。
将该结构指针称为filp,file指结构本身。
inode结构
内核用inode结构在内部表示文件,因此它和file结构不同,后者表示打开的文件描述符。对单个文件,可能会有许多表示打开的文件描述符的file结构,但它们都指向单个ionde结构。
ionde结构中包含了大量有关文件的信息。作为常规,只有下面两个字段对编写驱动程序代码有用:
dev_t i_rdev;//对表示设备文件的inode结构,该字段包含了真正的设备编号。
struct cdev *i_cdev;//struct cdev是表示字符设备的内核的内部结构。当inode指向一个字符设备文件时,该字段包含了指向struct cdev结构的指针。
字符设备的注册
内核内部使用struct cdev结构来表示字符设备。在内核调用设备的操作之前,必须分配并注册一个或者多个上述结构。所以代码应包含<linux/cdev.h>
如果打算在运行时获取一个独立的cdev结构,应该如下编写代码:
struct cdev *my_cdev = cdev_alloc();
my_cdev->ops = &my_fops;
接下来要初始化:
void cdev_init(struct cdev *cdev, struct file_operations *fops);//用于初始化已分配的cdev结构,并建立cdev和file_oprations质检单连接。
还有一个struct cdev的字段需要初始化:一个所有者字段。应被设置为THIS_MODULE。
最后步骤:告诉内核该结构的信息
int cdev_add(struct cdev *dev, dev_t num, unsigned int count);
从系统中移除一个字符设备:void cdev_del(struct cdev *dev);
//字符设备的注册这一段,理解错误了。cdev_alloc用于动态初始化而cdev_init适用于静态初始化的。
//具体的区别参考转载内容:Linux内核中的cdev_alloc和cdev_add