Как работает программа Go

Go

Когда я впервые начал писать эту статью, у меня была очень большая цель изучить жизнь программы Go: кодирование, компиляция, сборка, компоновка, запуск, выход. Как выполняется каждый шаг, пытаясь понять жизнь программы Go.

В процессе я еще раз просмотрел «Саморазвитие программиста». Это книга о компиляции и линковке, очень подробная и достойная прочтения! Много лет назад, когда я впервые увидел название этой книги, оно мне очень понравилось. Потому что она имитирует книгу, появившуюся в «Короле комедии» Стивена Чоу — «Саморазвитие актера». Стремление к этому!

Прежде чем приступить к этой статье, рекомендую блог крупного хедлайнера - "Программирование, ориентированное на веру". Его серия статей о компиляции Go очень глубока и уходит прямо в исходный код компилятора. Я читал ее много раз. . Ссылку на блог можно получить в Ресурсах.

Идеал очень велик, а реализация очень трудна. Чтобы не разгромить бренд «глубокой расшифровки», на этот раз более скромное имя, хе-хе.

вводить

Мы из одногоHello WorldПример начинается с:

package main

import "fmt"

func main() {
	fmt.Println("hello world")
}

Когда я изящно набрал вышеприведенный код hello world на своей вишневой клавиатуре за 1800 долларов,hello.goФайл представляет собой последовательность байтов, каждый байт представляет символ.

Откройте файл hello.go с помощью vim и в режиме командной строки введите команду:

:%!xxd

Вы можете просмотреть содержимое файла в шестнадцатеричном формате в vim:

hex .go

Крайний левый столбец представляет значение адреса, средний столбец представляет символ ASCII, соответствующий тексту, а крайний правый столбец — наш код. Выполнить снова в терминалеman ascii:

ASCII

По сравнению с таблицей символов ASCII можно обнаружить, что средний столбец и самый правый столбец находятся во взаимно однозначном соответствии. То есть только что написанный файл hello.go полностью представлен символами ASCII, которые называются文本文件, другие файлы называются二进制文件.

Конечно, если посмотреть глубже, все данные в компьютере, такие как файлы на диске, данные в сети, на самом деле представляют собой строку битов, в зависимости от того, как вы на это смотрите. В разных контекстах идентичная последовательность байтов может быть представлена ​​как целое число, число с плавающей запятой, строка или машинная инструкция.

Для такого файла, как hello.go, 8 бит, то есть байт, рассматриваются как единица (при условии, что все символы исходной программы представляют собой коды ASCII) и, наконец, интерпретируются как исходный код Go, понятный людям.

Программы Go нельзя запускать напрямую, каждый оператор Go должен быть преобразован в серию инструкций машинного языка низкого уровня, эти инструкции упакованы вместе и сохранены в виде двоичных файлов на диске, то есть исполняемых объектных файлов.

Процесс преобразования из исходных файлов в исполняемые объектные файлы:

compile

Именно система компиляции Go завершает вышеуказанные этапы. Вы должны знать знаменитый GCC (GNU Compile Collection), китайское название — GNU Compiler Suite, он поддерживает C, C++, Java, Python, Objective-C, Ada, Fortran, Pascal и может генерировать машинный код для множества разных машин.

Исполняемые объектные файлы могут выполняться непосредственно на машине. Вообще говоря, сначала выполняется некоторая работа по инициализации, находится запись основной функции и выполняется код, написанный пользователем, после завершения выполнения происходит выход основной функции, а затем выполняются некоторые завершающие работы, и весь процесс завершен.

В следующей статье мы рассмотрим编译и运行процесс.

Обзор компиляции и компоновки

Исходный код компилятора в исходном коде Go находится вsrc/cmd/compileПо пути исходный код компоновщика находится вsrc/cmd/linkпод дорожкой.

процесс компиляции

Я предпочитаю использовать IDE (интегрированную среду разработки) для написания кода, Goland используется для исходного кода Go, а иногда я напрямую нажимаю кнопку «Выполнить» в строке меню IDE, и программа запускается. На самом деле это подразумевает процесс компиляции и компоновки, и мы обычно комбинируем процесс компиляции и компоновки вместе как сборку.

Процесс компиляции заключается в выполнении лексического анализа, синтаксического анализа, семантического анализа, оптимизации исходного файла и, наконец, генерации файла ассемблерного кода для.sкак расширение файла.

После этого ассемблер превращает ассемблерный код в инструкции, которые машина может выполнять. Поскольку почти каждый ассемблерный оператор соответствует машинной инструкции, это просто взаимно однозначное соответствие, которое относительно простое, без синтаксического и семантического анализа и без оптимизации этих шагов.

