底层逻辑-理解Go语言的本质

news2024/12/24 8:49:46

1.Java VS Go语言

Java,从源代码到编译成可运行的代码

java组图1

上图已经展示了这个过程:从Java的源代码编译成jar包或war包(字节码),最终运行在JVM中。

java组图2

我们把Java源代码编译后的jar包或war包看成是工程师生产出来的产品,操作系统是一个平台,JVM就是中间商,那程序的整体性能也要受到中间商JVM的因素影响了。

  • 优点:一次编译,到处运行(windows、linux、macos)
  • 缺点:JVM性能损失大。

Go语言,从源代码到编译成可运行的代码

golang组图1

我们把Go语言的源代码编译后,生成二进制文件,直接就可以在操作系统上运行,没有中间商

优点:

  • 直接编译成二进制
  • 无需进行虚拟机环境,自动执行
  • 一次编写代码,跨平台执行
  • 高性能并发能力

2.为什么Go语言运行-“没有中间商”

每种编程语言都有自己的Runtime, 把这个单词拆开来看,Run=运行,Time=时间,简称:运行时

Go语言的Runtime作用:

  • 内存管理
  • 协程调度
  • 垃圾回收

Go语言的运行时,是和源代码最终编译生成到二进制文件中的。当我们启动二进制文件的时候,运行时也就是一并启动了。

Go语言是如何编译成二进制文件的

package main

import "fmt"

func main() {
    fmt.Println("面向加薪学习-从0到Go语言微服务架构师")
}

在命令行执行 go build -n(-n含义代表:打印编译时会用到的所有命令,但不真正执行)

编译过程1

编译过程1

从上图可以看到:

  1. import config 导入配置
  2. fmt.a—>对应fmt包
  3. runtime.a—>对应runtime包
  4. compile -o 编译输出到 pkg.a

编译过程2

编译过程2

  1. 创建exe目录
  2. link链接到a.out
  3. 把a.out该名成menu1

总结:看到上面的过程已经把runtime包放到我们的二进制文件中了。

3.编译过程

在编译原理中,有一个名词:AST(抽象语法树) = Abstract Syntax Tree
1. 把源代码变成文本,然后把每个单词拆分出来
2. 把每个单词变成语法树
3. 类型检查、类型推断、类型匹配、函数调用、逃逸分析

分析阶段

  1. 词法检查分析、语法检查分析、语义检查分析)
  2. 生成中间码生成(SSA代码,类似汇编)。
    执行export GOSSAFUNC=main,代表你要看main函数的ssa代码,然后执行go build,会生成ssa.html
    图1. ssa1
    图2. ssa2
  3. 代码优化
  4. 生成机器码(支持生成.a的文件)
  5. go build -gcflags -S main.go(生成和平台相关的plan9汇编代码)
  6. 链接(生成可执行二进制文件)

4.Go语言是如何启动的

Go语言启动的时候,Runtime到底发生了什么?

可以到runtime目录中找到rt0_darwin_amd64.s找到这个文件(由于我的电脑是mac,所以找到了这个,其他平台可以找各自的),这是一个汇编文件。

rt0_darwin_amd64.s

TEXT _rt0_amd64_darwin(SB),NOSPLIT,$-8
    JMP	_rt0_amd64(SB)

asm_amd64.s

TEXT _rt0_amd64(SB),NOSPLIT,$-8
    MOVQ	0(SP), DI	// argc
    LEAQ	8(SP), SI	// argv
    JMP	runtime·rt0_go(SB)

接下来在同名文件中找到

TEXT runtime·rt0_go(SB),NOSPLIT|TOPFRAME,$0

它执行

  • 在堆栈上复制参数。
  • 从给定的(操作系统)堆栈中创建 iStack。
  • _cgo_init(可能会更新堆栈保护)
  • 收集用到的处理器信息

上面信息就是初始化一个协程G0(这是一个根协程,此时还没有调度器,也就是说不受调度器控制)

接下来是各种平台的检测和判断

CALL	runtime·check(SB)

查找代码 在runtime1.go,很亲切的Go语言函数了吧。里面是各种检查。看看都干了啥。

func check() {
    unsafe.Sizeof(...)
    unsafe.Offsetof(...)
    atomic.Cas(...)
    atomic.Or8()
    unsafe.Pointer()
    if _FixedStack != round2(_FixedStack){
        ...
    }   
    ...
}

上面代码执行了:

  • 检查类型长度是否合法
  • 检查偏移量是否合法
  • 检查CAS执行是否合法
  • 检查原子执行是否合法
  • 检查指针执行是否合法
  • 判断栈大小是否是2的幂次方

接下来

CALL	runtime·args(SB)
func args(c int32, v **byte) {
    argc = c
    argv = v
    sysargs(c, v)
}

