前言
依据《Golang 如何实现自举(一)》的相干疏导,晓得了 go1.3 的 go 编译是须要 go_bootstrap、然而生成 go_bootstrap,须要 dist 工具进行生成。那么本期次要关注 dist 工具。
1.dist 工具介绍
其实 dist 工具是属于 go 的一个疏导工具,它负责构建 C 程序(如 Go 编译器)和 go 工具的初始疏导正本。它也能够作为一个无所不包用 shell 脚本替换以前实现的零工。通过“go tool dist”命令能够操作该工具。该工具不同零碎下对应在 pkg/tool/ 下的目录中。
<center> 图 1 -1-1 dist 工具介绍 </center>
那么来看一下 dist 工作都有哪些操作,如图 1 -1-1。能够看出 dist 工作有 6 个操作,别离为打印装置信息,编译 go_boostrap, 清理编译文件,查看 go env,装置拷贝 go 工具,查看 go 版本, 这几个操作。
通过对《【Golang 源码剖析】Golang 如何实现自举(一)》的理解,晓得 dist 是 C 源码所写。linux 下是通过 make.bash 中 gcc 编译进去的,命令如下:
#gcc -O2 -Wall -Werror -ggdb -o cmd/dist/dist -Icmd/dist '-DGOROOT_FINAL="/mnt"' cmd/dist/buf.c cmd/dist/build.c cmd/dist/buildgc.c cmd/dist/buildruntime.c cmd/dist/goc2c.c cmd/dist/main.c cmd/dist/unix.c cmd/dist/windows.c
2.dist 文件介绍
所有学习的本源都是先看看官网文档怎么说,而后学习能力强的能够在看看源码,加深对学习对了解。
看 dist 目录前,先在看看它对应的文档:
https://github.com/golang/go/…
文档中说:Dist 自身是用非常简单的 C 编写的。所有与 C 库的交互,甚至规范的 C 库也被限度在单个零碎特定的文件中(plan9.c,unix.c,windows.c),以进步可移植性。须要的性能其余文件应通过可移植性层公开。职能在可移植层中以 x 前缀结尾,否则应用与现有性能雷同的名称,或与现有性能混同。例如,xprintf 是可移植的 printf。
到目前为止,dist 中最常见的数据类型是字符串和字符串。然而,dist 应用了两个命名为而不是应用 char 和 char * 数据结构 Buf 和 Vec,它们领有它们指向的所有数据。Buf 操作是以 b 结尾的函数;Vec 操作是以 v 结尾的函数。任何函数申明的根本模式堆栈上的 Buf 或 Vecs 应该是
void myfunc(void)
{
Buf b1, b2;
Vec v1;
binit(&b1);
binit(&b2);
vinit(&v1);
... main code ...
bprintf(&b1, "hello, world");
vadd(&v1, bstr(&b1)); // v1 takes a copy of its argument
bprintf(&b2, "another string");
vadd(&v1, bstr(&b2)); // v1 now has two strings
bfree(&b1);
bfree(&b2);
vfree(&v1);
}
binit / vinit 调用筹备要应用的缓冲区或向量,从而初始化 数据结构以及 bfree / vfree 调用开释它们仍在的任何内存保持。应用这个习惯用法能够给咱们提供词法范畴的调配。
看完文档的一些根底介绍之后,能够来看看 dist 对应源码文件作用。
<center> 图 2 -1-1 dist 对应源码 </center>
对应源码如图 2 -1- 1 所示,dist 源码对应有 8 个 c 文件和 2 个头文件,那么来解析下各个 c 文件之间的用处。
- main.c 文件:该文件为文件入口,不过属于伪文件入口。因为文件依据零碎判断最终是通过 unix.c 或者是 windows.c 作为入口。
- unix.c 文件:unix/linux 入口文件。
- windows.c 文件:windows 入口文件。
- buf.c 文件:提供了对 Buf 和 Vec 的操作。
- build.c 文件:初始化对 dist 的任何调用,即运行 dist 时须要调用 build.c 中的函数执行初始化。
- buildgc.c 文件:构建 cmd/gc 时的辅助文件。
- buildruntime.c 文件:构建 pkg/runtime 时的辅助文件。
- goc2c.c 文件:将.goc 文件转为.c 文件。一个.goc 文件是一个组合体:蕴含 Go 代码和 C 代码。留神:goc 文件和 cgo 是不一样的。
3.dist 源码剖析
在钻研源码前,能够先看一下 go_boostrap 是如何编译进去的。依据对《【Golang 源码剖析】Golang 如何实现自举(一)》得悉 go_boostrap 是通过如下命令编译:
#/mnt/pkg/tool/linux_amd64/dist boostrap -a -v
<center> 图 3 -1-1 执行 dist 命令 </center>
执行 dist 命令后,能够看进去编译 boostrap 时,相应编译来 lib、cmd、pkg 相应问题。接下来,通过 gdb 来理解 dist 编译 boostrap 的过程。
3.1 调试带参数的 dist
<center> 图 3 -1-2 调试 dist</center>
在调试 dist 过程中,最好应用 src/cmd/dist/dist 编译的 dist 文件。因为在执行 dist boostrap 之后会清理掉“/mnt/pkg/tool/linux_amd64/dist”中文件, 编译时去掉“-O2”。应用 gdb 进行调试能够输出:
#gdb -c /mnt/src/cmd/dist/dist
进入终端后,再次输出:
(gdb)set args bootstrap -a -v
这样就能够调试带参数的 dist 如图 3 -1-2 所示。
3.2 解析 dist 的入口源码
在查看 dist 源码之前,首先来看一下 dist/main.c 源码,如下:
#include "a.h"
int vflag;
char *argv0;
// cmdtab records the available commands.
static struct {
char *name;
void (*f)(int, char**);
} cmdtab[] = {{"banner", cmdbanner}, // 查看编译信息函数
{"bootstrap", cmdbootstrap}, //bootstrap 函数
{"clean", cmdclean}, // 清理 cmd 函数
{"env", cmdenv}, // 查看 go env 函数
{"install", cmdinstall}, // 装置 cmd 函数
{"version", cmdversion}, // 查看 go 版本函数
};
// The OS-specific main calls into the portable code here.
void
xmain(int argc, char **argv)
{
int i;
if(argc <= 1)
usage();
// 依据参数命令不同的函数
for(i=0; i<nelem(cmdtab); i++) {if(streq(cmdtab[i].name, argv[1])) {cmdtab[i].f(argc-1, argv+1);
return;
}
}
xprintf("unknown command %s\n", argv[1]);
usage();}
依据源码能够得悉,bootstrap 会调用 cmdbootstrap 函数,而编译 go_bootstrap 其实也在 cmdbootstrap 函数中。
3.3 解析 cmdbootstrap 函数
接下来看一下对应 cmdbootstrap 函数的实现:
void
cmdbootstrap(int argc, char **argv)
{
int i;
Buf b;
char *oldgoos, *oldgoarch, *oldgochar;
binit(&b);
ARGBEGIN{
case 'a': // 承受 - a 参数, 示意编译全副
rebuildall = 1;
break;
case 'v': // 承受 - v 参数, 打印装置信息
vflag++;
break;
default:
usage();}ARGEND
if(argc > 0)
usage();
if(rebuildall)
clean(); // 清理装置内容信息
goversion = findgoversion();
setup();
xsetenv("GOROOT", goroot); // 设置 GOROOT 环境变量
xsetenv("GOROOT_FINAL", goroot_final); // 设置 GOROOT_FINAL 环境变量
// For the main bootstrap, building for host os/arch.
oldgoos = goos;
oldgoarch = goarch;
oldgochar = gochar;
goos = gohostos;
goarch = gohostarch;
gochar = gohostchar;
xsetenv("GOARCH", goarch);
xsetenv("GOOS", goos);
for(i=0; i<nelem(buildorder); i++) {install(bprintf(&b, buildorder[i], gohostchar)); // 编译并装置
if(!streq(oldgochar, gohostchar) && xstrstr(buildorder[i], "%s"))
install(bprintf(&b, buildorder[i], oldgochar)); // 编译并装置
}
goos = oldgoos;
goarch = oldgoarch;
gochar = oldgochar;
xsetenv("GOARCH", goarch);
xsetenv("GOOS", goos);
// Build pkg/runtime for actual goos/goarch too.
if(!streq(goos, gohostos) || !streq(goarch, gohostarch))
install("pkg/runtime"); 编译并装置 runtime
bfree(&b);
}
cmdbootstrap 函数比较简单,次要是做了一些承受参数,清理装置内容,初始化环境变量等操作。其实比拟要害的是 install 函数。
3.4 解析 install 函数过程
<center> 图 3 -4-1 dist 编译过程 </center>
是对编译参数的拼装,其实最终会调用 runv 函数进行编译, 而 runv 函数又会依据不同的零碎调用不同 genrun 函数。如果是 unix/linux 系列的会调用 unix.c 中的 genrun,如果是 windows 会调用 windows.c 中的 genrun,genrun 函数中进行拼装参数后。会依据零碎不同调用不同的执行函数。
<center> 图 3 -4-2 调试 go 源码编译 </center>
其实 go 源码编译会调用“/mnt/pkg/tool/linux_amd64/6g”,这个 6g 其实是不固定的文件。咱们能够来调试看看。
4. 调试 6g
<center> 图 4 -1 调试 6g</center>
调试 6g,下 mian 函数断点。能够清晰的看到应用来 src/lib9/main.c 中的 main。这一块调用来 Plan 9 C,而后 plan9 中又调用 lex 生成的源码做词法解析。
<center> 图 4 -2 lex</center>
对应在 src/cmd/gc/lex.c 中,lex 又联合来 yacc 做语法解析。最终生成对应的可执行文件。
总结
- dist 工具是属于 go 的一个疏导工具。
- go_boostrap 是通过 dist 编译。
- dist 工具能够编译 c 和 go 两种。
- go1.3 是采纳 Plan 9 对 go 进行编译。
- genrun 函数中拼装编译参数,会依据零碎不同调用不同的执行函数。