GoSuda

Go syscall é um substituto brilhante para I/O de baixo nível

By Yunjin Lee
views ...

Sumário

Aprenderemos sobre chamadas diretas de sistema em Go. Uma vez que Go oferece erros de compilador rigorosos e um GC rígido, é muito melhor substituir chamadas de baixo nível em Pure Go. Felizmente, a maioria das chamadas de função em C é totalmente reimplementada em Go, de maneira eficiente e contemporânea. Vamos analisar isso.

Chamada de Sistema

Uma chamada de sistema é uma solicitação direta ao sistema operacional. Como o sistema é geralmente escrito em um estilo rígido e antiquado, pois está sendo executado diretamente em hardware, precisamos considerar que sua chamada deve entregar uma forma de solicitação estrita e correta. Assim, mesmo que não precisemos de algumas variáveis, ainda precisamos preencher o tamanho, independentemente do uso. Vamos verificar com um exemplo totalmente funcional.

Exemplo Completo

 1package main
 2import (
 3	"fmt"
 4	"syscall"
 5	"unsafe"
 6)
 7
 8type sysinfo_t struct {
 9	Uptime    int64
10	Loads     [3]uint64
11	Totalram  uint64
12	Freeram   uint64
13	Sharedram uint64
14	Bufferram uint64
15	Totalswap uint64
16	Freeswap  uint64
17	Procs     uint16
18	Pad       uint16
19	_         [4]byte
20	Totalhigh uint64
21	Freehigh  uint64
22	MemUnit   uint32
23	_         [4]byte
24}
25
26func main() {
27	var info sysinfo_t
28	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
29	if errno != 0 {
30		fmt.Println("sysinfo syscall failed:", errno)
31		return
32	}
33
34	scale := float64(1 << 16)
35	fmt.Printf("Uptime: %d seconds\n", info.Uptime)
36	fmt.Printf("Load Average: %.2f %.2f %.2f\n",
37		float64(info.Loads[0])/scale,
38		float64(info.Loads[1])/scale,
39		float64(info.Loads[2])/scale)
40	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
41		info.Totalram*uint64(info.MemUnit)/1024/1024,
42		info.Freeram*uint64(info.MemUnit)/1024/1024,
43		info.Bufferram*uint64(info.MemUnit)/1024/1024)
44	fmt.Printf("Swap: total=%d MB free=%d MB\n",
45		info.Totalswap*uint64(info.MemUnit)/1024/1024,
46		info.Freeswap*uint64(info.MemUnit)/1024/1024)
47	fmt.Printf("Processes: %d\n", info.Procs)
48}

Este exemplo inclui todas as variáveis e imprime informações extensivas do sistema atual. Podemos comparar este código a um armário e uma chave.syscall.SYS_SYSINFO é uma chave que destranca um armário que está dentro de um kernel. Portanto, usar a chave correta para um armário é importante. O que acontecerá se usarmos syscall.SYS_GETPID para esta chamada? Esta é uma chave para um armário que contém o Process ID. Isso tentará obter um PID de um espaço para informações do sistema. Como resultado, nenhuma das informações pode ser lida corretamente; a chamada deve ser retornada como um estado de falha.

Agora, precisamos saber quais itens estão contidos e como os itens estão ordenados. No primeiro slot de um armário, temos Uptime, com um tamanho de 2^64. Se tentarmos ler isso com 2^32, a sequência de bits não será lida completamente. Não podemos usar esses tipos de binários parciais, a menos que estejamos prestes a escrever truques de baixo nível.

Após ler 64 bits de dados binários, finalmente estamos no segundo slot. Ele só pode ser lido com precisão quando tivermos lido o inteiro anterior de 64 bits.

Ao repetir esses fluxos rigorosos e lógicos para obter informações adequadas de um sistema, podemos manipular corretamente os dados lidos.

Como ignorar 'nomes de variáveis'

Embora não possamos 'ignorar' as variáveis em si, é importante distinguir as variáveis usadas das descartadas. Se o uso do programa for suficientemente claro, é melhor usar variáveis sem nome como placeholders do que rotular cada valor, mesmo que nunca sejam usados. Vamos verificar isso com um exemplo, "Free Memory Checker".

Exemplo - Verificador de Memória Livre

Ao verificar a Memória Livre/Swaps, não precisamos de outras informações que indiquem recursos diferentes. Para alcançar uma melhor visibilidade, você pode criar variáveis anônimas para manter espaços específicos.

 1package main
 2
 3import (
 4	"fmt"
 5	"syscall"
 6	"unsafe"
 7)
 8
 9type sysinfo_t struct {
10	_          int64
11	_         [3]uint64
12	Totalram  uint64
13	Freeram   uint64
14	Sharedram uint64
15	Bufferram uint64
16	Totalswap uint64
17	Freeswap  uint64
18	_         uint16  // anonymous, and unused ones are marked as _
19	_         uint16  
20	_         [4]byte 
21	_         uint64  
22	_         uint64  
23	MemUnit   uint32
24	_         [4]byte
25}
26
27func main() {
28	var info sysinfo_t
29	_, _, errno := syscall.Syscall(syscall.SYS_SYSINFO, uintptr(unsafe.Pointer(&info)), 0, 0)
30	if errno != 0 {
31		fmt.Println("sysinfo syscall failed:", errno)
32		return
33	}
34
35	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n",
36		info.Totalram*uint64(info.MemUnit)/1024/1024,
37		info.Freeram*uint64(info.MemUnit)/1024/1024,
38		info.Bufferram*uint64(info.MemUnit)/1024/1024)
39	fmt.Printf("Swap: total=%d MB free=%d MB\n",
40		info.Totalswap*uint64(info.MemUnit)/1024/1024,
41		info.Freeswap*uint64(info.MemUnit)/1024/1024)
42}

Consequentemente, as variáveis são lidas sem rótulos. Embora os valores anônimos sejam realmente armazenados em uma estrutura, não há rótulos/marcas legíveis no código.

Conclusão

  • Usar syscall e unsafe de Go ainda é mais seguro do que C/CGo.
  • Se você está escrevendo um projeto enorme que pode ser expandido facilmente:
    • Não crie variáveis anônimas; crie nomes para os membros.
  • Se você está escrevendo um projeto com uso limitado:
    • Você pode usar variáveis anônimas para manter espaços que estão realmente sem uso.
  • syscall de Go é poderoso e moderno para lidar com chamadas de baixo nível.

Leitura Adicional

syscall unsafe x/sys/unix