文件I/O:通用的 I/O 模型 — Go 封装

WhitneyWert 8年前
   <p>本文介绍 Unix I/O 模型中的4个通用系统调用:open()、read()、write()和close() 的 Go 语言封装。</p>    <h2>1、Linux 中 open 系统调用的定义</h2>    <pre>  <code class="language-go">#include <sys/stat.h>  #include <fcntl.h>    int open(const char* pathname, int flags, … /* mode_t mode */);                     Returns file descriptor on success, or -1 on error</code></pre>    <h2>2、Go 中 open 系统调用的封装</h2>    <p>一般的,我们使用 os 标准库的 Open/Create 方法来间接调用 open 系统调用,跟踪代码,找到 Go 中 open 系统调用的封装:</p>    <pre>  <code class="language-go">func Open(path string, mode int, perm uint32) (fd int, err error) {      return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)  }</code></pre>    <h3>2.1 openat 又是什么呢?</h3>    <p>从 2.6.16 (Go 支持的 Linux 版本是 2.6.23)开始,Linux 内核提供了一系列新的系统调用(以at结尾),它们在执行与传统系统调用相似任务的同时,还提供了一些附加功能,对某些程序非常有用,这些系统调用使用目录文件描述符来解释相对路径。</p>    <pre>  <code class="language-go">#define _XOPEN_SOURCE 700 /* Or define _POSIX_C_SOURCE >= 200809 */  #include <fcntl.h>    int openat(int dirfd, const char* pathname, int flags, … /* mode_t mode */);                     Returns file descriptor on success, or -1 on error</code></pre>    <p>可见,openat 系统调用和 open 类似,只是添加了一个 dirfd 参数,其作用如下:</p>    <p>1)如果 pathname 中为一相对路径名,那么对其解释则以打开文件描述符 dirfd 所指向的目录为参照点,而非进程的当前工作目录;</p>    <p>2)如果 pathname 中为一相对路径,且 dirfd 中所含为特殊值 AT_FDCWD(其值为-100),那么对 pathname 的解释则相对于进程当前工作目录,这时 openat 和 open 行为一致;</p>    <p>3)如果 pathname 中为绝对路径,那么将忽略 dirfd 参数;</p>    <p>在 Go 中,只要存在相应的 at 系统调用,都会使用它。</p>    <h3>2.2 解读 Go 中的 openat</h3>    <p>有上可知,Go 中的 Open 并非执行 open 系统调用,而是 openat 系统调用,行为和 open 一致。</p>    <pre>  <code class="language-go">func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {      var _p0 *byte      // 根据要求,path 必须是 C 语言中的字符串,即以 NULL 结尾      // BytePtrFromString 的作用就是返回一个指向 NULL 结尾的字节数组指针      _p0, err = BytePtrFromString(path)      if err != nil {          return      }      // SYS_OPENAT openat 系统调用编号      r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)      // 空操作,用于保证 _p0 存活      use(unsafe.Pointer(_p0))      fd = int(r0)      if e1 != 0 {          err = errnoErr(e1)      }      return  }</code></pre>    <h3>2.3 反过来解读 os.OpenFile 函数</h3>    <p>OpenFile 函数有一个点需要注意,创建或打开文件时,自动加上了 O_CLOEXEC 标志,也就是执行 Exec 系统调用时该文件描述符不会被继承;</p>    <p>os.OpenFile 返回 os.File 类型的指针,通过 File.Fd() 可以获取到文件描述符;</p>    <h2>3、read、write 和 close 系统调用</h2>    <p>通过 open 系统调用的分析可以很容易的自己分析 read、write和close 系统调用。</p>    <p>说明一点:close 系统调用,企图关闭一个未打开的文件描述符或两次关闭同一个文件描述符,会返回错误。一般都不需要关心错误。</p>    <h2>4、lseek 系统调用</h2>    <p>Go 中的 os 包的 File.Seek 对应的系统调用是 lseek</p>    <h2>5、其他文件 I/O 相关的系统调用</h2>    <p>os 包中,File 类型中 ReadAt/WriteAt 对应的系统调用是 pread/pwrite</p>    <p>Truncate 对应 truncate 等;</p>    <p>几乎所有 Linux 文件相关系统调用,Go 都有封装;另外,通过上面 Open 的介绍,即使没有封装,自己封装也不是难事。</p>    <p> </p>    <p>来自: <a href="/misc/goto?guid=4959674271045511864" rel="nofollow">http://blog.studygolang.com/2016/06/go-wrapper-io/</a></p>    <p> </p>