新风向网站建设,昆山市建设局招投标网站,石家庄网站建设选汉狮,做网站系统用什么语言前面我们学习了字符设备结构体cdev
Linux 字符设备驱动开发 #xff08;一#xff09;—— 字符设备驱动结构#xff08;上#xff09; 下面继续学习字符设备另外几个重要的
数据结构。 先看下面这张图#xff0c;这是Linux 中虚拟文件系统、一般的设备文件与设备驱动程…前面我们学习了字符设备结构体cdev
Linux 字符设备驱动开发 一—— 字符设备驱动结构上 下面继续学习字符设备另外几个重要的
数据结构。 先看下面这张图这是Linux 中虚拟文件系统、一般的设备文件与设备驱动程序值间的函数调用关系; 上面这张图展现了一个应用程序调用字符设备驱动的过程, 在设备驱动程序的设计中一般而言会关心 file 和 inode 这两个结构体 用户空间使用 open() 函数打开一个字符设备 fd open(/dev/hello,O_RDWR) , 这一函数会调用两个数据结构 struct inode{...}与struct file{...} 二者均在虚拟文件系统VFS处下面对两个数据结构进行解析 一、file 文件结构体 在设备驱动中这也是个非常重要的数据结构必须要注意一点这里的file与用户空间程序中的FILE指针是不同的用户空间FILE是定义在C库中从来不会出现在内核中。而struct file却是内核当中的数据结构因此它也不会出现在用户层程序中。 file结构体指示一个已经打开的文件设备对应于设备文件其实系统中的每个打开的文件在内核空间都有一个相应的struct file结构体它由内核在打开文件时创建并传递给在文件上进行操作的任何函数直至文件被关闭。如果文件被关闭内核就会释放相应的数据结构。 在内核源码中struct file要么表示为file或者为filp(意指“file pointer”), 注意区分一点file指的是struct file本身而filp是指向这个结构体的指针。
下面是几个重要成员
a -- fmode_t f_mode; 此文件模式通过FMODE_READ, FMODE_WRITE识别了文件为可读的可写的或者是二者。在open或ioctl函数中可能需要检查此域以确认文件的读/写权限你不必直接去检测读或写权限因为在进行octl等操作时内核本身就需要对其权限进行检测。 b -- loff_t f_pos; 当前读写文件的位置。为64位。如果想知道当前文件当前位置在哪驱动可以读取这个值而不会改变其位置。对read,write来说当其接收到一个loff_t型指针作为其最后一个参数时他们的读写操作便作更新文件的位置而不需要直接执行filp -f_pos操作。而llseek方法的目的就是用于改变文件的位置。
c -- unsigned int f_flags; 文件标志如O_RDONLY, O_NONBLOCK以及O_SYNC。在驱动中还可以检查O_NONBLOCK标志查看是否有非阻塞请求。其它的标志较少使用。特别地注意的是读写权限的检查是使用f_mode而不是f_flog。所有的标量定义在头文件中
d -- struct file_operations *f_op; 与文件相关的各种操作。当文件需要迅速进行各种操作时内核分配这个指针作为它实现文件打开读写等功能的一部分。filp-f_op 其值从未被内核保存作为下次的引用即你可以改变与文件相关的各种操作这种方式效率非常高。 file_operation 结构体解析如下Linux 字符设备驱动结构四—— file_operations 结构体知识解析
e -- void *private_data; 在驱动调用open方法之前open系统调用设置此指针为NULL值。你可以很自由的将其做为你自己需要的一些数据域或者不管它如你可以将其指向一个分配好的数据但是你必须记得在file struct被内核销毁之前在release方法中释放这些数据的内存空间。private_data用于在系统调用期间保存各种状态信息是非常有用的。 二、 inode结构体 VFS inode 包含文件访问权限、属主、组、大小、生成时间、访问时间、最后修改时间等信息。它是Linux 管理文件系统的最基本单位也是文件系统连接任何子目录、文件的桥梁。 内核使用inode结构体在内核内部表示一个文件。因此它与表示一个已经打开的文件描述符的结构体(即file 文件结构)是不同的我们可以使用多个file 文件结构表示同一个文件的多个文件描述符但此时所有的这些file文件结构全部都必须只能指向一个inode结构体。 inode结构体包含了一大堆文件相关的信息但是就针对驱动代码来说我们只要关心其中的两个域即可
(1) dev_t i_rdev; 表示设备文件的结点这个域实际上包含了设备号。
(2) struct cdev *i_cdev; struct cdev是内核的一个内部结构它是用来表示字符设备的当inode结点指向一个字符设备文件时此域为一个指向inode结构的指针。
下面是源代码 [cpp] view plaincopy struct inode { struct hlist_node i_hash; struct list_head i_list; struct list_head i_sb_list; struct list_head i_dentry; unsigned long i_ino; atomic_t i_count; unsigned int i_nlink; uid_t i_uid;//inode拥有者id gid_t i_gid;//inode所属群组id dev_t i_rdev;//若是设备文件表示记录设备的设备号 u64 i_version; loff_t i_size;//inode所代表大少 #ifdef __NEED_I_SIZE_ORDERED seqcount_t i_size_seqcount; #endif struct timespec i_atime;//inode最近一次的存取时间 struct timespec i_mtime;//inode最近一次修改时间 struct timespec i_ctime;//inode的生成时间 unsigned int i_blkbits; blkcnt_t i_blocks; unsigned short i_bytes; umode_t i_mode; spinlock_t i_lock; struct mutex i_mutex; struct rw_semaphore i_alloc_sem; const struct inode_operations *i_op; const struct file_operations *i_fop; struct super_block *i_sb; struct file_lock *i_flock; struct address_space *i_mapping; struct address_space i_data; #ifdef CONFIG_QUOTA struct dquot *i_dquot[MAXQUOTAS]; #endif struct list_head i_devices; union { struct pipe_inode_info *i_pipe; struct block_device *i_bdev; struct cdev *i_cdev;//若是字符设备对应的为cdev结构体 }; 三、chardevs 数组 从图中可以看出通过数据结构 struct inode{...} 中的 i_cdev 成员可以找到cdev而所有的字符设备都在 chrdevs 数组中
下面先看一下 chrdevs 的定义 [cpp] view plaincopy #define CHRDEV_MAJOR_HASH_SIZE 255 static DEFINE_MUTEX(chrdevs_lock); static struct char_device_struct { struct char_device_struct *next; // 结构体指针 unsigned int major; // 主设备号 unsigned int baseminor; // 次设备起始号 int minorct; // 次备号个数 char name[64]; struct cdev *cdev; /* will die */ } *chrdevs[CHRDEV_MAJOR_HASH_SIZE]; // 只能挂255个字符主设备span stylefont-family: Arial, Helvetica, sans-serif; background-color: rgb(255, 255, 255); /span 可以看到全局数组 chrdevs 包含了255(CHRDEV_MAJOR_HASH_SIZE 的值)个 struct char_device_struct的元素每一个对应一个相应的主设备号。 如果分配了一个设备号,就会创建一个 struct char_device_struct 的对象,并将其添加到 chrdevs 中这样,通过chrdevs数组,我们就可以知道分配了哪些设备号。 相关函数这些函数在上篇已经介绍过现在回顾一下 register_chrdev_region( ) 分配指定的设备号范围 alloc_chrdev_region( ) 动态分配设备范围
他们都主要是通过调用函数 __register_chrdev_region() 来实现的要注意,这两个函数仅仅是注册设备号!如果要和cdev关联起来,还要调用cdev_add()。 register_chrdev( )申请指定的设备号,并且将其注册到字符设备驱动模型中. 它所做的事情为:
a -- 注册设备号, 通过调用 __register_chrdev_region() 来实现
b -- 分配一个cdev, 通过调用 cdev_alloc() 来实现
c -- 将cdev添加到驱动模型中, 这一步将设备号和驱动关联了起来. 通过调用 cdev_add() 来实现
d -- 将第一步中创建的 struct char_device_struct 对象的 cdev 指向第二步中分配的cdev. 由于register_chrdev()是老的接口,这一步在新的接口中并不需要。 四、cdev 结构体 在 Linux 字符设备驱动开发 一—— 字符设备驱动结构上 有解析。 五、文件系统中对字符设备文件的访问 下面看一下上层应用open() 调用系统调用函数的过程 对于一个字符设备文件, 其inode-i_cdev 指向字符驱动对象cdev, 如果i_cdev为 NULL ,则说明该设备文件没有被打开. 由于多个设备可以共用同一个驱动程序.所以,通过字符设备的inode 中的i_devices 和 cdev中的list组成一个链表 首先,系统调用open打开一个字符设备的时候, 通过一系列调用,最终会执行到 chrdev_open (最终是通过调用到def_chr_fops中的.open, 而def_chr_fops.open chrdev_open. 这一系列的调用过程,本文暂不讨论) int chrdev_open(struct inode * inode, struct file * filp)
chrdev_open()所做的事情可以概括如下: 1. 根据设备号(inode-i_rdev), 在字符设备驱动模型中查找对应的驱动程序, 这通过kobj_lookup() 来实现, kobj_lookup()会返回对应驱动程序cdev的kobject. 2. 设置inode-i_cdev , 指向找到的cdev. 3. 将inode添加到cdev-list 的链表中. 4. 使用cdev的ops 设置file对象的f_op 5. 如果ops中定义了open方法,则调用该open方法 6. 返回
执行完 chrdev_open()之后,file对象的f_op指向cdev的ops,因而之后对设备进行的read, write等操作,就会执行cdev的相应操作。