现在的位置: 首页 > 综合 > 正文

Linux文件系统之文件的读写

2013年09月14日 ⁄ 综合 ⁄ 共 10518字 ⁄ 字号 评论关闭
一:前言
文件的读写是文件系统中最核心也是最复杂的一部份,它牵涉到了很多的概念.之前分析文件系统其它操作的时候,遇到与文件系统相关的读写部份都忽略过去了.在这一节里,来讨论一下文件的读写是怎样实现的.
二:I/O请求的概述
如之前所提到的,为了提高文件的操作效率,文件系统中的内容都是缓存在内存里的.每当发起一个Rear/Write请求的时候,都会到页面高速缓存中寻找具体的页面.如果页面不存在,则在页面高速缓存中建立相关页面的缓存.如果当前的页面不是最新的.那就必须要到具体的文件系统中读取数据了.一般来说,内核提供了这样的界面:它产生一个I/O请求.这个界面为上层隐藏了下层的不同实现.在这个界面中,将产生的I/O请求提交给I/O调度.再与I/O调度调用具体的块设备驱动程序.
整个过程如下图所示:
上图中的Generic Block Layer就是上面描述中所说的I/O的界面.
接下来我们以上图从下到上的层次进行讨论.
三:块设备驱动
块设备与字符设备的区别在于:块设备可以随机的访问,例如磁盘.正是因为它可以随机访问,内核才需要一个高效的手段去管理每一个块设备.例如对磁盘的操作,每次移动磁针都需要花不少的时候,所以尽量让其处理完相同磁道内的请求再将磁针移动到另外的磁道.而对于字符设备来说,不存在这样的顾虑,只需按顺序从里面读/写就可以了.
先来看一下块设备驱动所涉及到的数据结构.
3.1: block_device结构:
struct block_device {
     //主次驱备号
     dev_t              bd_dev;  /* not a kdev_t - it's a search key */
     //指向bdev文件系统中块设备对应的文件索引号
     struct inode *         bd_inode; /* will die */
     //计数器,统计块驱备被打开了多少次
     int           bd_openers;
     // 块设备打开和关闭的信号量
     struct semaphore   bd_sem;  /* open/close mutex */
     //禁止在块设备上建行新安装的信号量
     struct semaphore   bd_mount_sem; /* mount mutex */
     //已打开的块设备文件inode链表
     struct list_head   bd_inodes;
     //块设备描述符的当前拥有者
     void *             bd_holder;
     //统计字段,统计对bd_holder进行更改的次数
     int           bd_holders;
     //如果当前块设备是一个分区,此成员指向它所属的磁盘的设备
     //否则指向该描述符的本身
     struct block_device *  bd_contains;
     //块大小
     unsigned      bd_block_size;
     //指向分区描述符的指针
     struct hd_struct * bd_part;
     /* number of times partitions within this device have been opened. */
     //统计字段,统计块设备分区被打开的次数
     unsigned      bd_part_count;
     //读取块设备分区表时设置的标志
     int           bd_invalidated;
     //指向块设备所属磁盘的gendisk
     struct gendisk *   bd_disk;
     //指向块设备描述符链表的指针
     struct list_head   bd_list;
     //指向块设备的专门描述符backing_dev_info
     struct backing_dev_info *bd_inode_backing_dev_info;
     /*
      * Private data.  You must have bd_claim'ed the block_device
      * to use this.  NOTE:  bd_claim allows an owner to claim
      * the same device multiple times, the owner must take special
      * care to not mess up bd_private for that case.
      */
      //块设备的私有区
     unsigned long      bd_private;
}
通常,对于块设备来说还涉及到一个分区问题.分区在内核中是用hd_struct来表示的.
3.2: hd_struct结构:
struct hd_struct {
     //磁盘分区的起始扇区
     sector_t start_sect;
     //分区的长度,即扇区的数目
     sector_t nr_sects;
     //内嵌的kobject
     struct kobject kobj;
     //分区的读操作次数,读取扇区数,写操作次数,写扇区数
     unsigned reads, read_sectors, writes, write_sectors;
     //policy:如果分区是只读的,置为1.否则为0
     //partno:磁盘中分区的相对索引
     int policy, partno;
}
每个具体的块设备都会都应一个磁盘,在内核中磁盘用gendisk表示.
3.3: gendisk结构:
struct gendisk {
     //磁盘的主驱备号
     int major;             /* major number of driver */
     //与磁盘关联的第一个设备号
     int first_minor;
     //与磁盘关联的设备号范围
     int minors;                     /* maximum number of minors, =1 for
                                         * disks that can't be partitioned. */
     //磁盘的名字
     char disk_name[32];         /* name of major driver */
     //磁盘的分区描述符数组                                      
     struct hd_struct **part;    /* [indexed by minor] */
     //块设备的操作指针
     struct block_device_operations *fops;
     //指向磁盘请求队列指针
     struct request_queue *queue;
     //块设备的私有区
     void *private_data;
     //磁盘内存区大小(扇区数目)
     sector_t capacity;
     //描述磁盘类型的标志
     int flags;
     //devfs 文件系统中的名字
     char devfs_name[64];        /* devfs crap */
     //不再使用
     int number;            /* more of the same */
     //指向磁盘中硬件设备的device指针
     struct device *driverfs_dev;
     //内嵌kobject指针
     struct kobject kobj;
     //记录磁盘中断定时器
     struct timer_rand_state *random;
     //如果只读,此值为1.否则为0
     int policy;
     //写入磁盘的扇区数计数器
     atomic_t sync_io;      /* RAID */
     //统计磁盘队列使用情况的时间戳
     unsigned long stamp, stamp_idle;
     //正在进行的I/O操作数
     int in_flight;
     //统计每个CPU使用磁盘的情况
#ifdef   CONFIG_SMP
     struct disk_stats *dkstats;
#else
     struct disk_stats dkstats;
#endif
}
以上三个数据结构的关系,如下图所示:
如上图所示:
每个块设备分区的bd_contains会指它的总块设备节点,它的bd_part会指向它的分区表.bd_disk会指向它所属的磁盘.
从上图中也可以看出:每个磁盘都会对应一个request_queue.对于上层的I/O请求就是通过它来完成的了.它的结构如下:
3.4:request_queue结构:
struct request_queue
{
     /*
      * Together with queue_head for cacheline sharing
      */
      //待处理请求的链表
     struct list_head   queue_head;
     //指向队列中首先可能合并的请求描述符
     struct request         *last_merge;
     //指向I/O调度算法指针
     elevator_t         elevator;
 
