5

【Golang源码分析】Golang如何实现自举 - dist介绍(二)

 3 years ago
source link: https://studygolang.com/articles/32559
Go to the source link to view the article. You can view the picture content, updated content and better typesetting reading experience. If the link is broken, please click the button below to view the snapshot at that time.
neoserver,ios ssh client

前言

根据《Golang如何实现自举(一)》的相关引导,知道了go1.3的go编译是需要go_bootstrap、然而生成go_bootstrap,需要dist工具进行生成。那么本期主要关注dist工具。

1.dist工具介绍

其实dist工具是属于go的一个引导工具,它负责构建C程序(如Go编译器)和go工具的初始引导副本。它也可以作为一个包罗万象用shell脚本替换以前完成的零工。通过“go tool dist”命令可以操作该工具。该工具不同系统下对应在pkg/tool/下的目录中。

3Mv2AjM.png!mobile

<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对应源码文件作用。

bYbeaeY.png!mobile

<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

bqMziaI.png!mobile

<center>图3-1-1 执行dist命令</center>

执行dist命令后,可以看出来编译boostrap时,相应编译来lib、cmd、pkg相应问题。接下来,通过gdb来了解dist编译boostrap的过程。

3.1调试带参数的dist

IfQrEjj.png!mobile

<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函数过程

qMBryqE.png!mobile

<center>图3-4-1 dist 编译过程</center>

是对编译参数的拼装,其实最终会调用runv函数进行编译,而runv函数又会根据不同的系统调用不同genrun函数。如果是unix/linux系列的会调用unix.c中的genrun,如果是windows会调用windows.c中的genrun,genrun函数中进行拼装参数后。会根据系统不同调用不同的执行函数。

I7vYRzf.png!mobile

<center>图3-4-2 调试go源码编译</center>

其实go源码编译会调用“/mnt/pkg/tool/linux_amd64/6g”,这个6g其实是不固定的文件。咱们可以来调试看看。

4. 调试6g

mE736vb.png!mobile

<center>图4-1 调试6g</center>

调试6g,下mian函数断点。可以清晰的看到使用来src/lib9/main.c中的main。这一块调用来Plan 9 C,然后plan9中又调用lex生成的源码做词法解析。

A36ZVr.png!mobile

<center>图4-2 lex</center>

对应在src/cmd/gc/lex.c中,lex又结合来yacc做语法解析。最终生成对应的可执行文件。

总结

  1. dist工具是属于go的一个引导工具。
  2. go_boostrap是通过dist编译。
  3. dist工具可以编译c和go两种。
  4. go1.3是采用Plan 9对go进行编译。
  5. genrun函数中拼装编译参数,会根据系统不同调用不同的执行函数。

有疑问加站长微信联系(非本文作者)

eUjI7rn.png!mobile

About Joyk


Aggregate valuable and interesting links.
Joyk means Joy of geeK