1# 适配新的文件系统 2 3## 基本概念 4 5所谓对接VFS层,其实就是指实现VFS层定义的若干接口函数,可根据文件系统的特点和需要适配其中部分接口。一般情况下,支持文件读写,最小的文件系统适配看起来是这样的: 6 7``` 8struct MountOps g_yourFsMountOps = { 9 .Mount = YourMountMethod, 10}; 11 12struct file_operations_vfs g_yourFsFileOps = { 13 .read = YourReadMethod, 14 .write = YourWriteMethod, 15} 16 17struct VnodeOps g_yourFsVnodeOps = { 18 .Create = YourCreateMethod; 19 .Lookup = YourLookupMethod; 20 .Reclaim = YourReclaimMethod; 21}; 22 23FSMAP_ENTRY(yourfs_fsmap, "your fs name", g_yourFsMountOps, TRUE, TRUE); // 注册文件系统 24``` 25 26>  **说明:** 27> 1. open和close接口不是必须要实现的接口,因为这两个接口是对文件的操作,对下层的文件系统一般是不感知的,只有当要适配的文件系统需要在open和close时做一些特别的操作时,才需要实现。 28> 29> 2. 适配文件系统,对基础知识的要求较高,适配者需要对要适配的文件系统的原理和实现具有深刻的理解,本节中不会事无巨细地介绍相关的基础知识,如果您在适配的过程中遇到疑问,建议参考kernel/liteos_a/fs目录下已经适配好的文件系统的代码,可能就会豁然开朗。 30 31 32## 适配Mount接口 33 34Mount是文件系统第一个被调用的接口,该接口一般会读取驱动的参数,根据配置对文件系统的进行初始化,最后生成文件系统的root节点。Mount接口的定义如下: 35 36 37``` 38int (*Mount)(struct Mount *mount, struct Vnode *blkDriver, const void *data); 39``` 40 41其中,第一个参数struct Mount \*mount是Mount点的信息,适配时需要填写的是下面的变量: 42 43 44``` 45struct Mount { 46 const struct MountOps *ops; /* Mount相关的函数钩子 */ 47 struct Vnode *vnodeCovered; /* Mount之后的文件系统root节点 */ 48 void *data; /* Mount点的私有数据 */ 49}; 50``` 51 52第二个参数struct Vnode \*blkDriver是驱动节点,可以通过这个节点访问驱动。 53 54第三个参数const void \*data是mount命令传入的数据,可以根据文件系统的需要处理。 55 56 下面以JFFS2为例,详细看一下mount接口是如何适配的: 57 58``` 59int VfsJffs2Bind(struct Mount *mnt, struct Vnode *blkDriver, const void *data) 60{ 61 int ret; 62 int partNo; 63 mtd_partition *p = NULL; 64 struct MtdDev *mtd = NULL; 65 struct Vnode *pv = NULL; 66 struct jffs2_inode *rootNode = NULL; 67 68 LOS_MuxLock(&g_jffs2FsLock, (uint32_t)JFFS2_WAITING_FOREVER); 69 70 /* 首先是从驱动节点中获取文件系统需要的信息,例如jffs2读取的是分区的编号 */ 71 p = (mtd_partition *)((struct drv_data *)blkDriver->data)->priv; 72 mtd = (struct MtdDev *)(p->mtd_info); 73 74 if (mtd == NULL || mtd->type != MTD_NORFLASH) { 75 LOS_MuxUnlock(&g_jffs2FsLock); 76 return -EINVAL; 77 } 78 79 partNo = p->patitionnum; 80 81 /* 然后生成一个文件系统的根Vnode,这里注意不要搞混rootNode和根Vnode,rootNode类型是inode,是jffs2内部维护的私有数据,而Vnode是VFS的概念,是通用的文件节点, 82 这一步实际上就是把文件系统内部的私有信息保存到Vnode中,这样就可以通过Vnode直接找到文件系统中的对应文件。 83 */ 84 ret = jffs2_mount(partNo, &rootNode); 85 if (ret != 0) { 86 LOS_MuxUnlock(&g_jffs2FsLock); 87 return ret; 88 } 89 90 ret = VnodeAlloc(&g_jffs2Vops, &pv); 91 if (ret != 0) { 92 LOS_MuxUnlock(&g_jffs2FsLock); 93 goto ERROR_WITH_VNODE; 94 } 95 96 /* 下面这段填写的是关于这个Vnode对应文件的相关信息,uid\gid\mode这部分信息,有的文件系统可能不支持,可以不填 */ 97 pv->type = VNODE_TYPE_DIR; 98 pv->data = (void *)rootNode; 99 pv->originMount = mnt; 100 pv->fop = &g_jffs2Fops; 101 mnt->data = p; 102 mnt->vnodeCovered = pv; 103 pv->uid = rootNode->i_uid; 104 pv->gid = rootNode->i_gid; 105 pv->mode = rootNode->i_mode; 106 107 /* 这里的HashInsert是为了防止重复生成已经生成过的Vnode, 第二个参数一般会选择本文件系统内可以唯一确定某一个文件的信息,例如这里是jffs2内部inode的地址 */ 108 (void)VfsHashInsert(pv, rootNode->i_ino); 109 110 g_jffs2PartList[partNo] = blkDriver; 111 112 LOS_MuxUnlock(&g_jffs2FsLock); 113 114 return 0; 115ERROR_WITH_VNODE: 116 return ret; 117} 118// ... 119// ... 120const struct MountOps jffs_operations = { 121 .Mount = VfsJffs2Bind, 122 // ... 123 // ... 124}; 125``` 126 127总结: 128 1291. 首先从驱动节点中获取需要的私有信息。 130 1312. 根据私有信息,生成文件系统的根节点。 132 133 134## 适配Lookup接口 135 136Lookup是查找文件的接口,它的函数原型是: 137 138 139``` 140int (*Lookup)(struct Vnode *parent, const char *name, int len, struct Vnode **vnode); 141``` 142 143很好理解,就是从父节点parent开始,根据文件名name和文件名长度len,查找到对应的vnode返回给上层。 144 145这个接口适配起来思路很清晰,给了父节点的信息和文件名,实现从父目录中查询名字为name的文件这个功能,同样以JFFS2为例: 146 147 148``` 149int VfsJffs2Lookup(struct Vnode *parentVnode, const char *path, int len, struct Vnode **ppVnode) 150{ 151 int ret; 152 struct Vnode *newVnode = NULL; 153 struct jffs2_inode *node = NULL; 154 struct jffs2_inode *parentNode = NULL; 155 156 LOS_MuxLock(&g_jffs2FsLock, (uint32_t)JFFS2_WAITING_FOREVER); 157 158 /* 首先从private data中提取父节点的信息 */ 159 parentNode = (struct jffs2_inode *)parentVnode->data; 160 161 /* 然后查询得到目标节点的信息,注意这里调用的jffs2_lookup是jffs2本身的查询函数 */ 162 node = jffs2_lookup(parentNode, (const unsigned char *)path, len); 163 if (!node) { 164 LOS_MuxUnlock(&g_jffs2FsLock); 165 return -ENOENT; 166 } 167 168 /* 接着先校验一下查找到的目标是否已经有现成的vnode了,这里对应之前提到的VfsHashInsert */ 169 (void)VfsHashGet(parentVnode->originMount, node->i_ino, &newVnode, NULL, NULL); 170 LOS_MuxUnlock(&g_jffs2FsLock); 171 if (newVnode) { 172 newVnode->parent = parentVnode; 173 *ppVnode = newVnode; 174 return 0; 175 } 176 177 /* 如果vnode不存在,就新生成一个vnode,并填写相关信息 */ 178 ret = VnodeAlloc(&g_jffs2Vops, &newVnode); 179 if (ret != 0) { 180 PRINT_ERR("%s-%d, ret: %x\n", __FUNCTION__, __LINE__, ret); 181 (void)jffs2_iput(node); 182 LOS_MuxUnlock(&g_jffs2FsLock); 183 return ret; 184 } 185 186 Jffs2SetVtype(node, newVnode); 187 newVnode->fop = parentVnode->fop; 188 newVnode->data = node; 189 newVnode->parent = parentVnode; 190 newVnode->originMount = parentVnode->originMount; 191 newVnode->uid = node->i_uid; 192 newVnode->gid = node->i_gid; 193 newVnode->mode = node->i_mode; 194 195 /* 同时不要忘记将新生成的vnode插入hashtable中 */ 196 (void)VfsHashInsert(newVnode, node->i_ino); 197 198 *ppVnode = newVnode; 199 200 LOS_MuxUnlock(&g_jffs2FsLock); 201 return 0; 202} 203``` 204 205总结: 206 2071. 从父节点获取私有数据; 208 2092. 根据私有信息查询到目标文件的私有数据; 210 2113. 通过目标文件的私有数据生成目标Vnode。 212 213 214## 适配总结和注意事项 215 216通过上面两个接口的适配,其实可以发现一个规律,不管是什么接口,基本都遵循下面的适配步骤: 217 2181. 通过入参的vnode获取文件系统所需的私有数据。 219 2202. 使用私有数据完成接口的功能。 221 2223. 将结果包装成vnode或接口要求的其他返回格式,返回给上层。 223 224核心的逻辑其实在使用私有数据完成接口的功能,这些接口都是些文件系统的通用功能,文件系统在移植前本身应该都有相应实现,所以关键是归纳总结出文件系统所需的私有数据是什么,将其存储在vnode中,供之后使用。一般情况下,私有数据的内容是可以唯一定位到文件在存储介质上位置的信息,大部分文件系统本身都会有类似数据结构可以直接使用,比如JFFS2的inode数据结构。 225 226>  **注意:** 227> 1. 访问文件时,不一定会调用文件系统中的Lookup接口,仅在上层的路径缓存失效时才会调用到。 228> 229> 2. 通过VfsHashGet找到了已经存在的Vnode,不要直接将其作为结果返回,其储存的信息可能已经失效,请更新相应字段后再返回。 230> 231> 3. Vnode会根据内存占用在后台自动释放,需要持久保存的信息,不要只保存在Vnode中。 232> 233> 4. Reclaim接口在Vnode释放时会自动调用,请在这个接口中释放私有数据中的资源。 234