     /*
      * the queue request freelist, one for reads and one for writes
      */
      //为分配请请求描述符所使用的数据结构
     struct request_list    rq;
 
     //驱动程序策略例程入口点的方法
     request_fn_proc        *request_fn;
     //检查是否可能将bio合并到请求队列的最后一个请求的方法
     merge_request_fn   *back_merge_fn;
     //检查是否可能将bio合并到请求队列的第一个请求中的方法
     merge_request_fn   *front_merge_fn;
     //试图合并两个相邻请求的方法
     merge_requests_fn  *merge_requests_fn;
     //将一个新请求插入请求队列时所调用的方法
     make_request_fn        *make_request_fn;
     //该方法反这个处理请求的命令发送给硬件设备
     prep_rq_fn         *prep_rq_fn;
     //去掉块设备方法
     unplug_fn     *unplug_fn;
     //当增加一个新段时,该方法驼回可插入到某个已存在的bio  结构中的字节数
     merge_bvec_fn      *merge_bvec_fn;
     //将某个请求加入到请求队列时,会调用此方法
     activity_fn        *activity_fn;
     //刷新请求队列时所调用的方法
     issue_flush_fn         *issue_flush_fn;
 
     /*
      * Auto-unplugging state
      */
      //插入设备时所用到的定时器
     struct timer_list  unplug_timer;
     //如果请求队列中待处理请求数大于该值,将立即去掉请求设备
     int           unplug_thresh;     /* After this many requests */
     //去掉设备之间的延迟
     unsigned long      unplug_delay; /* After this many jiffies */
     //去掉设备时使用的操作队列
     struct work_struct unplug_work;
     //
     struct backing_dev_info backing_dev_info;
 
