cgo是哪个机场代码(cgo是什么意思) 您所在的位置:网站首页 cgo是哪个机场代码 cgo是哪个机场代码(cgo是什么意思)

cgo是哪个机场代码(cgo是什么意思)

2024-07-12 02:46| 来源: 网络整理| 查看: 265

打通C和GO:CGO入门剖析与实践

作者:panhuili,腾讯 IEG 后台开发工程师

一、CGO 快速入门 1.1、启用 CGO 特性

在 golang 代码中加入 import “C” 语句就可以启动 CGO 特性。这样在进行 go build 命令时,就会在编译和连接阶段启动 gcc 编译器。

// go.1.15// test1.gopackage mainimport C" // import C"更像是一个关键字,CGO工具在预处理时会删掉这一行func main {}

使用 -x 选项可以查看 go 程序编译过程中执行的所有指令。可以看到 golang 编译器已经为 test1.go 创建了 CGO 编译选项

[root@VM-centos ~/cgo_test/golink2]# go build -x test1.goWORK=/tmp/go-build330287398mkdir -p $WORK/b001/cd /root/cgo_test/golink2CGO_LDFLAGS=&apos"-g" "-O2"&apos /usr/lib/golang/pkg/tool/linux_amd64/cgo -objdir $WORK/b001/ -importpath command-line-arguments -- -I $WORK/b001/ -g -O2 ./test1.go # CGO编译选项cd $WORKgcc -fno-caret-diagnostics -c -x c - -o /dev/null || truegcc -Qunused-arguments -c -x c - -o /dev/null || truegcc -fdebug-prefix-map=a=b -c -x c - -o /dev/null || truegcc -gno-record-gcc-switches -c -x c - -o /dev/null || true....... 1.2、Hello Cgo

通过 import “C” 语句启用 CGO 特性后,CGO 会将上一行代码所处注释块的内容视为 C 代码块,被称为 序文(preamble)。

// test2.gopackage main//#include stdio.h> // 序文中可以链接标准C程序库import C"func main { C.putscgo 工具就会被调用,在 C 转换 Go、Go 转换 C 的之间生成各种文件。

2)系统的 C 编译器会被调用来处理包中所有的 C 文件。

3)所有独立的编译单元会被组合到一个 .o 文件。

4)生成的 .o 文件会在系统的连接器中对它的引用进行一次检查修复。

cgo 是一个 Go 语言自带的特殊工具,可以使用命令 go tool cgo 来运行。它可以生成能够调用 C 语言代码的 Go 语言源文件,也就是说所有启用了 CGO 特性的 Go 代码,都会首先经过 cgo 的"预处理"。

对 test2.go,cgo 工具会在同目录生成以下文件

_obj--| |--_cgo.o // C代码编译出的链接库 |--_cgo_main.c // C代码部分的main函数 |--_cgo_flags // C代码的编译和链接选项 |--_cgo_export.c // |--_cgo_export.h // 导出到C语言的Go类型 |--_cgo_gotypes.go // 导出到Go语言的C类型 |--test1.cgo1.go // 经过“预处理”的Go代码 |--test1.cgo2.c // 经过“预处理”的C代码 二、CGO 的 N 种用法

CGO 作为 Go 语言和 C 语言之间的桥梁,其使用场景可以分为两种:Go 调用 C 程序 和 C 调用 Go 程序。

2.1、Go 调用自定义 C 程序 // test3.gopackage main/*#cgo LDFLAGS: -L/usr/local/lib#include stdio.h>#include stdlib.h>#define REPEAT_LIMIT 3 // CGO会保留C代码块中的宏定义typedef struct{ // 自定义结构体 int repeat_time char* str}blobint SayHello使用_cgo_cmalloc 在 C 空间内申请内存使用该段 C 内存初始化一个[]byte 对象

3)将 string 拷贝到[]byte 对象

4)将该段 C 空间内存的地址返回

它的实现方式类似前述,切片的类型转换。不同在于切片的类型转换,是将 Go 空间内存暴露给 C 函数使用。而_Cfunc_CString 是将 C 空间内存暴露给 Go 使用。

_cgo_cmalloc

定义了一个暴露给 Go 的 C 函数,用于在 C 空间申请内存

与 C.CString对应的是从 C 字符串转 Go 字符串的转换函数 C.GoString。C.GoString函数的实现较为简单,检索 C 字符串长度,然后申请相同长度的 Go-string 对象,最后内存拷贝。

如下是 C.GoString的底层实现

//go:linkname _cgo_runtime_gostring runtime.gostringfunc _cgo_runtime_gostring

C 语言结构体中位字段对应的成员无法在 Go 语言中访问,如果需要操作位字段成员,需要通过在 C 语言中定义辅助函数来完成。对应零长数组的成员 entersyscall将当前的 M 与 P 剥离,防止 C 程序独占 M 时,阻塞 P 的调度。

2) asmcgocall将栈切换到 g0 的系统栈,并执行 C 函数调用

3) exitsyscall寻找合适的 P 来运行从 C 函数返回的 Go 程,优先选择调用 C 之前依附的 P,其次选择其他空闲的 P

