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   开心种树  阅读(1169)  评论(1编辑  收藏  举报

编辑推荐:
· AI与.NET技术实操系列(二):开始使用ML.NET
· 记一次.NET内存居高不下排查解决与启示
· 探究高空视频全景AR技术的实现原理
· 理解Rust引用及其生命周期标识(上)
· 浏览器原生「磁吸」效果!Anchor Positioning 锚点定位神器解析
阅读排行:
· DeepSeek 开源周回顾「GitHub 热点速览」
· 物流快递公司核心技术能力-地址解析分单基础技术分享
· .NET 10首个预览版发布:重大改进与新特性概览!
· AI与.NET技术实操系列(二):开始使用ML.NET
· 单线程的Redis速度为什么快?
< 2025年3月 >
23 24 25 26 27 28 1
2 3 4 5 6 7 8
9 10 11 12 13 14 15
16 17 18 19 20 21 22
23 24 25 26 27 28 29
30 31 1 2 3 4 5

统计

点击右上角即可分享
微信分享提示