     /*
      * The queue owner gets to use this for whatever they like.
      * ll_rw_blk doesn't touch it.
      */
      //指向块设备驱动程序中的私有数据
     void          *queuedata;
     //activity_fn()所用的参数
     void          *activity_data;
 
     /*
      * queue needs bounce pages for pages above this limit
      */
      //如果页框号大于该值,将使用回弹缓存冲
     unsigned long      bounce_pfn;
     //回弹缓存区页面的分配标志
     int           bounce_gfp;
 
     /*
      * various queue flags, see QUEUE_* below
      */
      //描述请求队列的标志
     unsigned long      queue_flags;
 
     /*
      * protects queue structures from reentrancy
      */
      //指向请求队列锁的指针
     spinlock_t         *queue_lock;
 
     /*
      * queue kobject
      */
      //内嵌的kobject
     struct kobject kobj;
 
     /*
      * queue settings
      */
      //请求队列中允许的最大请求数
     unsigned long      nr_requests;  /* Max # of requests */
     //如果待请求的数目超过了该值,则认为该队列是拥挤的
     unsigned int       nr_congestion_on;
     //如果待请求数目在这个阀值下,则认为该队列是不拥挤的
     unsigned int       nr_congestion_off;
 
     //单个请求所能处理的最大扇区(可调的)
     unsigned short         max_sectors;
     //单个请求所能处理的最大扇区(硬约束)
     unsigned short         max_hw_sectors;
     //单个请求所能处理的最大物理段数
     unsigned short         max_phys_segments;
     //单个请求所能处理的最大物理段数(DMA的约束)
     unsigned short         max_hw_segments;
     //扇区中以字节 为单位的大小
     unsigned short         hardsect_size;
     //物理段的最大长度(以字节为单位)
     unsigned int       max_segment_size;
     //段合并的内存边界屏弊字
     unsigned long      seg_boundary_mask;
     //DMA缓冲区的起始地址和长度的对齐
     unsigned int       dma_alignment;
     //空闲/忙标记的位图.用于带标记的请求
     struct blk_queue_tag   *queue_tags;
     //请求队列的引用计数
     atomic_t      refcnt;
     //请求队列中待处理的请求数
     unsigned int       in_flight;
 
     /*
      * sg stuff
      */
      //用户定义的命令超时
     unsigned int       sg_timeout;
     //Not Use
     unsigned int       sg_reserved_size;
}
request_queue表示的是一个请求队列,每一个请求都是用request来表示的.
3.5: request结构:
struct request {
     //用来形成链表
     struct list_head queuelist; /* looking for ->queue? you must _not_
                        * access it directly, use
                        * blkdev_dequeue_request! */
     //请求描述符的标志              
     unsigned long flags;        /* see REQ_ bits below */
 
     /* Maintain bio traversal state for part by part I/O submission.
      * hard_* are block layer internals, no driver should touch them!
      */
     //要传送的下一个扇区
     sector_t sector;       /* next sector to submit */
     //要传送的扇区数目
     unsigned long nr_sectors;   /* no. of sectors left to submit */
     /* no. of sectors left to submit in the current segment */
     //当前bio段传送扇区的数目
     unsigned int current_nr_sectors;
     //要传送的下一个扇区号
     sector_t hard_sector;       /* next sector to complete */
     //整个过程中要传送的扇区号
     unsigned long hard_nr_sectors;   /* no. of sectors left to complete */
     /* no. of sectors left to complete in the current segment */
     //当前bio段要传送的扇区数目
     unsigned int hard_cur_sectors;
 
     /* no. of segments left to submit in the current bio */
     //
     unsigned short nr_cbio_segments;
     /* no. of sectors left to submit in the current bio */
     unsigned long nr_cbio_sectors;
 
