082016
 

一、系统调用概述

系统调用是受控的内核入口,借助于这一机制,进程可以请求内核以自己的名义去执行某些动作。Linux 内核以 C 语言语法 API 接口形式(头文件),提供有一系列服务供程序访问。可以通过 man 2 syscall 查看系统调用信息。

关于系统调用,需要注意以下几点:
1、系统调用将处理器从用户态切换到核心态,以便 CPU 访问受到保护的内核内存;
2、系统调用的组成是固定的,每个系统调用都由一个唯一的数字来标识;
3、每个系统调用可辅之以一套参数,对用户控件(进程虚拟地址控件)与内核空间之间(相互)传递的信息加以规范;

以C语言为例,执行系统调用时,幕后会历经诸多步骤。以 x86-32 平台为例,按时间发生顺序对这些步骤加以分析:
1、应用程序通过 C 语言函数库中的外壳(wrapper)函数,来发起系统调用;
2、对系统调用中断处理例程来说,外壳函数必须保证所有的系统调用参数可用。通过堆栈,这些参数传入外壳函数,但内核却希望将这些参数置入特定寄存器。因此,外壳函数会将上述参数复制到寄存器;
3、由于所有系统调用进入内核的方式相同,内核需要设法区分每个系统调用。为此,外壳函数会将系统调用编号复制到一个特殊的 CPU 寄存器 (%eax) 中;
4、外壳函数执行一条中断机器指令(int 0×80),引发处理器从用户态切换到核心态,并执行系统中断 0×80(十进制128)的中断矢量所之指向的代码;(2.6内核 和 glibc 2.3.2 以后的版本支持 sysenter 指令,进入内核速度更快);
5、为响应中断 0×80,内核会调用 system_call 例程(内核源码中 arch/i386/entry.S)来处理这次中断;
6、若系统调用服务例程的返回值表明调用有误,外壳函数会使用该值来设置全局变量 errno,然后外壳函数会返回到调用程序,并同时返回一个整数值,以表明系统调用是否成功;

二、Go 语言封装的系统调用

Go 语言调用系统调用,并没有使用系统提供的 C 语言函数形式,而是自己封装了系统调用。以 AMD64 为例,Go 语言提供了如下调用系统调用的方式:

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)

其中,Syscall 和 RawSyscall 的区别如下:(以6结尾的一样)
从源码可以看出,Syscall 开始和结束,分别调用了 runtime 中的进入系统调用和退出系统调用的函数,这就说明,系统调用被 runtime 运行时(调度器)管理,系统调用可以在任何 goroutine 中执行;而 RawSyscall 并没有,因此它可能会阻塞,导致整个程序阻塞。我们应该总是使用 Syscall,RawSyscall 存在的意义是为那些永远不会阻塞的系统调用准备的,比如 Getpid。我们自己的程序需要时,应该用 Syscall。

Go 中 Syscall 的实现,在汇编文件 syscall/asm_linux_amd64.s 中:

// func Syscall(trap int64, a1, a2, a3 int64) (r1, r2, err int64);
// Trap # in AX, args in DI SI DX R10 R8 R9, return in AX DX
// Note that this differs from "standard" ABI convention, which
// would pass 4th arg in CX, not R10.

TEXT    ·Syscall(SB),NOSPLIT,$0-56
    CALL    runtime·entersyscall(SB)
    MOVQ    a1+8(FP), DI
    MOVQ    a2+16(FP), SI
    MOVQ    a3+24(FP), DX
    MOVQ    $0, R10
    MOVQ    $0, R8
    MOVQ    $0, R9
    MOVQ    trap+0(FP), AX    // syscall entry
    SYSCALL     // 进入内核,执行内核处理例程
    // 0xfffffffffffff001 是 linux MAX_ERRNO 取反 转无符号,http://lxr.free-electrons.com/source/include/linux/err.h#L17
    CMPQ    AX, $0xfffffffffffff001        // 发生错误,r1=-1
    JLS    ok
    MOVQ    $-1, r1+32(FP)
    MOVQ    $0, r2+40(FP)
    NEGQ    AX             // 错误码,因为错误码是负数,这里转为正数
    MOVQ    AX, err+48(FP)
    CALL    runtime·exitsyscall(SB)
    RET
ok:
    MOVQ    AX, r1+32(FP)
    MOVQ    DX, r2+40(FP)
    MOVQ    $0, err+48(FP)
    CALL    runtime·exitsyscall(SB)
    RET

相关参考文献
https://groups.google.com/forum/#!searchin/golang-nuts/Syscall$20RawSyscall/golang-nuts/y9lT_1loJj4/g4ZrYB2_80YJ
《The Linux Programming Interface —A Linux and UNIX System Programming Handbook》

2,063 浏览数

 评论

您可以使用这些 HTML 标签和属性: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <strike> <strong>