蚊章汇 - Go
type: channel
link: https://linuxcn.bwsl.wang/tag/Go/
items: 硬核观察 #1212 Go 程序员大多使用 Linux 或 MacOS
items: 这儿几个字节，那里几个字节，我们说的是真正的内存
items: 硬核观察 #1094 “Go 2 永远不会出现”
items: Go 数组和切片的介绍
items: 在 Go 中复制文件的三种方法
items: 在 Go 中实现一个支持并发的 TCP 服务端
items: 在 Go 中生成随机的安全密码
items: 我最喜欢的 Go 构建选项
items: 硬核观察 #597 Fedora 项目负责人抨击英伟达的专有 Linux 驱动程序
items: 你需要了解的 Go 中的模糊测试

硬核观察 #1212 Go 程序员大多使用 Linux 或 MacOS
type: item
date: 2023-12-12T02:27:00+08:00
description: 1 Go 程序员大多使用 Linux 或 MacOSGo 团队在八月份对 Go 开发者进行了一项调查，调查结果显示 90% 的受访者表示他们在上一年使用 Go 时感到满意。他们在 Linux（63%）和 macOS（58%）系统上使用 Go 语言，但新的 Go 程序员更倾向于使用 Windows。虽然 x86 兼容系统仍占开发的大多数（89%），但 ARM64 现在也被大多数受访者使用（56%），这种采用似乎部分是由苹果芯片推动的。受访者最喜欢的代码编辑器是 VS Code（44%）、GoLand（31%）、Vim/Neovim（16%）和 Emacs（3%）。3/4 的受访者在使用云服务的 Go 软件上工作，这表明开发人员认为 Go 是一种适用于现代云开发的语言。（插图：DA/3cf4022f-9a7a-460b-8be4-741c10daa2f7）消息来源：Go Dev老王点评：使用 Linux 的比 macOS 和 Windows 的要多，但是为什么使用 VS Code 的最多呢。2 欧盟就《人工智能法》的推出达成共识来自欧洲议会及其 27 个成员国的谈判代表克服了巨大分歧，签署了《人工智能法》的临时政治协议。争议点主要是生成式人工智能和警方使用面部识别监控等方面。这一结果是在上周举行的马拉松式闭门会谈后达成的，其中一次会谈持续了 22 个小时，“欧盟成为第一个为人工智能的使用制定明确规则的大陆”。关于最终法律的具体内容，官员们提供的细节很少，该法律最早要到 2025 年才能生效，预计将为进一步谈判留出余地，以制定出更细的条款。（插图：DA/04fef112-325d-43c4-a6b1-1731cd9866b8）消息来源：AP News老王点评：就怕立法追不上 AI 的发展速度。3 让网速感觉更快的新标准 L4S这项名为 L4S 的新互联网标准已于今年 1 月定稿并发布，它可以大大减少我们等待网页或流媒体加载的时间，并减少视频通话中的故障。它还有助于改变我们对网速的看法，并帮助开发人员创建在当前互联网现实情况下无法实现的应用程序。L4S 是 低延迟、低损耗、可扩展吞吐量 Low Latency, Low Loss, Scalable Throughput 的缩写，其目标是通过减少排队的需要，确保数据包减少不必要的排队等待，花费尽可能少的时间。在某些情况下，数据包缓冲延迟通常为数百毫秒甚至数千毫秒，而 L4S 则可以将延迟时间降至几毫秒。更好的是，它与目前使用的拥塞控制系统广泛兼容。虽然 L4S 还没有被广泛使用，但苹果、谷歌、英伟达、爱立信、德国电信等公司都已经对此表示了极大兴趣。（插图：DA/cdd2468a-758a-487e-b5bd-b908700ce05e）消息来源：The Verge老王点评：为什么这么好的协议改进，我居然之前都没听说过。

