字符设备

主要内容来自《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

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值