Компилятор — это инструмент для перевода языка высокого уровня на машинный язык Процесс компиляции обычно делится на шесть этапов: сканирование, синтаксический анализ, семантический анализ, оптимизация исходного кода, генерация кода и оптимизация объектного кода. Следующая картинка из "Саморазвития программиста":

编译过程总览

лексический анализ

Из предыдущих примеров мы знаем, что программный файл Go — это просто набор битов для машины. Мы можем прочитать его, потому что Голанд кодирует эту кучу битов в ASCII (на самом деле UTF-8). Например, 8 бит группируются в группу, соответствующую символу, который можно найти, сравнив кодовую таблицу ASCII.

Когда все двоичные биты отображаются в символы ASCII, мы можем видеть осмысленные строки. Это может быть ключевое слово, например: package, это может быть строка, например: «Hello World».

Лексический анализ на самом деле делает именно это. Входными данными является исходный файл программы Go. В глазах лексического анализатора это просто набор двоичных битов. Он не знает, что это такое. После анализа он становится осмысленной лексемой. Проще говоря, лексический анализ — это процесс преобразования последовательности символов в последовательность токенов в информатике.

Давайте посмотрим на определение, данное в Википедии:

Лексический анализ — это процесс преобразования последовательности символов в последовательность токенов в информатике. Программа или функция, выполняющая лексический анализ, называется лексическим анализатором (лексером), также известным как сканер. Лексический анализатор обычно существует в виде функции, которую должен вызывать синтаксический анализатор.

.goФайл подается в сканер, который использует有限状态机Алгоритм делит серию символов исходного кода на серию токенов (Token).

Символы обычно делятся на следующие категории: ключевые слова, идентификаторы, литералы (включая числа, строки), специальные символы (такие как знаки плюс, знаки равенства).

Например, для следующего кода:

slice[i] = i * (2 + 6)

Содержит в общей сложности 16 непустых символов, после сканирования

отметка тип
slice идентификатор
[ левая квадратная скобка
i идентификатор
] правая квадратная скобка
= назначать
i идентификатор
* Знак умножения
( левая скобка
2 номер
+ плюс
6 номер
) правая скобка

Приведенный выше пример взят из книги "Саморазвитие программиста", которая в основном объясняет содержание, связанное с компиляцией и компоновкой. Это очень увлекательно и рекомендуется для изучения.

Путь в исходном коде Токена, поддерживаемого сканером на языке Go (версия Go в этой статье — 1.9.2):

src/cmd/compile/internal/syntax/token.go

Почувствуй это:

var tokstrings = [...]string{
	// source control
	_EOF: "EOF",

	// names and literals
	_Name:    "name",
	_Literal: "literal",

	// operators and operations
	_Operator: "op",
	_AssignOp: "op=",
	_IncOp:    "opop",
	_Assign:   "=",
	_Define:   ":=",
	_Arrow:    "<-",
	_Star:     "*",

	// delimitors
	_Lparen:    "(",
	_Lbrack:    "[",
	_Lbrace:    "{",
	_Rparen:    ")",
	_Rbrack:    "]",
	_Rbrace:    "}",
	_Comma:     ",",
	_Semi:      ";",
	_Colon:     ":",
	_Dot:       ".",
	_DotDotDot: "...",

	// keywords
	_Break:       "break",
	_Case:        "case",
	_Chan:        "chan",
	_Const:       "const",
	_Continue:    "continue",
	_Default:     "default",
	_Defer:       "defer",
	_Else:        "else",
	_Fallthrough: "fallthrough",
	_For:         "for",
	_Func:        "func",
	_Go:          "go",
	_Goto:        "goto",
	_If:          "if",
	_Import:      "import",
	_Interface:   "interface",
	_Map:         "map",
	_Package:     "package",
	_Range:       "range",
	_Return:      "return",
	_Select:      "select",
	_Struct:      "struct",
	_Switch:      "switch",
	_Type:        "type",
	_Var:         "var",
}

Все еще относительно знакомы, включая имена и литералы, операторы, разделители и ключевые слова.

И путь к сканеру такой:

src/cmd/compile/internal/syntax/scanner.go

Наиболее важной функцией является следующая функция, которая непрерывно считывает следующий символ (не следующий байт, потому что язык Go поддерживает кодировку Unicode, в отличие от примера кода ASCII, который мы приводили ранее, символ имеет только один байт), пока эти символы не может сформировать токен.

