• 如果您觉得本站非常有看点,那么赶紧使用Ctrl+D 收藏吧

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

互联网 diligentman 2周前 (01-11) 14次浏览

前言

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

1.dist工具介绍

  其实dist工具是属于go的一个引导工具,它负责构建C程序(如Go编译器)和go工具的初始引导副本。它也可以作为一个包罗万象用shell脚本替换以前完成的零工。通过“go tool dist”命令可以操作该工具。该工具不同系统下对应在pkg/tool/下的目录中。
【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<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/&#8230;

  文档中说: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对应源码文件作用。
【Golang源码分析】Golang如何实现自举 - 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

【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<center>图3-1-1 执行dist命令</center>
  执行dist命令后,可以看出来编译boostrap时,相应编译来lib、cmd、pkg相应问题。接下来,通过gdb来了解dist编译boostrap的过程。

3.1调试带参数的dist

【Golang源码分析】Golang如何实现自举 - 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 %sn", 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函数过程

【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<center>图3-4-1 dist 编译过程</center>
  是对编译参数的拼装,其实最终会调用runv函数进行编译,而runv函数又会根据不同的系统调用不同genrun函数。如果是unix/linux系列的会调用unix.c中的genrun,如果是windows会调用windows.c中的genrun,genrun函数中进行拼装参数后。会根据系统不同调用不同的执行函数。
【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<center>图3-4-2 调试go源码编译</center>
  其实go源码编译会调用“/mnt/pkg/tool/linux_amd64/6g”,这个6g其实是不固定的文件。咱们可以来调试看看。

4. 调试6g

【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<center>图4-1 调试6g</center>
  调试6g,下mian函数断点。可以清晰的看到使用来src/lib9/main.c中的main。这一块调用来Plan 9 C,然后plan9中又调用lex生成的源码做词法解析。
【Golang源码分析】Golang如何实现自举 - dist介绍(二)
<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函数中拼装编译参数,会根据系统不同调用不同的执行函数。

喜欢 (0)