A syscall Go é uma substituição brilhante para o I/O de baixo nível
Resumo
Aprenderemos sobre chamadas diretas de sistema 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 são totalmente reimplementadas em Go, de uma maneira boa e contemporânea. Vejamos isto.
Chamada de Sistema
Chamada de sistema é uma requisição direta ao sistema operacional. Como o sistema é geralmente escrito em um estilo rígido e antiquado, pois está sendo executado diretamente no hardware, precisamos considerar que sua chamada deve entregar uma forma estrita e correta de requisiçã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 o 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 o 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 ID do Processo.
Isto tentará obter um PID de um espaço destinado à informação 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 espaço 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 estes tipos de binários parciais, a menos que estejamos prestes a escrever truques de baixo nível.
Após a leitura de 64 bits de dados binários, finalmente estamos no segundo espaço. Ele só pode ser lido com precisão se tivermos lido o inteiro anterior de 64 bits.
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 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, "Verificador de Memória Livre"
Exemplo - Verificador de Memória Livre
Ao verificar 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 // variáveis 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
syscalleunsafede Go ainda é mais seguro do que C/CGo - Se você está escrevendo um projeto grande que pode ser expandido facilmente:
- Não crie variáveis anônimas; crie nomes para cada um dos membros.
- Se você está escrevendo um projeto com uso limitado:
- Você pode usar variáveis anônimas para manter espaços que são realmente não utilizados.
- O
syscallde Go é poderoso e moderno para lidar com chamadas de baixo nível