【问题标题】:Atomicity of writev() system call in LinuxLinux 中 writev() 系统调用的原子性
【发布时间】:2017-06-13 21:26:50
【问题描述】:

我查看了 linux 内核 4.4.0-57-generic 的内核源代码,但在 writev() 源代码中没有看到任何锁。有什么我想念的吗?我看不出writev() 是原子的还是线程安全的。

【问题讨论】:

    标签: linux linux-kernel kernel system-calls


    【解决方案1】:

    这里不是内核专家,但无论如何我都会分享我的观点。随时发现任何错误。

    浏览内核(v4.9 虽然我不希望它如此不同),并尝试跟踪 writev(2) 系统调用,我可以观察到创建以下路径的后续函数调用:

    1. SYSCALL_DEFINE3(writev, ..)

    2. do_writev(..)

    3. vfs_writev(..)

    4. do_readv_writev(..)

    现在路径分支,取决于是否实现了write_iter 方法并挂钩到系统调用所指的struct filestruct file_operations 字段。

    • 如果不是NULL,则路径为:

    5a。 do_iter_readv_writev(..),调用方法filp->f_op->write_iter(..)at this point

    • 如果是NULL,则路径为:

    5b。 do_loop_readv_writev(..),在循环中反复调用方法filp->f_op->writeat this point


    所以,据我了解,writev() 系统调用与底层write()(或write_iter())一样是线程安全的,当然可以通过多种方式实现,例如在设备驱动程序中,并且可能会或可能不会根据其需要和设计使用锁。


    编辑

    在内核 v4.4 中,路径看起来非常相似:

    1. SYSCALL_DEFINE3(writev, ..)

    2. vfs_writev(..)

    3. do_readv_writev(..)

    然后取决于struct filestruct file_operations中作为字段的write_iter方法是否为NULL,就像上面描述的v4.9中的情况一样。

    【讨论】:

    • 还是其他人?我正在学习这些东西,发现任何错误都会有所帮助。
    • 这和我看到的一样。在我读到这个 ​​writev(2) 之后依赖filp->f_op->write 下面的 write 方法来获得原子性似乎很奇怪:*readv() 和 writev() 执行的数据传输是原子的: 数据由 writev() 写入的块被写入单个块,不会与其他进程中写入的输出混合(但请参阅 pipe(7) 以了解异常);类似地, readv() 保证从文件中读取连续的数据块,而不管在其他线程或具有文件描述符的进程中执行的读取操作如何。 . .
    • @C6Up1bQ73STi29cA 我明白你的意思。我相信在依赖do_iter_readv_writev() 和方法write_iter 时如何实现原子性是显而易见的,因为后者可以正确获取必要的锁以原子地执行数据传输。然而,在write_iter 根本没有实现的情况下,因此writev(2) 基本上依赖于手动重复调用的write 方法,在一个循环中,老实说,我不知道它怎么能是原子的。我最好的猜测是,在这种情况下,write_iter 方法 必须 实现以实现原子性。 ...
    • 感谢 chrk 和 Tsyvarev。正如@Tsyvarev 为ext4 指出的那样,驱动程序可能必须 实现原子write() 方法。如果我看到 writev() 中的锁,我会更放心。我正在写一个套接字,所以我将打开该代码的 write() 方法。使用抢占式内核,没有锁,没有原子。当然,可能有内核魔法不会抢占 writev()。我可能应该在 Unix 和 Linux 上发帖;我想我会。再次感谢。
    【解决方案2】:

    VFS(虚拟文件系统)本身不保证writev() 调用的 原子性。它只是调用struct file_operations 的文件系统特定的.write_iter 方法。

    make 方法原子地写入文件是特定文件系统实现的责任

    例如在ext4文件系统函数ext4_file_write_iter使用

    mutex_lock(&inode->i_mutex);
    

    让写作原子化。

    【讨论】:

      【解决方案3】:

      在 fs.h 中找到它:

      static inline void file_start_write(struct file *file)
      {
          if (!S_ISREG(file_inode(file)->i_mode))
              return;
          __sb_start_write(file_inode(file)->i_sb, SB_FREEZE_WRITE, true);
      }
      

      然后在 super.c 中:

      /*
       * This is an internal function, please use sb_start_{write,pagefault,intwrite}
       * instead.
       */
      int __sb_start_write(struct super_block *sb, int level, bool wait)
      {
          bool force_trylock = false;
          int ret = 1;
      
      #ifdef CONFIG_LOCKDEP
      /*
       * We want lockdep to tell us about possible deadlocks with freezing
       * but it's it bit tricky to properly instrument it. Getting a freeze
       * protection works as getting a read lock but there are subtle
       * problems. XFS for example gets freeze protection on internal level
       * twice in some cases, which is OK only because we already hold a
       * freeze protection also on higher level. Due to these cases we have
       * to use wait == F (trylock mode) which must not fail.
       */
        if (wait) {
          int i;
      
          for (i = 0; i < level - 1; i++)
              if (percpu_rwsem_is_held(sb->s_writers.rw_sem + i)) {
                  force_trylock = true;
                  break;
              }
        }
      #endif
        if (wait && !force_trylock)
          percpu_down_read(sb->s_writers.rw_sem + level-1);
        else
          ret = percpu_down_read_trylock(sb->s_writers.rw_sem + level-1);
      
        WARN_ON(force_trylock & !ret);
        return ret;
      }
      EXPORT_SYMBOL(__sb_start_write);
      

      再次感谢。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 2015-05-24
        • 2012-06-30
        • 1970-01-01
        • 2015-12-22
        • 2012-12-30
        • 2016-05-12
        • 2016-03-02
        • 1970-01-01
        相关资源
        最近更新 更多