前言
依据《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.voidxmain(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函数的实现:
voidcmdbootstrap(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函数中拼装编译参数,会依据零碎不同调用不同的执行函数。