by fanxiushu 2018-05-12 转载或引用请注明原始作者。
xFsRedir是windows平台下的分布式网络文件系统程序。
这个软件已经持续比较长的时间了,最近更新了部分功能。
完整版本安装和使用请查阅如下链接:
https://blog.csdn.net/fanxiushu/article/details/53821943
GITHUB上最新程序下载地址:
https://github.com/fanxiushu/xFsRedir
CSDN上最新版本下载地址:
更新的内容包括如下几个方面:
1,重写了虚拟磁盘镜像文件存储方式,以前的版本只是简单的利用稀疏文件来存储虚拟磁盘镜像。
2,在第一条的基础上,扩展实现了内存虚拟磁盘,从而给xFsRedir实现内存目录提供基础。
同时也实现了重定向到本地任意一个目录。
3,删除了百度网盘的接口,替代成微软的OneDrive云盘接口。
国内这些云盘接口像昙花一样,
花了时间查了他们的接口资料好不容易实现了,没多久结果又不能用了,因此懒得去折腾,
还不如实现能提供稳定公开接口的比如OneDrive等云盘,
本想再实现Google,DropBox,亚马逊等云盘接口,可惜在中国境内要么被墙,要么速度慢的要死。
xFsRedir本身并不是做这些网盘的客户端,xFsRedir鼓励你建立自己的私有云存储。
xFsRedir提供了尽量多的网络文件传输协议帮助你建立私有云存储。
4,软件配置界面做了些修改,配置界面对高分屏的电脑屏幕提供了支持。
5,修改了其他一些BUG。
xFsRedir是在windows平台下实现目录重定向,
也就是把多个异构的网络文件系统集中到windows的目录中进行访问,如同访问windows的本地文件系统一样。
xFsRedir运行效果如下图(WIN10平台):
如上图所述,非常热闹。
各种系统的文件目录都被重定向到 FXS-VDISK的虚拟磁盘中。
FXS-VDISK是xFsRedir创建的一个虚拟磁盘,磁盘被格式化成NTFS文件系统,然后在FXS-VDISK中实现目录重定向。
CentOS目录指向CentOS系统(linux的一个分支),github是我的github账户目录,iPhone是iPhone手机共享目录,
LocalDir是本地目录,就是把随意的某个本地目录重定向到另一个本地目录。
memdir是内存目录,就是文件目录放到内存中。
openwrt是嵌入式路由操作系统openwrt对应的系统目录。
macOS是macOS系统的目录,OneDrive和OneDrive2是OneDrive云盘注册的两个账号。
w-FTP,PC1,SMB,坚果云等目录是使用通用协议的重定向。缺了Andriod系统,因为手上没Andriod手机。
这些重定向的目录都等同于本地文件系统,不是资源管理器的插件扩展,也不是同步软件的同步目录,
所有的文件操作都被实时的发送给远端的文件目录处理,xFsRedir原理可查阅我之前写的“过滤驱动实现目录重定向”的文章。
一,重写虚拟磁盘镜像存储方式:
xFsRedir提供了创建新盘符的功能,以便把重定向目录扩展到新盘符的某个目录中。
如上图所示,FXS-VDISK(K:) 就是xFsRedir驱动创建的新的K盘符。
新建的虚拟磁盘是采用类似FileDisk驱动框架实现的,
当把这个虚拟磁盘格式化成NTFS,FAT等文件系统的时候,格式化等数据全部被写入到镜像文件中。
最初设计xFsRedir创建新盘符的功能的时候,采用的是自定义的文件系统,根据目录重定向的特点,所有请求实际都被重定向了。
也就是没有任何实际数据写入到虚拟磁盘中,因此在自定义文件系统中也就没必要引入镜像文件的概念了。
但是开发完成之后,发现某些奇怪的软件,尤其是win10平台下的UWP软件,比如视频播放器,压根就不认这个自定义文件系统,
(当然这其中的原因可能是没处理好某些特殊磁盘命令造成的。)
而且xFsRedir的驱动核心部分就是过滤驱动拦截某个目录的文件请求,
定义了新文件系统,结果还是要做新文件系统下的某个目录重定向。也就是这样做显得比较罗嗦了。
因此放弃了自定义文件系统(其实xfs_redir.sys驱动中依然保留了自定义文件系统,只是我没把它导出到应用层而已),
对虚拟磁盘格式化为NTFS文件系统,这个时候就必须引入镜像文件。
已经发布的xFsRedir版本对镜像文件只是简单的利用稀疏文件来保存虚拟磁盘数据。
所谓稀疏文件,就是文件可以很大,但是不一定都需要占用文件大小这么大的空间,
因为可能只有文件很少一部分写入了真实数据,其他都是空的。
比如分配一个100GB的虚拟磁盘空间,需要的镜像文件大小必须是100G,这个时候,采用稀疏文件格式,
镜像文件虽然100G,但是实际只占用磁盘上了其中几十MB字节的数据。
本来这种简单使用稀疏文件方式,在之前的多台电脑上运行得比较良好,
自从换了台新电脑MacBookPro,而且安装Windows10之后,利用xFsRedir创建新盘符,机器就蓝屏死机,
调试发现是镜像读写文件问题,也不想去找具体原因了,打算修改存储算法,采用其他办法来替代稀疏文件方式。
这也是本次发布新版本xFsRedir的原动力。
至于如何修改,达到怎样的效果。
想到了vmware,它生成的vmdk磁盘文件,属于动态增长方式,也就是开始创建一个虚拟机的时候,
vmdk磁盘文件很小,随着虚拟机的使用,vmdk磁盘文件慢慢增长,当然增大了之后就不会变小了。
我们的的虚拟磁盘的镜像文件也要达到这种效果。
我们采用尽量简单的数据结构来达到这种效果。
在镜像文件的前4*512K字节以4字节(也就是一个int长度)为单位存储真正虚拟磁盘的磁盘数据块的偏移值,
以4M字节为一个磁盘数据块。
然后接下来开辟4K字节存储其他头信息,
再接下来就是以4M字节为单位,存储真正的数据内容。
我们可以简单算一下,这种存储方式能最大达到多大虚拟磁盘容量。
前4*512K字节以4字节为单位存储偏移,也就是可以存储 512K个偏移,而每个偏移数据块是4M大小,因此总容量是:
512KB * 4MB = 2TB
也就是最大 2TB 的虚拟磁盘容量,这正好是使用MBR存储方式单个分区能达到的最大容量,
因为我在虚拟磁盘使用的是MBR分区而不是GPT分区。
接下来要解决虚拟磁盘的数据块与镜像文件这种存储结构的数据块的映射关系。
镜像文件头部 4*512K + 4K(以下简称IMG_HEADER)之后是 4M 数据块(以下简称IMG_BLOCK) 组成的数组。
IMG_BLOCK在本镜像中的偏移很好定位,比如第一个IMG_BLOCK偏移就是0,第二个是1,以此类推。
IMG_BLOCK在真正的虚拟磁盘的偏移(也就是这个4M数据块在虚拟磁盘处于哪个位置),
则是根据 IMG_HEADER的4*512K数据区给出。
比如在本地镜像中第一个IMG_BLOCK,对应在虚拟磁盘中的偏移根据IMG_HEADER的 0-4字节给出,第二个IMG_BLOCK则是4-8字节给出。
而在内存中,使用MAP结构来保存这种映射关系,以便能迅速定位。
MAP的key是在IMG_BLOCK在虚拟磁盘中的真实偏移,value对应在本镜像中的偏移。
MAP结构在初始化虚拟磁盘的时候,从本地镜像的IMG_HEADER的前4*512K字节读取映射关系并存储到MAP结构中。
当虚拟磁盘驱动发起IRP_MJ_READ/IRP_MJ_WRITE请求的时候,根据请求的offset和length计算出
这个请求的数据块属于哪个IMG_BLOCK内或者包含哪些IMG_BLOCK的偏移值,然后从MAP结构查找对应在镜像文件中的位置,
接着就是ZwReadFile/ZwWriteFile读写镜像文件内容了。
大致伪代码如下:
//计算出偏移属于哪个IMG_BLOCK中,i_virtual_block对应起始值
ULONG i_virtual_block = (ULONG)(offset.QuadPart /IMG_BLOCK_SIZE);
LONG in_off = (LONG)(offset.QuadPart % IMG_BLOCK_SIZE); //计算在起始IMG_BLOCK内的偏移,字节为单位。
LONG in_len = min( (IMG_BLOCK_SIZE - in_off),length); //计算在起始IMG_BLOCK内长度,字节为单位。
// vdisk_image_read_write_block执行真正的数据块读写,其中i_virtual_block 是在虚拟磁盘中的IMG_BLOCK偏移。
// len 作为返回值,真正读写的字节数。in_off是在当前IMG_BLOCK中的字节偏移。
status = vdisk_image_readwrite_block(img,is_read,buf,i_virtual_block,in_off,&len);//
。。。。
length -= in_len; //length是读写总长度
buf += in_len; //buf是读写缓冲
while (length > 0) {
len = min(IMG_BLOCK_SIZE,length);
i_virtual_block++; //next block, 下一个数据块
// DPT("2-- read/write block.\n");
status = vdisk_image_readwrite_block(img,&len); //
。。。// 其他处理
///继续执行下一个IMG_BLOCK读写,直到出错或者读写完成。
length -= len;
buf += len;
}
上面代码片段中vdisk_image_readwrite_block函数的大致流程如下:
首先根据i_virtual_block从MAP结构中查询对应的本地镜像的偏移值,如果没找到,说明没有建立本地镜像和虚拟磁盘对应块的关系,
这个时候就新建一个对应块关系。
如果找到,则根据查找到的本地镜像的IMG_BLOCK偏移,
计算出在镜像文件中的实际位置,调用ZwReadFile/ZwWriteFile发起文件读写操作。
经过这样设计之后的虚拟磁盘镜像,已经就可以达到vmware中的那样的效果,一开始格式化NTFS文件系统的虚拟磁盘,
占100M左右的镜像文件大小,之后慢慢朝这个虚拟磁盘添加文件,镜像的大小才会慢慢变大。
当然变大了就不会缩小,除非删除虚拟磁盘和对应的镜像文件,重新格式化。
采用如上的数据结构存储虚拟磁盘数据,发现很容易扩展到虚拟内存磁盘,因此也就顺便实现了虚拟内存磁盘。
虚拟内存磁盘和写到镜像文件的虚拟磁盘除了数据存储位置不同(一个存储到内存,一个存储到文件),其他没什么区别。
跟镜像文件虚拟磁盘一样,首先固定一个数据块大小,我们使用的是16M为一个块大小(以下简称MEM_BLOCK)。
我们依然使用MAP结构来保存虚拟磁盘的MEM_BLOCK数据块偏移,不过MAP的value是对应的内存地址,
因为我们使用的是 MmAllocatePagesForMdl 系统函数来分配内核内存,因此实际上MAP的Value保存的是MDL指针。
跟上面的文件镜像磁盘一样,
当内存虚拟磁盘驱动发起IRP_MJ_READ/IRP_MJ_WRITE请求的时候,根据请求的offset和length计算出哪些MEM_BLOCK,
然后根据MEM_BLOCK偏移,从 MAP结构查找对应的数据块的MDL,
如果找到则从MDL读写对应的数据,其实就是计算出具体位置,从MDL链中获取对应内存位置,调用RtlCopyMemory复制数据。
如果没找到,说明对应偏移值的MEM_BLOCK没在内存中存在,
我们必须创建一个16M的MEM_BLOCK并且保存到MAP结构中。
系统函数 MmAllocatePagesForMdl 有个特点,就是如果分配16M大小内存,不一定就能获得16M的内存,
可能只获取到一个很小内存的MDL,为了一次分配达到16M,代码按照如下方式实现:
static NTSTATUS
AllocateFixedBufferSizeMDL(vdisk_image_t* img,image_block_t* block)
{
const PHYSICAL_ADDRESS physical_address_zero = { 0,0 };
const PHYSICAL_ADDRESS physical_address_max64 = { ULONG_MAX,ULONG_MAX };
////
LONG length = img->fixed_block_size; // 16M, MEM_BLOCK_SIZE
while (length > 0) {
PMDL mdl = MmAllocatePagesForMdl(physical_address_zero,
physical_address_max64,physical_address_zero,length );
if (!mdl) {
DPT("*** MmAllocatePagesForMdl error NULL.\n");
FreeMdlChain(block->mdl);//释放 mdl 数据链。
return STATUS_INSUFFICIENT_RESOURCES;
}
//////
mdl->Next = block->mdl;
block->mdl = mdl;
LONG cc = MmGetMdlByteCount(mdl);
length -= cc;
}
return STATUS_SUCCESS;
}
其中 image_block_t就是MAP存储单元,mdl对应的就是MDL,如上代码,mdl是个链,包含多个MDL描述符。
所有这些MDL组成的内存总长度就是16M。
实现了虚拟内存磁盘,我们用他来做什么呢?
其实就是文章开头的更新部分提到的,用来实现内存目录。
内存目录是什么呢?我们大部分目录都是存储在硬盘上,电脑关机之后,数据也不会丢失。
内存目录是保存到内存中,关机之后,数据就会丢失。
就像linux平台下的/tmp这类的目录,是把数据保存到内存的目录。
根据windows的特点,是很难实现内存方式的目录的,顶多模拟出一个虚拟内存磁盘,然后挂载出一个新的盘符来使用内存文件系统。
而不能像linux那样可以挂载到任意目录。
这个时候就需要借用xFsRedir程序的功能,实现原理也不复杂。
首先实现一个虚拟内存磁盘,格式化为NTFS系统,然后把某个需要重定向作为内存目录的比如 d:\memdir,经过xfsRedir驱动,
全部重定向到这个虚拟内存磁盘上。这就达到要求了。有兴趣可下载最新版本的xFsRedir程序来使用它的内存目录。
二, 添加 OneDrive 云盘。
把早已没用的百度云盘接口删除了,想着得要再实现一个类似的公网云盘,找来找去,
国内没一个像样的公开接口的云盘。甚至都没找到有公开接口的云盘。
因此只好想到了微软的OneDrive,OneDrive的服务器不在中国境内,访问起来速度还是比较慢。
但基本上能接受,而且再看windows10自带的OneDrive客户端同步软件,有些时候,同步起来速度还是挺快。
OneDrive 的开发接口文档请查阅如下链接:
https://docs.microsoft.com/zh-cn/onedrive/developer/rest-api/
这是 REST-API 方式的接口,看他们的文档应该能很好理解。
有兴趣可以在GITHUB下载我实现的OneDrive接口,链接如下:
同时把它移植到了 linux,iOS,macOS,等平台。
这是为了给xFsRedir提供类似文件系统接口,
实现的接口大致如下,
onedrive_find_open/onedrive_find_next,类似 FindFirstFile和FindNext函数,
onedrive_stat,类似GetFileInformationByHandle等获取文件属性的函数,
onedrive_mkdir, 类似CreateDirectory创建目录函数
onedrive_delfile,类似DeleteFile和RemoveDirectory等删除函数。
onedrive_upfile,更新整个文件到服务端,这个是为了解决不支持随机写的协议必须实现的函数。
onedrive_offset_read, 类似ReadFile等函数,
本来还应该有 onedrive_offset_write 函数,可惜 OneDrive协议并不支持随机写(目前的云盘都这德行,几乎都不支持随机写)
因此为了解决随机写的问题,xFsRedir专门实现了本地文件缓存目录来处理不支持随机写的协议,
除了这里所说的OneDrive不支持随机写,还包括FTP协议,WebDav协议,GITHUB都不支持。
详细内容下面介绍xFsRedir的缓存原理的时候会介绍。
xFsRedir实现机制跟windows10自带的OneDrive同步软件不同,OneDrive是属于目录同步方式,
就是任何文件其实都是被下载到本地来来处理,如果文件内容被修改了之后,会被及时的同步到服务器端。
这是目前大部分云盘客户端同步软件的处理方式。
xFsRedir本身就是个文件系统,它对文件数据的修改和读取,都是实时的从服务端读写的。
xFsRedir不会在本地留下任何文件痕迹。xFsRedir这种网络文件系统访问方式,如果访问量太多,容易给服务端造成一定压力。
因此如果你的网络到OneDrive服务端并不理想,访问OneDrive实际是比较慢,而且如果由于网络问题,很容易让电脑卡死。
使用xFsRedir来访问OneDrive的时候必须注意这个问题。
但是由于xFsRedir是支持Windows系列的,它支持 WINXP系统,这样就可以在WINXP系统下使用OneDrive了。
不过在WINXP下获取OneDrive的刷新令牌有些麻烦,基本上微软封杀了它的WINXP亲儿子,验证网站不再支持winxp系统的浏览器。
这个时候,你可以在win7以上系统利用xFsRedir获取到OneDrive的刷新令牌,再把刷新令牌复制到winXP中的xFsRedir配置文件中。
如下图是在WinXP系统下运行 xFsRedir的效果:
三,xFsRedir缓存原理和解决不支持随机写的协议采用的办法。
xFsRedir在驱动里实现了缓存,其实是利用windows操作系统的缓存机制进行的缓存,也就是windows那套Cc前缀的函数集合,
在以前介绍 过滤驱动实现目录重定向的原理的时候,提到过Cc缓存机制。
其实Cc缓存机制已经能极大可能的处理了数据缓存。
除此之外,xFsRedir在应用层只简单缓存了文件属性信息(比如大小,修改时间,访问时间等等),
而且采用超时策略,也就是超过一定时间之后,就会再次从服务端获取文件属性信息。
读取子目录结构,读取和写入文件数据块等,都是实时的从服务端获取。
其实这里边有可以改进的地方,就是读取子目录结构信息,可以做内存缓存,这样下次读取相同目录结构的时候,就直接从内存获取。
不过这里有个问题,就是如果服务端的文件或目录发生了改变,是没法及时获取变化信息的。
除非有种通知机制,服务端发生变化的时候,及时通知给xFsRedir客户端。
可惜不管是 NFS,SMB, SFTP,还是FTP等,都没有找到类似的机制。
而自己当初自己设计PRIVATE私有协议的时候,也没考虑这点,因为其实嫌麻烦。
在带宽足够的网络环境,使用xFsRedir重定向的目录,跟访问本地文件系统的速度,其实没啥区别。
因此后来也就没再考虑缓存子目录结构和实现通知机制。
只是遇到了 OneDrive,GITHUB等,访问目录结构时候的像蜗牛一样的速度,才有这么一个给目录做缓存的想法。
不过依然是嫌麻烦,而且没找到OneDrive,GITHUB等有简单的通知机制而打算放弃。
不过有个大问题,嫌麻烦也必须解决,就是有些协议不支持随机写功能。
最早遇到就是FTP协议。
所谓随机写,就是比如你有一个100字节的文件,想写新数据到这个文件,
但是文件是从文件开始的300 字节之后的位置,再写入200字节的数据。
写入成功之后,文件总长度变成了 500字节。
一般的操作流程
SetFilePointer 设置 300 偏移值,然后
WriteFile, 从 300的位置开始写200字节的数据。
这就是一般的文件系统必须能处理的随机写的功能。
但是FTP协议,只有把数据附加到文件末尾的功能,不支持随机写。
再后来遇到的 WEBDAV协议, GITHUB,OneDrive等都是不支持随机写的。
为了解决这个麻烦,只好引入了本地缓存文件的办法,我采用如下的办法来解决问题。
也就是把文件先写到本地缓存,直到发生CloseHandle关闭文件的时候,才把整个文件Update到服务端。
看起来原理比较简单,实现起来却不算简单。
而且是在 CloseHandle,也就是发送 IRP_MJ_CLEANUP请求里边 update 文件到服务端 ,
如果文件比较大,而且网络又不好,阻塞就挺严重,而且这种阻塞是全局的,会造成整个操作系统的文件系统阻塞。
一不小心,容易造成电脑卡死。暂时没想到更好的缓存策略。
这个缓存办法,以前的版本已经实现了,这次新版本只是修改了其中某些BUG。
鉴于以上的缓存办法,推荐你尽量使用PRIVATE私有协议,SMB协议,SFTP协议,NFS协议。
这些协议都是完整支持随机写功能的,所有读写请求,都是实时跟服务端交互,不会在本地做任何文件缓存。
如果你要使用FTP协议, OneDrive等不支持随机写功能写文件的时候,请确保你的网络速度,并且写的文件不宜过大。
新版本的配置界面如下图所示:
支持的通讯协议如下:
目前支持10种通讯协议:
1,PRIVATE私有协议,
2,NFS协议
3, SMB协议,这就是windows的文件共享。
4,SFTP,这个是SSH登录到linux的文件传输协议
5,FTP,
6,WEBDAV,
7,Memory Directory 这个是内存目录,把文件存储到内存中
8,Local Directory,这个功能是把电脑的任意一个目录重定向到另外一个目录。
9,OneDrive, 这个是微软的OneDrive云盘。
10,GITHUB,这个是源代码共享网站。与普通使用者没啥关系。
如果你的电脑安装过以前的xFsRedir版本,请先完整卸载驱动,然后重启电脑,再次打开旧版xFsRedir,
确保”状态“里边的驱动和服务都被删除了,才能替换成新版本程序。
请关注稍后公布到CSDN上和GITHUB上的xFsRedir程序。
GITHUB上的下载地址:
https://github.com/fanxiushu/xFsRedir
原文链接:https://www.f2er.com/windows/373218.htmlxFsRedir是windows平台下的分布式网络文件系统程序。
这个软件已经持续比较长的时间了,最近更新了部分功能。
完整版本安装和使用请查阅如下链接:
https://blog.csdn.net/fanxiushu/article/details/53821943
GITHUB上最新程序下载地址:
https://github.com/fanxiushu/xFsRedir
CSDN上最新版本下载地址:
https://download.csdn.net/download/fanxiushu/10416862
更新的内容包括如下几个方面:
1,重写了虚拟磁盘镜像文件存储方式,以前的版本只是简单的利用稀疏文件来存储虚拟磁盘镜像。
2,在第一条的基础上,扩展实现了内存虚拟磁盘,从而给xFsRedir实现内存目录提供基础。
同时也实现了重定向到本地任意一个目录。
3,删除了百度网盘的接口,替代成微软的OneDrive云盘接口。
国内这些云盘接口像昙花一样,
花了时间查了他们的接口资料好不容易实现了,没多久结果又不能用了,因此懒得去折腾,
还不如实现能提供稳定公开接口的比如OneDrive等云盘,
本想再实现Google,DropBox,亚马逊等云盘接口,可惜在中国境内要么被墙,要么速度慢的要死。
xFsRedir本身并不是做这些网盘的客户端,xFsRedir鼓励你建立自己的私有云存储。
xFsRedir提供了尽量多的网络文件传输协议帮助你建立私有云存储。
4,软件配置界面做了些修改,配置界面对高分屏的电脑屏幕提供了支持。
5,修改了其他一些BUG。
xFsRedir是在windows平台下实现目录重定向,
也就是把多个异构的网络文件系统集中到windows的目录中进行访问,如同访问windows的本地文件系统一样。
xFsRedir运行效果如下图(WIN10平台):
如上图所述,非常热闹。
各种系统的文件目录都被重定向到 FXS-VDISK的虚拟磁盘中。
FXS-VDISK是xFsRedir创建的一个虚拟磁盘,磁盘被格式化成NTFS文件系统,然后在FXS-VDISK中实现目录重定向。
CentOS目录指向CentOS系统(linux的一个分支),github是我的github账户目录,iPhone是iPhone手机共享目录,
LocalDir是本地目录,就是把随意的某个本地目录重定向到另一个本地目录。
memdir是内存目录,就是文件目录放到内存中。
openwrt是嵌入式路由操作系统openwrt对应的系统目录。
macOS是macOS系统的目录,OneDrive和OneDrive2是OneDrive云盘注册的两个账号。
w-FTP,PC1,SMB,坚果云等目录是使用通用协议的重定向。缺了Andriod系统,因为手上没Andriod手机。
这些重定向的目录都等同于本地文件系统,不是资源管理器的插件扩展,也不是同步软件的同步目录,
所有的文件操作都被实时的发送给远端的文件目录处理,xFsRedir原理可查阅我之前写的“过滤驱动实现目录重定向”的文章。
一,重写虚拟磁盘镜像存储方式:
xFsRedir提供了创建新盘符的功能,以便把重定向目录扩展到新盘符的某个目录中。
如上图所示,FXS-VDISK(K:) 就是xFsRedir驱动创建的新的K盘符。
新建的虚拟磁盘是采用类似FileDisk驱动框架实现的,
在 https://blog.csdn.net/fanxiushu/article/details/9903123 (磁盘驱动与虚拟磁盘Miniport驱动之一)
filedisk提供了以镜像文件方式代替真正的磁盘扇区,简单的说虚拟磁盘最终存储的是一个镜像文件而不是真实的磁盘。当把这个虚拟磁盘格式化成NTFS,FAT等文件系统的时候,格式化等数据全部被写入到镜像文件中。
最初设计xFsRedir创建新盘符的功能的时候,采用的是自定义的文件系统,根据目录重定向的特点,所有请求实际都被重定向了。
也就是没有任何实际数据写入到虚拟磁盘中,因此在自定义文件系统中也就没必要引入镜像文件的概念了。
但是开发完成之后,发现某些奇怪的软件,尤其是win10平台下的UWP软件,比如视频播放器,压根就不认这个自定义文件系统,
(当然这其中的原因可能是没处理好某些特殊磁盘命令造成的。)
而且xFsRedir的驱动核心部分就是过滤驱动拦截某个目录的文件请求,
定义了新文件系统,结果还是要做新文件系统下的某个目录重定向。也就是这样做显得比较罗嗦了。
因此放弃了自定义文件系统(其实xfs_redir.sys驱动中依然保留了自定义文件系统,只是我没把它导出到应用层而已),
对虚拟磁盘格式化为NTFS文件系统,这个时候就必须引入镜像文件。
已经发布的xFsRedir版本对镜像文件只是简单的利用稀疏文件来保存虚拟磁盘数据。
所谓稀疏文件,就是文件可以很大,但是不一定都需要占用文件大小这么大的空间,
因为可能只有文件很少一部分写入了真实数据,其他都是空的。
比如分配一个100GB的虚拟磁盘空间,需要的镜像文件大小必须是100G,这个时候,采用稀疏文件格式,
镜像文件虽然100G,但是实际只占用磁盘上了其中几十MB字节的数据。
本来这种简单使用稀疏文件方式,在之前的多台电脑上运行得比较良好,
自从换了台新电脑MacBookPro,而且安装Windows10之后,利用xFsRedir创建新盘符,机器就蓝屏死机,
调试发现是镜像读写文件问题,也不想去找具体原因了,打算修改存储算法,采用其他办法来替代稀疏文件方式。
这也是本次发布新版本xFsRedir的原动力。
至于如何修改,达到怎样的效果。
想到了vmware,它生成的vmdk磁盘文件,属于动态增长方式,也就是开始创建一个虚拟机的时候,
vmdk磁盘文件很小,随着虚拟机的使用,vmdk磁盘文件慢慢增长,当然增大了之后就不会变小了。
我们的的虚拟磁盘的镜像文件也要达到这种效果。
我们采用尽量简单的数据结构来达到这种效果。
在镜像文件的前4*512K字节以4字节(也就是一个int长度)为单位存储真正虚拟磁盘的磁盘数据块的偏移值,
以4M字节为一个磁盘数据块。
然后接下来开辟4K字节存储其他头信息,
再接下来就是以4M字节为单位,存储真正的数据内容。
我们可以简单算一下,这种存储方式能最大达到多大虚拟磁盘容量。
前4*512K字节以4字节为单位存储偏移,也就是可以存储 512K个偏移,而每个偏移数据块是4M大小,因此总容量是:
512KB * 4MB = 2TB
也就是最大 2TB 的虚拟磁盘容量,这正好是使用MBR存储方式单个分区能达到的最大容量,
因为我在虚拟磁盘使用的是MBR分区而不是GPT分区。
接下来要解决虚拟磁盘的数据块与镜像文件这种存储结构的数据块的映射关系。
镜像文件头部 4*512K + 4K(以下简称IMG_HEADER)之后是 4M 数据块(以下简称IMG_BLOCK) 组成的数组。
IMG_BLOCK在本镜像中的偏移很好定位,比如第一个IMG_BLOCK偏移就是0,第二个是1,以此类推。
IMG_BLOCK在真正的虚拟磁盘的偏移(也就是这个4M数据块在虚拟磁盘处于哪个位置),
则是根据 IMG_HEADER的4*512K数据区给出。
比如在本地镜像中第一个IMG_BLOCK,对应在虚拟磁盘中的偏移根据IMG_HEADER的 0-4字节给出,第二个IMG_BLOCK则是4-8字节给出。
而在内存中,使用MAP结构来保存这种映射关系,以便能迅速定位。
MAP的key是在IMG_BLOCK在虚拟磁盘中的真实偏移,value对应在本镜像中的偏移。
MAP结构在初始化虚拟磁盘的时候,从本地镜像的IMG_HEADER的前4*512K字节读取映射关系并存储到MAP结构中。
当虚拟磁盘驱动发起IRP_MJ_READ/IRP_MJ_WRITE请求的时候,根据请求的offset和length计算出
这个请求的数据块属于哪个IMG_BLOCK内或者包含哪些IMG_BLOCK的偏移值,然后从MAP结构查找对应在镜像文件中的位置,
接着就是ZwReadFile/ZwWriteFile读写镜像文件内容了。
大致伪代码如下:
//计算出偏移属于哪个IMG_BLOCK中,i_virtual_block对应起始值
ULONG i_virtual_block = (ULONG)(offset.QuadPart /IMG_BLOCK_SIZE);
LONG in_off = (LONG)(offset.QuadPart % IMG_BLOCK_SIZE); //计算在起始IMG_BLOCK内的偏移,字节为单位。
LONG in_len = min( (IMG_BLOCK_SIZE - in_off),length); //计算在起始IMG_BLOCK内长度,字节为单位。
// vdisk_image_read_write_block执行真正的数据块读写,其中i_virtual_block 是在虚拟磁盘中的IMG_BLOCK偏移。
// len 作为返回值,真正读写的字节数。in_off是在当前IMG_BLOCK中的字节偏移。
status = vdisk_image_readwrite_block(img,is_read,buf,i_virtual_block,in_off,&len);//
。。。。
length -= in_len; //length是读写总长度
buf += in_len; //buf是读写缓冲
while (length > 0) {
len = min(IMG_BLOCK_SIZE,length);
i_virtual_block++; //next block, 下一个数据块
// DPT("2-- read/write block.\n");
status = vdisk_image_readwrite_block(img,&len); //
。。。// 其他处理
///继续执行下一个IMG_BLOCK读写,直到出错或者读写完成。
length -= len;
buf += len;
}
上面代码片段中vdisk_image_readwrite_block函数的大致流程如下:
首先根据i_virtual_block从MAP结构中查询对应的本地镜像的偏移值,如果没找到,说明没有建立本地镜像和虚拟磁盘对应块的关系,
这个时候就新建一个对应块关系。
如果找到,则根据查找到的本地镜像的IMG_BLOCK偏移,
计算出在镜像文件中的实际位置,调用ZwReadFile/ZwWriteFile发起文件读写操作。
经过这样设计之后的虚拟磁盘镜像,已经就可以达到vmware中的那样的效果,一开始格式化NTFS文件系统的虚拟磁盘,
占100M左右的镜像文件大小,之后慢慢朝这个虚拟磁盘添加文件,镜像的大小才会慢慢变大。
当然变大了就不会缩小,除非删除虚拟磁盘和对应的镜像文件,重新格式化。
采用如上的数据结构存储虚拟磁盘数据,发现很容易扩展到虚拟内存磁盘,因此也就顺便实现了虚拟内存磁盘。
虚拟内存磁盘和写到镜像文件的虚拟磁盘除了数据存储位置不同(一个存储到内存,一个存储到文件),其他没什么区别。
跟镜像文件虚拟磁盘一样,首先固定一个数据块大小,我们使用的是16M为一个块大小(以下简称MEM_BLOCK)。
我们依然使用MAP结构来保存虚拟磁盘的MEM_BLOCK数据块偏移,不过MAP的value是对应的内存地址,
因为我们使用的是 MmAllocatePagesForMdl 系统函数来分配内核内存,因此实际上MAP的Value保存的是MDL指针。
跟上面的文件镜像磁盘一样,
当内存虚拟磁盘驱动发起IRP_MJ_READ/IRP_MJ_WRITE请求的时候,根据请求的offset和length计算出哪些MEM_BLOCK,
然后根据MEM_BLOCK偏移,从 MAP结构查找对应的数据块的MDL,
如果找到则从MDL读写对应的数据,其实就是计算出具体位置,从MDL链中获取对应内存位置,调用RtlCopyMemory复制数据。
如果没找到,说明对应偏移值的MEM_BLOCK没在内存中存在,
我们必须创建一个16M的MEM_BLOCK并且保存到MAP结构中。
系统函数 MmAllocatePagesForMdl 有个特点,就是如果分配16M大小内存,不一定就能获得16M的内存,
可能只获取到一个很小内存的MDL,为了一次分配达到16M,代码按照如下方式实现:
static NTSTATUS
AllocateFixedBufferSizeMDL(vdisk_image_t* img,image_block_t* block)
{
const PHYSICAL_ADDRESS physical_address_zero = { 0,0 };
const PHYSICAL_ADDRESS physical_address_max64 = { ULONG_MAX,ULONG_MAX };
////
LONG length = img->fixed_block_size; // 16M, MEM_BLOCK_SIZE
while (length > 0) {
PMDL mdl = MmAllocatePagesForMdl(physical_address_zero,
physical_address_max64,physical_address_zero,length );
if (!mdl) {
DPT("*** MmAllocatePagesForMdl error NULL.\n");
FreeMdlChain(block->mdl);//释放 mdl 数据链。
return STATUS_INSUFFICIENT_RESOURCES;
}
//////
mdl->Next = block->mdl;
block->mdl = mdl;
LONG cc = MmGetMdlByteCount(mdl);
length -= cc;
}
return STATUS_SUCCESS;
}
其中 image_block_t就是MAP存储单元,mdl对应的就是MDL,如上代码,mdl是个链,包含多个MDL描述符。
所有这些MDL组成的内存总长度就是16M。
实现了虚拟内存磁盘,我们用他来做什么呢?
其实就是文章开头的更新部分提到的,用来实现内存目录。
内存目录是什么呢?我们大部分目录都是存储在硬盘上,电脑关机之后,数据也不会丢失。
内存目录是保存到内存中,关机之后,数据就会丢失。
就像linux平台下的/tmp这类的目录,是把数据保存到内存的目录。
根据windows的特点,是很难实现内存方式的目录的,顶多模拟出一个虚拟内存磁盘,然后挂载出一个新的盘符来使用内存文件系统。
而不能像linux那样可以挂载到任意目录。
这个时候就需要借用xFsRedir程序的功能,实现原理也不复杂。
首先实现一个虚拟内存磁盘,格式化为NTFS系统,然后把某个需要重定向作为内存目录的比如 d:\memdir,经过xfsRedir驱动,
全部重定向到这个虚拟内存磁盘上。这就达到要求了。有兴趣可下载最新版本的xFsRedir程序来使用它的内存目录。
二, 添加 OneDrive 云盘。
把早已没用的百度云盘接口删除了,想着得要再实现一个类似的公网云盘,找来找去,
国内没一个像样的公开接口的云盘。甚至都没找到有公开接口的云盘。
因此只好想到了微软的OneDrive,OneDrive的服务器不在中国境内,访问起来速度还是比较慢。
但基本上能接受,而且再看windows10自带的OneDrive客户端同步软件,有些时候,同步起来速度还是挺快。
OneDrive 的开发接口文档请查阅如下链接:
https://docs.microsoft.com/zh-cn/onedrive/developer/rest-api/
这是 REST-API 方式的接口,看他们的文档应该能很好理解。
有兴趣可以在GITHUB下载我实现的OneDrive接口,链接如下:
https://github.com/fanxiushu/onedrive-xfsredir
https://download.csdn.net/download/fanxiushu/10409573
同时把它移植到了 linux,iOS,macOS,等平台。
这是为了给xFsRedir提供类似文件系统接口,
实现的接口大致如下,
onedrive_find_open/onedrive_find_next,类似 FindFirstFile和FindNext函数,
onedrive_stat,类似GetFileInformationByHandle等获取文件属性的函数,
onedrive_mkdir, 类似CreateDirectory创建目录函数
onedrive_delfile,类似DeleteFile和RemoveDirectory等删除函数。
onedrive_upfile,更新整个文件到服务端,这个是为了解决不支持随机写的协议必须实现的函数。
onedrive_offset_read, 类似ReadFile等函数,
本来还应该有 onedrive_offset_write 函数,可惜 OneDrive协议并不支持随机写(目前的云盘都这德行,几乎都不支持随机写)
因此为了解决随机写的问题,xFsRedir专门实现了本地文件缓存目录来处理不支持随机写的协议,
除了这里所说的OneDrive不支持随机写,还包括FTP协议,WebDav协议,GITHUB都不支持。
详细内容下面介绍xFsRedir的缓存原理的时候会介绍。
xFsRedir实现机制跟windows10自带的OneDrive同步软件不同,OneDrive是属于目录同步方式,
就是任何文件其实都是被下载到本地来来处理,如果文件内容被修改了之后,会被及时的同步到服务器端。
这是目前大部分云盘客户端同步软件的处理方式。
xFsRedir本身就是个文件系统,它对文件数据的修改和读取,都是实时的从服务端读写的。
xFsRedir不会在本地留下任何文件痕迹。xFsRedir这种网络文件系统访问方式,如果访问量太多,容易给服务端造成一定压力。
因此如果你的网络到OneDrive服务端并不理想,访问OneDrive实际是比较慢,而且如果由于网络问题,很容易让电脑卡死。
使用xFsRedir来访问OneDrive的时候必须注意这个问题。
但是由于xFsRedir是支持Windows系列的,它支持 WINXP系统,这样就可以在WINXP系统下使用OneDrive了。
不过在WINXP下获取OneDrive的刷新令牌有些麻烦,基本上微软封杀了它的WINXP亲儿子,验证网站不再支持winxp系统的浏览器。
这个时候,你可以在win7以上系统利用xFsRedir获取到OneDrive的刷新令牌,再把刷新令牌复制到winXP中的xFsRedir配置文件中。
如下图是在WinXP系统下运行 xFsRedir的效果:
三,xFsRedir缓存原理和解决不支持随机写的协议采用的办法。
xFsRedir在驱动里实现了缓存,其实是利用windows操作系统的缓存机制进行的缓存,也就是windows那套Cc前缀的函数集合,
在以前介绍 过滤驱动实现目录重定向的原理的时候,提到过Cc缓存机制。
其实Cc缓存机制已经能极大可能的处理了数据缓存。
除此之外,xFsRedir在应用层只简单缓存了文件属性信息(比如大小,修改时间,访问时间等等),
而且采用超时策略,也就是超过一定时间之后,就会再次从服务端获取文件属性信息。
读取子目录结构,读取和写入文件数据块等,都是实时的从服务端获取。
其实这里边有可以改进的地方,就是读取子目录结构信息,可以做内存缓存,这样下次读取相同目录结构的时候,就直接从内存获取。
不过这里有个问题,就是如果服务端的文件或目录发生了改变,是没法及时获取变化信息的。
除非有种通知机制,服务端发生变化的时候,及时通知给xFsRedir客户端。
可惜不管是 NFS,SMB, SFTP,还是FTP等,都没有找到类似的机制。
而自己当初自己设计PRIVATE私有协议的时候,也没考虑这点,因为其实嫌麻烦。
在带宽足够的网络环境,使用xFsRedir重定向的目录,跟访问本地文件系统的速度,其实没啥区别。
因此后来也就没再考虑缓存子目录结构和实现通知机制。
只是遇到了 OneDrive,GITHUB等,访问目录结构时候的像蜗牛一样的速度,才有这么一个给目录做缓存的想法。
不过依然是嫌麻烦,而且没找到OneDrive,GITHUB等有简单的通知机制而打算放弃。
不过有个大问题,嫌麻烦也必须解决,就是有些协议不支持随机写功能。
最早遇到就是FTP协议。
所谓随机写,就是比如你有一个100字节的文件,想写新数据到这个文件,
但是文件是从文件开始的300 字节之后的位置,再写入200字节的数据。
写入成功之后,文件总长度变成了 500字节。
一般的操作流程
SetFilePointer 设置 300 偏移值,然后
WriteFile, 从 300的位置开始写200字节的数据。
这就是一般的文件系统必须能处理的随机写的功能。
但是FTP协议,只有把数据附加到文件末尾的功能,不支持随机写。
再后来遇到的 WEBDAV协议, GITHUB,OneDrive等都是不支持随机写的。
为了解决这个麻烦,只好引入了本地缓存文件的办法,我采用如下的办法来解决问题。
也就是把文件先写到本地缓存,直到发生CloseHandle关闭文件的时候,才把整个文件Update到服务端。
看起来原理比较简单,实现起来却不算简单。
而且是在 CloseHandle,也就是发送 IRP_MJ_CLEANUP请求里边 update 文件到服务端 ,
如果文件比较大,而且网络又不好,阻塞就挺严重,而且这种阻塞是全局的,会造成整个操作系统的文件系统阻塞。
一不小心,容易造成电脑卡死。暂时没想到更好的缓存策略。
这个缓存办法,以前的版本已经实现了,这次新版本只是修改了其中某些BUG。
鉴于以上的缓存办法,推荐你尽量使用PRIVATE私有协议,SMB协议,SFTP协议,NFS协议。
这些协议都是完整支持随机写功能的,所有读写请求,都是实时跟服务端交互,不会在本地做任何文件缓存。
如果你要使用FTP协议, OneDrive等不支持随机写功能写文件的时候,请确保你的网络速度,并且写的文件不宜过大。
新版本的配置界面如下图所示:
支持的通讯协议如下:
目前支持10种通讯协议:
1,PRIVATE私有协议,
2,NFS协议
3, SMB协议,这就是windows的文件共享。
4,SFTP,这个是SSH登录到linux的文件传输协议
5,FTP,
6,WEBDAV,
7,Memory Directory 这个是内存目录,把文件存储到内存中
8,Local Directory,这个功能是把电脑的任意一个目录重定向到另外一个目录。
9,OneDrive, 这个是微软的OneDrive云盘。
10,GITHUB,这个是源代码共享网站。与普通使用者没啥关系。
如果你的电脑安装过以前的xFsRedir版本,请先完整卸载驱动,然后重启电脑,再次打开旧版xFsRedir,
确保”状态“里边的驱动和服务都被删除了,才能替换成新版本程序。
请关注稍后公布到CSDN上和GITHUB上的xFsRedir程序。
GITHUB上的下载地址:
https://github.com/fanxiushu/xFsRedir
CSDN上下载地址: