İşletim Sistemi 101 – #3 (System Call)

Bilgisayarları kullanırken yaptığımız en basit işler bile, arkaplanda oldukça karmaşık süreçleri tetikleyebiliyor. En azından benim için karmaşık.

Çocukken Buca Şirinyer pazarından satın aldığımız, Atari dediğimiz ama aslında Nintendo oyunlarını çalıştıran, merdiven altı oyun konsolları mesela. Televizyona bağlıyorum. UHF kanalları tarıyorum. 99’a kaydediyorum bulduğum oyun yayınını. AV-1 AV-2 falan geziyorum. Joystick’ten bir düğmeye basıyorum, televizyondaki şey hareket ediyor. Büyük olay değil mi?

Peki işletim sistemleri, biz günlük işlerimizi yaparken, donanımlarla nasıl haberleşiyor? Sistem çağrıları (system call) sayesinde.

Uyarı: Sistem çağrılarını anlamlandırmaya çalıştığım kısımlara çok güvenmeyin. Lütfen ilgili sistem çağrısının dokümantasyonunu okuyun ve kesin bilgiye oradan ulaşın. Sistem çağrılarının listelerini “Bağlantılar” kısmına ekledim.

System Call (Syscall) Nedir?

System call, bir bilgisayar programının, üzerinde çalıştığı işletim sisteminin çekirdeğinden hizmet talep etmesidir.^1 Eee, yani?

Şöyle ki. Bir metin editörü açın ve içine bir şeyler yazın. Sonrasında kaydetmeyi deneyin. Çalıştırdığınız program, HDD ya da SSD üzerinden bir yer talep eder. Sonrasında ona atanan alana verileri yazabilir. Ya da web tarayıcınızı açın. Bir web sayfasına girin. Bu iki adımda da işletim sisteminden taleplerde bulunuyorsunuz. Programın çalıştırılması, ağ üzerinden bir verinin çekilmesi gibi. Sonrasında yeni bir sekme açın. Web tarayıcınız sizin için yeni bir process açılmasını talep eder. Çünkü bu iki sekme, birbirinden farklı olacaktır.

Daha da basitleştirelim. Bir dosyaya çift tıkladınız ve açtınız. Bu süreçte kullanılan sistem çağrılarından biri “open” çağrısıdır.^2

Biraz Örnekleyelim

Bilgisayarımda “yazı” adında bir dosya oluşturmak istiyorum. Bu dosyanın içine de “ali” yazmak istiyorum.

GNU/Linux ortamında “bash” kabuğuna yazmam gereken komut şu olacaktır:

echo ali > yazı

Bu komut, çalıştırıldığı dizinde “yazı” isminde bir dosya yoksa oluşturacak ve içine “ali” yazacak. Eğer “yazı” isminde bir dosya varsa, içerdiği her şeyi silecek ve sadece “ali” yazacak. Tabii dosyanın yeni hâli kaydedilecek. Oldukça kolay bir iş gibi görünüyor. İşin system call tarafına bakmak istediğimiz için “strace” isimli bir programdan yardım alacağız.

General Commands Manual
NAME
     strace - trace system calls and signals
DESCRIPTION
     In the simplest case strace runs the specified command until it exits. It intercepts and records the system calls which are called by a process and the signals which are received by a process. The name of each system call, its arguments and its return value are printed on standard error or to the file specified with the -o option.

Şimdi şu komutu çalıştıralım ve bize neler döndürdüğüne bakalım. Çıktıyı daha rahat inceleyebilmemiz için başına satır numaraları ekledim. Çıktının orijinalinde görünmüyorlar:

strace echo ali > yazı
0. execve("/usr/bin/echo", ["echo", "ali"], 0x7fff3b44ea18 /* 24 vars */) = 0
1. brk(NULL) = 0x1c77000
2. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6c3000
3. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
4. open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
5. fstat(3, {st_mode=S_IFREG|0644, st_size=18708, …}) = 0
6. mmap(NULL, 18708, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa2cf6be000
7. close(3) = 0
8. open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
9. read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"…, 832) = 832
10. fstat(3, {st_mode=S_IFREG|0755, st_size=2156240, …}) = 0
11. mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7fa2cf0d5000
12. mprotect(0x7fa2cf298000, 2097152, PROT_NONE) = 0
13. mmap(0x7fa2cf498000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7fa2cf498000
14. mmap(0x7fa2cf49e000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf49e000
15. close(3) = 0
16. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6bd000
17. mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6bb000
18. arch_prctl(ARCH_SET_FS, 0x7fa2cf6bb740) = 0
19. mprotect(0x7fa2cf498000, 16384, PROT_READ) = 0
20. mprotect(0x606000, 4096, PROT_READ) = 0
21. mprotect(0x7fa2cf6c4000, 4096, PROT_READ) = 0
22. munmap(0x7fa2cf6be000, 18708) = 0
23. brk(NULL) = 0x1c77000
24. brk(0x1c98000) = 0x1c98000
25. brk(NULL) = 0x1c98000
26. open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
27. fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, …}) = 0
28. mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7fa2c8b93000
29. close(3) = 0
30. fstat(1, {st_mode=S_IFREG|0644, st_size=0, …}) = 0
31. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7fa2cf6c2000
32. write(1, "ali\n", 4) = 4
33. close(1) = 0
34. munmap(0x7fa2cf6c2000, 4096) = 0
35. close(2) = 0
36. exit_group(0) = ?
37. +++ exited with 0 +++

Emin olun burada olan biten şeyleri anlayabilmeyi çok isterdim 🙂 Biraz kafa yoralım, en azından 3-5 satırını anlamayı deneyelim:

0. satırda, “/usr/bin/echo” adlı programın çalıştırıldığını görüyoruz. “execve” adlı syscall’ın görevi bu, program çalıştırmak. Köşeli parantezler içerisinde yer alan “echo” ve “ali” kelimelerine dikkat edin. execve, “echo” isimli programı “ali” parametresi ile çalıştıracak gibi görünüyor.

3. satırda ise “access” syscall’ını görüyoruz. Bu çağrının amacı, talebi yapan process’in bu dosyaya erişim durumunu kontrol etmektir. Process’in bu dosyaya erişmeye izni olmayabilir. Başka bir deyişle; A kullanıcısı kendisine ait bir dosyanın yalnızca kendisi tarafından açılmasını istemiştir. Buna rağmen B kullanıcısı bu dosyayı açmak istemiştir. Burada access devreye girer ve böyle bir yetkinin olup olmadığını kontrol eder. Sonucunu da talebi yapan programa döndürür. Siz de “Bu bıdı bıdıyı yapmak için yönetici yetkilerine sahip olmalısınız.” gibi uyarılar alabilirsiniz. Nasıl ama? Ya da bir programda önceden açılan dosyalardan birini sildiniz. Sonra “Recent Files” gibi bir menüden tekrar aynı dosyayı açmayı denediniz. Fakat dosya yok, bulunamadı gibi uyarılar aldınız. İşte, kullandığınız programın yaptığı access çağrısı (hatta belki de open çağrısı yaptı ve open çağrısı da access’i çağırdı), olumsuz sonuçlandı. Bu satırda da access çağrısı olumsuz sonuçlanmış. Türkçe bir hata görüyoruz hatta (OS dilinden kaynaklı): “(Böyle bir dosya ya da dizin yok)”. Fakat neden o dosyaya çağrı yapılmış, bilemiyorum Altan.

4. satırdan 32. satıra kadar olan biten hakkında hiçbir fikrim yok. “open” ve “close” syscall’larını görüyoruz. “mmap” ile bir şeylerin RAM’e gönderildiğini düşünebiliriz. “fstat” ile dosyanın özellikleri inceleniyor vs. Buraya kadar olan kısımda muhtemelen dosyamız, gerekli izin düzenlemeleri ile birlikte (kullanıcı, grup ve diğer herkes için okuma, yazma, çalıştırma izinleri) oluşturuldu.

32. satırdaki “write” syscall’ına bakalım. Birtakım parametreler almış, ne olduklarını bilmiyorum. Fakat içinde “ali” yazıyor. Bu syscall sayesinde oluşturulan dosyanın içine “ali” yazıldı.

33. – 35. satırlarda yazma işlemini sonlandırmak için dosya kapatılmış ve belleğe map edilen kısımlar unmap edilmiş (“munmap” syscall’ı) gibi görünüyor.

37. satır ise konuyu tamamen kapatıyor. “0 ile çıkıldı”. Yani herhangi bir hata ile karşılaşılmadı.

Hadi başka bir örnek daha inceleyelim. Yetkimiz olmayan bir dosyayı okumak istediğimizde neler oluyor:

strace cat /etc/shadow
0. execve("/usr/bin/cat", ["cat", "/etc/shadow"], 0x7ffd49c00dc8 /* 25 vars */) = 0
1. brk(NULL) = 0x10b1000
2. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6d2000
3. access("/etc/ld.so.preload", R_OK) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
4. open("/etc/ld.so.cache", O_RDONLY|O_CLOEXEC) = 3
5. fstat(3, {st_mode=S_IFREG|0644, st_size=18708, …}) = 0
6. mmap(NULL, 18708, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f979f6cd000
7. close(3) = 0
8. open("/lib64/libc.so.6", O_RDONLY|O_CLOEXEC) = 3
9. read(3, "\177ELF\2\1\1\3\0\0\0\0\0\0\0\0\3\0>\0\1\0\0\0`&\2\0\0\0\0\0"…, 832) = 832
10. fstat(3, {st_mode=S_IFREG|0755, st_size=2156240, …}) = 0
11. mmap(NULL, 3985920, PROT_READ|PROT_EXEC, MAP_PRIVATE|MAP_DENYWRITE, 3, 0) = 0x7f979f0e4000
12. mprotect(0x7f979f2a7000, 2097152, PROT_NONE) = 0
13. mmap(0x7f979f4a7000, 24576, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_DENYWRITE, 3, 0x1c3000) = 0x7f979f4a7000
14. mmap(0x7f979f4ad000, 16896, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x7f979f4ad000
15. close(3) = 0
16. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6cc000
17. mmap(NULL, 8192, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6ca000
18. arch_prctl(ARCH_SET_FS, 0x7f979f6ca740) = 0
19. mprotect(0x7f979f4a7000, 16384, PROT_READ) = 0
20. mprotect(0x60b000, 4096, PROT_READ) = 0
21. mprotect(0x7f979f6d3000, 4096, PROT_READ) = 0
22. munmap(0x7f979f6cd000, 18708) = 0
23. brk(NULL) = 0x10b1000
24. brk(0x10d2000) = 0x10d2000
25. brk(NULL) = 0x10d2000
26. open("/usr/lib/locale/locale-archive", O_RDONLY|O_CLOEXEC) = 3
27. fstat(3, {st_mode=S_IFREG|0644, st_size=106172832, …}) = 0
28. mmap(NULL, 106172832, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f9798ba2000
29. close(3) = 0
30. fstat(1, {st_mode=S_IFCHR|0620, st_rdev=makedev(136, 0), …}) = 0
31. open("/etc/shadow", O_RDONLY) = -1 EACCES (Erişim engellendi)
32. write(2, "cat: ", 5cat: ) = 5
33. write(2, "/etc/shadow", 11/etc/shadow) = 11
34. open("/usr/share/locale/locale.alias", O_RDONLY|O_CLOEXEC) = 3
35. fstat(3, {st_mode=S_IFREG|0644, st_size=2502, …}) = 0
36. mmap(NULL, 4096, PROT_READ|PROT_WRITE, MAP_PRIVATE|MAP_ANONYMOUS, -1, 0) = 0x7f979f6d1000
37. read(3, "# Locale name alias data base.\n#"…, 4096) = 2502
38. read(3, "", 4096) = 0
39. close(3) = 0
40. munmap(0x7f979f6d1000, 4096) = 0
41. open("/usr/share/locale/tr_TR.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
42. open("/usr/share/locale/tr_TR.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
43. open("/usr/share/locale/tr_TR/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
44. open("/usr/share/locale/tr.UTF-8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
45. open("/usr/share/locale/tr.utf8/LC_MESSAGES/libc.mo", O_RDONLY) = -1 ENOENT (Böyle bir dosya ya da dizin yok)
46. open("/usr/share/locale/tr/LC_MESSAGES/libc.mo", O_RDONLY) = 3
47. fstat(3, {st_mode=S_IFREG|0644, st_size=132344, …}) = 0
48. mmap(NULL, 132344, PROT_READ, MAP_PRIVATE, 3, 0) = 0x7f979f6a9000
49. close(3) = 0
50. open("/usr/lib64/gconv/gconv-modules.cache", O_RDONLY) = 3
51. fstat(3, {st_mode=S_IFREG|0644, st_size=26970, …}) = 0
52. mmap(NULL, 26970, PROT_READ, MAP_SHARED, 3, 0) = 0x7f979f6a2000
53. close(3) = 0
54. write(2, ": Eri\305\237im engellendi", 20: Erişim engellendi) = 20
55. write(2, "\n", 1
56. ) = 1
57. close(1) = 0
58. close(2) = 0
59. exit_group(1) = ?
60. +++ exited with 1 +++

31. satırda, /etc/shadow dosyası için open syscall’ını görüyoruz. Ve “Erişim engellendi” uyarısı bizi karşılıyor. 54. satırda ise “Erişim engellendi” yazısının ekranımıza yazılması için write syscall’ı çağrılmış. 60. satır ise “1 ile çıkıldı” diyor. Bu iş başarıyla tamamlanamadı.

Buraya kadar olan kısımda birçok Linux system call ismi geçti. Şuraya bırakayım.^3

System Call’ı Anlamaya Çalışalım

System call, genellikle C ya da C++ gibi düşük seviyeli (bu bir hakaret değil)^4 dillerle yazılır. Bazı çağrıların yazılması için assembly kullanımı da gerekebilir. Yani sistem çağrıları da aslında birer kod parçası. Linux’da 300’ün üzerinde syscall bulunuyor.^3 Bu sayı, Windows için 700’lere çıkıyor.^5 Buradan da anlaşılacağı üzere, sistem çağrıları işletim sistemlerine göre farklılıklar gösterebilir.

Peki böyle bir dünyada biz nasıl program yazacağız? Ekrana “Merhaba Dünya!” yazmak için bile binbir takla atmamız gerekmiyor mu? Hayır. Neden? API’ler (Application Programming Interface) sayesinde.^6

Örneğin, siz program yazarken “print (‘Hello World!’)” ifadesini kullanıyorsunuz. Arkaplanda bu komutlarınız, derleyici ya da yorumlayıcı tarafından, sistem çağrılarına dönüştürülüyor ve siz tek satır kodla ekrana bir yazı yazabiliyorsunuz. Detaylarla boğulmuyorsunuz. Derlenen dillere bir örnek olarak C# dilini ele alalım. Daha önce ilgilendiyseniz, derleme sırasında size birtakım sorular gelir: “Hangi işlemci mimarisi?, Hangi işletim sistemi?” gibi. Neden dersiniz? Derleyiciniz yazdığınız koda karşılık gelen sistem çağrılarını bu seçimlerinize göre belirliyor. Yorumlanan dillerden ise Python dilini ele alalım. GNU/Linux ortamında yazdığınız bir Python kodu, Windows ortamında da çalışır (Nispeten basit işler yapan kodlardan bahsediyorum. Yazılımcı değilim.). Bu nasıl mümkün olabiliyor? Gayet basit. Kodu siz derlemiyorsunuz. Python yorumlayıcısı yorumluyor. Peki aynı Python yorumlayıcısı, farklı işletim sistemlerinde çalışıyor mu? Hayır. Python indirme sayfasına gittiğinizde, hangi işletim sistemi için indirmek istediğinizi size sorduğunu göreceksiniz. Java için de benzer durum söz konusu. Java’nın klasikleşmiş “bir kere yaz, her yerde çalıştır” mottosu; çok da göründüğü gibi değil. Java kodlarınız bir Java Sanal Makinesi (JVM) üzerinde çalışıyor. Ve eğer bilgisayarınızda Java yüklü değilse, Java ile yazılmış bir programı çalıştıramıyorsunuz. Tabii ki Java’yı yüklerken de hangi işletim sistemini kullanıyorsanız ona göre yüklüyorsunuz. Dolayısıyla aynı Java kodunu GNU/Linux’a kurduğunuz JVM ile Windows’a kurduğunuz JVM farklı çalıştırıyor. Yani farklı sistem çağrılarına yönlendiriyor. Fakat aynı sonucu üretiyor.

Daha kolay bir örnek verelim. FIFA 2020 oyunu Windows için var, Xbox için var, PlayStation için var. Aynı oyun değil mi? Değil işte, farklı işletim sistemleri için hazırlandılar. Dolayısıyla kullandıkları sistem çağrıları değişiyor. Bu nedenle PlayStation için satın aldığınız FIFA’yı, Xbox’ınızda kullanamıyorsunuz. (Tek sebebi bu değil tabii ki. İşin içine dosya sistemlerinden tutun da ticari kaygılara kadar birçok etmen giriyor.)

Sonuç Olarak

Bilgisayarınızı kullanırken yaptığınız her iş, bir system call’a bağlı. Klavyenizden bir tuşa basmak ve ekranda karşılığını görmek, farenizi hareket ettirmek, Internet’ten dosya indirmek, oyun oynamak, müzik dinlemek… Eğer bir program çalışabiliyorsa, bunu sistem çağrıları sayesinde yapabilmektedir. Yazılım geliştiriciler, API’ler sayesinde bu sistem çağrılarını bilmek zorunda kalmazlar. Ayrıca derleyiciler ve yorumlayıcılar sayesinde de hangi ortamda hangi sistem çağrılarının kullanılacağı konusuyla da ilgilenmek zorunda değillerdir.

Bağlantılar

1 – System call, https://en.wikipedia.org/wiki/System_call

2 – open (system call), https://en.wikipedia.org/wiki/Open_(system_call)

3 – syscalls(2) – Linux manual page, https://man7.org/linux/man-pages/man2/syscalls.2.html

4 – Low-level programming language, https://en.wikipedia.org/wiki/Low-level_programming_language

5 – Windows WIN32K.SYS System Call Table (NT/2000/XP/2003/Vista/2008/7/8/10), https://j00ru.vexillium.org/syscalls/win32k/32/

6 – API, https://en.wikipedia.org/wiki/API

Referans

1 – Operating System Concepts Essentials, Abraham SILBERSCHATZ (Yale University), Peter Baer GALVIN (Corporate Technologies, Inc.), Greg GAGNE (Westminster College)

“İşletim Sistemi 101 – #3 (System Call)” için bir yorum

Bir cevap yazın

E-posta hesabınız yayımlanmayacak. Gerekli alanlar * ile işaretlenmişlerdir