文章目录

  • 一、什么是系统调用
  • 二、Golang标准库-syscall
    • 1. syscall无处不在
    • 2. syscall demo举例: go版本的strace
      • Strace
      • go版本的strace
  • 三、Go 语言中的系统调用
    • demo:使用 Syscall 函数查看当前进程 PID
  • 四、参考

一、什么是系统调用

In computing, a system call is the programmatic way in which a computer program requests a service from the kernel of the operating system it is executed on. This may include hardware-related services (for example, accessing a hard disk drive), creation and execution of new processes, and communication with integral kernel services such as process scheduling. System calls provide an essential interface between a process and the operating system.

系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等。系统调用提供了一个进程和操作系统之间的接口。

二、Golang标准库-syscall

syscall包包含一个指向底层操作系统原语的接口。

注意:该软件包已被锁定。标准以外的代码应该被迁移到golang.org/x/sys存储库中使用相应的软件包。这也是应用新系统或版本所需更新的地方。 Signal , Errno 和 SysProcAttr 在 golang.org/x/sys 中尚不可用,并且仍然必须从 syscall 程序包中引用。有关更多信息,请参见 https://golang.org/s/go1.4-syscall。

https://pkg.go.dev/golang.org/x/sys
该存储库包含用于与操作系统进行低级交互的补充Go软件包。

1. syscall无处不在

C 语言标准库中不少都是对操作系统提供的系统调用的封装,比如大家耳熟能详的 printf, gets, fopen 等,就分别是对 read, write, open 这些系统调用的封装。使用 ltrace 来追踪调用就可以清楚地看到这一点,例如:

#include <stdio.h>
/* The well-known "Hello World" */
int main(void) {printf("Hello World!\n");
}

对于上面这段代码编译后使用 ltrace 调试,即可得到如下输出:

