Golang 系統調用 syscall

對于寫后端語言來說的人,文件操作是很常見的。go對文件操作的支持非常的好。今天通過go中文件操作記錄下syscall相關內容。

先看下文件定義:

type File struct {
    *file
}
// file is the real representation of *File.
// The extra level of indirection ensures that no clients of os
// can overwrite this data, which could cause the finalizer
// to close the wrong file descriptor.
type file struct {
    fd      int
    name    string
    dirinfo *dirInfo // nil unless directory being read
}

// Auxiliary information if the File describes a directory
type dirInfo struct {
    buf  []byte // buffer for directory I/O
    nbuf int    // length of buf; return value from Getdirentries
    bufp int    // location of next record in buf.
}

是不是夠簡潔的,而且注釋寫的很清楚。嘿嘿

我們從文件創建開始

// Create creates the named file with mode 0666 (before umask), truncating
// it if it already exists. If successful, methods on the returned
// File can be used for I/O; the associated file descriptor has mode
// O_RDWR.
// If there is an error, it will be of type *PathError.
func Create(name string) (*File, error) {
    return OpenFile(name, O_RDWR|O_CREATE|O_TRUNC, 0666)
}

第一個參數是文件名,第二個參數是文件模式,第三個參數是文件權限,默認權限是0666
O_RDWR O_CREATE O_TRUNC是file.go文件中定義好的一些常量,標識文件以什么模式打開,常見的模式有讀寫,只寫,只讀,權限依次降低。

// Flags to OpenFile wrapping those of the underlying system. Not all
// flags may be implemented on a given system.
const (
    O_RDONLY int = syscall.O_RDONLY // open the file read-only.
    O_WRONLY int = syscall.O_WRONLY // open the file write-only.
    O_RDWR   int = syscall.O_RDWR   // open the file read-write.
    O_APPEND int = syscall.O_APPEND // append data to the file when writing.
    O_CREATE int = syscall.O_CREAT  // create a new file if none exists.
    O_EXCL   int = syscall.O_EXCL   // used with O_CREATE, file must not exist
    O_SYNC   int = syscall.O_SYNC   // open for synchronous I/O.
    O_TRUNC  int = syscall.O_TRUNC  // if possible, truncate file when opened.
)

syscall 里面也是一些常量

    O_RDWR                           = 0x2
    O_RSYNC                          = 0x101000
    O_SYNC                           = 0x101000
    O_TRUNC                          = 0x200
    O_WRONLY                         = 0x1

OpenFile 函數參數介紹完,進到函數中,看到關鍵一句

r, e = syscall.Open(name, flag|syscall.O_CLOEXEC, syscallMode(perm))

看到syscall對于文件的操作進行了封裝,繼續進入

func Open(path string, mode int, perm uint32) (fd int, err error) {
    return openat(_AT_FDCWD, path, mode|O_LARGEFILE, perm)
}

//sys   openat(dirfd int, path string, flags int, mode uint32) (fd int, err error)

func Openat(dirfd int, path string, flags int, mode uint32) (fd int, err error) {
    return openat(dirfd, path, flags|O_LARGEFILE, mode)
}
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
    }
    r0, _, e1 := Syscall6(SYS_OPENAT, uintptr(dirfd), uintptr(unsafe.Pointer(_p0)), uintptr(flags), uintptr(mode), 0, 0)
    use(unsafe.Pointer(_p0))
    fd = int(r0)
    if e1 != 0 {
        err = errnoErr(e1)
    }
    return
}

跟進Syscall6,看到

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func Syscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)
func RawSyscall6(trap, a1, a2, a3, a4, a5, a6 uintptr) (r1, r2 uintptr, err Errno)

這里,看到相似的函數有四個,后面沒數字的,就是4個參數,后面為6的,就是6個參數。調用的是操作系統封裝好的API。可以 man syscall 或者這里http://man7.org/linux/man-pages/man2/syscall.2.html#top_of_page看下詳細信息。

NAME
       syscall - indirect system call

SYNOPSIS
       #define _GNU_SOURCE         /* See feature_test_macros(7) */
       #include <unistd.h>
       #include <sys/syscall.h>   /* For SYS_xxx definitions */

       long syscall(long number, ...);

