linux系统编程——文件IO——并发写的冲突问题

1. 写文件的互斥

不论是 多线程,多进程,同时写一个文件,在数据写入 页缓存时,都是原子的,
如进程a 写入 "aaaa" 进程b 写入 "bbbb",则内核可能会将 "aaaa" 完整的写入 页缓存,在将完整的 "bbbb" 写入页缓存,
不会出现 "aa" "bbbb" "aa" 的情况。

相关代码如下
对于普通文件,write() 底层会调用 aio_write()

ssize_t generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
		unsigned long nr_segs, loff_t pos)
{
	struct file *file = iocb->ki_filp;
	struct inode *inode = file->f_mapping->host;
	ssize_t ret;

	BUG_ON(iocb->ki_pos != pos);

	mutex_lock(&inode->i_mutex);
	ret = __generic_file_aio_write(iocb, iov, nr_segs, &iocb->ki_pos);
	mutex_unlock(&inode->i_mutex);

	if (ret > 0) {
		ssize_t err;

		err = generic_write_sync(file, pos, ret);
		if (err < 0 && ret > 0)
			ret = err;
	}
	return ret;
}

2. 导致冲突的是 f_pos

aio_write() 的参数 pos,是从 f_pos来的,也就说,在 真正的原子写 前,确定的 pos,则pos没有原子性,
若 两个进程 写同个文件,由于 两个 进程的 struct file不同,所以 f_pos不同,导致内容覆盖。
若 父子进程 写同个文件,由于 struct file 相同,所以 f_pos 相同,但是 pos 的设置没有原子性,所以 也可能内容覆盖。
若 多线程 写同个文件,情况同上。

ksys_write() 中 获得 pos 和 设置 pos 没有上锁,所以 A线程获得pos为0,B线程获得pos为0,A线程写"aa",设置pos为2,B线程的pos仍然为0,导致A的数据被覆盖。

static inline loff_t file_pos_read(struct file *file)                                                                                                                                                                                                                          
{   
    return file->f_pos;
}

static inline void file_pos_write(struct file *file, loff_t pos)
{
    file->f_pos = pos;
}  

ssize_t ksys_write(unsigned int fd, const char __user *buf, size_t count)
{
    struct fd f = fdget_pos(fd);
    ssize_t ret = -EBADF;
                                                                                                                                                                                                                                                                               
    if (f.file) {
        loff_t pos = file_pos_read(f.file);
        ret = vfs_write(f.file, buf, count, &pos);
        if (ret >= 0)
            file_pos_write(f.file, pos);
        fdput_pos(f);
    }

    return ret; 
}

SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
        size_t, count)
{
    return ksys_write(fd, buf, count);
}

3. 用O_APPEND解决冲突

使用O_APPEND时,generic_file_aio_write()使用的pos在用的时候根据 inode大小 获得的,
所以 使用 O_APPEND 时,无论是 多线程,多进程 都能保证同步。

但是 若文件是 pipe,则写大小必须小于 PIPE_SIZE 才能保证写的完整性

4. 启发

由于 多线程 write() 最后会成 串行 write,所以使用mmap 直接 并发写页缓存更合适

5. 标准IO

标准IO能保证原子性,只要 对一个 FILE 写,则不会有冲突

posted on 2021-08-20 14:00  开心种树  阅读(1037)  评论(1编辑  收藏  举报