func (s *scanner) next() {
// ……

redo:
	// skip white space
	c := s.getr()
	for c == ' ' || c == '\t' || c == '\n' && !nlsemi || c == '\r' {
		c = s.getr()
	}

	// token start
	s.line, s.col = s.source.line0, s.source.col0

	if isLetter(c) || c >= utf8.RuneSelf && s.isIdentRune(c, true) {
		s.ident()
		return
	}

	switch c {
    // ……

	case '\n':
		s.lit = "newline"
		s.tok = _Semi

	case '0', '1', '2', '3', '4', '5', '6', '7', '8', '9':
		s.number(c)
		
	// ……
	
   default:
		s.tok = 0
		s.error(fmt.Sprintf("invalid character %#U", c))
		goto redo
	return

assignop:
	if c == '=' {
		s.tok = _AssignOp
		return
	}
	s.ungetr()
	s.tok = _Operator
}

Основная логика кода заключается в передачеc := s.getr()Получите следующий неразобранный символ и пропустите следующие пробелы, возврат каретки, перевод строки, символы табуляции, а затем введите большойswitch-caseоператор, соответствующий различным ситуациям, и, наконец, токен может быть проанализирован, и соответствующие номера строки и столбца записаны, таким образом завершая процесс синтаксического анализа.

Сканер лексического анализатора в текущем пакете предоставляет следующий метод только для верхнего уровня, процесс лексического анализа ленивый, и следующий Токен будет вызван только тогда, когда он понадобится парсеру верхнего уровня.

Разбор

Последовательность токена, генерируемая на предыдущем шаге, должна быть дополнительно обработана для генерации表达式для узла语法树.

Как и первый пример,slice[i] = i * (2 + 6), результирующее синтаксическое дерево выглядит следующим образом:

语法树

Вся инструкция рассматривается как выражение присваивания, левое поддерево — это выражение массива, а правое поддерево — это выражение умножения; выражение массива состоит из двух символьных выражений; выражение умножения состоит из формулы символьного выражения и плюса. -знаковое выражение; выражение со знаком плюс состоит из двух чисел. Символы и числа — это наименьшие выражения, которые уже не могут быть разложены, обычно как конечные узлы дерева.

В процессе синтаксического анализа могут быть обнаружены некоторые формальные ошибки, такие как: если скобки отсутствуют наполовину,+В числовом выражении отсутствует операнд и т. д.

Синтаксический анализ — это процесс анализа входного текста, состоящего из последовательностей токенов, в соответствии с определенной формальной грамматикой (грамматикой) и определение его грамматической структуры.

Семантический анализ

После завершения синтаксического анализа мы не знаем, каково конкретное значение оператора. как выше*Если два поддерева числа являются двумя указателями, это недопустимо, но синтаксический анализ не может это обнаружить, а семантический анализ должен это сделать.

Что можно проверить во время компиляции, так это статическую семантику, которую можно рассмотреть на этапе «кода», включая сопоставление типов переменных, преобразование и т. д. Например, при присвоении значения с плавающей запятой переменной-указателю очевидное несоответствие типов приведет к ошибке компиляции. И для ошибок, которые возникают только во время выполнения: случайно, кроме 0, семантический анализ не может его обнаружить.

После завершения фазы семантического анализа каждый узел помечается типом:

语义分析完成

На этом этапе компилятор Go проверяет типы констант, типов, объявлений функций и операторов присваивания переменных, а затем проверяет типы ключей в хеше. Функции, реализующие проверку типов, обычно представляют собой гигантские операторы switch/case, состоящие из нескольких тысяч строк.

Проверка типов — это второй этап компиляции языка Go.После лексического и грамматического анализа мы получаем абстрактное синтаксическое дерево, соответствующее каждому файлу.Последующая проверка типов будет проходить по узлам в абстрактном синтаксическом дереве и проверять тип каждого узла.Проверить за грамматические ошибки.

В этом процессе также может быть переписано абстрактное синтаксическое дерево, которое может не только удалить некоторый код, который не будет выполняться, оптимизировать компиляцию и повысить эффективность выполнения, но также изменить тип операции узлов, соответствующих ключевым словам, таким как make и новый.

Например, более часто используемое ключевое слово make может использоваться для создания различных типов, таких как фрагмент, карта, канал и т. д. Когда этот шаг будет достигнут, для ключевого слова make, то есть узла OMAKE, он сначала проверит свой тип параметра и войдет в соответствующую ветвь в соответствии с типом. Если тип параметра — slice, он войдет в ветвь case TSLICE, чтобы проверить, соответствуют ли len и cap требованиям, например len

Генерация промежуточного кода

Мы знаем, что процесс компиляции в целом можно разделить на интерфейсную часть и серверную часть.Фронтенд генерирует независимый от платформы промежуточный код, а серверная часть генерирует разные машинные коды для разных платформ.

Предыдущий лексический анализ, синтаксический анализ, семантический анализ и т. д. относятся к интерфейсу компилятора, а более поздние этапы относятся к серверу компилятора.

В процессе компиляции есть много ссылок на оптимизацию, в этой ссылке имеется в виду оптимизация на уровне исходного кода. Он преобразует синтаксическое дерево в промежуточный код, который является последовательным представлением синтаксического дерева.

Промежуточный код, как правило, не зависит от целевой машины и среды выполнения и имеет несколько распространенных форм: трехадресный код, P-код. Например, самые основные三地址码Такова, что:

x = y op z

Указывает, что переменная y и переменная z присваиваются x после операции op. op может быть математической операцией, такой как сложение, вычитание, умножение и деление.

Приведенный ранее пример можно записать в следующем виде:

t1 = 2 + 6
t2 = i * t1
slice[i] = t2

Здесь 2 + 6 можно вычислить напрямую, поэтому временную переменную t1 можно «оптимизировать», а переменную t1 можно использовать повторно, поэтому t2 также можно «оптимизировать». После оптимизации:

t1 = i * 8
slice[i] = t1

Промежуточным кодовым представлением языка Go является SSA (Static Single-Assignment), которое называется одиночным присвоением, поскольку каждое имя присваивается только один раз в SSA. .

На этом этапе устанавливаются соответствующие переменные, используемые для генерации промежуточного кода в соответствии с архитектурой ЦП, такие как размер указателей и регистров, используемых компилятором, список доступных регистров и т.д. Генерация промежуточного кода и генерация машинного кода имеют одни и те же настройки.

Некоторые элементы узлов в абстрактном синтаксическом дереве заменяются перед генерацией промежуточного кода. Вот картинка из блога, связанная с принципом компиляции «Программирование, ориентированное на веру»:

builtin mapping

Например, операция карты m[i] здесь будет преобразована в mapaccess или mapassign.

Основная программа языка Go будет вызывать функции в среде выполнения при ее выполнении, а это означает, что функции ключевых слов и встроенные функции фактически выполняются компилятором языка и средой выполнения.

Процесс генерации промежуточного кода на самом деле является процессом преобразования из абстрактного синтаксического дерева AST в промежуточный код SSA.В течение этого периода ключевые слова в синтаксическом дереве будут обновлены один раз, а обновленное синтаксическое дерево пройдет несколько циклов обработки. для преобразования конечного SSA Промежуточный код.

Генерация и оптимизация объектного кода

Длина машинного слова, регистры и т. д. разных машин разные, а это означает, что машинный код, работающий на разных машинах, разный. Целью последнего шага является создание кода, который может работать на различных архитектурах ЦП.

Чтобы выжать из машины каждую каплю масла, оптимизатор объектного кода оптимизирует некоторые инструкции, такие как использование инструкций сдвига вместо инструкций умножения.

Эта часть действительно не способна углубляться, но, к счастью, ей и не нужно углубляться. Для инженеров-разработчиков программного обеспечения на прикладном уровне этого достаточно для понимания.

процесс связывания

Процесс компиляции ведется для одного файла, и глобальные переменные или функции, определенные в других модулях, неизбежно ссылаются между файлами, адреса этих переменных или функций могут быть определены только на этом этапе.

Процесс компоновки заключается в компоновке объектных файлов, сгенерированных компилятором, в исполняемые файлы. Конечный файл делится на различные сегменты, такие как сегмент данных, сегмент кода, сегмент BSS и т. д., которые будут загружены в память во время выполнения. Каждый сегмент имеет разные атрибуты чтения-записи и выполнения, что обеспечивает безопасную работу программы.

Для этой части рекомендуется прочитать "Саморазвитие программиста" и "Углубленное понимание компьютерных систем".

Перейти к запуску программы

Все еще используя пример проекта hello-world. Выполнить в корневом каталоге проекта:

go build -gcflags "-N -l" -o hello src/main.go

-gcflags "-N -l"Это нужно для того, чтобы отключить оптимизацию компилятора и встраивание функций, чтобы предотвратить обнаружение соответствующего местоположения кода при последующей установке точек останова.

Получите исполняемый файл hello, выполните:

[qcrao@qcrao hello-world]$ gdb hello

Войдите в режим отладки gdb, выполнитеinfo files, чтобы получить заголовок исполняемого файла со списком различных разделов:

gdb info

При этом мы также получаем адрес входа: 0x450e20.

(gdb) b *0x450e20
Breakpoint 1 at 0x450e20: file /usr/local/go/src/runtime/rt0_linux_amd64.s, line 8.

Это адрес входа программы Go, я работаю в Linux, поэтому файл входаsrc/runtime/rt0_linux_amd64.s, В каталоге среды выполнения есть различные файлы входа в программу с разными именами, поддерживающие различные операционные системы и архитектуры, код:

TEXT _rt0_amd64_linux(SB),NOSPLIT,$-8
	LEAQ	8(SP), SI // argv
	MOVQ	0(SP), DI // argc
	MOVQ	$main(SB), AX
	JMP	AX

Главное вытащить argc и argv из памяти в регистры. Здесь LEAQ заключается в том, чтобы вычислить адрес памяти, а затем поместить сам адрес памяти в регистр, то есть поместить адрес argv в регистр SI. Наконец, перейдите к:

TEXT main(SB),NOSPLIT,$-8
	MOVQ	$runtime·rt0_go(SB), AX
	JMP	AX

продолжать прыгать наruntime·rt0_go(SB),Место расположения:/usr/local/go/src/runtime/asm_amd64.s, код:

TEXT runtime·rt0_go(SB),NOSPLIT,$0
    // 省略很多 CPU 相关的特性标志位检查的代码
    // 主要是看不懂,^_^
    
    // ………………………………
    
    // 下面是最后调用的一些函数,比较重要
    // 初始化执行文件的绝对路径
    CALL	runtime·args(SB)
    // 初始化 CPU 个数和内存页大小
	CALL	runtime·osinit(SB)
	// 初始化命令行参数、环境变量、gc、栈空间、内存管理、所有 P 实例、HASH算法等
	CALL	runtime·schedinit(SB)

	// 要在 main goroutine 上运行的函数
	MOVQ	$runtime·mainPC(SB), AX		// entry
	PUSHQ	AX
	PUSHQ	$0			// arg size
	
	// 新建一个 goroutine,该 goroutine 绑定 runtime.main,放在 P 的本地队列,等待调度
	CALL	runtime·newproc(SB)
	POPQ	AX
	POPQ	AX

    // 启动M,开始调度goroutine
	CALL	runtime·mstart(SB)

	MOVL	$0xf1, 0xf1  // crash
	RET

	
DATA	runtime·mainPC+0(SB)/8,$runtime·main(SB)
GLOBL	runtime·mainPC(SB),RODATA,$8	

Статья в справочнике [Исследуйте процесс запуска программы golang] содержит более глубокое исследование и резюмирует:

  1. Проверьте ЦП работающей платформы и установите соответствующие флаги, необходимые для запуска программы.
  1. Инициализация TLS.
  2. Три метода runtime.args, runtime.osinit и runtime.schedinit выполняют все виды переменных и планировщиков, необходимых для запуска программы.
  3. runtime.newproc создает новую горутину для привязки к основному методу, написанному пользователем.
  4. runtime.mstart запускает планирование goroutine.

Наконец, используйте картинку, чтобы подытожить процесс начальной загрузки:

golang bootstrap

Некоторые важные операции, выполняемые в основной функции, включают: создание нового потока для выполнения функции sysmon, регулярную сборку мусора и планирование вытеснения, запуск gc, выполнение всех функций инициализации и т. д.

Выше приведен процесс запуска, посмотрите на процесс выхода:

Когда основная функция завершит выполнение, она выполнит exit(0) для выхода из процесса. Если процесс не завершается после выполнения exit(0), последний код основной функции всегда будет обращаться к недопустимому адресу:

exit(0)
for {
	var x *int32
	*x = 0
}

При нормальных обстоятельствах, как только происходит незаконный доступ к адресу, система завершает процесс и использует этот метод, чтобы гарантировать, что процесс завершится.

Объяснение выхода из программы исходит из группового чата "golang runtime reading", который также является высокоуровневой организацией по чтению исходного кода. См. справочные материалы на домашней странице github.

Конечно, эта часть запуска программы Go на самом деле включает в себя создание нового процесса, загрузку исполняемых файлов, передачу управления и другие вопросы. Первые две книги все же рекомендую прочитать, лучше написать не думаю, поэтому описывать не буду.

GoRoot и GoPath

GoRoot — это путь установки Go. mac или unix находится в/usr/local/goПо пути посмотрим, что здесь установлено:

/usr/local/go

Под каталогом bin:

bin

Под каталогом pkg:

pkg

Каталог инструментов Go выглядит следующим образом, наиболее важным из которых является компилятор.compile, Линкерlink:

pkg/tool

Цель GoPath — предоставить.goПуть к исходному коду, это концепция рабочей области, и можно установить несколько каталогов. Согласно официальным требованиям Go, GoPath должен содержать три папки:

src
pkg
bin

src хранит исходные файлы, pkg хранит файлы библиотек, скомпилированные из исходных файлов, а суффикс.a;bin хранит исполняемые файлы.

Детали команды «Перейти»

Выполнить прямо в терминале:

go

Вы можете ознакомиться с командами, связанными с go:

go commands

Команды, связанные с компиляцией, в основном следующие:

go build
go install
go run

go build

go buildИспользуется для компиляции исходных файлов в указанные пакеты и их зависимые пакеты, которые поступят в момент компиляции.$GoPath/src/packageНайдите исходный файл в пути.go buildВы также можете напрямую скомпилировать указанный файл исходного кода, а также можете указать более одного файла одновременно.

выполнивgo help buildкоманда, чтобы получитьgo buildКак пользоваться:

usage: go build [-o output] [-i] [build flags] [packages]

-oПоявляется только при компиляции одного пакета, указывает имя выходного исполняемого файла.

-iПакеты, от которых зависит цель компиляции, будут установлены.Установка означает создание соответствующих пакетов кода..aФайл, то есть файл статического библиотеки (будет связан позже), помещается в каталог PKG текущего рабочего пространства, а уровень каталога файла библиотеки совпадает с уровнем исходного кода.

Что касается параметра флагов сборки,build, clean, get, install, list, run, testЭти команды имеют общий набор:

параметр эффект
-a Принудительно перекомпилирует все задействованные пакеты, включая пакеты кода в стандартной библиотеке, что перезаписывает каталог /usr/local/go..aдокумент
-n Процесс выполнения команды печати, на самом деле не выполняется
-p n Задает параллельное количество выполняемых команд во время компиляции, n по умолчанию равно количеству ядер ЦП.
-race Обнаружение и сообщение о проблемах гонки данных в вашей программе
-v Выведите имя пакета кода, участвующего в выполнении команды
-x Распечатайте команды, участвующие в процессе выполнения команды, и выполните
-work Распечатайте временную папку во время компиляции. Обычно он удаляется после компиляции

Мы знаем, что файлы исходного кода языка Go делятся на три категории: исходный код команды, исходный код библиотеки и исходный код теста.

Исходный файл команды: запись программы Go, включаяfunc main()функция, а в первой строке используетсяpackage mainДекларация относится к основному пакету.

Исходные файлы библиотеки: в основном различные функции, интерфейсы и т. д., например функции инструментов.

Исходный файл теста: начните с_test.goФайл с суффиксом для проверки функции и производительности программы.

Уведомление,go buildбудет игнорировать*_test.goдокумент.

Продемонстрируем на очень простом примереgo buildЗаказ. Я создал новый с Голандомhello-worldПроект (отличается от предыдущей программы hello-world тем, что показывает ссылку на пользовательский пакет), структура проекта следующая:

example structure

Структуру проекта можно увидеть в крайнем левом углу, включая три папки: bin, pkg, src. Среди них есть main.go в каталоге src, который определяет основную функцию, которая является входом всего проекта, который является упомянутым выше так называемым исходным файлом команд, а также есть каталог util в каталоге src. каталог, который содержит файл util.go.Определяет функцию, которая может получить локальный IP-адрес, который является так называемым исходным файлом библиотеки.

Посередине находится исходный код main.go, который ссылается на два пакета: один — fmt стандартной библиотеки, другой — пакет util, а путь импорта util —util. Так называемый путь импорта относится к исходному каталогу относительно Go.$GoRoot/srcили$GoPath/srcподпуть под . Например, исходный путь fmt, указанный в основном пакете:/usr/local/go/src/fmt, а исходный путь утилиты/Users/qcrao/hello-world/src/util, точно так же, как мы установили GoPath = /Users/qcrao/hello-world.

Крайний справа исходный код библиотечной функции, которая реализует функцию получения нативного IP.

В каталоге src выполнить напрямуюgo buildкоманда, исполняемый файл создается в каталоге того же уровня, имя файлаsrc,использовать./srcКоманда выполняется напрямую, вывод:

hello world!
Local IP: 192.168.1.3

Мы также можем указать имя сгенерированного исполняемого файла:

go build -o bin/hello

Таким образом, исполняемый файл будет сгенерирован в каталоге bin, и результат работы будет таким же, как и выше.srcТакой же.

На самом деле пакет util можно скомпилировать отдельно. Мы можем выполнить в корневом каталоге проекта:

go build util

Компилятор перейдет по пути $GoPath/src, чтобы найти пакет утилиты (на самом деле папку). также доступен в./src/utilВыполнять прямо в каталогеgo buildкомпилировать.

Конечно, прямая компиляция исходных файлов библиотеки не приведет к созданию файлов .a, потому что:

Когда команда go build компилирует пакет кода, который содержит только исходные файлы библиотеки (или одновременно компилирует несколько пакетов кода), она выполняет только контрольную компиляцию без вывода каких-либо файлов результатов.

Чтобы показать запущенный процесс всей ссылки компиляции, мы выполняем следующую команду в корневом каталоге проекта:

go build -v -x -work -o bin/hello src/main.go

-vНапечатает имя скомпилированного пакета,-xвывести команды, выполненные во время компиляции,-workПечатать пути к временным файлам, созданным во время компиляции и не удаляемым после завершения компиляции.

Результаты:

编译过程

Из результатов стрелки на рисунке указывают на то, что этот процесс компиляции включает в себя два пакета: util, командные аргументы. Второй пакет довольно странный, такого имени нет в исходном коде, хорошо? На самом деле этоgo buildКоманда обнаруживает, что [пакеты] заполнены.goфайл, создав таким образом фиктивный пакет: command-line-arguments.

При этом compile, ссылка обведены красными прямоугольниками, то есть сначала компилируется пакет util и пакет util.main.goфайлы, соответственно.aфайл, а затем соедините их, чтобы, наконец, создать исполняемый файл, который перемещается в каталог bin и переименовывается в hello.

Также первая строка показывает рабочий каталог во время компиляции, файловая структура этого каталога:

临时工作目录

Как видите, уровень каталога hello-world практически такой же. аргументы командной строки — это пакет, в котором находится виртуальный файл main.go. Исполняемые файлы в каталоге exe были перемещены в каталог bin на последнем шаге, поэтому он пуст.

Общий,go buildПри выполнении он сначала будет рекурсивно искать пакеты, от которых зависит main.go, а также зависимости зависимостей, до самого нижнего пакета. Это может быть обход в глубину или обход в ширину. Если циклическая зависимость найдена, она завершится напрямую, что также является часто возникающей ошибкой компиляции циклической ссылки.

В нормальных условиях эти зависимости образуют дерево, растущее вверх ногами, корнем которого является файл main.go вверху, а внизу — пакет без каких-либо других зависимостей. Компилятор начнет компиляцию с пакета, представленного крайним левым узлом, один за другим, а после этого скомпилирует пакет предыдущего слоя.

Здесь обратитесь к учебнику по команде go, опубликованному г-ном Хао Линем на github несколько лет назад, и вы можете найти исходный адрес в справочных материалах.

С точки зрения компиляции пакета кода, если пакет кода A зависит от пакета кода B, то пакет кода B называется зависимым пакетом кода пакета кода A (в дальнейшем именуемым зависимым пакетом), а пакет кода A является триггерным кодом. пакет кода пакета B (далее триггерный пакет).

воплощать в жизньgo buildЕсли управляемый компьютер имеет несколько логических ядер ЦП, может быть некоторая неопределенность в отношении порядка, в котором компилируются пакеты кода. Однако он должен соответствовать ограничениям: зависимый пакет кода -> текущий пакет кода -> пакет кода триггера.

Кстати, рекомендую браузерный плагин Octotree, при просмотре проекта на гитхабе этот плагин может напрямую отображать файловую структуру всего проекта в браузере, что очень удобно:

github 插件

На этом этапе вы определенно обнаружите, что каталог pkg в папке hello-wrold, по-видимому, не задействован.

На самом деле в каталоге pkg должны храниться скомпилированные пакеты задействованных библиотечных файлов, то есть некоторые.aдокумент. Но во время выполнения сборки эти.aФайл помещается во временную папку и будет удален сразу после компиляции, поэтому обычно не используется.

Как мы упоминали ранее, добавьте в команду go build-iПараметры будут устанавливать пакеты, скомпилированные этими библиотечными файлами, то есть эти.aФайлы будут помещены в каталог pkg.

Выполнить в корневом каталоге проектаgo build -i src/main.goПосле этого в каталог pkg был добавлен файл util.a:

pkg

darwin_amd64означает:

ГСНО и ГОАРЧ. Эти две переменные среды не должны быть установлены нами, системные значения по умолчанию.

GOOS — это тип операционной системы, в которой находится Go, а GOARCH — это вычислительная архитектура, в которой находится Go.

На платформе Mac имя этого каталога — darwin_amd64.

После того, как файл util.a сгенерирован, при повторной компиляции файл util.go не будет перекомпилирован, что ускоряет компиляцию.

В то же время в корневом каталоге создается исполняемый файл с именем main, который запускается с именем файла main.go.

Код проекта hello-world выложен на проект github.Go-Questions, этот проект импортируется вопросом, пытается соединить все точки знаний Го, совершенствуется, с нетерпением ждет своей звезды. Адрес см. в ссылке [Проект Go-Questions hello-world].

go install

go installИспользуется для компиляции и установки указанных пакетов кода и их зависимостей. по сравнению сgo build, он просто добавляет шаг «установки скомпилированного файла результатов в указанный каталог».

Все еще используя предыдущий пример проекта hello-world, мы сначала удаляем каталог pkg и выполняем его в корневом каталоге проекта:

go install src/main.go

或者

go install util

Оба создадут новый в корневом каталогеpkgкаталог и создатьutil.aдокумент.

И, когда первое выполняется, исполняемый файл с именем main будет сгенерирован в каталоге GOBIN.

Итак, бегиgo installкоманда, соответствующая исходному пакету библиотеки.aфайл будет помещен вpkgкаталог, исполняемый файл, сгенерированный исходным пакетом команды, будет помещен в каталог GOBIN.

go installКогда GoPath имеет несколько каталогов, могут возникнуть некоторые проблемы.Go 命令教程, здесь не раскрыто.

go run

go runИспользуется для компиляции и запуска исходных файлов команд.

В корневом каталоге проекта hello-world выполните команду go run:

go run -x -work src/main.go

-x может печатать команды, участвующие во всем процессе, -work может видеть временный рабочий каталог:

go run 过程

Как видно из рисунка выше, он по-прежнему сначала компилируется, затем подключается и, наконец, выполняется напрямую, а результат выполнения выводится на печать.

Первая строка выводит рабочий каталог, а полученный исполняемый файл помещается сюда:

go run 结果

main — результирующий исполняемый файл.

Суммировать

На этот раз тема слишком велика и сложная. Из принципа компиляции к процессу GO Startup, к принципу команды GO, каждая тема может быть написана отдельно.

К счастью, есть некоторые очень хорошие книги, а посты в блоге могут ссылаться. Как введение в эту статью, вы можете следить за некоторыми ссылками в рекомендуемом контент для расхождения.

использованная литература

[Полная книга "Самосовершенствование программиста"]book.Douban.com/subject/365…

[Обзор процесса компиляции ориентированного на веру программирования]D Raven ES is.what/go wave-comp…

[чтение во время выполнения golang]GitHub.com/ live ah/ Голаны...

【Проект Go-Questions привет-мир】GitHub.com/езда на велосипеде/go-Q U…

[Заметки об изучении языка го босса знака дождя]github.com/qyuhen/book

[vim в шестнадцатеричном формате]блог woo woo woo.cn on.com/no Principal/ah…

[Перейти к процессу выполнения команды компиляции]halfrost.com/go_command/

[Go] процесс выполнения командыGitHub.com/hyper0small/go_…

[Перейти к лексическому анализу]Смущает, о, о, о, спичка, ты только что GitHub.IO/this-cn/2016/…

[Блог Цао Да golang и ast]xargin.com/ast/

[Лексический анализатор Golang, анализ исходного кода сканера]blog.CSDN.net/Чжао Руйсянь…

【Объяснение Гопата】flaviocopes.com/go-gopath/

【Понимание GOPATH】woohoo.digital ocean.com/community/he…

【обсуждать】stackoverflow.com/questions/7…

【Перейти к официальному гопату】golang.org/cmd/go/#Многие люди…

[Изучение пакета Go]Tickets.WeChat.QQ.com/Yes/OI в v LX FZ6…

[Официально об организационной структуре проекта Go]golang.org/doc/code Контракты…

【Перейти к модулям】Woohoo Мелвин vivas.com/go-version-…

【Установка, настройка Golang, GOPATH и рабочее пространство Go】woohoo.call ICO people.com/golang-Inst…

[Ссылка на процесс компиляции и компоновки]mikespook.com/2013/11/Перевод-…

[1.5 Компилятор дополнен языком go]Woohoo.info Q.capable/article/201…

[Перейти к серии статей о процессе компиляции]Woohoo. Cto lib.com/topics-3724...

Цао идет бутстрап []GitHub.com/часто123/потяните…

[Процесс запуска Голанга]blog.ice в.com/posts/go/body…

[Изучите процесс запуска программы golang]cbsheng.github.io/posts/explore гол…

[Изучите создание горутин]cbsheng.github.io/posts/ Исследуйте Гор ...

QR