以新建文件为例,对比一下几个常见平台的区别。
继续看下Linux平台的代码:
// os/file.go
// 新建文件
func Create(name string) (*File, error) {
// 跳转到下面的OpenFile
return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}
// OpenFile在这里还是平台无关的代码
func OpenFile(name string, flag int, perm FileMode) (*File, error) {
testlog.Open(name)
// 从openFileNolog开始,不同平台代码会有不同
f, err := openFileNolog(name, flag, perm)
if err != nil {
return nil, err
}
f.appendMode = flag&O_APPEND != 0
return f, nil
}
// os/file_unix.go
// openFileNolog的unix实现
func openFileNolog(name string, flag int, perm FileMode) (*File, error) {
setSticky := false
if !supportsCreateWithStickyBit && flag&O_CREATE != 0 && perm&ModeSticky != 0 {
if _, err := Stat(name); IsNotExist(err) {
setSticky = true
}
}
var r int
var s poll.SysFile
for {
var e error
//跳转到open
r, s, e = open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))
if e == nil {
break
}
// We have to check EINTR here, per issues 11180 and 39237.
if e == syscall.EINTR {
continue
}
return nil, &PathError{Op: "open", Path: name, Err: e}
}
// open(2) itself won't handle the sticky bit on *BSD and Solaris
if setSticky {
setStickyBit(name)
}
// There's a race here with fork/exec, which we are
// content to live with. See ../syscall/exec_unix.go.
if !supportsCloseOnExec {
syscall.CloseOnExec(r)
}
kind := kindOpenFile
if unix.HasNonblockFlag(flag) {
kind = kindNonBlock
}
// 封装为File结构
f := newFile(r, name, kind)
f.pfd.SysFile = s
return f, nil
}
// os/file_open_unix.go
func open(path string, flag int, perm uint32) (int, poll.SysFile, error) {
// 跳转到syscall.Open
fd, err := syscall.Open(path, flag, perm)
return fd, poll.SysFile{}, err
}
// syscall/syscall_linux.go
func Open(path string, mode int, perm uint32) (fd int, err error) {
// 跳转到openat
return openat(AT_FDCWD, path, mode|O_LARGEFILE, perm)
}
//sys openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)
// syscall/zsyscall_linux_amd64.go
func openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
var _p0 *byte
_p0, err = BytePtrFromString(path)
if err != nil {
return
}
// 跳转到Syscall6
r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
fd = int(r0)
if e1 != 0 {
err = errnoErr(e1)
}
return
}
// syscall/syscall_linux.go
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno) {
runtime_entersyscall()
// 跳转到RawSyscall6
r1, r2, err = RawSyscall6(trap, a1, a2, a3, a4, a5, a6)
runtime_exitsyscall()
return
}
// N.B. RawSyscall6 is provided via linkname by runtime/internal/syscall.
//
// Errno is uintptr and thus compatible with the runtime/internal/syscall
// definition.
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
// syscall/zsysnum_linux_amd64.go
SYS_OPENAT = 257
// RawSyscall6是通过汇编实现的,传入SYS_OPENAT,最终调用openat函数
// openat函数是libc标准库中的函数,C语言定义为
int openat(int dirfd, const char *pathname, int flags, mode_t mode);
// runtime/internal/syscall/asm_linux_amd64.s // Syscall6 的实现在这里 // func Syscall6(num, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2, errno uintptr) // // We need to convert to the syscall ABI. // // arg | ABIInternal | Syscall // --------------------------- // num | AX | AX // a1 | BX | DI // a2 | CX | SI // a3 | DI | DX // a4 | SI | R10 // a5 | R8 | R8 // a6 | R9 | R9 // // r1 | AX | AX // r2 | BX | DX // err | CX | part of AX // // Note that this differs from "standard" ABI convention, which would pass 4th // arg in CX, not R10. TEXT ·Syscall6<ABIInternal>(SB),NOSPLIT,$0 // a6 already in R9. // a5 already in R8. MOVQ SI, R10 // a4 MOVQ DI, DX // a3 MOVQ CX, SI // a2 MOVQ BX, DI // a1 // num already in AX. SYSCALL CMPQ AX, $0xfffffffffffff001 JLS ok NEGQ AX MOVQ AX, CX // errno MOVQ $-1, AX // r1 MOVQ $0, BX // r2 RET ok: // r1 already in AX. MOVQ DX, BX // r2 MOVQ $0, CX // errno RET
// 然后回到openFileNolog中
// 在openFileNolog中,继续调用newFile,整体封装为File结构,原路返回
func newFile(fd int, name string, kind newFileKind) *File {
f := &File{&file{
pfd: poll.FD{
Sysfd: fd,
IsStream: true,
ZeroReadIsEOF: true,
},
name: name,
stdoutOrErr: fd == 1 || fd == 2,
}}
pollable := kind == kindOpenFile || kind == kindPipe || kind == kindNonBlock
// If the caller passed a non-blocking filedes (kindNonBlock),
// we assume they know what they are doing so we allow it to be
// used with kqueue.
if kind == kindOpenFile {
switch runtime.GOOS {
case "darwin", "ios", "dragonfly", "freebsd", "netbsd", "openbsd":
var st syscall.Stat_t
err := ignoringEINTR(func() error {
return syscall.Fstat(fd, &st)
})
typ := st.Mode & syscall.S_IFMT
// Don't try to use kqueue with regular files on *BSDs.
// On FreeBSD a regular file is always
// reported as ready for writing.
// On Dragonfly, NetBSD and OpenBSD the fd is signaled
// only once as ready (both read and write).
// Issue 19093.
// Also don't add directories to the netpoller.
if err == nil && (typ == syscall.S_IFREG || typ == syscall.S_IFDIR) {
pollable = false
}
// In addition to the behavior described above for regular files,
// on Darwin, kqueue does not work properly with fifos:
// closing the last writer does not cause a kqueue event
// for any readers. See issue #24164.
if (runtime.GOOS == "darwin" || runtime.GOOS == "ios") && typ == syscall.S_IFIFO {
pollable = false
}
}
}
clearNonBlock := false
if pollable {
if kind == kindNonBlock {
// The descriptor is already in non-blocking mode.
// We only set f.nonblock if we put the file into
// non-blocking mode.
} else if err := syscall.SetNonblock(fd, true); err == nil {
f.nonblock = true
clearNonBlock = true
} else {
pollable = false
}
}
// An error here indicates a failure to register
// with the netpoll system. That can happen for
// a file descriptor that is not supported by
// epoll/kqueue; for example, disk files on
// Linux systems. We assume that any real error
// will show up in later I/O.
// We do restore the blocking behavior if it was set by us.
if pollErr := f.pfd.Init("file", pollable); pollErr != nil && clearNonBlock {
if err := syscall.SetNonblock(fd, false); err == nil {
f.nonblock = false
}
}
runtime.SetFinalizer(f.file, (*file).close)
return f
}