Linux kernel FAT32文件系统分析

文本探讨了Linux kernel中对fat32文件系统的实现,关于fat文件的格式可以查看微软的fat白皮书。

1.     FAT表操作

FAT文件系统中,使用FAT表标记哪个cluster被占用,哪个没被占用。在Linux内核代码中,与FAT表操作对应的是fat_entryfatent_ops结构和fat_cache_id缓存等。

1.1 fat_entry

fat中的fat entry用于描述fat文件系统的FAT分配表。

struct fat_entry {

         int entry;                            // 代表当前的簇索引

         union {                                // 簇索引表

                   u8 *ent12_p[2];

                   __le16 *ent16_p;

                   __le32 *ent32_p;

         } u;

         int nr_bhs;                         // buffer_head数目,可能是1也可能是2FAT321

         struct buffer_head *bhs[2];         // FAT表的扇区的buffer_head

         struct inode *fat_inode;                // 超级块的inode

};

FAT文件系统中对FAT12/16/32,分别实现了一个fatent_operationsfatent_operations的初始化在fat_ent_access_init函数,大意是根据超级块里的fat_bits判断当前FAT类型,然后将对应的fatent_operations赋值到超级块的fatent_ops中。

对于FAT32,超级块的fatent_ops会指向fat32_opsfatent_shift2,代表一个簇的编号占用4个字节:

static struct fatent_operations fat32_ops = {

         .ent_blocknr  = fat_ent_blocknr,

         .ent_set_ptr  = fat32_ent_set_ptr,

         .ent_bread     = fat_ent_bread,

         .ent_get = fat32_ent_get,

         .ent_put = fat32_ent_put,

         .ent_next        = fat32_ent_next,

};

fat_ent_blocknr

根据entry编号,得到block号和在block里的索引。entry为簇的索引,比如entry 5,对于FAT32它在FAT表的位置为5*4 +

static void fat_ent_blocknr(struct super_block *sb, int entry, int *offset, sector_t *blocknr)

         int bytes = (entry << sbi->fatent_shift);    // FAT32sbi->fatent_shift2

         *offset = bytes & (sb->s_blocksize - 1);

         *blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits);

ent_set_ptr

设置fat_entryu.ent32_p值。

static void fat32_ent_set_ptr(struct fat_entry *fatent, int offset)

         fatent->u.ent32_p = (__le32 *)(fatent->bhs[0]->b_data + offset);

ent_bread

FAT分配表,读到后保存到fat_entrybhs,并将其offset开始放到u.ent32_p

static int fat_ent_bread(struct super_block *sb, struct fat_entry *fatent, int offset, sector_t blocknr)

         struct fatent_operations *ops = MSDOS_SB(sb)->fatent_ops;

         fatent->fat_inode = MSDOS_SB(sb)->fat_inode;

         fatent->bhs[0] = sb_bread(sb, blocknr);   // 注意这里bhs是个数组,FAT32只用里面第一个,所以下面的nr_bhs1

         fatent->nr_bhs = 1;

         ops->ent_set_ptr(fatent, offset);

ent_get

得到当前簇号

static int fat32_ent_get(struct fat_entry *fatent)

         int next = le32_to_cpu(*fatent->u.ent32_p) & 0x0fffffff;

         if (next >= BAD_FAT32)

                   next = FAT_ENT_EOF;

         return next;

ent_put

FAT表中某个簇的位置写成new代表的值,即更新簇链,一般在释放簇的时候会用到。

static void fat32_ent_put(struct fat_entry *fatent, int new)

         if (new == FAT_ENT_EOF)

                   new = EOF_FAT32;

         new |= le32_to_cpu(*fatent->u.ent32_p) & ~0x0fffffff;

         *fatent->u.ent32_p = cpu_to_le32(new);

         mark_buffer_dirty_inode(fatent->bhs[0], fatent->fat_inode);

ent_next

得到下一个簇,注意这里是整个FAT表的下一个簇,不是某一个文件的下一个簇。此函数在分配空闲簇、统计空闲簇是用于遍历整个FAT表。

static int fat32_ent_next(struct fat_entry *fatent)

         const struct buffer_head *bh = fatent->bhs[0];

         fatent->entry++;

         if (fatent->u.ent32_p < (__le32 *)(bh->b_data + (bh->b_size - 4))) {

                   fatent->u.ent32_p++;

                   return 1;

         }

         fatent->u.ent32_p = NULL;

1.2  fat_entry的对外接口

fat_get_cluster

fat_get_cluster根据cluster值得到该cluster在文件中以及硬盘中的簇号。这里的参数cluster是在文件中的簇号,fclus是返回的在文件中的簇号,dclus是在磁盘中的簇号。

int fat_get_cluster(struct inode *inode, int cluster, int *fclus, int *dclus)

         struct super_block *sb = inode->i_sb;

         const int limit = sb->s_maxbytes >> MSDOS_SB(sb)->cluster_bits;

         struct fat_entry fatent;

         struct fat_cache_id cid;

         *fclus = 0;

         *dclus = MSDOS_I(inode)->i_start;

         // cache中查找与cluster最近的fclusdclus

         fat_cache_lookup(inode, cluster, &cid, fclus, dclus);

         。。。。。。

         fatent_init(&fatent);    // 初始化一个fat_entry

         while (*fclus < cluster) {

                   nr = fat_ent_read(inode, &fatent, *dclus);

                   。。。。。。

                   if (nr == FAT_ENT_EOF) {

                            fat_cache_add(inode, &cid);

                            goto out;

                   }

                   (*fclus)++;

                   *dclus = nr;

                   if (!cache_contiguous(&cid, *dclus))

                            cache_init(&cid, *fclus, *dclus);

         }

         // 加入缓存

         fat_cache_add(inode, &cid);

