Pure Go ile syscall.Syscall ve unsafe.Pointer Kullanarak Doğrudan Linux Sistem Çağrıları

Özet
Go üzerinde doğrudan sistem çağrılarını (direct system call) öğreneceğiz. Go katı derleyici hataları ve sıkı bir çöp toplayıcı (GC) sunduğu için, düşük seviyeli çağrıları Saf Go ile değiştirmek çok daha iyidir. Neyse ki, C fonksiyon çağrılarının çoğu Go'da iyi ve çağdaş bir biçimde yeniden uygulanmıştır. Bir göz atalım.
Sistem Çağrısı
Sistem çağrısı, işletim sistemine yapılan doğrudan bir istektir. Sistem genellikle donanımın üzerinde doğrudan çalıştığı için katı ve eski usul bir biçimde yazıldığından, çağrısının katı ve doğru biçimde bir istek iletmesi gerektiğini göz önünde bulundurmalıyız. Bu nedenle, bazı değişkenlere ihtiyacımız olmasa bile, kullanılsın veya kullanılmasın boyutunu yine de doldurmamız gerekir. Tamamen çalışan bir örneğe bakalım.
Tam Örnek
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}
Bu örnek tüm değişkenleri içermekte ve mevcut sistem bilgisinin kapsamlı bir dökümünü yazdırmaktadır.
Bu kodu bir kilit ve anahtar gibi karşılaştırabiliriz.syscall.SYS_SYSINFO, bir çekirdeğin içindeki kilidi açan bir anahtardır.
Bu nedenle, kilit için doğru anahtarı kullanmak önemlidir.
Bu çağrı için syscall.SYS_GETPID kullanırsak ne olur?
Bu, İşlem Kimliğini (Process ID) içeren bir kilit için bir anahtardır.
Bu, sistem bilgisi alanından bir PID almaya çalışacaktır.
Sonuç olarak, bilgilerin hiçbiri doğru biçimde okunamaz; çağrı başarısız durumla geri dönmelidir.
Şimdi, hangi öğelerin bulunduğunu ve öğelerin nasıl sıralandığını bilmemiz gerekir. Kilidin ilk yuvasında, 2^64 boyutunda Uptime bulunur. Bunu 2^32 ile okumaya çalışırsak, bit dizisi tam olarak okunmamış olur. Düşük seviyeli numaralar yazmadığımız sürece bu tür kısmi ikili verileri kullanamayız.
64 bitlik ikili veriyi okuduktan sonra, sonunda ikinci yuvadayız. Bu yuva, yalnızca önceki 64 bitlik tam sayı okunduktan sonra doğru biçimde okunabilir.
Bu katı ve mantıksal akışları tekrarlayarak, sistemden doğru bilgiyi elde edebilir ve okunan verileri düzgün biçimde işleyebiliriz.
Değişken adlarını nasıl atlayabiliriz
Değişkenlerin kendilerini "atlayamasak" da, kullanılan ve atılan değişkenleri ayırt etmek önemlidir. Programın kullanımı yeterince açıksa, değerleri kalıcı olarak kullanılmasa bile her birini etiketlemek yerine, yer tutucu olarak adsız değişkenler kullanmak daha iyidir. Bunu "Boş Bellek Denetleyicisi" adlı bir örnekle inceleyelim.
Örnek - Boş Bellek Denetleyicisi
Boş Bellek/Takas alanını kontrol ederken, farklı kaynakları gösteren diğer bilgilere ihtiyacımız yoktur. Daha iyi bir görünürlük elde etmek için, belirli alanları tutmak üzere adsız değişkenler oluşturabilirsiniz.
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 // anonim olan ve kullanılmayanlar _ ile işaretlenir
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}
Sonuç olarak, değişkenler etiketsiz okunur. Adsız değerler aslında bir yapıda saklansa da, kodda okunabilir etiketler/işaretler bulunmaz.
Sonuç
- Go'nun
syscallveunsafepaketlerini kullanmak, C/CGo'ya göre hâlâ daha güvenlidir - Kolayca genişletilebilecek büyük bir proje yazıyorsanız:
- Adsız değişkenler kullanmayın; üyeler için her birine ad verin.
- Sınırlı kullanıma sahip bir proje yazıyorsanız:
- Gerçekte kullanılmayan alanları tutmak için adsız değişkenleri kullanabilirsiniz.
- Go'nun
syscallpaketi, düşük seviyeli çağrıları işlemek için güçlü ve modern bir araçtır