以新建文件为例,对比一下几个常见平台的区别。
继续看下MacOS平台的代码:
// 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/zsyscall_darwin_amd64.go func Open(path string, mode int, perm uint32) (fd int, err error) { var _p0 *byte _p0, err = BytePtrFromString(path) if err != nil { return } // 调用syscall r0, _, e1 := syscall(abi.FuncPCABI0(libc_open_trampoline), uintptr(unsafe.Pointer(_p0)), uintptr(mode), uintptr(perm)) fd = int(r0) if e1 != 0 { err = errnoErr(e1) } return }
// syscall/syscall_darwin.go func syscall(fn, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno) // internal/abi/funcpc.go func FuncPCABI0(f interface{}) uintptr // syscall/zsyscall_darwin_amd64.go func libc_open_trampoline() //go:cgo_import_dynamic libc_open open "/usr/lib/libSystem.B.dylib" // 先通过abi.FuncPCABI0(libc_open_trampoline)先获取到open函数的地址 // 然后通过syscall调用open函数 // open函数是libc标准库中的函数,C语言定义为 int open(const char *pathname, int flags, mode_t mode);
//syscall在这里实现 //runtime/sys_darwin.go //go:linkname syscall_syscall syscall.syscall //go:nosplit func syscall_syscall(fn, a1, a2, a3 uintptr) (r1, r2, err uintptr) { args := struct{ fn, a1, a2, a3, r1, r2, err uintptr }{fn, a1, a2, a3, r1, r2, err} entersyscall() //跳转到libcCall libcCall(unsafe.Pointer(abi.FuncPCABI0(syscall)), unsafe.Pointer(&args)) exitsyscall() return args.r1, args.r2, args.err } func syscall() // runtime/sys_libc.go func libcCall(fn, arg unsafe.Pointer) int32 { // Leave caller's PC/SP/G around for traceback. gp := getg() var mp *m if gp != nil { mp = gp.m } if mp != nil && mp.libcallsp == 0 { mp.libcallg.set(gp) mp.libcallpc = getcallerpc() // sp must be the last, because once async cpu profiler finds // all three values to be non-zero, it will use them mp.libcallsp = getcallersp() } else { // Make sure we don't reset libcallsp. This makes // libcCall reentrant; We remember the g/pc/sp for the // first call on an M, until that libcCall instance // returns. Reentrance only matters for signals, as // libc never calls back into Go. The tricky case is // where we call libcX from an M and record g/pc/sp. // Before that call returns, a signal arrives on the // same M and the signal handling code calls another // libc function. We don't want that second libcCall // from within the handler to be recorded, and we // don't want that call's completion to zero // libcallsp. // We don't need to set libcall* while we're in a sighandler // (even if we're not currently in libc) because we block all // signals while we're handling a signal. That includes the // profile signal, which is the one that uses the libcall* info. mp = nil } // 跳转到asmcgocall res := asmcgocall(fn, arg) if mp != nil { mp.libcallsp = 0 } return res } // 硬件平台相关代码 // runtime/asm_arm64.s // func asmcgocall(fn, arg unsafe.Pointer) int32 // Call fn(arg) on the scheduler stack, // aligned appropriately for the gcc ABI. // See cgocall.go for more details. TEXT ·asmcgocall(SB),NOSPLIT,$0-20 MOVD fn+0(FP), R1 MOVD arg+8(FP), R0 MOVD RSP, R2 // save original stack pointer CBZ g, nosave MOVD g, R4 // Figure out if we need to switch to m->g0 stack. // We get called to create new OS threads too, and those // come in on the m->g0 stack already. Or we might already // be on the m->gsignal stack. MOVD g_m(g), R8 MOVD m_gsignal(R8), R3 CMP R3, g BEQ nosave MOVD m_g0(R8), R3 CMP R3, g BEQ nosave // Switch to system stack. MOVD R0, R9 // gosave_systemstack_switch<> and save_g might clobber R0 BL gosave_systemstack_switch<>(SB) MOVD R3, g BL runtime·save_g(SB) MOVD (g_sched+gobuf_sp)(g), R0 MOVD R0, RSP MOVD (g_sched+gobuf_bp)(g), R29 MOVD R9, R0 // Now on a scheduling stack (a pthread-created stack). // Save room for two of our pointers /*, plus 32 bytes of callee // save area that lives on the caller stack. */ MOVD RSP, R13 SUB $16, R13 MOVD R13, RSP MOVD R4, 0(RSP) // save old g on stack MOVD (g_stack+stack_hi)(R4), R4 SUB R2, R4 MOVD R4, 8(RSP) // save depth in old g stack (can't just save SP, as stack might be copied during a callback) BL (R1) MOVD R0, R9 // Restore g, stack pointer. R0 is errno, so don't touch it MOVD 0(RSP), g BL runtime·save_g(SB) MOVD (g_stack+stack_hi)(g), R5 MOVD 8(RSP), R6 SUB R6, R5 MOVD R9, R0 MOVD R5, RSP MOVW R0, ret+16(FP) RET nosave: // Running on a system stack, perhaps even without a g. // Having no g can happen during thread creation or thread teardown // (see needm/dropm on Solaris, for example). // This code is like the above sequence but without saving/restoring g // and without worrying about the stack moving out from under us // (because we're on a system stack, not a goroutine stack). // The above code could be used directly if already on a system stack, // but then the only path through this code would be a rare case on Solaris. // Using this code for all "already on system stack" calls exercises it more, // which should help keep it correct. MOVD RSP, R13 SUB $16, R13 MOVD R13, RSP MOVD $0, R4 MOVD R4, 0(RSP) // Where above code stores g, in case someone looks during debugging. MOVD R2, 8(RSP) // Save original stack pointer. BL (R1) // Restore stack pointer. MOVD 8(RSP), R2 MOVD R2, RSP MOVD R0, ret+16(FP) 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 }