Golang 系统调用 syscall

0.476字数 1090阅读 6890

对于写后端语言来说的人,文件操作是很常见的。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/

推荐阅读更多精彩内容