GoSuda

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

By Yunjin Lee views ...

Ö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 syscall ve unsafe paketlerini 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 syscall paketi, düşük seviyeli çağrıları işlemek için güçlü ve modern bir araçtır

Daha Fazla Okuma

syscall unsafe x/sys/unix