對于不同架構的參數:

       arch/ABI      arg1  arg2  arg3  arg4  arg5  arg6  arg7  Notes
       ──────────────────────────────────────────────────────────────────
       arm/OABI      a1    a2    a3    a4    v1    v2    v3
       arm/EABI      r0    r1    r2    r3    r4    r5    r6
       arm64         x0    x1    x2    x3    x4    x5    -
       blackfin      R0    R1    R2    R3    R4    R5    -
       i386          ebx   ecx   edx   esi   edi   ebp   -
       ia64          out0  out1  out2  out3  out4  out5  -
       mips/o32      a0    a1    a2    a3    -     -     -     See below
       mips/n32,64   a0    a1    a2    a3    a4    a5    -
       parisc        r26   r25   r24   r23   r22   r21   -
       s390          r2    r3    r4    r5    r6    r7    -
       s390x         r2    r3    r4    r5    r6    r7    -
       sparc/32      o0    o1    o2    o3    o4    o5    -
       sparc/64      o0    o1    o2    o3    o4    o5    -
       x86_64        rdi   rsi   rdx   r10   r8    r9    -
       x32           rdi   rsi   rdx   r10   r8    r9    -

好了,到這里大致上有了解了。我們在看下細節的東西。

func Syscall(trap, a1, a2, a3 uintptr) (r1, r2 uintptr, err Errno)

系統調用:
第一個參數是系統調用號,每個系統調用在操作系統里面都有一個唯一的操作碼,后面的參數是系統調用所需要的參數。返回的參數是系統調用的結果和錯誤(如果有的話)

r0, _, e1 := Syscall(SYS_OPENAT, uintptr(unsafe.Pointer(_p0)), uintptr(mode), 0)

好了,回到我們系統調用的地方,操作碼為SYS_OPENAT 到定義常量的地方,我們看到,這里定義的都是系統調用的操作碼,細心的你可能已經看到了,還有一個SYS_OPEN,那他們的差別是什么呢?
從Linux2.6.16開始,linux內核提供了一系列新的系統調用,為了和以前的系統調用兼容和區分,這些新的系統調用就以at結尾,它們在執行與傳統系統調用相似任務的同時,還提供了一些附加功能,對某些程序非常有用,這些系統調用使用目錄文件描述符來解釋相對路徑。

系統調用參數講完了,說下RawSyscall 和 Syscall的區別吧。Syscall在開始和結束的時候,會分別調用runtime中的進入系統和退出系統的函數,所以Syscall是受調度器控制的,因為調度器有開始和結束的的事件。而RawSyscall則不受調度器控制,
RawSyscall 可能會導致其他正在運行的線程(協程)阻塞,調度器可能會在一段時間后運行它們,但是也有可能不會。所以,我們在進行系統調用的時候,應該極力避免使用RawSyscall,除非你確定這個操作是非阻塞的。

看完了系統調用,但是系統調用到底有什么用呢??問的好,其實,系統調用的函數是操作系統提供的,也就是如果我們想用系統的功能,你就必須使用系統調用。比如上面講的文件操作(創建,讀取,更新,刪除),網絡操作(監聽端口,接受請求和數據,發送數據),最近很火的docker,實現方式也是系統調用(NameSpace + CGroup),簡單的在命令行執行 echo hello,也用到了系統調用。不信?來看看

jin@Desktop:~$ strace -c echo hello
hello
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------
  0.00    0.000000           0         1           read
  0.00    0.000000           0         1           write
  0.00    0.000000           0         3           open
  0.00    0.000000           0         5           close
  0.00    0.000000           0         4           fstat
  0.00    0.000000           0         8           mmap
  0.00    0.000000           0         4           mprotect
  0.00    0.000000           0         1           munmap
  0.00    0.000000           0         3           brk
  0.00    0.000000           0         3         3 access
  0.00    0.000000           0         1           execve
  0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    35         3 total
jin@Desktop:~$ 

看到沒有,滿滿的都是系統調用。go 提供了 linux下面提供了303個系統調用。不管你實現多么牛逼的功能,都離不開這些系統調用,再牛逼的系統,也離不開cup的指令集,致“國家高新企業”中興。

參考資料:
linux syscall 中文資料:https://www.ibm.com/developerworks/cn/linux/kernel/syscall/part1/appendix.html
linux syscall 英文資料:https://syscalls.kernelgrok.com/

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容