Linux内核源码分析--打开块设备文件--open_bdev_excl函数

本文主要参考《深入理解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对象,具体描述参见代码注释

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值