下面看一下启动顺序:
osinit(操作系统的初始化) -> schedinit(调度器的初始化) -> make & queue new G(新建一个队列G) -> mstart(启动)

CALL    runtime·osinit(SB)
func osinit() {
    ncpu = getncpu()
    physPageSize = getPageSize()
}

runtime/proc.go

CALL	runtime·schedinit(SB)
func schedinit() {
    ...
    stackinit()     //栈空间内存分配
    mallocinit()    //堆内存空间初始化
    cpuinit()      // must run before alginit
    alginit()      // maps, hash, fastrand must not be used before this call
    fastrandinit() // must run before mcommoninit
    mcommoninit(_g_.m, -1)
    modulesinit()   // provides activeModules
    typelinksinit() // uses maps, activeModules
    itabsinit()     // uses activeModules
    stkobjinit()    // must run before GC starts
    ...
    goargs()
    goenvs()
    parsedebugvars()
    gcinit()
}

可以看到上面的代码的操作:

  1. CPU初始化
  2. 栈空间初始化
  3. 堆空间初始化
  4. 命令行参数初始化
  5. 环境变量初始化
  6. GC初始化
    //拿到主函数的地址	,是$runtime·main的地址,这里还没到我们写的main函数呢
    MOVQ	$runtime·mainPC(SB), AX	
    PUSHQ	AX
    //启动一个新协程
    CALL	runtime·newproc(SB) 
    POPQ	AX
    //启动一个M(可以把M看成是一个中间人,它联系Goroutine和Processor)
    CALL	runtime·mstart(SB) 

从上面看到,此时系统里拥有:

  1. G0-根协程
  2. runtime.main的主协程
  3. 启动了M等待调度

runtime.main在runtime/proc.go中(这个是runtime中的main方法,还没到我们自己写的main函数)

// The main goroutine.
func main() {
    g := getg()
    ...
    doInit(&runtime_inittask)
    gcenable()
    fn := main_main 
    fn()
}

从上面看到:

  1. getg() 获取当前的goroutine
  2. 对g做判断和设置操作
  3. 初始化runtime doInit(…)
  4. 启用GC
  5. fn := main_main 这是隐式的调用,因为linker运行时不知道主包的地址。在之前的学习,我们知道编译过程有链接的时候,就会从main_main去找main.main。这个时候,才真正执行到我们程序员写的代码中。 go:linkname main_main main.main

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/64047.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

【RCNN系列】RCNN论文总结

目标检测论文总结 【RCNN系列】 RCNN RCNN目标检测论文总结前言一、Pipeline二、模型设计1.warp2.SVM3.阈值设定4.box回归三、思考四、缺点前言 一些经典论文的总结。 一、Pipeline 首先传入Input image,利用Selective Search(比较古老)算法…

【计算机网络】数据链路层:拓展的以太网

在物理层拓展以太网: 使用光纤拓展:主机使用光纤和一对光纤调制解调器连接到集线器。 使用集线器拓展:使用集线器连成更大的以太网 集线器优点: 使原来不同碰撞域的计算机能够跨碰撞域通信,扩大了以太网覆盖的地理范…

GDB使用技巧和相关插件

GDB使用-小技巧 参考:《100个gdb小技巧》 链接中的文档有许多关于GDB的使用小技巧; $info functions - 列出函数的名称 $s/step - 步入,进入带有调试信息的函数 $n/next - 下一个要执行的程序代码 $call/print - 直接调用函数执行 $i/info …

jvm简介

.什么是JVM? JVM是Java Virtual Machine(Java虚拟机)的缩写,是通过在实际的计算机上仿真模拟各种计算机功能来实现的。由一套字节码指令集、一组寄存器、一个栈、一个垃圾回收堆和一个存储方法域等组成。JVM屏蔽了与操作系统平台相…

Postman如何做接口测试,那些不得不知道的技巧

目录:导读 前言 Postman如何做接口测试1:如何导入 swagger 接口文档 Postman如何做接口测试2:如何切换测试环境 Postman如何做接口测试3:什么?postman 还可以做压力测试? Postman如何做接口测试4&…

电源控制测试老化系统-国产电源测试仪器-电源模块测试系统NSAT-8000

*测试仪器:可编程直流电源、可编程直流电子负载、数字示波器、功率计 *测试产品:电源模块。纳米软件电源ATE自动测试系统适用于大功率工业电源、AC/DC类DC电源供应器、适配器、充电器、LED电源等开关电源之综合性能测试。 *被测项目:有效值电…

快来组战队,赢iPhone啦!

常见问题 问:我邀请的人再去邀请,也算我的战队队员么?我最多可以有多少个队员? 答:您将和您直接邀请的人组成战队,并担任该战队的队长。如果被您邀请的小伙伴再去邀请其他人,那么您邀请的小伙…

