Skip to content

Latest commit

 

History

History
114 lines (84 loc) · 3.18 KB

10-cgo.md

File metadata and controls

114 lines (84 loc) · 3.18 KB

10 cgo

「cgo 不是银弹」,cgo 是连接 Go 与 C (乃至其他任何语言)之间的桥梁。 cgo 性能远不及原生 Go 程序的性能,执行一个 cgo 调用的代价很大。 下图展示了 cgo, go, c 之间的性能差异(网络 I/O 场景):

图1: cgo v.s. Co v.s. C,图取自 changkun/cgo-benchmarks

本文则具体研究 cgo 在运行时中的实现方式。

入口

先来编写一个最简单的 cgo 程序:

package main

/*
#include "stdio.h"
void print() {
	printf("hellow, cgo");
}
*/
import "C"

func main() {
	C.print()
}

我们先观察一下汇编的结果:

TEXT main._Cfunc_print(SB) _cgo_gotypes.go
  _cgo_gotypes.go:40	0x40503c0		65488b0c2530000000	MOVQ GS:0x30, CX				
  (...)
  _cgo_gotypes.go:41	0x40503e7		488b053aca0600		MOVQ main._cgo_fd63072f180f_Cfunc_print(SB), AX	
  _cgo_gotypes.go:41	0x40503ee		48890424		MOVQ AX, 0(SP)					
  _cgo_gotypes.go:41	0x40503f2		488b442418		MOVQ 0x18(SP), AX				
  _cgo_gotypes.go:41	0x40503f7		4889442408		MOVQ AX, 0x8(SP)				
  _cgo_gotypes.go:41	0x40503fc		e8ff3bfbff		CALL runtime.cgocall(SB)			
  (...)

TEXT main.main(SB) /Users/changkun/dev/go-under-the-hood/demo/10-cgo/main.go
  main.go:11		0x4050420		65488b0c2530000000	MOVQ GS:0x30, CX			
  (...)
  main.go:12		0x405043b		e880ffffff		CALL main._Cfunc_print(SB)		
  (...)	

说明 Go 代码在进入 C 代码前,最终以用编译器配合的形式,进入了运行时的 runtime.cgocall

再来看一下整个编译过程中的临时文件,临时文件中的入口文件为 main.cgo1.go

// Code generated by cmd/cgo; DO NOT EDIT.

//line main.go:1:1
package main

/*
#include "stdio.h"
void print() {
	printf("hellow, cgo");
}
*/
import _ "unsafe"

func main() {
	(_Cfunc_print)()
}

可以看到 Go 编译器会将我们原有的 cgo 调用替换为:_Cfunc_print。 我们可以在 _cgo_gotypes.go 中看到这个函数的定义:

//go:cgo_unsafe_args
func _Cfunc_print() (r1 _Ctype_void) {
    // 调用 _cgo_runtime_cgocall 传递 C 函数的入口地址以及相关参数
	_cgo_runtime_cgocall(_cgo_222b4724d882_Cfunc_print, uintptr(unsafe.Pointer(&r1)))
	if _Cgo_always_false {
	}
	return
}

_cgo_runtime_cgocall 的定义:

//go:linkname _cgo_runtime_cgocall runtime.cgocall
func _cgo_runtime_cgocall(unsafe.Pointer, uintptr) int32

可以看到编译器通过编译标志 go:linkname 将这个调用链接为了 runtime.cgocall。 因此,从 Go 进入 C 空间的 cgo 调用,以 Go 程序为主体(运行时依然存在),通过编译器的配合, 当需要调用 C 代码时,会向运行时传递 C 函数的入口地址及所需传递的参数。

那么剩下的工作就是去分析 runtime.cgocall 这个调用如何与 Go 运行时进行交互了。

cgocall

TODO:

进一步阅读的参考文献

  1. Command cgo
  2. LINUX SYSTEM CALL TABLE FOR X86 64

许可

Go under the hood | CC-BY-NC-ND 4.0 & MIT © changkun