下图是 Go 调 C 函数过程中,MPG 的调度过程。

当 Go 程在调用 C 函数时,会单独占用一个系统线程。因此如果在 Go 程中并发调用 C 函数,而 C 函数中又存在阻塞操作,就很可能会造成 Go 程序不停的创建新的系统线程,而 Go 并不会回收系统线程,过多的线程数会拖垮整个系统。

_cgoCheckPointer & _cgoCheckResult //go:linkname _cgoCheckPointer runtime.cgoCheckPointerfunc _cgoCheckPointer go:cgo_export_dynamic在内链模式 go:linkname _cgoexp_bb7421b6328a_hello _cgoexp_bb7421b6328a_hello将 Go 函数 _cgoexp_bb7421b6328a_hello链接到符号 _cgoexp_bb7421b6328a_hello上

3) go:cgo_export_static _cgoexp_bb7421b6328a_hello在外链模式 go:nosplit go:norace关闭溢出检测 关闭竞态管理

_cgoexp_bb7421b6328a_hello即为 C 调用 Go 函数的入口函数,之后调用到 _cgoexpwrap_25bb4eb897ab_GSayHello,最后调用到用户定义的 Go 函数 GSayHello。

_cgo_export.c

_cgo_export.c 包含了 C 调用 Go 函数的入口 和 暴露给 Go 的内存分配函数 _Cfunc__Cmalloc(void *v)。

C 代码较为简单,不过多分析

/* Code generated by cmd/cgo DO NOT EDIT. */#include stdlib.h>#include "_cgo_export.h"#pragma GCC diagnostic ignored "-Wunknown-pragmas"#pragma GCC diagnostic ignored "-Wpragmas"#pragma GCC diagnostic ignored "-Waddress-of-packed-member"extern void crosscall2(void (*fn)(void *, int, __SIZE_TYPE__), void *, int, __SIZE_TYPE__) // 保存C环境的上下文,并调起Go函数extern __SIZE_TYPE__ _cgo_wait_runtime_init_done(void)extern void _cgo_release_context(__SIZE_TYPE__)extern char* _cgo_topofstack(void)#define CGO_NO_SANITIZE_THREAD#define _cgo_tsan_acquire#define _cgo_tsan_release#define _cgo_msan_write(addr, sz)extern void _cgoexp_25bb4eb897ab_GSayHello(void *, int, __SIZE_TYPE__)CGO_NO_SANITIZE_THREADint GSayHello(char* value) // test1.cgo2.c中调用的 GSayHello{ __SIZE_TYPE__ _cgo_ctxt = _cgo_wait_runtime_init_done struct { char* p0 int r0 char __pad0[4] } __attribute__((__packed__, __gcc_struct__)) _cgo_a _cgo_a.p0 = value _cgo_tsan_release crosscall2(_cgoexp_25bb4eb897ab_GSayHello, &_cgo_a, 16, _cgo_ctxt) _cgo_tsan_acquire _cgo_release_context(_cgo_ctxt) return _cgo_a.r0}

crosscall2对应的底层函数是 runtime.cgocallback,cgocallback 会恢复 Golang 运行时所需的环境包括 Go 函数地址,栈帧和上下文,然后会调用到 cgocallback_gofunc。

cgocallback_gofunc,首先判断当前线程是否为 Go 线程,再讲线程栈切到 Go 程栈,再将函数地址,参数地址等信息入 Go 程栈,最后调用到 cgocallbackg。

cgocallbackg确认 Go 程准备完毕后,就将线程从系统调用状态退出(见上节 exitsyscall),此时程序运行在 G 栈上,进入 cgocallbackg1 函数。

cgocallbackg1调用 reflectcall,正式进入到用户定义的 Go 函数。

如下是函数调用关系:

从 Go 调入到 C 函数时,系统线程会被切到 G0 运行,之后从 C 再回调到 Go 时,会直接在同一个 M 上从 G0 切回到普通的 Go 程,在这个过程中并不会创建新的系统线程。

从原生 C 线程调用 Go 函数的流程与这个类似,C 程序在一开始就有两个线程,一个是 C 原生线程,一个是 Go 线程,当 C 函数调起 Go 函数时,会切到 Go 线程运行。

如下是 Go 调 C,C 再调 Go 过程中,MPG 的调度流程。

五、总结

CGO 是一个非常优秀的工具,大部分使用 CGO 所造成的问题,都是因为使用方法不规范造成的。希望本文可以帮助大家更好的使用 CGO。

参考资料:

1.Golang 源码

2.赵志强的博客

3.Go 语言高级编程

5.给出了一种会造成线程暴增的 cgo 错误使用方法: http://xiaorui.cc/archives/5408

6.给出了一种会造成内存溢出的 cgo 错误使用方法: https://blog.csdn.net/wei_gw201

收藏

举报



【本文地址】

公司简介

联系我们

今日新闻

    推荐新闻

    专题文章
      CopyRight 2018-2019 实验室设备网 版权所有