上一篇说明了fuse的并行性,也就是同个回调函数是可以重入的。
这一篇主要是研究了fuse的read操作的并行性,说明fuse默认有可能为
应用层的一个I/O请求提供并发性。
问题:
在前面的实验中,fuse中除了read操作外,所有函数都使用全局的CLIENT cl句柄来连接mds和osd(当然,与各个机器之间的CLIENT是独立),这样的好处是,客户端不必为每个i/o请求都创建一个CLIENT,以致服务器端或osd端的portmap(用于rpc程序端口的查询)过于繁忙。
如果read中也使用全局的CLIENT句柄,则会出现很奇怪的错误。
发现,这些错误其实都是由于fuse的read操作有并发造成的。
实验1:
在read操作中使用全局的CLIENT句柄,程序还插入了一些终端输出和中断(就是一处getchar())便于调试
比如这样子:
static int dnfs_read(const char *path, char *buf, size_t size, off_t offset,
struct fuse_file_info *fi)
{
printf("[dnfs_read]: %s, size = %u, offset = %ld\n", path, size, offset);
static int flag = 0;
flag++;
printf(">>>>>>>>>>flag = %d<<<<<<<<<<<<<\n", flag);
getchar();
......
}
1$ ./client -d /mnt/dnfs/
2$ cd /mnt/dnfs && cp 1m ~/
1号终端启动了文件系统后台,2号终端进入文件系统dnfs的挂载点(根目录),再把该文件系统下的“1m“文件拷贝到用户home目录
下面是2$输出的log片段:
unique: 8, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 16384 bytes from 0
[dnfs_read]: /77/1m, size = 16384, offset = 0
>>>>>>>>>>flag = 1, press any key here<<<<<<<<<<<<< //插入的中断,这里全部拍回车,所以下面这一空行
co_read failed, flag = 1
192.168.0.70: RPC: Remote system error //连接不上osd70,不过没关系,后面会重连的
destroy clo: 0x6490e0
return -5
unique: 8, error: -5 (Input/output error), outsize: 16
unique: 9, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 4096 bytes from 0
[dnfs_read]: /77/1m, size = 4096, offset = 0
>>>>>>>>>>flag = 2, press any key here<<<<<<<<<<<<<
return 4096
READ[0] 4096 bytes
unique: 9, error: 0 (Success), outsize: 4112 //这次OK了
unique: 10, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 32768 bytes from 16384
[dnfs_read]: /77/1m, size = 32768, offset = 16384
>>>>>>>>>>flag = 3, press any key here<<<<<<<<<<<<< //问题出现了,可以看到,这里并没有输入回车(或任何键),下一个请求(unique 11)就到了,显然这里出现了并发
unique: 11, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 4096 bytes from 4096
[dnfs_read]: /77/1m, size = 4096, offset = 4096
>>>>>>>>>>flag = 4, press any key here<<<<<<<<<<<<< //这里输入的回车对应的是flag=3,也就是unique 10的请求
co_read failed, flag = 4
192.168.0.70: RPC: Remote system error
destroy clo: 0x2aaaac009990
return -5
unique: 10, error: -5 (Input/output error), outsize: 16 //unique 10请求到此返回。下面的回车对应的是unique 11号请求的中断
co_read 0 : res = 4096
return 4096
READ[0] 4096 bytes
unique: 11, error: 0 (Success), outsize: 4112
|
上面的例子说明了read中的并发。但这个例子是可以顺利通过的,只要不断的拍回车,最后的结果也是被验证是正确的。其实之所以是正确的,是因为在flag=2之后(也就是flag=3那里的输出)虽然并发产生了unique 10和11两个请求,但是由于我们设置了中断,所以unique11其实一直被拖到unique10执行完之后才真的的执行,也就是说unique10的执行并不妨碍到unique11的执行。
下面实验2把实验1中的中断(getchar()那行)去掉:
unique: 7, opcode: OPEN (14), nodeid: 3, insize: 48
OPEN[0] flags: 0x8000 /77/1m
unique: 8, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 16384 bytes from 0
[dnfs_read]: /77/1m, size = 16384, offset = 0
>>>>>>>>>>flag = 1<<<<<<<<<<<<<
co_read failed, flag = 1
192.168.0.70: RPC: Remote system error
destroy clo: 0x6480e0
return -5
unique: 8, error: -5 (Input/output error), outsize: 16
unique: 9, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 4096 bytes from 0
[dnfs_read]: /77/1m, size = 4096, offset = 0
>>>>>>>>>>flag = 2<<<<<<<<<<<<<
co_read 0 : res = 4096
return 4096
READ[0] 4096 bytes
unique: 9, error: 0 (Success), outsize: 4112
unique: 10, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 32768 bytes from 16384 //看到读32768字节的是unique10
[dnfs_read]: /77/1m, size = 32768, offset = 16384
>>>>>>>>>>flag = 3<<<<<<<<<<<<< //同实验1中一样,这里产生了并发unique10和11
unique: 11, opcode: READ (15), nodeid: 3, insize: 80
READ[0] 4096 bytes from 4096 //要读4096字节的是unique11
[dnfs_read]: /77/1m, size = 4096, offset = 4096
>>>>>>>>>>flag = 4<<<<<<<<<<<<<
co_read 0 : res = 4096
return 4096 //注意,这里读返回的是4096,所以对应的是unique11请求
READ[0] 4096 bytes //返回值也是对的,读了(返回)4096
unique: 10, error: 0 (Success), outsize: 4112 //但是却被当成unique10返回了!!!!!!
<cs_read>flag = 4: read from server failed : 192.168.0.2: RPC: Timed out //接着这里出现了奇怪的flag = 4,不要以为这是unique11的输出(unique11已经在前面被当成unique10返回了),这里是unique10的输出
return -3
unique: 11, error: -3 (No such process), outsize: 16//结果unique10的执行结果被当成unique11执行结果返回了!!!!!
unique: 12, opcode: FLUSH (25), nodeid: 3, insize: 64
FLUSH[0]
unique: 12, error: -38 (Function not implemented), outsize: 16
unique: 13, opcode: RELEASE (18), nodeid: 3, insize: 64
RELEASE[0] flags: 0x8000
[dnfs_release]
unique: 13, error: 0 (Success), outsize: 16
|
显然,这一次的结果是失败的。unique10和11并发产生,由于竞争的结果,unique11比unique10先从mds端得到返回结果,但却被fuse误以为这是unique10的返回值;同样,接下来当unique10返回时,结果也被当成unique11的结果了。
这样关于fuse的read为什么不能使用全局的CLIENT句柄,原因就很清楚了:由于fuse为上层的I/O请求产生了并发,如果使用一个全局的CLIENT句柄,则会产生竞争,致使结果错乱。
最后一点猜测:
前面的测试都是在一个客户端机器上只有一个进程在访问,如果有多个进程同时访问,比如写操作,这时fuse也会产生并发,那写操作如果使用全局的CLIENT句柄,那结果也很有可能是错的,跟上面的读操作类似。
这有待后面实验的验证。
-----
后面实验证明,如果在fuse中所有的fop操作共用一个CLIENT会造成问题的,如果这时客户端上同时运行多个进程,就会出现类似上面的数据混乱。
所有,结论是fuse中每一个fop操作都是可重入的,要确保资源共享与互斥。
正如上一篇
fuse是并发处理I/O请求的最后所说的,如果要取消fuse的并发性,可以在启动fuse时加入-s参数。
阅读(2371) | 评论(0) | 转发(0) |