这儿几个字节，那里几个字节，我们说的是真正的内存
type: item
date: 2023-11-15T23:48:58+08:00
description: 今天的帖子来自于最近的 Go 语言的一次小测试，观察下面的测试基础片段 [1] ：func BenchmarkSortStrings(b *testing.B) { s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"} b.ReportAllocs() for i := 0; i < b.N; i++ { sort.Strings(s) } } sort.Strings 是 sort.StringSlice(s) 的便捷包装器，sort.Strings 在原地对输入进行排序，因此不会分配内存（或至少 43% 回答此问题的 Twitter 用户是这么认为的）。然而，至少在 Go 的最近版本中，基准测试的每次迭代都会导致一次堆分配。为什么会是这种情况？正如所有 Go 程序员应该知道的那样，接口是以 双词结构 实现的。每个接口值包含一个字段，其中保存接口内容的类型，以及指向接口内容的指针。 [2] 在 Go 语言伪代码中，一个接口可能是这样的：type interface struct { // the ordinal number for the type of the value // assigned to the interface type uintptr // (usually) a pointer to the value assigned to // the interface data uintptr } interface.data 可以容纳一个机器字（在大多数情况下为 8 个字节），但一个 []string 却需要 24 个字节：一个字用于指向切片的底层数组；一个字用于存储切片的长度；另一个字用于存储底层数组的剩余容量。那么，Go 是如何将 24 个字节装入个 8 个字节的呢？通过编程中最古老的技巧，即间接引用。一个 []string，即 s，需要 24 个字节；但 *[]string —— 即指向字符串切片的指针，只需要 8 个字节。逃逸到堆为了让示例更加明确，以下是重新编写的基准测试，不使用 sort.Strings 辅助函数：func BenchmarkSortStrings(b *testing.B) { s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"} b.ReportAllocs() for i := 0; i < b.N; i++ { var ss sort.StringSlice = s var si sort.Interface = ss // allocation sort.Sort(si) } } 为了让接口正常运行，编译器将赋值重写为 var si sort.Interface = &ss，即 ss 的地址分配给接口值。 [3] 我们现在有这么一种情况：出现一个持有指向 ss 的指针的接口值。它指向哪里？还有 ss 存储在哪个内存位置？似乎 ss 被移动到了堆上，这也同时导致了基准测试报告中的分配：Total: 296.01MB 296.01MB (flat, cum) 99.66% 8 . . func BenchmarkSortStrings(b *testing.B) { 9 . . s := []string{"heart", "lungs", "brain", "kidneys", "pancreas"} 10 . . b.ReportAllocs() 11 . . for i := 0; i < b.N; i++ { 12 . . var ss sort.StringSlice = s 13 296.01MB 296.01MB var si sort.Interface = ss // allocation 14 . . sort.Sort(si) 15 . . } 16 . . } 发生这种分配是因为编译器当前无法确认 ss 比 si 生存期更长。Go 编译器开发人员对此的普遍态度是，觉得 这个问题改进的余地，不过我们另找时间再议。事实上，ss 就是被分配到了堆上。因此，问题变成了：每次迭代会分配多少个字节？为什么不去询问 testing 包呢？% go test -bench=. sort_test.go goos: darwin goarch: amd64 cpu: Intel(R) Core(TM) i7-5650U CPU @ 2.20GHz BenchmarkSortStrings-4 12591951 91.36 ns/op 24 B/op 1 allocs/op PASS ok command-line-arg…

硬核观察 #1094 “Go 2 永远不会出现”
type: item
date: 2023-08-16T04:54:00+08:00
description: “Go 2 永远不会出现”谷歌的 Russ Cox 称现在还没有 Go 2 的计划，这就提出了一个显而易见的问题：我们什么时候才能看到会破坏旧版 Go 1 程序的 Go 2 规范呢？在今天的博文中他写道：“答案是永远不会。从与过去决裂、不再编译旧程序的意义上讲，Go 2 永远不会出现。从 2017 年开始对 Go 1 进行重大修订的意义上来说，Go 2 已经出现了。” Go 语言的开发者认为，“优先考虑兼容性是我们为 Go 1 所做的最重要的设计决定。”消息来源：Phoronix老王点评：编程语言的大版本换代中，Python、Perl 都遭遇过严重的问题，而 Go 很好地绕开了这些前辈们遇到的陷阱，显然会让 Go 的发展更加顺畅。“诈弹” 毁了 DEF CON 派对之夜本次 DEF CON 黑客大会于 8 月 10 日在拉斯维加斯召开，并于 8 月 13 日结束，今年据称有超过 3 万人参加了大会。在周六傍晚，主会场受到炸弹威胁，导致主会场大厅被清空，消防人员和警察对大楼进行了搜查，但没有找到可疑物品。炸弹威胁被认为是一次恶作剧，但恶作剧者毁掉了所有人的夜晚。受此影响，大会主办方取消了当晚在主会场的庆祝活动，令数千人倍感失望。这个恶作剧者还导致四号轨道大厅欣赏《2001：太空漫游》的观众被迫错过了电影最后十分钟的关键时刻。消息来源：The Register老王点评：本来 DEF CON 是欢迎恶作剧的，但是这个蠢蛋的恶作剧太过分了，毁掉了大家的狂欢夜。微软发现可关闭发电厂的安全漏洞微软披露了一个广泛使用的工具集合中的 15 个高严重性漏洞，这些工具用于开发可编程逻辑控制器，这种面包大小的设备用于打开和关闭阀门、转动转子以及控制全球工业设施中的各种其他物理设备。微软警告说，虽然利用代码执行和拒绝服务漏洞很困难，但它能使威胁行为者 “对目标造成巨大损害”，如关闭发电厂，而远程代码执行可为设备创建后门，让攻击者篡改操作，导致 PLC 以异常方式运行，或窃取关键信息。消息来源：Ars Technica老王点评：这种工业控制领域的安全风险，不出则已，一旦被利用，造成的危害可远不止数据丢失那么简单。

Go 数组和切片的介绍
type: item
date: 2022-06-02T18:57:00+08:00
description: 了解使用数组和切片在 Go 中存储数据的优缺点，以及为什么其中一个更好。在本系列的第四篇文章中，我将解释 Go 数组和切片，包括如何使用它们，以及为什么你通常要选择其中一个而不是另一个。数组数组是编程语言中最流行的数据结构之一，主要原因有两个：一是简单易懂，二是可以存储许多不同类型的数据。你可以声明一个名为 anArray 的 Go 数组，该数组存储四个整数，如下所示：anArray := [4]int{-1, 2, 0, -4} 数组的大小应该在它的类型之前声明，而类型应该在声明元素之前定义。len() 函数可以帮助你得到任何数组的长度。上面数组的大小是 4。如果你熟悉其他编程语言，你可能会尝试使用 for 循环来遍历数组。Go 当然也支持 for 循环，不过，正如你将在下面看到的，Go 的 range 关键字可以让你更优雅地遍历数组或切片。最后，你也可以定义一个二维数组，如下：twoD := [3][3]int{ {1, 2, 3}, {6, 7, 8}, {10, 11, 12}} arrays.go 源文件中包含了 Go 数组的示例代码。其中最重要的部分是：for i := 0; i < len(twoD); i++ { k := twoD[i] for j := 0; j < len(k); j++ { fmt.Print(k[j], " ") } fmt.Println() } for _, a := range twoD { for _, j := range a { fmt.Print(j, " ") } fmt.Println() } 通过上述代码，我们知道了如何使用 for 循环和 range 关键字迭代数组的元素。arrays.go 的其余代码则展示了如何将数组作为参数传递给函数。以下是 arrays.go 的输出：$ go run arrays.go Before change(): [-1 2 0 -4] After change(): [-1 2 0 -4] 1 2 3 6 7 8 10 11 12 1 2 3 6 7 8 10 11 12 这个输出告诉我们：对函数内的数组所做的更改，会在函数退出后丢失。数组的缺点Go 数组有很多缺点，你应该重新考虑是否要在 Go 项目中使用它们。首先，数组定义之后，大小就无法改变，这意味着 Go 数组不是动态的。简而言之，如果你需要将一个元素添加到一个没有剩余空间的数组中，你将需要创建一个更大的数组，并将旧数组的所有元素复制到新数组中。其次，当你将数组作为参数传递给函数时，实际上是传递了数组的副本，这意味着你对函数内部的数组所做的任何更改，都将在函数退出后丢失。最后，将大数组传递给函数可能会很慢，主要是因为 Go 必须创建数组的副本。以上这些问题的解决方案，就是使用 Go 切片。切片Go 切片与 Go 数组类似，但是它没有后者的缺点。首先，你可以使用 append() 函数将元素添加到现有切片中。此外，Go 切片在内部使用数组实现，这意味着 Go 中每个切片都有一个底层数组。切片具有 capacity 属性和 length 属性，它们并不总是相同的。切片的长度与元素个数相同的数组的长度相同，可以使用 len() 函数得到。切片的容量是当前为切片分配的空间，可以使用 cap() 函数得到。由于切片的大小是动态的，如果切片空间不足（也就是说，当你尝试再向切片中添加一个元素时，底层数组的长度恰好与容量相等），Go 会自动将它的当前容量加倍，使其空间能够容纳更多元素，然后将请求的元素添加到底层数组中。此外，切片是通过引用传递给函数的，这意味着实际传递给函数的是切片变量的内存地址，这样一来，你对函数内部的切片所做的任何修改，都不会在函数退出后丢失。因此，将大切片传递给函数，要比将具有相同数量元素的数组传递给同一函数快得多。这是因为 Go 不必拷贝切片 —— 它只需传递切片变量的内存地址。slice.go 源文件中有 Go 切片的代码示例，其中包含以下代码：package main import ( "fmt" ) func negative(x []int) { for i, k := range x { x[i] = -k } } func printSlice(x []int) { for _, number := range x { fmt.Printf("%d ", number) } fmt.Println() } func main() { s := []int{0, 14, 5, 0, 7, 19} printSlice(s) negative(s) printSlice(s) fmt.Printf("Before. Cap: %d, length: %d\n…

在 Go 中复制文件的三种方法
type: item
date: 2022-05-31T23:34:00+08:00
description: 本文是 Go 系列的第三篇文章，我将介绍三种最流行的复制文件的方法。本文将介绍展示如何使用 Go 编程语言 来复制文件。在 Go 中复制文件的方法有很多，我只介绍三种最常见的：使用 Go 库中的 io.Copy() 函数调用、一次读取输入文件并将其写入另一个文件，以及使用缓冲区一块块地复制文件。方法一：使用 io.Copy()第一种方法就是使用 Go 标准库的 io.Copy() 函数。你可以在 copy() 函数的代码中找到它的实现逻辑，如下所示：func copy(src, dst string) (int64, error) { sourceFileStat, err := os.Stat(src) if err != nil { return 0, err } if !sourceFileStat.Mode().IsRegular() { return 0, fmt.Errorf("%s is not a regular file", src) } source, err := os.Open(src) if err != nil { return 0, err } defer source.Close() destination, err := os.Create(dst) if err != nil { return 0, err } defer destination.Close() nBytes, err := io.Copy(destination, source) return nBytes, err } 首先，上述代码做了两个判断，以便确定它可以被打开读取：一是判断将要复制的文件是否存在（os.Stat(src)），二是判断它是否为常规文件（sourceFileStat.Mode().IsRegular()）。剩下的所有工作都由 io.Copy(destination, source) 这行代码来完成。io.Copy() 函数执行结束后，会返回复制的字节数和复制过程中发生的第一条错误消息。在 Go 中，如果没有错误消息，错误变量的值就为 nil。你可以在 io 包 的文档页面了解有关 io.Copy() 函数的更多信息。运行 cp1.go 将产生以下输出：$ go run cp1.go Please provide two command line arguments! $ go run cp1.go fileCP.txt /tmp/fileCPCOPY Copied 3826 bytes! $ diff fileCP.txt /tmp/fileCPCOPY 这个方法已经非常简单了，不过它没有为开发者提供灵活性。这并不总是一件坏事，但是，有些时候，开发者可能会需要/想要告诉程序该如何读取文件。方法二：使用 ioutil.WriteFile() 和 ioutil.ReadFile()复制文件的第二种方法是使用 ioutil.ReadFile() 和 ioutil.WriteFile() 函数。第一个函数用于将整个文件的内容，一次性地读入到某个内存中的字节切片里；第二个函数则用于将字节切片的内容写入到一个磁盘文件中。实现代码如下：input, err := ioutil.ReadFile(sourceFile) if err != nil { fmt.Println(err) return } err = ioutil.WriteFile(destinationFile, input, 0644) if err != nil { fmt.Println("Error creating", destinationFile) fmt.Println(err) return } 上述代码包括了两个 if 代码块（嗯，用 Go 写程序就是这样的），程序的实际功能其实体现在 ioutil.ReadFile() 和 ioutil.WriteFile() 这两行代码中。运行 cp2.go，你会得到下面的输出：$ go run cp2.go Please provide two command line arguments! $ go run cp2.go fileCP.txt /tmp/copyFileCP $ diff fileCP.txt /tmp/copyFileCP 请注意，虽然这种方法能够实现文件复制，但它在复制大文件时的效率可能不高。这是因为当文件很大时，ioutil.ReadFile() 返回的字节切片会很大。方法三：使用 os.Read() 和 os.Write()在 Go 中复制文件的第三种方法就是下面要介绍的 cp3.go。它接受三个参数：输入文件名、输出文件名和缓冲区大小。cp3.go 最重要的部分位…

在 Go 中实现一个支持并发的 TCP 服务端
type: item
date: 2022-05-22T19:55:00+08:00
description: 仅用大约 65 行代码，开发一个用于生成随机数、支持并发的 TCP 服务端。TCP 和 UDP 服务端随处可见，它们基于 TCP/IP 协议栈，通过网络为客户端提供服务。在这篇文章中，我将介绍如何使用 Go 语言 开发一个用于返回随机数、支持并发的 TCP 服务端。对于每一个来自 TCP 客户端的连接，它都会启动一个新的 goroutine（轻量级线程）来处理相应的请求。你可以在 GitHub 上找到本项目的源码：concTcp.go。处理 TCP 连接这个程序的主要逻辑在 handleConnection() 函数中，具体实现如下：func handleConnection(c net.Conn) { fmt.Printf("Serving %s\n", c.RemoteAddr().String()) for { netData, err := bufio.NewReader(c).ReadString('\n') if err != nil { fmt.Println(err) return } temp := strings.TrimSpace(string(netData)) if temp == "STOP" { break } result := strconv.Itoa(random()) + "\n" c.Write([]byte(string(result))) } c.Close() } 如果 TCP 客户端发送了一个 “STOP” 字符串，为它提供服务的 goroutine 就会终止；否则，TCP 服务端就会返回一个随机数给它。只要客户端不主动终止，服务端就会一直提供服务，这是由 for 循环保证的。具体来说，for 循环中的代码使用了 bufio.NewReader(c).ReadString('\n') 来逐行读取客户端发来的数据，并使用 c.Write([]byte(string(result))) 来返回数据（生成的随机数）。你可以在 Go 的 net 标准包 文档 中了解更多。支持并发在 main() 函数的实现部分，每当 TCP 服务端收到 TCP 客户端的连接请求，它都会启动一个新的 goroutine 来为这个请求提供服务。func main() { arguments := os.Args if len(arguments) == 1 { fmt.Println("Please provide a port number!") return } PORT := ":" + arguments[1] l, err := net.Listen("tcp4", PORT) if err != nil { fmt.Println(err) return } defer l.Close() rand.Seed(time.Now().Unix()) for { c, err := l.Accept() if err != nil { fmt.Println(err) return } go handleConnection(c) } } 首先，main() 确保程序至少有一个命令行参数。注意，现有代码并没有检查这个参数是否为有效的 TCP 端口号。不过，如果它是一个无效的 TCP 端口号，net.Listen() 就会调用失败，并返回一个错误信息，类似下面这样：$ go run concTCP.go 12a listen tcp4: lookup tcp4/12a: nodename nor servname provided, or not known $ go run concTCP.go -10 listen tcp4: address -10: invalid port net.Listen() 函数用于告诉 Go 接受网络连接，因而承担了服务端的角色。它的返回值类型是 net.Conn，后者实现了 io.Reader 和 io.Writer 接口。此外，main() 函数中还调用了 rand.Seed() 函数，用于初始化随机数生成器。最后，for 循环允许程序一直使用 Accept() 函数来接受 TCP 客户端的连接请求，并以 goroutine 的方式来运行 handleConnection(c) 函数，处理客户端的后续请求。net.Listen() 的第一个参数net.Listen() 函数的第一个参数定义了使用的网络类型，而第二个参数定义了服务端监听的地址和端口号。第一个参数的有效值为 tcp、tcp4、tcp6、udp、udp4、udp6、ip、ip4、ip6、Unix（Unix 套接字）、Unixgram 和 Unixpacket，其中：tcp4、udp4 和 ip4 只接…

在 Go 中生成随机的安全密码
type: item
date: 2022-05-21T23:25:55+08:00
description: Go 的随机数生成器是生成难以猜测的密码的好方法。你可以使用 Go 编程语言 提供的随机数生成器来生成由 ASCII 字符组成的难以猜测的密码。尽管本文中提供的代码很容易阅读，但是你仍需要了解 Go 的基础知识，才能更好地理解它。如果你是对 Go 还不熟悉，请阅读 Go 语言之旅 来了解更多信息，然后返回此处。在介绍实用程序和它的代码之前，让我们先来看看这个 ASCII 表的子集，它可以在 man ascii 命令的输出中找到：30 40 50 60 70 80 90 100 110 120 --------------------------------- 0: ( 2 < F P Z d n x 1: ) 3 = G Q [ e o y 2: * 4 > H R \ f p z 3: ! + 5 ? I S ] g q { 4: " , 6 @ J T ^ h r | 5: # - 7 A K U _ i s } 6: $ . 8 B L V ` j t ~ 7: % / 9 C M W a k u DEL 8: & 0 : D N X b l v 9: ' 1 ; E O Y c m w 在所有 ASCII 字符中，可打印字符的十进制值范围为 33 到 126，其他的 ASCII 值都不适合用于密码。因此，本文介绍的实用程序将生成该范围内的 ASCII 字符。生成随机整数第一个实用程序名为 random.go，它生成指定数量的随机整数，这些整数位于给定范围内。random.go 最重要的部分是这个函数：func random(min, max int) int { return rand.Intn(max-min) + min } 此函数使用了 rand.Intn() 函数来生成一个属于给定范围的随机整数。请注意，rand.Intn() 返回一个属于 [0,n) 的非负随机整数。如果它的参数是一个负数，这个函数将会抛出异常，异常消息是：panic: invalid argument to Intn。你可以在 math/rand 文档 中找到 math/rand 包的使用说明。random.go 实用程序接受三个命令行参数：生成的整数的最小值、最大值和个数。编译和执行 random.go 会产生这样的输出：$ go build random.go $ ./random Usage: ./random MIX MAX TOTAL $ ./random 1 3 10 2 2 1 2 2 1 1 2 2 1 如果你希望在 Go 中生成更安全的随机数，请使用 Go 库中的 crypto/rand 包。生成随机密码第二个实用程序 randomPass.go 用于生成随机密码。randomPass.go 使用 random() 函数来生成随机整数，它们随后被以下 Go 代码转换为 ASCII 字符：for { myRand := random(MIN, MAX) newChar := string(startChar[0] + byte(myRand)) fmt.Print(newChar) if i == LENGTH { break } i++ } MIN 的值为 0，MAX 的值为 94，而 startChar 的值为 !，它是 ASCII 表中第一个可打印的字符（十进制 ASCII 码为 33）。因此，所有生成的 ASCII 字符都位于 ! 和 ~ 之间，后者的十进制 ASCII 码为 126。因此，生成的每个随机数都大于 MIN，小于 MAX，并转换为 ASCII 字符。该过程继续进行，直到生成的密码达到指定的长度。randomPass.go 实用程序接受单个（可选）命令行参数，以定义生成密码的长度，默认值为 8，这是一个非常常见的密码长度。执行 randomPass.go 会得到类似下面的输出：$ go run randomPass.go 1 Z $ go run randomPass.go 10 #Cw^a#IwkT $ go run randomPass.go Using default values! [PP8@'Ci 最后一个细节：不要忘记调用 rand.Seed()，并提供一个 种子 seed 值，以初始化随机数生成器。如果你始终使用相同的种子值，随机数生成器将生成相同的随机整数序列。你可以在 GitHub 找到 random.go 和 randomPass.go 的源码。你也可以直接在 play.golang.org 上执行它们。我希望这篇文章对你有所帮助。如有任何问题，请在下方发表评论或在 Twitter 上与我联系。via: https://opensource.com/article/18/5/creat…

我最喜欢的 Go 构建选项
type: item
date: 2022-05-01T01:21:00+08:00
description: 这些方便的 Go 构建选项可以帮助你更好地理解 Go 的编译过程。学习一门新的编程语言最令人欣慰的部分之一，就是最终运行了一个可执行文件，并获得预期的输出。当我开始学习 Go 这门编程语言时，我先是阅读一些示例程序来熟悉语法，然后是尝试写一些小的测试程序。随着时间的推移，这种方法帮助我熟悉了编译和构建程序的过程。Go 的构建选项提供了更好地控制构建过程的方法。它们还可以提供额外的信息，帮助把这个过程分成更小的部分。在这篇文章中，我将演示我所使用的一些选项。注意：我使用的“ 构建 build ”和“ 编译 compile ”这两个词是同一个意思。开始使用 Go我使用的 Go 版本是 1.16.7。但是，这里给出的命令应该也能在最新的版本上运行。如果你没有安装 Go，你可以从 Go 官网 上下载它，并按照说明进行安装。你可以通过打开一个命令提示符，并键入下面的命令来验证你所安装的版本：$ go version 你应该会得到类似下面这样的输出，具体取决于你安装的版本：go version go1.16.7 linux/amd64 基本的 Go 程序的编译和执行方法我将从一个在屏幕上简单打印 “Hello World” 的 Go 程序示例开始，就像下面这样：$ cat hello.go package main import "fmt" func main() { fmt.Println("Hello World") } 在讨论更高级的选项之前，我将解释如何编译这个 Go 示例程序。我使用了 build 命令，后面跟着 Go 程序的源文件名，本例中是 hello.go，就像下面这样：$ go build hello.go 如果一切工作正常，你应该看到在你的当前目录下创建了一个名为 hello 的可执行文件。你可以通过使用 file 命令验证它是 ELF 二进制可执行格式（在 Linux 平台上）。你也可以直接执行它，你会看到它输出 “Hello World”。$ ls hello hello.go $ file ./hello ./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped $ ./hello Hello World Go 提供了一个方便的 run 命令，以便你只是想看看程序是否能正常工作，并获得预期的输出，而不想生成一个最终的二进制文件。请记住，即使你在当前目录中没有看到可执行文件，Go 仍然会在某个地方编译并生成可执行文件并运行它，然后把它从系统中删除。我将在本文后面的章节中解释。$ go run hello.go Hello World $ ls hello.go 更多细节上面的命令就像一阵风一样，一下子就运行完了我的程序。然而，如果你想知道 Go 在编译这些程序的过程中做了什么，Go 提供了一个 -x 选项，它可以打印出 Go 为产生这个可执行文件所做的一切。简单看一下你就会发现，Go 在 /tmp 内创建了一个临时工作目录，并生成了可执行文件，然后把它移到了 Go 源程序所在的当前目录。$ go build -x hello.go WORK=/tmp/go-build1944767317 mkdir -p $WORK/b001/ << snip >> mkdir -p $WORK/b001/exe/ cd . /usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK \ /b001/exe/a.out -importcfg $WORK/b001 \ /importcfg.link -buildmode=exe -buildid=K26hEYzgDkqJjx2Hf-wz/\ nDueg0kBjIygx25rYwbK/W-eJaGIOdPEWgwC6o546 \ /K26hEYzgDkqJjx2Hf-wz -extld=gcc /root/.cache/go-build /cc \ /cc72cb2f4fbb61229885fc434995964a7a4d6e10692a23cc0ada6707c5d3435b-d /usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK \ /b001/exe/a.out # internal mv $WORK/b001/exe/a.out hello rm -r $WORK/b001/ 这有助于解决在程序运行后却在当前目录下没有生成可执行文件的谜团。使用 -x 显示可执行文件确实在 /tmp 工作目录下创建并被执行了。然而，与 build 命令不同的是，可执行文…

硬核观察 #597 Fedora 项目负责人抨击英伟达的专有 Linux 驱动程序
type: item
date: 2022-04-06T03:45:32+08:00
description: Fedora 项目负责人抨击英伟达的专有 Linux 驱动程序据消息，Fedora 项目负责人称，Fedora/Red Hat 和其他公司已经做了“一大堆额外的费劲工作”来改善英伟达的专有驱动程序的 Linux 体验，但结果并不理想。他呼吁英伟达工程师为开源的 Nouveau 驱动程序做出贡献，由于缺乏对重启的支持，以及签名固件镜像的障碍，该驱动程序对英伟达的现代 GPU 相当无用。而与之对比的是，英特尔和 AMD 都支持开源驱动程序，并为上游 Linux 组件做出了巨大贡献。老王点评：看来 Torvalds 多年前指着鼻子的臭骂还不够啊。英国政府让皇家铸币厂铸造 NFT据消息，英国财政大臣已经要求英国皇家铸币厂在今年夏天发行一种 NFT。财政大臣说，这一宣布是“使英国成为全球加密资产技术中心”，并计划建立一个“金融市场基础设施沙盒”，让加密货币公司可以进行实验和创新。但另一方面，英国金融行为监管局也加强了对提供加密货币产品和服务的公司的监管，并警告消费者，加密货币资产是“高风险的”。老王点评：监管与拥抱，缺一不可，这才是开放的态度。谷歌要求每项 Go 语言变化都要有两名员工审查据消息，谷歌正计划加强其开源 Go 语言的安全性，现在要求在向用户发布每项改动之前，必须有两名谷歌员工进行审查，而以前只需要一名谷歌员工。审查者可以投下从 +2 到 -2 的票，至少有一个维护者对该修改投 +2 的票才能通过。但是也有贡献者对此改变不满，认为“这一变化有效地将提交者群体限制为谷歌员工”。老王点评：虽然初衷可能是为了保障 Go 语言的供应链安全，但是确实加强了谷歌对 Go 语言的控制力。

你需要了解的 Go 中的模糊测试
type: item
date: 2022-03-18T18:31:29+08:00
description: Go 团队接受了新增对模糊测试的支持的提议。Go 的应用越来越广泛。现在它是云原生软件、容器软件、命令行工具和数据库等等的首选语言。Go 很早之前就已经有了内建的 对测试的支持。这使得写测试代码和运行都相当简单。什么是模糊测试？ 模糊测试 fuzz testing （fuzzing）是指向你的软件输入非预期的数据。理想情况下，这种测试会让你的应用程序崩溃或有非预期的表现。抛开最终的结果，从程序对非预期的输入数据的处理结果中你可以得到很多信息，这样你就可以增加一些合适的错误处理。任何一个软件都有对不同来源的输入或数据的接收说明，软件会对这些数据进行处理并返回适当的结果。软件开发后，测试工程师团队对其进行测试，找出软件中的错误，给出测试报告，并（由开发者）修复。通常测试的目的是验证软件的行为是否符合预期。测试又可以细分为不同的类型，如功能测试、集成测试、性能测试等等。每种测试方法关注软件功能的某一个方面，以便发现错误或者提升可靠性或性能。模糊测试在这一测试过程上更进一步，尝试向软件程序输入一些“无效”或“随机”的数据。这种输入是故意的，期望得到的结果就是程序崩溃或输出异常，这样就可以暴露程序中的错误以便由开发者来修复它们。与其他测试类似，很少需要手动进行模糊测试，业界有大量的模糊测试工具可以将这个过程自动化。Go 中的软件测试举个例子，假如你想测试 add.go 中的 Add() 函数，你可以在 add_test.go 中导入 testing 包并把测试体写在以 TestXXX() 开头的函数内。考虑如下代码：func Add(num1, num2 int) int { } 在 add_test.go 文件中，你可能有如下测试代码：import "testing" func TestAdd(t *testing.T) { } 运行测试：$ go test 新增对模糊测试的支持Go 团队已经接受了 新增对模糊测试的支持的提议，以进一步推动这项工作。这涉及到新增一个 testing.F 类型，在 _test.go 文件中新增 FuzzXXX() 函数，在 Go 工具中会新增一个 -fuzz 选项来执行这些测试。在 add_test.go 文件中：func FuzzAdd(f *testing.F) { } 执行以下代码：$ go test -fuzz 在本文编写时，这个 功能还是试验性的，但是应该会在 1.18 发布版本中包含。（LCTT 译注：Go 1.18 刚刚发布，已经包含了对模糊测试的支持）目前很多功能如 -keepfuzzing、-race 等也还没有支持。Go 团队最近发布了一篇 模糊测试教程，值得读一下。安装 gotip 来获取最新的功能如果你极度渴望在正式发布之前尝试这些功能，你可以使用 gotip 来测试即将正式发布的 Go 功能并反馈给他们。你可以使用下面的命令来安装 gotip。安装之后，你可以用 gotip 程序代替以前的 go 程序来编译和运行程序。$ go install golang.org/dl/gotip@latest $ gotip download $ gotip version go version devel go1.18-f009910 Thu Jan 6 16:22:21 2022 +0000 linux/amd64 社区对于模糊测试的观点软件社区中经常会讨论模糊测试，不同的人对模糊测试有不同的看法。有些人认为这是一种有用的技术，可以找到错误，尤其是在安全方面。然而考虑到模糊测试所需要的资源（CPU、内存），有人就认为这是一种浪费，而他们更愿意用其他的测试方法。即使在 Go 团队内部，意见也不统一。我们可以看到 Go 的联合创始人 Rob Pike 对模糊测试的使用和在 Go 中的实现是持轻微的怀疑态度的。...虽然模糊测试有助于发现某类错误，但是它会占用大量的 CPU 和存储资源，并且效益成本比率也不明确。我担心为了写模糊测试浪费精力，或者 git 仓库中充斥大量无用的测试数据~Rob Pike然而，Go 安全团队的另一个成员，Filo Sottile，似乎对 Go 新增支持模糊测试很乐观，举了很多例子来支持，也希望模糊测试能成为开发过程中的一部分。我想说模糊测试可以发现极端情况下的错误。这是我们作为安全团队对其感兴趣的原因：在极端情况下发现的错误可以避免在生产环境中成为弱点。我们希望模糊测试能成为开发的一部分 —— 不只是构建或安全方面 —— 而是整个开发过程：它能提升相关代码的质量...~Filo Sottile现实中的模糊测试对我而言，模糊测试在发现错误以及让系统变得更安全和更有弹性方面似乎非常有效。举个例子，Linux 内核也会使用名为 syzkaller 的工具进行模糊测试，这…