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