GoSuda

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

By Yunjin Lee views ...

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 syscall et unsafe de 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 syscall de Go est puissant et moderne pour gérer les appels de bas niveau

Pour aller plus loin

syscall unsafe x/sys/unix