字符设备驱动

来源:互联网 发布:淘宝网注册开店 编辑:程序博客网 时间:2024/05/16 18:18
一、设备分类

按设备访问方式(特点):

1、字符设备:鼠标,键盘,串口,帧缓存
        特点:以字节为单位访问,通常只支持顺序访问,无缓冲存
2、块设备:磁盘,光驱,flash
        特点:以固定大小为单位访问,支持随机访问,有缓存,不直接和VFS交互
3、网络设备
        特点:无设备节点,通过套接字访问设备

设备号概念:
在linux中,每个设备对应一个或者多个设备号
设备号为dev_t类型,一般是一个32位的整数

主设备号:区分不同类型的设备
次设备号:区分相同类型的不同设备

对设备号操作的有三个宏函数:
MAJOR(dev_t   dev)   获得主设备号
MINOR(dev_t    dev)    获得次设备号
MKDEV(int   major,int   minjor)    将主设备和次设备号转换成dev_t类型的设备号

240—254是实验用的设备号,内核不会使用

1、分配设备号和释放设备号
分为静态和动态分配两种
第一种,静态分配:
#include<linux/fs.h>
int register_chrdev_region(dev_t  from, unsigned count, const char  *name);
    功能:静态分配设备号,即分配我们指定的设备号,可以连续分配多个
    参数:
                 from:是你要分配的起始设备号,first的次设备号经常被设置为0。
                 count:请求的连续设备号的个数
                 name:设备名称,这个名称是与设备号范围相关联的。它会出现在 /proc/devices 和 sysfs 中
    返回值:成功返回0,错误返回负的错误码

第二种,动态分配:
我们经常不知道设备将要使用那些设备号,很有可能我们指定的设备号已经被别的设备使用,这样就会导致设备号分配失败。这时我们应当动态分配设备号
#include<linux/fs.h>
int alloc_chrdev_region(dev_t *dev, unsigned baseminor, unsigned count,
const char *name)
    功能:动态分配设备号
    参数:
                dev:                当成功分配设备号后,此变量用于保存已分配设备号范围的第一个编号
                
baseminor:  
请求的第一个要用的次设备号,它常常是 0
                count:            请求的连续设备号的个数
                 name:            设备名称,这个名称是与设备号范围相关联的。它会出现在 /proc/devices 和 sysfs 中
    返回值:成功返回0,错误返回负的错误码
    通常我们会查看/proc/devices来得知我们新分配的主设备号

不论你用哪种方法分配设备号,都应该在不使用它们时释放这些设备编号:
#include<linux/fs.h>
void unregister_chrdev_region(dev_t from, unsigned count)
    功能:释放设备号
    参数:
                from:  要释放的起始设备号
                count:要连续释放的设备号的个数
    返回值:成功返回0,错误返回负的错误码

2、一些重要的数据结构

1、struct   file_operations 定义在#include<linux/fs.h>中,这个结构包含了一组函数指针,可以通过给这些函数指针赋值,来指定对设备的一系列操作,如open,close,read等        

2、struct    cdev用来描述一个字符设备,在 <include/linux/cdev.h> 中定义
struct cdev {
struct     kobject      kobj;
struct     module     *owner;
const     struct     file_operations   *ops;
struct     list_head list;
dev_t     dev;
unsigned     int count;
};

3、注册字符设备
#include <include/linux/cdev.h> 
void cdev_init(struct cdev *cdev, struct file_operations *fops);
经过上述的字符设备注册后,
便将相应的操作函数与设备号关联到了一起。并将这个cdev结构插入到链表中,
struct cdev 有一个拥有者成员, 应当设置为 THIS_MODULE

一旦 cdev 结构建立, 最后的步骤是把它告诉内核, 调用: 
#include <include/linux/cdev.h> 
int cdev_add(struct cdev *dev, dev_t num, unsigned int count); 
    num:起始设备号
    count:要连续关联的个数

至此,才将设备号和相应的操作关联起来。

如果要移除一个字符设备,调用:
#include <include/linux/cdev.h> 
void cdev_del(struct cdev *dev); 