     struct bio *cbio;      /* next bio to submit */
     //请求中第一个没有完成的bio
     struct bio *bio;       /* next unfinished bio to complete */
     //最后的bio
     struct bio *biotail;
     //指向I/O调度的私有区
     void *elevator_private;
     //请求的状态
     int rq_status;     /* should split this into a few status bits */
     //请求所引用的磁盘描述符
     struct gendisk *rq_disk;
     //统计传送失败的计数
     int errors;
     //请求开始的时间
     unsigned long start_time;
 
     /* Number of scatter-gather DMA addr+len pairs after
      * physical address coalescing is performed.
      */
      //请求的物理段数
     unsigned short nr_phys_segments;
 
     /* Number of scatter-gather addr+len pairs after
      * physical and DMA remapping hardware coalescing is performed.
      * This is the number of scatter-gather entries the driver
      * will actually have to deal with after DMA mapping is done.
      */
      //请求的硬段数
     unsigned short nr_hw_segments;
     //与请求相关的标识
     int tag;
     //数据传送的缓冲区,如果是高端内存,此成员值为NULL
     char *buffer;
     //请求的引用计数
     int ref_count;
     //指向包含请求的请求队列描述符
     request_queue_t *q;
     struct request_list *rl;
     //指向数据传送终止的completion
     struct completion *waiting;
     //对设备发达“特殊请求所用到的指针”
     void *special;
 
     /*
      * when request is used as a packet command carrier
      */
      //cmd中的数据长度
     unsigned int cmd_len;
     //请求类型
     unsigned char cmd[BLK_MAX_CDB];
     //data中的数据长度
     unsigned int data_len;
     //为了跟踪所传输的数据而使用的指针
     void *data;
     //sense字段的数据长度
     unsigned int sense_len;
     //指向输出sense缓存区
     void *sense;
     //请求超时
     unsigned int timeout;
 
     /*
      * For Power Management requests
      */
      //指向电源管理命令所用的结构
     struct request_pm_state *pm;
}
请求队列描述符与请求描述符都很复杂,为了简化驱动的设计,内核提供了一个API,供块设备驱动程序来初始化一个请求队列.这就是blk_init_queue().它的代码如下:
//rfn:驱动程序自动提供的操作I/O的函数.对应请求队列的request_fn
//lock:驱动程序提供给请求队列的自旋锁
request_queue_t *blk_init_queue(request_fn_proc *rfn, spinlock_t *lock)
{
     request_queue_t *q;
     static int printed;
     //申请请求队列描述符
     q = blk_alloc_queue(GFP_KERNEL);
     if (!q)
         return NULL;
     //初始化q->request_list
     if (blk_init_free_list(q))
         goto out_init;
 
     if (!printed) {
         printed = 1;
         printk("Using %s io scheduler\n", chosen_elevator->elevator_name);
     }
 
     //初始化请求队列描述符中的各项操作函数
     q->request_fn      = rfn;
     q->back_merge_fn       = ll_back_merge_fn;
     q->front_merge_fn      = ll_front_merge_fn;
     q->merge_requests_fn   = ll_merge_requests_fn;
     q->prep_rq_fn      = NULL;
     q->unplug_fn       = generic_unplug_device;
     q->queue_flags         = (1 << QUEUE_FLAG_CLUSTER);
     q->queue_lock      = lock;
 
    
     blk_queue_segment_boundary(q, 0xffffffff);
     //设置q->make_request_fn函数,初始化等待队对列的定时器和等待队列
     blk_queue_make_request(q, __make_request);
     //设置max_segment_size,max_hw_segments,max_phys_segments
     blk_queue_max_segment_size(q, MAX_SEGMENT_SIZE);
     blk_queue_max_hw_segments(q, MAX_HW_SEGMENTS);
     blk_queue_max_phys_segments(q, MAX_PHYS_SEGMENTS);
 
     /*
      * all done
      */
      //设置等待队列的I/O调度程序
     if (!elevator_init(q, chosen_elevator))

抱歉!评论已关闭.