文件I/O:通用的 I/O 模型 — Go 封裝
本文介紹 Unix I/O 模型中的4個通用系統調用:open()、read()、write()和close() 的 Go 語言封裝。
1、Linux 中 open 系統調用的定義
#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
2、Go 中 open 系統調用的封裝
一般的,我們使用 os 標準庫的 Open/Create 方法來間接調用 open 系統調用,跟蹤代碼,找到 Go 中 open 系統調用的封裝:
func Open(path string, mode int, perm uint32) (fd int, err error) {
return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)
}
2.1 openat 又是什么呢?
從 2.6.16 (Go 支持的 Linux 版本是 2.6.23)開始,Linux 內核提供了一系列新的系統調用(以at結尾),它們在執行與傳統系統調用相似任務的同時,還提供了一些附加功能,對某些程序非常有用,這些系統調用使用目錄文件描述符來解釋相對路徑。
#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
可見,openat 系統調用和 open 類似,只是添加了一個 dirfd 參數,其作用如下:
1)如果 pathname 中為一相對路徑名,那么對其解釋則以打開文件描述符 dirfd 所指向的目錄為參照點,而非進程的當前工作目錄;
2)如果 pathname 中為一相對路徑,且 dirfd 中所含為特殊值 AT_FDCWD(其值為-100),那么對 pathname 的解釋則相對于進程當前工作目錄,這時 openat 和 open 行為一致;
3)如果 pathname 中為絕對路徑,那么將忽略 dirfd 參數;
在 Go 中,只要存在相應的 at 系統調用,都會使用它。
2.2 解讀 Go 中的 openat
有上可知,Go 中的 Open 并非執行 open 系統調用,而是 openat 系統調用,行為和 open 一致。
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
}
2.3 反過來解讀 os.OpenFile 函數
OpenFile 函數有一個點需要注意,創建或打開文件時,自動加上了 O_CLOEXEC 標志,也就是執行 Exec 系統調用時該文件描述符不會被繼承;
os.OpenFile 返回 os.File 類型的指針,通過 File.Fd() 可以獲取到文件描述符;
3、read、write 和 close 系統調用
通過 open 系統調用的分析可以很容易的自己分析 read、write和close 系統調用。
說明一點:close 系統調用,企圖關閉一個未打開的文件描述符或兩次關閉同一個文件描述符,會返回錯誤。一般都不需要關心錯誤。
4、lseek 系統調用
Go 中的 os 包的 File.Seek 對應的系統調用是 lseek
5、其他文件 I/O 相關的系統調用
os 包中,File 類型中 ReadAt/WriteAt 對應的系統調用是 pread/pwrite
Truncate 對應 truncate 等;
幾乎所有 Linux 文件相關系統調用,Go 都有封裝;另外,通過上面 Open 的介紹,即使沒有封裝,自己封裝也不是難事。
來自: http://blog.studygolang.com/2016/06/go-wrapper-io/