Appels système Linux directs en Go pur avec syscall.Syscall et unsafe.Pointer

Résumé
Nous allons apprendre à utiliser les appels système directs en Go. Étant donné que Go offre des erreurs de compilation strictes et un ramasse-miettes (GC) rigide, il est bien préférable de remplacer les appels de bas niveau par du Pure Go. Heureusement, la plupart des appels de fonctions C sont entièrement réimplémentés en Go, de manière élégante et moderne. Examinons cela.
Appel système
Un appel système est une requête directe adressée au système d'exploitation. Le système étant généralement écrit dans un style rigide et ancien, puisqu'il fonctionne directement sur le matériel, nous devons veiller à ce que son appel transmette une forme de requête stricte et correcte. Ainsi, même si nous n'avons pas besoin de certaines variables, nous devons tout de même en spécifier la taille, indépendamment de leur utilisation. Voyons un exemple entièrement fonctionnel.
Exemple complet
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}
Cet exemple inclut toutes les variables et affiche de nombreuses informations détaillées sur les informations système actuelles.
On peut comparer ce code à une serrure et à une clé.syscall.SYS_SYSINFO est une clé qui ouvre une serrure située à l'intérieur du noyau.
Par conséquent, il est important d'utiliser la bonne clé pour la serrure.
Que se passe-t-il si nous utilisons syscall.SYS_GETPID pour cet appel ?
Il s'agit d'une clé pour une serrure contenant l'identifiant de processus (PID).
Cela tentera d'obtenir un PID à partir d'un espace réservé aux informations système.
En conséquence, aucune information ne peut être lue correctement ; l'appel doit donc être signalé comme ayant échoué.
Maintenant, nous devons savoir quels éléments sont contenus et comment ils sont ordonnés. Dans le premier emplacement de la serrure, nous trouvons Uptime, d'une taille de 2^64. Si nous tentons de le lire avec 2^32, la séquence de bits n'est pas entièrement lue. Nous ne pouvons pas utiliser ce type de fragments binaires, sauf si nous sommes prêts à recourir à des astuces de bas niveau.
Après la lecture de 64 bits de données binaires, nous atteignons enfin le deuxième emplacement. Il ne peut être lu correctement qu'après lecture de l'entier de 64 bits précédent.
En répétant ces flux stricts et logiques, nous pouvons obtenir les informations appropriées du système et traiter correctement les données lues.
Comment omettre les « noms de variables »
Bien qu'il ne soit pas possible de « omettre » les variables elles-mêmes, il est important de distinguer les variables utilisées de celles qui sont écartées. Si l'usage du programme est suffisamment clair, il est préférable d'utiliser des variables sans nom comme espaces réservés plutôt que d'étiqueter chaque valeur, même si elles ne sont pas utilisées de façon permanente. Vérifions cela avec un exemple, le « Vérificateur de mémoire libre ».
Exemple : Vérificateur de mémoire libre
Lors de la vérification de la mémoire libre et de l'espace d'échange (swap), nous n'avons pas besoin des autres informations qui indiquent d'autres ressources. Pour une meilleure lisibilité, vous pouvez créer des variables anonymes pour réserver des emplacements spécifiques.
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 // anonyme, et les éléments inutilisés sont marqués _
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}
Par conséquent, les variables sont lues sans étiquettes. Bien que les valeurs anonymes soient effectivement stockées dans une structure, aucune étiquette ni marque lisible n'apparaît dans le code.
Conclusion
- Utiliser
syscalletunsafede Go reste plus sûr que C/CGo - Si vous écrivez un projet important et facilement extensible :
- Ne créez pas de variables anonymes ; donnez un nom à chaque membre.
- Si vous écrivez un projet à usage limité :
- Vous pouvez utiliser des variables anonymes pour réserver les emplacements réellement inutilisés.
- Le package
syscallde Go est puissant et moderne pour gérer les appels de bas niveau