GoSuda

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

By Yunjin Lee
views ...

Resumo

Aprenderemos sobre a chamada de sistema direta em Go. Uma vez que Go oferece erros de compilador rigorosos e GC rígido, é muito melhor substituir chamadas de baixo nível em Pure Go. Felizmente, a maioria das chamadas de função C é totalmente reimplementada em Go, de uma maneira boa e contemporânea. Vamos dar uma olhada nisso.

Chamada de Sistema

Chamada de sistema é uma solicitação direta ao sistema operacional. Como o sistema é geralmente escrito em um estilo rígido e antiquado, visto que está sendo executado diretamente em um hardware, precisamos considerar que sua chamada deve entregar uma forma estrita e correta de solicitação. Portanto, 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 extensas sobre a informação atual do sistema. Podemos comparar este código, como 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á quando 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 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 é totalmente lida. Não podemos usar esses tipos de binários parciais, a menos que estejamos indo 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 lemos o inteiro de tamanho de 64 bits anterior.

Ao repetir esses fluxos estritos e lógicos para obter uma informação adequada de um sistema, podemos lidar adequadamente com 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 e as descartadas. Se o uso do programa for claro o suficiente, é melhor usar variáveis sem nome como espaços reservados do que rotular cada valor, mesmo que não sejam usados para sempre. Vamos verificar isso com um exemplo, "Verificador de Memória Livre".

Exemplo - Verificador de Memória Livre

Ao verificar a Memória/Swaps Livres, 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 _ (anônimas e não utilizadas são marcadas como _)
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 do Go ainda é mais seguro do que C/CGo
  • Se você estiver escrevendo um projeto enorme que pode ser expandido facilmente:
    • Não crie variáveis anônimas; crie nomes para cada membro.
  • Se você estiver escrevendo um projeto que tem uso limitado:
    • Você pode usar variáveis anônimas para manter espaços que estão realmente sem uso.
  • syscall do Go é poderoso e moderno para lidar com chamadas de baixo nível

Leitura Adicional

syscall unsafe x/sys/unix