分类: LINUX
2016-08-29 13:18:21
原文地址:如何写一个简易文件系统(2):挂载文件系统mount 作者:mournjust
myfs_fill_super是一个比较重要的函数,用于在mount的过程中填充super_block,注意这儿myfs_fill_super的参数sb是上层已经分配好的一个super_block指针,我们只需要在这儿填充就可以了。
static int myfs_fill_super(struct super_block *sb,void *data, int silent)
{
struct myfs_sb_info *sbi = NULL;
struct dentry*root_dir;
struct myfs_sb * myfs_sb;//每一个文件系统的超级块信息在物理介质上的保存方式都不一样,这个因设计者的想法而已。这边就需要讲到另外一个问题,就是在文件系统设计之初,如何考虑各个部分数据的分布问题。下面这个表格是myfs的数据分布
超级块 |
根目录下的子文件(myfs只支持一级目录) |
数据分布 |
一个block |
一个block |
剩余block |
Myfs暂时只实现了NAND部分,所以这边的block都是NAND的block,而不是块设备的block。
struct inode *root_inode = NULL;
struct mtd_info *mtd;
char devname_buf[33];
int err = 0,retlen;
unsigned int i,j ,k,total_blocks,total_inodes;
struct myfs_inode * li;
myfs_trace("%s",__func__);
if (!sb)
printk("myfs: sb is NULL\n");
else if (!sb->s_dev)
printk("myfs: sb->s_dev is NULL\n");
else if (!bdevname(sb->s_bdev, devname_buf))
printk("myfs: devname is NULL\n");
/* Check it's an mtd device..... */
if (MAJOR(sb->s_dev) != MTD_BLOCK_MAJOR)
return MYFS_FAIL; /* This isn't an mtd device */
sb->s_magic = MYFS_MAGIC1;//每一个文件系统都一个MAGIC_NUMBER ,并且是唯一的,用于却别与别的文件系统。
sb->s_op = &myfs_super_ops;//超级块的操作结构体,后面再讲
sb->s_flags |= MS_NOATIME;// MS_NOATIME是mount时候的一个参数,你也可以不知道,随便写都可以,读者可以通过man mount来看具体的含义。
#ifdef CONFIG_IS_NAND
/* Get the device */
mtd = get_mtd_device(NULL, MINOR(sb->s_dev));
if(!mtd){
myfs_trace("MTD device #%u doesn't appear to exist",MINOR(sb->s_dev));
return MYFS_FAIL;
}
myfs_trace("MTD name:%s",mtd->name);
myfs_trace("MTD size:%llu",mtd->size);
myfs_trace("MTD writesize:%u",mtd->writesize);
myfs_trace("MTD erasesize:%u",mtd->erasesize);
myfs_trace("MTD oobsize:%u",mtd->oobsize);
//做一些前期的校验,因为后面的空间分配,读写等等都依赖于这些的writesize,擦除依赖于erasesize的大小,为了防止将来出错,所以需要提前确保无误。
if(!is_power_of_2(mtd->writesize)){
myfs_trace("invalid writesize");
goto fail;
}
if(!is_power_of_2(mtd->erasesize)){
myfs_trace("invalid erasesize");
goto fail;
}
/* Check it's NAND */
if (mtd->type != MTD_NANDFLASH) {
myfs_trace("MTD device is not NAND it's type %d",mtd->type);
return MYFS_FAIL;
}
myfs_trace("erase %p", mtd->erase);
myfs_trace("read %p", mtd->read);
myfs_trace("write %p", mtd->write);
myfs_trace("readoob %p", mtd->read_oob);
/* Check for functions */
if (!mtd->erase ||!mtd->read ||!mtd->write || !mtd->read_oob || !mtd->write_oob) {
myfs_trace("MTD device does not support required functions");
return MYFS_FAIL;
}
#endif
//好戏要开始了,怎么去填充superblock
sbi = kzalloc(sizeof(struct myfs_sb_info), GFP_KERNEL);
if(!sbi)
return -ENOMEM;
sb->s_fs_info = sbi;
//通常每一个文件系统都会对于VFS所规定的要求做以前扩充。比如EXT4可能用一个叫做EXT4_superblock的超类来兼容superblock,但是我们通常要保证,这两个结构体都能互相找到对方。因为说不定什么时候就用到了。
//比如在超类myfs_superblock中设置一个指针指向 sb,这样可以通过sbi->sb来找到sb,同时又可以通过container_of(ino, struct myfs_inode_info, vfs_inode);从sb找到sbi。
sbi->sb = sb;
sb->s_blocksize = mtd->erasesize;
myfs_sb = &sbi->myfs_sb;
#ifdef CONFIG_IS_NAND
sbi->mtd = mtd;
sbi->buf = kmalloc(mtd->writesize ,GFP_KERNEL);
if(!sbi->buf){
err = -ENOMEM;
goto fail;
}
//分配一个buf,大小为mtd->erasesize,用于擦除时做数据的保存。后面会讲到。虽然myfs是一个NAND型的文件系统,但是并没有考虑到损耗均衡等等因素,要不然会变得很复杂。
sbi->block_buf = kmalloc(mtd->erasesize ,GFP_KERNEL);
if(!sbi->block_buf){
err = -ENOMEM;
goto fail;
}
myfs_trace("read superblock \n");
/*
*just read the first five pages to find the superblock
*why need to read five pages ? Q:maybe bad page
*/
for(i = 0 ;i < mtd->erasesize/mtd->writesize;i++){
err = mtd->read(mtd,i*mtd->writesize,mtd->writesize,&retlen,sbi->buf);
if(!err)
break;
myfs_trace("read superblock failed %d, %d times",err,i);
mdelay(1000);
}
#endif
myfs_trace("read superblock ok");
memcpy(myfs_sb,sbi->buf,sizeof(struct myfs_sb));
if((myfs_sb->magic1 != MYFS_MAGIC1 ) ||(myfs_sb->magic2 != MYFS_MAGIC2)){
myfs_trace("invalid magic:%x ,%x \n",myfs_sb->magic1,myfs_sb->magic2);
/*format and write a new superblock*/
myfs_format(sbi,mtd,i);
}
if((myfs_sb->page_size != mtd->writesize ) || (myfs_sb->block_size != mtd->erasesize)){
myfs_trace("invalied pagesize or erasesize");
myfs_format(sbi,mtd,i);
}
if(myfs_sb->block_shift != (ffs(mtd->erasesize) - 1)){
myfs_trace("invalid block_shift");
myfs_format(sbi,mtd,i);
}
if(myfs_sb->page_shift != (ffs(mtd->writesize) - 1)){
myfs_trace("invalid page_shift");
myfs_format(sbi,mtd,i);
}
/*verify the sb_in_page*/
if(myfs_sb->sb_in_page != i ){
myfs_trace("sb_in_page (%d) is not equal to %d",myfs_sb->sb_in_page,i);
myfs_sb->sb_in_page = i;
}
total_blocks = mtd->size >> (ffs(mtd->erasesize) - 1);
//这儿在系统内部并没有保存文件系统的统计信息,比如哪些块被使用了,哪些块还是空闲的,而是在mount的过程中去扫描。所以通过一个block_bitmap_node的字段来动态的保存文件系统的统计信息。
sbi->block_bitmap_node = kmalloc((sizeof(struct block_bitmap_node) + (total_blocks+7)/8),GFP_KERNEL);
if(!sbi->block_bitmap_node){
myfs_trace("kmalloc sbi->block_bitmap_node failed");
err = -ENOMEM;
goto fail;
}
sbi->block_bitmap_node->version = 0;
sbi->block_bitmap_node->magic = BITMAP_MAGIC;
sbi->block_bitmap_node->bitmap_size = (total_blocks+7)/8;//每一个bit代表一个块的使用情况
memset(sbi->block_bitmap_node->block_bitmap,0x0,sbi->block_bitmap_node->bitmap_size);
/*statistics the block status*/
set_bit_to_bitmap(sbi->block_bitmap_node,0); /*superblock block*/
set_bit_to_bitmap(sbi->block_bitmap_node,1); /*inode block*/
myfs_sb->free_blocks = total_blocks - 2;
total_inodes = MAX_INODE_NUMBERS;
//后面会说到inode,这儿先提一下。Inode是文件的唯一标示,不管是文件还是文件夹还是其他。所以每一个inode的number都是唯一的。所以分配的时候要小心。这儿现在mount的时候统计已经被使用掉的number号,以免后续的分配重复。
sbi->inode_bitmap_node = kmalloc((sizeof(struct inode_bitmap_node) + (total_inodes+7)/8),GFP_KERNEL);
if(!sbi->inode_bitmap_node){
myfs_trace("kmalloc sbi->inode_bitmap_node fail");
err = -ENOMEM;
goto fail;
}
sbi->inode_bitmap_node->version = 0;
sbi->inode_bitmap_node->bitmap_size = (total_inodes+7)/8;
memset(sbi->inode_bitmap_node->inode_bitmap,0x00,sbi->inode_bitmap_node->bitmap_size);
/*set inode 0 is used*/
sbi->inode_bitmap_node->inode_bitmap[MYFS_ROOT_INO/8] |= 1<<(MYFS_ROOT_INO%8);
for(i = 0; i < sbi->block_bitmap_node->bitmap_size ; i++)
myfs_trace("0x%x",sbi->block_bitmap_node->block_bitmap[i]);
myfs_trace("begin to statics block status, total blocks (%d)",total_blocks);
//上面提到了分配第二个block用于存储文件信息,这儿读取这个blcok,做两方面的统计,一方面统计哪些inode number被占用了,另外一方面统计哪些block被使用了。
其实这个block中线性存储的都是struct myfs_inode类型的数据。
struct myfs_inode {
unsigned char flags;/*EMPTY,DELETE ,NORMAL INODES?*/
unsigned char attr; /*file,directory ?*/
unsigned char name[MYFS_MAX_NAME_LEN];//文件名称
unsigned int inode_no;//节点号
unsigned int inode_version;
unsigned int filesize;//文件系统大小
uid_t uid;
gid_t gid;
struct timespec mod_time;
struct timespec creat_time;
struct timespec write_time;
unsigned int assigned_block[MYFS_MAX_BLOCK_LEN];
/*
*if devices is nand ,first_cl[0-15] = page,first_cl[16:31] = block;
*if devices is emmc ,first_cl = sector in partition
*/
};其中assigned_block[MYFS_MAX_BLOCK_LEN]用于存储该文件的数据存储的位置,所以在myfs中文件大小被限制在MYFS_MAX_BLOCK_LEN*mtd->erasesize。
for( i = 0 ; i < mtd->erasesize/mtd->writesize;i++){
err = mtd->read(mtd,mtd->erasesize + i*mtd->writesize,mtd->writesize,&retlen,sbi->buf);
if(err){
myfs_trace("%s,read failed ,err = %d ",__func__,err);
return -EIO;
}
li = (struct myfs_inode*)sbi->buf;
for(j = 0; j < mtd->writesize/sizeof(struct myfs_inode);j++,li++){
if(i == 0 && j == 0)//位置为0的inode已经被根目录项占据了,所以跳过
continue;
if(li->flags == MYFS_ATTR_FILE){
//在NAND中,空白读出的数据位0XFF,所以这儿的total_inodes <= 0XFF。如果inode_no介于 1 和 total_inodes,那么就是一个有效的inode number,然后就去设置相应的位图。
if(li->inode_no >=1 &&li->inode_no <= total_inodes)
/*set this inode number has been used*/
sbi->inode_bitmap_node->inode_bitmap[li->inode_no/8] |= 1<<(li->inode_no%8);
}
//如果是一个有效的文件,那么查看文件所占用的数据块,然后去设置相应的bit为来表明数据块已经被使用了。
for(k = 0 ; k < MYFS_MAX_BLOCK_LEN;k++){
if((li->assigned_block[k] == 0x0))
break;
if(li->assigned_block[k] >= total_blocks)
continue;
set_bit_to_bitmap(sbi->block_bitmap_node,li->assigned_block[k]);
myfs_sb->free_blocks--;
}
}
}
myfs_trace("free_blocks %d",myfs_sb->free_blocks);
myfs_trace("statics done");
//单单有了superblock还不行,必须还有一个根目录。但是通常根目录都是胡诌出来的。因为根目录的信息想想都知道,inode number为0,是个目录文件等等。所以大部分的文件系统都是在mount的过程中胡诌一个根目录出来。
//注意这儿的new_inode函数,虽然是一个通用函数,但是它的参数为sb,因为它用到了超级块操作结构体中的一个函数。以后再讲。
root_inode = new_inode(sb);
if (!root_inode){
err = -ENOMEM;
goto fail;
}
root_inode->i_ino = MYFS_I(root_inode)->ino = MYFS_ROOT_INO;
root_inode->i_version = 1;
myfs_fill_root(sb,root_inode);
myfs_trace("got root inode");
//这儿又出来一个VFS中常用的结构体dentry,对于通常的文件,基本都是一个dentry,一个inode搞定。但是软连接和硬链接就不一样了。但是myfs中压根就没想过支持链接文件=.=!!
root_dir = d_alloc_root(root_inode);
if(!root_dir){
myfs_trace("d_alloc_root failed");
iput(root_inode);
err = MYFS_FAIL;
goto fail;
}
myfs_trace("d_alloc_root done");
sbi->root = MYFS_I(root_inode);
sbi->lif = NULL;
sb->s_root = root_dir;
return 0;
fail:
myfs_trace("%s failed",__func__);
if(sbi->buf)
kfree(sbi->buf);
if(root_inode)
iput(root_inode);
sb->s_fs_info = NULL;
if(sbi)
kfree(sbi);
return err;
}
至于超级块的操作结构体先贴上:
struct super_operations myfs_super_ops = {
.alloc_inode = myfs_alloc_inode,//分配私有inode节点
.destroy_inode = myfs_destroy_inode,//销毁私有inode节点
.write_inode = myfs_write_inode,//如何写入一个节点的相关信息到存储介质中去
.evict_inode = myfs_evict_inode,//如何从存储介质中删除一个节点
.put_super = myfs_put_super,
.write_super = myfs_write_super,//如何在sync或者unmount的时候写入superblock
.sync_fs = myfs_sync_fs,//同上
.statfs = myfs_statfs,//获得文件系统的相关信息
.remount_fs = myfs_remount,//如何remount文件系统,在myfs中直接return 0
};
这儿先说myfs_alloc_inode函数,因为上面的代码
root_inode = new_inode(sb);
会调用到这个函数。
还是我们说到的VFS会有一个结构体inode,但是基本每一个具体的文件系统的具体XXX_inode都是VFS的inode的超集。所以一般的具体的文件系统的inode数据结构都定义如下:
struct myfs_inode_info {
struct inode vfs_inode;//包含struct inode
….
struct myfs_inode myfs_inode;
struct myfs_inode_info *next;
struct myfs_inode_info *child;
struct myfs_inode_info *parent;
unsigned int ino;
unsigned int dirty;
};
就如前面superblock所说的,可以互相找到对方。既然每一个具体文件系统的inode都不一样,那么需要一个函数来分配inode节点。这就是
static struct inode *myfs_alloc_inode(struct super_block *sb)
{
struct myfs_inode_info *li = NULL;
myfs_trace("myfs_alloc_inode");
li = kzalloc(sizeof(struct myfs_inode_info),GFP_KERNEL);
if(!li)
return NULL;
li->myfs_inode.attr = MYFS_ATTR_FILE;
li->myfs_inode.flags = MYFS_INODE_NORMAL;
li->myfs_inode.inode_version = 0;
inode_init_once(&li->vfs_inode);
myfs_trace("myfs_alloc_inode - ok");
return &li->vfs_inode;
}
static void(struct super_block *sb,struct inode* inode)
{
inode->i_mode &= ~S_IFMT;
inode->i_mode |= S_IFDIR;//根目录是一个目录,directory
inode->i_op = &myfs_inode_operations;
inode->i_fop = &myfs_dir_operations;
inode->i_mapping->a_ops = &myfs_file_address_operations;
//这儿不得不提到文件系统的另外三大操作结构体,inode操作结构体,dir目录操作结构体,以及address_space操作结构体
inode->i_blocks = 0;
inode->i_size = 0;
inode->i_rdev = sb->s_dev;
inode->i_mtime = inode->i_atime = inode->i_ctime = CURRENT_TIME;
inode->i_ino = MYFS_ROOT_INO;//0
inode->i_uid = current->cred->fsuid;
inode->i_gid = current->cred->fsgid;
MYFS_I(inode)->myfs_inode.filesize = 0;
}
在mount的过程中需要虚构(或者从存储介质中读取)一个根目录,myfs_fill_root用于初始化这个根目录项的inode节点。
另外不得不提的是,在写文件系统的过程中,strace命令尤为重要,可以让你准确的知道是哪一个接口函数出了问题。比如
Strace mount –t myfs /dev/mtdblock3 /mnt