本文主要参考《深入理解Linux内核》,结合2.6.11.1版的内核代码,分析内核文件子系统中的打开设备文件函数,梳理了关于内核块设备文件打开的处理流程。
注意:
1、不描述内核同步、错误处理相关的内容
2、参考信息除具体说明外,包含在《深入理解Linux内核》第三版中
3、源码摘自Linux内核2.6.11.1版
1、open_bdev_excl
函数功能:
打开设备名为path的块设备,返回块设备描述符的地址
函数参数:
Path:块设备文件的路径名(如:/dev/sda1);
Flags:mount系统调用的的flag参数
Holder: 指向类型为file_system_type的文件系统类型对象的指针
函数源码及处理流程:
struct block_device*open_bdev_excl(const char *path, int flags, void *holder)
{
structblock_device *bdev;
mode_tmode = FMODE_READ;
interror = 0;
bdev= lookup_bdev(path);
if(IS_ERR(bdev))
returnbdev;
if(!(flags & MS_RDONLY))
mode|= FMODE_WRITE;
error= blkdev_get(bdev, mode, 0);
if(error)
returnERR_PTR(error);
error= -EACCES;
if(!(flags & MS_RDONLY) && bdev_read_only(bdev))
gotoblkdev_put;
error= bd_claim(bdev, holder);
if(error)
gotoblkdev_put;
returnbdev;
blkdev_put:
blkdev_put(bdev);
returnERR_PTR(error);
}
函数处理流程:
1、调用lookup_bdev函数,根据设备文件名path,查找或分配一个block_device对象,地址存入局部变量bdev中
2、根据flags参数设置文件系统权限为只读(FMODE_READ)或读写(FMODE_READ| FMODE_WRITE)
3、调用blkdev_get函数,初始化bdev中和分区、磁盘相关的数据,参见后面分析
4、调用bd_claim函数,更新bdev对象和其包含对象的持有者信息
2、lookup_bdev
函数功能:
打开或查找设备名为path的块设备。
函数参数:
略
函数源码:
/**
* lookup_bdev - lookup a struct block_device by name
*
* @path: specialfile representing the block device
*
* Get a reference to the blockdevice at @pathin the current
* namespace if possible and return it. Return ERR_PTR(error)
* otherwise.
*/
struct block_device *lookup_bdev(constchar *path)
{
structblock_device *bdev;
structinode *inode;
structnameidata nd;
interror;
if(!path || !*path)
returnERR_PTR(-EINVAL);
error= path_lookup(path, LOOKUP_FOLLOW, &nd);
if(error)
returnERR_PTR(error);
inode= nd.dentry->d_inode;
error= -ENOTBLK;
if(!S_ISBLK(inode->i_mode))
gotofail;
error= -EACCES;
if(nd.mnt->mnt_flags & MNT_NODEV)
gotofail;
error= -ENOMEM;
bdev= bd_acquire(inode);
if(!bdev)
gotofail;
out:
path_release(&nd);
returnbdev;
fail:
bdev= ERR_PTR(error);
gotoout;
}
函数处理流程:
1、调用path_lookup函数(参见p495“路径名查找”),查找块设备文件名对应的nameidata对象并存入nd局部变量中,设备文件对应的节点对象地址存入inode类型的inode局部变量中
2、调用bd_acquire函数,打开或查找设备文件名为path的块设备,返回块设备描述符
3、blkdev_get
函数功能:
初始化块设备描述符中和分区、磁盘相关的字段
函数参数:
略
函数源码:
int blkdev_get(struct block_device*bdev, mode_t mode, unsigned flags)
{
/*
* This crockload is due to bad choice of->open() type.
* It will go away.
* For now, block device ->open() routinemust _not_
* examine anything in 'inode' argument except->i_rdev.
*/
structfile fake_file = {};
structdentry fake_dentry = {};
fake_file.f_mode= mode;
fake_file.f_flags= flags;
fake_file.f_dentry= &fake_dentry;
fake_dentry.d_inode= bdev->bd_inode;
returndo_open(bdev, &fake_file);
}
static int do_open(struct block_device*bdev, struct file *file)
{
structmodule *owner = NULL;
structgendisk *disk;
intret = -ENXIO;
intpart;
file->f_mapping= bdev->bd_inode->i_mapping;
lock_kernel();
disk= get_gendisk(bdev->bd_dev, &part);
if(!disk) {
unlock_kernel();
bdput(bdev);
returnret;
}
owner= disk->fops->owner;
down(&bdev->bd_sem);
if(!bdev->bd_openers) { //第一次打开
bdev->bd_disk= disk;
bdev->bd_contains= bdev;
if(!part) { //如果不是分区,是磁盘
structbacking_dev_info *bdi;
if(disk->fops->open) {
ret= disk->fops->open(bdev->bd_inode, file);
if (ret)
gotoout_first;
}
if(!bdev->bd_openers) { //第一次打开
bd_set_size(bdev,(loff_t)get_capacity(disk)<<9);
bdi= blk_get_backing_dev_info(bdev); //磁盘的IO数据流量的信息,如预读和请求队列拥塞状态的信息
if(bdi == NULL)
bdi= &default_backing_dev_info;
bdev->bd_inode->i_data.backing_dev_info= bdi;
}
if(bdev->bd_invalidated)
rescan_partitions(disk,bdev);
}else { //如果是分区
structhd_struct *p;
structblock_device *whole;
whole= bdget_disk(disk, 0);
ret= -ENOMEM;
if(!whole)
gotoout_first;
ret= blkdev_get(whole, file->f_mode, file->f_flags);//对磁盘递归了
if(ret)
gotoout_first;
bdev->bd_contains= whole; //包含分区的块设备
down(&whole->bd_sem);
whole->bd_part_count++;//块设备中的打开的分区数
p= disk->part[part - 1];//分区表描述符
bdev->bd_inode->i_data.backing_dev_info=
whole->bd_inode->i_data.backing_dev_info;磁盘的IO数据流量的信息,如预读和请求队列拥塞状态的信息
if(!(disk->flags & GENHD_FL_UP) || !p || !p->nr_sects) {//磁盘将被初始化、分区描述符为空、分区中的扇区数为0
whole->bd_part_count--;
up(&whole->bd_sem);
ret= -ENXIO;
gotoout_first;
}
kobject_get(&p->kobj);
bdev->bd_part= p;
bd_set_size(bdev,(loff_t) p->nr_sects << 9);
up(&whole->bd_sem);
}//endfor else { //如果是分区
}else { //if (!bdev->bd_openers)
put_disk(disk);
module_put(owner);
if(bdev->bd_contains == bdev) { //不是分区
if(bdev->bd_disk->fops->open) {
ret= bdev->bd_disk->fops->open(bdev->bd_inode, file);
if(ret)
gotoout;
}
if(bdev->bd_invalidated)
rescan_partitions(bdev->bd_disk,bdev);
}else { //是分区
down(&bdev->bd_contains->bd_sem);
bdev->bd_contains->bd_part_count++;
up(&bdev->bd_contains->bd_sem);
}
}
bdev->bd_openers++;
up(&bdev->bd_sem);
unlock_kernel();
return0;
out_first:
bdev->bd_disk= NULL;
bdev->bd_inode->i_data.backing_dev_info= &default_backing_dev_info;
if(bdev != bdev->bd_contains)
blkdev_put(bdev->bd_contains);
bdev->bd_contains= NULL;
put_disk(disk);
module_put(owner);
out:
up(&bdev->bd_sem);
unlock_kernel();
if(ret)
bdput(bdev);
returnret;
}
函数处理流程:
从源码可以看出,blkdev_get把相关参数放入file对象后,调用do_open函数完成具体的处理工作,do_open函数的处理流出如下:
1、把file的地址空间初始化为块设备在bdev文件系统中的索引节点的地址空间
2、根据块设备文件的标识符,调用函数get_gendisk返回类型为gendisk磁盘描述符和块设备对应的分区索引(从1开始,0表示打开的块设备是一个磁盘)
3、剩下的处理流出参见代码注释
4、bd_claim
函数功能:
更新bdev对象和其包含对象的持有者信息
函数参数:
略
函数源码:
int bd_claim(struct block_device *bdev,void *holder)
{
intres;
spin_lock(&bdev_lock);
/*first decide result */
if(bdev->bd_holder == holder)
res= 0; /* already a holder */
elseif (bdev->bd_holder != NULL)
res= -EBUSY; /* held by someone else */
elseif (bdev->bd_contains == bdev)
res= 0; /* is a whole device which isn't held */
elseif (bdev->bd_contains->bd_holder == bd_claim)
res= 0; /* is a partition of a device that is beingpartitioned */
elseif (bdev->bd_contains->bd_holder != NULL)
res= -EBUSY; /* is a partition of a helddevice */
else
res= 0; /* is a partition of an un-helddevice */
/*now impose change */
if(res==0) {
/*note that for a whole device bd_holders
* will be incremented twice, and bd_holderwill
* be set to bd_claim before being set toholder
*/
bdev->bd_contains->bd_holders++;
bdev->bd_contains->bd_holder= bd_claim;
bdev->bd_holders++;
bdev->bd_holder= holder;
}
spin_unlock(&bdev_lock);
returnres;
}
函数处理流程:
从代码可见,在两种情况下(1、bdev->bd_holder不等于holder且不为空时,即bdev 有其它持有者2、bdev->bd_contains->bd_holder不等于bd_claim且不为空时,即bdev->bd_contains的持有者不为bd_claim)返回错误;在三种情况下(1、bdev->bd_holder为holder,即bdev的持有者再次持有2、bdev->bd_contains等于bdev,即bdev是整个磁盘描述符且没有持有者3、bdev->bd_contains->bd_holder等于bd_claim)
5、bd_acquire
函数功能:
打开或查找设备文件名为path的块设备,返回块设备描述符。
函数参数:
inode:块设备文件的路径名的设备节点。
函数源码:
static struct block_device*bd_acquire(struct inode *inode)
{
structblock_device *bdev;
spin_lock(&bdev_lock);
bdev= inode->i_bdev;
if(bdev && igrab(bdev->bd_inode)) {
spin_unlock(&bdev_lock);
returnbdev;
}
spin_unlock(&bdev_lock);
bdev= bdget(inode->i_rdev);
if(bdev) {
spin_lock(&bdev_lock);
if(inode->i_bdev) //如果块设备文件对应的索引节点已与另外一个块设备描述符相关联,消除关联
__bd_forget(inode);
inode->i_bdev= bdev; //块设备描述符
inode->i_mapping= bdev->bd_inode->i_mapping; //块设备在bdev文件系统中的索引节点的地址空间对象的地址
list_add(&inode->i_devices,&bdev->bd_inodes); //把索引节点加入块设备描述符的索引节点链表
spin_unlock(&bdev_lock);
}
returnbdev;
}
函数处理流程:
1、把inode的i_bdev存放在类型为block_device的局部变量bdev中,如果bdev不为NULL(即块设备文件已经被打开),通过调用函数igrab增加bdev->bd_inode索引节点的引用计数器,并返回bdev
2、如果bdev等于NULL或该节点已被释放(igrab返回零值),调用bdget函数获取块设备描述符的地址,具体流程参见下面描述
3、如果成功获取块设备描述符(bdev不等于NULL),处理流出参见代码注释
6、bdget
函数功能:
根据块设备文件对应的设备标识符,在bdev文件系统中查找或新建一个索引节点、块设备描述符,返回块设备描述符的地址
函数参数:
dev:块设备文件对应的设备标识符(含主设备号和次设备号)
函数源码:
struct block_device *bdget(dev_t dev)
{
structblock_device *bdev;
structinode *inode;
inode= iget5_locked(bd_mnt->mnt_sb, hash(dev),
bdev_test,bdev_set, &dev);
if(!inode)
returnNULL;
bdev= &BDEV_I(inode)->bdev;
if(inode->i_state & I_NEW) {
bdev->bd_contains= NULL; // bdev是特殊块设备文件系统的超级块,所以该对象初始化为NULL
bdev->bd_inode= inode; //bdev文件系统中的索引节点
bdev->bd_block_size= (1 << inode->i_blkbits); //块的字节大小,默认是1024,参见函数get_sb_pseudo
bdev->bd_part_count= 0; //分区被打开的次数
bdev->bd_invalidated= 0; //需要读块设备的分区表时设置的标志
inode->i_mode= S_IFBLK; //文件类型与访问权限,块设备
inode->i_rdev= dev; //块设备文件标识符
inode->i_bdev= bdev; //块设备描述符
inode->i_data.a_ops= &def_blk_aops; //bdev块设备文件地址空间的默认操作函数
mapping_set_gfp_mask(&inode->i_data,GFP_USER);//设置bdev文件节点的页分配标志,参见p305
inode->i_data.backing_dev_info= &default_backing_dev_info;
spin_lock(&bdev_lock);
list_add(&bdev->bd_list,&all_bdevs); //把块设备描述符插入块设备描述符全局链表
spin_unlock(&bdev_lock);
unlock_new_inode(inode);
}
returnbdev;
}
函数处理流程:
1、vfsmount*类型的全局变量bd_mnt中,存放类型名为”bdev”的块设备文件系统类型的安装文件系统描述符的地址,调用函数iget5_locked获得与块设备文件标识符对应的在bd_mnt文件系统中的索引节点的地址,存入局部变量inode中,函数的具体分析参加下面具体描述
2、通过宏BDEV_I从inode导出块设备描述符的地址,存入bdev局部变量中
3、如果inode是调用iget5_locked时新分配的索引节点,初始化bdev和inode相关信息,具体信息参见代码注释
7、iget5_locked
函数功能:
函数参数:
sb:
函数源码:
struct inode *iget5_locked(structsuper_block *sb, unsigned long hashval,
int(*test)(struct inode *, void *),
int(*set)(struct inode *, void *), void *data)
{
structhlist_head *head = inode_hashtable + hash(sb, hashval);
structinode *inode;
inode= ifind(sb, head, test, data);
if(inode)
returninode;
/*
* get_new_inode() will do the right thing,re-trying the search
* in case it had to block at any point.
*/
returnget_new_inode(sb, head, test, set, data);
}
函数处理流程:
调用函数ifind从inode_hashtable中查找inode,找到则返回
未找到调用函数get_new_inode
8、get_new_inode
函数功能:
函数参数:
函数源码:
/*
* This is called without the inode lock held..Be careful.
*
* We no longer cache the sb_flags in i_flags -see fs.h
* --rmk@arm.uk.linux.org
*/
static struct inode *get_new_inode(struct super_block *sb, struct hlist_head *head, int(*test)(struct inode *, void *), int (*set)(struct inode *, void *), void*data)
{
structinode * inode;
inode= alloc_inode(sb);
if(inode) {
structinode * old;
spin_lock(&inode_lock);
/*We released the lock, so.. */
old= find_inode(sb, head, test, data);
if(!old) {
if(set(inode, data))
gotoset_failed;
inodes_stat.nr_inodes++;
list_add(&inode->i_list,&inode_in_use);
list_add(&inode->i_sb_list,&sb->s_inodes);
hlist_add_head(&inode->i_hash,head);
inode->i_state= I_LOCK|I_NEW;
spin_unlock(&inode_lock);
/*Return the locked inode with I_NEW set, the
* caller is responsible for filling in thecontents
*/
returninode;
}
/*
* Uhhuh, somebody else created the same inodeunder
* us. Use the old inode instead of the one wejust
* allocated.
*/
__iget(old);
spin_unlock(&inode_lock);
destroy_inode(inode);
inode= old;
wait_on_inode(inode);
}
returninode;
set_failed:
spin_unlock(&inode_lock);
destroy_inode(inode);
returnNULL;
}
函数处理流程:
1、调用alloc_inode函数分配一个新的索引节点,地址存入局部变量inode中
2、获取锁后,调用find_inode函数在inode_hashtable中搜索块设备文件对应的节点,如果未找到则没有其他进程打开该设备文件,调用set初始化块设备描述符的设备标识符字段,把该设备索引节点加入inode_in_use和超级块的索引节点链表,设置索引节点的I_LOCK和I_NEW标识并返回
3、如果已有其他进程打开了该设备文件,增加已打开的索引节点引用计数器,并释放新分配的索引节点,返回找到的索引节点地址
9、alloc_inode
函数功能:
函数参数:
sb:“bdev”块设备的超级块对象指针
函数源码:
static struct inode *alloc_inode(structsuper_block *sb)
{
staticstruct address_space_operations empty_aops;
staticstruct inode_operations empty_iops;
staticstruct file_operations empty_fops;
structinode *inode;
if(sb->s_op->alloc_inode)
inode= sb->s_op->alloc_inode(sb);
else
inode= (struct inode *) kmem_cache_alloc(inode_cachep, SLAB_KERNEL);
if(inode) {
structaddress_space * const mapping = &inode->i_data;
inode->i_sb= sb; //超级块
inode->i_blkbits= sb->s_blocksize_bits; //块的位数(以位为单位的块的大小)
inode->i_flags= 0; //文件系统安装标志
atomic_set(&inode->i_count,1); //引用计数器
inode->i_sock= 0; //文件是否为套接字,是则非零
inode->i_op= &empty_iops; //节点操作函数
inode->i_fop= &empty_fops; //文件操作函数
inode->i_nlink= 1; //硬链接的数目
atomic_set(&inode->i_writecount,0); //写进程的引用计数器
inode->i_size= 0; //文件字节数
inode->i_blocks= 0; //文件块数
inode->i_bytes= 0; //文件中最后一块的字节数
inode->i_generation= 0; //索引节点版本
#ifdef CONFIG_QUOTA
memset(&inode->i_dquot,0, sizeof(inode->i_dquot)); //索引节点磁盘限额
#endif
inode->i_pipe= NULL; //如果文件是一个管道则使用
inode->i_bdev= NULL; //指向块设备描述符的指针
inode->i_cdev= NULL;//指向字符设备描述符的指针
inode->i_rdev= 0; //设备标识符
inode->i_security= NULL; //指向索引节点安全结构的指针
inode->dirtied_when= 0; //索引节点的弄脏时间,以节拍为单位
if(security_inode_alloc(inode)) { //索引节点分配的安全钩子函数
if(inode->i_sb->s_op->destroy_inode)
inode->i_sb->s_op->destroy_inode(inode);
else
kmem_cache_free(inode_cachep,(inode));
returnNULL;
}
mapping->a_ops= &empty_aops; //文件地址空间操作函数
mapping->host = inode; //文件地址空间持有者
mapping->flags= 0; //错误位和内存分配器的标志
mapping_set_gfp_mask(mapping,GFP_HIGHUSER);//初始化分配器标志,参见p305
mapping->assoc_mapping= NULL;//指向间接块所在块设备的address_space对象的地址
mapping->backing_dev_info= &default_backing_dev_info;
/*
* If the block_device provides abacking_dev_info for client
*inodes then use that. Otherwise theinode share the bdev's
* backing_dev_info.
*/
if(sb->s_bdev) {
structbacking_dev_info *bdi;
bdi= sb->s_bdev->bd_inode_backing_dev_info;
if(!bdi)
bdi= sb->s_bdev->bd_inode->i_mapping->backing_dev_info;
mapping->backing_dev_info= bdi;
}
memset(&inode->u,0, sizeof(inode->u));
inode->i_mapping= mapping;
}
returninode;
}
函数处理流程:
1、如果超级块的操作包含alloc_inode操作函数,调用该函数分配一个索引节点对象,对”bdev”文件系统来说,实现该指针的函数是bdev_alloc_inode,该函数从slab高速缓存bdev_cachep中分配一个bdev_inode对象,并返回其包含的inode对象的地址;如果不包含,从inode_cachep slab高速缓存中分配一个索引节点对象;把对象的地址存入局部变量inode中
2、初始化inode和其address_space对象,具体描述参见代码注释