Go syscall es un brillante reemplazo de la I/O de bajo nivel
Summary
Aprenderemos sobre la llamada directa al sistema en Go. Dado que Go ofrece errores de compilador estrictos y un GC rígido, es mucho mejor reemplazar las llamadas de bajo nivel en Pure Go. Afortunadamente, la mayoría de las llamadas a funciones de C están completamente reimplementadas en Go, de una manera buena y contemporánea. Echémosle un vistazo.
System Call
Una System call es una solicitud directa al sistema operativo. Dado que el sistema generalmente está escrito en un estilo rígido y anticuado, ya que se ejecuta directamente en un hardware, debemos considerar que su llamada debe entregar una forma estricta y correcta de una solicitud. Por lo tanto, incluso si no necesitamos algunas variables, aún necesitamos completar el tamaño independientemente de su uso. Verifiquemos esto con un ejemplo completamente funcional.
Full Example
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 ejemplo incluye todas las variables e imprime información exhaustiva del sistema actual.
Podemos comparar este código con una cerradura y una llave.syscall.SYS_SYSINFO es una llave que abre una cerradura que está dentro de un kernel.
Por lo tanto, es importante utilizar la llave correcta para una cerradura.
¿Qué sucederá si usamos syscall.SYS_GETPID para esta llamada?
Esta es una llave para una cerradura que contiene el ID de Proceso (Process ID).
Esto intentará obtener un PID del espacio para la información del sistema.
Como resultado, ninguna de la información puede leerse correctamente; la llamada debe ser devuelta como estado fallido.
Ahora, necesitamos saber qué elementos están contenidos y cómo están ordenados. En el primer espacio de una cerradura, tenemos Uptime, con un tamaño de 2^64. Si intentamos leer esto con 2^32, la secuencia de bits no se lee completamente. No podemos usar este tipo de binarios parciales a menos que vayamos a escribir trucos de bajo nivel.
Después de leer 64 bits de datos binarios, finalmente estamos en el segundo espacio. Solo se puede leer con precisión cuando hemos leído el entero anterior de 64 bits de tamaño.
Al repetir esos flujos estrictos y lógicos para obtener información adecuada de un sistema, podemos manejar correctamente los datos leídos.
How to skip 'variable names'
Aunque no podemos 'saltarnos' las variables en sí mismas, es importante distinguir las variables utilizadas de las descartadas. Si el uso del programa es lo suficientemente claro, es mejor usar variables sin nombre como marcadores de posición que etiquetar cada valor, incluso si no se usan nunca. Verifiquemos esto con un ejemplo, "Free Memory Checker" (Verificador de Memoria Libre).
Example - Free Memory Checker
Al verificar la Memoria/Swaps Libres, no necesitamos otra información que indique diferentes recursos. Para lograr una mejor visibilidad, puede crear variables anónimas para mantener espacios 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 _ (las anónimas y no utilizadas se marcan 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}
Consecuentemente, las variables se leen sin etiquetas. Aunque los valores anónimos se almacenan realmente en una estructura, no hay etiquetas/marcas legibles en el código.
Conclusion
- Usar
syscallyunsafede Go sigue siendo más seguro que C/CGo - Si está escribiendo un proyecto enorme que se puede expandir fácilmente:
- No cree variables anónimas; asigne nombres a cada miembro.
- Si está escribiendo un proyecto que tiene un uso limitado:
- Puede usar variables anónimas para mantener espacios que realmente no se utilizan.
- El
syscallde Go es potente y moderno para manejar llamadas de bajo nivel