一次项目中,我们用的基于fuse的文件系统进程出core挂掉了,排查发现,这并不是个BUG,算是个缺陷吧。出core表现是这样的,libfuse从/dev/fuse中调用read读取内容,读返回成功了,但缓冲区只更新了部分内容,还有部分内容是旧的,导致libfuse执行了旧的命令,而我们实现的fuse文件系统,某些命令重复执行是会出core的,也幸亏会出core才能暴露这个bug,不然某些命令重复执行,造成数据丢失就玩大发了
当排查到是read系统调用有问题的时候,我是不愿意相信的,堂堂centos 3.10.0 的内核还会有这种bug嘛?当然,事实这不算是bug,只是一个缺陷,用户层在多线程里fork,并使用非对齐到页的内存时,就会触发这个缺陷。下面我们来探下究竟。
fuse的原理还是比较简单易懂的,在用户层有个设备名为/dev/fuse,用户层(libfuse)就是通过这个来和内核的fuse通信。当用户层读写fuse实现的文件系统的时候,内核就会产生一个请求挂到队列里等待libfuse的读取,而libfuse读/dev/fuse来读取请求并执行相应操作后写回/dev/fuse。问题就在这个/dev/fuse的读实现里。
/dev/fuse是个混杂设备,其的最终的读函数是fuse_dev_read,我们分析下它的代码:
static ssize_t fuse_dev_read(struct kiocb *iocb, const struct iovec *iov,
unsigned long nr_segs, loff_t pos)
{
struct fuse_copy_state cs;
struct file *file = iocb->ki_filp;
struct fuse_conn *fc = fuse_get_conn(file);
if (!fc)
return -EPERM;
fuse_copy_init(&cs, fc, 1, iov, nr_segs);
return fuse_dev_do_read(fc, file, &cs, iov_length(iov, nr_segs));
}
//fuse_copy_state 初始化, 这个结构体用于维护copy一个请求的状态。
static void fuse_copy_init(struct fuse_copy_state *cs, struct fuse_conn *fc,
int write,
const struct iovec *iov, unsigned long nr_segs)
{
memset(cs, 0, sizeof(*cs));
cs->fc = fc; //fc连接
cs->write = write; //读写方向,1为读
cs->iov = iov; //用户地址iov,一般一个
cs->nr_segs = nr_segs; //一般为1
}
fuse_copy_init初始化fuse_copy_state 这个结构体,这个结构体是维护fuse拷贝请求的,fuse_dev_do_read进行具体的读。
//读取一个请求到用户空间的缓存里,这个调用会阻塞。 当一个请求不需要回复(FORGET)或者被abort或发生
//错误,它会调用request_end来结束。其它情况下,这个请求被读取后会被挂载到processing 链表,然后设置 sent 标记
static ssize_t fuse_dev_do_read(struct fuse_conn *fc, struct file *file,