字符设备驱动程序

一、概述

本文只讨论字符设备驱动的简单实现。为什么说是简单实现,因为在现有的内核版本中,几乎找不到如此的简单的驱动程序。本文就字符设备驱动的实现说说自己的一些理解。
开始之前,我们先做一个比喻:如果把内核比作一个“操作员”,那么驱动程序就相当于写有某类设备的使用方法的操作说明书。基于这样的比喻,我们设想一下,要安装新的设备时,我们需要实现哪几个方面。
- 1、首先,我们需要把将设备支持的操作做详细的说明,并写到操作说明书上。
- 2、其次,因为操作员可能要面对很多说明书和很多设备(同一类设备或许又有若干台),我们怎样将其对应?如果我们用一个主编号代表说明书,副编号代表设备,类似,1-1,1-2,1-3,这样一来问题是不是就解决了。
- 3、当操作说明书写好,并完成编号后,我们要将其交给操作员。
- 这样操作员就可以准确的操作特定的设备了。

与之对应的,从驱动程序的角度,我们要实现以上的几个机制:
- 1、我们需要一个结构体代表设备的描述说明,内核使用cdev结构体表示字符设备;并且,cdev要包含设备支持的操作函数,即cdev使用结构体成员file_operations表示;
- 2、我们需要一组编号机制标识驱动程序和设备,内核使用主设备号和次设备号表示,并将设备编号嵌入到cdev结构体中。
- 3、内核将字符设备对象抽象成cdev结构体之后,需要将其注册到系统中,以便系统能够在必要时能够及时调用。

二、设备号

2.1 主设备号和次设备号

通常情况下,主设备号标识设备对应的驱动程序,大多数设备按照“一个主设备号对应一个驱动程序”的原则来组织。
次设备号为内核所使用,用于正确确定设备文件(节点)所指的设备。

2.2 设备号的内核表达

内核使用一个dev_t类型同时表达主设备号和次设备号,我们不要假定的dev_t类型的位数。一般地,我们使用宏来实现主设备号和次设备到dev_t类型的转换。
- dev_t ->主设备号 :MAJOR(dev_t dev);
- dev_t->次设备号 :MINOR(dev_t dev);
- 设备号->dev_t :MKDEV(int major, int minor);

2.3 设备号的分配与释放

既然设备号是用于标识驱动程序和具体设备的(嵌在cdev结构体中),那么在注册设备结构体cdev之前,需要先分配好设备编号。
设备号的分配用两种函数:
int register_chrdev_region(dev_t from, unsigned count , const char * name);
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count, const char *name);
前者适用于已知起始设备的设备号情况,后者用于设备号未知,想系统动态申请未被占用的设备号的情况。值得注意的是,动态申请函数使用dev参数返回设备号分配结果。
动态分配设备编号是推荐的做法,但是在加载设备驱动的时候,我们怎么获取到主设备号,是需要解决的问题。

设备号的释放函数:
不论是使用的哪种方法分配设备号,都应该在不再使用它们时释放这些设备编号。使用以下函数
void unregister_chrdev_region(dev_t from, unsigned count);

三、设备操作结构体file_operations

内核需要分配设备编号来标识驱动程序和设备文件,也需要定义一组操作,来描述对关联到与编号对应的设备的操作。即我们需要的cdev结构体中实现结构体成员file_operations。
在file_operations结构体,我们实现相应的成员函数:open, read, write等等

四、设备的注册和注销

在linux内核中,使用cdev结构体描述一个字符设备,即,设备的注册本质上是向系统添加一个cdev结构对象的过程。

4.1 cdev结构体

两个重要成员:
struct file_operations *ops : 该指针指向一组文件操作成员函数
dev_t dev:设备编号
可见,设备注册的前提是需要分配好设备编号和定义好文件操作函数。
有一点值得注意:一般驱动程序并不单纯使用cdev,而是将其嵌入到自定义特定设备结构体xxx_dev中,并在xxx_dev结构体中定义其他成员(信号量等)。比如宋宝华书中globalmem驱动,使用一段内存模拟设备,则结构体xxx_dev就可以添加一个数组变量表示内存。

4.2 cdev结构体的相关操作

linux内核提供了一组函数用于操作cdev结构体:
1、初始化cdev成员,建立cdev和file_operations之间的连接
void cdev_init(struct cdev*, struct file_operations *);
2、动态申请一个cdev内存
struct cdev * cdev_alloc(void);
通常字符驱动不仅指分配cdev内存,而是申请自定义结构体xxx_dev结构体的内存。
3、向系统添加和删除一个cdev,完成字符设备的注册和注销
int cdev_add(struct cdev *, dev_t , unsigned );
void cdev_del(struct cdev *);

五、字符设备驱动的组成

至此,我们就能大概了解一个设备驱动程序都有哪些部分所组成
1、实现file_operations结构体中的成员函数;
2、在加载函数中,实现设备号的申请,初始化cdev结构体并完成cdev的注册;(linux内核编码习惯是为设备定义一个设备相关的结构体xxx_dev,该结构体包含设备所涉及的cdev,私有数据及锁的信息)
3、在卸载函数中,实现设备号的释放和cdev的注销。

  • 2
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值