上面函数中,提到了fat_entry的缓存,其实现代码在fs/fat/chche.c,作用为根据文件中的簇号找到磁盘中的簇号。

另外,上文中调用了fat_ent_read函数,此函数执行一系列fatent_operations中的操作,例如读fat表中某一个block,查找该block中的cluster表索引,从而得到文件簇对应的磁盘簇。注意这里的读block并不一定是真正去磁盘上读,大多数情况下该block的内容会在block层的disk cache中得到。

fat_count_free_clusters

fat_count_free_clusters的作用是得到当前空闲的簇的总和,即磁盘上有多少剩余空间。此函数实现很简单,就是扫描FAT表,统计哪些簇是空闲的。扫描后的结果会保存在超级块的free_clusters中。

fat_alloc_clusters

为某个文件(inode)分配新簇,这些新簇会链接到当前文件簇链的末尾。

fat_ent_write

更新FAT表,这里的更新包括两部分,一部分为主FAT,另一部分为mirror FAT

fat_free_clusters

释放FAT表某个簇标记,更新包括两部分,一部分为主FAT,另一部分为mirror FAT

2.     file_operations

FAT文件系统使用的file_operations结构为fat_file_operations

const struct file_operations fat_file_operations = {

         .llseek               = generic_file_llseek,

         .read                 = do_sync_read,   // 通用读函数,通过generic_file_aio_read实现

         .write                = do_sync_write,  // 通用写函数,通过generic_file_aio_write实现

         .aio_read         = generic_file_aio_read,      // 通用实现,通过aops实现

         .aio_write       = generic_file_aio_write, // 通用实现,通过aops实现

         .mmap              = generic_file_mmap,

         .release  = fat_file_release,

         .unlocked_ioctl      = fat_generic_ioctl,

         .fsync                = fat_file_fsync,

         .splice_read   = generic_file_splice_read,

};

先看一下FAT的读写函数的实现。

读操作

do_sync_readkernel提供的通用同步读函数,最终会调用到file_operations的异步读写函数实现。

ssize_t do_sync_read(struct file *filp, char __user *buf, size_t len, loff_t *ppos)

         。。。。。。

         filp->f_op->aio_read(&kiocb, &iov, 1, kiocb.ki_pos);

         wait_on_retry_sync_kiocb(&kiocb);

这里aio_readgeneric_file_aio_read

 

ssize_t generic_file_aio_read(struct kiocb *iocb, const struct iovec *iov, unsigned long nr_segs, loff_t pos)

         if (filp->f_flags & O_DIRECT)

                   filemap_write_and_wait_range

                   mapping->a_ops->direct_IO

         。。。。。。

         do_generic_file_read

         。。。。。。

do_generic_file_read中,会先在disk cache中查找有没有对应的disk cache,如果找到则直接返回,否则调用aops->read_page调用真正的读操作,这里aops会指向fat_aops

 

写操作

写操作与读操作类似:同步调用函数由异步调用函数实现,异步调用函数最终又是调用到aops-> write_beginwrite_end

 seek

seek函数的实现使用的是generic_file_llseek,此函数中设置了file->f_pos

mmap

mmap的实现使用的是generic_file_mmap

int generic_file_mmap(struct file * file, struct vm_area_struct * vma)

         struct address_space *mapping = file->f_mapping;

         if (!mapping->a_ops->readpage)

                   return -ENOEXEC;

         file_accessed(file);

         vma->vm_ops = &generic_file_vm_ops;

         vma->vm_flags |= VM_CAN_NONLINEAR;

         return 0;

const struct vm_operations_struct generic_file_vm_ops = {

         .fault                 = filemap_fault,

};

generic_file_vm_ops中实现了fault函数,fault会在处理缺页异常时被调用。

int filemap_fault(struct vm_area_struct *vma, struct vm_fault *vmf)

         // 查找对应页是否在cache

         page = find_get_page(mapping, offset);

         if (likely(page))

                   do_async_mmap_readahead(vma, ra, file, page, offset);

         else

                   do_sync_mmap_readahead(vma, ra, file, offset);

                   page = find_lock_page(mapping, offset);

         vmf->page = page;        // 返回此page

3.     address_space_operations

file_operations结构中提到了aops结构,此结构在FAT文件系统中为fat_aops

static const struct address_space_operations fat_aops = {
         .readpage       = fat_readpage,
         .readpages     = fat_readpages,
         .writepage      = fat_writepage,
         .writepages    = fat_writepages,
         .sync_page     = block_sync_page,
         .write_begin  = fat_write_begin,
         .write_end     = fat_write_end,
         .direct_IO        = fat_direct_IO,
         .bmap               = _fat_bmap
};

readpage

static int fat_readpage(struct file *file, struct page *page)

         return mpage_readpage(page, fat_get_block);

 

int mpage_readpage(struct page *page, get_block_t get_block)

         struct bio *bio = NULL;

         sector_t last_block_in_bio = 0;

         struct buffer_head map_bh;

         unsigned long first_logical_block = 0;

         map_bh.b_state = 0;

         map_bh.b_size = 0;

         bio = do_mpage_readpage(bio, page, 1, &last_block_in_bio,

                            &map_bh, &first_logical_block, get_block);

         if (bio)

                   mpage_bio_submit(READ, bio);

 

你可能感兴趣的:(linux,struct,file,cache,buffer,linux内核)