跨域推荐(Cross-Domain Recommendation)的最新综述

论文解读系列第十六篇:IJCAI 2021--跨域推荐(Cross-Domain Recommendation)的最新综述 - 知乎 数据稀疏问题 目录 1.背景介绍 (1)内容层级相关性(content-level relevance) (2)用户层级相关性(user-level relevance) (3)产品层级相关性…

OpenCV从2到3的过渡

与版本2.4相比,OpenCV 3.0引入了许多新算法和功能。有些模块已被重写,有些已经重组。尽管2.4中的大多数算法仍然存在,但接口可能不同。本节描述了一般性的最显着变化,过渡操作的所有细节和示例都在本文档的下一部分中。 1、贡献存…

nginx安装与配置反向代理

Nginx (engine x) 是一款基于异步框架的轻量级/高性能的Web 服务器/反向代理服务器/缓存服务器/电子邮件(IMAP/POP3)代理服务器,由俄罗斯的程序设计师Igor Sysoev(伊戈尔赛索耶夫)所开发.话不多说直接上步骤 1.安装nginx,我是在root用户下不需要加sudo yum install nginx 安…

嵌入式分享合集116

一、DC-DC升压电路模块原理 DC-DC 转换器是一种电力电子电路,可有效地将直流电从一个电压转换为另一个电压。 DC-DC 转换器在现代电子产品中扮演着不可或缺的角色。这是因为与线性稳压器相比,它们具有多项优势。尤其是线性稳压器会散发大量热量&#x…

什么是天气预报 API 接口?如何获取天气预报 API?

什么是天气预报API接口? 天气的好坏和人们的生活、工作息息相关,每天的天气如何?总是牵动着人们的心,关注天气就行了人们茶余饭后的话题了。如何获得准确的天气预报?还得从天气预报API接口说起。 天气预报API是提供未…

150398-22-4,三肽Phe-Arg-Arg

The tripeptide FRR was found to exert a Zn⁺ dependent, insulin-mimetic inhibitory action on myocardial proteolysis. 三肽FRR对心肌蛋白水解具有Zn⁺依赖性、胰岛素样抑制作用。 编号: 197811中文名称: 三肽Phe-Arg-Arg英文名: Phe-Arg-ArgCAS号: 150398-22-4单字母: H…

yolov1 论文精读 - You Only Look Once

YOLOv1 Introduction 作者将目标检测进行重构并看作为单一的回归问题,直接从图像到边界框坐标和类别概率。使用我们的系统,您只需要在图像上看一次(you only look once, YOLO),以预测出现的目标和位置。 系统将输入…

SpringMVC(九):作用域传参

文章目录 作用域传参 一、传统方式传递数据 二、使用Model传递数据

PHP反序列化与SESSION

php存储session的三种模式php_serialize&#xff08;php>5.5.4&#xff09; 经过serialize()函数序列化数组 php 键名竖线经过seralize()序列处理的值 php_biary 键名的长度对应ASCII字符键名serialize()序列化的值 测试代码 <?php //ini_set("sessi…

cubeIDE开发,在LCD显示摄像头抓取的图片数据

一、摄像头相关资料信息 在LCD上显示当前camera的图像数据&#xff0c;类似我们前面提到的LCD显示图片数据&#xff0c;就是实时将摄像头抓取的视频数据帧&#xff08;图片&#xff09;转换成图片字码表&#xff0c;即LCD宽*LCD高像素大小的颜色点阵&#xff0c;然后推送到LCD接…

(三) Spring Security Oauth2.0 源码分析--认证中心全流程分析

一 引言 Spring Security Oauth2.0 的认证中心可以简单的理解为是对Spring Security的加强,也是通过FilterChainProxy(其原理可参考前面的Security源码分析)对客户端进行校验后在达到自定义token颁发站点,进行token的颁发,具体流程如下: 用户发起token申请请求(‘/oauth/to…

ARM异常处理(4):SVC和PendSV的作用详解

SVC(Supervisor Call)和PendSV(Pendable Service Call)是针对软件和操作系统的两个异常。 1 SVC SVC用于生成系统函数调用&#xff0c;例如&#xff0c;用户程序不允许直接访问硬件&#xff0c;操作系统可以通过SVC提供对硬件的访问。因此&#xff0c;当用户程序想要使用某些…

STC 51单片机56——摇摇棒

主要代码&#xff1a; //增减图像时&#xff0c;需要修改 switch_show&#xff08;&#xff09;和 H对应参数 //所用单片机&#xff1a;STC15W408S 内部Rc 11.0592MHz #include <STC15.H> #include <intrins.h> #define POSITIVE 0 #define OPPOSE 1 //sbi…