文本探讨了Linux kernel中对fat32文件系统的实现,关于fat文件的格式可以查看微软的fat白皮书。
FAT文件系统中,使用FAT表标记哪个cluster被占用,哪个没被占用。在Linux内核代码中,与FAT表操作对应的是fat_entry,fatent_ops结构和fat_cache_id缓存等。
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也可能是2,FAT32是1
struct buffer_head *bhs[2]; // FAT表的扇区的buffer_head
struct inode *fat_inode; // 超级块的inode
};
FAT文件系统中对FAT12/16/32,分别实现了一个fatent_operations。fatent_operations的初始化在fat_ent_access_init函数,大意是根据超级块里的fat_bits判断当前FAT类型,然后将对应的fatent_operations赋值到超级块的fatent_ops中。
对于FAT32,超级块的fatent_ops会指向fat32_ops;fatent_shift为2,代表一个簇的编号占用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); // FAT32中sbi->fatent_shift为2
*offset = bytes & (sb->s_blocksize - 1);
*blocknr = sbi->fat_start + (bytes >> sb->s_blocksize_bits);
ent_set_ptr
设置fat_entry的u.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_entry的bhs,并将其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_bhs为1。
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;
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最近的fclus和dclus
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。
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_read是kernel提供的通用同步读函数,最终会调用到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_read为generic_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_begin和write_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
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);