以新建文件为例,对比一下几个常见平台的区别。
继续看下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 }