name1e5s@asgard:~$ gcc test.c
name1e5s@asgard:~$ ltrace -S ./a.out
SYS_brk(0)                                                                                                           = 0x55eb2abba000
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_access("/etc/ld.so.preload", 04) = -2
SYS_openat(0xffffff9c, 0x7f2290c00428, 0x80000, 0) = 3
SYS_fstat(3, 0x7ffd2e03aa20) = 0
SYS_mmap(0, 0x21b06, 1, 2)  = 0x7f2290de4000
SYS_close(3) = 0
SYS_access("/etc/ld.so.nohwcap", 00) = -2
SYS_openat(0xffffff9c, 0x7f2290e08dd0, 0x80000, 0) = 3
SYS_read(3, "\177ELF\002\001\001\003", 832) = 832
SYS_fstat(3, 0x7ffd2e03aa80) = 0
SYS_mmap(0, 8192, 3, 34) = 0x7f2290de2000
SYS_mmap(0, 0x3f0ae0, 5, 2050) = 0x7f22907ee000
SYS_mprotect(0x7f22909d5000, 2097152, 0) = 0
SYS_mmap(0x7f2290bd5000, 0x6000, 3, 2066) = 0x7f2290bd5000
SYS_mmap(0x7f2290bdb000, 0x3ae0, 3, 50) = 0x7f2290bdb000
SYS_close(3) = 0
SYS_arch_prctl(4098, 0x7f2290de34c0, 0x7f2290de3e00, 0x7f2290de2988) = 0
SYS_mprotect(0x7f2290bd5000, 16384, 1) = 0
SYS_mprotect(0x55eb28ecf000, 4096, 1) = 0
SYS_mprotect(0x7f2290e06000, 4096, 1) = 0
SYS_munmap(0x7f2290de4000, 137990) = 0
puts("Hello World!" <unfinished ...>
SYS_fstat(1, 0x7ffd2e03b280) = 0
SYS_brk(0) = 0x55eb2abba000
SYS_brk(0x55eb2abdb000) = 0x55eb2abdb000
SYS_write(1, "Hello World!\n", 13Hello World!
) = 13
<... puts resumed> ) = 13
SYS_exit_group(0 <no return ...>
+++ exited (status 0) +++

其中 SYS_ 开头的均为系统调用,可见系统调用几乎是无处不在。

举个最常用的例子, fmt.Println(“hello world”), 这里就用到了系统调用 write, 我们翻一下源码。

func Println(a ...interface{}) (n int, err error) {return Fprintln(os.Stdout, a...)
}
Stdout = NewFile(uintptr(syscall.Stdout), "/dev/stdout")func (f *File) write(b []byte) (n int, err error) {if len(b) == 0 {return 0, nil}// 实际的write方法,就是调用syscall.Write()return fixCount(syscall.Write(f.fd, b))
}

2. syscall demo举例: go版本的strace

Strace

strace 是用于查看进程系统调用的工具, 一般使用方法如下:

strace -c 用于统计各个系统调用的次数


[root@localhost ~]# strace -c echo hello
hello
% time     seconds  usecs/call     calls    errors syscall
------ ----------- ----------- --------- --------- ----------------0.00    0.000000           0         1           read0.00    0.000000           0         1           write0.00    0.000000           0         3           open0.00    0.000000           0         5           close0.00    0.000000           0         4           fstat0.00    0.000000           0         9           mmap0.00    0.000000           0         4           mprotect0.00    0.000000           0         2           munmap0.00    0.000000           0         4           brk0.00    0.000000           0         1         1 access0.00    0.000000           0         1           execve0.00    0.000000           0         1           arch_prctl
------ ----------- ----------- --------- --------- ----------------
100.00    0.000000                    36         1 total
[root@localhost ~]#

stace 的实现原理是系统调用 ptrace, 我们来看下 ptrace 是什么。

man page 描述如下:

The ptrace() system call provides a means by which one process (the “tracer”) may observe and control the execution of another process (the “tracee”), and examine and change the tracee’s memory and registers. It is primarily used to implement breakpoint debuggingand system call tracing.

简单来说有三大能力:

追踪系统调用
读写内存和寄存器
向被追踪程序传递信号

ptrace接口:

int ptrace(int request, pid_t pid, caddr_t addr, int data);request包含:
PTRACE_ATTACH
PTRACE_SYSCALL
PTRACE_PEEKTEXT, PTRACE_PEEKDATA
等

tracer 使用 PTRACE_ATTACH 命令,指定需要追踪的PID。紧接着调用 PTRACE_SYSCALL。
tracee 会一直运行,直到遇到系统调用,内核会停止执行。 此时,tracer 会收到 SIGTRAP 信号,tracer 就可以打印内存和寄存器中的信息了。

接着,tracer 继续调用 PTRACE_SYSCALL, tracee 继续执行,直到 tracee退出当前的系统调用。
需要注意的是,这里在进入syscall和退出syscall时,tracer都会察觉。

go版本的strace

了解以上内容后,presenter 现场实现了一个go版本的strace, 需要在 linux amd64 环境编译。
https://github.com/silentred/gosys

// strace.go

package mainimport ("fmt""os""os/exec""syscall"
)func main() {var err errorvar regs syscall.PtraceRegsvar ss syscallCounterss = ss.init()fmt.Println("Run: ", os.Args[1:])cmd := exec.Command(os.Args[1], os.Args[2:]...)cmd.Stderr = os.Stderrcmd.Stdout = os.Stdoutcmd.Stdin = os.Stdincmd.SysProcAttr = &syscall.SysProcAttr{Ptrace: true,}cmd.Start()err = cmd.Wait()if err != nil {fmt.Printf("Wait err %v \n", err)}pid := cmd.Process.Pidexit := truefor {// 记得 PTRACE_SYSCALL 会在进入和退出syscall时使 tracee 暂停,所以这里用一个变量控制,RAX的内容只打印一遍if exit {err = syscall.PtraceGetRegs(pid, &regs)if err != nil {break}//fmt.Printf("%#v \n",regs)name := ss.getName(regs.Orig_rax)fmt.Printf("name: %s, id: %d \n", name, regs.Orig_rax)ss.inc(regs.Orig_rax)}// 上面Ptrace有提到的一个request命令err = syscall.PtraceSyscall(pid, 0)if err != nil {panic(err)}// 猜测是等待进程进入下一个stop,这里如果不等待,那么会打印大量重复的调用函数名_, err = syscall.Wait4(pid, nil, 0, nil)if err != nil {panic(err)}exit = !exit}ss.print()
}

// 用于统计信息的counter, syscallcounter.go

package mainimport ("fmt""os""text/tabwriter""github.com/seccomp/libseccomp-golang"
)type syscallCounter []intconst maxSyscalls = 303func (s syscallCounter) init() syscallCounter {s = make(syscallCounter, maxSyscalls)return s
}func (s syscallCounter) inc(syscallID uint64) error {if syscallID > maxSyscalls {return fmt.Errorf("invalid syscall ID (%x)", syscallID)}s[syscallID]++return nil
}func (s syscallCounter) print() {w := tabwriter.NewWriter(os.Stdout, 0, 0, 8, ' ', tabwriter.AlignRight|tabwriter.Debug)for k, v := range s {if v > 0 {name, _ := seccomp.ScmpSyscall(k).GetName()fmt.Fprintf(w, "%d\t%s\n", v, name)}}w.Flush()
}func (s syscallCounter) getName(syscallID uint64) string {name, _ := seccomp.ScmpSyscall(syscallID).GetName()return name
}

最后结果:

Run:  [echo hello]
Wait err stop signal: trace/breakpoint trap
name: execve, id: 59
name: brk, id: 12
name: access, id: 21
name: mmap, id: 9
name: access, id: 21
name: open, id: 2
name: fstat, id: 5
name: mmap, id: 9
name: close, id: 3
name: access, id: 21
name: open, id: 2
name: read, id: 0
name: fstat, id: 5
name: mmap, id: 9
name: mprotect, id: 10
name: mmap, id: 9
name: mmap, id: 9
name: close, id: 3
name: mmap, id: 9
name: arch_prctl, id: 158
name: mprotect, id: 10
name: mprotect, id: 10
name: mprotect, id: 10
name: munmap, id: 11
name: brk, id: 12
name: brk, id: 12
name: open, id: 2
name: fstat, id: 5
name: mmap, id: 9
name: close, id: 3
name: fstat, id: 5
hello
name: write, id: 1
name: close, id: 3
name: close, id: 31|read1|write3|open5|close4|fstat7|mmap4|mprotect1|munmap3|brk3|access1|execve1|arch_prctl

三、Go 语言中的系统调用

尽管 Go 语言具有 cgo 这样的设施可以方便快捷地调用 C 函数,但是其还是自己对系统调用进行了封装,以 amd64 架构为例, Go 语言中的系统调用是通过如下几个函数完成的:

// In syscall_unix.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)

demo:使用 Syscall 函数查看当前进程 PID

package mainimport ("fmt""syscall"
)func main() {pid, _, _ := syscall.Syscall(39, 0, 0, 0) // 用不到的就补上 0fmt.Println("Process id: ", pid)
}

输出如下:

$ go run test.go
Process id:  19184

四、参考

Golang标准库——syscall
参考URL: https://www.jianshu.com/p/44109d5e045b
Golang 与系统调用
参考URL: https://blog.csdn.net/weixin_33744141/article/details/89033990
[推荐阅读]Go 语言中的系统调用
参考URL: https://zhuanlan.zhihu.com/p/58285124

Golang标准库-syscall(什么是系统调用/Go 语言中的系统调用)相关推荐

  1. golang 标准库间依赖的可视化展示

    简介 国庆看完 << Go 语言圣经 >>,总想做点什么,来加深下印象.以可视化的方式展示 golang 标准库之间的依赖,可能是一个比较好的切入点.做之前,简单搜了下相关的内 ...

  2. Golang 标准库log的实现

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://gotaly.blog.51cto.com/8861157/1406905 前一篇 ...

  3. Golang标准库RPC实践及改进

    转载自:http://daizuozhuo.github.io/golang-rpc-practice/ 一直用Golang标准库里的的RPC package来进行远程调用,简单好用. 但是随着任务数 ...

  4. Golang标准库CHM格式文档

      上手Go后,想熟悉golang标准库来做一些项目.在学习和使用golang标准库的时候,发现golang标准库文档不太友好.主要是导航区域和内容区域无法同屏浏览,在包和包间.包内不同对象间来回切换 ...

  5. golang标准库os模块-文件目录相关

    golang标准库os模块-文件目录相关 本文视频教程:https://www.bilibili.com/video/BV1zR4y1t7Wj?from=search&seid=7990946 ...

  6. Golang标准库中的fmt

    Golang标准库中的fmt fmt包实现了类似C语言printf和scanf的格式化I/O.主要分为向外输出内容和获取输入内容两大部分. 1. 向外输出 标准库fmt提供了以下几种输出相关函数. P ...

  7. Go标准库syscall调用dll

    什么是系统调用   了解syscall包之前先了解下什么是系统调用.系统调用是程序向操作系统内核请求服务的过程,通常包含硬件相关的服务(例如访问硬盘),创建新进程等.系统调用提供了一个进程和操作系统之 ...

  8. golang标准库http服务器处理流程

    http标准库 golang本身就提供了http的标志库,在golang中可以轻松的编写http服务,本文主要是因为在编写http服务的过程中,对整个处理流程不是很了解故想了解一下. 示例代码 pac ...

  9. Golang 标准库提供的Log(一)

    原创作品,允许转载,转载时请务必以超链接形式标明文章 原始出处 .作者信息和本声明.否则将追究法律责任.http://gotaly.blog.51cto.com/8861157/1405754 Gol ...

最新文章

  1. 【科技金融】某平台互金产品设计流程和运营策略
  2. RecyclerView复杂适配器的终极形态?代码更解耦
  3. java 不可修改的集合对象_[改善Java代码]asList方法产生的List对象不可更改
  4. 一款不错的开源 Laravel 后台面板/CMS系统 —— LaraAdmin
  5. 关于 MySQL 8.0 新特性“隐藏索引”的一点思考
  6. 有什么软件可以测试win10电脑性能,微软能否翻盘 Win10系统4K高分优化实测
  7. php中的http是什么意思,请问php中三种http请求方式有什么区别呢(cURL,stream,socket)...
  8. 【D】分布式系统的CAP理论
  9. CSS定位设置实例——盒子的定位
  10. Android原生开发如何深入进阶?完整版开放下载
  11. modbus模拟器使用
  12. 数据结构分类之什么是线性结构、非线性结构
  13. 三星手机如何通过Exchange账户同步联系人到手机中?
  14. [附源码]Java计算机毕业设计SSM大学生学科竞赛管理系统
  15. c语言 申请变量函数,C语言中变量和函数
  16. Mac外接显示器调色方法
  17. 拼多多一晚被薅千万,倒赚276亿:一次蓄意营销的阴谋?
  18. PatchMatchNet实践(环境配置、相关软件安装)及理论学习笔记(更新中)
  19. 麒麟V10 arm 环境配置yum源
  20. 怎么实现抓取同行网站访客号码

热门文章

  1. 《数字中国建设整体布局规划》出炉,背后蕴含的数字城市巨大机遇
  2. PTA 九宫格输入法
  3. Android TV 上使用的RecyclerView和焦点框架,焦点框移动效果,完胜androidTvwidget的MainUpView
  4. Java基础(集合框架——Collection、List、Set、泛型)
  5. 我们写的论文有意义吗?
  6. 月薪两万不是梦,麻辣隔壁的
  7. java 圈复杂度_详解圈复杂度
  8. VMWare常见问题-CPU虚拟化引擎
  9. 将进酒、凉州词(两个版本)、 回乡偶书
  10. 世界最会赚钱的书店之一“茑屋书店“:数据驱动是秘密武器