Professional Documents
Culture Documents
www.csystem.org
Sayfa 1 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Unix 1969-1970 yıllarında AT&T Bell Lab tarafından DEC-PDP 7 makinaları için
üretilmiştir. Unix projesinden önce AT&T MIT gibi üniversitelerle işbirliği yaparak
MULTICS isimli bir işletim sistemi geliştirme projesinde çalışıyordu. Daha sonra grup çeşitli
haklı gerekçelerle bu projeden çekilmiştir. Unix ismi B.Kernighan tarafından Multics
sözcüğünden kelime oyunu yaparak uydurulmuştur. Ilk Unix sistemi tamamen PDP
makinalarının sembolik makine dilinde yazılmıştı. O yıllara kadar tüm işletim sistemleri zaten
sembolik makine dilinde yazılıyordu.
Unix işletim sistemi geliştirilirken Ken Thompson bir işletim sisteminin ciddi bir sistem
olabilmesi için yüksek seviyeli bir derleyiciye sahip olması gerektiğini düşünüyordu.
Thompson , önceleri Fortran üzerinde durduysa da daha sonra BCPL gibi bir sistemin daha
uygun olduğuna karar vermiştir. Bundan sonra Thompson BCPL nin benzeri olan B dilinin
tasarımında da çalışmıştır. B bir yorumlayıcı sistemdi ve yavaş çalışıyordu. B programlama
dili proje grubu tarafından yerel bir biçimde ve bazı sistem programlarında kullanılmıştır.
Proje grubu K.Thompson, D.Ritche, B.Kernighan gibi önemli kişilerden oluşuyordu. Grubun
asıl amaçlarından biri Unix sistemini yüksek seviyeli bir dille yeniden yazmaktı. D.Ritche, B
programlama dilini geliştirerek ilk kez 1971 yılında C programlama dilini tanımlamıştır. 1973
yılında Unix işletim sistemi DEC-PDP 11 için çok büyük oranda C ile yeniden yazılmıştır.
Unix yüksek seviyeli bir programlama diliyle yazılmış olan ilk işletim sistemidir. Bu durum
Unix işletim sisteminin farklı platfromlarda yazılmasını çok kolaylaştırmıştır. AT&T Unix
işletim sisteminin kaynak kodlarını pekçok araştırma grubu ve üniversitelere dağıtmıştır. Bu
durum C programlama dilinin tanınmasına yol açarken Unix sistemlerinin taşınabilirliğinin
bozulması sonucunu da doğurmuştur. Çünkü kaynak kodları ele geçiren kurumlar orijinal
Unix sistemlerinde değişiklikler yaparak farklı Unix sistemleri oluşturmuşlardır. Bu
sistemlerin en önemlilerinden biri Berkeley Sisitemleridir, bu sistemler BSD (Berkeley
software distrubition) olarak bilinmektedir. BSD nin FreeBSD, NetBSD ve Open BSD
biçiminde versiyonları vardır. AT&T kendi Unix sistemlerine de numara vermiştir. Örneğin,
system III, systemV gibi... Bugün Unix sistemlerinin en temel biçimi SVR4 (system 5 release
4 ) sürümüdür. Bundan sonra AT&T nin çeşitli Unix sistemleri çıktıysa da, SVR4 bir standart
anlatan özellik olmuştur.
80 li yıllarda pekçok özel firma da kendi Unix sistemlerini yazmaya başlamıştır. Örneğin; Sun
firmasının Solaris, HP firmasının HP-UX, SCO firmasının SCO_UNIX, IBM firmasının AIX
örnek olarak verilebilir.
AT&T 80 li yıllarının başında Unix sistemlerine telif uygulamaya başlamıştır. Bu durumdan
tedirgin olan üniversiteler ve araştırıcılar bedava Unix sistemleri geliştirme yönünde motive
olmuşlardır. Bunun üzerine Hollandali profesor A. Tanenbaum işletim sistemi derslerinde
kullanmak üzere MINIX isminde mini bir Unix sistemi yazmıştır. MINIX ticari amaçla çeşitli
kesimler tarafından kullanılmış olsa da ciddi bir yaygınlık kazanmamıştır. Fakat kaynak
kodları pekçok geliştiriciye ilham vermiştir.
1985 yılında Richard Stallman FSF (Free Software Foundation) isimli bir kurum altında
serbest yazılım fikrini atmıştır. Serbest yazılım bedava yazılımdan daha çok geliştirme de
özgürlüğü belirtmektedir. Yani, programcı yazdığı programı kaynak koduyla birlikte verir,
kaynak kodu sahiplenip değiştirilmesini engelleyemez. Kaynak kodu elde eden programcı
orijinal yazardan alıntı yaptığını belirterek kodu değiştirebilir. Serbest yazılım bedava
olabilirde olmayabilirde, fakat serbest yazılım parayla satılsa bile kaynak kodu verilmek
zorundadır. Richard Stallman FSF kurumunu oluşturduktan sonra GNU isimli bir proje
oluşturmuştur. GNU (GNU’ not UNIX) bir işletim sistemi ve yardımcı ek programlar
oluşturmayı hedefleyen bir projedir. Bu proje hala devam etmektedir(www.fsf.org) GNU
projesi kapsamında pekçok yararlı programlar yazılmıştır(gcc derleyicisi). Ancak maalesef
işletim sisteminin kendisi yazılamamıştır.
Sayfa 2 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
1990 yılında Linus Torvalds GNU lisansı altında MINIX ve UNIX sistemlerinden alıntılarla
serbest bir Unix sistemi oluşturmuştur. Bugün Linux sistemlerindeki yazılımlar GNU lisanslı
yazılımlardır. Yani, Linux sistemleri çekirdeği Linus Torvalds tarafından tasarlanmış olan
yaralı programları GNU projesi kapsamıyla elde edilmiş olan bir sistemdir. Pekçok kişi bugün
kullanılan bu sisteme Linux yerine GNU/ Linux denmesinin daha uygun olacağını
düşünmektedir.
1980 li yılların sonlarına doğru UNIX sistemleri standart hale getirilmeye çalışılmıştır. Bunun
için IEEE bünyesinde stadardizasyon ekibi kurulmuş ve POSIX standartları diye bilinen
standartlar oluşturulmuştur. POSIX bir grup standartlardan oluşan bir standartlar
topluluğudur. Bu grup içerisindeki herbir standart POSIX 1003.X biçiminde
isimlendirilmiştir. Örneğin, 1003.1 POSIX uyumlu UNIX sistemlerinin C de bulundurması
gereken sistem fonksiyonlarına ayrılmıştır. 1003.2 Shell programlarının kullandığı standart
komutları tanımlamaktadır.
Sayfa 3 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
UNIX/LINUX KOMUTLARI
Unix/Linux sistemlerinde içsel komut kavramı yoktur. Bütün komutlar bir program dosyası
biçiminde yani bir programın çalıştırılması biçiminde yürütülmektedir. Komutları ilişkin
program dosyaları /bin dosyasında oluşturulmuştur.
Sayfa 4 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Unix/Linux komutlarındaki seçenekleri ayırmak için POSIX standartlarında belirtilen getopt
tercih edilmektedir. Bu fonksiyon komut satırını ayrıştırmak için oldukça kolaylaştırıcı
işlevlere sahiptir.
SÜPER KULLANICI
Unix/Linux sistemlerinde bir kullanıcının başkasının bilgilerine ulaşması bir güvenlik
mekanizmasıyla engellenmiştir. Herhangi bir kullanıcı sistemin bütününü engelleyecek
komutlarıda kullanamamaktadır. Ancak kullanıcı ismi root olan ve süper kullanıcı (super
user) diye isimlendirilen kullanıcı hiçbir güvenlik engeline takılmadan her türlü işlemi
yapabilmektedir. Süper kullanıcının kullanıcı adı her zaman root dur, süper kullanıcının
password bilgisi kurulum sırasında belirli bir aşamada kurulumu gerçekleştiren kişi tarafından
belirlenir.
Sayfa 5 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
POSIX FONKSİYONLARI
POSIX standartlarının birinci cildi (POSIX 1003.1) C programlama dili için POSIX uyumlu
bir işletim sisteminin bulundurması gereken fonksiyonları tanımlamaktadır. POSIX
standartlarında her fonksiyon parametrik yapısı, geri dönüş değeri ve prototipinin hangi başlık
dosyasında olduğu bilgileri verilerek ayrıntılı bir biçimde tanımlanmıştır. Şüphesiz, POSIX
uyumlu bir Unix türevi sistem POSIX fonksiyonları dşında o sisteme özgü ek bir takım
fonksiyonlarda bulundurabilir. Tabii C programcısı yazdığı kodun her sistemde problemsiz
derlenmesini istiyorsa POSIX fonksiyonlarını kullanması gerekir. POSIX standardının birinci
cildi yalnızca C programlama dili dikkate alınarak yazılmıştır.
Bazı programcılar aynı anlamda kullanıyor olsa da Unix terminolojisinde sistem fonksiyonu
(system call) kavramı ile API fonksiyonu yada POSIX fonksiyonu kavramı birbirinden
farklıdır. Sistem fonksiyonu işletim sisteminin bir parçası olan, o sisteme özgü aşağı seviyeli
fonksiyondur. Genellikle bu fonksiyonlar prossesin önceliğini curnell moda geçirerek işlem
yaparlar, yani Unix sistemlerinin sistem fonksiyonları birbirinden farklı olabilir. (tüm Linux
dağıtımları aynı çekirdek yapısına sahip olduğu için sistem fonksiyonları da aynıdır.) POSIX
API fonksiyonları birer sarma fonksiyondur, yani parametre olarak standartlardır ama kendi
içlerinde standart olmayan sistem fonksiyonlarını çağırırlar.
POSIX 1003.1 e göre POSIX tamamen C90 nın standart kütüphanesini desteklemektedir.
Yani her standart C fonksiyonu bir POSIX fonksiyonudur ama her POSIX fonksiyonu bir
standart C fonksiyonu değildir.
Sayfa 6 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 7 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Bir program dosyasının çalıştırılabilmesi için hem dosyanın çalıştırılabilir bir dosya olması,
hemde x hakkına kullanıcının sahip olması gerekir. Unix/Linux sistemlerinde çalışabilen
dosyalar uzantılarıyla belirtilmezler, x haklarıyla tespit edilirler. Örneğin, bir kullanıcı için
onun kendi dosyası şu özelliklere sahip olsun, -rwx - - - - - -. Burada programı yalnızca
program dosyasının sahibi çalıştırabilir. Yükleyici dosyayı yüklemek için önce o kişinin x
hakkına sahip olup olmadığına bakar, eğer x hakkı belirtilmemişse dosyayı zaten yüklemez.
Örneğin, çalışabilen bir dosyanın özellikleri dışarıdan değiştirilerek x hakları değiştirilmiş
olsun. bu dosyayı ne süper kullanıcı ne de diğer kullanıcılar çalıştıramaz.
Anahtar Notlar:
Shell üzerinde ID isimli program o anda çalışmakta olan shell programının kullanıcı ve grup
ID lerini vermektedir. Programcı shell üzerinden bir program çalıştırdığında aslında shell
prosesi çalıştırmış olur. Yani çalıştırılan programın kullanıcı ve grup ID si shell programının
kullanıcı ve grup ID si olur.
Anahtar Notlar:
Bir kullanıcı yaratıldığında geleneksel olarak o kullanıcıya ilişkin home dizini altında bir
dizin yaratılır.
Sayfa 8 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Unix/Linux sistemlerinde dos/windows taki gibi path kavramı vardır.ancak dos/windows
sistemlerinde o anda bulunulan dizin otomatik olarak path directory sine dahil
edilmektedir.ancak Unix/Linux sistemlerinde geleneksel olarak bulunulan dizin default olarak
path ifadesine dahil kabul edilmemektedir. Ayrıca path ifadesine . ile bulunulan dizininde
eklenmesi gerekir.unix/Linux sistemlerinde . kökten itibaren mutlak bir dizin yeri
balirtmektedir. Bu durumda o anda bulunulan dizine path yoksa bir program aşağıdaki gibi
çalıştırılabilir. ./a.out
-o seçeneğiyle eski dosya isimlendirilebilir, -c yalnızca derle ama bağlama işlemini yapma
anlamına gelir. –c seçeneğinde amaç kod .o uzantılı yaratılmaktadır. Komut satırında birden
fazla kaynak dosya ismi verilebilir.
Anahtar Notlar:
Kursumuzda her ne kadar arada bir fark olsa da sistem fonksiyonu kavramı ile POSIX API
fonksiyonları aynı anlamda kullanılacaktır.
Sayfa 9 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Unix/Linux sistemlerinde sistem fonksiyonlarının büyük çoğunluğunun prototipleri
<unistd.h> dosyası içindedir.
Anahtar Notlar:
Bazı programcılar sistem fonksiyonunun başarısızlığını –1 değeriyle karşılaştırma yaparak
değil, <0 biçiminde sorgulamaktadır. Örneğin,
if(open(....) < 0){
}
Genel olarak –1 le karşılaştırma yerine <0 karşılaştırması daha etkin bir makine kodu
üretilmesine yol açmaktadır. Tabii bunun gerçek uygulamalarda ciddi bir etkisi yoktur, ancak
geleneksel olarak böyle bir eğilim vardır.
Fonksiyonların hata nedenlerinin errno değişkenine bakarak yazdırılması zor bir uğraştır.
Bunun için perror fonksiyonu kullanılmaktadır. Bu fonksiyon errno global değişkeninin
içerisindeki değere bakarak uygun bir yazıyı stderr dosyasına yazdırmaktadır.
void perror (const char *s);
perror aslında standart bir C fonksiyonudur ve prototipi stdio.h içerisindedir. Perror
fonksiyonu önce parametresiyle belirtilen yazıyı, sonra : errno değişkeninin içerisindeki
değere ilişkin yazıyı yazdırır. Örneğin, open : permision denied çağırılması için böyle bir
yazı yazdırılabilir. Bu durumda örneğin bir dosya açma işleminin başarısı aşağıdaki gibi tespit
edilebilir.
int fd;
if ((fd = open(“xxxx”, O_RDWR)) < 0) {
perror(“open”);
exit(EXIT_FAILURE);
}
Ayrıca strerror isimli bir standart C fonksiyonu da vardır. Bu fonksiyon parametresiyle
verilen bir hata numarasına karşılık gelen yazıyı elde etmekte kullanılır.
char *strerror(int errnum);
Sayfa 10 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 11 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Windows sistemlerinde tipik olarak macar notasyonu kullanılmaktadır. Macar notasyonunda
tür isimleri tüm harfleri büyük değişkenlerle typedef edilmiştir. Halbuki C de çeşitli typedef
isimleri t son ekiyle oluşturulmuştur.(örneğin size_t time_t) POSIX standartları doğal olarak
C nin isimlendirme sistemini tercih etmiştir.
Benzer biçimde bir dosya açıldığında işletim sistemi dosya işlemlerini gerçekleştirmek için
yine bir handle alanı oluşturmaktadır. Örneğin, Win32 sistemlerinde bu handle alanına dosya
tablosu denilmektedir ve handle değeri HANDLE türüyle temsil edilmektedir. Unix/Linux
sistemlerinde handle alnını gösteren handle değeri int türden bir bilgidir.
open FONKSİYONU:
unix/Linux sistemlerinde tüm dosyalaropen sistem fonksiyonuyla açılmaktadır. Fonksiyonun
prototipi <fcntl.h> dosyası içindedir.
int open(const char *path, int oflag, ...);
Anahtar Notlar:
C de ve C++ da fonksiyon prototipinde yada tanımlamasında ...(ellipses) fonksiyonun
değişken sayıda parametre aldığını göstermektedir. Değişken sayıda parametre alan
fonksiyonların en az belirlenmiş bir parametreye sahip olması gerekir.
Anahtar Notlar:
Geçerli dizin (current directory yada working directory) path ifadesi / ile başlatılmadığında
yazılmış olan göreli path ifadesinin nereden başlatılacağını belirten bir kavramdır. Her
prosesin yaratıldığında bir geçerli dizini vardır, prosesin geçerli dizini proses dizininde
saklanmaktadır. Aslında bir dizin içerisinde bulunma diye bir kavram yoktur, fakat shell
programları bir prompt eşliğinde prosesin geçerli dizinini gösterdikleri için sanki bir dizinin
içerisinde bulunma duygusu yaratmaktadırlar. Örneğin, shell üzerinde cd komutu
uygulandığında aslında yalnızca prosesin geçerli dizini değiştirilmiş olur. Prosesin geçerli
dizini prosesin handle alanında tutulmaktadır. Bir proses başka bir prosesi çalıştırdığında
çalıştırılan proses(child process) çalıştıran proses(parent process) geçerli dizinin almaktadır.
Örneğin biz shell üzerinden bir program çalıştırdığımızda çalıştırılan programın geçerli
dizini shell programının geçerli dizini olacaktır. Kullanıcı sisteme login olduğunda program
ve o programın geçerli dizini /etc/passwd dosyasında bulunmaktadır.
open fonksiyonunun birinci parametresi açılacak dosyanın ismine ilişkin path ifadesidir. Eğer
path ifadesi göreli ise bu durumda dosya prosesin geçerli dizininde aranır. Path mutlak ise
dosya belirtilen dizininde aranır.
open fonksiyonunun ikinci parametresi dosya açış modudur. Dosya açış modları tüm bitleri 0
yalnızca bir biti 1 olan sembolik sabitler biçiminde fcntl.h dosyası içinde bildiril.miştir. Şunlar
olabilir; O_RDONLY, O_WRONLY, O_RDWR, O_CREAT, O_EXCL, O_TRUNC,
O_APPEND
Dosya açılırken okuma yazma işlemlerinin hangisinin yapılacağı O_RDONLY, O_WRONLY
bayraklarından yalnızca bir tanesi belirtilerek yapılır. Ayrıca bu bayraklardan en az birinin
Sayfa 12 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
kesinlikle belirtilmesi gerekir. Normal olarak dosya yoksa açış işlemi başarısız olur. Eğer
O_CREAT bayrağıda moda eklenirse, dosya yoksa fonksiyon başarısız olmaz, dosyayı
yaratır. Yani O_CREAT dosya varsa “onu aç ama yoksa yarat” anlamına gelmektedir.
O_TRUNC bayrağı O_CREAT ile birlikte kullanılmak zorundadır. O_CREAT | O_TRUNC
“dosya varsa bile onu silerek sıfırla ve aç” anlamına gelir. O_EXCL seçeneği de O_CREAT
ile kullanılmak zorundadır. O_CREAT | O_EXCL “dosya yoksa yarat ama varsa hata vererek
çık” anlamına gelir. O_APPEND dosyanın herhangibir yerinden okumaya izin veriri ama
yazma işleminin her zaman dosyanın sonuna yapılmasını sağlar. Yani yazma işlemi sırasında
sistem dosya göstericisini otomatik dosyanın sonuna çekerek yazma yapmaktadır. fopen
standart C fonksiyonundaki açış modunun open fonksiyonundaki karşılıkları şöyledir.
fopen Unix open
“r ” O_RDONLY
“r+” O_RDWR
“w” O_WRONLY | O_CREAT | O_TRUNC
“w+” O_RDWR | O_CREAT | O_TRUNC
“a” O_WRONLY | O_CREAT | O_APPEND
“a+” O_RDWR | O_CREAT | O_APPEND
open fonksiyonu sentaks olarak değişken parametre alan bir fonksiyondur. Fakat bu
fonksiyona ya iki yada üç parametre girilmelidir. Üçüncü parametre dosyanın erişim
haklarıdır ve O_CREAT seçeneği belirlenmişse yani dosya yaratılacaksa kullanılmaktadır.
(tabii O_CREAT bayrağının belirtilmiş olması üçüncü parametrenin kesinlikle kullanılacağı
anlamına gelmez.) Görüldüğü gibi dosyanın erişim haklarını dosyayı yaratan kişi
belirlemektedir.
Fonksiyonun üçüncü parametresi POSIX standartlarına göre aşağıdaki sembolik sabitlerin
bitor işlemine sokulmasıyla oluşturulmak zorundadır.
S_IRUSR,
S_IWUSR,
S_IXUSR
S_IRGRP
S_IWGRP
S_IXGRP
S_IROTH
S_IWOTH
S_IXOTH
Bu bayraklar genel olarak şöyle oluşturulmuştur. Her ne kadar POSIX standartlarında bu
sembolik sabitlerin kullanılması öneriliyorsa da Unix/Linux sistemlerinin hemen hepsinde bu
parametrenin oktal bir sayı olarak girilmesi de kabul edilmektedir. Örneğin;
S_IRUSR | S_IWUSR | S_IRGRP | S_WGRP | S_IROTH | S_IWOTH
Sayfa 13 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
ile
0666 eşdeğerdir.
Burada belirtilen erişim hakları prosesin umask değeriyle işleme sokulmaktadır. Umask
kavramı sonraki bölümde ele alınmaktadır.
Open fonksiyonu başarılı olursa dosya betimleyicisi (file descriptor) denilen int türden bir
handle değriyle geri döner. Fonksiyon başarısızlık durumunda –1 değerine geri dönerek errno
değişkenini set etmektedir. Fonksiyonun prototipi, mod bayraklarına ilişkin sembolik sabitler
<fcntl.h> içerisindedir. Örneğin;
if((fd = open(“a.dat”, O_RDWR | O_CREAT, S_IRUSR | S_IWUSR)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
POSIX standartları erişim haklarına ilişkin sembolik sabitlerin <sys/stat.h> dosyası içerisinde
bulunacağını belirtmiştir. Fakat ayrıca bu sembolik sabitlerin fcntl.h içerisinde de
tanımlanabilmesine izin vermiştir. Yani open fonksiyonu için POSIX standartlarına göre
<fcntl.h> ve <sys/stat.h> ı include etmek gerekir.
O_TRUNC bayrağı dosyanın dizindeki inode bilgilerini değiştirmez, yalnızca dosyanın içini
sıfırlar.(yani O_CREAT | O_TRUNC ile yeni bir dizin girişi yaratılmamakta eski giriş
kullanılmakta)
close FONKSİYONU
close fonksiyonu dosyayı kapatmaktadır.
int close (int fd);
fonksiyon parametre olarak açılmış olan dosyanın betimleyici değerini alır ve dosyayı kapatır.
Başarılıysa 0 değerine, başarısızsa –1 değerine geri dönerek errno değişkenini set etmektedir.
Proses normal olarak sonlandırıldığında (_exit fonksiyonuyla) bütün açılmış olan dosyalar
işletim sistemi tarafından kapatılmaktadır.
creat FONKSİYONU
Eskiden Unix sistemlerinde open fonksiyonu yalnızca olan dosyaları açabiliyordu. Fakat
sonra POSIX standartlarıyla birlikte open fonksiyonu bugünkü haline getirilmiştir. Bu yüzden
eskiden bir dosyayı ilk kez yaratmak için creat fonksiyonu kullanılırdı. Bugün bu fonksiyon
işlevsiz duruma gelmiştir, fakat geçmişe doğru uyumu korumak için POSIX standartlarında
muhafaza edilmiştir. Fonksiyon prototipi şöyledir.
int creat(const char *pathname, mode_t mode);
Fonksiyonun birinci parametresi yaratılacak dosyanın ismi, ikinci parametresi açış modudur.
mode_t türü <sys/types.h> dosyası içerisinde typedef edilmiştir. Bu tür Linux sistemlerinde
unsigned int biçiminde alınmıştır. Aşağıdaki iki çağırma tamamen eşdeğerdir.
Sayfa 14 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
fd = creat(fname, mode);
fd = open(fname, O_WRONLY | O_CREAT | O_TRUNC, mode);
Görüldüğü gibi creat fonksiyonu dosyayı yalnızca yazılabilir modda açmaktadır.
Anahtar Notlar:
sys / types.h dosyası sistem fonksiyonlarının kullandığı ve POSIX tarafından belirlenen t son
ekiyle biten çeşitli typedef isimlerini barındırmaktadır. Örneğin, prosesin kullanıcı ID si uid_t
türüyle, grup ID si değeri gid_t değeriyle, bir dosyanın erişim haklarını belirten sayı mode_t
ile belirtilmektedir.
Anahtar Notlar:
POSIX in t ile biten bütün tür isimleri<sys/types.h> dosyası içinde typedef edilmiştir.
Standart C nin t ile biten türleri ise temel olarak <stddef.h> dosyası içinde typedef edilmiştir.
Fakat size_t gibi bazı türler <stddef.h> dışında da başka başlık dosyalarında da typedef
edilmiştir. (size_t ayrıca <sys/types.h> içinde de typedef edilmiştir.)
ssize_t işaretli bir tam sayı türü olmak zorundadır ve tipik olarak sistemlerde signed int
biçimindedir. size_t ise işaretsiz bir tamsayı türü olmak zorundadır ve tipik olarak sistemlerde
unsigned int biçimindedir. Eskiden read ve write fonksiyonlarının geri dönüş değerleri int
biçimindeydi. Fonksiyonların prototipleri <unistd.h> içindedir. Fonksiyonların birinci
parametresi dosya betimleyicisi, iknci parametreler transfer adresi, üçüncü parametreleri ise
okunacak yada yazılacak byte sayılarıdır. Fonksiyonlar hiç okuma yada yazma yapamazlarsa
0 değerine başarısız olurlarsa –1 değerine geri dönerler. read fonksiyonunun 0 a geri dönmesi
tipik olarak dosya sonuna gelinmesi yüzünden olur. Bu durum bir okuma hatası olarak ele
alınmaz.
Anahtar Notlar:
Tipik olarakUnix/Linux sistemlerinde (aslında diğer sistemlerde de) bir sistem fonksiyonunu
çağırmanın normal bir fonksiyonu çağırmaktan daha fazla bir zamansal maaliyeti vardır, bu
maaliyetin nedenleri çeşitlidir.
Unix/Linux sistemlerinde bir dosyayı koyalamanın tek yolu DOS ta olduğu gibi iki dosyayı
da open fonksiyonuyla açmak, read ve write fonksiyonlarıyla blok blok alarak aktarım
yapmaktır. Blok büyüklüğü belirli bir noktaya kadar kopyalama zamanını azaltmaktadır. İdeal
blok büyüklüğü stat fonksiyonuyla elde edilebilir. Tipik kopyalama fonksiyonu şöyle
yazılabilir.
int fs, fd;
#define BUFSIZE 8192
Sayfa 15 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
exit(EXIT_FAILURE);
}
if((fs = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, S_IRUSR | S_IWUSR |
S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
while((n = read(fs, buf, BUFSIZE)) > 0)
if(write(fd, buf, n) != n) {
perror(“write”);
exit(EXIT_FAILURE);
}
if(n < 0){
perror(“write”);
exit(EXIT_FAILURE);
}
prinf(“1 file copied...\n”);
close(fs);
close(fd);
return 0;
}
lseek FONKSİYONU
Bu fonksiyon dosya göstericisini konumlandırmak için kullanılmaktadır.
off_t lseek(int fd, off_t offset, int whence);
off_t dosyanın offset numarasını temsil eden tipik olarak long türden bir typedef ismidir.
Fonksiyonun birinci parametresi dosya betimleyicisi, ikinci parametresi konumlandırılacak
offset ,üçüncü parametresi ise konumlandırma orijinidir. Bu orijin şunlardan biri olabilir.
SEEK_SET, SEEK_CUR, SEEK_END. Fonksiyonun prototipi <unistd.h> içerisindedir. off_t
türü <sys/types.h> içerisinde, SEEK_SET, SEEK_CUR, SEEK_END sembolik sabitleri
<unistd.h> içerisindedir. lseek fonksiyonu başarı durumunda konumlandırılmış olan offset
numarasına başarısızlık durumunda –1 değerine geri dönmektedir. (verilen offset numarası
her zaman dosyanın başından itibarendir.) Tabii bu fonksiyonun geri dönüş değerinin çoğu
kez kontrol etmek gerekmemektedir. Bazı sistemlerde ve bazı aygıtlarda negatif offset
kavramı tanımlı olduğu için bu fonksiyonun başarısızlığı test edilecekse aşağıdaki kalıp
kullanılmalıdır.
if(lseek(fd, offset, SEEK_SET) == -1){
}
Dosya Delikleri
lseek fonksiyonu ile dosya göstericisi (EOF) dosyanın uzunluğundan daha öteye
konumlandırılabilir. Bu durumda dosyada bir delik oluşur, delikten okuma yapıldığında her
zaman 0 okunmaktadır. Dosyadaki tüm delikler sistem tarafından bir tabloda saklanmaktadır.
Delik alanları için başlangıçta bir disk tahsisatı yapılmaz. read fonksiyonu okuma yapılan
alanın delik olduğunu anladığı zaman 0 değeriyle geri dönmektedir, yani delikten okuma
yapılırsa hep 0 okunur. Deliğe yazma yapıldığında yalnızca delik için gerekli blok gerçek
anlamda tahsis edilir.
Sayfa 16 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
lseek fonksiyonu yalnızca dosya göstericisinin durumunu güncellemektedir. Dolayısıyla dosya
deliklerini oluşturabilmek için konumlandırma işleminden sonra bir write işlemi yapılmalıdır.
Anahtar Notlar:
od isimli bazı Unix/Linux sistemlerinde bulunan program bir dosyayı çeşitli formatlarda
görüntülemekte kullanılmaktadır. Tipik olarak birer byte görüntü için od -tx1 dosya isimi
biçimi kullanılmaktadır. –v eklenirse kesintisiz olarak * kullanmadan görüntü verir.
Sayfa 17 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Tipik olarak bir prosesin açabilecegi dosya sayisi diye bir kavram sözkonusudur. Örnegin
open fonksiyonu tipik olarak dosya betimleyici tablosunda bos bir giris arastirmaktadir. Open
fonksiyonunun bos ve en küçük numarali dosya betimleyicisini verecegi standartlarda
garantilenmistir. Tablo pekçok sistemde sinirli bir büyüklüktedir ve boyutu
degistirilememektedir. Örnegin eski Unix sistemlerinde dosya 14 elemanliydi. Daha sonra bu
sayi çesitli sistemlerde 64, 128 hatta 1024 gibi degerlere yükseltilmistir. Örnegin Unix
sisteminde tipik olarak dosya betimleyici tablosunun bastan tüm elemanlari NULL adres
biçimindedir. Sonra open fonksiyonu NULL olan ilk bos betimleyici index ini elde eder, close
fonksiyonu da dosyayi kapattiktan sonra bu elemana NULL degerini yazmaktadir. C' de
ekrana yazan standart C fonksiyonlari aslinda stdout dosyasina yazarlar. Bu fonksiyonlari
yazanlar write fonksiyonu ile 1 numarali betimleyiciyi kullanmislardir. Benzer biçimde
klavyeden okuma yapan fonksiyonlar read fonksiyonunu 0 numarali betimleyici ile
kullanirlar.
int main(void)
{
if ((fd = open("data", O_CREAT | O_RDONLY)) < 0){
perror("open");
exit(EXIT_FAILURE)
}
printf(“ %d", fd);
}
close(7);
close(14);
if ((fd = open("data", O_CREAT | O_RDONLY)) < 0){
perror("open");
exit(EXIT_FAILURE)
}
printf("ilk bos yer: %d", fd);
}
return 0;
}
Kullandigimiz Unix çekirdeklerinde dosya betimleyici tablosunun uzunlugu 1024 elemanlidir,
yani bu sistemlerde biz proses basina bu syaidan daha fazla dosyayi açik tutamayiz.
Ilk 3 betimleyici için dosya otomatik olarak açilmis durumdadir. Böylece biz bir Unix/Linux
programinda dogrudan ekrana söyle yazdirma yapabiliriz.
write(1, "Deniz Kurumoglu\n", 16);
Read fonksiyonu ile 0 numarali betimleyiciden yani stdin dosyasindan okuma yapabiliriz. Bu
durumda sistem bir satirlik bilginin tamamini klavyeden alig onu bir tampona yazar istenilen
Sayfa 18 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
kadar miktari tampondan verir. Tamponda karakter oldugu sürece tampondan verir, tamponda
karakter kalmayinca yeniden bir satirlik bilgi ister.
int main(void)
{
char s[100];
int n;
n = read(0, s, 10);
s[n] = '\0';
puts(s);
return 0;
}
POSIX standartlarina göre stdin, stdout ve stderr dosyalarinin betimleyici numaralari 0, 1, 2
olmak zorunda degildir. Fakat bütün Unix/Linux sistemlerinde durum böyledir. POSIX
standartlari <unistd.h> dosyasi içerisinde derleyiciyi yazanlarin bu betimleyici numaralarini
STDIN_FILENO, STDOUT_FILENO ve STDERR_FILENO biçiminde define etmelerini
zorunlu tutmustur. Yani biz programimizda ekrana birseyler yazdirmak için 1 numarali
betimleyici yerine STDOUT_FILENO sembolik sabitini kullanirsak kodumuz daha tasinabilir
olacaktir, fakat bu durum programcilar tarafindan pek ciddiye almazlar.
stdin dosyasindan okuma yapan standart Unix fonksiyonlari Unix/Linux sistemlerinde aslinda
read fonksiyonunu kullanarak 0 numarali betimleyicide islemlerini yaparlar. Benzer biçimde
stdout dosyasina yazma yapan standart fonksiyonlar ise en sonunda write fonksiyonuyla 1
numarali betimleyici kullanmaktadirlar. Bu durumda biz 0 numarali betimleyiciye iliskin
dosyayi kapatip sonra open fonksiyonu ile bir dosya açarsak open fonksiyonu bize yine 0
numarali betimleyiciyi verecektir. Bu durumda standart C fonksiyonlari ile okuma
yaptigimizda dosyadan okuma yapmis oluruz. Örnegin;
int main(void)
{
int fd;
close(0);
if((fd = open("a.dat", O_RDONLY)) < 0){
perror("open");
exit(EXIT_FAILURE);
}
scanf("%d", &n); /*a.dat dosyasindan okunuyor*/
printf("%d\n", n);
close(fd);
return 0;
}
Sayfa 19 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
a.dat
a.dat pRead
pWrite
Şimdi böyle bir yapıda bir betimleyici numarasıyla read fonksiyonun çağırıldığını düşünelim;
read(fd,...);
read fonksiyonu sırasıyla şunları yapacaktır;
1. O anda çalışmakta olan processin process tablosunu bulur ve dosya betimleyici
tablosunun adresini elde eder.
2. Dosya betimleyici tablosundan fd numaralı elemanı çekerek dosya nesnesinin
adresini elde eder.
Sayfa 20 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
stdout
pWrite
Dosya Betimleme
Tablosu Dosya Nesnesi
fd
process id pDesTable ... pFileOps
fdnew
... ...
...
fdnew = dup(fd);
Sayfa 21 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
dup işleminden sonra iki betimleyici aynı dosya nesnesini gördüğü için bu iki betimleyici
arasında fark kalmaz. Örneğin, dosya betimleyicisinin konumu dosya nesnesinde saklandığına
göre, bir betimleyici kullanılarak yapılmış lseek işlemi diğerini etkileyecektir.
Normal olarak close işlemi dosya nesnesini silmektedir. Aslında işletim sistemi dosya
nesnesinin içerisinde bir sayaç tutar. Bu sayaç o dosya nesnesinin kaç betimleyici tarafından
referans edildiğini gösterir. Dosya open ile ilk kez açıldığında ve dosya nesnesi yaratıldığında
sayaç 1’dir. dup işlemi yapıldığında, sayaç bir artırılır. close fonksiyonu da bu referans
sayacını önce bir azaltmalıdır. Sayaç 0’a düşünce dosya nesnesini gerçekte siler.
Amacımız 0 nolu betimleyici olan stdin dosyasını bizim açtığımız bir dosyaya yönlendirmek
olsun. Yani gets ya da scanf gibi fonksiyonlarla okuma yaptığımızda klavyeden değil de
belirttiğimiz dosyadan okuma işleminin yapılmasını isteyelim. bu işlemi aşağıdaki gibi
yapabiliriz.
if ((fd = open(”x.dat”, O_RDONLY) < 0) {
perror(“open”)
exit(EXIT_FAILURE);
}
close (STDIN_FILENO);
dup(fd);
Bu tür yönlendirme işlemlerine unix sistemlerinde çok sık rastlanmaktadır. İki işlem arasında
araya bir başka thread ya da sinyal mekanizması girebiliyor ise bu işlemlere atomik olmayan
işlemler denir.
Örneğin:
close (STDIN_FILENO);
dup(fd);
Bu iki işlem atomik değildir. Yani, tam close işlemi bittiğinde bir threadler arası geçiş olursa
ya da bir sinyal gelse o kod da bir dosya açılsa, 0 numaralı betimleyiciyi biz alamayız. İşte iki
işlem arasında araya hiçbir akışın giremeyeceğinin garanti edildiği olaylara atomik olaylar
denilmektedir. close ve dup işlemlerinin atomik biçimi dup2 fonksiyonu tarafından
yapılmaktadır.
int dup2(int fd1, int fd2);
Fonksiyonun prototipi unistd.h dosyası içerisindedir. Bu fonksiyon önce, fd2 ile belirtilen
dosya betimleyicisini close fonksiyonuyla kapatır, sonra ilk boş betimleyiciye bakmaksızın
fd2 ile belirtilen betimleyicinin fd1 ile aynı dosya nesnesini göstermesini sağlar. fd2 değeriyle
geri döner. Hem dup fonksiyonu hem de dup2 fonksiyonu başarısızlık durumunda -1 değerine
geri dönmektedir. dup2 fonksiyonu, ilk boş betimleyiciye bakmadan her zaman fd2
betimleyicisini geri vermektedir.
Örneğin:
Sayfa 22 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Dosya Betimleme
Tablosu Dosya Nesnesi
fd1
process id pDesTable ... pFileOps
fd2
... ...
...
Sayfa 23 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Donanım birimlerine doğrudan erişilebilecek yani ring 0’da çalışabilecek kod yazmak özel bir
programlama modeli kullanılmaktadır. Bu tür programlara aygıt sürücüler denilmektedir.
İşletim sistemlerinde zaten işletim sisteminin kendisinin de kullanıdığı çeşitli aygıt sürücüler
vardır. Tipik bir aygıt sürücü tıpkı bir dosya gibi unix/linux sistemlerinde open fonskiyonu ile
win32 sistemlerinde CreateFile fonksiyonuyla açılır.Aygıt sürücü aslında okuma yazma
işlemlerini destekleyen ve çeşitli fonksiyonların ring 0 seviyesinde çağırılmasına izin veren,
bir kod grubudur. Aygıt sürücüyü kullanacak kişi sürücü içerisindeki fonksiyonların ne iş
yaptıklarını ve bunların numaralarını bilmek zorundadır. Aygıt sürücülerdeki kodu
çalıştırabilmek için unix/linux sistemlerinde ioctl, win32 sistemlerinde DeviceIOControl
fonksiyonları kullanılır.
Yine aygıt sürücü programlar, veri transferine izin vermektedir. Veri transferi sanki aygıt
sürücü bir dosyaymış gibi dosya fonksiyonlarıyla yapılmaktadır. Örneğin unix/linux
sistemlerinde read ve write fonksiyonları; win32 sistemlerinde ReadFile ve WriteFile
fonksiyonları bu işlemler için kullanılabilir. En sonunda aygıt sürücüler, dosya kapatan
fonksiyonlarla kapatılırlar.
Madem ki, aygıt sürücüler sanki birer dosyaymış gibi açılıp kullanılıyorlar, o halde bunları
temsil eden bir dosyanın olması gerekmez mi? İşte, unix/linux sistemlerinde /dev dizininin
altında aygıtları temsil eden standart aygıt sürücü dosyaları bulunmaktadır.
pDesTa Fd1
ble Fd2
Birden fazla processin aynı kayıt üzerinde işlem yapması durumunda bir eş zamanlılık
mekanizmasının kurulması gerekmektedir. Örneğin, bir process bir veri dosyasına aşağıdaki
gibi bir kaydı yazıyor olsun. genel olarak POSIX sistemlerinde böyle bir işlemin atomik
yapılacağının bir garantisi yoktur, kaldı ki bir kayıt birden fazla write fonksiyonu ile
güncelleniyor olabilir. İşletim sistemlerinin hemen hepsinde bir kayıt kilitleme mekanizması
vardır. kayıt kilitleme işleminde dosyanın iki offseti arası okuma yazma işlemi için
kilitlenebilir. Böylece bu işlem bitene kadar o offsetler arasındaki bilgiler için kimse işlem
yapamaz. Veri tabanı programları işlemleri bu biçimde yapmaktadır. Kayıt kilitleme
mekanizması ileride ele alınacaktır.
Sayfa 24 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
BootBlock
SuperBlock
i-nodeBlock
data
Ardışıl sektörlere blok denilmektedir. BootBlock boot sektörü içeren bir yada bir den fazla
sektörden oluşur. İşletim sisteminin yüklenmesi boot sektörden haraketle yapılmaktadır.
SuperBlock dosya sistemine ilişkin temel bilgileri tutan sektörlerden oluşmaktadır.
SuperBlock tipik olarak Dos taki BPB tablosuna benzemektedir.
i-node dosya sistemlerinde de bir dosyanın parçaları disk üzerinde rasgele yerlerde olabilir.
Bilindiği gibi Windows sistemlerinin dosya sisteminde dosyanın parçalarının nerelerde
olduğu FAT bölümünde tutulmaktadır. Oysa i-node dosya sistemlerinde böyle bir FAT
mekanizması yoktur. i-node dosya sisteminde bir dosyanın parçası olabilecek en küçük
sektör topluluğuna blok denilmektedir, halbuki FAT dosya sisteminde bu kavram cluster
ismiyle bilinir.
i-nodeBlock i-node elemanlarından oluşmaktadır. Bir i-node elemanı bir yapıyla temsil
edilebilecek biçimde bir kayıttır. Bir i-node elemanının kaç byte uzunlukta olduğu Unix
varyasyonlarında değişebilmektedir. Dosya sisteminde her dosyaya ilişkin i-node Block ta bir
i-node elemanı vardır. disk formatlandığında i-node block taki i-node elemanları boştur. Sonra
dosyalar yaratıldıkça bu blokta i-node elemanları tahsis edilir. İ-nodeBlock
büyütülemediğinden buradaki i-node elemanlarının sayısı sınırlıdır. Yani diskte uzunluğu ne
olursa olsun i-node elemanlarının sayısından daha fazla dosya bulunamaz.
i-node elemanı bir dosyayla ilgili isminin dışında her türlü bilgiyi tutmaktadır. Örneğin,
dosyanın kullanıcı ID’ si, groupID’ si, erişim hakları, dosyanın diskte hangi blokta olduğu
gibi bilgileri tutmaktadır. Dosya açıldığında dosyanın bu bilgileri diskteki i-node elemanından
alınarak dosya nesnesinin içerisine çekilmektedir, yani diskteki i-node elemanı adeta belleğe
çekilmektedir. i-nodeBlock’ ta her i-node elemanına 0 dan başlayarak bir sıra numarası
verilmiştir. Buna ilgili dosyanın i-node numarası denilmektedir. i-node numarası sistem
genelinde tektir.
Anahtar Notlar:
ls –i komutuyla dosyanın i-node numarası görüntülenebilir. –l bu bilgiyi vermemektedir, –l i
biçiminde birleştirilebilir.
Sayfa 25 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Dosya ismi dosyanın odizindeki ismini, i-node numarası ise dosya bilgilerinin kaçıncı i-node ‘
da olduğunu belirtmektedir. 0. i-node elemanı root dizinine ilişkindir.
open fonksiyonu ve daha pekçok sistem fonksiyonu bir path ifadesi aldığında bunu dosyanın
i-node numarasına dönüştürüp, dosyanın i-node bilgilerini diskten almak isterler. Bu işleme
Unix/Linux çekirdek geliştirme terminolojisinde pathname resolution denilmektedir.
Örneğin, /a/b/c.dat biçiminde işlem yapılacak olsun, sistem fonksiyonunun ilk amacı c.dat
dosyasına ilişkin i-node bigilerini ele geçirip belleğe işlemektir. Bu işlem en basit olarak şöyle
yapılabilir:
1. Sistem 0 numaralı i-node elemanına giderek root dizininin hangi bloklarda
olduğuna bakar ve onun içeriğini elde eder.
2. Root dizinini dizin elemanları içerisinde a’ yı bulur ve onun i-node elemanını elde
eder.
3. a bir dizindir ama onunda i-node elemanı vardır. a dizininin içerisinde b dosyasına
ilişkin i-node elemanını b nin içerisinde de c.dat dosyasına ilişkin i-node elemanını bulur.
Görüldüğü gibi algoritma recursive karakterlidir.
Eskiden Unix işlemlerinde pathname resolution işlemiyukarıda anlatıldığı gibi adım adım
diske başvurularak yapılıyordu, fakat daha sonraları bir cache sistemi oluşturulma yoluna
gidilmiştir. Yani son okunan dizin girişleri bir cache’ ta tutulur, mümkün olduğu kadar path’
te belirtilen dosya ismine kadar aramalar cache’ te yapılır. Ancak cache te yoksa diske
başvurulmaktadır. Linux’ te bu cache sistemine “dentry cache” denilmektedir.(csd işletim
sistemi geliştirme projesinde Linux takine benzer bir cache sistemi kullanılmaktadır.)
Sayfa 26 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
ino_t st_ino;
mode_t st_mode;
nlink_t st_nlink;
uid_t st_uid;
gid_t st_gid;
dev_t st_rdev;
off_t st_size;
blksize_t st_blksize;
blkcnt_t st_blocks;
time_t st_atime;
time_t st_mtime;
time_t st_ctime;
};
Buradaki _t son ekli typedef isimleri <sys/types.h> dosyası içerisinde bildirilmiştir.
Elemanların anlamları şöyledir;
dev_t st_dev: Dosyanın ilişkin olduğu aygıtın numarasıdır. (Bu tür tipik olarak unsigned
short int biçimindedir.)
ino_t st_ino: Dosyanın i-node numarasıdır. (Tipik olarak unsigned long biçimindedir.)
mode_t st_mode: Dosyanın bütün erişim bilgileri buradadır.(Tipik olarak unsigned long int
biçimindedir.)
nlink_t st_nlink: Buarada dosyanın hardlink sayısı tutulmaktadır.(Tipik olarak unsigned
short int biçimindedir.)
uid_t st_uid: Burada dosyanın kullanıcı ID’ si tutulmaktadır. (Tipik olarak unsigned int
biçimindedir.)
gid_t st_gid: Burada dosyanın grup ID’ si tutulmaktadır.( Tipik olarak unsigned int
biçimindedir.)
dev_t st_rdev: Eğer dosya bir aygıt dosyasıysa aygtın türü belirtilmektedir.
off_t st_size: Dosyanın byte cinsinden uzunluğunu belirtir.(Tipik olarak signed long int
biçimindedir.)
blksize_t st_blksize: Bu değer bir bloğun (yani bir cluster’ ın) kaç byte olduğu bilgisini
tutar. Bu değer aynı zamanda bir dosyanın blok blok kopyalanması durumunda performans
bakımından alınması gereken blok büyüklüğünü de belirtmektedir.(Tipik olarak signed long
int biçimindedir.)
blkcnt_t st_blocks: Bu değer dosya için kaç sektörün (yani kaç 512 byte lık disk alanının)
ayrıldığını göstermektedir. Burada sözü edilen blok teknik anlamda sektördür.
Anahtar Notlar:
Bir dosyanın diskte kaç sektörden oluştuğu du komutuyla elde edilebilir.du komutu
parametresiz olarak kullanıldığında recursive olarak tüm dizin ağaçlarının blok uzunluğunu
vermektedir. Parametre olarak dosya ismi verildiğinde dosyanın kaç sektörden oluştuğu
Sayfa 27 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Pekçok kaynağa ve derneğe göre tanımlama(definition) bir çeşit bildirimdir, derleyicinin yer
ayırmadığı bildirimlerdir. Bu kaynaklara göre bildirim denilince yalnızca derleyiciye bilgi
veren fakat bellekte yer ayrılmayan isimlerin tanıtılması söz konusudur. Örneğin, yapı
şablonu, typedef işlemi birer tanımlama değil bildirimdir. Oysa standartlara göre tanımlama
kavramı derleyicinin iki kez gördüğünde hata verdiği, bildirim kavramı ise derleyicinin iki kez
gördüğünde hata vermediği durumlar için kullanılmıştır. Örneğin standartlara göre yapı
şablonunu derleyici iki kez gördüğünde hata verdiği için yapı bildirimi bir tanımlama
işlemidir.işte standartlara göre C standartdına göre typedef bir tanımlama, C++ da
bildirimdir.
LINK KAVRAMI
Bilindiği gibi dizin girişlerinde yalnızca dosyanın ismi ve i-node numarası tutulmaktadır. cp
komutuyla bir dosya kopyalandığında tamamen yeni bir i-node elemanı yaratılır ve dosya
bloklarının da birer kopyasından çıkartılır. Aynı i-node numarasına referans eden dizin
girişleri varsa bu dosyadan aslında fiziksel olarak ve i-node elemanı olarak bir tane vardır. İşte
bu farklı girişlere dosyanın linkleri (hard links) denilmektedir. Bir dosyanın link inden
oluşturmak için cp yerine ln komutu kullanılır. Bir dosyanın link’ inden çıkartıldığında artık
ilk kopyasıyla link i arasında bir fark kalmaz. Bir dosyanın disk üzerinde kaç tane link inin
olduğu i-node elemanında yazmaktadır. Bu değer stat fonksiyonu ile stat yapısının st_nlink
elemanından alınabilir. Bir dosyanın linkinden oluşturulduğunda sistem yeni link için bir
dizin girişi oluşturmakta ve söz konusu dosyanın i-node elemanındaki link sayacını bir
arttırmaktadır. Dosya rm komutuyla sislndiğinde sistem dizin girişini kesinlikle siler, sonra i-
node elemanına başvurur. Buradaki link sayacını bir eksiltir, link sayacı 0’ a düşmüşse
dosyanın diskteki bloklarını ve i-node elemanını siler.
Link kavramı dosyalarla çalışmayı oldukça kolaylaştırmaktadır. Örneğin, programcı başka bir
dizindeki büyük bir dosyanın kendi dizininde bir tane link inden oluşturabilir. Bu dosyayı
path belirtmeden kolaylıkla kullanabilir. Böylece neredeyse hiçbir maaliyet oluuşmadan
işlemlerini kolaylaştırabilir.
link FONKSİYONU
Bilindiği gibi kopyalama işlemi tek bir sistem fonksiyonuyla yapılamamaktadır, fakat link
işlemi link isimli sistem fonksiyonuyla yapılabilir.
Sayfa 28 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
unlink FONKSİYONU
unlink fonksiyonu dosyayı silmek için kullanılan bir POSIX fonksiyonudur. Dosya silmekte
kullanılan remove isimli standart C fonksiyonu Unix/Linux sistemlerinde unlink
fonksiyonunu çağırmaktadır, yani unlink bir POSIX sistem fonksiyonu fakat remove bir
standart C fonksiyonudur. unlink fonksiyonun da prototipi <unistd.h> dosyası içerisindedir.
int unlink(const char *path);
Fonksiyon başarı durumunda 0 değerine başarısızlık durumunda –1 değerine geri
dönmektedir. Bir dosyayı silebilmek için kesinlikle o dosyanın bulunduğu dizine yazma
hakkımızın bulunması gerekir. Ayrıca şu koşullarında sağlanmış olması gerekir.
1. Dosyanın sahibi dosyayı silebilir.
2. Silinecek dosyanın içinde bulunduğu dizin de silecek kişinin dizini olmalıdır.
Tabii root kullanıcısı hiçbir güvenlik engeline takılmadan istediğini yapabilmektedir. unlink
fonksiyonu ile dosyanın birdeb fazla linki varsa modern sistemler dizinin sahibi olmak
koşuluyla ve dizine yazma hakkının olması koşuluyla linkin silinebilmesine izin vermektedir.
Sayfa 29 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 30 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
fileno Fonksiyonu: Standart bir C fonksiyonu değildir, bir POSIX fonksiyonudur. FILE
yapısının içerisindeki dosya betimleyicisinin değerini verir.
Sayfa 31 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
stdin, stdout ve stderr dosyalarının da setvbuf fonksiyonu ile tamponlama modu
değiştirilebilir. Ancak stdin dosyası için özel bir durum söz konusudur, stdin dosyasının
Sayfa 32 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Çok eskiden setvbuf fonksiyonu yoktu ve yalnızca setbuf isimli daha yeteneksiz bir fonksiyon
vardı, setvbuf fonksiyonu setbuf fonksiyonunu işlevsel olarak kapsamaktadır. Bu nedenle
burada ele alınmayacaktır, fakat setbuf fonksiyonu da bir standart C fonksiyonudur.
.....
Sayfa 33 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sistemdeki bütün processler bir üst processin fork yapmasıyla oluşturulmuştur. Örneğin, login
processi init processi tarafından, shell processi login processi tarafından, bizim shell de
çalıştırdığımız program ise shell tarafından yaratılmaktadır.
init
login
shell
myapp
Processin Sonlandırılması
Process sonlandırmakta kullanılan exit fonksiyonu standart bir C fonksiyonudur. exit C’ de
tüm <stdio.h> tamponlarını tazeleyerek dosyaları kapatır. C++’ da exit aynı zamanda static
ömürlü nesnelerin bitiş fonksiyonunu da çağırmaktadır. Bu işlemler standartlarda belirtilen
minimum yapılması gereken işlemlerdir. exit bunun dışında derleyiciye ve işletim sistemine
özgü başka bir takım işlemleri de yapıyor olabilir.
Özetle exit fonksiyonu derleyiciye özgü bir takım işlemlerin geri alınmasını sağladıktan sonra
processi sonlandırmaktadır. Win32 sistemlerindeki processleri sonlandıran API fonksiyonu
ExitProcess API fonksiyonudur. Yani exit standart C fonksiyonu Win32 sistemlerinde geri
alma işlemlerini yaptıktan sonra ExitProcess çağırır. Normal olarak C programcısının exit
fonksiyonu ile processi sonlandırması gerekir, yoksa yukarıda sözü edilen işlemler
yapılmadan process sonladırılmış olur.
ExitProcess API fonksiyonu processlerin tüm threadlerini sonlandırmaktadır. ExitProcess
fonksiyonu dışında Windows kendisi process te çalışan hiçbir thread kalmadığında processi
sonlandırmaktadır.
Bir process başka bir processten zorla TerminateProcess API fonksiyonuyla sonlandırılabilir.
Şüphesiz bir processin rasgele bir zamanda dışarıdan sonlandırılması iyi bir teknik değildir.
BOOL TerminateProcess(HANDLE hProcess, UINT uExitCode);
Fonksiyonun birinci parametresi sonlandırılacak processin handle değeri, ikinci parametresi
çıkış kodudur.
Process1.c
#include <windows.h>
#include <stdio.h>
int main(int argc, char *argv[])
{
int i;
Sayfa 34 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
process2.c
int main(void)
{
STARTUPINFO si = { sizeof(STARTUPINFO) };
PROCESS_INFORMATION pi;
char arg[] = "..\\process1\\debug\\process1.exe ali veli";
DWORD dw;
if (!CreateProcess(NULL, arg,
NULL, NULL, TRUE, 0, NULL, NULL, &si, &pi)) {
fprintf(stderr, "Cannot create process!..\n");
exit(EXIT_FAILURE);
}
getchar();
TerminateProcess(pi.hProcess, 100);
printf("Child process terminated by TerminateProcess\n");
getchar();
{
GetExitCodeProcess(pi.hProcess, &dw);
printf("%ld\n", dw);
}
return 0;
}
//Yukarıdaki teknik iyi bir teknik değildir!
GetCurrentProcess API Fonksiyonu
Win32 sistemlerinde üst processin handle değerini almak gibi bir yöntem yoktur. Çünkü
processler de bir kernel nesnesi olduğuna göre üst processin handle değeri alt processin
process handle tablosuna yazılmadıktan sonra handle göreli bir değer olduğu için bu işlemin
bir anlamı yoktur.
Sayfa 35 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Unix/Linux sistemlerinde processlerin ID değerleri sistem genelinde tek bir değerdir. Yani
Win32 sistemlerinde olduğu gibi göreli bir değer değildir. Bu sistemlerde getppid fonksiyonu
üst processin ID değerini almakta kullanılır.
GetCurrentProcess fonksiyonu aslında 7FFF FFFF biçiminde sahte bir değer vermektedir.
İşletim sistemi o anda çalışmakta olan processi kendi içerisinde zaten tutmaktadır.
GetCurrentProcess görüldüğü gibi process handle tablosunda bulunan bir handle ile değil
tamamen uydurma bir değerle geri dönmektedir. Processin handle değerini isteyen API
fonksiyonlarına bu uydurma değer verildiğinde bu fonksiyonlar o anda çalışmakta olan
processe ilişkin işlem yapıldığını anlamaktadır. Yani, biz GetCurrentProcess fonksiyonundan
elde ettiğimiz handle değerini kullanmak yerine bu uydurma değeri kullanabiliriz. Fakat, bu
uydurma değerin gelecekte aynı biçimde kalması garanti edilmediği için programcı
GetCurrentProcess fonksiyonundan aldığı handle değerini kullanmalıdır.
using namespace std;
struct Sample {
Sample()
{
cout << "Constructor!..\n";
}
~Sample()
{
cout << "Destructor!..\n";
printf("destructor\n");
}
};
Sample g_x;
DWORD WINAPI ThreadProc(PVOID param)
{
for (int i = 0; i < 10; ++i) {
Sleep(1000);
printf("%d\n", i);
}
return 0;
}
int main(void)
{
Sayfa 36 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
HANDLE hThread;
DWORD dwThreadID;
hThread = CreateThread(NULL, 0, ThreadProc, NULL, 0, &dwThreadID);
if (hThread == NULL) {
fprintf(stderr, "Cannot create thread!..\n");
exit(1);
}
TerminateProcess(GetCurrentProcess(), 100);
return 0;
}
DuplicateHandle Fonksiyonu
Bir processin yaratmış olduğu kernel nesnesinin başka bir process tarafından paylaşılması
gerekebilmektedir. Kernel nesneleri handle değerleriyle kullanılabildiğine göre bir kernel
nesnesinin paylaşılabilmesi için farklı processlerde farklı handle değerlerinin aynı kernel
nesnesini görmesi gerekir. Aşağıda hProcess1 ve hProcess2 ile temsil edilen iki processin bir
kernel nesnesini paylaşması şekilsel olarak gösterilmektedir.
HProcess1
Flags
adres
pHand
le
Kernel
nesnes
i
Flags
adres
pHand
le
fork İşlemi
fork sistem fonksiyonu yeni bir process yaratmak için kullanılan yegane fonksiyondur. Bu
fonksiyonun teknik bakımdan iki önemli işlevi vardır.
1. Alt process için yeni bir process handle alanı yani process tablosu oluşturur. Bazı
istisnalar dışında üst processin process tablosunu alt processin tablosuna kopyalar. Böylece
üst ve alt processin neredeyse tüm özellikleri aynı olur. (Örneğin, üst processin kullanıcı ve
group ID leri ve çalışma dizini tamamen alt processe aktarılır.) Tabii alt processin ayrı bir
process tablosu vardır, yani üst processin ID siyle alt processin ID si farklıdır.
Sayfa 37 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
2. Üst processin bellek alanının tamamı alt processin bellek alanına kopyalanır. Yani
alt process de tamamen üst processle aynı koda sahip olacaktır. Bu durumda fork işleminden
sonra üst process de alt process de aynı değişkenlerin içerisinde aynı değerleri görecektir.
Şüphesiz burada yalnızca bir kopyalama yapılmaktadır, fork işleminden sonra iki processin de
bellek alanı içerik olarakk aynı olsada birbirinden ayrılmıştır. Yani fork işleminden sonra üst
process bir değişkenin içerisine bir şey yazdığında bundan alt process etkilenmez.
Fork sistem fonksiyonunda yapılanlar sembolik düzeyde aşağıdaki gibidir.
pid_t fork(void)
{
1. alt process için yeni bir process tablosu oluştur üst processin tablosunu alt
processin tablosuna kopyala
2. alt process için yeni bir bellek alanı oluşturup üst processin bellek alanını alt
processin bellek alanına kopyala
3. yeni bir çizelge alanı oluştur ve başlangıç noktasını CHILD_EXIT yap
return childpid;
/* CHILD_EXIT:
return 0; */
}
fork fonksiyonunun prototipi şöyledir.
pid_t fork(void);
fork üç biçimde geri dönebilir; fork işlemini başaramayabilir, yani alt processi
oluşturamayabilir. Bu durumda başarısız olur ve –1 değeriyle geri döner. fork işlemini
başardıysa artık hem üst process hemde alt process fork işleminden çıkacaktır. Üst process alt
processin ID değeriyle, alt process ise 0 değeriyle geri döner. Bu durumda fork fonksiyonu
tipik olarak aşağıdaki gibi kullanılmalıdır.
pid_t pid;
...
if((pid = fork()) == -1){
perror(“fork\n”);
exit(EXIT_FAILURE);
}
if(pid > 0){
..... üst process
}
else{
..... alt process
}
int main(void)
{
Sayfa 38 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
pid_t pid;
if((pid = fork()) == -1){
perror(“fork\n”);
exit(EXIT_FAILURE);
}
if(pid > 0){
printf(“parent\n”);
}
else{
printf(“child\n”);
}
printf(“continue...”);
return 0;
}
fork işleminden sonra üst processin mi yoksa alt processin mi daha önce çizelgeleneceği
POSIX standartlarında belirtilmemiştir. Bu durum sistemden sisteme değişebilmektedir,
programcı hangisinin önce çizelgeleneceğini varsayarak taşınabilir bir program oluşturamaz.
Soru: Ekrana kaç tane “ok” yazısı çıkar?
int main(void)
{
int i;
for(i = 0; i < 3; ++i)
fork();
printf(“ok!\n”);
}
Program ekrana 8 tane “ok” yazısı çıkarır.(2^n)
Sayfa 39 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
dosya
Dosya bet chi
pDe 0 pde
0
sT 1 sT
1
2 2
Dosya göstericisinin konumu dosya nesnesine saklandığına göre örneğin, üst process dosya
göstericisini lseek fonksiyonuyla konumlandırsa, alt process dosya göstericisini
konumlandırılmış görecektir.
int main(void)
{
int fd;
pid_t pid;
if((fd = open(“data”, O_RDWR)) < 0)
perror(“open”);
exit(EXIT_FAILURE);
}
if((pid = fork()) == -1){
perror(“fork\n”);
exit(EXIT_FAILURE);
}
if(pid > 0){
char ch;
sleep(5);
read(fd, &ch, 1);
putchar(ch);
}
else{
lseek(fd,5, SEEK_SET);
exit(0);
}
Sayfa 40 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
hemde alt process te yapılacağı tehlikeli bir duruma gelinmiş olur. Bu durumda programcı
güvenlik bakımında iki şey yapabilir.
1. fork işleminden önce stdio dosyalarını fclose ile kapatır.
2. fork işleminden önce fflush fonksiyonuyla tamponu tazeler.
Örneğin, gcc derleyicisinde default tamponlama satır tabanlı tamponlamadır. Bu durumda
aşağıdaki kodda ali yazısı bir kez değil iki kez görüntülenir.
printf(“ali”);
fork();
printf(“veli\n”);
Anahtar Notlar:
ps, shell komutu o anda sistemdeki çalışmakta olan processlerin listesini almakta kullanılır.
Ps default olarak o anda çalışmakta olan terminele ilişkin processleri verir. ps –A seçeneği
ile tüm procdessler görüntülenebilir. Liste çeşitli seçeneklerle genişletilebilir, örneğin ps –l
processlere ilişkin daha değerli bilgileri vermektedir. ps –l –A komutundan da görüldüğü gibi
ps programını bash, bash programını login, login programını ise init processini
çalıştırmaktadır. Init processinin process ID si 1 dir init processinin üst processinin ID si 0
dır. 0 numaralı processe swapper yada pager denilmektedir. 0 ID ye sahip bu process
çekirdeği temsil etmektedir. 0 ve 1 numaralı ID ye sahip processler boot sırasında işletim
sistemi tarafından oluşturulmaktadır.
Processin Sonlandırılması
exit bir standart C fonksiyonudur, prototipi <stdlib.h> içerisindedir.
void exit(int exitcode);
Processin kendisi standart exit fonksiyonu tarafından çağırılan sistem fonksiyonu çağırılır,
yani aslında processin sonlandırılması işletim sisteminin sistem fonksiyonu tarafından
yapılmaktadır. Unix/Linux sistemlerinde processi sonlandıran exit fonksiyonu _exit
fonksiyonudur. Win32 sistemlerinde ise aynı iş ExitProcess fonksiyonu tarafından
yapılmaktadır. Standart exit fonksiyonu processi sonlandırmadan önce stdio tamponlarını
tazeleyerek dosyaları kapatmaktadır. C programcısının exit fonksiyonunu çağırması daha
uygundur. Aksi halde tazelenmeyen tamponlar çıkış sırasında otomatik tazelenmez. Default
satır tabanlı tamponlama uyguladığını bildiğimiz gcc derleyicisinde aşağıdaki kodda yazı
ekrana çıkmaz.
int main(void)
{
printf(“Kaan”);
_exit(0);
}
Çünkü _exit tamponları tazlememektedir, _exit yerine exit kullanılsaydı problem
oluşmayacaktı.
Anahtar Notlar:
Standartlara göre main fonksiyonunu parametrik yapısı aşağıdaki iki biçimden bir olmak
zorundadır.
int main(void)
{
...
}
int main(int argc, char *argv[])
{
Sayfa 41 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
...
}
main fonksiyonunda return uygulamak ile exit uygulamak tamamen aynı anlamdadır.
Satndartlara göre main fonksiyonu şöyle çağırılmaktadır.
exit(main(...));
Yine standartlara göre main fonksiyonunda hiç return kullanılmamışsa return 0; yapılmış
kabul edilir.
exit fonksiyonu içerisinde belirtilmiş olan sayıya processin çıkış kodu denilir. Processin çıkış
kodunun kaç olduğunun işletim sistemi için hiçbir özel anlamı yoktur.
Processin çıkış kodu process bittiğinde processin handle alanında yani process tablosunda
saklanır. Processin sonlanmış olması processin handle alanının boşaltılacağı anlamına gelmez.
Genellikle işletim sistemlerinde processin handle alanı processi yaratan process tarafından
sistem fonksiyonuyla silinir. Unix/Linux sistemlerinde processin handle alanını yok etmek
için wait yada waitpid fonksiyonu kullanılır, win32 sistemlerinde processin handle alanı
CloseHandle API fonksiyonuyla yok edilir.
Anahtar Notlar:
Processin handle alanının otomatik sistem tarafından yok edilmesi uygun bir tasarım değildir.
Çünkü processin çıkış kodu orada saklanacaksa onu yaratan processin çıkış kodunu aldıktan
sonra handle alanını yok etmesi gerekir.
Unix/Linux sistemlerinde üst process fork fonksiyonu ile yeni bir processi yaratırsa fakat
process bittiği halde onun handle alanını yok etmezse böyle processlere zombie process
denilmektedir. Zombie processler ps komutunda Z harfiyle gösterilmektedir.
Process yaratmada en normal durum üst processin alt processin sonlanmasını beklemesi, alt
process sonlanınca onun çıkış kodunu alması ve alt processin handle alanını yok etmesidir.
Şüphesiz üst process alt processin sonlanmasını beklemezse ne çıkış kodunu alabilir, ne de
handle alanını boşaltabilir.
blocking
Run ready
wait Fonksiyonu
wait fonksiyonunun iki işlevi vardır.
1. Alt process sonlana kadar üst processi (yani wait fonksiyonunu uygulayan processi)
çizelge dışı bırakarak bekletir.
2. Processin çıkış kodunu alarak handle alanını boşaltır.
pid_t wait(int *stat);
Anahtar Notlar:
Win32 sistemlerinde wait fonksiyonunun işlevi üç ayrı fonksiyon tarafından yapılmaktadır. Bu
sistemlerde üst process WaitForSingleObject fonksiyonuyla üst processin bitmesini bekler,
Sayfa 42 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
wait fonksiyonu ilk sona ermiş olan alt processin çıkış kodunu alarak onun handle alanını
yokeder. Yani wait fonksiyonu yalnızca tek bir alt processin bitmesini beklemektedir, o alt
process te ilk sonlananan herhangi bir alt processtir. Örneğin, üst process iki kez fork yapıp iki
alt process yaratmış olsun. İkisinin birden sonlanması beklenmek isteniyorsa iki kez wait
uygulamak gerekir. wait fonksiyonu başarısızlık durumunda –1 değerine, başarı durumunda
beklediği alt processin ID değerine geri dönmektedir.
Alt process wait fonksiyonundan önce sonlanmış olabilir. Bu durumda wait fonksiyonu hiç
beklemeden alt processin çıkış kodunu alarak handle alnını yok eder. wait fonksiyonu
yalnızca processin .ıkış kodunu değil başka bazı bilgileri de almaktadır. Örneğin, process nasıl
sonlandırılmıştır? Şüphesiz bu bilgilerin hepsi parametreyle belirtilen değişkene bitsel olarak
yerleştirilmiştir. wait fonksiyonunun bu bilgileri bitsel olarak nasıl yerleştirileceği
standartlarda belirtilmemiştir, ancak bu işlemi yapan standart makrolar vardır.
WIFEXITED(stat) makrosu ğer process normal olarak sonlandırılmışsa 0 dışı bir değere,
sinyal yoluyla sonlandırılmışsa 0 değerine geri dönmektedir. Programcı önce programın
normal sonlandırılıp sonlandırılmadığına bakmalıdır. Eğer normal sonlandırılmamışsa
WEXITSTATUS(stat) makrosuyla çıkış kodunu almalıdır.
int stat;
...
wait(&stat);
if(WIFEXITED(stat)){
printf(“Exit code: %d\n”, WEXITSTATUS(stat));
}
int main(void){
pid_t pid;
if((pid = fork()) < 0){
perror(“fork”);
exit(EXIT_FAILURE);
}
if(pid > 0){
int stat;
wait(&stat);
if(WIFEXITED(stat))
printf(“Child terminated normally. Exit code: %d\n”, WEXITSTATUS(stat));
else
printf(“Chil terminated via signal\n” );
}
else{
int i;
for(i = 0; i < 10; ++i){
printf(“child : %d\n”, i);
sleep(1);
}
exit(10);
}
return 0;
Sayfa 43 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
}
Alt process sinyal ile sonlandırılmışsa WIFSIGNALED(stat) makrosu 0 dışı bir değer verir.
programcı WTERMSIG(stat) makrosuyla processin sonlanmasını sağlayan sinyalin numarasını
alabilir.
wait fonksiyonunu parametresi NULL geçilebilir, bu durumda fonksiyon yerleştirme yapmaz.
Fonksiyonun prototipi ve makrolar <sys/wait.h> içerisinde olabilir.
waitpid Fonksiyonu
Bu fonksiyon wait fonksiyonunu işlevsel olarak daha geniş bir biçimidir.
pid_t waitpid(pid_t pid, int *status, int options);
Bu fonksiyon pekçok alt process olduğunda bunlardan belirli bir tanesini bekleyebilmektedir.
Fonksiyonun birinci parametresi beklenecek processin ID değeridir, fakat bu değer aşağıdaki
özel durumlara sahip olabilir.
a. Bu parametre <-1 biçiminde girilirse, üst process, process grubu bu girilen sayınıon
mutlak değerine eşit olan herhangi bir processi bekler.
b. Bu değer –1 olarak girilirse bu durumda davranış wait fonksiyonu gibi olur
fonksiyon herhangi bir processi bekler.
c. Bu değer 0 olursa üst process process grup ID si üst processle aynı olan herhangi bir
alt processi bekler.
d. Bu değer >0 ise üst process bu değerle belirtilen process ID ye sahip alt processi
bekler. Genellikle de bu biçim kullanılır.
waitpid fonksiyonunu ikinci parametresi tamamen wait fonksiyonun parametresi gibidir,
NULL geçilebilir. Fonksiyonun son parametresi ya 0 girilebilir yada WNOHANG ve
WUNTRACED sembolik sabitlerinin yalnızca biri yada her ikisinin birleştirilmesiyle
girilebilir. Uygulamaların çoğunda bu parametre 0 geçilmektedir. Bu parametre WNOHANG
biçiminde geçilirse process alt process sonlanmaış olsa bile bloke olmaz 0 değerine geri
döner.
waitpid fonksiyonu başarısızlık durumunda –1 değerine geri döner. Eğer son parametre de
WNOHANG kullanılmışsa ve alt process sonlanmamışsa 0 ile geri döner. Başarı durumunda
beklenen processin ID değeriyle geri dönmektedir.
Anahtar Notlar:
POSIX standartlarında bir POSIX fonksiyonun başarısız olması durumunda errno
değişkeninin hangi değerlerle set edilebileceği tek tek açıklanmıştır. Oysa Win32
sistemlerinde bir API fonksiyonu başarısız olduğunda başarısızlığın nedenleri microsoft
dökümanlarında tek tek açıklanmamıştır. (Microsoft dökümanlarında GetLastError
fonksiyonunun bazı durumlarda hangi değerlere geri döneceği açıklanmıştır. Fakat fonksiyon
temelinde kesin bir liste verilmemiştir.)
int main(void){
pid_t pid;
if((pid = fork()) < 0){
perror(“fork”);
Sayfa 44 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
exit(EXIT_FAILURE);
}
if(pid > 0){
int stat;
Zombie Processler
Üst process fork yaptıktan sonra iki durum söz konusu olabilir.
1. Üst process alt processten daha önce sonlanmıştır. Bu durumda alt processin üst processi
sistem tarafından otomatik olarak init processi yapılır. Alt process sonlandığında init, wait
işlemi uygulayarak alt processin handle alanını yok etmektedir.
int main(void){
pid_t pid;
if((pid = fork()) < 0){
perror(“fork”);
exit(EXIT_FAILURE);
}
if(pid > 0){
int i;
Sayfa 45 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
}
return 0;
}
2. Alt process üst processten daha önce sonlamıştır.normal olarak bu gibi durumlarda alt
processin üst processi beklemesi gerekir. Eğer üst process alt processi wait fonksiyonu
bekleyip alt processin handle alanını boşaltmamışsa bu biçimdeki processlere zombie process
denir. Zombie durumu yalnızca üst process çalıştığı sürece oluşan bir durumdur. Eğer zombie
processin üst processi de sonlanmış ise alt processin zombielik durumu init process tarafından
sonlandırılmaktadır.
Zombie processler, processin yaratacağı alt process sayısını azaltabilmektedir. Bu nedenle
programcının zombie process oluşmasını engellemesi gerekmektedir. Üst processin alt
processi beklemeden zombie lik durumunu ortadan kaldırabilmesi için sinyal mekanizmasının
kullanılması gerekmektedir. Alt process sonlandığında üst processe SIGCHLD
göndermektedir.
Şüphesiz, shell programı da normal olarak çalıştırılan programları wait fonksiyonuyla
beklemektedir. Shell üzerinde program isminden sonra & konularak program çalıştırılırsa,
shell alt processi beklemez. Bu durumda shell process arka planda çalışmaya devam eder.
Sınıf Çalışması:
A processinin B ve C processlerini çalıştırmasını sağlayınız, yani iki kez fork yapınız. (yani A
içerisinde iki kez fork yapınız.) waitpid fonksiyonuyla her iki alt processin bitmesini
bekleyiniz.
exec Fonksiyonları
Unix/Linux sistemlerinde ismi exec ile başlayan bir grup exec fonksiyonu vardır. Bu
fonksiyonların işlevleri aynı olmasına karşın parametrik yapıları birbirinden farklıdır. Exec
fonksiyonları temel olarak bir programı yüklenerek çalıştırılmasını sağlamaktadır. Exec
fonksiyonları Unix/Linux sistemlerine özgüdür ve Win32 sistemlerinde bu fonksiyonların tam
bir karşılığı yoktur.
Exec fonksiyonları bir processin başka bir program olarak çalışmaya devam etmesine yol
açmaktadır, process tablosunda bir değişiklik yapılmaz(bazı istisnalar vardır.) Yalnızca o anda
çalışan processin bellekteki kod, data, ve stack alanları atılarak çalıştırılacak olan programın
kod, data ve stack i yüklenir. Çalıştırılacak olan program baştan çalışmaya başlatılmaktadır.
Bir process exec fonksiyonunu uygulamış olsun, exec işlemi başarılıysa artık çalışmakta olan
program bellekten kaldırılacak ve yerine yeni bir program çalıştırılmaya başlanacaktır. Yani
exec fonksiyonları başarı durumunda geri dönmezler. Exec işlemini uygulayan program ile
çalıştırılan programların process ID leri değişmez. Yani fork fonksiyonu yeni bir process
yaratırken exec çalışmakta olan bir processin başka bir program olarak çalışmasını devam
ettirmektedir.
Anahtar Notlar:
C de ve C++ da fonksiyon prototipinde yada tanımlama ifadesinde parametre listesinin
sonuna üç nokta konulursa, bu durum fonksiyonun istenildiği kadar parametreyle
Sayfa 46 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 47 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Bir fonksiyonun, statik yerel bir dizinin yada değişkeninin adresine geri dönmesinin
programcı için üç anlamı vardır.
1. Bu çeşit fonksiyonun çağırılmasında aslında aynı değer verilir bu nedenle aşağıdaki
gibi bir yol izlenmemelidir.
char *pVal1, *pVal2;
pVal1 = getenv(“ali”);
pVal2 = getenv(“veli”);
2. Böyle fonksiyonların geri döndürdüğü adres free fonksiyonuyla boşaltılmamalıdır.
3. Bu tür fonksiyonlar çok thread li sistemlerde güvenli olarak kullanılamazlar.
puts(pVal);
return 0;
}
Çevre değişkenlerinin büyük harf küçük harf duyarlılığı sistemden sisteme değişebilmektedir.
Örneğin, Dos ve Win32 sistemlerinde büyük harf küçük harf duyarlılığı olmamasına karşın,
Unix/Linux sistemlerinde büyük harf küçük harf duyarlılığı söz konusudur.
Processe yeni bir çevre değişkeni eklenebilir, bunun için standart bir C fonksiyonu yoktur
ancak Unix/Linux sistemlerinde putenv ve setenv fonksiyonları bu iş için kullanılmaktadır.
putenv bir POSIX fonksiyonudur, setenv bir POSIX fonksiyonu değildi ve bazı sistemler
tarafından desteklenmiyordu ama son POSIX standartlarında bu fonksiyonda eklenmiştir.
int putenv(char *string);
Fonksiyonun parametresi “isim = değer” biçiminde bir yazı olarak geçilir. Fonksiyon
başarılıysa 0 değerine, başarısızsa 0 dışı bir değere geri döner.
int setenv(const char *envname, const char *envval, int overwrite);
Fonksiyonun birinci parametresi çevre değişkeninin ismi ikinci parametresi değeridir. Üçüncü
parametre daha önce o çevre değişkeni varsa yeni değerin eski değeri ezip ezmeyeceğini
belirlemekte kullanılır. Üçüncü parametre 0 dışı bir değerse ezer, 0 ise ezmez. (putenv her
Sayfa 48 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
Dos ve win32 sistemlerinde shell üzerinden bir program çalıştırıldığı zaman önce bulunulan
dizine eğer çalıştırılacak dosya bu dizinde yoksa PATH ile belirtilen dizinlere bakılmaktadır.
Unix/Linux sistemlerinde bulunulan dizine bakılması gibi bir durum söz konusu değildir. Shell
programları programı exec fonksiyonlarının p li versiyonlarıyla çalıştırır, exec
fonksiyonlarının p li versiyonları ise yalnızca PATH çevre değişkenine bakmaktadır. Bu
durumda Unix/Linux sistemlerinde bulunulan dizindeki bir programın çalıştırılabilmesi için
bulunulan dizini temsil eden “.” ifadesinin path çevre değişkeni içerisinde olması gerekir.
Sayfa 49 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
PATH=$PATH”:.”
Dos ve win32 sistemlerinde PATH çevre değişkenine bir dizin ekleyecek olsak şöyle yaparız:
set PATH=%PATH%;c:\Deniz
Görüldüğü gibi Unix/Linux sistemlerindeki “ ” çevre değişkeniyle diğer yazıları ayırmak için
kullanılmaktadır.
Sayfa 50 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
C de ve C ++ da NULL adres (NULL pointer) hiçbir nesnenin yada fonksiyonun adresi
olmayacak derleyici tarafından seçilmiş olan bir adrestir. Derleyicilerin çok büyük çoğunluğu
NULL adresi değeri için 0 adresini kullanmaktadır. C de NULL adres sabiti iki biçimde
oluşturulabilir.
1. 0 sayısı biçiminde yada 0 değerini veren bir sabit ifadesi biçiminde
2. (void *) 0 biçiminde
C ++ da ikinci biçim kaldırılmıştır, yalnızca birinci biçim NULL adres sabiti
oluşturmaktadır. C de ve C++ da 0 sabit ifadesi ğere bir adresle ilişkilendirilmişse int bir 0
değil NULL adres anlamına gelmaktadir. Bu durumda örneğin, p bir gösterici olmak üzere
p=0
işleminde p ye 0 değil, derleyicinin tespit etmiş olduğu NULL adres değeri atanmaktadır.
Benzer biçimde:
if(p != 0){
...
}
Burada p, NULL adres ile karşılaştırılmaktadır. Ayrıca standartlara göre
if(p){
...
}
ile
if(p != 0){
...
}
else{
}
işlemi aynıdır. Yani
if(p){} işleminde p adresinin 0 dışı olup olamdığı değil NULL olup olmadığı
sorgulanmaktadır.
Hem C de hemde C++ da NULL sembolik sabitinin NULL adres sabiti olarak define edilmesi
ön görülmüştür. Bu durumda NULL sembolik sabiti
1. #define NULL 0
2. #define NULL (void *) 0
biçimlerinden biriyle define edilebilir. Oysa C++ da yalnızca birinci biçimde olduğu gibi
define edilmiş olmalıdır.
Bir fonksiyonu 0 parametresiyle çağırırsak buradaki 0 NULL adres anlamına mı, yoksa int 0
anlamına mı gelir? İşte derleyici bu durumda fonksiyon prototipine bakarak karar
vermektedir. Fakat bu parametre değişken sayıda parametre alan bir fonksiyonun
parametresi olarak kullanılmış ise bu durumda 0 default int anlamına gelmektedir. O halde
exec fonksiyonunun aşağıdaki gibi kullanımı yaygın sistemlerde problem oluşturmasa da hata
içermektedir:
execlp(“den”, “den”, NULL);
Çünkü derleyici NULL sembolik sabitini 0 olarak define etmiş olabilir. Bu 0 da int anlamına
gelecektir. O halde bu durumda oluşacak problemin iki yönü olabilir.
Sayfa 51 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
1. Eğer NULL sembolik sabiti 0 adresi değilse exec fonlksiyonu içerisinde NULL adres
görülemeyecektir.
2. İnt türü ile adres türleri farklı uzunlukta ise daha ciddi bir problem ortaya
çıkacaktır.
Bu nedenle yukarıdaki exec çağırmasının şöyle düzenlemesi gerekmektedir.
execlp(“den”, “den”, (char *) 0);
{
if(execlp(“ls”, “ls”, “-l”, (char *)0) < 0){
perror(“exec”);
exit(EXIT_FAILURE);
}
printf(“unreachable code!...\n”);
return 0;
}
Bilindiği gibi exec fonksiyonu processi başka bir program olarak çalıştırmaya devam
ettirmektedir. Hem çalışan programın devam etmesi hemde yeni bir programın
çalıştırılabilmesi için önce bir fork uygulamak sonra alt processe exec uygulamak gerekir.
if((pid = fork()) < 0){
perror(“fork”);
exit(1);
}
if((pid = = 0){
if(execlp(...) < 0){
perror(“fork”);
exit(0);
}
}
wait(NULL);
Sayfa 52 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Flags adres
dosyaN
pDesTbl
“close on exec” bayrağı fork işlemini etkileyen bir bayrak değildir. Yalnızca exec işleminde
etkili olan bir bayraktır. Fork işlemi sırasında kesinlikle tüm dosyaların açık olma durumu
korunmaktadır. Biz istediğimiz dosyaların bu bayrağını exec işleminden önce set edebiliriz.
int fd, pid;
if(argc != 2){
fprintf(stderr, “wrong number of arguments\n”);
exit(EXIT_FAILURE);
}
if((fd = open(“data”, O_RDWR|O_CREAT|O_TRUNC)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
if((pid = fork()) < 0){
perror(“fork”);
exit(EXIT_FAILURE);
}
if((pid = = 0){
dup2(fd, STDOUT_ FILENO);
if(execlp(argv[1], argv[1], (char *) 0) < 0){
perror(“exec”);
exit(EXIT_FAILURE);
}
_exit(0);
}
wait(NULL);
return 0;
Sayfa 53 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
perror(“fork”);
exit(EXIT_FAILURE);
}
if((pid = = 0){
if((fd = open(“data”, O_RDWR|O_CREAT|O_TRUNC, 0666)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
dup2(fd, STDOUT_ FILENO);
if(execlp(argv[1], argv[1], (char *) 0) < 0){
perror(“exec”);
exit(EXIT_FAILURE);
}
_exit(0);
}
wait(NULL);
return 0;
/*shell.c’yi al.*/
system Fonksiyonu
Unix/Linux klasik shell programları normal olarak bir komut satırında bekleyerek girilen
komutları yorumlamaktadır. Ancak shell programları –c ile çalıştırılırsa tek bir komutu
çalıştırıp sonlanırlar. Örneğin, /bin/bash –c ls ↵
Unix/Linux sistemlerinin çoğunda /bin dizinini altında sh isimli dosya sisteminin default shell
programına bir link yapmaktadır.
System fonksiyonu bir shell Unix/Linux sistemlerinde shell programını –c ile çalıştırarak bir
komutun işletilmesini sağlar.
int system(const char *cmd);
Yani biz komut satırında hangi komutu yazabiliyorsak bu komutu sistem fonksiyonuna
parametre olarak verebiliriz. System fonksiyonu şüphesiz shell programını fork ve exec ile
çalıştırmaktadır. Eğer fork fonksiyonunun başarısızlığı nedeniyle sistem fonksiyonu başarısız
olmuşsa –1 ile geri döner. Eğer fork başarılı fakat exec başarılı ise fonksiyon 127 ile geri
döner. Eğer ikiside başarılıysa system fonksiyonu çalıştırdığı shell processinin çıkış koduyla
geri döner. Shell programının çıkış kodu ise shellin çalıştırdığı programın çıkış kodudur.
System aynı zamanda standart bir C fonksiyonudur. Yani bu fonksiyon DOS veWin32
sistemlerinde onların shell programlarını çalıştıracak biçimde yazılmıştır. Prototipi <stdlib.h>
içerisindedir.
System fonksiyonu çok kullanışlı ve pratik bir fonksiyondur. Program yazılmayı gerektiren
pekçok küçük işler system fonksiyonu ile shell programına yaptırılabilir. Örneğin bir program
çalıştırıp o programın stdout dosyasını başka bir dosyaya yönlendirecek olalım. Bunun
programla yapılması yerine system(“a > b”); biçiminde yapılması oldukça pratiktir.
fcntl Fonksiyonu:
fcntl fonksiyonu açılmış yani betimleyicisi elde edilmiş bir dosya üzerinde bazı özelliklerin
değiştirilmesi için kullanılır. Fcntl fonksiyonunun prototipi <fcntl.h> dosyası içerisindedir.
Sayfa 54 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 55 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Client-Server Yazılımlar
Client-server uygulamalarında iki ayrı program vardır.
1. client program
2. server program
client program işin kendisini yapan program değildir. Yalnızca server programa ne yapmak
istediğini ve yapmak istediği işe ilişkin parametreleri gönderen programdır. İşin kendisi server
program tarafından yapılır. Genellikle server programdan bir tane çalıştırılır. Bir server birden
fazla client programa hizmet verebilmek için tasarlanır. Şüphesiz server programın
çalıştırılacağı makinanın daha hızlı olması tercih edilir. Client-server yazılım mimarisinde
hem client program server a hemde server program client programa bilgi göndermektedir.
Client-server mimarisinde client-server programlar nasıl haberleşmektedir? İşte böyle bir
mimarinin oluşturulabilmesi için işletim sistemlerinin processler arası haberleşme
mekanizmalarını oluşturmuş olması gerekmektedir. Yani client-server programları
yazabilmek için işletim sistemlerinin sunduğu processler arası haberleşme yöntemlerini
kullanmak gerekir.
Network altında processler arası haberleşmeyi sağlayan yöntemler için çeşitli protokol aileleri
kullanılmaktadır. Böylece farklı sistemler aynı dili konuşarak haberleşmeyi sağlayabilirler.
Örneğin, TCP/IP processler arası haberleşme yöntemleriyle aynı network e bağlı bir unix
makinasındaki process ile bir win32 makinasındaki process haberleşebilmektedir.
Pipe Yöntemi
Pipe yöntemi pekçok işletim sisteminin desteklediği en basit processler arası haberleşme
yöntemidir. Bu yöntem kendi içerisinde senkronizasyon problemini de çözdüğünden çok
popüler bir biçimde kullanılmaktadır.
Pipe yöntemi Unix/Linux sistemlerinde isimli pipe yöntemi (named pipes) ve isimsiz pipe
yöntemi (anonymous pipe) olamak üzere iki biçimde uygulanabilmektedir.
İsimsiz pipe’ lar yalnızca üst-alt processler arasındaki haberleşmede kullanılabilmektedir.
Oysa isimli pipe’lar herhangi iki process arasında haberleşme sağlayabilirler.
İsimsiz Pipe’lar
İsimsiz pipe’lar pipe fonksiyonuyla yaratılırlar.
Sayfa 56 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Anahtar Notlar:
C de fonksiyon parametresi gösterici ise bu aşağıdaki gibi üç biçimde belirtilebilir.
void Func(int *p);
void Func(int p[]);
void Func(int p[uzunluk]);
Benzer biçimde fonksiyon göstericisi içinde iki yazım biçimi kullanılabilir:
void Func(void (*p) (int));
void Func(void p(int));
Çok boyutlu diziler için de durum benzerdir:
void Func(int (*p)[SIZE]);
void Func(int p[][SIZE]);
pipe aslında belirli bir uzunlukta sistem tarafından organize edilmiş bir fifo kuyruk sistemidir.
Pipe tan bir taraf okur, bir tarafta pipe tan yazar. pipe a yazma yapılırken yazan taraf pipe
dolana kadar bloke olmadan yazma işlemini başarıyla yapar ancak pipe dolmuşsa yazma
yapıldığında yazma yapan write fonksiyonu, pipe ta yer açılana kadar processi bloke eder.
Pipe okuyan taraf pipe ta bilgi varsa bloke olmadan pipe taki bilgiyi okur. Ancak pipe boşsa
bu kez okumayı yapan read fonksiyonu processi bloke edecektir. Yani özetle pipe a yazan
taraf pipe doluysa pipe tan okuyan taraf ise pipe boşsa blokeye yol açmaktadır. Böylece
okuyan ve yazan taraf pipe içerisinde zorunlu olarak birbirini beklemek zorundadır. Pipe
haberleşmesi pipe a yazan tarafın pipe ı kapatmasıyla belirli bir düzen içerisinde sonlandırılır.
İsimsiz pipe lar pipe fonksiyonuyla yaratılır, pipe fonksiyonu programcıya iki betimleyici
verir. Betimleyicilerden biri pipe’ tan okuma yapmak için diğeri pipe a yazma yapmak için
kullanılır. Pipe fonksiyonu okuma yapmak için kullanılan betimleyiciyi parametreyle aldığı
dizinin 0. İndexli elemanına yazma yakmak için gereken betimleyiciyi dizinin birinci
elemanına yerleştirir. Örneğin,
int pipedes[2];
if(pipe (pipedes) < 0){
perror(“pipe”);
exit(EXIT_FAILURE);
}
pipedes[0] → okumak için
pipedes[1] → yazmak için
POSIX standartlarında pipe tek yönlü olarak tanımlanmıştır. Tek yönlü demek bir betimleyici
ile yalnızca okuma yada yalnızca yazma yapabilmek demektir. Bazı Unix sistemleri de pipe
lar çift yönlü olsa da POSIX standartlarında böyle bir zorunluluk yoktur. Programcı pipe ın
tek yönlü olduğu fikriyle işlemini yapmalıdır.
Unix/Linux sistemlerinde pipe larda tamamen dosyalar gibi düşünülmüştür. Yani pipe tan
okuma yapmak için yine read fonksiyonu pipe a yazma yapmak için write fonksiyonu
kullanılmaktadır. Pipe yine close fonksiyonuyla kapatılır. Pipe için lseek fonksiyonu
anlamsızdır.
Pipe tan blokeli okuma işlemlerinde read fonksiyonu pipe boşsa geri dönmez. Örneğin, pipe ta
10 byte olsun fakat biz read fonksiyonuyla 12 byte okumak isteyelim, read fonksiyonu 10
byte okur ve 10 değeri ile geri döner. Aynı dururm write fonksiyonu içinde söz konusudur.
Write fonksiyonu bütün bilgi yazılana kadar yada pipe kapatılana kadar geri dönmez.
Sayfa 57 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 58 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
durumunda ortaya çıkar. Pipe’a yazılacak blok büyüklüğünün en fazla PIPE_BUF kadar
olması bu tür problemlerin olmasını engelleyecektir.
pid_t pid;
int pipedes[2];
if (pid == 0) {
close(pipedes[1]);
....
okuyan process(alt process)
...
close(pipedes[0]);
}
else {
close(pipedes[0]);
.... yazan process(üst process)
....
close(pipedes[1]);
}
/* pipe1.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <string.h>
Sayfa 59 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
#include <sys/types.h>
int main(void)
{
int pipedes[2];
pid_t pid;
if (pipe(pipedes) < 0) {
perror("pipe");
exit(EXIT_FAILURE);
}
if ((pid = fork()) < 0) {
perror("fork");
exit(EXIT_FAILURE);
}
if (pid == 0) {
close(pipedes[1]);
child(pipedes[0]);
_exit(0);
}
else {
close(pipedes[0]);
parent(pipedes[1]);
exit(0);
}
}
for (;;) {
printf("Enter text: ");
fgets(buf, BUFSIZE, stdin);
if (!strcmp(buf, "exit\n"))
break;
write(pipedes, buf, strlen(buf));
sleep(1);
}
close(pipedes);
}
Sayfa 60 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
char ch;
int n;
/*lcount.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
if (argc > 2) {
fprintf(stderr, "wrong number of arguments!..\n");
exit(EXIT_FAILURE);
}
if (argc != 1) {
if ((f = fopen(argv[1], "r")) == NULL) {
if (access(argv[1], F_OK) < 0)
fprintf(stderr, "File not found: %s\n", argv[1]);
else
fprintf(stderr, "File cannot open!..\n");
Sayfa 61 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
exit(EXIT_FAILURE);
}
}
else
f = stdin;
fclose(f);
UNIX/LINUX programcıları da pipe işlemine olanak sağlamak için dosya isminin komut
satırı parametresi olarak verilmediği durumlarda stdin dosyasını kullanırlar. Shell üzerinde,
birden fazla pipe işlemi uygulanabilir.
Örneğin,
ls | sort | more
burada ls’nin çıktısı sort edilmiş onun da çıktısı more işlemine sokulmuştur.
Sayfa 62 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
npiperead.c
{
int fd, result;
char buf[20];
if((fd = open(“mypipe”, O_RDONLY)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
while(result = read(fd, buf, sizeof(buf)) > 0)
printf(buf);
if(result < 0){
perror(“read”);
exit(EXIT_FAILURE);
}
printf(“open ended..\n”);
close(fd);
return 0;
}
npipewrite.c
{
int fd, result;
char buf[20];
if((fd = open(“mypipe”, O_WRONLY)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
for(;;){
printf(“enter string”);
fgets(buf, 20, stdin);
if(!strcmp(buf, “exit\n”))
break;
if((write(fd,buf, strlen(buf) + 1) < 0){
perror(“write”);
exit(EXIT_FAILURE);
}
}
close(fd);
return 0;
}
Sayfa 63 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
yazdığı durumlarda yazılan miktar pipe uzunluğu olan PIPE_BUF değerini aşmadıktan sonra
içiçe geçme olmaz. Ancak bu değeri aşan yazmalar karışmaya yol açabilmektedir.
Birden fazla processin aynı pipe’ a yazma yaptığı fakat tek processin okuma yaptığı durumda
okuyan processin pipe’ ın kapatıldığını anlayabilmesi için tüm yazan processlerin pipe’ ı
kapatmış olması gerekir.
client
server
client
client
Sayfa 64 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
#ifndef _CHATSERVER_H_
#define _ CHATSERVER_H_
/* Symbolic constants*/
#define NICK_SIZE 10
#define TEXT_SIZE 100
#define SERVER_PIPE_NAME “serverpipe”
#define MAX_CLIENT 100
#define MAX_PATH 264
#define FALSE 0
#define TRUE 1
/*General typedefs*/
/*Message Types*/
/*Structure Declarations*/
/* Function prototypes*/
Sayfa 65 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
#endif
chatclient.h
#ifndef _CHATCLIENT_H_
#define _ CHATCLIENT_H_
/* Symbolic constants*/
#define NICK_SIZE 10
#define TEXT_SIZE 100
#define SERVER_PIPE_NAME “serverpipe”
/*General typedefs*/
/*Message Types*/
/*Structure Declarations*/
/* Function prototypes*/
Sayfa 66 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
void connect_server(void);
void disconnect_server(void);
void send_msg(const char *pmsg);
void talk_server(void);
int get_client_id(void);
void *alloc_mem(size_t size);
#endif
Chatserver.c
CLIENT_INFO g_clinfo[MAX_CLIENT];
int main(int argc, char *argv[])
{
check_args();
init_prog();
listen_clients();
end_prog();
return 0;
}
void check_args(int argc, char *argv[])
{
if(argc != 1){
fprintf(stderr, “usage: chtclient <nickname> \n”);
exit(EXIT_SUCCESS);
}
if(argc > 2){
fprintf(stderr, “wrong number of arguments... \n”);
exit(EXIT_SUCCESS);
}
strncpy(g_nick, argv[1], NICK_SIZE - 1);
g_nick[NICK_SIZE - 1] = ‘\0’;
if(strlen(argv[1]) > NICK_SIZE - 1){
fprintf(stderr, “warning: nick name will be truncated: %s\n”, g_nick);
}
}
void init_prog(void)
{
if(access(SERVER_PIPE_NAME, F_OK) < 0){
if(mkfifo(SERVER_PIPE_NAME, 0666) < 0){
fprintf(stderr, “server pipe does not exists.\n”);
exit(EXIT_FAILURE);
}
}
if ((g_spipe = open(SERVER_PIPE_NAME, O_RDONLY)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
}
void listen_clients(void)
Sayfa 67 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
{
HEADER header;
for(;;){
if(read(g_spipe, &header, sizeof(HEADER)) <= 0)
break;
switch(header.type){
case CONNECT_MSG:
connect_client(&header);
break:
case SEND_MSG:
get_client_msg(text);
display_message(g_clinfo[header.clid].nick, text);
break;
case DISCONNECT_MSG:
disconnect_client(&header);
break;
default:
fprintf(stderr, “fatal error: unknown client message\n”);
exit(EXIT_FAILURE);
}
}
}
void connect_client(HEADER *pheader)
{
char nick[NICK_SIZE];
int clid;
char clpipe_name[MAX _PATH];
if(read(g_spipe, nick, pheader->size) <= 0){
fprintf(stderr, “fatal error: pipe broken\n”);
exit(EXIT_FAILURE);
}
if((clid = get_free_slot()) == -1){
fprintf(stderr, “warning too many clients connected\n”);
return;
}
sprintf(clpipe_name, “client%02d”, clid);
if(mkfifo(nick, 0666) < 0){
fprintf(stderr, “warning:cannot create client pipe\n”);
return;
}
if((g_clinfo[clid].cpipe = open(clpipe_name, O_WRONLY)) < 0){
fprintf(stderr, “warning:cannot open client pipe\n”);
return;
}
strcpy(g_clinfo[clid].nick, nick);
g_clinfo[clid].empty_flag = TRUE;
}
void disconnect_client(HEADER *pheader)
{
close(g_clinfo[pheader->clid].cpipe);
Sayfa 68 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
}
void display_message(const char *pnick, const char *ptext)
{
printf(“<%s>: %s\n”, pnick, )
}
void end_prog(void)
{
close(g_spipe);
if(SERVER_PIPE_NAME < 0){
fprintf(stderr, “warning client pipe cannot remove\n”);
return;
}
}
int get_free_slot(void)
{
int i;
for(i = 0; i < MAX_CLIENT; ++i)
if(g_clinfo[i].empty_flag)
return i;
}
return –1;
}
Chatclient.c
char g_nick[NICK_SIZE];
int g_spipe;
int main(int argc, char *argv[])
{
check_args(argc, argv);
init_prog();
connect_server();
talk_server();
disconnect_server();
return 0;
}
void check_args(int argc, char *argv[])
{
if(argc != 1){
fprintf(stderr, “usage: chtclient <nickname> \n”);
exit(EXIT_SUCCESS);
Sayfa 69 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
}
if(argc > 2){
fprintf(stderr, “wrong number of arguments... \n”);
exit(EXIT_SUCCESS);
}
strncpy(g_nick, argv[1], NICK_SIZE - 1);
g_nick[NICK_SIZE - 1] = ‘\0’;
if(strlen(argv[1]) > NICK_SIZE - 1){
fprintf(stderr, “warning: nick name will be truncated: %s\n”, g_nick);
}
}
void init_prog(void)
{
if(access(SERVER_PIPE_NAME, F_OK) == -1){
fprintf(stderr, “server pipe does notexists.\n”);
exit(EXIT_FAILURE);
}
if ((g_spipe = open(SERVER_PIPE_NAME, O_WRONLY)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
}
void connect_server(void)
{
MSG *pconnect_msg;
int nick_size;
nick_size = strlen(g_nick) + 1;
pconnect_msg = (MSG*) alloc_mem(sizeof(HEADER) + nick_size)
connect_msg->header.type = CONNECT_MSG;
connect_msg->header.size = nick_size;
strcpy(connect_msg->data, g_nick);
write(g_spipe, pconnect_msg, sizeof(HEADER) + nick_size);
g_id = get_client_id;
free(pconnect_msg);
}
void disconnect_server(void)
{
MSG disconnect_msg;
disconnect_msg->header.type = CONNECT_MSG;
disconnect_msg->header.clid = g_clid;
write(g_spipe, &disconnect_msg, sizeof(MSG));
}
void send_msg(const char *pmsg)
{
MSG *psend_msg;
int text_size;
}
void talk_server(void)
{
char text[TEXT_SIZE], *pstr;
Sayfa 70 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
for(;;){
printf(“text: ”);
fgets(text, TEXT_SIZE, stdin);
if((pmsg = strchr(text, ‘\n’)) != NULL)
*pstr = ‘\0’;
if(!strcmp(text, “quit”))
break;
send_msg(text);
}
}
int get_client_id(void)
{
int i;
}
}
void *alloc_mem(size_t size)
{
void ptr = malloc(size);
if(ptr == NULL){
fprintf(stderr, “cannot allocate memory\n”);
exit(EXIT_FAILURE);
}
return ptr;
}
Anahtar Notlar:
mkfifo fonksiyonu eğer fifo dosyası varsa onu yeniden yaratmaz hata ile geri döner, yoksa
yaratır.
Sayfa 71 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
pclose Fonksiyonu
popen fonksiyonu ile açılan dosya pclose fonksiyonu ile kapatılmaktadır. pclose fonksiyonu
hem dosya bilgi göstericisinde belirtilen dosyayı hemde pipe sistemini kapatmaktadır.
popen ve pclose fonksiyonlarının prototipleri <stdio.h> dosyası içerisindedir.
int pclose(FILE *stream);
popen ve pclose fonksiyonlarını kullanarak ls programının çıktısını elde edip ekrana
yazdıracak olalım:
int main(void)
{
FILE *f;
int ch;
if((f = popen(“ls -l”, “r”)) ==NULL){
fprintf(stderr, “cannot open pipe\n”);
exit(EXIT_FAILURE);
}
while((ch = fgetc(f)) != EOF)
putchar(ch);
pclose(f);
return 0;
}
Sayfa 72 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Bir programın çıktısını diğer programın girdisine yönlendiren bir pipe programı da kolaylıkla
aşağıdaki gibi yazılabilir.
int main(void)
{
FILE *fs, *fd;
int ch;
if(argc != 3){
fprintf(stderr, “wrong numberof arguments!\n”);
exit(EXIT_FAILURE);
}
if((fs = open(argv[1], “r”)) == NULL){
fprintf(stderr, “cannot create source pipe!\n”);
exit(EXIT_FAILURE);
}
if((fd = open(argv[2], “w”)) == NULL){
fprintf(stderr, “cannot create destination pipe!\n”);
exit(EXIT_FAILURE);
}
while((ch = fgetc(fs)) != EOF)
fputc(ch, fd);
pclose(fs);
pclose(fd);
return 0;
}
Sinyal İşlemleri
Sinyal (signal) Unix/Linux sistemlerinde bir çeşit yazılım kesmesi gibi kullanılan
mekanizmadır. Program çalışırken bir sinyal oluştuğunda programın akışı sinyal kodu (signal
handler) denen bir koda geçer ve o kod çalıştırılır. Programcı bir sinyal oluşturulduğunda
hangi kodun çalıştırılacağını belirleyebilir.
Bir sinyal kim tarafından ve nasıl oluşturulmaktadır? Sinyal oluşturan temel olarak üç tür
kaynak vardır:
1. Klavyede belirli tuş kombinasyonlarına basıldığında o anda çalışan processe sinyal
gönderilebilir. Yani sinyal klavye yoluyla oluşturulabilir.
2. Programcı gösterici hataları, 0’ a bölme hatası gibi işlemcide kesme oluşturacak bir
işlem gerçekleştirirse donanım tabanlı sinyaller oluşabilir. Örneğin, 0’ a bölme işlemi aslında
işlemci tarafından bir içsel kesmeyle ele alınan bir işlemdir. Bu içsel kesme işletim sistemi
tarafından kancalanarak processe sinyal gönderilir.
3. Sinyal programlama yolula kill sistem fonksiyonuyla oluşturulabilir. Yani programcı
ID’ sini bildiği bir processe sinyal gönderebilir.
Bir sinyal oluştuğunda processin bu sinyale tepkisi üç biçimde olabilir:
1. Sinyal tamamen göz ardı edilebilir. Yani sanki sinyal hiç oluşmamış gibi bir işlem
yapılabilir.
2. Sinyal geldiğinde default durum işelmi yapılabilir. Her sinyalin bir default durum
davranışı vardır. bu default durum davranışı bazı sinyaller için sinyalin göz ardı edilmesi
biçiminde bazı sinyaller için processin sonlandırılması biçimindedir.
Sayfa 73 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
3. Nihayet bir sinyal oluştuğunda processin belirlediği bir fonksiyon sinyal fonksiyonu
olarak çalıştırılabilir.
Bir sinyal yukarıda belirtilen üç durumdan birinde olabilir. Yani o sinyal geldiğinde eğer o
sinyal göz ardı edilme durumundaysa göz ardı edilir, default işlem durumundaysa default
işlem uygulanır, sinyal fonksiyonu atanmışsa sinyal fonksiyonu atanır.
POSIX sistemlerinde standart olarak 32 sinyal tanımlanmıştır. Her sinyalin bir numarası
vardır, sinyal numaraları <signal.h> içerisinde SIGXXX biçiminde define edilmiştir. Bu
sayıları belirtmek yerine programcılar sembolik sabitleri kullanırlar.
Sayfa 74 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Zombie oluşmasını otomatik engellemek için programcı SIGCHLD sinyali için sinyal
fonksiyonu yazar ve burada waitpid fonksiyonunu uygulayarak alt processin handle alanını
yok eder.
Anahtar Notlar:
Zombie process oluşmasını engellemenin dört yolu vardır.
1. alt processin wait yada waitpid ile beklemek
2. Üst processin sonlanmasını sağlamak
3. SIGCHLD sinyali için sinyal fonksiyonu yazarak orada waitpid() fonksiyonu
uygulamak
4. Önce bir alt process yaratmak alt processte yeniden bir alt process yaratarak alt
processi sonlandırmak. Böylece alt processin alt processinin üst processi init process
olacaktır ve otomatik olarak init tarafından zombielik durumu ortadan kaldırılacaktır. Bu
durumda alt process artık en üst process tarafından wait fonksiyonlarıyla gerçek anlamda
beklemeden zombielik durumundan kurtarılacaktır.
Zombie lik durumunun engellenmesinde en çok istenen durum üst processin beklemeden
otomatik bir biçimde alt process sonlandığında onun zombielik durumunu kaldırmasıdır.
Bunu sağlamak için ya SIGCHLD sinyalini kullanmak yada alt processin alt processi
yöntemini kullanmak gerekir. alt processin alt processi yöntemi şöyle kullanılabilir.
int main(void)
{
pid_t pid1, pid2;
if((pid1 = fork()) < 0){
perror(“fork”);
_exit(0);
}
if(pid1 == 0){
if((pid2 = fork()) < 0){
perror(“fork”);
_exit(0);
}
if(pid2 == 0){
if(execlp(“ls”, “ls”, “-l”, (char *) 0) < 0){
perror(“fork”);
_exit(0);
}
}
_exit(0);
}
waitpid(pid1, NULL,0);
pause();
return 0;
}
SIGSEGV: Programcı tahsis etmediği bir alana erişerek bir gösterici hatası yaptığında
işlemci bunu tespit eder, durumu işletim sistemine bildirir. İşletim sistemi de SIGSEGV
Sayfa 75 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
sinyalini göndererek processi sonlandırır. Bu sinyal gözardı edilemez, default davranış ekrana
“segmentation violation” gibi bir mesaj çıkarark processin sonlandırılması biçimindedir.
İşletim sistemi belleğin o anki durumunu gösteren bir teşhis dosyası (core) da oluşturur. Bu
sinyal için sinyal fonksiyonu yazılabilir. Fakat programcının sinyal fonksiyonun da processi
sonlandırması gerekir. programcı processi sonlandırmazsa zaten sinyal fonksiyonunun
çalışması bitince process sonlandırılacaktır. Bu durumda Unix/Linux sistemlerinde bir
gösterici hatası yapıldığında process otomatik olarak bir mesaj verilerek sonlandırılacaktır.
SIGUSR1, SIGUSR2: Bu sinyaller tamamen bir çeşit processler arası haberleşme amacıyla
kullanılmak için düşünülmüştür. Ayni örneğin bir process bir işlem gerçekleştiğinde diğer
processi bu sinyalleri göndererek haberdar edebilir. Bu sinyallerin default davranışı
processlerin sonlandırılmasıdır. Bu sinyalde ihmal edilebilir. Bu sinyal adeta bir kullanıcı
mesajı oluşturmaktadır.
SIGALRM: Bu sinyal alarm ve setitiger fonksiyonlarıyla set edilen zaman dolduğunda
gönderilir. Bu sinyalin default davranışı da processin sonlandırılması biçimindedir.
Eski ve Yeni Sinyal Fonksiyonları
Sinyal işlemleri için başlangıçta Unix sistemlerinde düşünülmüş olan bazı fonksiyonların
zamanla kusurlu olduğu sonucuna varılmıştır. Yani bu fonksiyonların çalışma sistematiğinde
tasarımsal bazı hatalar vardır. Zamanla BSD 4.2 versiyonuyla birlikte sinyal fonksiyonları
değiştirlmiştir. Bu yeni sinyal fonksiyonlarına yaygın olarak güvenilir (relaible) sinyal
fonksiyonları denilmektedir. Güvenilir sinyal fonksiyonları POSIX sistemlerine de alınmıştır.
Eski ve yeni sinyal fonksiyonları process temelinde öalışan sinyal fonksiyonlarıdır. Fakat
Unix sistemlerinde threadli çalışma yaygınlaştığında sinyal konusununda threadli çalışmaya
göre yeniden düzenlenmesi gerekmiştir. Çünkü threadler söz konusu olduğunda pek çok kafa
karıştırıcı problemde ortaya çıkmaktadır. Örneğin, processe bir sinyal geldiğinde bunu hangi
thread ele alacaktır. pthread kütüphanesinin POSIX standartlarına eklenmesiyle thread
temelinde anlamlı olan başka sinyal fonksiyonları da tanımlanmıştır. O halde sinyal konusuna
yönelik üç grup fonksiyondan bahsedilebilir.
1. Process temelinde çalışan eski (güvenilir olmayan) fonksiyonlar
2. Process temelinde çalışan yeni (güvenilir olan) fonksiyonlar
3. Thread temelinde çalışan yeni fonksiyonlar
Tüm sinyal fonksiyonlarının prototipleri <signal.h> içerisindedir.
Anahtar Notlar:
Yazılımda yada donanımda kusurlu bir tasarım daha sonra nasıl düzeltilir? Örneğin bir
işletim sisteminin bir API fonksiyonu yanlış tasarlanmışsa işletim sisteminin sonraki
versiyonlarında ne yapılacaktır? Genel eğilim eski bozuk fonksiyonun yeni sistemde de
desteklenmesi fakat yeni sistem için düzgün yeni bir fonksiyonun tasarlanmasıdır. Eski
fonksiyonun düzeltilmesi (hem parametrik yapı hem davranış bakımından) eski programların
yanlış çalışmasına neden olabilir. Çünkü eski programlar onların yanlış tasarımına göre
yazılmışlardır.
signal Fonksiyonu
Bu fonksiyon sinyal geldiğinde çalıştırılacak fonksiyonu belirlemek için kullanılan eski yani
güvenilir olmayan bir fonksiyondur. Bu fonksiyon yerine daha sonra bunun güvenilir biçimi
olan sigaction fonksiyonu eklenmiştir.
Sayfa 76 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 77 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
pause Fonksiyonu
Bu fonksiyon bir sinyal gelene kadar processi çizelge dışına çıkartarak bekleten önemli bir
fonksiyondur.
int pause (void);
Fonksiyon herhangi bir sinyal gelip sinyal fonksiyonu çalıştırılana kadar geri dönmez sinyal
fonksiyonu çalıştırıldığında –1 değeriyle geri dönerve errno set edilir. Programcılar bu
fonksiyonu bir olay gerçekleşene kadar processi çizelge dışında bekletmek amacıyla
kullanırlar. Şüphesiz pause fonksiyonunda beklenirken sinyal fonksiyonu set edilmemiş bir
sinyal gelirse gene process sonlandırılır. Fonksiyonun prototipi <unistd.h> içerisindedir.
pause fonksiyonunun bir döngü içerinde sürekli çağırılması durumuyla sık karşılaşılır.
Örneğin,
....
for(;;)
pause();
...
Burada her sinyal geldikçe pause fonksiyonundan çıkılır ama döngü içerisinde yeniden pause
fonksiyonuna girilecektir. Böyle bir program nasıl sonlanacaktır?
1. Sinyal fonksiyonunun içerisinde exit fonksiyonu içerisinde belirli bir durum
oluştuğunda sinyal sonlandırılabilir.
2. Set edilmemiş bir sinyal geldiğinde process işletim sistemi tarafından default
davranışla sonlandırılır.
Bazı programlar CPU zamanı harcamadan arka planda yalnızca belirli olaylar
gerçekleştiğinde o olaylara yanıt vermek için çalışırlar. Örneğin böyle bir program şöyle
tasarlanabilir.
void Handler(int sno)
{
....
}
int main(void)
{
if(signal(SIGUSR1, Handler) == SIG_ERR) {
perror(“signal”);
exit(EXIT_FAILURE);
}
for(;;)
pause();
return 0;
}
alarm Fonksiyonu
Bu fonksiyon belirli bir zaman dolduğunda processe SIGALRM sinyalini gönderir. Böylece
belirli bir süre dolduğunda otomatik bazı işlemlerin yapılması sağlanabilir.
unsigned int alarm(unsigned int seconds);
Sayfa 78 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
alarm fonksiyonu periyodik bir sinyal oluşmasını sağlamaz. Yalnızca bir kez SIGALRM
sinyalinin oluşmasını sağlar. Programcı SIGALRM sinyalinin periyodik bir biçimde
oluşmasını istiyorsa sinyal fonksiyonu içerisinde yeniden alarm fonksiyonunu çağırmalıdır.
at Shell Komutu
Unix sistemlerinde POSIX standartlarında da belirtilen at komutu belirli bir zamanda belirli
bir işin yapılması için kullanılmaktadır.
at komutu SIGALRM sinyali ve alarm fonksiyonuyla yazılabilir. Bunun için programcı alarm
fonksiyonuyla periyodik bir sinyal fonksiyonunun çağırılmasını sağlayabilir ve bu sinyal
fonksiyonunun içerisinde zaman kontrolü yaparak yapılacak iş için zamanın dolmuş olup
olmadığına bakabilir.
Sayfa 79 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
içerisinde değil, sürekli yapılmaktadır. Yani bir kez sinyal oluşup sinyal fonksiyonu
oluştuğunda eğer programcı bu sinyali yeniden sinyal fonksiyonu içerisinde set etmemişse
sinyal default duruma çekilecek ve bir daha aynı sinyal oluştuğunda process sonlanacaktır. Bu
işlem şöyle yapılabilir.
Orijinal Unix sistemlerinde sinyal işleme konulduğunda yeniden default duruma çekilmesi bir
tasarım kusurudur. Muhtemelen tasarımın bu biçimde yapılmasının nedeni sinyal geldiğinde
fonksiyon çalışırken yeniden aynı sinyalin oluşması durumunda processin sonlandırılmasının
istenmesidir. Fakat sinyal defaulta çekildikten sonra sinyal fonksiyonu bitse bile defaultta
kalması bunu yeniden set etmeyi zorunlu hale getirmektedir. Fakat bu işlemin yukarıdaki gibi
yapılmasınında maalesef bir garantisi yoktur. Çünkü sinyal fonksiyonuna girildiğinde fakat
henüz signal fonksiyonuyla yeniden set işlemi yapılmadan önce aynı sinyal yeniden oluşursa
bu durumda yeniden process sonlanmaktadır.
Sayfa 80 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Sayfa 81 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Buradaki sigset_t türünün gerçekte hangi tür olduğu belli değildir. Ancak şüphesiz bu türün
bütün sinyallerin bloke durumunu içerecek durumda olması gerekir. sigset_t türü
değişebileceği için bunun içerisine bloke edilecek sinyalleri yerleştiren ara birim fonksiyonlar
düşünülmüştür. Örneğin, 32 sinyalin kullanıldığı bir sistemde sigset_t, unsigned long türü
olabilir. Her bit bir sinyalin blokede olup olmadığını gösterebilir.
Bloke kümesini belirleyen yani sigset_t türüyle işlem yapan bir grup fonksiyon vardır.
sigemptyset(sigset_t set);
sigfillset(sigset_t set);
sigaddset(sigset_t set, int signum);
sigemptyset(sigset_t set, int signum);
sigemptyset(const sigset_t set, int signum);
sigemtyset sinyallerin bloke kümesini boşaltır yani kümedeki tüm sinyaller blokesiz durumda
olur. sigfillset bloke kümesini tamamen doldurur yani kümedeki tüm sinyaller bloke
durumunda olur. sigdelset belirli bir sinyali bloke kümesinden çıkartır. sigismember belirli bir
sinyalin bloke kümesinin elemanı olup olmadığına bakar. sigaddset belirli bir sinyali bloke
kümesine ekler.
Örneğin, bir bloke kümesi tanımlayıp içerisine üç tane sinyal yerleştirecek olalım bu işlem
şöyle yapılabilir.
sigset_t sset;
sigemtyset(&sset);
sigaddset(&sset, SIGALRM);
sigaddset(&sset, SIGINT);
sigaddset(&sset, SIGTERM);
Yukarıdaki işlemlerle programcı yalnızca bir bloke kümesi belirlemiş durumdadır. Sinyalleri
bloke etmek için sigprocmask fonksiyonu kullanılır. sigprocmask fonksiyonunun ikinci ve
üçüncü parametreleri sigset_t türünden göstericilerdir. Fonksiyon ikinci parametresiyle
verilen sinyal kümesini processin bloke kümesi yapmaya çalışır. Bu işlem fonksiyonun birinci
parametresiyle belirlenir. Üçüncü parametre processin bir önceki sinyal bloke kümesidir,
ikinci parametre NULL geçilebilir. Bu parametrelerden herhangi biri NULL geçilebilir.
Fonksiyonun birinci parametresi şunlardan biri olabilir: SIG_BLOCK, SIG_UNBLOCK,
SIG_SETMASK. SIG_BLOCK, fonksiyonun ikinci parametresiyle belirlenen sinyalleri
processin sinyal bloke kümesine ekler. (Örneğin, processin sinyal bloke kümesinde üç sinyal
bloke durumda olsun. Biz fonksiyonun ikinici parametresinde sigset_t parametresinde iki
sinyali bloke olarak belirlemiş olalım. Fonksiyonun birinci parametresini SIG_BLOCK
yaparsak processin sinyal bloke kümesinde toplam 5 sinyal olacaktır.) SIG_UNBLOCK,
processin mevcut sinyal bloke kümesinden ikinci parametreyle belirtilmiş olan kümeyi
çıkartır. SIG_SETMASK, processin sinyal bloke kümesini tamamen silerek ikinci
parametreyle belirtilen kümeyi processin sinyal bloke kümesi yapar. Örneğin,
sigset_t sset;
sigemtyset(&sset);
sigaddset(&sset, SIGALRM);
Sayfa 82 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
sigaddset(&sset, SIGINT);
sigaddset(&sset, SIGTERM);
sigprocmask(SIG_BLOCK, &sset, NULL);
Eğer fonksiyon aşağıdaki gibi çağırılsaydı bu durumda processin sinyal kümesi tamamen bu
üç sinyalden oluşacaktı.
sigset_t sset;
sigemtyset(&sset);
sigaddset(&sset, SIGALRM);
sigaddset(&sset, SIGINT);
sigaddset(&sset, SIGTERM);
sigprocmask(SIG_SETMASK, &sset, NULL);
Bloke edilmiş olan bir sinyal tmamen blokesi çözülmedikten sonra saanki hiç oluşmamış gibi
işlem görür. Örneğin, biz pause fonksiyonunda bekliyor olalım. Blokede olan bir sinyalin
oluşmasıyla blokeden çıkamayız.
kill Fonksiyonu
Bu fonksiyon herhangi bir processe sinyal göndermek için kullanılan genel bir fonksiyondur,
maalesef kötü bir biçimde isimlendirilmiştir.
int kill(pid_t pid, int sig)
Fonksiyonun birinci parametresi sinyalin gönderileceği processin ID değeri, ikinci
parametresi gönderilecek sinyalin numarasıdır. Eğer birinci parametre özel olarak 0 girilirse
sinyal processin process grubundaki tüm processlere gönderilir. Eğer birinci parametre –1 ise
sinyal sistemdeki tüm processlere gönderilir. Fonksiyon başarılıysa 0 değerine başarısızsa –1
değerine geri döner. Fonksiyonun başarılı olabilmesi için sinyali gönderen processin kullanıcı
ID’ si (gerçek kullanıcı ID) yada etkin kullanıcı ID’ si sinyalin gönderildiği processin
kullanıcı ID’ sine yada etkin kullanıcı ID’ sine eşit olması gerekir. Buradan şu sonuçlar
çıkmaktadır.
1. Biz kill fonksiyonuyla her processe sinyal gönderemeyiz.
2. Biz SETUSERID bayrağı set edilmiş bir programı çalıştırdığımızda programın etkin
kullanıcı ID’si değişir. Fakat biz programa sinyal gönderebiliriz.
kill aynı zamanda bir shell komutudur, şüphesiz bu komutta aslında kill sistem fonksiyonunu
çağırmaktadır. Komut “kill <pid> ↵” biçiminde kullanılırsa programa SIGTERM sinyali
gönderilir. Komut genel olarak şu biçimlerde kullanılabilir.
kill [-no] <pid>↵
kill [-sembol] <pid>↵
Sembol kullanırken C’ deki sembolik sabitlerin başındaki SIG öneki kaldırılır. Örneğin,
kill –USR1 1716↵
kill –KILL 1813↵
Yada doğrudan sinyal numarası kullanılabilir.
kill –9 1813↵
Sayfa 83 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
kill sinyali pekçok sistemde 9 numaralı sinyaldir. Process tüm sinyallere kendini şöyle bloke
edebilir.
sigset_t sset;
sigfillset(&sset);
sigprocmask(SIG_BLOCK, &sset, NULL);
Daha önce de ele alındığı gibi SIGKILL ve SIGSTOP sinyalleri için sinyal fonksiyonu
yazılamaz, bu sinyaller bloke edilemez ve görmemezlikten gelinemez. Yani yukarıdaki
örnekte SIGKILL ve SIGSTOP sinyalleri dışındaki sinyaller bloke edilmiştir. Bilindiği gibi
SIGSTOP sinyali ile SIGTSTP sinyalinin her ikisi de processe SIGCONT sinyali gönderilene
kadar geçici olarak çizelge dışına çıkartılır. Fakat SIGSTOP sinyali ancak kill fonksiyonuyla
gönderilirken SIGTSTP klavyeden Ctrl+C tuşalrıyla gönderilmektedir. SIGSTOP sinyali
bloke edilemezken SIGTSTP bloke edilebilmekte, görmemezlikten geline bilmekte vebu
sinyal için sinyal fonksiyonu yazılabilmektedir.(Yani SIGSTOP il SIGTSTP arasındaki ilişki
SIGKILL ile SIGTERM arasındaki ilişkiye benzerdir.)
raise Fonksiyonu
kill fonksiyonu ile bir sinyal gönderildiğinde akış kill den geri döndüğünde bu sinyalin
işlenmiş yada iletilmiş olduğunun bir garantisi yoktur. Hatta kill ile kendi processimize sinyal
göndersek bile bu garanti değildir.
kill(getpid(), SIGUSR1);
Akış ok ile belirtilen noktaya geldiğinde sinyal fonksiyonunun çalıştırılmış olmasının bir
garantisi yoktur.
raise fonksiyonu processin kendisine sinyal göndermesi için kullanılan bir fonksiyondur.
int raise(int sig);
Fonksiyonun parametresi gönderilecek sinyalin numarasıdır. Fonksiyon başarı durumunda 0
başarısızlık durumunda 0 dışı bir değere geri döner. raise aynı zamanda bir standart C
fonksiyonudur. raise fonksiyonunun senkron olduğu garanti edilmiştir, yani program raise
fonksiyonunun çağırılması bittiğinde sinyalin işlenmiş olduğu garanti edilmiştir.
sigaction Fonksiyonu
sigaction fonksiyonu bu fonksiyon signal fonksiyonunun daha geniş ve güvenli biçimidir. Bu
fonksiyonun şu özellikleri vardır:
• Bu fonksiyon ile signal fonksiyonuyla yapılan herşey yapılabilmektedir.
Sayfa 84 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
• signal fonksiyonuna akış geçirildiğinde oluşan sinyal otomatik olarak bloke edilir. (A&T’
nin signal fonksiyonunda bu sinyal default duruma çekiliyordu. BSD’ nin signal
fonksiyonunda bu sinyal bloke edilmektedir.)
• Programın akışı sinyal fonksiyonuna geçtiğinde otomatik istenilen bazı sinyallerinde
bloke edilmesi sağlanabilmektedir.
• Diğer başka özelliklerde vardır.
int sigaction(int signum, const struct sigaction *act, struct sigaction *oldact);
Fonksiyonun birinci parametresi sinyal yapılacak fonksiyonun numarasıdır. İkinci parametre
set işleminin yapılacağı bilgileri içeren struct sigaction türünden bir yapının adresidir.
Fonksiyon eski set bilgilerini üçüncü parametresinde belirtilmiş yapının içerisine yerleştirir.
sigaction yapısı şöyledir:
struct sigaction {
void (*sa_handler)(int);
void (*sa_sigaction)(int, siginfo_t *, void *);
sigset_ t sa_mask;
int sa_flags;
}
Yapının sa_handler elemanı sinyal oluştuğunda çağırılacak fonksiyonu belirtir. Bu parametre
SIG_DFL ve SIG_IGN özel değerlerini alabilir. Yapının sa_sigaction elemanı da sinyal
oluşturulduğunda çağırılacak fonksiyonu belirtir. Genellikle birinci ve ikinci eleman bir union
biçiminde tutulmaktadır. Hangi sinyal fonksiyonunu tercih edileceği işletim sisteminden
işletim sistemine değişmektedir. (Hangi sinyal fonksiyonunun kullanılacağı yapısının
sa_siginfo elemanında belirtilecektir.) Yapının sa_mask elemanı sinyal fonksiyonu çalışırken
bloke edilecek diğer ilave sinyalleri belirtmektedir. sa_flags çeşitli SA_XXX değerlerinden
birini alabilir. Örneğin SA_NOMASK sinyal oluştuğunda sinyal fonksiyonu içerisinde oluşan
sinyalin bloke edilmemesini sağlar. SA_SIGINFO hangi sinyal fonksiyonunun kullanılacağını
belirtir. Eğer bu bayrak belirtilirse sa_sıgactıon ile belirtilen sinyal fonksiyonu, belirtilmezse
sa_handler parametresiyle belirtilen sinyal fonksiyonu kullanıllır. sa_flags parametresi
buradaki sembolik sabitlerin birleştirilmesiyle oluşturulabilir. sigaction fonksiyonun üçüncü
parametresi NULL geçilebilir. Aslında ikinci parametre de NULL geçilebilmektedir. İkinci
parametre NULL geçilirse davranış değişikliği olmaz. sigaction fonksiyonu aşağıdaki gibi
kullanılabilir:
int main(void)
{
struct sigaction sa;
sa.sa_handler = sigint_handler;
sigemptyset(&sa.sa_mask);
sa.sa_flags = 0;
if(sigaction(SIGINT, &sa, NULL) < 0){
perror(“sigaction”);
exit(EXIT_FAILURE);
}
pause();
printf(“main end...\n”);
return 0;
}
Sayfa 85 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Burada SIG_INT için sinyal fonksiyonu set edilmiştir. Sinyal fonksiyonunun çalıştığı süre
içerisinde başka hiçbir sinyal set edilmemiştir. sa_flags elemanını 0 geçmekle davranış
üzerinde özel bir belirleme yapılmamıştır.
Sayfa 86 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
sistem fonksiyonunun hangi nedenlerle başarısız olabileceği yani ERRNO değişkeninin hangi
değerleri alabileceği tek tek tespit edilmiştir. Bu durumda fonksiyonunun açıklamalarında hat
kaynağı olarak EINTR varsa bu fonksiyonun sinyal dolayısıyla başarısız olabilmesi
mümkündür.
read ve write fonksiyonları her zaman yavaş fonksiyonlar değildir. Biz bu fonksiyonlarla
normal dik üzerinde işlemler yapıyorsak bu fonksiyonlar hızlı fonksiyonlar gibi işlem görür
yani sinyallerle kesilmezler. Fakat bu fonksiyonlarla biz terminalden, pipelardan okuma
yazma yapıyorsak bu fonksiyonlar sinyallerle kesilebilirler.
Bir yavaş sistem fonksiyonu sinyalle kesilmiş olsun. Fonksiyonun yeniden çağırılması
gerekir, bunu programcı dikkate almalıdır. Tipik olarak aşağıdaki gibi bir döngü yapısı
kullanılabilir.
while((result = read(pipe, ...)) < 0 && errno == EINTR)
;
if(result < 0){
perror(“read”);
exit(1);
}
Şüphesiz programcı her türlü read işlemini böyle yapması gerekmez. Örneğin, hiçbir sinyal
için sinyal fonksiyonu yazmadıysa zaten process sonlandırılacağı için mantıksal olarak böyle
bir yeniden başlatma işlemine gerek yoktur. Ayrıca normal disk dosyalarında okuma yazma
yapılırken fonksiyonun sinyal fonksiyonları tarafından kesilmeyeceği de bilirnmektedir.
Yavaş sistem fonksiyonlarında kesilme sırasında BSD sistemleri otomatik yeniden çağırma
işlemi yapabilmektedir. Yani bu sistemlerde programcının yukarıdaki gibi bir döngü
kurmasına gerek kalmamaktadır. Fonksiyon başarısız olduğunda sistem tarafından yeniden
çağırılmaktadır. Ancak AT&T system V ve türevlerinde böyle bir sistem ve otomatik çağırma
yapılmamaktadır.
POSIX, sigaction fonksiyonunda yavaş sistem fonksiyonlarının otomatik yeniden çalıştırılıp
çalıştırılmayacağı sinyal fonksiyonu set edilirken belirlene bilmektedir. struct sigaction
yapısının sa_flags elemanına SA_RESTART değeri girilirse otomatik yeniden başlatma
sistem tarafından yapılır. Bu durumda programcının yukarıdaki gibi bir döngü yapısı
kullanmasına gerek kalmaz. İşte eski signal fonksiyonun böyle bir yeteneği yoktur.
sigsuspend Fonksiyonu
Bazı uygulamalarda belirli bir sinyal gelene kadar pause ile bekleme teması görülür. Örneğin
bir process belirli bir işlemi bitirdikten sonra diğer processi SIGUSR1 sinyalini
göndererekharekete geçirebilir. Bu durumda pause işlemine kadar o sinyalin gelmemesi
garanti edilmelidir. Yoksa, pause işlemi sürekli beklemeye yol açabilir. Bu durumda ilk akla
gelecek yöntem program başlar başlamaz tüm sinyalleri bloke etmek sonra onları açarak
hemen pause işlemine girmektir.
main(){
sigprocmask();
...
sigprocmask();
pause();
...
}
Sayfa 87 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
Burada birinci sigprocmask işleminde ilgili sinyal bloke edilmiş diğerinde açılmıştır. Fakat
maalesef yukarıdaki kodda yine bir kusur vardır ama ikinci sigprocmask işleminden hemen
sonra ama pause işleminden önce sinyal gelirse akış pause fonksiyonuna girdiğinde orada
sonsuza kadar kalabilir. Bu işlemin tek çözümü bloke çözme işlemiyle pause işleminin atomik
yapılmasıdır.
int sigsuspend(const sigset_t *mask);
Fonksiyon parametresiyle belirtilen sinyal kümesini processin sinyal bloke kümesi yapar ve
pause işlemiyle bekler. Programcı normal olarak parametrede belirtilen sinyal kümesini
blokeyi açacak biçimde belirlemelidir. Fonksiyon parametresiyle belirtilen sinyal kümesini
geçici olarak processin sinyal kümesi yapmaktadır. Yani sigsuspend fonksiyonundan
çıkıldığında processin sinyal bloke kümesi eski haline getirilir.
sigsuspend fonksiyonunun geri dönüş değeri her zaman –1 dir.Zaten bu fonksiyondan ancak
sinyal oluştuğunda çıkılabilir. Yani geri dönüş değerinin bir önemi yoktur. Tipik olarak bir
sinyalin oluşmasını atomik olarak beklemek şöyle yapılabilir:
{
sigset_t set_new, set_old, set_unblock;
sigfillset(&set_unblock);
sigdelset(&set_unblock, SIGUSR1);
setemptyset(&set_new);
setaddset(&set_new, SIGUSR1);
sigprocmask(SIG_BLOCK, &set_new, &set_old);
/*kritik kod*/
sigsuspend(&set_unblock);
sigprocmask(SET_SETMASK, &set_old, NULL);
}
Burada önce SIGUSR1’ in blokesinin açıldığı bir sinyal kümesi oluşturulmuştur, daha sonra
SIGUSR1 bloke edilerek kritik koda girilmiştir. Şimdi iki olasılık vardır; ya kritik kod
içerisinde SIGUSR1 oluşur yada oluşmaz. Eğer oluşursa zaten bloke durumda kalacaktır, işte
sigsuspend fonksiyonu atomik bir biçimde blokeyi açarak pause işlemi yapar. sigsuspend
fonksiyonundan çıkıldığında processin sinyal bloke kümesi SIGUSR1’ in bloke edilmiş
olduğu eski duruma döndürülür, daha sonra işlemler bittiği için SIGUSR1’ in blokesinin
kaldırıldığı eski duruma geri dönülmüştür. Şüphesiz yukarıdaki işlemde SIGUSR1 sinyali için
bir sinyal fonksiyonu yazılmış olmalıdır, yoksa zaten pause işleminden çıkılsa bile process
sonlanır. (Bilindiği gibi önce sinyal fonksiyonu çalıştırılıp sonra pause fonksiyonundan
çıkılmalıdır. Bu durum POSIX standartlarında garanti altına alınmıştır.)
sigpending Fonksiyonu
Bu fonksiyon o anda askıda olan sinyallerin ne olduğunu anlamak için kullanılır.
int sigpending(sigset_t *set);
Fonksiyon o anda oluşmuş fakat askıda bekletilen sinyalleri parametresiyle belirtilen sinyal
kümesine yazar. Fonksiyonun geri dönüş değeri başarı durumunda 0, başarısızlık durumunda
–1’ dir.
{
setemptyset(&set_new);
setaddset(&set_new, SIGUSR1);
sigprocmask(SIG_BLOCK, &set_new, &set_old);
while(sigpending(&set_pending) == 0 && )/*eksik*/
Sayfa 88 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
sigsuspend(&set_unblock);
sigprocmask(SET_SETMASK, &set_old, NULL);
}
Sayfa 89 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
C++’ daki exception handling mekanizmasıda bu amaca yöneliktir. C++’ daki exception
handling mekanizması fonksiyonlar arası dallanma mekanizmasına benzemektedir.
#include <stdio.h>
#include <stdlib.h>
#include <setjmp.h>
enum {ERROR_FILE = 1, ERROR_MEMORY = 2};
void set_error_handler(void);
void error_handler(int errno);
jmp_buf g_pos;
void func_other(void)
{
FILE *f;
if ((f = fopen("hgjhg", "rb")) == NULL)
longjmp(g_pos, ERROR_FILE);
}
void func(void)
{
func_other();
}
int main(void)
{
set_error_handler();
// ...
func();
return 0;
}
void set_error_handler(void)
{
int result;
if ((result = setjmp(g_pos)) != 0)
error_handler(result);
}
void error_handler(int errno)
{
switch (errno) {
case ERROR_FILE:
fprintf(stderr, "File error!..\n");
break;
case ERROR_MEMORY:
fprintf(stderr, "Memory error!..\n");
break;
}
exit(1);
}
Sayfa 90 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
bir takım işlemler yapıldıktan sonra akışın başka bir noktadan devam ettirilmesi istenebilir.
Bunun için sinyal fonksiyonu içerisinde longjmp kullanmak gerekir.
Sinyal fonksiyonunun içerisinden nasıl longjmp yapılmaktadır? Sinyal oluştuğunda işletim
sistemi aşağıdaki gibi bir kodu uygular.
Signal fonksiyonunu çağır();
Kalınan yerden devam et();
Biz sinyal fonksiyonunun içine longjump yapınca tamamen akışı işletim sisteminin içindeki
noktadan koparmış oluruz. Yani artık akış sinyal fonksiyonunu bitirmeyeceğinden dolayı eski
noktaya da geri dönmeyecektir.
Bilindiği gibi sigaction fonksiyonuyla sinyal set edildiğinde sinyal fonksiyonu süresince
oluşan sinyalin kendisi ve sinyal fonksiyonunda belirtilen sinyaller bloke edilmektedir. Biz
sinyal fonksiyonu içerisinde longjump yaptığımızda bu sinyaller blokede kalmış olur. Çünkü
blokenin çözülmesi yani eski haline getirilmesi sinyal fonksiyonunun çalışması bittiğinde
işletim sistemi tarafından yapılmatadır. Halbuki longjump işlemiyle bu şans verilmemektedir.
İşte bu yüzden jump fonksiyonlarının blokeyi çözebilen versiyonları tanımlanmıştır.
sigsetjmp, siglongjmp fonksiyonları sinyal fonksiyonu içerisinden longjump yapma için
kullanılmaktadır. bunların standart C fonksiyonlarından tek farkı longjump işlemi sırasında
sanki sinyal fonksiyonu bitmiş gibi sinyal blokesini eski haline getirmesidir.
int sigsetjmp(sigjmp_buf env, int savesigs);
Fonksiyonun ikinci parametresi 0 dışı herhangi bir değerdeyse bloke kümesi eski haline
getirilir. 0 ise fonksiyonun setjmp fonksiyonundan bir farkı yoktur.
int siglongjmp(sigjmp_buf env, int val);
Bu fonksiyonun parametrik yapı olarak longjmp fonksiyonundan bir farkı yoktur.
Sayfa 91 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
alamayacaktır. En iyi çözüm döngü içerisinde bloke olmadan aygıtlara bakmak bilgi oluştuğu
zaman oluşan aygıttan almaktır. Bu tür uygulamalarda hiçbir aygıttaq bilgi oluşmadığında
gereksiz bir CPU zamanı kaybı olmaktadır. İşte bu durum select yada poll gibi fonksiyonlarla
çözülebilmektedir.
Bir dosya üzerinde blokesiz işlem yapabilmek için dosya açılırken açış moduna
O_NONBLOCK bayrağının eklenmesi gerekir. dosya açıldıktan sonrada fcntl fonksiyonu ile
dosyanın açış modu değiştirilebilmektedir. Blokesiz modda read fonksiyonu şöyle
çalışmaktadır.
1. Eğer okunacak hiçbir bilgi oluşmamışsa read bloke olmaz. Başarısızlık değeri olan
–1 ile geri döner ve errno değişkeni EAGAIN değişkeniyle set edilir.
2. Eğer okunacak en az 1 byte bilgi varsa read okuyabildiğini okur, okunan byte
sayısına geri döner.
Blokesiz modda yazma işlemi de şöyle gerçekleştirilir.
1. write fonksiyonu hiçbir bilgi yazamadıysa başarısızlık anlamına gelen –1 değeriyle
geri döner ve errno değişkeni EAGAIN değeriyle set edilir.
2. En az 1 byte’ ın yazılabilmesi mümkün ise write fonksiyonu yazabildiğini yazar ve
yazabildiği byte sayısına geri döner.
Blokesiz okuma işlemlerinin tipik algoritması aşağıdaki gibidir. Aşağıdaki kalıpta dosyadan
size kadar bilgi buf ile belirtilen tampona aktarılmaya çalışılmış fakat akışın read
fonksiyonunda bloke olması engellenmiştir.
for(index = 0; size > 0;){
n = read(fd, buf + index, size);
if(n == 0)
break;
if(n != -1){
else if(errno != EAGAIN){
perror(“read”);
exit(EXIT_FAILURE);
}
}
..... Arka plan işlemler
size kadar byte ı blokesiz yazma işlemleri de benzer biçimde yapılmaktadır.
for(index = 0; size > 0;){
n = write(fd, buf + index, size);
if(n == 0)
break;
if(n != -1){
else if(errno != EAGAIN){
perror(“write”);
exit(EXIT_FAILURE);
}
}
..... Arka plan işlemler
Sayfa 92 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
index = 0;
for(;;){
...
n = read(fd, buf + index, size);
if(n = -1 && errno == EAGAIN)
continue;
size -= n;
index += n;
if (size ==0)
break;
}
Birden fazla pipe için birer bytelık blokesiz okuma yapacak olalım. Bu işlem şöyle yapılabilir.
close_flag1 = close_flag2 = 1;
do{
if(close_flag1){
n1 = read(fd1, &ch, 1);
if(n1>0)
printf(“pipe1: %c\n”, ch);
else if(n1==0)
close_flag1 = 0;
else if(n1 == -1 && errno != EAGAIN){
perror(“read”);
exit(EXIT_FAILURE);
}
}
if(close_flag2){
n2 = read(fd2, &ch, 1);
if(n2>0)
printf(“pipe2: %c\n”, ch);
else if(n2==0)
close_flag1 = 0;
else if(n2 == -1 && errno != EAGAIN){
perror(“read”);
exit(EXIT_FAILURE);
}
}
}while(close_flag1 || close_flag2);
Birden fazla betimleyiciden n bytelık kayıt okuyan kalıp daha genel yazılabilir.
Sayfa 93 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
bırakmak en az bir betimleyicide bilgi varsa bilgi olan betimleyicilerdeki bilgiyi almaktır. İşte
bu işlem için select yada poll fonksiyonları kullanılmaktadır.
int select(int n, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval
*timeout);
Fonksiyon bir betimleyici kümesini alarak eğer bu betimleyicilerden herhangi birinde olay
gerçekleşene kadar threadi bloke eder. Fonksiyonun en kötü olasılıkla bir zaman aşaımı
paramteresi de avrdır. Zaman aşımı hiçbir betimleyicide olay gerçekleşmemişse en kötü
olasılıkla beklenecek zaman miktarını belirtmektedir. betimleyici kümesi fd_set türüyle
belirlenmiştir. Fd_set isminin gerçekte hangi tür olduğunun programcının bilmesine gerek
yoktur. Fakat bu tür pekçok derleyicide içinde bir dizi elemanı olan yapı elemanıdır.
Genellikle bu küme aslında bitsel bir biçimde oluşturulmuştur. Yani örneğin, dizi tüm
betimleyicileri içerecek biçimde bir bit sayısına sahiptir. İlgili bit 0 ise o bit indexsiyle
belirtilen betimleyici kümede yok, 1 ise kümede vardır. programcı fd_set türünden bir
değişken tanımlar önce tüm betimleyicileri küme dışı bırakmak için FD_ZERO makrosunu
kullanır. Belirli bir betimleyiciyi kümeye eklemek için FD_SET, belirli bir betimleyiciyi
kümeden çıkarmak için FD_CLR kullanılır. FD_ISSET makrosu belirli bir betimleyicinin
kümeye dahil olup olmadığına bakar.
FD_CLR(int fd, fd_set *set);
FD_ISSET(int fd, fd_set *set);
FD_SET(int fd, fd_set *set);
FD_ZERO(fd_set *set);
select fonksiyonunun birinci parametresi fonksiyondan izlemesinin istediğimiz betimleyici
kümesindeki en yüksek indisli betimleyici değerinin bir fazlasını alır. Örneğin, biz 9 ve 23
numaralı betimleyicileri blokesiz okumak isteyelim. Bu parametre 24 olarak girilmelidir.
Aslında bu parametre fonksiyonda hiç bulunmayabilirdi, fakat fd_set ile belirtilen kümedeki
dahil edilmiş elemanların kolay bulunabilmesi için bu parametre düşünülmüştür. Bu
parametre sayesinde select fonksiyonu kendi içerisinde fd_set türüyle belirlenmiş olan
kümeye dahil olan betimleyicileri tespit edebilmek için maximum betimleyici sayısına kadar
inceleme yapmaz. Örneğin, biz bu parametreyi 24 olarak girdiğimizde fonksiyon fd_set
türüyle belirtilen bit yapısının ilk 23 elemanına bakark 9 ve 23 indexli elemanların set
edildiğine bakacaktır. Programcının bu parametreyi sağlıklı belirleyebilmesi için izlemek
istediği betimleyicilerin en büyük değerinin bulması gerekir. Eğer programcı bunu yapmak
istemiyorsa bu parametreye olabilecek en büyük değeri verebilir. Sistemdeki maximum
betimleyici sayısı elde edilebilmektedir. (Bu sayı Linux sistemlerinde 1024 tür.) select
fonksiyonunun ikinci parametresi okuma olayı için izlenecek betimleyicileri belirlemekte,
üçüncü parametresi ise yazma olayı için izlenecek betimleyicileri belirlemekte kullanılır.
Dördüncü parametre burada ele alınmayacak özel bir duruma yöneliktir, bu parametreler
NULL geçilebilir. Fonksiyonun son parametresi zaman aşımını belirtmek için gereken zaman
bilgisinin belirlemekte kullanılır. Bu parametre NULL geçilirse zaman aşımı uygulanmaz.
select fonksiyonu başarı ile geri döndüğünde fd_set ile belirtilen kümeyi tamamen 0’ lar ve bu
kümede ilgili olayın gerçekleştiği betimleyicileri set eder. Örneğin, fonksiyonu şöyle
kullanmış olalım:
fd_set set;
FD_ZERO(&set);
FD_SET(fd1, &set);
Sayfa 94 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
FD_SET(fd2, &set);
FD_SET(fd3, &set);
select(MAX(fd1, fd2, fd3) + 1, &set, NULL, NULL, NULL);
Burada fd1, fd2 ve fd3 betimleyicilerindeki okuma olayları izlenmek istenmektedir.
Fonksiyon bu betimleyicilerden herjhangi birinde okumak için bilgi oluştuğunda blokeyi
çözer ve geri döner. fd2 betimleyicisinde bir okuma olayı olduğunu varsayalım. Fonksiyon
geri döndüğünde set ile belirtilen kümenin yalnızca fd2 elemanı set edilmiş durumda
olacaktır. O halde programcı select fonksiyonundan çıktığında olayın hangi betimleyicide
gerçekleştiğini aşağıdaki gibi tespit etmelidir.
if(FD_IS SET(fd1, &set)){
...
}
if(FD_IS SET(fd2, &set)){
...
}
if(FD_IS SET(fd3, &set)){
...
}
select fonksiyonunun parametreleri olan betimleyici kümeleri ile verilen betimleyicilerin açık
durumda olması gerekmektedir. Eğer döngü içerisinde okuma işlemi bitirildiğinde betimleyici
close işlemiyle kapatıldığında programcının betimleyiciyi betimleyici kümesinden de
çıkarması gerekir. Tabii programcı isterse döngü içerisinde betimleyiciyi kapatmaz, bu
durumda bu betimleyiciyi kümeden çıkartması da gerekmez.
Sayfa 95 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
select fonksiyonu sleep fonksiyonunun duyarlı bir biçimi olarakta kullanılabilir. Bunun için
tüm betimleyici kümeleri NULL ile geçilir. Zaman aşımı olarak beklenecek miktar verilir.
Fonksiyon bakacak betimleyici bulamayacağı için belirlenen zaman kadar bekler.
Anahtar Notlar:
Unix/Linux dökümanlarında processler arası haberleşme yöntemleri (IPS) konusu semafor
senkronizasyon nesnelerini de kapsamaktadır. Halbuki semaforlar haberleşme konusundan
daha çok senkronizasyon konusuyla ilgilidir. Semaforlar kursumuzda bu başlık altında ele
alınmayacaktır.
Sayfa 96 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
engellenmesinin garanti bir yöntemi yoktur. Bir çakışma olduğunda program sonlandırılabilir
yada başka bir değerde anlaşma sağlanabilir. Sayısal belirteçi oluşturabilmek için ftok gibi bir
fonksiyon önerilmişse de bu fonksiyonu kullanmanında çakışmanın olmaması yönünde bir
garantisi yoktur. ftok fonksiyonu daha esnek bir belirleme yapılmasında yardımcı olmaktadır.
Sayfa 97 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
msgget Fonksiyonu
Bu fonksiyon yeni bir mesaj kuyruğu yaratmak yada olanı açmak içn kullanılmaktadır.
int msgget(key_t key, int msgflg);
Fonksiyonun birinci parametresi yaratılacak yada açılacak olan mesaj kuyruğu nesnesinin
(ipc) sayısal belirteç değeridir. Bu değer yeni bir mesaj kuyruğu yaratılacaksa IPC_PRIVATE
olarak girilebilir. IPC_PRIVATE sistemdeki mesaj kuyruklarının belirteçlerine bakarak
olmayan bir belirtece ilişkin mesaj kuyruğu yaratır. Fonksiyonun ikinci parametresi 0
geçilebilir yada IPC_CREAT ve IPC_EXCL biçiminde girilebilir. Eğer yalnızca IPC_CREAT
belirlemesi yapılırsa fonksiyon birinci parametresiyle belirtilen belirtece ilişkin mesaj
kuyruğu yoksa yaratır, varsa olanı açar. IPC_EXCL tek başına kullanılmaz, acak
IPC_CREAT ile birlikte kullanılabilir. Bu durumda belirtece ilişkin mesaj kuyruğu yoksa yeni
mesaj kuyruğu yaratılacak varsa fonksiyon –1 ile geri dönecektir. Bu parametre 0 olarak
geçilirse birinci parametresiyle belirtilen mesaja ilişkin kuyruk varsa açılır, yoksa fonksiyon
başarısızlıkla geri döner. Fonksiyonun birinci parametresi IPC_PRIVATE olarak girilirse
ikinci parametrede ne yazılırsa yazılsın olmayan belirtece ilişkin yeni bir kuyruk yaratılır.
Fonksiyonun birinci parametresi IPC_PRIVATE olarak geçildiğinde belirteç değerini sistem
kendisi tespit eder, fakat bu değeri programcı bilmez. Bu nedenle böyle bir yaratma ancak üst
ve alt processlerde kullanılabilir.
İki processin mesaj kuyruğuyla haberleşeceğini düşünelim. Her iki processte de msgget
fonksiyonu aynı belirteç değeri verilerek IPC_CREAT bayrağı kullanılarak çağırılır. Böylece
ilk çalıştırılan process kuyruğu yaratmış olur, ikinci çalıştırılanda açmış olur. Fakat belirteç
değerinin çakışması durumunda bir problem yaşanabilecektir. Bunu engellemek için mesaj
kuyruğu IPC_CREAT | IPC_EXCL ile yaratılmaya çalışılabilir. Böylece programcı daha önce
böyle bir kuyruğun olup olmadığını anlayabilecektir. Tabii kuyruğun processlerden biri
Sayfa 98 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
tarafından yaratılması daha uygundur. Yada tamamen farklı üçüncü bir process kuyruğu bu
yöntemle yaratıp bunu bir dosya yada pipe yoluyla yada komut satırı argümanlarıyla diğer
processlere geçirebilir. msgget fonksiyonunun geri dönüş değeri mesaj kuyruğunun ID
değeridir. Programcı fonksiyonun ikinci parametresinde IPC_CREAT geçmişse yaratılacak
kuyruk nesnesinin erişim haklarınıda belirtmelidir. Erişim hakları user, owner, other
biçiminde 9 bit ile belirtilebilir. Örneğin,
id = msgget (KEYVAL, IPC_CREAT|0666);
Görüldüğü gibi haberleşme nesnelerinin de erişim hakları vardır. Yani örneğin biz istemezsek
root dışındaki kullanıcıların kendi yarttığımız mesaj kuyruğunu kullanmasını engelleyebiliriz.
Sistem
1GB
Her processler arası geçişte düşük anlamlı 3GB’ lik kısım bellekten atılarak geçilen processin
bilgileri yüklenmektedir. Böylece farklı processlerdeki aynı adreslerde aynı bilgiler bulunmak
zorunda değildir. Processlerin bellek alanları tamamen birbirinden izole edilmiştir. Ayrıca
normal processler sistem alanına da erişemezler.
Processlerin bellek alanları izole edildiği için iki process aynı bellek alanını paylaşamaz.
Paylaşımlı bellek alanları yönteminde işte ortak bir bellek alanının processler arasında
Sayfa 99 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org
paylaşılması sağlanmaktadır. Bu yönteme göre işletim sistemi fiziksel RAM’ de bir alanı
belirleyerek farklı processlerin bu alana sanki kendi adres alanları içerisinde bir bölgeymiş
gibi erişmesini sağlar. Bu yöntemde iki process farklı adreslerle gerçekte aynı bölgeye aynı
fiziksel RAM’ e erişmek zorundadır.
Paylaşımlı bellek alanlarının kullanımı tipik olarak şöyle yapılmaktadır.
1. shmget fonksiyonu ile IPC nesnesi yaratılır ve ID elde edilir. Yaratılma sırasında
paylaşılan alanın uzunluğu belirtilir.
2. shmat fonksiyonu ile paylaşılan bellek alanı için sanal bellekte bir adres elde edilir.
3. shmdt fonksiyonu ile elde edilen sanal bellek adresi geri bırakılır.
4. Paylaşımlı bellek alanı shmctl fonksiyonu ile silinebilir.
Paylaşımlı bellek alanı yöntemi çok hızlı bir yöntemdir, fakat mesaj kuyruklarında olduğu
gibi bir senkronizasyon sistemi mevcut değildir. Bu senkronizasyon işlemi ayrıca programcı
tarafından sağlanmak zorundadır.
Bir processin bir bilgi üretip paylaşılan bir bellek alanına yazdığını diğer processin de bu
bilgiyi buradan alarak işlediğini düşünelim. Bu tür problemlere genel olarak üetici tüketici
problemleri denilmektedir. Üretici ve tüketici processler birbirinden bağımsız olarak
çalışmaktadır. Senkronizasyon sağlanması için üreticinin tüketici almadan yeni bir bilgiyi
paylaşımlı alana bırakmaması gerekir. Benzer biçimde tüketicinin de yeni bir bilgi gelmeden
paylaşımlı alana başvurmaması gerekmektedir.
Üretici- tüketici problemlerinde senkronizasyonun sağlanması için semaforlar gibi
senkronizasyon oluşturan mekanizmalara gereksinim vardır.
Üretici- tüketici problemleri Unix sistemlerinde belli bir düzeye kadar sinyal
mekanizmalarıyla çözümlenebilir. Örneğin, üretici bilgiyi paylaşılan bellek alanına yazdıktan
sonra tüketiciye sinyal gönderebilir, tüketicide sinyal fonksiyonu içerisinde bilgiyi alabilir.
Benzer biçimde tüketici yeni bir bilginin gönderilmesini yine sinyal göndererek talep edebilir.
sanal adresin paylaşılan bellek alanına bağlanmasını talep edebilir. İkinci parametre NULL
geçilebilir, bu durumda böyle bir talep söz konusu olmaz ve sistem kendi tespit ettiği bir adres
ile bağlama işlemi yapar. İkinci parametre SHMLBA değerinin (Örneğin intel işlemcilerinde
4Klık sayfa uzunluk değeridir.) katları olmak zorundadır. SHMLBA <sys/shm.h> içerisinde
bir sembolik sabittir. Eğer fonksiyonun üçüncü parametresinde SHM_RND bayrağı girilirse
bu durumda ikinci parametredeki adresin SHMLBA katlarında olması gerekmez. Sistem bu
değeri otomatik olarak aşağıya doğru SHMLBA katlarına yuvarlar. Şüphesiz ikinci
parametrede belirtilen adresin tahsis edilebilmesi için boş ve uygun bir adres olması gerekir.
Eğer bu adres uygun değilse fonksiyon NULL değeriyle geri döner. Fonksiyonun üçüncü
parametresi SHM_RND yada SHM_RDONLY bayraklarından oluşturulabilir.
SHM_RDONLY paylaşılan adrese yazma yapılamayacağını belirtmektedir. Fonksiyonun geri
dönüş değeri başarı durumunda tahsis edilmiş olan adres, başarısızlık durumunda -1 değeridir.
Thread Kavramı
Thread bir processin farklı çizelgelenen farklı akışlarına denir. Örneğin, bir processin
birbirinden bağımsız çalışan üç akışı olabilir. Her akış sanki ayrı bir programmış gibi farklı
bir quanta içerisinde çizelgelenmektedir. İşletim sistemi sistemdeki tüm threadleri çizelgeler
her threadi belirli bir zaman dilimi içerisinde çalıştırır. Çalışmasına ara vererek sıradaki
threadi çalıştırır. Bir threadin çalışmasına ara verilmesi ve başka bir threadin çalıştırılması
işlemine threadler arası geçiş denilmektedir. Threadler arası geçiş aynı processin threadleri
arasında yada farklı processin threadleri arasında olabilir. Aynı processin threadleri arasında
geçişte processin bellek alanı boşaltılmaz. Fakat farklı processin threadleri arasında geçişte
processin bellek alanı boşaltılır.
Process çalışmaya başladığında bir ana threade sahiptir. Yani akış main fonksiyonuna
girdiğinde bir thread vardır. Bu thread fork işlemi sırasında işletim sistemi tarafından
oluşturulmuştur ve buna processin ana threadi denir. Bir thread pthread_create fonksiyonuyla
herhangi bir thread akışında yaratılabilir.
pthread_create fonksiyonuyla thread yaratılırken thread akışının başlatılacağı fonksiyon
parametre olarak verilir. pthread_create fonksiyonunda belirtilen fonksiyon thread akışının
başlatılacağı fonksiyondur. Thread bir akış belirtir, farklı iki thread aynı fonksiyon üzerinde
ilerliyor olabilir.
Bir thread üç biçimde sonlanabilir.
1. Thread fonksiyonu bittiğinde otomatik olarak thread sonlanır.
2. pthread_exit fonksiyonu çağırılan threadi sonlandırır.
3. pthread_cancel fonksiyonu başka bir threadi sonlandırmak için kullanılır.
pthread_create i.erisinde yeni bir akışın yaratılması aşağıdaki gibi yapılmaktadır.
pthread_create (...)
{
• yeni bir akış oluştur ve yeni akışı NEWTHREAD noktasından başlat
• return 0;
NEWTHREAD:
• Thread fonksiyonunu çağır;
• Thread fonksiyonunu geri dönerse çizelge elemanını sil
• Akışı yok et
}
Görüldüğü gibi yeni thread akışı pthread_create fonksiyonu içerisindeki NEWTHREAD
noktasından başlatılmıştır. pthread_create fonksiyonunu çağıran thread normal olarak
sonlanır. Yeni akış pthread_create içerisinden başlatılır. Böylece thread fonksiyonu çalışmış
olur, thread fonksiyonu sonlandığında akış pthread_create fonksiyonuna geri döner ve o
noktada thread akışı sonlandırılır. pthread_exit fonksiyonu yalnızca kendi threadini
sonlandıran bir fonksiyondur. Halbuki _exit fonksiyonu tüm processi sonlandırır. Bir process
sonlandığında processin tüm threadleri sistem tarafından otomatik olarak sonlandırılmaktadır.
Programın akışı main fonksiyonunu bitirdiğinde exit sıtandart C fonksiyonu çağırılmakta exit
fonksiyonu tüm dosyaları kapattıktan sonra _exit fonksiyonuyla processi sonlandırmaktadır.
pthread_create fonksiyonuyla yeni bir thread yaratıldığında akışın hangi threadden
başlatılacağı sistemden sisteme değişebilir. Yani örneğin, akış yeni thread çizelgelenerek
oradan devam edebilir yada eski threadden devam edilebilir.
Aynı processin threadleri arasındaki haberleşme için global değişkenler kullanılabilir. Çünkü
tüm threadler aynı global değişkenleri görürler. Heap bölgeside tüm threadler arasında ortak
bir alandır. Fakat her threadin stack alanı birbirinden ayrılmıştır. Yani iki farklı thread aynı
fonksiyon üzerinde ilerlerken yerel değişkenlerin farklı kopyaları üzerinde işlem yaparlar.
POSIX sistemlerinde yaratılan bir threadin stack alanı standart olarak belirlenmemiştir, yani
sistemlerde farklı uzunluklarda olabilir. Fakat genel olarak 64K dan daha düşük bir thread
kullanılmamaktadır.
Threadlerin ID Değerleri
Tıpkı processlerde olduğu gibi threadlerinde bir ID değeri vardır. Threadlerin ID değerleri
sistem genelinde tek değildir. Threadlerin ID değerleri de processlerin ID değerlerinde olduğu
gibi handle alanına ulaşmak için kullanılır.
pthread_create Fonksiyonu
Fonksiyonun prototipi şöyledir:
int pthread_create(pthread_t *thread, pthread_attr_t *attr, void * (*start_routine)(void*),
void * arg);
Threadlerin ID değerleri pthread_t türüyle temsil edilebilir. pthread_t Linux sistemlerinde
unsigned long olarak typedef edilmiştir. Fonksiyonun birinci parametresi thread ID sinin
yerleştirileceği pthread_t türünden nesnenin adresidir. Fonksiyonun ikinci parametresi thread
özelliklerini belirlemekte kullanılan pthread_attr_t türünden yapının adresidir. Bu parametre
NULL geçilebilir, bu durumda thread default özelliklerle yaratılır. Fonksiyonun üçüncü
parametresi thread fonksiyonunun başlangıç adresini belirtir. Thread fonksiyonunun
parametrik yapısı aşağıdaki gibi olmak zorundadır.
void *thread_func(void *ptr);
Görüldüğü gibi thread fonksiyonun geri dönüş değeri void* türündendir. void* geri dönüş
değeri tam sayısal bir değerden daha genel olduğu için tercih edilmiştir. Fonksiyonun son
parametresi thread fonksiyonuna geçirilecek parametreyi belirtir. Fonksiyon başarı
durumunda 0 değerine başarısızlık durumunda hatanın nedenini belirten 0 dışı bir değere geri
döner.
Pthread kütüphanesindeki fonksiyonlar genel olarak errno değişkenini set etmezler. Bu
nedenle hata mesajı perror fonksiyonuyla yazdırılmamalıdır. Thread fonksiyonları hata
durumunda errno değerinin kendisine geri dönerler. Hata mesajı strerror fonksiyonuyla
yazdırılabilir. Örneğin;
...
int result;
result = pthread_xxx();
if (result){
fprintf(stderr, “%s\n”, strerror(result));
exit(EXIT_FAILURE);
}
pthread_exit Fonksiyonu
Bu fonksiyon threadi sonlandırmak için kullanılır.
void pthread_exit(void *retval);
Fonksiyonun parametresi thread fonksiyonuna geçirilecek parametreyi belirtir.
Fonksiyonun birinci parametresi beklenecek threadin ID değeri, ikinci parametresi ise thread
fonksiyonunun geri dönüş değerinin yerleştirileceği void göstericinin adresidir. Bir threadin
bu fonksiyonla beklenmesi için threadin “joinable” olması gerekir. “detached” threadler bu
fonksiyonla beklenemezler. Fonksiyon başarılıysa 0 değerine başarısızsa hata koduna geri
döner. fonksiyonun ikinci parametresi NULL geçilebilir. Bu durumda threadin geri dönüş
değeri yerleştirilmez.
void *func(void *ptr)
{
int i;
for(i = 0; i < 10; ++i){
printf(“%s thread\n”, (char *) ptr);
sleep(1);
}
return (void *) 100;
}
int main(void)
{
pthread_t tid;
void *ptr;
if(pthread_create(&tid, NULL,func,”my thread”) != 0){
fprintf(stderr, “cannot create thread!...\n”);
exit(EXIT_FAILURE);
}
printf(“main thread\n”);
pthread_join(tid, &ptr);
printf(“%d\n”, (int) ptr);
return 0;
}
Eğer pthread_join ile beklediğimiz thread zaten sonlanmışsa bu durumda fonksiyon hiç
bekleme yapmaz thread fonksiyonunun geri dönüş değerini alarak başarılı biçimde sonlanır.
pthread_self Fonksiyonu
Normal olarak bir threadin ID si onu yaratan thread tarafından elde edilmektedir. Fakat bir
thread istediği zaman pthread_self fonksiyonu ile kendi ID sini elde edebilir.
pthread_t pthread_self(void);
int main(void)
{
printf(“%lu\n”, pthread_self());
return 0;
}
fonksiyonlar tarafından ortak erişilebilecek hemde her thread için farklı olacak bir alanın
bulunması gerekir. Threade özgü global alanlar ileride ele alınacaktır.
pthread_atfork Fonksiyonu
fork işlemi yapıldığında üst processin yalnızca bir tek threadi alt proceeste çalışmaya devam
etmektedir. fork işlemiyle birlikte programcı alt processe geçirilmeyen threadler için bir takım
son işlemler yapacak olabilir. Örneğin mutex nesnelerinin serbest bırakılması gerekebilir.
Bunun için pthread_atfork fonksiyonu düşünülmüştür.
int pthread_atfork(void (*prepare)(void), void (*parent)(void), void (*child)(void));
Bu fonksiyon fork işlemi sırasında çağırılacak bir grup fonksiyonu belirlemek için
kullanılmaktadır. Birinci parametreyle belirtilen fonksiyon fork tarafından alt process henüz
yaratılmadan önce bir kez çağırılır. İkinci parametreyle belirtilen fonksiyon fork tarafından üst
process için alt process yaratıldıktan hemen sonra çağırılır. Üçüncü fonksiyon alt process
yaratıldıktan sonra fork tarafından alt processte bir kez çağırılmaktadır. Fonksiyon başarılıysa
0, başarısızsa hata koduna geri döner. Parametreler NULL olarak geçilebilir, bu durumda
fonksiyonlar için çağırma yapılamaz.
durumdur. Yani thread özel belirtilen fonksiyonlar içerisinde sonlandırılır. Fonksiyonun ikinci
parametresi threadin eski sonlandırma durumunu belirtmektedir. geri dönüş değeri başarılıysa
0, başarısızsa 0 dışı bir değerdir. Fonksiyonun ikinci parametresi NULL geçilebilir.
pthread_setcanceltype fonksiyonu fonksiyonu çağıran threadin sonlandırma durumunu
değiştirir. Default olarak tüm threadler yaratıldığında sonlandırma modu
PTHREAD_CANCEL_DEFERRED biçimindedir.
akışının yol açtığı hatalardan doğan sinyallerin threade gönderilmesi gerektiği söylenmiştir.
Sinyalin threade gönderilmesi demek sinyal fonksiyonunu o threadin işlemesi demektir. Yine
standartlara göre process ID ile ilgili olan yada terminelle ilgili olan tüm sinyallerin processin
bütününe gönderilmesi gerektiği söylenmiştir. Örneğin, kill fonksiyonuyla gönderilen bütün
sinyaller ve Ctrl+C gibi klavye yolula üretilen tüm sinyaller processe gönderilmektedir.
Processe gönderilen sinyallerin hangi thread tarafından ele alınacağı belirlenmemiştir.
POSIX’ te sinyal mekanizmasını düzenlemek için bir grup threade özgü sinyal fonksiyonu
eklenmiştir.
pthread_kill Fonksiyonu
pthread_kill fonksiyonu kill fonksiyonunun threade özgü biçimidir. Yani kill sinyali processin
bütününe gönderirken (ki bu durumda bu sinyali hangi threadin ele alacağı belli değildir.)
pthread_kill sinyali belirli bir threade göndermektedir.
int pthread_kill(pthread_t thread, int signo);
Fonksiyonun birinci parametresi sinyalin gönderileceği threadin ID değeri, ikinci parametresi
gönderilecek sinyalin numarasıdır. Fonksiyon başarı durumunda 0, başarısızlık durumunda
hata değerine geri döner. pthread_kill fonksiyonu gönderilen sinyalin ilgili thread tarafından
ele alınacağını garanti eder. pthread_kill fonksiyonu ile threade sinyal gönderildiğinde eğer
sinyal fonksiyonu set edilmemişse tüm process sonlanır.
pthread_sigmask Fonksiyonu
Daha önceden processin bütününün bir sinyal bloke kümesi olduğu belirtilmiştir. Processin
sinyal bloke kümesi sigprocmask fonksiyonuyla belirlenmektedir. Processin sinyal bloke
kümesi tüm threadler için geçerlidir.
Procesin bütününe ilişkin sinyal bloke kümesinin yanısıra birde threade özgü sinyal bloke
kümesi vardır. Threade özgü sinyal bloke kümesi pthread_sigmask fonksiyonuyla belirlenir.
int pthread_sigmask(int how, const sigset_t *newmask, sigset_t *oldmask);
Threadin başlangıçtaki sinyal bloke kümesi onu yaratan threadden alınmaktadır.
Processe gönderilen SIGINT gibi bir sinyali düşünelim. Sinyal fonksiyonunun hangi thread
tarafından ele alnacağı belli değildir. Bunu belirli hale getirmek için yapılacak tek şey bütün
threadleri pthread_sigmask ile bu sinyale kapatmak yalnızca tek bir threadi açık bırakmaktır.
Threadlerin Senkronizasyonu
Bilindiği gibi threadler belirli bir quanta süresince diğerlerinden bağımsız olarak
çalıştırılmaktadır. Pekçok uygulamada aynı global datalar üzerinde işlem yapılması
durumunda threadlerin duruma göre birbirlerini beklemesi gerekir. Bazı işlemlerin başından
sonuna kadar tek bir thread tarafından yapılması gerekebilir. Örneğin, iki thread aynı global
bağlı listeyi kullanıyor olsun. Bir thread bağlı listenin sonuna eleman eklerken threadler arası
geçiş olsa ve diğer threadde bağlı liste üzerinde bir işlem yapıyor olsa bağlı liste bozulur.
Aynı anda tek bir thread akışı tarafından çalıştırılması gereken kodlara kritik kodlar
denilmektedir.
Unix/Linux sistemlerindeki senkronizasyon yöntemleri şunlardır:
1. Mutex yöntemi
2. Kilit yöntemi (read, write lock)
3. Semaforlar: semaforlar sistemV semaforları ve POSIX semaforları olmak üzere iki
biçimdedir.
Yukarıdaki 3 yöntem de POSIX standartlarında belirlenmiş yöntemlerdir.
Bu fonksiyonlar daha önce kilit alınmışsa kilit açılana kadar blokeye yol açarlar. Bu iki
fonksiyonun birde bloke etmeyen biçimleri de vardır. Bloke etmeyen biçimleri kilit açık
değilse blokeye yol açmamakta fakat başarısızlıkla geri dönmektedir.
int pthread_rwlock_tryrdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_trywrlock(pthread_rwlock_t *rwlock);
Kilidi açmak için pthread_rwlock_unlock fonksiyonu kullanılır.
int pthread_rwlock_unlock(pthread_rwlock_t *rwlock);
Tüm bu fonksiyonlar başarı durumunda 0, başarısızlık durumunda hata değerine geri dönerler.
Mutex Yöntemi
Mutex kullanımı kilitleme yöntemine benzemektedir. Mutex nesnesinin sahipliği bir thread
tarafından alınır, o thread sahipliği bırakana kadar başka bir thread mutex nesnesinin
sahipliğini almak isterse bloke olur.
için paylaşılan bir bellek alanı ayırmalı daha sonra mutex nesnesini pthread_mutex_init
fonksiyonuyla yaratmalıdır. pthread_mutex_lock ve pthred_mutex_unlock fonksiyonları bu
paylaşılan bellek alanındaki mutex nesnesinin adresi verilmelidir. Prosesler arası mutex
nesnesinin paylaşılabilmesi için ayrıca mutex nesnesinin özelliklerini prosesler arası
paylaşılabilir yapmak gerekir.
ÜRETİCİ TÜKETİCİ
for(;;){ T for(;;){
sem_wait(g_sem_producer); A sem_wait(g_sem_consumer);
... M ...
sem_post(g_sem_consumer); P sem_post(g_sem_producer);
} O }
N
İsimli İsimsiz
sem_open sem_init
sem_wait
sem_trywait
sem_post
sem_getvalue
sem_close sem_destroy
sem_unlink
sem_open fonksiyonunun prototipi şöyledir:
sem_t *sem_open(const char * name, int oflag,...);
Fonksiyonun birinci parametresi semafor dosyasının yol ifadesidir. Burada belirtilen
dosyanın kök dizinde olması gerekir. Pekçok sistem belirtilen dosyanın herhangi bir dizinde
olması konusunda bir kısıtlama getirmese de bazı sistemlerde böyle bir kısıtlama vardır.
fonksiyonun ikinci parametresi 0, O_CREAT yada O_CREAT|O_EXCL olabilir.
Fonksiyonun ikinci parametresinde O_CREAT geçilmişse üçüncü parametre de erişim hakları
belirtilmelidir. Fonksiyon başarı durumunda semafor nesnesinin handle değerine, başarısızlık
durumunda sem_failed değerine geri döner. sem_close ve sem_unlink fonksiyonları da
şöyledir:
int sem_close(sem_t *sem);
int sem_unlink(const char *name);
İsimli semafor nesneleri halen Unix sistemlerinde POSIX uyumlu olarak
gerçekleştirilememiştir.
Anahtar Notlar:
Win32 sistemlerinde threadin default stack uzunluğu PE formatının içerisinde yazmaktadır.
Microsoft linkerları bu uzunluğu default olarak yaklaşık 1MB olarak almaktadır. Bu
sistemlerde threadin stack uzunluğu CreateThread fonksiyonunda belirlenebilmektedir. Bu
fonksiyonda stack uzunluğu 0 geçilirse default değer alınmaktadır.
Thread Çizelgelemesi
POSIX sistemlerinde thread çizelgelemesi Win32 sistemlerine göre çok daha esnektir. Bu
sistemlerde programcı sistemin genelindeki yada processin threadleri arasındaki çizelgeleme
algoritmasını değiştirebilir.
Thread çizelgelemesi için yine pthread_attr_t ile belirtilen özellik yapısından faydalanılır.
kilitlemede kayıt daha önce kilitlenmiş ise fcntl başarısız olur ve errno EAGAIN yada
EACCESS değeriyle set edilir. F_SETLKW biçiminde alan başka bir process tarafından
kilitlendiyse fcntl fonksiyonu başarısız olur.
Kayıt kilitlerken fcntl fonksiyonun üçüncü parametresi struct flock türünden içi doldurulmuş
bir yapının adresi biçiminde girilir.
struct flock{
short l_type;
short l_whence;
off_t l_start;
off_t l_len;
pid_t l_pid;
...
}
Yapının l_type elemanı F_RDLCK, F_UNLCK yada F_WDLCK biçiminde girilebilir.
F_RDLCK, eğer kayıt daha önce bu biçimde kilitlendiyse kilitleme işleminin başarılı olmasını
sağlar. Böylece aynı kayıt birden fazla process tarafından okunabilir. F_WRLCK, başka bir
process ilgili kaydı kilitlemediyse başarılı olur. F_UNLCK, daha önce kilitlenmiş bir kaydın
kilidinin açılması için kullanılmaktadır. Yapının l_whence elemanı offset için orijin noktasını
belirtir. l_start elemanı kilitlenecek bölgenin başlangıç offset numarasıdır. l_len kilitlenecek
kaydın uzunluğunu verir. 0 özel olarak bulunulan offsetten sonrası (EOF’ un ötesi de dahil
olmak üzere) anlamına gelir.Yapının l_pid elemanı kilitleme bilgisi alırken kullanılmaktadır.
fonksiyon başarılıysa 0 değerine geri döner, başarısızsa –1 değerine geri döner. Programcı
fcntl fonksiyonunu çağıran ayrı ve daha kolay kullanılan bir fonksiyon yazabilir.
İşletim sistemlerinde genel olarak dosya kilitleri processe özgü bir biçimdedir. Yani örneğin,
bir process bir dosyanın bir kaydını kilitlemiş olsun. O processin her threadi kilit hakkına
sahip durumdadır. Process sonlandığında processin elde bulundurduğu kilitlerin hepsi
otomatik olarak açılmaktadır. Aslında kilidin otomatik açılması işlemi dosya kapatılırken
close fonksiyonunda yapılır. Yani process bir betimleyici üstünde kilit işlemi uygulamışsa o
dosya close’ la kapatıldığında kilit otomatik olarak geri bırakılır. Bir dosya için processin
birden fazla betimleyicisi bulunsun. Yani bu betimleyiciler aynı dosya nesnesini görüyor
olsun. Betimleyicilerden herhangi biri kapatıldığında diğer betimleyiciler kapatılmamış olsa
bile kilitler geri bırakılır.
fork işlemi ile üst processin elde etmiş olduğu kilitler alt processe aktarılmaz. Yani üst process
bir bölgeyi kilitlemiş olsun. fork işleminden sonra alt process her hangi bir processmiş gibi
kilitten olumsuz etkilenir.
Bir process önce dosyada bir bölgeyi kilitlemiş olsun, daha sonra exec işlemi yapsın. exec
işlemi sırasında betimleyiler kapatılmıyorsa (CLOSE_ON_EXEC bayrağıyla ilgilidir. Default
olarak kapatılmaz.) exec işlemi sonrasında kilitler yeni programda da devam eder. (zaten exec
işlemi ile process ID değişmemektedir.)
Daha önce kilitlenmiş bir kayıt bölge fcntl fonksiyonunda struct flock yapısının l_type
elemanında F_UNLCK verilerek açılabilir.
Zorunlu Kilitleme İşlemleri
Zorunlu kilitlenme işlenlerinde kilitlenmiş olan bölgeye okuma-yazma yapılamaz. Zorunlu
olmayan kilitleme ile bir uygulamanın yazılabilmesi için uygulamayı oluşturan programların
aynı kişi tarafından read ve write fonksiyonlarını hemen uygulamadan önce fcntl ile kilitleme
deneyerek yazılmış olması gerekir. oysa zorunlu kilitlemede kilitlenmiş bir bölgeye herhangi
bir process erişememektedir. Zorunlu kilitlemede kilitlenmiş bir bölgeye read yada write
fonksiyonları uygulandığında bu fonksiyonlar kilit açılana kadar bloke oluşturmaktadır.
Zorunlu kilitleme işlemi de yine fcntl fonksiyonu ile zorunlu olmayan kilitlemede olduğu gibi
yapılmaktadır. Kilitlemenin zorunlu olup olmadığı kilitlemeye konu olan dosyanın
SET_GROUP_ID bayrağının set edilip edilmediğine bağlıdır. Zorunlu kilitlemeyi sağlamak
için dosyanın grup özelliğinde x hakkının olmaması SET_GROUP_ID bayrağının set edilmiş
olması gerekmektedir. yani biz bir dosyanın belirli bir bölgesini zorunlu kilitleme işlemine
sokmak istiyorsak şunları yapmamız gerekir.
1. Dosyanın erişim grup bilgisindeki x hakkını kaldırmak ve SET_GROUP_ID
bayrağını set etmek.
2. Fcntl fonksiyonuyla zorunlu olmayan kilitleme de olduğu gibi kilitlemeyi
uygulamak
read ve write fonksiyonları artık kilitlenmiş bir bölgede kilit açılana kadar bloke olur.
Anahtar Notlar:
Dosyanın SET_USER_ID ve SET_GROUP_ID bayrakları erişim haklarını değiştiren chmod
fonksiyonuyla değiştirilmektedir. Chmod fonksiyonunun bu değişikliği yapabilmesi için ya
fonksiyonu çağıran processin root olması yada fonksiyonu çağıran processin değişikliğin
yapılacağı dosyanın kullanıcısıyla aynı etkin kullanıcı ID’sine sahip olması gerekir. Yani bir
kullanıcı başka bir kullanıcının oluşturduğu dosyanın erişim haklarını değiştirememektedir.
Bir dosyanın sahipliğini değiştirebilmek için yani kullanıcı ve grup ID’ lerini değiştirebilmek
için root olmak gerekir, bu işlem chmod ile yapılır. yani herhangi bir dosyanın sahipliği
dosyanın sahibi tarafından bile değiştirilememektedir.
getuid fonksiyonu herzaman processin gerçek kullanıcı ID’ sini, geteuid ise her zaman etkin
kullanıcı ID’ sini vermektedir. Benzer biçimde processin gerçek grup ID’ si getgid fonksiyonu
ile etkin grup ID’ si ise getegid fonksiyonuyla alınmaktadır.
Unix/Linux sistemlerinde daha ileride ele alınacak olan bir tasarım problemini gidermek için
BSD 4.3 ve System V-r4 sistemlerinden itibaren saklanmış kullanıcı ID’ ve saklanmış grup
ID’ si kavramları da eklenmiştir. Normal olarak saklanmış kullanıcı ID’ si etkin kullanıcı ID’
sine, saklanmış grup ID’ si de etkin grup ID’ sine eşittir. Exec işlemi ile etkin kullanıcı ID’ si
değiştiğinde saklanmış kullanıcı ID si de değişmekte, etkin grup ID’ si de saklanmış grup ID’
si değiştiğinde değişmektedir. Yani exec işleminden sonra gerçek kullanıcı ve grup ID leri
/etc/passwd dosyasında belirtilen ID ler olarak kalmakta ama etkin kullanıcı ID si, etkin grup
ID si, saklanmış kullanıcı ID ve saklanmış grup ID si değişmiş olabilmektedir.
yeni durum
uid = A
euid = B
suid = B
Burada anlatılan aynı problem grup ID’ si içinde söz konusudur. Yani biz SET_GROUP_ID
bayrağı set edilmiş bir programı çağırıp eski grup ID li dosyaya eriştikten sonra etkin grup ID
sini aynı değere çekebiliriz.
Yukarıdaki bu problemi çözmek için BSD sistemleri daha ilginç bir çözüm düşünmüştür.
Yani suid ve guid kavramını kullanmadan bu problem setreuid ve setregid fonksiyonlarıyla
çözülebilmektedir. Bu iki fonksiyon sonraları POSIX standartlarına da eklenmiştir.
setreuid fonksiyonu processin kullanıcı ID siyle etkin kullanıcı ID sini tamamen
yerdeğiştirmektedir.
int setreuid(uid_t ruid, uid_t euid);
Bu işlemin başarıyla yapılabilmesi için fonksiyonun birinci parametresinde belirtilmiş olan
ID’ nin processin gerçek kullanıcı yada etkin kullanıcı ID sine eşit olmalıdır. Görüldüğü gibi
bir kullanıcı istediği zaman bu fonksiyon ile kendi kullanıcı ID ile etkin kullanıcı ID sini
değiştirebilmektedir. Örneğin:
1. Başlangıç durumu:
id = euid =A
2. B’ nin bir programı fork ve exec ile çalıştırılır. Çalışma işleminden sonraki durum:
uid = A
euid = B
3. Çalıştırılan program A’ nın dosyasına erişemiyor. Erişmek için şunu yapıyor:
setreuid (B, A);
yeni durum
uid = B
euid = A
4. Çalıştırılan program şimdi ilk duruma dönmek için şu yapılabilir:
setreuid (A, B);
yeni durum
uid = A
euid = B
Ayrıca seteuid ve seteugid bu fonksiyonların sıradan kullanıcılar için setuid ve setgid
fonksiyonlarından hiçbir farkı yoktur. Ancak root kullanıcısı söz konusu olduğunda setuid
fonksiyonu tüm Idleri set ederken seteuid fonksiyonu ve setegid fonksiyonu yalnızca etkin
kullanıcı ID sini set etmektedir. Aynı durum setegid fonksiyonu içinde benzer biçimde
geçerlidir.
init processi terminal işlemleri için getty isimli bir programı çalıştırır. Terminal için
çalıştırılan bu programın ismi çeşitli sistemlerde değişebilmektedir. getty programı sisteme
bağlı olan tüm terminal bilgilerini bir konfigürasyon dosyasından elde eder. Unix/Linux
sistemlerinde pekçok farklı terminal bağlanabilmektedir. Bütün terminallerin hakkında bilgi
bulunduran bu dosya pekçok sistemde /etc/ttys biçimindedir. Terminal hem klavye hemde
ekranı temsil eden bir aygıttır. Her terminali temsil eden /dev dizini altında bir terminal
dosyası vardır. Örneğin /dev/tty1 birinci terminali /dev/tty2 ikinci terminali set eder. Bu tty
dosyaları open fonksiyonuyla açılabilir. Terminal dosyası açıldıktan sonra write
fonksiyonuyla yazma yaptıktan sonra yazılanlar o dosyanın ekranında görülür. read
fonksiyonula okuma yapıldığında klavyeden okuma yapılır. getty programının Unix’ teki ismi
mingetty programıdır. İnit processi sistemdeki tüm terminallerin sayısı kadar her terminal için
bir getty programı çalıştırmaktadır. getty processi çalıştığı sırada henüz hiçbir dosya
açılmadığı için dosya betimleyici tablosu boştur. Yani henüz bu noktada hiçbir dosya açık
değildir. getty programı /dev/ttyn dosyasını önce open fonksiyonuyla O_RDONLY olarak
açar. Bu işlemle open 0 numaralı betimleyiciyi verecektir. Böylece 0 numaralı betimleyici
stdin biçiminde oluşturulmuştur. Yani biz artık 0 numaralı betimleyiciden okuma
yaptığımızda gerçekte /dev/ttyn aygıtından okuma yapmış oluruz. Daha sonra getty /dev/ttyn
dosyasını bu kez O_WRONLY biçiminde açar. open bu işlemden 1 numaralı betimleyiciyi
verecektir. Böylece stdout betimleyicisi de oluşturulmuş olur. getty son olarak dup(1) işlemini
yaparak iki numaralı betimleyiciyi 1 numaralı betimleyicinin kopyasıyla oluşturur.
swapper
init
Artık sıra login işlemine gelmiştir, getty programı ekrana username yazısını çıkarır. Bundan
sonra getty login isimli programı çalıştırır. Getty login programına komut satırı argümanı
olarak kullanıcı ismini vermektedir.
swapper
Terminal
no init
Kullanıcı
ismi getty getty getty
Login programı çalıştığında kullanıcı ID’ si ve etkin kullanıcı ID’ si root biçimindedir. Login
klavyeden password bilgisini alır, bu password bilgisini encrypt fonksiyoınuna sokarak
swapper
init
Login programı password doğrulamasını yapamazsa kendisini sonlandırır. getty bir döngü
içerisinde login programını kullanıcı ismini sorarak sürekli çalıştırmaktadır. login shell
programını çalıştırdıktan sonra onu wait fonksiyonuyla beklemektedir. Shell’ den logout
komutuyla çıkıldığında login programından da çıkılır ve yeniden getty programına dönülür.
Aygıt Dosyaları
Unix/Linux sistemlerinde donanım aygıtları birer dosya gibi işlem görmektedir. Bütün
aygıtlar open fonksiyonuyla sanki bir dosyaymı gibi açılıp kullanılırlar. Tüm aygıtları temsil
eden /dev dizini altında aygıt dosyaları vardır. aslında bu aygıt dosyaları içi boş olan sözde
dosyalardır. Bu dosya yalnızca aygıtları temsil etmekte kullanılır. İşletim sistemi aygıta ilişkin
bilgileri aygıt dosyasının i-node elemanından hareketle elde etmektedir.
Aygıt sürücüler kernel modda çalışabilecek biçimde özel formatta yazılmış dosyalardır. Aygıt
sürücü programları yazmak zor olsa da kullanmak oldukça kolaydır. Programcı aygıt sürücü
ile ilgili üç işlem yapabilir:
1. Aygıt sürücüye bilgi gönderebilir.
2. Aygıt sürücüden bilgi alabilir.
3. Aygıt sürücü programın bir şeyler yapması için ona komut gönderir.
Aygıt sürücü dosyası açıldıktan sonra Unix/Linux sistemlerinde sürücüye write fonksiyonuyla
bilgi gönderilebilir, sürücüden bilgi read fonksiyonuyla okunur. ioctl fonksiyonuyla da aygıt
sürücüye komut gönderilir. ioctl fonksiyonunun prototipi şöyledir:
int ioctl(int fd, int command, ...);
Fonksiyonun prototipi <sys/ioctl.h> içerisindedir. Fonksiyonun birinci parametresi open
fonksiyonuyla açılan aygıt sürücü dosyasının betimleyici değeridir. İkinci parametre aygıt
sürücüye gönderilecek komutu belirtir. Hangi değerin ne yapacağı aygıt sürücüyü yazanlar
Unix/Linux sistemlerinde konsol ekranında ekran işlemleri yapmak için kullanılan en yaygın
kütüphane Curses kütüphanesidir. Curses kütüphanesi hemen hemen tüm Unix türevi
sistemlerde standart bir biçimde bulunmaktadır. Curses kütüphanesi ilk kez BSD
sistemlerinde yazılmıştır, sonra AT&T tarafından da benimsenmiştir.
Eskiden Unix sistemlerinde aptal terminallerle çalışılıyordu. Aptal terminal yalnızca yazma ve
görüntüleme işlemini yapan bir birimdir. Eski aptal terminaller genellikle ana bilgisayra seri
port yoluyla bağlanıyordu. Bu terminallerde cursor’ ı taşımak gibi renkli yazdırma yapmak
gibi özel işlemler ESC komutuyla yapılmaktaydı. Önce 1B numaralı bir ESC karakteri sonra
bir köşeli parantez karakteriyle başlatılan yazıları bu terminaller göstermiyor. Onların yerine
özel işlemler yapıyordu. Her terminalin ESC karater komutları birbirinden farklıydı. Daha
sonra ANSI bu ESC komutlarını standart hale getirmiştir. Terminal türünün ANSI olması
demek bu standart ESC komutlarının kullanılması demektir. Bugün PC sistemlerinde
monitörler doğrudan ESC komutlarını çalıştırmamaktadır. Fakat ekran aygıt sürücüleriyle
bugünkü işletim sistemlerinde yine ESC komutları kullanılabilmektedir.
Curses kütüphanesi aslında kendi içerisinde ESC komutlarıyla görüntüyü oluşturmaktadır.
Böylece curses programları herhangi bir terminalde çalışabilmektedir. Curses kütüphanesi
başlatılırken önce kütüphane kullanılacak terminalin türüne bakar, o terminale ilişkin ESC
komutlarını /etc/termcap isimli bir veri tabanı dosyasından çeker. /etc/termcap dosyası içinde
çeşitli terminallere ilişkin ESC komutlarının neler olduğu bilgisi vardır.
Curses kütüphanesinin klasik biçimi daha sonraları iyileştirilmiştir. Bu yeni biçime newcurses
denilmektedir. Klasik Curses kütüphanesinin başlık dosyası curses.h, yeni Curses
kütüphanesinin başlık dosyası ncurses.h biçimindedir. Benzer biçimde klasik Curses
kütüphanesi için libcurses.a dosyası, yeni Curses kütüphanesi için ise libncurses.a dosyası
kullanılmaktadır. Bu kütüphaneler derleme işlemi sırasında –l seçeneği ile belirtilmelidir.
Örneğin;
gcc –o test –lncurses test.c↵
Cursor’ ın Taşınması
Cursor’ın taşınması için move ve wmove fonksiyonları kullanılır.
int move(int x, int y);
Görüntünün Tazelenmesi
Curses kütüphanesi online işlem yapan bir kütüphane değildir. Kütüphanede yazılar ve
hareketler önce bir tamponda oluşturulmakta daha sonra bu tampon refresh yada wrefresh
fonksiyonuyla tazelenmektedir. Curses’ in klavye fonksiyonları bu tazeleme işlemini kendi
içlerinde yapmaktadır.
int refresh (void);
int wrefresh (WINDOW *win);
Özetle yaptığımız işlemlerin etkisini görebilmek için ya bilinçli olarak refresh işlemi
yapmamız yada cursos’ ın kendi içerisinde refresh yapan klavye fonksiyonlarını çağırmalıyız.
Anahtar Notlar:
getch fonksiyonu ile özel tuşların elde edilebilmesi için önce bir kez keypad isimli
fonksiyonun çağırılması gerekir.
int keypad(WINDOW *win, bool bf);
Görüldüğü gibi bu fonksiyon belirli bir pencereyi özel tuşalra açmaktadır. Fonksiyonun ikinci
parametresi açma-kapama işlemleri için kullanılır. Örneğin tüm pencereyi açmak için;
keypad(stdscr, TRUE);
bool has_colors(void);
if(has_colors() == FALSE){
printw(“cannotsupport colors\n”);
exit(1);
}
Bu işlemden sonra renkli yazdırmak için önce init_pair fonksiyonu ile zemin ve şekil rengi
tanımlanır. Sonra attron fonksiyonu ile o şekil ve zemin rengi COLOR_PAIR(n) makrosuyla
aktif hale getirilir.
init_pair(short pair, short f, short b);
fonksiyonun birinci parametresi oluşturulan renk özelliğinin sayısal kodudur. Bu sayı
istenildiği gibi verilebilir. Fonksiyonun ikinci parametresi yazının şekil rengi, üçüncü
parametre ise zemin rengidir. Buradaki fonksiyonun birinci parametresinde belirtilen sayı
daha sonra COLOR_PAIR makrosuna parametre olrak geçirilir. Renkler sembolik sabitler
halinde define edilmişlerdir. Genel olrak COLOR_XXX biçiminde sembolik sabitler
oluşturulmuştur. Aslında bu sembolik sabitler 0-15 arası sayılardan oluşur. Ayrıca renkli
yazım işleminden önce bir kez start_color fonksiyonunun çağırılması gerekir. Örneğin;
if(has_color() == FALSE){
printw(“cannot support colors...\n”);
exit(1);
}
start_color();
init_pair(1, COLOR_YELLOW, COLOR_BLUE);
attron(COLOR_PAIR(1));
printw(“deniz\n”);
printw(“deniz”);
getch();
Anahtar Notlar:
Cursor’ ı yok etmek için curs_set fonksiyonu ekranı silmek için ise clear fonksiyonu kullanılır.
Pencerelerin Yaratılması
init_scr fonksiyonuyla ekranın tamamını temsil eden bir pencere yaratılmaktadır. Ancak
programcı isterse newwin fonksiyonu ile yeni bir pencere yaratabilir.
WINDOW *newwin(int nlines, int ncols, int begin_y, int begin_x);
Görüldüğü gibi parametreler pencerenin satır ve sutün uzunluğu ve sol üst köşegenin
koordinatları biçimindedir. Yaratılmış bir pencere daha sonra delwin fonksiyonuyla silinebilir.
int delwin(WINDOW *win);
Fare İşlemleri
Curses kütüphanesi aynı zamanda fare kullanımını da desteklemektedir. Bunun için önce
mousemask fonksiyonu ile hangi fare olaylarının izleneceği belirlenir.
mmask_t mousemask(mmask_t newmask, mmask_t *oldmask);
Daha sonra farenin tuşuna basılıp basılmadığı tespit edilir ve getmouse fonksiyonuyla farenin
konumu alınır. Tipik olarak fare işlemleri şöyle yapılabilmektedir.
for(;;){
ch = getch();
if(ch == KEY_MOUSE){
getmouse(...);
//fare işlemleri
}
else{
//klavye işlemleri
}
}
- /proc dizini altında net, sys, tty gibi çeşitli dizinlerde vardır. Özellikle sys dizini
çalışan sistem hakkında bilgiler veren pek çok girişe sahiptir.
formatın elf formatı olacağını belirtir. Link işlemindeki –e start programın başlangıç noktasını
belirtmektedir. Başlangıç noktasının etiket ismi farklı olabilmektedir.
Sistem fonksiyonları yukarıda anlatılan biçimin dışında doğrudan C’ den de çağırılabilir.
syscall isimli Linux fonksiyonu sistem fonksiyonlarını çağıran fonksiyondur.
int syscall(int number, ..);
Görüldüğü gibi fonksiyonun birinci parametresi çağırılacak sistem fonksiyonunun numarasını
belirtir. Diğer parametreler sırasıyla sistem fonksiyonlarının parametreleridir. syscall
fonksiyonunun yaptığı tek şey yazmaçlara uygun değerleri yerleştirerek 80h kesmesini
çağırmaktır. Fonksiyonun prototip <unistd.h> içerisindedir. Ayrıca <sys/call.h> dosyası
içerisinde sistem fonksiyonlarına ilişkin faydalı sembolik sabitler vardır.
Sembolik makine dilinde yazılmış fonksiyonu C’ den çağırmak için fonksiyon nasm
derleyicisinde aşağıdaki gibi yazılır.
[BITS 32]
[section.text]
GLOBAL AddAsm;
AddAsm:
push ebp
mov ebp, esp
mov eax, [ebp+8]
add eax, [ebp+12]
pop ebp
ret
Artık fonksiyonun bulunduğu dosya aşağıdaki gibi derlenir.
nasm –f elf add.asm ↵
Test programı şöyle olabilir:
int AddAsm(int, int);
int main(void)
{
pirntf(“%d\n”, AddAsm(100, 200));
return 0;
}
Şimdi gcc derleyicisiyle test programında oluşturulan .o dosyası bir arada derleme ve link
işlemine sokulmalıdır.
gcc –o testadd testadd.c add.o ↵
testadd ↵
Makro Kullanımı
Make dosyalarında bazı uzun yazımları engellemek için Makrolar kullanılmaktadır. Makro bir
çeşit C deki sembolik sabit gibidir. Örneğin:
CC = cc
SOURCE = a.c b.c
OBJS = a.o b.o
Makro kullanımı için ${MAKRO_İSMİ} yada $(MAKRO_İSMİ) kullanılır.Böylece yazımda
gereksiz yinelemeler engellenmiş olur. Aşağıdaki makrolar önceden tanımlanmıştır.
UNIX/Linux sistemlerinde önceden tanımlanmasa da olur.
CC = gcc
OBJS = a.o b.o c.o
EXE = test
$(EXE) : $(OBJS)
$(CC) -o $(EXE) $(OBJS)
a.o b.o c.o : general.h
a.o : a.c
$(CC) -c a.c
b.o : b.c
$(CC) -c b.c
c.o : c.c
$(CC) –c c.c
Make dosyaları yazılırken sondan başa gidilmelidir. Bir kuralda yapılacak işlemler
belirtilmeyebilir. Bu durumda bağımlılığın sağ tarafında olan değişimler sanki sol tarafında
değişmiş gibi işlem görür. Örneğin;
a.c b.c: general.h
dbopen Fonksiyonu
Veri tabanına ulaşmak için bu fonksiyon kullanılır. fonksiyon db ile temsil edilen bir handle
değerine geri döner.
DB * dbopen(const char * file, int flags, int mode, DBTYPE type, const void *openinfo);
Fonksiyonun birinci parametresi veri tabanı dosyasının ismi, ikinci parametre dosyanın açış
bayraklarıdır. Bu parametre open fonksiyonunda kullanılan O_CREAT, O_RDONLY,
O_RDWR ve O_EXCL bayraklarını içerebilir. Üçüncü parametresi dosya yaratılacaksa erişim
haklarını belirtir. fonksiyonun dördüncü parametresi kullanılacak veri tabanı algoritmasını
belirtir. Bu parametre DB_BTREE, DB_HASH yada DB_RECNO biçiminde olabilir.
fonksiyonun son parametresi NULL geçilebilir. fonksiyon başarısızlık durumunda NULL
değerine geri döner.
DB *db;
if((db = dbopen(“test.dat”,O_RDWR| O_CREAT,0666,DB_BYTREE,NULL)) == NULL){
perror(“dbopen”);
exit(EXIT_FAILURE);
}
DB ile temsil edilen handle alanının içerisinbde çeşitli fonksiyon göstericileri vardır. Bu
kütüphane fonksiyonların handle değerini geçirmek yerine handle alanındaki fonksiyon
göstericilerini kullanma esasına dayandırılmıştır. Yani örneğin, veri tabanını kapatan
fonksiyon şöyle kullanılmalıdır.”db -> close();”.
close Fonksiyonu
close fonksiyonu aslında db handle alnındaki bir fonksiyon göstericisidir. Parametre olarak db
handle değerini alır.
db -> close(db);
Veri Tabanına Kayıt Eklenmesi
Kayıt eklemek için handle alanındaki out fonksiyon göstericisi kullanılır.
int (*put) (const DB *db, DBT *key, const DBT *data, u_int flags);
DBT isimli yapı şöyledir;
typedef struct{
void *data;
size_t size;
} DBT;
Fonksiyon üçüncü parametresinde belirtilen kaydı ikinci parametresinde belirtilen anahtara
göre yerleştirilir. Kayıt bulunacağı zaman bu anahtar bilgisi kullanılacaktır. Fonksiyonun son
parametresindeki bayraklar ekleme işleminin biçimi hakkında bilgi vermektedir. Son
parametre 0 yada R_NOOVERRIDE biçiminde geçirilebilir. Fonksiyon başarılıysa0 değerine
başarısızsa –1 değerine geri döner.
Fonksiyonun birinci parametresi veri tabanının handle değeri ikinci ve üçüncü parametreleri
sırasıyla anahtar değerler ve bulunacak kaydın yerleştirileceği adrestir. Son parametre
şunlardan biri olabilir.
R_CURSOR, R_FIRST, R_LAST, R_NEXT, R_PREV
R_CURSOR, o andaki aktif kaydı elde etmek için kullanılır. R_FIRST, o andaki ilk uygun
kaydı bulur. R_LAST, anahtara uygun son kaydı bulmakta kullanılır. R_NEXT, o andaki aktif
kayıttan sonraki ilk uygun kaydı bulur. R_PREV, o andaki aktif kayıttan itibaren geriye doğru
ilk uygun kaydı bulur. Bu işlemlerin hepsi uygun kaydı bulmaktadır. Örneğin programcı ismi
“deniz” olan tüm kayıtları bulacak olsun. Bunun için key “deniz” yazısını oluşturulacak
biçimde girilir. İlk çağırımda seq, falg parametresi first olacak biçimde çağırılır. Ondan sonra
döngü içerisinde R_NEXT işlemleriyle devam edilir. Fonksiyon başarısızlık durumunda –1
değerine, başarı durumunda 0 değerine, kayıt bulamadan dönerse 1 değerine geri döner. db
kütüphanesi veri tabanının aynı anda paylaşılması konusunda hiçbir önlem almamaktadır. (fd)
fonksiyonu ile veri tabanı dosyasına ilişkin dosya betimleyicisi elde edilebilir. Programcı
kayıt kilitlemeleri fcntl fonksiyonu ile bu betimleyiciyi kullanmalıdır.
int (*fd) (const DB *db);
DK