分类: LINUX
2013-12-06 08:42:22
原文地址:inode 设备文件 作者:xingzuzi
设备并不是通过其文件名来标识,而是通过文件的主、次设备号标识(文件名和文件的主次设备号在设备文件的父目录的inode的数据区中表现出来的,这是fs层的东东)。
设备文件和普通文件的区别:
查看设备文件的命令为:ls -l /dev/
1:访问权限前面的字母b/c,分别表示块设备和字符设备。
2:设备文件没有文件长度,而增加了另外两个值,分别为主设备号和次设备号。二者共同形成一个唯一的号码,内核由此可以查找到对应的设备驱动程序。
由于引入了udev机制,/dev不再放置到基于磁盘的文件系统中,而是使用tmpfs,这是RAM磁盘文件系统ramfs的一种轻型变体。这意味着设备结点不是持久性的,系统关机/重启后就会消失。
IOCTL:输入输出控制接口,是用于配置和修改特定设备属性的通用接口。
内核如果能了解到系统中有哪些字符设备和块设备可用,那自然是很有利的,因而需要维护一个数据库,此外必须提供一个接口,以便驱动程序开发者能够将新项添加到数据库中。
数据结构:
1、设备数据库
尽管块设备和字符设备彼此的行为有很大的不同,但用于跟踪所有可用设备的数据库是相同的。因为字符设备和块设备都是通过唯一的设备号标识。但是,数据库会根据块设备/字符设备来跟踪不同的对象。
。每个字符设备都表示为一个struct cdev的实例
。struct genhd用于管理块设备的分区,作用类似于字符设备的cdev.
有两个全局数组(bdev_map用于块设备,cdev_map用于字符设备)用来实现散列表,使用主设备号作为散列键。cdev_map和bdev_map都是同一数据结构struct kobj_map的实例。散列的方法很简单:major%255.
struct kobj_map {
struct probe {
struct probe *next;
dev_t dev;
unsigned long range;
struct module *owner;
kobj_probe_t *get;
int (*lock)(dev_t, void *);
void *data;
} *probes[255];
struct mutex *lock;
};
互斥量lock实现了对散列表访问的串行化.struct probe的成员如下:
next:将所有散列表链接在一个单链表中。(没有搞明白这个单链表中的设备的主设备号都是相同的否?)
dev: 表示设备号,包括主次设备号
rang:从设备号的连续范围存储在range中。那么与设备关联的各个从设备号的范围是[MINORS(dev), MINORS(dev)+range-1].
owner:指向提供设备驱动程序的模块。
get:指向一个函数,可以返回与设备关联的kobject实例。
data:字符设备和块设备的区别就在于data.对于字符设备,他指向struct cdev的一个实例,而对于块设备,则指向struct genhd的实例。
2、字符设备范围数据库
第二类数据库只是用于字符设备。他是用于管理为驱动程序分配的设备号范围。驱动程序可以请求一个动态的设备号(用register_chrdev_region()函数注册设备号),或者指定一个范围,从中获取(用alloc_chrdev_region()函数分配设备号)。
再次使用散列表来跟踪已经分配的设备号范围,并同样使用主设备号作为散列键。数据结构如下:
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];
next:链接同一散列行中的所有散列元素
major:主设备号
baseminor是包含minorct个从设备号的连续范围的最小的从设备
name为设备提供了一个标识符
与文件系统关联
inode中设备文件成员
只列出与驱动有关的成员
struct inode{
...
dev_t i_rdev;
...
umode_t i_mode;
...
struct file_operations *i_fop;
...
union {
struct block_device *i_bdev;
struct cdev *i_cdev;
};
....
};
i_mode:为唯一的标识与一个设备文件关联,内核在i_mode中存储量文件类型(面向块/面向字符)
i_rdev中存储了主次设备号
i_fop是一组函数指针的集合,包括许多文件操作(open,read,write等),这些有虚拟文件系统使用来出来块设备
内核会根据inode表示块设备还是字符设备,来使用i_bdev/i_cdev指向更多具体信息。
标准文件操作
在打开一个设备文件时,各种文件系统的实现都会调用init_special_inode函数,为字符设备或者块设备文件创建一个Inode(在文件系统层).
在此还是有疑问的,在打开设备的时候才创建Inode,难道在设备文件创建的时候fs系统没有创建Inode吗,fs只是把设备文件的ID和文件名存于/dev的inode的dentry中吗?
针对上面的问题需要说明的是:在设备文件打开的时候和创建设备文件的时候都创建了inode,但这两个inode不是同一个,是两 个不同类型的数据。设备文件创建的时候生成的Inode是保存在硬盘中的,他的结构比较简单,随设备文件的删除会消失的。而设备文件打开的时候创建的 inode是struct inode结构体,他是保存在ram中的,随着文件的关闭会消失的。
对下面的问题“fs只是把设备文件的id 和文件名存于/dev的inode的dentry中吗?”这样的猜想是错误的,有这样的说法,/dev目录是一个临时的文件夹,他并不在inode块中存 在inode结点,但他目录下的文件在inode块中是存在inode结点的。那么我们就该想了,那fs是如何找到/dev目录下的文件呢?设备文件是一 类比较特殊的文件,他的查找方式跟普通的文件的查找方式是不同的。其实设备文件根本就不需要查找他在磁盘上对应的inode结点,因为设备文件根本就没有 数据区,我们需要的设备文件的设备操作指针(f_ops).如何找到设备操作指针的呢?看下面的黑体部分就可以了。(上面的说法对字符设备文件是可行的, 但没有看块设备,不知道块设备是否也是这样的。其实也同样存在一个问题,按照上面的解释,我们完全可以不用为设备文件在磁盘上创建一个inode结点,但 系统还是建了,可以肯定是这样的inode结点是有作用的,系统不会干没意义的事的)
void init_special_inode(struct inode *inode, umode_t mode, dev_t rdev)
{
inode->i_mode = mode;
if (S_ISCHR(mode)) {
inode->i_fop = &def_chr_fops;
inode->i_rdev = rdev;
} else if (S_ISBLK(mode)) {
inode->i_fop = &def_blk_fops;
inode->i_rdev = rdev;
} else if (S_ISFIFO(mode))
inode->i_fop = &def_fifo_fops;
else if (S_ISSOCK(mode))
inode->i_fop = &bad_sock_fops;
else
printk(KERN_DEBUG "init_special_inode: bogus i_mode (%o)\n", mode);
}
用于字符设备的标准操作
fs/device.c
const struct file_operations def_chr_fops = {
.open = chrdev_open,
};
chrdev_open()函数的主要任务是向该结构(其实不明白"该结构"指的是什么结构,似乎有点像struct file)填入已打开设备的函数指针(file_operation),使得能够在设备文件上执行有意义的操作,并最终能够操作设备本身。
chrdev_open()函数的框图:
假定表示设备文件的inode此前没有打开过,根据设备号,kobject_lookup查询字符设备的数据库(cdev_map),并返回与该驱动程序向关联的kobject实例。该返回值可用于获取cdev实例。
获取了对应于设备的cdev实例,内核通过cdev->ops还可以访问特定于设备的file_operations.接下来设置各种数据结构之间的关联
inode->i_cdev指向所选择的cdev实例。在下一次打开该inode时,就不必再查询字符设备的数据库,因为可以使用缓存的值。
该inode将添加到cdev->list
file->f_ops是用于struct file新的file_operations,设置为指向struct cdev给出的file_operations实例。
接下来调用struct file新的file_operations中的open函数(现在是用于特定的设备,我们针对特定的设备在驱动层实现的open函数),在设备上执行所需的初始化任务。
用于块设备的标准操作
fs/block_dev.c
const struct file_operations def_blk_fops = {
.open = blkdev_open,
.release = blkdev_close,
.llseek = block_llseek,
.read = do_sync_read,
.write = do_sync_write,
.aio_read = generic_file_aio_read,
.aio_write = generic_file_aio_write_nolock,
.mmap = generic_file_mmap,
.fsync = block_fsync,
.unlocked_ioctl = block_ioctl,
#ifdef CONFIG_COMPAT
.compat_ioctl = compat_blkdev_ioctl,
#endif
.splice_read = generic_file_splice_read,
.splice_write = generic_file_splice_write,
};
尽管file_operations与block_device_operations的结构类似,但两者不能混淆。 file_operations由VFS层用来与用户空间通信的,他里面的函数是有用户层来调用的。其中的函数会调用 block_device_operations中的函数,以实现与块设备的通信。block_device_operations必须针对各种具体的块 设备分别实现,对设备的属性加以抽象,而在此基础上建立file_operations,是用户可以使用相同的操作(file_operations提供 的函数)即可处理所有的块设备。