Linux设备驱动(二)字符设备

来源:互联网 发布:淘宝网牛仔背带裙 编辑:程序博客网 时间:2024/06/08 08:26

1、工作基本原理

#include <linux/fs.h>

struct file_operations;

struct file;

struct inode;

用户进程通过设备文件同硬件打交道,对设备文件的操作方式就是一些系统调用,如open、write等。file_operations建立了系统调用和驱动程序之间的关联,指向该结构的指针称为fops。该结构的每一个成员的名字都对应着一个系统调用。每个打开的文件(file结构)和一组函数关联(通过包含指向一个file_options结构的fops字段)。用户进程利用系统调用在对设备文件进行诸如read/write操作时,系统调用通过设备文件的主设备号找到相应的设备驱动程序,然后读取这个数据结构相应的函数指针,接着把控制权交给该函数。编写设备驱动程序的主要工作就是编写子函数,并填充file_operations的各个域。

struct file是一个内核结构,代表一个打开的文件,由内核在open时创建,并传递给在该文件上进行操作的所有函数,直到最后的close函数。filp是指向该结构的指针。

inode表示一个磁盘上的文件。可能有多个表示打开的文件file结构,但都指向单个inode结构。

 

struct file_operations 

struct module*owner;//指向拥有该结构的模块的指针

loff_t (*llseek) (struct file *, loff_t ,int); //修改文件的当前读写位置

ssize_t (*read) (struct file *, char __user *,size_t, loff_t *); 
ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 
int (*readdir) (struct file *, struct dirent * ,int); //读取目录
int (*ioctl) (struct inode * ,struct file *, unsined int ,unsigned long); 
int (*mmap) (struct file *, struct vm_area_struct *); //请求将设备内存映射到进程地址空间
int (*open) (struct inode * ,struct file *); //对设备文件执行的第一个操作

int (*flush)(struct file*); //进程关闭设备文件描述符副本时,执行设备上尚未完结的操作
int (*fsync) (struct file *, struct dentry *, int); //刷新待处理的数据
}  

 

struct file

{

mode_t f_mode;                            //文件模式

loff_t f_pos;                                               //当前的读写位置

unsigned intf_flag;                     //文件标志

structfile_operations *f_op; //与文件相关的操作

void*private_data;                       

struct dentry*f_dentry;             //文件对应的目录项结构

}

 

struct inode

{

dev_t i_rdev;        //包含设备编号

struct cdev*i_cdev;   //字符设备内核的内部结构

}

 

内核内部用structcdev来表示字符设备,定义如下:

#include <linux/cdev.h>

struct cdev {

struct kobject kobj;                                     //每个cdev都是kobject

struct module *owner;                              //指向实现驱动的模块

const struct file_operations *ops;      //操作字符设备的方法

struct list_head list;                 //与cdev对应的字符设备文件的inode->i_devices链表头

dev_t dev;                                                                  //起始设备编号

unsigned int count;                                     //设备范围号大小

}

l  kobject工作原理

内核中所有都字符设备都会记录在一个kobj_map结构的cdev_map 变量中。kobj_map包含一个散列表用来快速存取所有的对象。kobj_map()函数就是用来把字符设备编号和 cdev 结构变量一起保存到 cdev_map 这个散列表里。当后续要打开一个字符设备文件时,通过调用kobj_lookup() 函数,根据设备编号就可以找到 cdev 结构变量,从而取出其中的 ops 字段。

 

2、设备的注册

//申请设备编号

建立设备之前,首先要获得一个或多个设备编号,允许在编译或加载的时候获得主设备号。

#include<linux/fs.h>   //“文件系统”头文件,是编写设备驱动程序必需的头文件

//声明了许多重要函数和数据结构。

         register_chrdev_region       //字符设备静态分配设备编号

alloc_chrdev_region              //字符设备的动态分配

unregister_chrdev_region   //字符设备释放设备编号

 

intregister_chrdev_region(dev_t first, unsigned int count, char *name);

其中first是要分配设备编号范围的起始值,count是所请求的连续设备编号的个数,name是和该编号范围关联的设备名称。分配成功时返回0,否则返回一个负的错误码。

 

int alloc_chrdev_region(dev_t *dev, unsignedint firstminor, unsigned int count, char *name);

如果明确知道所需设备编号,使用register_chrdev_region;否则使用动态分配。dev仅用于输出的参数,保存已分配范围的第一个编号。firstminor是要使用的被请求的第一个次设备号。动态分配的缺点:分配的主设备号不能保持始终一致,无法预先创建设备节点。

 

3、初始化

//向内核添加设备

//cdev的定义初始化方式,一般有两种。

//静态内存定义初始化

         struct cdev my_cdev;

         cdev_init(&my_cdev, &fops);

//cdev_init建立cdev和file_operations之间的连接

                                     //voidcdev_init(struct cdev *cdev, struct file_operations *fops);

         my_cdev.owner = THIS_MODULE;

//动态内存定义初始化

         struct cdev *my_cdev = cdev_alloc();

         my_cdev->ops = &fops;

         my_cdev->owner = THIS_MODULE;

 

//cdev的注册,告诉内核该结构的信息

intcdev_add(struct cdev *dev, dev_t num, unsigned int count);

第一个参数是设备,第二个参数是起始设备编号,第三个参数是和该设备关联的设备编号的数量,通常取1。

 

4、设备的注销和删除

voidcdev_del(struct cdev *dev);  

voidunregister_chrdev_region(dev_t first, unsigned int count);

 

早期内核2.6以前不使用cdev接口。

intregister_chrdev(unsigned int major, const char *name, struct file_operations*fops);

major是设备的主设备号,name是驱动程序的名称。对register_chrdev的调用将为给定的主设备号注册次设备号,并为每个设备建立一个对应的默认cdev结构。

intunregister_chrdev(unsigned int major, const char *name);

5、访问

         用户调用open函数时,将发起系统调用sys_open进入内核空间。在内核空间由do_sys_open函数发起整个文件设备打开操作。

l  get_unused_fd_flags为本次的open操作分配一个未使用过的文件描述符fd。

l  do_filp_open函数,查找/dev/demodev设备文件所对应的inode。找到inode之后,调用get_empty_filp函数为每个打开的文件分配一个新的struct file类型的内存空间filp。

l  __dentry_open函数将/dev/demodev对应节点的inode中的i_fop赋值给filp->f_op,然后调用i_fop中的open函数。对字符设备节点的inode而言,i_fop函数就是chrdev_open函数。

 

chrdev_open函数通过kobj_lookup()在cdev_map中用inode->i_rdev中查找inode对应的字符设备cdev,cdev_map中记录着所有通过cdev_add加入系统的字符设备。当在cdev_map中成功查找到该字符设备时,chrdev_open将inode->i_cdev指向找到的字符设备对象,同时将cdev->ops赋值给filp->f_op。这样下次再对该设备文件节点进行打开操作时,就可以直接通过i_cdev成员得到设备节点所对应的字符设备对象,而无须再通过cdev_map进行查找。

内核在每次打开一个设备文件时,都会产生一个整型的文件描述符fd和一个新的struct file对象filp来跟踪对该文件的这一次操作,在打开设备文件时,内核会将filp和fd关联起来。

最后sys_open系统调用将设备文件描述符fd返回用户空间,这样用户空间对字符设备的后续操作,比如read、write和ioctl等,将通过fd找到对应的filp,然后调用filp->f_op中实现的各类字符设备操作函数。

0 0
原创粉丝点击