`

linux下文件的append是原子的(线程安全的)

 
阅读更多
具体的理由可以参考这个文章:http://www.pagefault.info/?p=139
下面是这个文章的全文:


先来描述一下,write系统调用的大体流程,首先内核会取得对应的文件偏移,然后调用vfs的write操作,而在vfs层的write操作的时候会调用对应文件系统的write方法,而在对应文件系统的write方法中aio_write方法,最终会调用底层驱动。这里有一个需要注意的就是内核在写文件的时候会加一把锁(有些设备不会加锁,比如块设备以及裸设备).这样也就是说一个文件只可能同时只有一个进程在写。而且快设备是不支持append写的。

而这里append的原子操作的实现很简单,由于每次写文件只可能是一个进程操作(计算文件偏移并不包含在锁里面),而append操作是每次写到末尾(其他类型的写是先取得pos才进入临界区,而这个时间内有可能pos已经被其他进程改变,而append的pos的计算是包含在锁里面的),因此自然append就是原子的了.

ok,接下来来看代码。

首先来看write的系统调用,函数很简单,就是取得当前文件的偏移,然后调用vfs的写方法。最后更改文件的偏移。这里要注意,取得文件偏移的方法并没有加锁,也就是说这里存在竞争。

这里有个要注意的就是POS,如果是append写的话,后面的代码会修改这个值,这里先跳过,后面遇到我们会说明。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,
        size_t, count)
{
    struct file *file;
    ssize_t ret = -EBADF;
    int fput_needed;

    file = fget_light(fd, &fput_needed);
    if (file) {
//取得文件句柄的偏移
        loff_t pos = file_pos_read(file);
//写文件。传递偏移量。
        ret = vfs_write(file, buf, count, &pos);
//更新偏移
        file_pos_write(file, pos);
        fput_light(file, fput_needed);
    }

    return ret;
}
接下来就是vfs_write,这个函数主要就是进行一些合法性判断,然后调用具体文件系统的write方法,这里要注意,write方法不一定会调用到文件系统的write方法,比如块设备以及裸设备都会调用到blkdev_aio_write。

而file op的初始化在ext3_iget中的,也就是获取超级块的方法,可以看到如果是一般文件,则会被初始化为ext3_file_inode_operations。

1
2
3
4
5
6
7
8
struct inode *ext3_iget(struct super_block *sb, unsigned long ino)
................................................................................
    if (S_ISREG(inode->i_mode)) {
//初始化
        inode->i_op = &ext3_file_inode_operations;
        inode->i_fop = &ext3_file_operations;
        ext3_set_aops(inode);
.................................................
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{
.........................................................................
    ret = rw_verify_area(WRITE, file, pos, count);
    if (ret >= 0) {
        count = ret;
//调用具体的文件系统的方法。
        if (file->f_op->write)
            ret = file->f_op->write(file, buf, count, pos);
        else
            ret = do_sync_write(file, buf, count, pos);
..................................................................................
    }

    return ret;
}
我们主要来看ext3的实现,其他的文件系统基本差不多。下面就是ext3文件系统对应回调操作函数。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
const struct file_operations ext3_file_operations = {
    .llseek     = generic_file_llseek,
//主要是下面4个
    .read       = do_sync_read,
    .write      = do_sync_write,
    .aio_read   = generic_file_aio_read,
    .aio_write  = generic_file_aio_write,
    .unlocked_ioctl = ext3_ioctl,
#ifdef CONFIG_COMPAT
    .compat_ioctl   = ext3_compat_ioctl,
#endif
    .mmap       = generic_file_mmap,
    .open       = dquot_file_open,
    .release    = ext3_release_file,
    .fsync      = ext3_sync_file,
    .splice_read    = generic_file_splice_read,
    .splice_write   = generic_file_splice_write,
};
可以看到它的write方法就是do_sync_write,在do_sync_write中会调用它自己的aio_write方法:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
ssize_t do_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{
...............................................
//如果不是块设备才进入下面的处理
    if (!isblk) {
        /* FIXME: this is for backwards compatibility with 2.4 */
//调用i_size_read得到文件大小,从而定位append的位置。
        if (file->f_flags & O_APPEND)
                        *pos = i_size_read(inode);

        if (limit != RLIM_INFINITY) {
            if (*pos >= limit) {
                send_sig(SIGXFSZ, current, 0);
                return -EFBIG;
            }
            if (*count > limit - (typeof(limit))*pos) {
                *count = limit - (typeof(limit))*pos;
            }
        }
    }
...................................................
    return ret;
}
因此可以看到最关键的操作都是放在aio_write中,也就是generic_file_aio_write,这个函数我们可以看到在调用具体的实现__generic_file_aio_write之前会加一把锁(i_mutex),这样就保证了一个文件同时只会有一个进程来写。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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 || ret == -EIOCBQUEUED) {
        ssize_t err;

        err = generic_write_sync(file, pos, ret);
        if (err < 0 && ret > 0)
            ret = err;
    }
    return ret;
}
上面可以看到先加锁然后调用__generic_file_aio_write,而对应的blkdev_aio_write则是直接调用__generic_file_aio_write,也就是不用加锁,下面就是内核里面的注释:

1
2
* It expects i_mutex to be grabbed unless we work on a block device or similar
* object which does not need locking at all.
然后来看__generic_file_aio_write的实现,这里它调用了generic_write_checks,这个函数主要用来执行写之前的一些检测。

1
2
3
4
5
6
7
8
ssize_t __generic_file_aio_write(struct kiocb *iocb, const struct iovec *iov,
                 unsigned long nr_segs, loff_t *ppos)
{
...................................................................

    err = generic_write_checks(file, &pos, &count, S_ISBLK(inode->i_mode));
......................................................................
}
然后是generic_write_checks,这个函数就是做一些检测,并且APPEND写的原子性也是由这个函数进行控制的。

这里会修改对应的pos,调用i_size_read来得到文件的大小,从而进行append操作。

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
inline int generic_write_checks(struct file *file, loff_t *pos, size_t *count, int isblk)
{
    struct inode *inode = file->f_mapping->host;
    unsigned long limit = rlimit(RLIMIT_FSIZE);

        if (unlikely(*pos < 0))
                return -EINVAL;

    if (!isblk) {
        /* FIXME: this is for backwards compatibility with 2.4 */
//如果是append操作,则调用i_size_read得到文件大小,然后得到文件该写的位置,这里更改了pos的值.
        if (file->f_flags & O_APPEND)
//更改pos
                        *pos = i_size_read(inode);

        if (limit != RLIM_INFINITY) {
            if (*pos >= limit) {
                send_sig(SIGXFSZ, current, 0);
                return -EFBIG;
            }
            if (*count > limit - (typeof(limit))*pos) {
                *count = limit - (typeof(limit))*pos;
            }
        }
    }
.............................................................
}
分享到:
评论

相关推荐

    Linux下ftp命令详解

    Linux下ftp命令详解 FTP&gt; ! 从 ftp 子系统退出到外壳。 FTP&gt; ? 显示 ftp 命令说明。? 与 help 相同。 格式:? [command] 说明:[command]指定需要帮助的命令名称。如果没有指定 command,ftp 将显示全部命令的列表...

    Python append()函数在for循环中的覆盖问题解决文件

    文件就是一个python文件,里面有append()函数在for循环中使用出现覆盖的错误,并予以解决,配合本人博文内容《Python append()函数在for循环中的覆盖问题》观看更好

    详解Python多线程下的list

    尽管多线程下的 list 是线程不安全的,但是在 append 的操作下是它又是线程安全的. 如何判断线程安全呢? 对于线程安全不安全,我们可以通过极端条件下去复现,从而得出结论。比如说判断 list 是否

    Append原理Append原理

    Append原理Append原理Append原理

    QT/C++多线程练习:单生产者多消费者(源码)

    使用linux qt打开文件夹下的.pro文件即可。 涉及 线程创建与退出、线程暂停、父子线程之前以及兄弟线程之间的参数和信号传递、多线程的以及多线程的管理。要求是练习的demo对于以上的点只要涉及基础即可。 主线程...

    /* append*/ oracle append 知识点

    INSERT /*append */ INTO TABLE1 AS SELECT * FROM TABLE2;

    深入探究文件I/O-I.MX6U嵌入式Linux C应用编程学习笔记基于正点原子阿尔法开发板

    深入探究文件 I/O:深入了解了文件 I/O 中的一些细节,譬如文件的管理方式、 错误返回的处理、空洞文件、O_APPEND 和 O_TRUNC 标志、原子操作与竞争冒险等等

    O_APPEND与读操作

    当以O_APPEND选项打开一个文件时,验证能否使用lseek修改文件偏移量。若能修改,那修改之后,读操作是从文件尾部读,还是从修改后的偏移量开始读,编程进行验证

    arcgis下append面图层

    arcgis中拼接面图层的具体操作,具体的操作过程已经详细的列出来了。

    一个可以重用的线程安全生产者消费者队列类

    不过一般的文档中给出的例子往往只是“例子”,要用于实际的项目开发中差的很远,共享一下我以前写的一个线程安全的生产者消费者队列类。具有如下特点: 1.可以设置队列中的最大长度; 2.线程安全; 3.多线程想向...

    Append.c源文件

    Append.c源文件

    Linux 监控文件被什么进程修改(详解)

    安装: apt-get install auditd. 1.auditd 是后台守护进程,负责监控记录 2.auditctl 配置规则的...•-p awrx 要监控的操作类型,append, write, read, execute •-k 给当前这条监控规则起个名字,方便搜索过滤 查看修

    例程 VC++ VS2010 MFC CString 追加append 写入文件 txt 捕捉键盘按键消息

    怎么处理(不存在从 "CString" 到 "LPCWSTR" 的适当转换函数); MFC对话框程序,如何适当的处理(回车键和esc键退出); 如何把键盘输入捕获并保存在内存或者硬盘优盘等存储设备。

    深入探究文件IO,嵌入式Linux

    深入探究文件 I/O:深入了解了文件 I/O 中的一些细节,譬如文件的管理方式、 错误返回的处理、空洞文件、O_APPEND 和 O_TRUNC 标志、原子操作与竞争冒险等等。

    前端开源库-vue-append

    前端开源库-vue-appendvue append,vue append,类似v-html指令,但它可以调用javascript函数

    jquery append

    这里面主要是讲关于jquery append 函数的使用,方便用户使用函数

    NOLOGGING、APPEND、ARCHIVE和PARALLEL下,REDO、UNDO和执行速度的比较

    BLOG_Oracle_lhr_【知识点整理】Oracle中NOLOGGING、APPEND、ARCHIVE和PARALLEL下,REDO、UNDO和执行速度的比较BLOG_Oracle_lhr_【知识点整理】Oracle中NOLOGGING、APPEND、ARCHIVE和PARALLEL下,REDO、UNDO和执行...

    VS2008 编译 append2simg 工程

    从 android-7.1.1_r1\system\core\libsparse\append2simg.c 剥离出来的程序,用于 Concatenate the system image, the verity metadata, and the hash tree.

    Ansible-ansible-append-list.zip

    Ansible-ansible-append-list.zip,如何附加到ansible中的列表如何附加到ansible中的列表,ansible是一个简单而强大的自动化引擎。它用于帮助配置管理、应用程序部署和任务自动化。

    Python中循环后使用list.append()数据被覆盖问题的解决

    主要给大家介绍了关于Python中循环后使用list.append()数据被覆盖问题的解决方法,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一起学习学习吧

Global site tag (gtag.js) - Google Analytics