这三个结构体均在文件linux-2.6.22.6>include>linux>fs.h中定义,大部分驱动程序操作都涉及三个重要的内核数据结构,分别是file_operations,file,inode。第一个是文件操作,file_operations结构就是用来连接驱动程序操作连接到我们前面给自己保留的编号的工作的。结构定义在<linux/fs.h>中,其中包含一组指针,每个打开的文件和一组函数关联(通过包含指向一个file_operations结构的f_op字段)。这些操作主要用来实现系统调用,也可以说文件可以认为是一个对象,而操作它的函数是方法。
惯例,file_operations 结构或者指向这类结构的指针称为fops,这个结构中每个字段都必须指向驱动中实现特定操作的函数。
对于不支持的操作,可以对应的字段设置为NULL。
而file结构是设备驱动程序所使用的第二个重要的数据结构。它是一个内核结构,不会出现在用户程序中。它不仅仅代表一个打开的文件。它由内核在open时创建,并传递给该文件上进行操作的所有函数,知道最后的close函数,在文件的所有实例都被关闭后,内核会释放这个数据结构。
inode结构它表示打开的文件描述符。他包含了大量有关文件的信息。而只有 dev_t i_rdev; struct cdev *i_cdev 与驱动程序代码有关用。
前者表示了设备文件的inode结构,包含了真正的设备编号,而后者表示字符设备的内核的内部结构,当其指向一个字符设备文件时,则包含了指向struct cdev结构的指针。
三者之间关系:
struct file结构体中包含有struct file_operations结构体,struct file_operations是struct file的一个域;我们在使用系统调用open()打开一个设备节点struct inode时,我们会得到一个文件struct file,同时返回一个文件描述符,该文件描述符是一个整数,我们称之为句柄,通过访问句柄我们能够访问设备文件struct file,描述符是一个有着特殊含义的整数,特定位都有一定的意义或属性。
---------------------
作者:SkyHandy
来源:CSDN
原文:https://blog.csdn.net/u010944778/article/details/45077565
版权声明:本文为博主原创文章,转载请附上博文链接!
1. struct file_operations{ ... }结构体
1 /* 2 * NOTE: 3 * read, write, poll, fsync, readv, writev, unlocked_ioctl and compat_ioctl 4 * can be called without the big kernel lock held in all filesystems. 5 */ 6 struct file_operations { 7 struct module *owner; 8 loff_t (*llseek) (struct file *, loff_t, int); 9 ssize_t (*read) (struct file *, char __user *, size_t, loff_t *); 10 ssize_t (*write) (struct file *, const char __user *, size_t, loff_t *); 11 ssize_t (*aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 12 ssize_t (*aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t); 13 int (*readdir) (struct file *, void *, filldir_t); 14 unsigned int (*poll) (struct file *, struct poll_table_struct *); 15 int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long); 16 long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long); 17 long (*compat_ioctl) (struct file *, unsigned int, unsigned long); 18 int (*mmap) (struct file *, struct vm_area_struct *); 19 int (*open) (struct inode *, struct file *); 20 int (*flush) (struct file *, fl_owner_t id); 21 int (*release) (struct inode *, struct file *); 22 int (*fsync) (struct file *, struct dentry *, int datasync); 23 int (*aio_fsync) (struct kiocb *, int datasync); 24 int (*fasync) (int, struct file *, int); 25 int (*lock) (struct file *, int, struct file_lock *); 26 ssize_t (*sendfile) (struct file *, loff_t *, size_t, read_actor_t, void *); 27 ssize_t (*sendpage) (struct file *, struct page *, int, size_t, loff_t *, int); 28 unsigned long (*get_unmapped_area)(struct file *, unsigned long, unsigned long, unsigned long, unsigned long); 29 int (*check_flags)(int); 30 int (*dir_notify)(struct file *filp, unsigned long arg); 31 int (*flock) (struct file *, int, struct file_lock *); 32 ssize_t (*splice_write)(struct pipe_inode_info *, struct file *, loff_t *, size_t, unsigned int); 33 ssize_t (*splice_read)(struct file *, loff_t *, struct pipe_inode_info *, size_t, unsigned int); 34 };
2.struct file{ ... }结构体
1 struct file 2 { 3 /* 4 * fu_list becomes invalid after file_free is called and queued via 5 * fu_rcuhead for RCU freeing 6 */ 7 union 8 { 9 struct list_head fu_list; 10 struct rcu_head fu_rcuhead; 11 } f_u; 12 struct path f_path; 13 #define f_dentry f_path.dentry 14 #define f_vfsmnt f_path.mnt 15 const struct file_operations *f_op; 16 atomic_t f_count; 17 unsigned int f_flags; 18 mode_t f_mode; 19 loff_t f_pos; 20 struct fown_struct f_owner; 21 unsigned int f_uid, f_gid; 22 struct file_ra_state f_ra; 23 24 unsigned long f_version; 25 #ifdef CONFIG_SECURITY 26 void *f_security; 27 #endif 28 /* needed for tty driver, and maybe others */ 29 void *private_data; 30 31 #ifdef CONFIG_EPOLL 32 /* Used by fs/eventpoll.c to link all the hooks to this file */ 33 struct list_head f_ep_links; 34 spinlock_t f_ep_lock; 35 #endif /* #ifdef CONFIG_EPOLL */ 36 struct address_space *f_mapping; 37 };
3. struct inode{ ... }结构体
1 /* 2 * Use sequence counter to get consistent i_size on 32-bit processors. 3 */ 4 #if BITS_PER_LONG==32 && defined(CONFIG_SMP) 5 #include <linux/seqlock.h> 6 #define __NEED_I_SIZE_ORDERED 7 #define i_size_ordered_init(inode) seqcount_init(&inode->i_size_seqcount) 8 #else 9 #define i_size_ordered_init(inode) do { } while (0) 10 #endif 11 12 struct inode { 13 struct hlist_node i_hash; 14 struct list_head i_list; 15 struct list_head i_sb_list; 16 struct list_head i_dentry; 17 unsigned long i_ino; 18 atomic_t i_count; 19 unsigned int i_nlink; 20 uid_t i_uid; 21 gid_t i_gid; 22 dev_t i_rdev; 23 unsigned long i_version; 24 loff_t i_size; 25 #ifdef __NEED_I_SIZE_ORDERED 26 seqcount_t i_size_seqcount; 27 #endif 28 struct timespec i_atime; 29 struct timespec i_mtime; 30 struct timespec i_ctime; 31 unsigned int i_blkbits; 32 blkcnt_t i_blocks; 33 unsigned short i_bytes; 34 umode_t i_mode; 35 spinlock_t i_lock; /* i_blocks, i_bytes, maybe i_size */ 36 struct mutex i_mutex; 37 struct rw_semaphore i_alloc_sem; 38 const struct inode_operations *i_op; 39 const struct file_operations *i_fop; /* former ->i_op->default_file_ops */ 40 struct super_block *i_sb; 41 struct file_lock *i_flock; 42 struct address_space *i_mapping; 43 struct address_space i_data; 44 #ifdef CONFIG_QUOTA 45 struct dquot *i_dquot[MAXQUOTAS]; 46 #endif 47 struct list_head i_devices; 48 union { 49 struct pipe_inode_info *i_pipe; 50 struct block_device *i_bdev; 51 struct cdev *i_cdev; 52 }; 53 int i_cindex; 54 55 __u32 i_generation; 56 57 #ifdef CONFIG_DNOTIFY 58 unsigned long i_dnotify_mask; /* Directory notify events */ 59 struct dnotify_struct *i_dnotify; /* for directory notifications */ 60 #endif 61 62 #ifdef CONFIG_INOTIFY 63 struct list_head inotify_watches; /* watches on this inode */ 64 struct mutex inotify_mutex; /* protects the watches list */ 65 #endif 66 67 unsigned long i_state; 68 unsigned long dirtied_when; /* jiffies of first dirtying */ 69 70 unsigned int i_flags; 71 72 atomic_t i_writecount; 73 #ifdef CONFIG_SECURITY 74 void *i_security; 75 #endif 76 void *i_private; /* fs or device private pointer */ 77 };
以下为摘录的几篇博客:
linux驱动程序中最重要的涉及3个重要的内核数据结构,分别为file_operations,file和inode。
链接:http://blog.163.com/seven_7_one/blog/static/16260641220112710311323/
在linux中inode结构用于表示文件,而file结构则表示打开的文件的描述,因为对于单个文件而言可能会有许多个表示打开的文件的描述符,因而就可能会的对应有多个file结构,但是都指向单个inode结构。
在系统内部,I/O设备的存取操作通过特定的的入口来进行,而这组特定的入口由驱动程序来提供的。通常这组设备驱动的接口是由结构体file_operations向系统说明的,它定义在include/linux/fs.h中。
file_operations的数据结构如下:(2.6内核)
struct file_operations{
struct module *owner;//拥有该模块的指针,一般THIS_MODULE
loff_t (*llseek) (struct file*, loff_t,int);//修改文件当前的读写位置
ssize_t (*read)(struct file *,char *, size_t, loff_t *);
//从设备同步读取数据
ssize_t (*write)(struct file *,const char *, size_t, loff_t *);
//向设备发送数据
int (*readdir) (struct file *, void *, filldir_t);
//仅用于读取目录,对于设备文件,该字段为NULL
unsigned int (*poll)(struct file *, struct poll_table_struct *);
//轮询函数,判断目前是否可以进行非阻塞的读取或写入
int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
//执行设备IO控制命令
int (*mmap) (sturct file *, struct vm_area_struct*);
//用于请求将设备内存映射到进程地址空间
int (*open) (struct inode *, struct file *);//打开
int (*flush)(struct file *);
int (*release)(struct inode *, struct file *);//关闭
int (*synch)(struct file *, struct dentry *, int datasync);//刷新待处理的数据
int (*fasync)(int, struct file *, int);//通知设备fasync标志发生变化
int (*lock)(struct file *, int, struct file_lock);
ssize_t (*readv) (struct file *, const struct iovec *, unsigned long*, loff_t * );
sszie_t(*writev)(struct file *, const struct iovec *, unsigned long *,
loff_t *);//分散聚集型的读写
ssize_t (*sengpage)(struct file *, struct page *, int, size_t,loff_t *, int);
unsigned long (*get_unmaapped_area)(struct file *,unsigned long, unsigned long, unsigned long, unsigned long );
long (*fcntl)(int fd, unsigned int cmd,unsigned arg, struct file *filp);
};
随着内核的不断升级,file_operations结构也会越来越大,不同版本会稍有不同。
一般的设备驱动程序只需用到上面的蓝色部分的五个函数!
Linux的设备文件是同硬件一一对应的,因而对设备的操作可以通过对设备文件的操作来实现。而这些操作的实现其实就是对一些标准的系统调用,如open(),read(),write(),close()等。实际上file_operations就是把系统调用和驱动程序 关联起来的关键数据结构。这个结构的每一成员都对应着一个系统调用。当用户进程利用系统调用对设备进行读写操作的时候,这些系统调用通过设备的主设备号和 次设备号来确定相应的驱动程序,然后读取file_operations中相应的函数指针,接着把控制权交给函数,从而完成linux设备驱动程序的工 作。
struct file提供关于被打开的文件信息,主要供与文件系统对应的设备文件驱动程序使用。结构如下:
struct file{
mode_t f_mode;//表示文件是否可读或可写,FMODE_READ或FMODE_WRITE
dev_ t f_rdev ;// 用于/dev/tty
off_t f_ops;//当前文件位移
unsigned short f_flags;//文件标志,O_RDONLY,O_NONBLOCK和O_SYNC
unsigned short f_count;//打开的文件数目
unsigned short f_reada;
struct inode *f_inode;//指向inode的结构指针
struct file_operations *f_op;//文件索引指针
}
还有 struct device_struct
在系统启动过程中的块设备和字符设备管理表的定义在文件fs/device.h中
struct device_struct
{
const char *name;
struct file_operations *fops;
}
static struct device_struct chrdevs[MAX_CHRDEV];
static struct device_struct blkdevs[MAX_BLKDEV];
其实块设备表和字符设备表使用了相同的数据结构。这些设备表也称作设备开关表,不同的是他们定义了一组函数指针对设 备进行管理。而这里系统用文件操作(file_operations)代替了那组开关。文件操作是文件系统与驱动程序之间的接口,系统特殊文件在建立的时 候并没有把两者对应起来,只是把设备的默认文件结构和i节点结构赋给设备文件,而真正的对应定义在系统启动后,当设备被打开时才进行的。
linux驱动编写之二(字符设备驱动程序)
int scull_init_module(void)
{
int result;
dev_t dev = 0;
//分配设备号,register函数如果在事先知道所需要的设备号,则会很合适,可是很多情况是不知道的,因此使用alloc进行动态设备编号
if(scull_major)
{
dev = MKDEV(scull_major,scull_minor);//此功能是将设备号和次设备号转换成dev_t类型,此类型是32位数,前12主,后次。
result = register_chrdev_region(dev,scull_nr_devs,"scull");
//register的第一个参数是设备编号的范围的起始值,而scull_nr_devs则是连续请求设备编号的个数,而scull则是和该编号范围关联的设备名称,将出现在/proc/devices和sysfs中
}
else
{
result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull");
//动态分配设备号,内核将为我们恰当分配所需要的主设备号。此函数第一个参数是用于输出的参数,成功完成调用后,将保存已分配范围的第一个编号,而第二个参数是要使用的被请求的第一个次设备号,通常是0。其余两个与register后两个参数作用相同。
scull_major = MAJOR(dev);//获得主设备号
printk(KERN_ALERT"THE scull is ok\n");
}
if(result<0)
{
printk(KERN_ALERT"scull:can't get major %d\n",scull_major);
return result;
}
return 0;
}
void scull_cleanup_module(void)
{
dev_t dev = MKDEV(scull_major,scull_minor);//获得设备号
unregister_chrdev_region(dev,scull_nr_devs);//释放设备编号,第一个设备号,第二个设备数。
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
****************************************************************************************************************************************
大部分驱动程序操作都涉及三个重要的内核数据结构,分别是file_operations,file,inode。第一个是文件操作,file_operations结构就是用来连接驱动程序操作连接到我们前面给自己保留的编号的工作的。结构定义在<linux/fs.h>中,其中包含一组指针,每个打开的文件和一组函数关联(通过包含指向一个file_operations结构的f_op字段)。这些操作主要用来实现系统调用,也可以说文件可以认为是一个对象,而操作它的函数是方法。
惯例,file_operations 结构或者指向这类结构的指针称为fops,这个结构中每个字段都必须指向驱动中实现特定操作的函数。
对于不支持的操作,可以对应的字段设置为NULL。
而file结构是设备驱动程序所使用的第二个重要的数据结构。它是一个内核结构,不会出现在用户程序中。它不仅仅代表一个打开的文件。它由内核在open时创建,并传递给该文件上进行操作的所有函数,知道最后的close函数,在文件的所有实例都被关闭后,内核会释放这个数据结构。
inode结构它表示打开的文件描述符。他包含了大量有关文件的信息。而只有dev_t i_rdev; struct cdev *i_cdev与驱动程序代码有关用。
前者表示了设备文件的inode结构,包含了真正的设备编号,而后者表示字符设备的内核的内部结构,当其指向一个字符设备文件时,则包含了指向struct cdev结构的指针。
//scull设备驱动所实现的只是最重要的设备方法,所以file_operation结构被初始化为如下形式
struct file_operations scull_fops =
{
.owner = THIS_MODULE,//所有者字段,应该设置为THIS_MODULE
// .read = scull_read,
// .write = scull_write,
// .open = scull_open,
// .release = scull_release,
};
//scull中的设备注册,scull_dev结构来表示每个设备,定义在scull.h中
static void scull_setup_cdev(struct scull_dev *dev,int index)
{
int err,devno = MKDEV(scull_major,scull_minor+index);
cdev_init(&dev->cdev,&scull_fops);//用此函数来初始化已分配到的结构,因为前面已经分配设备编号,故接下来初始化
dev->cdev.owner = THIS_MODULE;//所有者字段,应该设置为THIS_MODULE
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev,devno,1);
//此调用告诉内核该结构的信息,devno是该设备对应的第一个设备编号,第三个参数是和该设备关联的设备编号的数量,经常取1。
if(err)
{
printk(KERN_NOTICE"Error %d adding scull %d ",err,index);
}
//cdev_add这个调用有可能会失败,如果返回的是一个为负的错误码,则设备不会被添加到系统中。同时需要注意一点,在驱动程序还没有完全准备好设备上的操作时,不能调用cdev_add.
}
**********************************************************************************************************************
//提供给驱动程序初始化的能力,为以后完成初始化作准备,大部分open都需要完成以下工作。1检查设备特定的错误2如果设备首次打开,则初始化3如果必要,更新f_op指针。分配并填写置于filp->private_data里的数据结构。
int scull_open(struct inode *inode ,struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev,struct scull_dev,cdev);//这个宏用来找到适当的设备结构
filp->private_data = dev;//用这个字段指向已分配的数据
if((filp->f_flags & O_ACCMODE)==O_WRONLY)
{
scull_trim(dev);//简单的遍历列表,并释放所有找到的量子和量子集
}
return 0;
}
int scull_release(struct inode *inode,struct file *filp)
{
return 0;
}
//简单的遍历链表,并释放所有找到的量子和量子集
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next,*dptr;
int qset=dev->qset;//当前数组大小
int i;
for(dptr=dev->data;dptr;dptr=next)
{
if(dptr->data)
{
for(i=0;i<qset;i++)
{
kfree(dptr->data[i]);//释放当前集中的一个量
}
kfree(dptr->data);
dptr->data=NULL;
}
next=dptr->next;//指向下一个
kfree(dptr);
}
//初始化
dev->size=0;
dev->quantum=scull_quantum;
dev->qset=scull_qset;
dev->data=NULL;
return 0;
}
//********************************************************************************************************************
#include <linux/module.h>
#include <linux/init.h>
#include <linux/moduleparam.h>
#include <linux/kernel.h>
#include <linux/types.h>//定义dev_t类型,用来保存设备编号,包括主设备号,和从设备号
#include <linux/kdev_t.h>//获得dev_t中的主设备号和从设备号需要动用此中定义的宏
#include <linux/fs.h>//要获得一个或者多个设备编号需要使用从中声明的函数
#include <linux/slab.h> /* kmalloc() */
#include <linux/errno.h> /* error codes */
#include <linux/types.h> /* size_t */
#include <linux/fcntl.h> /* O_ACCMODE */
#include <linux/cdev.h>
#include <asm/system.h> /* cli(), *_flags */
#include <asm/uaccess.h> /* copy_*_user */
#include "scull.h"//里面定义了设备号的初始化数值
//*********************************************************************************************************************
struct scull_dev *scull_devices;
int scull_major = SCULL_MAJOR;
int scull_minor = 0;//此数值为0时,采用动态分配
int scull_nr_devs = SCULL_NR_DEVS;//scull0-scull3
int scull_quantum = SCULL_QUANTUM;
int scull_qset = SCULL_QSET;
void scull_cleanup_module(void);
int scull_open(struct inode *inode ,struct file *filp);
int scull_release(struct inode *inode,struct file *filp);
ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos);
ssize_t scull_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos);
module_param(scull_major, int, S_IRUGO);
module_param(scull_minor, int, S_IRUGO);
module_param(scull_nr_devs, int, S_IRUGO);
module_param(scull_quantum, int, S_IRUGO);
module_param(scull_qset, int, S_IRUGO);
//*********************************************************************************************************************
//scull设备驱动所实现的只是最重要的设备方法,所以file_operation结构被初始化为如下形式
struct file_operations scull_fops =
{
.owner = THIS_MODULE,//所有者字段,应该设置为THIS_MODULE
.read = scull_read,
.write = scull_write,
.open = scull_open,
.release = scull_release,
};
//**********************************************************************************************************************
//scull中的设备注册,scull_dev结构来表示每个设备,定义在scull.h中
static void scull_setup_cdev(struct scull_dev *dev,int index)
{
int err,devno = MKDEV(scull_major,scull_minor+index);
cdev_init(&dev->cdev,&scull_fops);//用此函数来初始化已分配到的结构,因为前面已经分配设备编号,故接下来初始化
dev->cdev.owner = THIS_MODULE;//所有者字段,应该设置为THIS_MODULE
dev->cdev.ops = &scull_fops;
err = cdev_add(&dev->cdev,devno,1);
//此调用告诉内核该结构的信息,devno是该设备对应的第一个设备编号,第三个参数是和该设备关联的设备编号的数量,经常取1。
if(err)
{
printk(KERN_NOTICE"Error %d adding scull %d ",err,index);
}
//cdev_add这个调用有可能会失败,如果返回的是一个为负的错误码,则设备不会被添加到系统中。同时需要注意一点,在驱动程序还没有完全准备好设备上的操作时,不能调用cdev_add.
//printk(KERN_ALERT"SETUP OK\n");
}
//************************************************************************************************************
struct scull_qset *scull_follow(struct scull_dev *dev, int n)//顺次遍历
{
struct scull_qset *qs = dev->data;
if (! qs) {
qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs == NULL)
return NULL;
memset(qs, 0, sizeof(struct scull_qset));
}
while (n--) {
if (!qs->next) {
qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL);
if (qs->next == NULL)
return NULL;
memset(qs->next, 0, sizeof(struct scull_qset));
}
qs = qs->next;
continue;
}
return qs;
}
ssize_t scull_read(struct file *filp,char __user *buf,size_t count,loff_t *f_pos)//读取
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum,qset = dev->qset;
int itemsize = quantum*qset;
int item,s_pos,q_pos,rest;
ssize_t retval = 0;
if(down_interruptible(&dev->sem))
{
return -ERESTARTSYS;
}
if(*f_pos >= dev->size)
{
goto out;
}
if(*f_pos + count>dev->size)
{
count = dev->size - *f_pos;
}
item = (long)*f_pos / itemsize; //当前位置
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest & quantum;
dptr = scull_follow(dev,item);
if(dptr == NULL || !dptr->data || !dptr->data[s_pos])
{
goto out;
}
if(count > quantum - q_pos)
{
count = quantum - q_pos;
}
if(copy_to_user(buf,dptr->data[s_pos]+q_pos,count))
{
retval = -EFAULT;
goto out;
}
*f_pos +=count;
retval = count;
out:
up(&dev->sem);
return retval;
}
ssize_t scull_write(struct file *filp,const char __user *buf,size_t count,loff_t *f_pos)//写入
{
struct scull_dev *dev = filp->private_data;
struct scull_qset *dptr;
int quantum = dev->quantum,qset = dev->qset;
int itemsize = quantum * qset;
int item,s_pos,q_pos,rest;
ssize_t retval = -ENOMEM;
if(down_interruptible(&dev->sem))
{
return -ERESTARTSYS;
}
item = (long)*f_pos / itemsize;
rest = (long)*f_pos % itemsize;
s_pos = rest / quantum;
q_pos = rest % quantum;
dptr = scull_follow(dev,item);
if(dptr == NULL)
{
goto out;
}
if(!dptr->data)
{
dptr->data = kmalloc(qset * sizeof(char *),GFP_KERNEL);
if(!dptr->data)
{
goto out;
}
memset(dptr->data,0,qset*sizeof(char *));
}
if(!dptr->data[s_pos])
{
dptr->data[s_pos]=kmalloc(quantum,GFP_KERNEL);
if(!dptr->data[s_pos])
{
goto out;
}
}
if(count>quantum - q_pos)
{
count = quantum - q_pos;
}
if(copy_from_user(dptr->data[s_pos]+q_pos,buf,count))
{
retval = -EFAULT;
goto out;
}
*f_pos += count;
retval = count;
if(dev -> size < *f_pos)
{
dev ->size = *f_pos;
}
out:
up(&dev->sem);
return retval;
}
//************************************************************************************************************
//提供给驱动程序初始化的能力,为以后完成初始化作准备,大部分open都需要完成以下工作。1检查设备特定的错误2如果设备首次打开,则初始化3如果必要,更新f_op指针。分配并填写置于filp->private_data里的数据结构。
int scull_open(struct inode *inode ,struct file *filp)
{
struct scull_dev *dev;
dev = container_of(inode->i_cdev,struct scull_dev,cdev);//这个宏用来找到适当的设备结构
filp->private_data = dev;//用这个字段指向已分配的数据
if((filp->f_flags & O_ACCMODE)==O_WRONLY)
{
scull_trim(dev);//简单的遍历列表,并释放所有找到的量子和量子集
up(&dev->sem);
}
return 0;
}
int scull_release(struct inode *inode,struct file *filp)
{
return 0;
}
//简单的遍历链表,并释放所有找到的量子和量子集
int scull_trim(struct scull_dev *dev)
{
struct scull_qset *next,*dptr;
int qset=dev->qset;//当前数组大小
int i;
for(dptr=dev->data;dptr;dptr=next)
{
if(dptr->data)
{
for(i=0;i<qset;i++)
{
kfree(dptr->data[i]);//释放当前集中的一个量
}
kfree(dptr->data);
dptr->data=NULL;
}
next=dptr->next;//指向下一个
kfree(dptr);
}
//初始化
dev->size=0;
dev->quantum=scull_quantum;
dev->qset=scull_qset;
dev->data=NULL;
return 0;
}
//************************************************************************************************************
int scull_init_module(void)
{
int result,i;
dev_t dev = 0;
//分配设备号,register函数如果在事先知道所需要的设备号,则会很合适,可是很多情况是不知道的,因此使用alloc进行动态设备编号
if(scull_major)
{
dev = MKDEV(scull_major,scull_minor);//此功能是将设备号和次设备号转换成dev_t类型,此类型是32位数,前12主,后次。
result = register_chrdev_region(dev,scull_nr_devs,"scull");
//register的第一个参数是设备编号的范围的起始值,而scull_nr_devs则是连续请求设备编号的个数,而scull则是和该编号范围关联的设备名称,将出现在/proc/devices和sysfs中
}
else
{
result = alloc_chrdev_region(&dev,scull_minor,scull_nr_devs,"scull");
//动态分配设备号,内核将为我们恰当分配所需要的主设备号。此函数第一个参数是用于输出的参数,成功完成调用后,将保存已分配范围的第一个编号,而第二个参数是要使用的被请求的第一个次设备号,通常是0。其余两个与register后两个参数作用相同。
scull_major = MAJOR(dev);//获得主设备号
printk(KERN_ALERT"THE scull is ok\n");
}
if(result<0)
{
printk(KERN_ALERT"scull:can't get major %d\n",scull_major);
return result;
}
scull_devices = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL);
if (!scull_devices) {
result = -ENOMEM;
goto fail; /* Make this more graceful */
}
memset(scull_devices, 0, scull_nr_devs * sizeof(struct scull_dev));
/* Initialize each device. */
for (i = 0; i < scull_nr_devs; i++) {
scull_devices[i].quantum = scull_quantum;
scull_devices[i].qset = scull_qset;
init_MUTEX(&scull_devices[i].sem);
scull_setup_cdev(&scull_devices[i], i);
}
return 0;
fail:
scull_cleanup_module();
return result;
}
void scull_cleanup_module(void)
{
int i;
dev_t devno = MKDEV(scull_major,scull_minor);//获得设备号
if (scull_devices)
{
for (i = 0; i < scull_nr_devs; i++)
{
scull_trim(scull_devices + i);
cdev_del(&scull_devices[i].cdev);
}
kfree(scull_devices);
}
unregister_chrdev_region(devno,scull_nr_devs);//释放设备编号,第一个设备号,第二个设备数。
}
module_init(scull_init_module);
module_exit(scull_cleanup_module);
MODULE_AUTHOR("THISISGOOD");
MODULE_LICENSE("Dual BSD/GPL");
应用层和驱动的衔接,一直是一个老大难问题,若弄不清楚,总觉得驱动写起来似是而非的。下面就说说我对他们的理解,还有就是如何实现一个驱动支持多个上设备的问题。最主要涉及两个机制:inode和file
在驱动中:
(1)、我们先找到一个设备号devno,可以动态申请,也可以静态设定,假设静态设定为major,minor,通过宏MKDEV(major,minor)来生成devno
(2)、构建对设备的操作函数file_opreation结构体,里面包含了的设备的操作:open、read、write、release、ioctl等
(3)、构建cdev结构体,里面填充两个主要成员dev(设备号)、file_operation(对设备的操作)
(4)、把cdev添加的cdev链表中:cdev_init、cdev_add
应用程序中:
fd=open("/dev/hello",O_RDWR)来打开设备文件,此设备节点对应有一个设备号,这是我们识别驱动和设备的桥梁。
打开 /dev/hello时,根据设备号,在cdev链表中找到cdev这个结构体,cdev里面包含了file_operation结构体,有设备的各种操作,打开时就调用里面的.open 函数。在这里要完成几件事:
(1)inode节点 每一个文件都对应有一个inode节点,inode结构体里.i_fop由cdev的file_operation填充,i_dev由cdev的设备号填充
(2)file结构体中的file_operation也同样由cdev中对应项填充,还有一项fd,对应于打开文件的文件描述符,fd和file一一对应,文件每打开一次,就有一个file结构it。所以file里面的.private就很重要,下面会说到。
还有一个问题,那就是多个相同的设备,会公用同一个驱动,所以要把每一个设备的私有数据封装起来,构成一个私有数据结构体。对设备的每一次读写,都通过操作设备的私有数据结构体中的资源来完成。也就是说,驱动在加载的时候,会申请多个设备私有资源结构体,每个结构体中包含了设备的所有私有资源,虽然公用一个驱动,可是通过设备号找到此设备号对应设备的私有资源,说的有点拗口。这可以通过file结构体的.private来指向。
例如封装私有数据的结构体为:
struct hello_device{
char buf[128]; //设备的私有资源,譬如buf
struct cdev cdev;//设备结构体,里面有devno和file_operation
……
};
前面应经提到inode中的i_cdev会指向cdev结构,所以可以由container宏来得到hello_device的地址。
所以,在驱动的open函数中有两个参数,inode和file
int open(structc inode *inode,struct file *file){
struct hello_device *p =container(inode->i_cdev,hello_struct,cdev)
file->private=p;
}
这样file中就包含了设备的私有数据。
驱动read函数中:
ssize_t read(fd,char __user *buf,count)
fd和file一一对应,每打开一次设备,虽然有不同的fd,但他们的file.private是一样的。
前面主要说了一个驱动如何可以支持多个设备的问题,以及应用层和驱动之间的联系。还有一个问题就是,如何处理过个进程访问同一个设备的问题。