Process Kavramı
An operating system is only as good as the applications that run on it.
Anonim ?
İşletim sistemleri eğer üzerlerinde herhangi bir program çalıştırmazlarsa pek de kullanışlı olmuyorlar. Sonuçta temel var oluş amaçları zaten kullanıcıların problemlerini çözen programların sorunsuzca çalışmasını ve bu programları geliştiren programcılara çeşitli kolalıklar sağlamak. O yüzden üzerinde bir şey program çalıştırmayan bir işletim sistemi pek kullanışlı olmayacaktır. Bilgisayarınızı açıp, giriş yaptıktan sonra sadece ekrana baktığınızı düşünün, herhalde pek keyif almazdınız.
İşletim sistemi üzerinde çalışan programlara process denilmektedir. Türkçe karşılığı olarak proses (eh…) ya da süreç kelimelerini kullanabiliriz. Bilgisayarımızda programlar çalıştırılabilir dosyalar olarak ikincil hafızada yani diskimizde dururlar, birinci hafıza bellek yani RAM’dir. Biz bir programı çift tıklayıp ya da terminalden adını yazıp çalıştırdığımızda diskte duran çalıştırılabilir kodlar işletim sistemi tarafından diskten okunur ve belleğe açılır ve daha sonra bellek üzerinde yürütülmeye başlanır. İşte bu işlem de process creation yani proses yaratma/oluşturma olarak adlandırılır. İlgili program artık işletim sistemi tarafından bir proses haline getirilmiştir ve işlemci üzerinde yürütülür.
Diskte duran bir bilgisayar programı proses haline gelip yürütülmeye başlandığı zaman kernel içerisinde birçok işlem yapılır. Kernel o anda sistem üzerinde çalışan tüm prosesleri takip etmek zorundadır.
Eğer aynı programı tekrar çalıştırırsak yani o programdan aynı anda birden fazla çalıştırırsak her biri ayrı proeses haline gelir. Örneğin Linux üzerinde birden fazla terminal çalıştırdığımız zaman ve eğer BASH kullanıyorsak diskte duran BASH programı terminal sayısı kadar çalıştırılır ve hepsi ayrı proses haline gelir.
Prosesler bir olaylar silsilesidir.
Prosesler ile İlgili Komutlar
Sistem programlama açısından proses kavramına bakmadan önce Linux’u kullanan bir kullanıcı olarak proses ile ilgili kullanabileceğimiz komutlara bir bakalım.
Bu komutların en meşhuru ps
yani process status komutudur. Bu komut
birçok argüman alabilmektedir.
alper@brs23-2204:~$ ps u
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
alper 1774 0.0 0.0 165144 5888 tty2 Ssl+ 08:56 0:00 /usr/libexec/gdm-wayland-session env GNOME_
alper 1778 0.0 0.1 225796 15616 tty2 Sl+ 08:56 0:00 /usr/libexec/gnome-session-binary --session
alper 4762 0.0 0.0 14184 5504 pts/0 Ss+ 09:06 0:00 /usr/bin/bash --init-file /usr/share/code/r
alper 4766 0.0 0.0 14184 5504 pts/1 Ss 09:06 0:00 /usr/bin/bash --init-file /usr/share/code/r
alper 5280 0.0 0.1 18172 8884 pts/1 S 09:10 0:00 zsh
alper 5326 0.0 0.0 16380 4436 pts/1 S 09:10 0:00 zsh
alper 5342 0.0 0.0 17784 5588 pts/1 S 09:10 0:00 zsh
alper 5343 0.0 0.0 17768 5204 pts/1 S 09:10 0:00 zsh
alper 5345 0.0 0.0 20304 3328 pts/1 Sl 09:10 0:00 /home/alper/.cache/gitstatus/gitstatusd-lin
alper 5371 3.5 0.3 42000 28192 pts/1 S+ 09:10 0:31 /home/alper/.local/share/virtualenvs/ayazar
alper 7160 0.0 0.0 14156 5248 pts/2 Ss 09:24 0:00 bash
alper 7236 0.0 0.0 15424 3328 pts/2 R+ 09:25 0:00 ps u
Örneğin ps u
dediğimiz zaman o kullanıcıya ait çalışan prosesler hakkında
bilgi verir. Ya da ps -e
diyerek sistemde çalışan tüm prosesleri görebiliriz.
alper@brs23-2204:~$ ps -e --no-headers | wc -l
269
Örneğin benim sistemimde 269 adet proses çalışıyormuş. Masaüstü ortamı kurulu, kullanıcının interaktif olarak bilgisayarı kullandığı bir Linux sisteminde, örneğin Ubuntu üzerinde çalışıyorsunuz diyelim, birkaç yüz adet proses görmek şaşırtıcı değildir.
Bazı işletim sistemlerinde process yerine task da denmektedir. Bu biraz işletim sisteminin jargonuna da bağlıdır. Örneğin FreeRTOS işletim sisteminde task kelimesi kullanılmaktadır ama Linux’ta proses diyoruz.
task_struct
Çalışan her proses için kernel çeşitli bilgiler tutmak zorundadır. İşte bu bilgilerin tutulduğu yere Process Control Block (PCB) diyebiliriz. Yine burada işletim sisteminden işletim sistemine isimler değişebilir ama mantık aynıdır. PCB ismi de bana Baskı Devre Kartı’nı çağrıştırıyor.
Linux kernel’i C dilinde yazılmış bir programdır ve PCB veri yapısı için bir
struct
oluşturulmuştur. Bu struct’ın adı task_struct
oldup sched.h
içerisinde
tanımlanmıştır. [1]
struct task_struct {
#ifdef CONFIG_THREAD_INFO_IN_TASK
/*
* For reasons of header soup (see current_thread_info()), this
* must be the first element of task_struct.
*/
struct thread_info thread_info;
#endif
unsigned int __state;
/* saved state for "spinlock sleepers" */
unsigned int saved_state;
//******//
}
Yukarıda çok kısa bir parçasını gösterdim. task_struct
, büyük bir veri
yapısıdır. Örneğin kernel 6.9.5
sürümünde yorumlar ve önişlemci makroları ile
beraber 820 satır yer kaplamaktadır. Ayrıca buradaki elemanların bir kısmı
sadece pointer elemanlardır yani aslında daha başka veri yapılarını da gösterici
yolu ile içermektedir. Yıllar içerisinde kernel geliştikçe task_struct
da
büyümüştür.
Özellikle ilk aşamada prosesler ile ilgilendiğimiz kavramlar daha temel kavramlar
olacaktır. Bu yüzden task_struct
içerisindeki birkaç elemana bakacağız.
PID
PID dendiği zaman aklınıza PID kontrolcü gelebilir ama Linux dünyasında PID, Process ID demektir. Linux üzerinde koşan her bir prosesin bir ID değeri olmaktadır, buna PID diyoruz. PID, bir tam sayıdır. Bir PID değeri sadece bir prosese ait olabilir. Bir başka deyişle herhangi bir t anında sistem üzerinde bir PID’nin gösterdiği proses yalnızca bir tanedir. Bir PID birden fazla prosesi gösteremez, bir prosesin de birden fazla PID’si olamaz. Yani uzun lafın kısası, prosesler ile PID değerleri arasında bire bir eşleme vardır.
PID değerleri kernel tarafından handle olarak kullanılır. Bir veri
yapısında, değerlere erişmek için kullanılan anahtarlara handle denmektedir.
Kernel, PID değerleri ile proseslerin task_struct
elemanlarından oluşan bir
tablo tutmaktadır. Bir PID değeri ile işlem yapılmak istendiği zaman o prosese
ait task_struct
elemanı kernel tarafından bulunur ve ilgili işlem yapılır.
Benzer handle yapısını daha henüz pek detaylarını görmediğimiz dosya işlemlerinde
de görmüştük. Orada gördüğümüz file descriptor değerleri de birer handle’dır.
Yine benzer şekilde C dilinde olan FILE
nesneleri de birer handle olarak
kullanılmaktdır.
PID değerleri ile ilgili Linux üzerinde çeşitli limitler vardır. Herhangi bir anda PID’ler ile prosesler arasında bire bir eşleşme olacağı garanti edilmiştir. Özellikle sunucu gibi günlerce hatta yıllarca kapanmadan çalışan sistemlerde prosesler yaratılıp sonlanmaktadır. Bellirli bir noktadan sonra daha önceden kullanılmış bir PID değerinin tekrar başka bir proses için kullanılması gerekecektir. Bire bir eşleme bozulmadığı sürece bir PID değerinin birden fazla kez kullanılması problem değildir.
İpucu
Örneğin TC kimlik numarası da aslında bir ID değeridir fakat bir kişi ölürse onun TC kimlik numarası yeni doğan birine verilmez, yani universal unique bir değerdir. PID değerleri ise böyle değildir, tekrar tekrar kullanılabilir. Örneğin bir bankaya gidip numeratörden numara aldığımız zaman o numaranın başka birisinde olmaması önemlidir ve aynı zamanda yeterlidir. Ertesi gün aynı numaralar başka kişilere de verilir, yeter ki herhangi bir anda çakışma olmasın. İşte PID de bu açıdan TC kimlik numarasına değil banka sıra numarasına benzemektedir.
Linux üzerinde kernel tarafından başlatılan ilk process, init process olarak
adlandırılır. Kernel adeta açıldıktan yani boot ettikten sonra sistemi bu process’e
emanet eder. Geri kalan tüm işlemler bu process tarafından yapılır. Bu yüzden
init process’ler özeldir ve bunlar için sistemlerimizde systemd, openrc gibi
init sistemleri kullanırız. İşte init process’in PID değeri 1 olmaktadır. Bazen
bu processler PID 1 process olarak da adlandırılır. Detaylarını ileride
göreceğiz, bu init process yeni prosesler yarattıkça kernel bu oluşturulan yeni
proseslere PID numaraları atar, 2, 3, 4 gibi. Elbette bu sayının da bir sınırı
vardır. Kernel tipik olarak belli bir limit değere kadar bu sayıyı arttır ve
o limit değere ulaşınca başa döner. İşte bu limit değeri
/proc/sys/kernel/pid_max
dosyasını okuyarak öğrenebiliriz. Örneğin benim
sistemimde
alper@brs23-2204:~$ cat /proc/sys/kernel/pid_max
4194304
böyle bir değer çıktı. Tipik olarak 32767
değerini de görebilirsiniz. Yine
bu dosyaya yazarak ya da başka yollardan bu limiti değiştirebilirsiniz ama
şu an konumuz değil.
Not
Burada Linux üzerinde her şey dosyadır yaklaşımını tekrar vurgulamak istiyorum.
/proc
altında bulunan dosyaların hiçbiri diskte bulunan dosyalar değildir.
Bilgisayarımızı kapattığımız zaman sabit diskimizde böyle bir klasör yer almaz.
/proc
, sanal dosya sistemidir. Bunu dilerseniz mount
komut ile kontrol
edebilirsiniz:
alper@brs23-2204:~$ mount | grep "/proc "
proc on /proc type proc (rw,nosuid,nodev,noexec,relatime)
Kernel, çeşitli parametreleri kullanıcının okuyabileceği ya da yazabileceği
hale getirmek için /proc
altında bunları birer dosyaymış gibi sergiler.
Biz buradan bir okuma yaptığımız zaman aslında diskten bir dosya okunmaz,
kernel bize döndürmek istediği değeri döndürür. İşte Linux’ta her şeyin dosya
olması böyle bir şeydir. cat
aslında bir dosyadan okuma yapmak için
kullanılan bir komuttur, cat dosya.txt
gibi. cat
yine burada bir dosyadan
okuma yapmaktadır ama cat
in bilmediği bir şey bu dosyanın aslında diskte
bir karşılığı olmadığıdır. İşte Linux üzerinde birçok arayüz standart dosya
arayüzü kullanılarak sağlanmıştır. Bu sayede cat
gibi bir programı farklı
işler için kullanabiliyoruz. Benzer şekilde echo
programını da /proc
altındaki
dosyalara yazma yapmak için kullanabiliriz. Bu durumda da kernel’in ayarlarını
değiştirmiş olacağız. /proc
altında mount edilmiş olan bu sanal dosya sistemi
procfs olarak da bilinmektedir. Bknz
Kernel tipik olarak yeni proses yaratıldıkça pid_max
değerine gidene kadar PID
değerlerini yeni oluşan proseslere verir. Bir proses sonlandığı zaman onun PID
değeri boşa çıkar. Ama sürekli geriye dönüp boşta değer var mı diye bakmak
kernel açısından maliyetli olacağından ileriye doğru bu sayı arttırılır.
pid_max
değerine gelindiği zaman bu sefer başa dönülüp boşta olan değerler
aranılır. Elbette yıllarca çalışan bir sistemde pid_max
tan kat kat fazla sayıda
proses yaratılıp, sonlandırılmış olabilir.
Sistem üzerinde aynı anda pid_max
tan fazla proses çalışamaz. Diyelim ki
pid_max
kadar proses aynı anda sistem üzerinde bulunuyor. Yeni proses yaratmak
istediğimiz zaman kernel boş bir PID değeri bulamayacağı için yeni proses
yaratılamaz. Elbette bu durum çoğu kez olası değildir ayrıca pid_max
değeri de
arttırılabilir.
Henüz konumuz olmayan threads konusu ile ilgili olan bir limit değer daha
vardır:threads-max
değeri, /proc/sys/kernel/threads-max
dosyasından okunabilir.
threads-max
sistemde çalışacak toplam thread sayısını limitler. Linux açısından
thread’ler ile prosesler benzer kavramlardır. Bir proses altında en az bir thread
bulunur ama birden fazla thread de bulunabilir. Buna genelde
multi-thread programming denir. threads-max
toplam thread sayısını limitler.
Ama tüm process’leirmiz tek thread’li ise aslında toplam proses sayısını da
limitlemiş olacaktır.
alper@brs23-2204:~$ cat /proc/sys/kernel/threads-max
62198
alper@brs23-2204:~$ ps -e --no-headers | wc -l
281
alper@brs23-2204:~$ ps -eL | wc -l
1422
Sistem üzerinde aynı anda threads-max
tan fazla proses çalışamaz.
Örneğin benin sistemimde threads-max
değeri 62198’dir. Anlık olarak sistemde
281 adet proses ve 1422 adet thread çalışmaktadır. Yani ortalamada bir proses
5 adet thread’e sahiptir. Dikkat ederseniz sistemdeki threads-max
değeri
pid_max
tan (4194304) küçük çıktı. Yani benim açımdan aslında aynı anda
çalıştırabileceğim process değeri 62198 oluyor, o da her proseste sadece bir
thread varsa, yoksa daha düşük olacaktır.
Hazır konusu açılmışken kullanıcı limitlerinden de bahsetmek istiyorum. BASH’te
ulimit
isimli bir built-in komut bulunuyor, -u
flag’i ile bir sayı elde
edebiliyoruz.
alper@brs23-2204:~$ type ulimit
ulimit is a shell builtin
alper@brs23-2204:~$ ulimit -u
31099
Bir de bu şekilde bu komutu çalıştıran kullanıcının aynı anda çalıştırabileceği maksimum proses sayısı (threads?) öğrenilebiliyor. [2] [3]
RLIMIT_NPROC
This is a limit on the number of extant process (or, more
precisely on Linux, threads) for the real user ID of the
calling process. So long as the current number of
processes belonging to this process's real user ID is
greater than or equal to this limit, fork(2) fails with
the error EAGAIN.
The RLIMIT_NPROC limit is not enforced for processes that
have either the CAP_SYS_ADMIN or the CAP_SYS_RESOURCE
capability, or run with real user ID 0.
Yani pid_max
ya da
threads-max
tan daha düşük bir değerse, ilgili kullanıcı bu sayı ile
limitlenecektir. Ama root
kullanıcı tipik olarak bir limite takılmaz.
Bu tip konular çoğunlukla Linux System Administrator başlığı altında yer almaktadır ama ucundan bakmış olduk.
getpid()
ve pid_t
Bir program çalıştığı zaman bir proses oluştuğunu söylemiştik. Peki biz kendi programımız bir proses olup çalıştığı zaman bu prosese atanan PID değerini programın içinden alabilir miyiz? Cevabımız evet.
getpid()
fonksiyonu tam olarak da bu işe yarayan bir fonksiyon. [4]
#include <unistd.h>
pid_t getpid(void);
Bildirimi unistd.h
içerisinde bulunan bu fonksiyon bir parametre almıyor ve
pid_t
türünden bir geri dönüş değerine sahip. pid_t
bir typedef
yani tür
eş ismi. pid_t
işaretli bir tam sayı yani signed integer fakat tam olarak
hangisi olacağı (short
, int
, long
vs) belirtilmiş değil, implementation
defined. [5] Örneğin x86 32-bit sistemlerde bunun int
olacağını söylemek
pek yanlış olmaz. POSIX standartlarına göre pid_t
en fazla long int
büyüklüğünde olabilir. [6] [7] [8] Bu detaylar C dilinde
pid_t
yi veri kaybı yaşamadan ya da işaret hatası yapmadan kullanabilmemiz için
önemli.
Özetle, pid_t
en fazla long int
büyüklüğünde olabilecek işaretli bir tam
sayı türüdür.
#include <unistd.h> //getpid(), pid_t
#include <stdio.h>
int main(void)
{
pid_t pid;
pid = getpid();
printf("PID = %ld\n", (long)pid); (void)fflush(stdout);
(void)getchar();
return 0;
}
Yukarıdaki kod ile getpid()
fonksiyonunu kullanarak prosesin PID değerini
öğreniyoruz. (void)
cast’ler ve fflush()
fonksiyonu kafanızı karıştırmasın,
burada pek önemli değiller. Bendeki çıktı bu şekilde.
PID = 4188
Programdaki getchar()
fonksiyonundan dolayı biz bir girdi yapana kadar
programımız çıkmadan bekliyor. Şimdi başka bir terminalde bu PID değeri ile
ilgili bilgiler elde etmeye çalışalım.
ay@2204:~$ ps up 4188
USER PID %CPU %MEM VSZ RSS TTY STAT START TIME COMMAND
ay 4188 0.0 0.0 2776 924 pts/1 S+ 21:49 0:00 ./a.out
Görüleceği üzere bu prosesi çalıştıran kullanıcı ay
ve ./a.out
komutu ile
çalıştırılmış.
Bir proses ile ilgili bilgier /proc/
altında da yaratılıyor. Bizim PID’yi
ele alırsak:
ay@2204:/proc/4188$ cd /proc/4188
ay@2204:/proc/4188$ ll
total 0
dr-xr-xr-x 9 ay ay 0 Jun 20 21:50 ./
dr-xr-xr-x 269 root root 0 Jun 20 21:42 ../
-r--r--r-- 1 ay ay 0 Jun 20 21:54 arch_status
dr-xr-xr-x 2 ay ay 0 Jun 20 21:54 attr/
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 autogroup
-r-------- 1 ay ay 0 Jun 20 21:54 auxv
-r--r--r-- 1 ay ay 0 Jun 20 21:54 cgroup
--w------- 1 ay ay 0 Jun 20 21:54 clear_refs
-r--r--r-- 1 ay ay 0 Jun 20 21:50 cmdline
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 comm
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 coredump_filter
-r--r--r-- 1 ay ay 0 Jun 20 21:54 cpu_resctrl_groups
-r--r--r-- 1 ay ay 0 Jun 20 21:54 cpuset
lrwxrwxrwx 1 ay ay 0 Jun 20 21:52 cwd -> /home/ay/temp/
-r-------- 1 ay ay 0 Jun 20 21:54 environ
lrwxrwxrwx 1 ay ay 0 Jun 20 21:52 exe -> /home/ay/temp/a.out*
dr-x------ 2 ay ay 0 Jun 20 21:52 fd/
dr-xr-xr-x 2 ay ay 0 Jun 20 21:54 fdinfo/
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 gid_map
-r-------- 1 ay ay 0 Jun 20 21:54 io
-r--r--r-- 1 ay ay 0 Jun 20 21:54 limits
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 loginuid
dr-x------ 2 ay ay 0 Jun 20 21:54 map_files/
-r--r--r-- 1 ay ay 0 Jun 20 21:52 maps
-rw------- 1 ay ay 0 Jun 20 21:54 mem
-r--r--r-- 1 ay ay 0 Jun 20 21:54 mountinfo
-r--r--r-- 1 ay ay 0 Jun 20 21:54 mounts
-r-------- 1 ay ay 0 Jun 20 21:54 mountstats
dr-xr-xr-x 55 ay ay 0 Jun 20 21:54 net/
dr-x--x--x 2 ay ay 0 Jun 20 21:54 ns/
-r--r--r-- 1 ay ay 0 Jun 20 21:54 numa_maps
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 oom_adj
-r--r--r-- 1 ay ay 0 Jun 20 21:54 oom_score
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 oom_score_adj
-r-------- 1 ay ay 0 Jun 20 21:54 pagemap
-r-------- 1 ay ay 0 Jun 20 21:54 patch_state
-r-------- 1 ay ay 0 Jun 20 21:54 personality
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 projid_map
lrwxrwxrwx 1 ay ay 0 Jun 20 21:52 root -> //
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 sched
-r--r--r-- 1 ay ay 0 Jun 20 21:54 schedstat
-r--r--r-- 1 ay ay 0 Jun 20 21:54 sessionid
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 setgroups
-r--r--r-- 1 ay ay 0 Jun 20 21:54 smaps
-r--r--r-- 1 ay ay 0 Jun 20 21:54 smaps_rollup
-r-------- 1 ay ay 0 Jun 20 21:54 stack
-r--r--r-- 1 ay ay 0 Jun 20 21:50 stat
-r--r--r-- 1 ay ay 0 Jun 20 21:54 statm
-r--r--r-- 1 ay ay 0 Jun 20 21:50 status
-r-------- 1 ay ay 0 Jun 20 21:54 syscall
dr-xr-xr-x 3 ay ay 0 Jun 20 21:54 task/
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 timens_offsets
-r--r--r-- 1 ay ay 0 Jun 20 21:54 timers
-rw-rw-rw- 1 ay ay 0 Jun 20 21:54 timerslack_ns
-rw-r--r-- 1 ay ay 0 Jun 20 21:54 uid_map
-r--r--r-- 1 ay ay 0 Jun 20 21:54 wchan
Kernel her proses için /proc/<PID>
altında bu şekilde sanal dosya ve
klasörler oluşturuyor.
ay@2204:/proc/4188$ ls -l /proc/4188/exe
lrwxrwxrwx 1 ay ay 0 Jun 20 21:52 /proc/4188/exe -> /home/ay/temp/a.out
Örneğin exe
isimli dosya aslında o prosesi oluşturan yani çalışan programı
gösteriyor. Yeri geldikçe diğer dosyalara da bakarız.
pid_t
Türünün C Dilinde Ele Alınması
pid_t
türü ile ilgili biraz daha konuşmak istiyorum. Yukarıda da belirttiğim
gibi bu tür en fazla signed long int
kadar geniş olabiliyor. Fakat long
olacağının garantisi yok, int
olabilir bir sistemde. Peki bu türü nasıl
ele alacağız? Özellikle printf()
gibi variadic fonksiyonlarda dikkat etmek
gerekiyor.
printf()
gibi variadic fonksiyonları, variadic parametrelerin türünü anlamak
için çeşitli yöntemler kullanıyorlar. Örneğin printf()
fonksiyonu ilk
parametresi olan string içerisinde %d
ile eşlediği parametreyi int
, %ld
ile eşlediği parametreyi long
olarak ele alıyor. Derleyicinin bu tarz
fonksiyonlarda tür kontrolü yapma şansı düşük, gerçi modern derleyiciler
printf()
te bunu yapabiliyor ama bizler C programcıları olarak doğru kod
yazmalıyız. Variadic fonksiyonlarda kontrol mekanizmaları kısıtlı olduğu için
programcıların doğru casting işlemlerini yapması gerekiyor. Peki printf()
ile pid_t
yi yazdırırken bunu neyle eşleyeceğiz, %d
mi %ld
mi yoksa
başka bir şey mi?
pid_t
, long
türünden büyük olamaz. O halde (long)pid
şeklinde casting
işlemi yapmamız bir veri kaybı yaratmayacaktır. Elimizde long
türden bir
nesne olduğunu bildiğimizde bunu %ld
ile bastırabiliriz. [9] Bir diğer
seçeneğimiz de C99 ile dile eklenen intmax_t
türünü kullanmak. intmax_t
nin platformdan bağımsız olarak dilde bulunan herhangi bir işaretli tam sayı
türündeki bir değeri tutabileceği garanti edilmiş durumdadır. pid_t
nin long
u geçemeyeceğini biliyoruz fakat böyle bir bilgi olmasaydı bu sefer intmax_t
ye cast edebilirdik. Bu türden bir değeri de printf()
içerisinde %jd
ile
yazdırabiliriz.
Kullanıcı ve Grup ID
Linux sistemleri çok kullanıcılı sistemlerdir. Her bir kullanıcının bir kullanıcı adı vardır. Fakat işletim sistemi seviyesinde kullanıcı takibi isimler ile değil numalar üzerinden yapılır. Buna kullanıcı numarası, user ID ya da çoğu zaman UID adı verilir. Bir kullanıcının bir adet UID numarası olabilir. Aksine, kullanıcıların ait olduğu gruplar vardır. Güncel sistemlerde kullanıcılar tipik olarak birden fazla gruba dahildir. Grupların da bir numarası vardır, group ID ya da GID olarak belirtilir. Kullanıcı/grup ID mekanizması Linux üzerindeki temel izin kontrol mekanizmasını oluşturur. Dosya sistemindeki her klasör ve dosyanın da ID bilgileri vardır ve bir kullanıcının dosya sistemi üzerinde yapabileceği şeyler (dosya yaratma, silme, var olan dosyayı değiştirme, okuma gibi) bu ID’ler üzerinden kontrol edilir.
id
kabuk komutu ile kullanıcı ID ve ait olduğumuz grup ID değerlerini
görebiliriz.
ay@2204:~$ id
uid=1000(ay) gid=1000(ay) groups=1000(ay),4(adm),24(cdrom),27(sudo),30(dip),46(plugdev),110(lxd)
Örneğin benim UID
değerim 1000
imiş ve dahil olduğum ana, primary grubun
ID değeri yani GID
değeri de 1000
imiş. Onun dışında dahil olduğum başka
gruplar da varmış. Bildiğim kadarıyla ilk UNIX sistemlerde bir kullanıcının bir
grubu olabiliyormuş ama bu çeşitli kısıtlar getirdiği için kullanıcıların ek
yani supplementary group ları olması da sağlanmış. Günümüzdeki Linux
sistemlerde de bir kullanıcının bir UID değeri olsa da dahil olduğu birden fazla
grup olabilir.
Linux proseslerin de ait olduğu kullanıcı ve grup ID’leri vardır. Çünkü prosesler, kullanıcılar ile ilişkilendirilir.
Prosesler için task_struct
isimli bir veri yapısından bahsetmiştik. Bu bilgiler
de burada tutulmaktadır. İlgili veri yapısı içerisinde cred
isimli,
credential ?, bir veri yapısına pointer bulunur. Bu veri yapısı içerisinde de
ilgili ID bilgileri saklanır: [10]
struct cred {
atomic_long_t usage;
kuid_t uid; /* real UID of the task */
kgid_t gid; /* real GID of the task */
kuid_t suid; /* saved UID of the task */
kgid_t sgid; /* saved GID of the task */
kuid_t euid; /* effective UID of the task */
kgid_t egid; /* effective GID of the task */
kuid_t fsuid; /* UID for VFS ops */
kgid_t fsgid; /* GID for VFS ops */
//
}
uid_t
ve gid_t
Bir proses, kendisine ait olan id değerlerini sistem fonksiyonlarına çağrı
yaparak öğrenebilir. UID için uid_t
, GID için gid_t
veri türleri
tanımlanmıştır. Bunlar typedef edilen tür eş isimleridir, tam sayı şeklinde
tanımlanırlar. pid_t
nin aksine genişlikleri ile ilgili bir kısıtlama POSIX
standartlarında yapılmamıştır. Peki uid_t
türünden bir değişkeni nasıl
printf()
ile yazdırabiliriz? uid_t
nin işaretli olup olmaması konusunda da
bir bilgi verilmemiştir. O yüzden nümerik olarak olabilecek en yüksek tam sayı
değerini tutan ve C99 standartı ile C diline eklenmiş uintmax_t
türünü
kullanmamız en mantıklısıdır. [11] Böyle bir değeri de printf()
içerisinde
%ju
ile bastırabiliriz. Fakat pratikte bu kadar zorlamaya gerek yok. Örneğin
Advanced Programming in The UNIX Environment kitabında int
gibi davranılmış
ve %d
ile eşleştirilmiştir, cast yapılmadan ki bence en azından cast
yapılmalıdır. The Linux Programming Interface adlı kitapta long
a cast
yapılıp, %ld
ile yazdırılmıştır. Ben Kaan Aslan Hoca’nın yaklaşımını doğru
buluyorum ve uintmax_t
kullanacağım. Fakat cast ettiğiniz sürece pratikte en
az int
olmak üzere bir tam sayıya cast ettiğiniz zaman problem yaşamamanız
gerekir çünkü ID değerleri çok büyük sayılar olmuyor. Fakat bir varsayım
yapmadan ilerlemek istiyorsak uintmax_t
en iyi seçenek.
Kullanıcı ve grup ID öğrenmek için getuid()
ve getgid()
fonksiyonlarını
kullanabiliriz. Prototipleri unistd.h
içerisindedir.
#include <unistd.h>
uid_t getuid(void);
gid_t getgid(void);
Örnek:
#include <unistd.h>
#include <stdio.h>
#include <stdint.h> //uintmax_t
int main(void){
uid_t uid;
gid_t gid;
uid = getuid();
gid = getgid();
printf("UID = %ju, GID = %ju\n", (uintmax_t)uid, (uintmax_t)gid);
return 0;
}
Yukarıdaki kodu çalıştırdığımız zaman bendeki çıktı:
UID = 1000, GID = 1000
Efektif UID ve GID: EUID ve EGID
Proseslerin bir de efektif yani etkin ID değerleri vardır. Biraz önce baktıklarımız real ID olarak da geçmektedir. Efektif ID’ler de benzer şekilde sistem fonksiyonları kullanılarak öğrenilebilirler. Burada da
#include <unistd.h>
uid_t geteuid(void);
gid_t getegid(void);
fonksiyonlarını kullanacağız. Programımızı değiştirelim:
#include <unistd.h>
#include <stdio.h>
#include <stdint.h> //uintmax_t
int main(void){
uid_t uid, euid;
gid_t gid, egid;
uid = getuid();
euid = geteuid();
gid = getgid();
egid = getegid();
printf("UID = %ju, GID = %ju\n", (uintmax_t)uid, (uintmax_t)gid);
printf("EUID = %ju, EGID = %ju\n", (uintmax_t)euid, (uintmax_t)egid);
return 0;
}
ve çalıştıralım:
UID = 1000, GID = 1000
EUID = 1000, EGID = 1000
Aynı değerleri gördük, bunların olayı nedir?
Etkin ID’lerin tam olarak ne işe yaradığına daha sonra bakacağız. Fakat kernel,
process’in bir şeyi yapma yetkisinin olup olmadığına bakmak için etkin ID
değerlerini kullanır. Örneğin proses bir dosyaya yazma yapmak istiyorsa bunu
yapıp yapamayacağı etkin ID değerleri ile kontrol edilir. Etkin ID’lerin, Gerçek
ID’lerden nasıl farklı olacağı bir başka yazının konusu ama demo amaçlı SUID
kullanarak bir bakalım:
ay@2204:~/temp$ sudo chown 4123:3456 a.out
ay@2204:~/temp$ ll a.out
-rwxrwxr-x 1 4123 3456 16136 Jun 21 00:04 a.out*
ay@2204:~/temp$ sudo chmod +s a.out
ay@2204:~/temp$ ./a.out
UID = 1000, GID = 1000
EUID = 4123, EGID = 3456
ay@2204:~/temp$ ll a.out
-rwsrwsr-x 1 4123 3456 16136 Jun 21 00:04 a.out*
Yukarıda ne oldu? Derlenmiş programımızın dosyasının, a.out
, sahipliğini
sistemimde olmayan 4123
ID’li kullanıcıya ve 3456
ID’li gruba geçirdim,
bunları uydurdum. Daha sonra dosyanın SUID
bayrağını set ettim. Bu durumda
dosyayı çalıştırdığımız zaman gerçek ID’lerin benim ID’ler fakat etkin ID’lerin
diğer ID’ler olduğunu görüyoruz. Yani programı ben çalıştırmış olsam da proses
adet diğer ID’li kullanıcı çalıştırıyormuş gibi, onun yetkileri ile çalışıyor.
Bunlara daha sonra bakacağız, şimdilik bu kadar 👋
Bakmaya Değer
: [12]