重庆分公司,新征程启航
为企业提供网站建设、域名注册、服务器等服务
Go语言也称 Golang,兼具效率、性能、安全、健壮等特性。这套Go语言教程(Golang教程)通俗易懂,深入浅出,既适合没有基础的读者快速入门,也适合工作多年的程序员查阅知识点。
创新互联公司专注于婺城网站建设服务及定制,我们拥有丰富的企业做网站经验。 热诚为您提供婺城营销型网站建设,婺城网站制作、婺城网页设计、婺城网站官网定制、成都小程序开发服务,打造婺城网络公司原创品牌,更为您提供婺城网站排名全网营销落地服务。
Go 语言
这套教程在讲解一些知识点时,将 Go 语言和其他多种语言进行对比,让掌握其它编程语言的读者能迅速理解 Go 语言的特性。Go语言从底层原生支持并发,无须第三方库、开发者的编程技巧和开发经验就可以轻松搞定。
Go语言(或 Golang)起源于 2007 年,并在 2009 年正式对外发布。Go 是非常年轻的一门语言,它的主要目标是“兼具 Python 等动态语言的开发速度和 C/C++ 等编译型语言的性能与安全性”。
Go语言是编程语言设计的又一次尝试,是对类C语言的重大改进,它不但能让你访问底层操作系统,还提供了强大的网络编程和并发编程支持。Go语言的用途众多,可以进行网络编程、系统编程、并发编程、分布式编程。
Go语言的推出,旨在不损失应用程序性能的情况下降低代码的复杂性,具有“部署简单、并发性好、语言设计良好、执行性能好”等优势,目前国内诸多 IT 公司均已采用Go语言开发项目。Go语言有时候被描述为“C 类似语言”,或者是“21 世纪的C语言”。Go 从C语言继承了相似的表达式语法、控制流结构、基础数据类型、调用参数传值、指针等很多思想,还有C语言一直所看中的编译后机器码的运行效率以及和现有操作系统的无缝适配。
因为Go语言没有类和继承的概念,所以它和 Java 或 C++ 看起来并不相同。但是它通过接口(interface)的概念来实现多态性。Go语言有一个清晰易懂的轻量级类型系统,在类型之间也没有层级之说。因此可以说Go语言是一门混合型的语言。
此外,很多重要的开源项目都是使用Go语言开发的,其中包括 Docker、Go-Ethereum、Thrraform 和 Kubernetes。Go 是编译型语言,Go 使用编译器来编译代码。编译器将源代码编译成二进制(或字节码)格式;在编译代码时,编译器检查错误、优化性能并输出可在不同平台上运行的二进制文件。要创建并运行 Go 程序,程序员必须执行如下步骤。
使用文本编辑器创建 Go 程序;
保存文件;编译程序;运行编译得到的可执行文件。
这不同于 Python、Ruby 和 JavaScript 等语言,它们不包含编译步骤。Go 自带了编译器,因此无须单独安装编译器。
链乔教育在线旗下学硕创新区块链技术工作站是中国教育部学校规划建设发展中心开展的“智慧学习工场2020-学硕创新工作站 ”唯一获准的“区块链技术专业”试点工作站。专业站立足为学生提供多样化成长路径,推进专业学位研究生产学研结合培养模式改革,构建应用型、复合型人才培养体系。
在Go语言中有一些调试技巧能帮助我们快速找到问题,有时候你想尽可能多的记录异常但仍觉得不够,搞清楚堆栈的意义有助于定位Bug或者记录更完整的信息。
本文将讨论堆栈跟踪信息以及如何在堆栈中识别函数所传递的参数。
Functions
先从这段代码开始:
Listing 1
01 package main
02
03 func main() {
04 slice := make([]string, 2, 4)
05 Example(slice, "hello", 10)
06 }
07
08 func Example(slice []string, str string, i int) {
09 panic("Want stack trace")
10 }
Example函数定义了3个参数,1个string类型的slice, 1个string和1个integer, 并且抛出了panic,运行这段代码可以看到这样的结果:
Listing 2
Panic: Want stack trace
goroutine 1 [running]:
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:9 +0x64
main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:5 +0x85
goroutine 2 [runnable]:
runtime.forcegchelper()
/Users/bill/go/src/runtime/proc.go:90
runtime.goexit()
/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1
goroutine 3 [runnable]:
runtime.bgsweep()
/Users/bill/go/src/runtime/mgc0.go:82
runtime.goexit()
/Users/bill/go/src/runtime/asm_amd64.s:2232 +0x1
堆栈信息中显示了在panic抛出这个时间所有的goroutines状态,发生的panic的goroutine会显示在最上面。
Listing 3
01 goroutine 1 [running]:
02 main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:9 +0x64
03 main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:5 +0x85
第1行显示最先发出panic的是goroutine 1, 第二行显示panic位于main.Example中, 并能定位到该行代码,在本例中第9行引发了panic。
下面我们关注参数是如何传递的:
Listing 4
// Declaration
main.Example(slice []string, str string, i int)
// Call to Example by main.
slice := make([]string, 2, 4)
Example(slice, "hello", 10)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
这里展示了在main中带参数调用Example函数时的堆栈信息,比较就能发现两者的参数数量并不相同,Example定义了3个参数,堆栈中显示了6个参数。现在的关键问题是我们要弄清楚它们是如何匹配的。
第1个参数是string类型的slice,我们知道在Go语言中slice是引用类型,即slice变量结构会包含三个部分:指针、长度(Lengthe)、容量(Capacity)
Listing 5
// Slice parameter value
slice := make([]string, 2, 4)
// Slice header values
Pointer: 0x2080c3f50
Length: 0x2
Capacity: 0x4
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
因此,前面3个参数会匹配slice, 如下图所示:
Figure 1
figure provided by Georgi Knox
我们现在来看第二个参数,它是string类型,string类型也是引用类型,它包括两部分:指针、长度。
Listing 6
// String parameter value
"hello"
// String header values
Pointer: 0x425c0
Length: 0x5
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
可以确定,堆栈信息中第4、5两个参数对应代码中的string参数,如下图所示:
Figure 2
figure provided by Georgi Knox
最后一个参数integer是single word值。
Listing 7
// Integer parameter value
10
// Integer value
Base 16: 0xa
// Declaration
main.Example(slice []string, str string, i int)
// Stack trace
main.Example(0x2080c3f50, 0x2, 0x4, 0x425c0, 0x5, 0xa)
现在我们可以匹配代码中的参数到堆栈信息了。
Figure 3
figure provided by Georgi Knox
Methods
如果我们将Example作为结构体的方法会怎么样呢?
Listing 8
01 package main
02
03 import "fmt"
04
05 type trace struct{}
06
07 func main() {
08 slice := make([]string, 2, 4)
09
10 var t trace
11 t.Example(slice, "hello", 10)
12 }
13
14 func (t *trace) Example(slice []string, str string, i int) {
15 fmt.Printf("Receiver Address: %p\n", t)
16 panic("Want stack trace")
17 }
如上所示修改代码,将Example定义为trace的方法,并通过trace的实例t来调用Example。
再次运行程序,会发现堆栈信息有一点不同:
Listing 9
Receiver Address: 0x1553a8
panic: Want stack trace
01 goroutine 1 [running]:
02 main.(*trace).Example(0x1553a8, 0x2081b7f50, 0x2, 0x4, 0xdc1d0, 0x5, 0xa)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:16 +0x116
03 main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:11 +0xae
首先注意第2行的方法调用使用了pointer receiver,在package名字和方法名之间多出了"*trace"字样。另外,参数列表的第1个参数标明了结构体(t)地址。我们从堆栈信息中看到了内部实现细节。
Packing
如果有多个参数可以填充到一个single word, 则这些参数值会合并打包:
Listing 10
01 package main
02
03 func main() {
04 Example(true, false, true, 25)
05 }
06
07 func Example(b1, b2, b3 bool, i uint8) {
08 panic("Want stack trace")
09 }
这个例子修改Example函数为4个参数:3个bool型和1个八位无符号整型。bool值也是用8个bit表示,所以在32位和64位架构下,4个参数可以合并为一个single word。
Listing 11
01 goroutine 1 [running]:
02 main.Example(0x19010001)
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:8 +0x64
03 main.main()
/Users/bill/Spaces/Go/Projects/src/github.com/goinaction/code/
temp/main.go:4 +0x32
这是本例的堆栈信息,看下图的具体分析:
Listing 12
// Parameter values
true, false, true, 25
// Word value
Bits Binary Hex Value
00-07 0000 0001 01 true
08-15 0000 0000 00 false
16-23 0000 0001 01 true
24-31 0001 1001 19 25
// Declaration
main.Example(b1, b2, b3 bool, i uint8)
// Stack trace
main.Example(0x19010001)
以上展示了参数值是如何匹配到4个参数的。当我们看到堆栈信息中包括十六进制值,需要知道这些值是如何传递的。
在前一小节中介绍了点亮第一个LED灯,这里我们准备进阶尝试下,输出第一段PWM波形。(PWM也就是脉宽调制,一种可调占空比的技术,得到的效果就是:如果用示波器测量引脚会发现有方波输出,而且高电平、低电平的时间是可调的。)
这里爪爪熊准备写成一个golang的库,并开源到github上,后续更新将直接更新到github中,如果你有兴趣可以和我联系。 github.com/dpawsbear/bear_rpi_go
我在很多的教程中都看到说树莓派的PWM(硬件)只有一个GPIO能够输出,就是 GPIO1 。这可是不小的打击,因为我想使用至少四个 PWM ,还是不死心,想通过硬件手册上找寻蛛丝马迹,看看究竟怎么回事。
手册上找寻东西稍等下讲述,这里先提供一种方法测试 树莓派3B 的 PWM 方法:用指令控制硬件PWM。
这里通过指令的方式掌握了基本的pwm设置技巧,决定去翻一下手册看看到底PWM怎么回事,这里因为没有 BCM2837 的手册,根据之前文章引用官网所说, BCM2835 和 BCM2837 应该是一样的。这里我们直接翻阅 BCM2835 的手册,直接找到 PWM 章节。找到了如下图:
图中可以看到在博通的命名规则中 GPIO 12、13、18、19、40、41、45、52、53 均可以作为PWM输出。但是只有两路PWM0 PWM1。根据我之前所学知识,不出意外应该是PWM0 和 PWM1可以输出不一样的占空比,但是频率应该是一样的。因为没有示波器,暂时不好测试。先找到下面对应图:
根据以上两个图对比可以发现如下规律:
对照上面的表可以看出从 BCM2837 中印出来的能够使用在PWM上的就这几个了。
为了验证个人猜想是否正确,这里先直接使用指令的模式,模拟配置下是否能够正常输出。
通过上面一系列指令模拟发现,(GPIO1、GPIO26)、(GPIO23、GPIO24)是绑定在一起的,调节任意一个,另外一个也会发生变化。也即是PWM0、PWM1虽然输出了两路,可以理解成两路其实都是连在一个输出口上。这里由于没有示波器或者逻辑分析仪这类设备(仅有一个LED灯),所以测试很简陋,下一步是使用示波器这类东西对频率以及信号稳定性进行下测试。
小节:树莓派具有四路硬件输出PWM能力,但是四路中只能输出两个独立(占空比独立)的PWM,同时四路输出的频率均是恒定的。
上面大概了解清楚了树莓派3B的PWM结构,接下来就是探究如何使用Go语言进行设置。
因为拿到了手册,这里我想直接操作寄存器的方式进行设置,也是顺便学习下Go语言处理寄存器的过程。首先需要拿到pwm 系列寄存器的基地址,但是翻了一圈手册,发现只有偏移,没有找到基地址。
经过了一段时间的努力后,决定写一个 树莓派3B golang包开源放在github上,只需要写相关程序进行调用就可以了,以下是相关demo(pwm)(在GPIO.12 上输出PWM波,放上LED灯会有呼吸灯的效果,具体多少频率还没有进行测试)
以下是demo(pwm) 源码