GoSuda

El syscall de Go constituye un reemplazo brillante de la I/O de bajo nivel

By Yunjin Lee
views ...

Resumen

Aprenderemos sobre la llamada directa a system call en Go. Dado que Go ofrece errores estrictos del compilador y una GC rígida, 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 de solicitud estricta y correcta. Por lo tanto, incluso si no necesitamos algunas variables, aún debemos completar el tamaño independientemente de su uso. Verifiquemos con un ejemplo completamente funcional.

Ejemplo 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) // El system call sysinfo falló:
31		return
32	}
33
34	scale := float64(1 << 16)
35	fmt.Printf("Uptime: %d seconds\n", info.Uptime) // Tiempo de actividad: %d segundos
36	fmt.Printf("Load Average: %.2f %.2f %.2f\n", // Carga promedio: %.2f %.2f %.2f
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", // Memoria: total=%d MB libre=%d MB buffer=%d MB
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", // Swap: total=%d MB libre=%d MB
45		info.Totalswap*uint64(info.MemUnit)/1024/1024,
46		info.Freeswap*uint64(info.MemUnit)/1024/1024)
47	fmt.Printf("Processes: %d\n", info.Procs) // Procesos: %d
48}

Este ejemplo incluye todas las variables e imprime información extensa del sistema actual. Podemos comparar este código como un casillero y una llave.syscall.SYS_SYSINFO es una llave que abre un casillero que está dentro de un kernel. Por lo tanto, usar la llave correcta para un casillero es importante. ¿Qué pasará cuando usemos syscall.SYS_GETPID para esta llamada? Esta es una llave para un casillero que contiene el Process ID. Esto intentará obtener un PID de un espacio para información del sistema. Como resultado, ninguna de la información puede leerse correctamente; la llamada debe ser devuelta en un estado de fallo.

Ahora, necesitamos saber qué elementos están contenidos y cómo están ordenados. En el primer espacio de un casillero, tenemos Uptime, con un tamaño de 2^64. Si intentamos leer esto con 2^32, la secuencia de bit 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.

Repitiendo esos flujos estrictos y lógicos para obtener información adecuada de un sistema, podemos manejar correctamente los datos leídos.

Cómo omitir 'nombres de variables'

Aunque no podemos 'omitir' las variables en sí mismas, es importante distinguir las variables utilizadas y 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 nunca se usan. Verifiquemos esto con un ejemplo, "Comprobador de Memoria Libre" (Free Memory Checker).

Ejemplo - Free Memory Checker

Al verificar la Memoria/Swaps Libre, no necesitamos otra información que esté indicando 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 están 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) // El system call sysinfo falló:
32		return
33	}
34
35	fmt.Printf("Memory: total=%d MB free=%d MB buffer=%d MB\n", // Memoria: total=%d MB libre=%d MB buffer=%d MB
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", // Swap: total=%d MB libre=%d MB
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.

Conclusión

  • Usar syscall y unsafe de 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; cree nombres para cada uno de los miembros.
  • Si está escribiendo un proyecto que tiene un uso limitado:
    • Puede usar variables anónimas para mantener espacios que en realidad no se utilizan.
  • El syscall de Go es potente y moderno para manejar llamadas de bajo nivel

Lectura Adicional

syscall unsafe x/sys/unix