2007-3-6 参考资料推荐: 当然是《Linux Device Dri vers 2nd》for 2.4.0 版本的。 Linux Driver 2nd chaper 12 : load block driver
先读一读chaper12, load block driver, 对于理解这个文件相当的有好处。这里仅罗列些block driver 如何与kernel配合的接口和数据结构。 block driver需要先注册自己到blkdevs, 就是这个文件实现的东西,这样kernel可以将此设备以设备文件的方式暴露给app。为了支持数据读写 driver必须实现自己的request_queue, 并注册到blk_dev, 这个queue和其操作函数是内核读写块设备的接口。 如果块设备支持分区,driver驱动还要分配并初始化一个gen_disk, 并用register_disk 进行注册(分区探测)。下图是对LDD Chapter12的一 点简单的总结。
了解了块设备驱动,再回过头来看block_dev.c就容易些。此模块提供了几个功能 1)为块设备建立设备文件,文件系统接口 struct file_operations def_blk_fops = { open: blkdev_open, release: blkdev_close, llseek: block_llseek, read: block_read, write: block_write, fsync: block_fsync, ioctl: blkdev_ioctl, }; 2)管理blkdevs int unregister_blkdev(unsigned int major, const char * name) int register_blkdev(unsigned int major, const char * name, struct block_device_operations *bdops)
3)管理block_device struct block_device *bdget(dev_t dev); 分配 struct block_device void bdput(struct block_device *bdev) 释放 struct block_device
int blkdev_get(struct block_device *bdev, mode_t mode, unsigned flags, int kind) 调用blkdevs的bd_ops->open int blkdev_put(struct block_device *bdev, int kind) 调用blkdevs的bd_ops->release
int ioctl_by_bdev(struct block_device *bdev, unsigned cmd, unsigned long arg) :block_device->bd_op->ioctl void __init bdev_init(void) struct block_device { struct list_head bd_hash; atomic_t bd_count; /* struct address_space bd_data; */ dev_t bd_dev; /* not a kdev_t - it's a search key */ atomic_t bd_openers; const struct block_device_operations *bd_op; struct semaphore bd_sem; /* open/close mutex */ };
4)block dev 的其他杂项操作 int check_disk_change(kdev_t dev) const struct block_device_operations * get_blkfops(unsigned int major) 5) internal function & misc init_once: for mem cache obj init hash: for bdget static struct block_device *bdfind(dev_t dev, struct list_head *head) : for bdget int get_blkdev_list(char * p) :一个块设备的列表 const char * bdevname(kdev_t dev)
这个文件出现了几个有关block device的结构,并且名字也类似,真是够乱的: 第一类是把块设备虚拟成文件的接口函数:struct file_operations def_blk_fops。第二类是块设备的操作和名字:数组 blkdevs struct block_device_operations *bdops;还有一类是block_device 的操作函数:也包含了block_device_operations *bd_op,呵呵有点意思。这个block_device的作用是嘛?搜索一下bdget可以看出些端倪: devfs_read_inode 和 init_special_inode 把结构block_device 注册到inode 中。这样看来这个结构是block dev到普通文件系统的一个桥梁。devfs和普通文件系统中的dev文件就记录这个东西。而def_blk_fops通过block_device成为打开的设备文件的操作函数,够绕!看看init_special_inodevoid就明白了。 init_special_inode(struct inode *inode, umode_t mode, int rdev) { inode->i_mode = mode; if (S_ISCHR(mode)) { inode->i_fop = &def_chr_fops; inode->i_rdev = to_kdev_t(rdev); } else if (S_ISBLK(mode)) { inode->i_fop = &def_blk_fops; inode->i_rdev = to_kdev_t(rdev); inode->i_bdev = bdget(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 imode (%o)\n", mode); }
而blkdev_get 和 blkdev_put 则绕过了文件系统本身,通过虚拟一个文件来带开和释放一个块设备。比较典型的应用是get_sb_bdev和mount_root:在这些情景下通过open打开一个块设备是很不对头的:这些情景下不对这个块设备进行read 和open操作,而是通过具体的文件系统如 ext2 再通过ll_rw_block来读写块设备。(还是绕)
当直接打开一个块设备(比如 dd)时是通过这个文件提供的def_blk_fops的几个接口把块设备虚拟成一个无结构的扇区序列来进行操作。(参考 init_special_inode)。
至于这个文件的具体函数,则不是那么难以理解。值得一读以前分析的file_map.c 这个文件,回顾一下buffer cache 和page cache 的区别与联系。 ssize_t block_write(struct file * filp, const char * buf, size_t count, loff_t *ppos) ssize_t block_read(struct file * filp, char * buf, size_t count, loff_t *ppos) 这两个函数实现对设备文件的读写,从其调用的分配缓冲区的函数 getblk看过去,就知道,直接dd一个设备的话其缓存于buffer cache。 而以前分析filemap。c 的时候知道,普通文件的内容缓存于page cache,普通文件的元数据缓存于buffer cache。 值得注意的是,先前注册到inode中的i_bdev (block_device),并没有在文件的读写过程中发挥什么作用,去搜索i_bdev就可以理解这个inode中的block_device仅仅是为了open, close这个相关的块设备文件而已。真正有用的是inode->i_rdev,init_special_inode中当然也没有忘记初始 化这个变量。 读写块设备文件,主要的工作是把文件的以byte为单位的(start_pos,len)转换为blk size为单位的读写单元,然后一个工作就是缓存,设备文件的读写靠的是buffer cache。 到分析buffer.c的时候再说罢。
static loff_t block_llseek(struct file *file, loff_t offset, int origin) 没啥说的。
static int block_fsync(struct file *filp, struct dentry *dentry, int datasync) { return fsync_dev(dentry->d_inode->i_rdev); } 注意到 fsync_dev int fsync_dev(kdev_t dev) { sync_buffers(dev, 0);
lock_kernel(); sync_supers(dev); sync_inodes(dev); DQUOT_SYNC(dev); unlock_kernel();
return sync_buffers(dev, 1); } 注意这是对我们分析的一个印证,和一个设备相关的所有数据缓冲: 1)dev相关的buffer cache(以(dev,block)为索引),含有像dd这种应用的缓存:sync_buffers,和普通文件的元数据 2)sync_supers(dev); 设备的super block 3)sync_inodes(dev); sync设备上所有inode(可能是在不同的sb中)的所有文件数据(包括filemap的数据)。(还包括inode本身,inode也算是文件的元数据,但是inode本身没有在buffer cache中,因为inode不仅仅是一个磁盘上的数据影像,他需要进行转换。见mark_inode_dirty,和inode 的分配释放函数,参考inode.c. sync_supers 道理相同。
剩下的函数真的不用多说了。。。。
end。
|