4、建立一个设备节点
mknod    /dev/设备节点名   c    major     minor

5、之后再应用程序中便可以调用相应的open,read函数来操作这个设备节点实现对设备的驱动。

一般在加载函数中实现1-—3步。在卸载函数中实现cdev_del


二、file和inode结构

1
struct    file定义在<linux/fs.h>文件中系统中每个打开的文件有一个关联的 struct file 在内核空间,它由内核在 open 时创建。
file结构的一些成员如下所示:
mode_t   f_mode; 
    文件模式确定文件是可读的或者是可写的(或者都是), 通过位 FMODE_READ 和 FMODE_WRITE. 你可能想在你的 open 或者 ioctl 函数中检查这个成员的读写许可,但由于内核在调用驱动程序的readwrite前已经检查了访问权限,所以在驱动程序的read和write不必检查权限。在没有获得对应访问权限而打开文件的情况下,对文件的读写操作将被内核拒绝,驱动无需为此而做额外的判断。
注:此权限检查对应的是文件本身的权限,也就是ls -l是的权限位,而不是open时指定的那个权限。
loff_t f_pos; 
     当前读写位置. loff_t 在所有平台都是 64 位( 在 gcc 术语里是 long long )。
驱动可以读这个值,如果它需要知道文件中的当前位置,,但是正常地不应该改变它; 
读和写应当使用它们作为最后参数而收到的指针来更新一个位置, 代替直接作用于 
filp->f_pos。 这个规则的一个例外是在 llseek 方法中, 它的目的就是改变文件位
置。
unsigned int f_flags; 
     这些是文件标志, 例如 O_RDONLY, O_NONBLOCK, 和 O_SYNC. 驱动应当检查 O_NONBLOCK 标志来看是否是请求非阻塞操作( 我们在第一章的"阻塞和非阻塞操作"一节中讨论非阻塞 I/O ); 其他标志很少使用.。
struct file_operations *f_op; 
    和文件关联的操作. 内核安排指针作为它的 open 实现的一部分, 接着读取它当它需要分派任何的操作时. filp->f_op 中的值从不由内核保存为后面的引用; 这意味着你可改变你的文件关联的文件操作, 在你返回调用者之后新方法会起作用.
void *private_data; 
    open 系统调用设置这个指针为 NULL, 在为驱动调用 open 方法之前. 你可自由使
用这个成员或者忽略它; 你可以使用这个成员来指向分配的数据, 但是接着你必须
记住在内核销毁file之前,,在 release 方法中释放那个内存.。private_data 
是一个非常有用的资源, 在系统调用间保留状态信息, 我们大部分例子模块都使用它. 
struct dentry *f_dentry; 
    文件对应的目录项( dentry )结构., 除了用filp->f_dentry->d_inode 访问索引节点结构外,设备驱动编写者正常地不需要关心 dentry 
结构, 实际的
结构里还有其他一些成员, 但是它们对设备驱动没有多大用处。 
 
因为驱动从不创建文件结构,它们只是对别处创建的file结构进行访问,因此 
我们可以安全地忽略这些成员,


2、
struct  indoe定义在<linux/fs.h>文件中,inode结构表示文件的静态属性,也就是文件本身的真正的属性。而file结构则是动态属性,
比如多个进程打开同一个文件,会有多个file结构,但只有一个indoe结构。

indoe结构中包含了大量的有关文件的信息。作为常规,一般只有下面两个成员对编写驱动代码有用:
dev_t i_rdev; 
    对于表示设备文件的indoe结构, 这个成员包含了真正的设备编号. 
struct cdev   *i_cdev; 
    struct cdev 是对应打开的字符设备的结构,当indoe指向一个字符设备文件时,该字段包含了指向struct  cdev结构的指针。

为了增加代码的可移植性,内核开发者增加了两个宏,可用来从一个indoe中获得主设备号和次设备号
unsigned int iminor(struct inode *inode); 
unsigned int imajor(struct inode *inode);




























0 0