Go syscall é um substituto brilhante para I/O de baixo nível
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
syscalleunsafedo 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.
syscalldo Go é poderoso e moderno para lidar com chamadas de baixo nível