最近在读GoLang的源码,源码中有一些跨平台的操作,Go处理的很有意思,在这整理一下。
以新建文件为例,对比一下几个常见平台的区别。
首先看下Windows平台的代码:
// 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_windows.go // openFileNolog的windows实现 func openFileNolog(name string, flag int, perm FileMode) (*File, error) { if name == "" { return nil, &PathError{Op: "open", Path: name, Err: syscall.ENOENT} } path := fixLongPath(name) // 跳转到了syscall.Open r, e := syscall.Open(path, flag|syscall.O_CLOEXEC, syscallMode(perm)) if e != nil { // We should return EISDIR when we are trying to open a directory with write access. if e == syscall.ERROR_ACCESS_DENIED && (flag&O_WRONLY != 0 || flag&O_RDWR != 0) { pathp, e1 := syscall.UTF16PtrFromString(path) if e1 == nil { var fa syscall.Win32FileAttributeData e1 = syscall.GetFileAttributesEx(pathp, syscall.GetFileExInfoStandard, (*byte)(unsafe.Pointer(&fa))) if e1 == nil && fa.FileAttributes&syscall.FILE_ATTRIBUTE_DIRECTORY != 0 { e = syscall.EISDIR } } } return nil, &PathError{Op: "open", Path: name, Err: e} } // 封装为File结构 f, e := newFile(r, name, "file"), nil if e != nil { return nil, &PathError{Op: "open", Path: name, Err: e} } return f, nil }
// syscall/syscall_windows.go func Open(path string, mode int, perm uint32) (fd Handle, err error) { if len(path) == 0 { return InvalidHandle, ERROR_FILE_NOT_FOUND } pathp, err := UTF16PtrFromString(path) if err != nil { return InvalidHandle, err } var access uint32 switch mode & (O_RDONLY | O_WRONLY | O_RDWR) { case O_RDONLY: access = GENERIC_READ case O_WRONLY: access = GENERIC_WRITE case O_RDWR: access = GENERIC_READ | GENERIC_WRITE } if mode&O_CREAT != 0 { access |= GENERIC_WRITE } if mode&O_APPEND != 0 { access &^= GENERIC_WRITE access |= FILE_APPEND_DATA } sharemode := uint32(FILE_SHARE_READ | FILE_SHARE_WRITE) var sa *SecurityAttributes if mode&O_CLOEXEC == 0 { sa = makeInheritSa() } var createmode uint32 switch { case mode&(O_CREAT|O_EXCL) == (O_CREAT | O_EXCL): createmode = CREATE_NEW case mode&(O_CREAT|O_TRUNC) == (O_CREAT | O_TRUNC): createmode = CREATE_ALWAYS case mode&O_CREAT == O_CREAT: createmode = OPEN_ALWAYS case mode&O_TRUNC == O_TRUNC: createmode = TRUNCATE_EXISTING default: createmode = OPEN_EXISTING } var attrs uint32 = FILE_ATTRIBUTE_NORMAL if perm&S_IWRITE == 0 { attrs = FILE_ATTRIBUTE_READONLY if createmode == CREATE_ALWAYS { // We have been asked to create a read-only file. // If the file already exists, the semantics of // the Unix open system call is to preserve the // existing permissions. If we pass CREATE_ALWAYS // and FILE_ATTRIBUTE_READONLY to CreateFile, // and the file already exists, CreateFile will // change the file permissions. // Avoid that to preserve the Unix semantics. h, e := CreateFile(pathp, access, sharemode, sa, TRUNCATE_EXISTING, FILE_ATTRIBUTE_NORMAL, 0) switch e { case ERROR_FILE_NOT_FOUND, _ERROR_BAD_NETPATH, ERROR_PATH_NOT_FOUND: // File does not exist. These are the same // errors as Errno.Is checks for ErrNotExist. // Carry on to create the file. default: // Success or some different error. return h, e } } } if createmode == OPEN_EXISTING && access == GENERIC_READ { // Necessary for opening directory handles. attrs |= FILE_FLAG_BACKUP_SEMANTICS } if mode&O_SYNC != 0 { const _FILE_FLAG_WRITE_THROUGH = 0x80000000 attrs |= _FILE_FLAG_WRITE_THROUGH } // 跳转CreateFile return CreateFile(pathp, access, sharemode, sa, createmode, attrs, 0) } func CreateFile(name *uint16, access uint32, mode uint32, sa *SecurityAttributes, createmode uint32, attrs uint32, templatefile int32) (handle Handle, err error) { // 跳转Syscall9 r0, _, e1 := Syscall9(procCreateFileW.Addr(), 7, uintptr(unsafe.Pointer(name)), uintptr(access), uintptr(mode), uintptr(unsafe.Pointer(sa)), uintptr(createmode), uintptr(attrs), uintptr(templatefile), 0, 0) handle = Handle(r0) if handle == InvalidHandle { err = errnoErr(e1) } return }
// syscall/dll_windows.go // 封装了Syscall9 func Syscall9(trap, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2 uintptr, err Errno) // syscall/zsyscall_windows.go // Syscall9中传入的API名为procCreateFileW procCreateFileW = modkernel32.NewProc("CreateFileW") // 实际上最终调用了windows API CreateFileW,下面是CPP版本的API定义 // 到这里,也可以看到,通过Syscall的定义,比较巧妙的做了一定程度上的解耦 HANDLE CreateFileW( [in] LPCWSTR lpFileName, [in] DWORD dwDesiredAccess, [in] DWORD dwShareMode, [in, optional] LPSECURITY_ATTRIBUTES lpSecurityAttributes, [in] DWORD dwCreationDisposition, [in] DWORD dwFlagsAndAttributes, [in, optional] HANDLE hTemplateFile );
// runtime/syscall_windows.go // Syscall9是在这里实现的 //go:linkname syscall_Syscall9 syscall.Syscall9 //go:nosplit func syscall_Syscall9(fn, nargs, a1, a2, a3, a4, a5, a6, a7, a8, a9 uintptr) (r1, r2, err uintptr) { return syscall_SyscallN(fn, a1, a2, a3, a4, a5, a6, a7, a8, a9) } //go:linkname syscall_SyscallN syscall.SyscallN //go:nosplit func syscall_SyscallN(trap uintptr, args ...uintptr) (r1, r2, err uintptr) { nargs := len(args) // asmstdcall expects it can access the first 4 arguments // to load them into registers. var tmp [4]uintptr switch { case nargs < 4: copy(tmp[:], args) args = tmp[:] case nargs > maxArgs: panic("runtime: SyscallN has too many arguments") } lockOSThread() defer unlockOSThread() c := &getg().m.syscall c.fn = trap c.n = uintptr(nargs) c.args = uintptr(noescape(unsafe.Pointer(&args[0]))) cgocall(asmstdcallAddr, unsafe.Pointer(c)) return c.r1, c.r2, c.err } // 最后,通过cgocall,将go的调用,转换为c的调用
// 然后回到openFileNolog中 // 在openFileNolog中,继续调用newFile,整体封装为File结构,原路返回 func newFile(h syscall.Handle, name string, kind string) *File { if kind == "file" { var m uint32 if syscall.GetConsoleMode(h, &m) == nil { kind = "console" } if t, err := syscall.GetFileType(h); err == nil && t == syscall.FILE_TYPE_PIPE { kind = "pipe" } } f := &File{&file{ pfd: poll.FD{ Sysfd: h, IsStream: true, ZeroReadIsEOF: true, }, name: name, }} runtime.SetFinalizer(f.file, (*file).close) // Ignore initialization errors. // Assume any problems will show up in later I/O. f.pfd.Init(kind, false) return f }