Chinaunix首页 | 论坛 | 博客
  • 博客访问: 108842
  • 博文数量: 58
  • 博客积分: 67
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2009-10-21 13:20
文章分类

全部博文(58)

文章存档

2014年(4)

2013年(54)

我的朋友

分类: 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提供 的函数)即可处理所有的块设备。

阅读(763) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~