You are on page 1of 138

C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –

www.csystem.org

Bu Notlar Kaan Aslan Tarafından C ve Sistem


Programcıları Derneği’nde Verilen Unix/Linux Sistem
Programlama Dersinde Tutulmuştur. Notlar Üzerinde
Herhangi Bir Düzeltme Yapılmamıştır.

Notları Tutan Deniz Kürümoğlu’na Teşekkür Ederiz.

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.

UNIX/LINUX SİSTEMLERİNİN PC LERDE KURULUMU


UNIX/LINUX sistemlerinin PC lere kurulumu bunların son versiyonlarıyla kolaylaştırılmıştır.
Örneğin, yeni versiyonlarda bir boot CD si vardır, makine bu CD ile boot edilebilir. Boot CD
si otomatik olarak setup sürecini başlatmaktadır. Eskiden programcının önce rawrite programı
ile boot ve root disketlerini oluşturması gerekiyordu, sonra makine boot disketiyle açılıp işlem
root disketiyle sürdürülüyordu. Root disketi yüklendikten sonra programcı setup la setup
işlemini başlatıyordu.
Setup programının ilk sorduğu sorulardan biri sistemin hangi disk bölümüne kurulacağıdır.
Eskiden bu sistemler disk bölümleme tablosunun ana dörtlük girişlerinden bir tanesine
kurulabiliyordu. Ancak özellikle UNIX sistemlerinin son versiyonları artık extended disk
bölümlerine de kurulabilmektedir.

SANAL MAKİNE PROGRAMLARI VE VIRTUAL PC


Bir dosyayı makinanın harddiski gibi gören ve gerçek bir makine gibi çalışan programlar
sanal makina programları denir. Sanal makine programları gerçek kodları çalıştırır, örneğin
sanal makine reset edildiğinde yine orijinal boot kodu çalıştırılmaktadır. Sanal makinanın
üzerinda kurulduğu işletim sistemine host – os denir. Sanal makinada gerçekleşen tüm olaylar
aslında yalnızca ana makinada (host machine) bir dosyayı etkilemektedir. Dolayısıyla sanal
makinalar her türlü deneme işlemleri için ideal ortam oluştururlar. Sanal makine
programlarının en önemlileri Virtual PC ve WM Ware programlarıdır. (Virtual PC, Microsoft
tarafından satın alınmıştır.)

UNİX SİSTEMLERİNİN TEMEL DİZİN YAPISI


Bir Linux/Unix sistemi install edildiğinde install programı belirli programları oluşturarak bu
dizinlere çeşitli dosyaları yerleştirir. Unix sistemlerinde dizin geçişleri \ ile değil / ile
yapılmaktadır. En dıştaki dizine kök dizin(root directory) denilmektedir.
/bin Dizini:
Bu dizinde sistem için gerekli çalışabilen dosyalar bulunur. Tipik Unix komutları bu
dizindedir.
/dev Dizini:
Bu dizinde sistem için gerekli olan çeşitli aygıt dosyaları bulunur.
/etc Dizini:

Sayfa 3 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Bu dizin içerisinde çeşitli konfigürasyon dosyaları bulunmaktadır.


/lib Dizini:
Burada uygulama programlarının kullandığı kütüphane dosyaları bulumaktadır.
/home Dizini:
Burada kullanıcılara ilişkin dizinler bulunur.
/mnt Dizini:
Bu dizin mount işlemleri için ana dizin biçiminde düşünülmüştür.
/usr Dizini:
Burada kullanıcılar için gerekli olan uygulama programları tarafından kullanılan çeşitli
dosyalar bulundurulur.
/tmp Dizini:
Geçici dosyalar için düşünülmüş olan bir dizindir.

UNIX/LINUX SİSTEMLERİNDE SİSTEME GİRİŞ


Unix / Linux sistemleri boot edildiğinde boot işleminin son aşamasında login isimli bir
program çalıştırılmaktadır. Bu programın görevi bir password denetimi altında kullanıcıyı
sisteme sokmaktadır. Login programı önce kullanıcı ismini sonrada password bilgisini ister.
Login programı kullanıcıdan aldığı password bilgisini tek yönlü şifreleme algoritmasıyla
şifreleyerek /etc/passwd dosyasında belirtilen şifrelenmiş password ile karşılaştırır.
/etc/passwd dosyasının içeriği ileride ayrıntılı olarak ele alınacaktır. Ancak kabaca bu
dosyanın içerisinde her kullanıcının ismi, şifrelenmiş password bilgisi ve sisteme giriş
onaylanırsa çalıştırılacak programın ne olduğu bilgisi vardır. Genellikle sisteme giriş
onaylandığında çalıştırılacak program bir shell programıdır. Böylece kullanıcı sisteme
girdğinde kendini shell programında bulur. /etc/passwd dosyasının içeriğinin normal bir
kullanıcı tarafından okunmasında bir sakınca görülmemiştir, ancak şüphesiz normal bir
kullanıcının bu dosyaya yazma yapması engellenmiştir. Son yıllarda güvenliğin biraz daha
arttırılması için şifrelenmiş password bilgisinin ayrı bir dosyada tutulması yöntemi
benimsenmiştir. Bu sistemlerde /etc/passwd dosyası içerisinde password alanında x yazıyorsa
şifrelenmiş password bilgisi bu dosyada değil /etc/shadow dosyasındadır. Bu dosya normal bir
kullanıcı tarafından okunamamaktadır.

UNIX/LINUX SİSTEMLERİNDE SHELL PROGRAMLARI


Unix/Linux sistemlerinde komut yorumlayıcı olarak tek bir shell programı yoktur. Populer
olarak kullanılan pekçok shell programı mevcuttur. Bu shell programları arasında komut
bakımından çeşitli küçük farklar vardır, ayrıca bunların “shell script” denilen script dilleri
birbirlerinden farklıdır. En ünlü shell programları Bourne shell, Korn shell, C shell ve Bourne
Again (bash) shell programlarıdır. En yaygın kullanılan shell programı Bourne Again shell
(/bin/bash) programıdır.
Sisteme login olabildiysek login programı tarafından shell programı çalıştırılmış olur. Shell
programından çıkarsak yeniden onu çalıştıran login programına döneriz. Genel olarak shell
programından çıkmak için logout kullanılmaktadır.

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

Unix/Linux komutlarının genel biçimi şöyledir; <Komut> [-seçenek][parametreler]


Seçenekler bir yada birden fazla olabilir, ama hepsinin başı – ile başlamak zorundadır. Bazı
komutlar onlarca farklı seçeneğe sahip olabilmektedir. Pekçok komutun eğer seçenek
belirtilmemişse default bir durumu vardır. Bazen yada bazı komutlarda seçenekler
birleştirilebilmektedir, ls –l –i yerine ls –li kullanılabilmektedir. Bazen seçenekten sonra
seçeneğin bir parametresi gelebilir, genel olarak seçenekle parametresi arasında boşluk
bulunabilir yada bulunmayabilir. Bazen seçenekler birden fazla karakterden
oluşabilir.(Seçenek ile parametre arasında boşluk vermek bu türden karışılıkları önlemektedir.
Çünkü komutların çoğu bu durumda en geniş seçeneği almakta geri kalanını parametre olarak
kabul etmektedir.)

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.

TEMEL UNIX/LINUX KOMUTLARI


POSIX standartlarınca belirlenen çok sayıda Unix/Linux komutları vardır. Kursumuzda en
temel komutlar bu başlık altında diğer bazı komutlar ise çeşitli konular içerisinde ele
alınacaktır. Tüm komutlar konuyla ilgi bir kaynaktan izlenebilir.
ls KOMUTU:
Bu komut belirli bir dizindeki dosyalar hakkında bilgi edinmek için kullanılır. Dos taki dir
komutunun karşılığı biçimindedir. ls –l listede ayrıntılı bilgilere de yer verir.
cd, mkdir, rmdir KOMUTLARI:
Bu komutlar sırasıyla dizin değiştirmek, dizin yaratmak ve dizin silmek için kullanılır.
pwd KOMUTU:
Bu komut hangi dizinde olunduğunu anlamakta kullanılır.
cp, rm, cat, mv KOMUTLARI:
cp, dosya kopyalama için; rm, dosya silmek için; cat, dosyayı type etmek için; mv, dosyanın
ismini değiştirmek yada başka bir dizine taşımak için kullanılmaktadır.
Diğer Temel Komutlar:
clear, ekranı temizlemek için kullanılabilir. find, dizin ağacında bir dosya aramak için
kullanılır. who, sistemde hangi kullanıcıların olduğunu belirlemekte kullanılır.

BİRDEN FAZLA KULLANICI İLE ÇALIŞMAK


Unix/Linux sistemleri çok işlemli çok kullanıcılı sistemlerdir. Sitemler bibirlrine bağlanarak
ağ oluşturabililer, yada aptal terminallerle ana makineye bağlanabilir. Bir PC terminalinden
birden fazla ekran açarak farklı kullanıcılarla aynı anda çalışılabilir. Ekran geçişleri
Ctrl+Alt+F1.. tuşlarıyla yapılmaktadır.

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.

UNIX/LINUX DOSYA SİSTEMLERİ HAKKINDA TEMEL BİLGİLER


Unix/Linux sistemlerinin ve türevlerinin dosya sistemleri birbirlerine çok benzemektedir.
Klasik Unix sistem V in dosya sistemine S5FS denilmektedir. Sonraki yıllarda bu dosya
sisteminin geliştirilmesiyle Berkley tarafından ilk kez BSD Unix sistemlerinde kullanılan yeni
bir dosya sistemi oluşturulmuştur, bu sistem FFS (Fast File System) ismiyle bilinir. Unix
sistemlerinin çoğu FFS türevlerini kullanmaktadır. Linux sistemlerinde bu dosya sistemine
yeni ekler yapılmıştır. Linux un kullandığı bu sistemlere ext1-ext2 sistemleri denilmektedir.
Tüm Unix sistemlerinin disk organizasyonları birbirine benzer ve bu sistemlerin hepsine kısca
i-node dosya sistemleri denilebilir. Modern Unix sistemlerinde dosya sisteminin çekirdek
kısmı VFS (Virtual File System) denilen teknikle yazılmaktadır.
Unix sistemlerinin en önemli özelliği tüm medyalardaki dosyalara, aygıtlara ve
senkronizasyon nesnelerine sanki onlar birer dosyaymış gibi dosya fonksiyonlarıyla
erişilmesidir. Unix sistemlerinde çok az sayıda dosya fonksiyonu mevcuttur, tüm aygıt
işlemleri bu fonksiyonlar yardımıyla gerçekleştirilir. open fonksiyonu dosyayı açmak için,
close fonksiyonu kapatmak için, read ve write fonksiyonları ise bir blok bilgiyi okuyup
yazmak için kullanılırlar. Bunların dışında birde dosya göstericisini konumlandırmak için
kullanılan lseek fonksiyonu vardır.
Unix/Linux sistemlerinde orta derecede karmaşık bir güvenlik sistemi kullanılır. her kullanıcı
her dosyaya erişemez, ancak süper kullanıcı her türlü dosyaya istediği gibi erişebilir.
Unix/Linux sistemlerinde her dosyanın bir sahibi ve grubu vardır. n tane kullanıcı bir grup
oluşturur. Dosyanın sahibi dosyayı yaratan kullanıcıdır, dosyanın grubu sahibinin ilişkin
olduğu gruptur. Örneğin, kaan isimli kullanıcı project isimli grubun elemanı olsun, bu
kullanıcı a.dat isimli bir dosya yaratmış olsun. a.dat dosyasının sahibi kaan, grubu ise project
olacaktır. Dosyaların sahipleri ve grupları ls –l komutuyla görüntülenir. Unix sistemelrinde
eskiden bir kullanıcı yalnızca bir gruba ilişkin olabiliyordu. Bu kavram daha sonra
genişletilmiştir; bir kullanıcının tek bir gerçek grubu vardır, bunun dışında birden fazla ek
gruba sahip olabilir. Dosyanın grubu onu yaratan kullanıcının gerçek grubuna ilişkindir. Bir
kullanıcının gerçek grubu /etc/passwd dosyasında tutulmaktadır. Her grupta hangi
kullanıcıların bulunduğu ayrıca /etc/group dosyasında tutulmaktadır.

Sayfa 6 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

KULLANICI ID’ si VE GRUP ID’ si KAVRAMI


Her ne kadar kullanıcının ismi ve grubu alfabetik olarak konuşuluyorsa da aslında sistem
tarafından sayısal bir biçimde belirtilmektedir. Yani işletim sistemi her kullanıcıya ismine
kullanıcı ID’ si denilen bir sayı düşürmekte ve kendi içinde işlemleri kullanıcının ismine göre
değil ID’ sine göre yapmaktadır. Bir kullanıcının ismine karşılık hangi ID’nin karşılık geldiği
/etc/passwd dosyasında yazmaktadır. Bu bilgi sistemde başka bir dosya içerisinde daha
yazmamaktadır. Örneğin, bir kullanıcı sistemde kaan ismiyle değil onun ID’si olan numarayla
belirlenmektedir. Benzer biçimde sistemde her grubun yine grup ID değeri vardır, her grubun
ismine hangi grup ID’sinin karşılık geldiği /etc/group dosyasında tutulmaktadır. Grup ve user
ID’leri WORD uzunlukta bir bilgidir.
Her dosyanın sahip olduğu kullanıcı ve grup bilgileri de aslında disk üzerinde isimsel olarak
deği, kullanıcı ve grup ID’ leri biçiminde sayısal olarak tutulmaktadır. Örneğin, ls programı
dosyanın sahibinin ve grubunun ID’ lerini elde etmekte, sonra /etc/psswd de /etc/group
dosyalarına başvurarak buradab elde ettiği isimleri yazdırmaktadır.

DOSYAYA ERİŞİM KONTROLLERİ


Bir kullanıcının bir dosyaya erişip erişemeyeceği kullanıcının ID’ sine kullanıcının grup ID’
sine , erişilmek istenen dosyanın sahibinin kullanıcı ID’ sine ve erişilmek istenen dosyanın
grup ID’ sine bakılarak karar verilmektedir.
Erişim kontrolünde aslında erişmek isteyen kullanıcının gerçek kullanıcı ID’ si vegrup ID’ si
değil, etkin kullanıcı ID’ si ve grup ID’ si işleme sokulmaktadır. Etkin kullanıcı ve grup ID’
leri ile gerçek kullanıcı ve grup ID’ leri arasında küçük fark vardır. Normal bir durumda etkin
kullanıcı ID’ si ile gerçek kullanıcı ID’ si ve etkin grup ID’ si ile gerçek grup ID’ si aynı
değerdedir.(Bu konu ileri de ele alınacaktır.)
Unix/Linux dosya sisteminde her dosyanın sahibi için sahibinin ilişkin olduğu grup için ve
sıradan bir kişi için erişim hakları vardır, bu haklara ingilizce owner, group, other hakları
denir. Her dosya için okuma hakkı r ile, yazma hakkı w ile ve dosya çalışabilen bir dosya ise
onu çalıştırma hakkı x ile gösterilmektedir.
ls –l komutu verildiğinde 10 hanelik aşağıdaki gibi bir gösterimle karılaşırız.
- rwx rwx rwx (owner, group, other )
En sol daki karakter dosyanın türünü belirtir. Burada – varsa dosya normal bir dosyadır, d ise
dizin, p ise pipe, s ise soket dosyası söz konusudur. Bu karakterden sonra üçerli grup halinde
owner, group, other hakları rwx formatıyla belitilir, - ilgili hakkın olmadığı anlamına gelir.
- rw- r- - r- - a.dat
Burada a.dat dosyası normal bir dosyadır, dosyanın sahibi hem okuma hem yazma yapabilir,
fakat dosya çalışabilen bir dosya olmadığı için çalıştırma yapamaz. Dosyanın sahibiyle aynı
grupta olan kullanıcı dosyayı yalnızca okuyabilir, herhangi birisi de dosyayı yalnızca
okuyabilir. Örneğin, ali, veli ve selami project isimli bir gruba ilişkin bir kullanıcı olsun. Ali
kendi yarattığı dosyanın hem kendisi hemde grup üyeleri tarafından okunup yazılabilmesini
istemekte ancak herhangibir kişinin erişimine tamamen kapatmak istemektedir. Bu dosyanın
erişim bilgileri şöyle olacaktır.
– rw- rw- - - - ali project a.dat
Dosyanın sahibinin yapamadıklarını, dosyanın sahibiyle aynı grupta olan yada herhangi birisi
yapabilir. Örneğin;
- r- - rw- rw-
Burada dosyanın sahibi dosyayı yalnız okuyabildiği halde aynı gruptaki kişi ve herhangi bir
kişi hem okuyup, hemde yazabilmektedir.
Süper kullanıcı (root) hiçbir biçimde güvenlik engeline takılmaz, her dosyaya her istediğini
yapabilir.

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.

PROSESİN KULLANICI ID si VE GRUP ID si OLUŞTURULMASI


Çalışmakta olan programlara proses denilmektedir. Unix/Linux sistemlerinde de bir proses
başka bir prosesi çalıştırabilmektedir. Bir proses bir prosesi çalıştırdığında çalıştırılan proses
otomatik olarak çalıştıran prosesin kullanıcı ID si ve grup ID sini alır. Unix/Linux
sistemlerinin başlatılması ve login programına gelene kadar ki aşamalar başka bir başlıkta ele
alınacaktır. Süper kullanıcı olan root un kullanıcı ID si ve grup ID si her sistemde 0 dır.
Login prosesi root önceliğinde çalışmaktadır, yani onun userID ve groupID si 0 dır. Login
programı daha önce de bahsedildiği gibi kullanıcı ismini kullanıcıdan alır ve /etc/psswd
dosyasına bakarak password doğrulaması yapar. Doğrulama yapıldıktan sonra kullanıcı ID si
ve grup ID sini bu dosyada belirtilen kullanıcı ve grup ID sine çeker ve shell programını
çalıştırır. Yani shell programı çalıştırıldığında shell prosesinin user ID si ve group ID si
/etc/psswd dosyasında belirtilen user ID si ve group ID si olur.

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.

DOSYANIN ERİŞİM HAKLARININ DEĞİŞTİRİLMESİ


Bir proses etkin kullanıcı ID si dosyanın kullanıcı ID sine eşit olan dosyaların (yani kendi
yarattığı dosyaların) erişim haklarını değiştirebilir, bunun için chmod komutu kullanılır. Bir
proses dosyanın sahibinin ilişkin olduğu grupta olsa bile değiştirme yapamaz. Chmod shell
komutunun genel biçimi şöyledir; chmod <3 octal digit> <dosya ismi> her octal digit sırasıyla
owner, group, other haklarını göstermektedir. Şüphesiz root kullanıcısı her türlü değişikliği
yapabilmektedir.

UNIX/LINUX YARDIM MEKANİZMASI


Unix/Linux sistemlerinde geleneksel olaral manual pages diye isimlendirilen bir yardım
mekanizması vardır. bu yardım mekanizması ciltlerden oluşmuştur. Örneğin birinci cilt shell
komutlarına ilişkin ikinci cilt sistem fonksiyonlarıyla ilgilidir. Yardım almak için man string
komutunu kullanmak gerekir. Default olarak man programı ilk bulduğu ciltteki bilgileri

Sayfa 8 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

göstermektedir, ancak cilt no su verilebilir, örneğin man 2 chmod. man programından Q


tuşuyla çıkılabilir.

UNIX/LINUX SİSTEMLERİNDE C PROGRAMLARININ DERLENMESİ VE


BAĞLANMASI
Unix/Linux sistemlerinde geleneksel olarak komut satırlı derleme işlemi kullanılır. Yani
program ayrı bir editörde yazlır ve komut satırında derlenir.Unix/Linux sistemlerinin en
klasik editörü vi editörüdür. Ancak bugün Linux sistemlerinde Linux paketi içerisinde daha
pekçok editör vardır.
Unix/Linux sistemlerindeki C derleyicilerinin geleneksel ismi cc (c compiler) ismindedir.
GNU projesi kapsamında yazılmış olan C derleyicisine gcc ismi verilmiştir. Unix/Linux
sistemlerindeki geleneksel bağlayıcı programın ismi ld dir. Aslında bugün kü Linux
sistemlerinde gcc yalnızca bir C derleyicisi değil, tüm derleyicileri çalıştıran ana bir program
görevindedir. Örneğin, bir gcc programı ile bir fortran yada bir ada programıda çalıştırılabilir.
Gcc programı default olarak derleme işleminden sonra ld isimli bağlayıcı programı da
çalıştırmaktadır. Bugün Linux sistemlerinde kullanılan C++ derleyici g++ derleyicisidir.
Fakat gcc programı dosyanın uzantısı C++ sa ilişkinse otomatik g++ derleyicisini sisteme
sokmaktadır. Gcc derleyicisiyle C programı derlemek basitçe şöyle yapılabilir. gcc
<dosya_ismi.c> Bu işlem hem derleme hemde bağlama işlemini yapar ve çalışabilen
dosyanın ismi default olarak a.out şeklinde olur.

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.

DOSYA SİSTEMİNE İLİŞKİN TEMEL SİSTEM FONKSİYONLARI


Unix/Linux sistemlerde pek az sayıda dosya sistem fonksiyonu vardır. bu sistem fonksiyonları
aynı parametreik yapılarla POSIX fonksiyonu olarak kabul edilmiştir. Bir dosyayı açmak ve
yaratmak için open, read ve write dosya göstericisisnin gösterdiği yerden n byte okumak ve
yazmak için, kapatmak için close, lseek dosya göstericisini konumlandırmak için sistem
fonksiyonları kullanılır.

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.

SİSTEM FONKSİYONLARININ BAŞARISI VE BAŞARISIZLIĞIN NEDENİNİN


TESPİT EDİLMESİ
Unix/Linux sistem fonksiyonlarının çok büyük çoğunluğu başarı durumunda 0 değerine
başarısızlıkta –1 değerine dönmektedir. Fonksiyonlar başarısızlık durumunda başarısızlığın
nedenini anlatan bir sayısal değeri errno isimli global bir değişkene yerleştirirler. errno
değişkeninin extern bildirimi errno.h içerisindedir. Ayrıca errno.h dosyası içerisinde hata
değerlerine ilişkin başı E ile başlayan çeşitli sembolik sabitler tanımlanmıştır. Örneğin, bir
dosyadan okuma yapmak isteyelim, fakat dosyayı okuma hakkımız olmasın. Bu durumda read
fonksiyonu –1 le geri döner ve errno değişkenine bakıldığında EACCES değerinin
yerleştirildiğini görürüz.

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

int strerror_r(int errnum, char *buf, size_t n);


Fonksiyonun prototipi string.h içerisindedir. Fonksiyon hata yazısının bulunduğu statik yerel
bir dizinin adresine dönmektedir.
int fd;
if((fd =open(...)) < 0){
fprintf(stderr “%s”, strerror(errno));
exit(EXIT_FAILURE);
}

DOSYA SİSTEMİNİN CACHE MEKANİZMASI


Modern işletim sistemlerinin dosya sistemleri birkaç düzey cache kullanmaktadır. Uygulanan
en aşağı seviye cache sistemi diskten son okunan blokların RAM de saklanmasına yöneliktir.
Bu düzeydeki cache sistemi dosya kavramının da altında blok düzeyinde çalışmaktadır.
Geleneksel olarak Unix/Linux sistemlerinde bu cache sistemine vuffer cache denilmektedir.
Buffer cache sisteminin yukarısında işletim sisteminin dosya sistemi oturtulmuştur. İşletim
sisteminin dosya sistemi ayrıca açılmış olan dosyaların belirli bölümlerini de o anda RAM de
tutarak tamponlamaktadır. Benzer biçimde standart C fonksiyonları da kullanıcı düzeyinde
ayrı bir tamponlama gerçekleştirmektedir.

İŞLETİM SİSTEMİNİN DOSYA FONKSİYONLARI VE STANDART C


FONKSİYONLARI
Aslında tüm dosya işlemleri işletim sisteminin bir grup sistem fonksiyonu ile yapılmaktadır.
Fakat her işletim sisteminin sistem fonksiyonları isim ve parametrik yapı bakımından
farklıdır. Örneğin dosyadan okuma yapmak için Win32 sistemlerinde readFile ,Unix/Linux
sistemlerinde read fonksiyonu kullanılmaktadır. Prototipleri <stdio.h> içerinde olan ve başı f
ile başlayan fonksiyonlar tipik dosya fonksiyonlarıdır. Standart C fonksiyonları aslında gerçek
dosya işlemi yapan fonksiyonlar değildir. Bu fonksiyonlar kütüphane hangi sistem için
yazılmışsa o sistemdeki fonksiyonları çağırırlar. Örneğin, fread fonksiyonu derleyici Win32
için yazılmışsa readFile API fonksiyonunu Unix/Linux sistemleri için yazılmışsa read
fonksiyonunu çağırmaktadır. Fakat standart C fonksiyonları ayrıca birde kullanıcı düzeyinde
tamponlama da yapmaktadır. Şüphesiz işletim sisteminin sistem fonksiyonları taşınabilir
olmamasına karşın daha geniş yeteneklere sahiptir.

UNIX/LINUX SİSTEMLERİNDE KERNEL NESNELERİ


İşletim sisteminin çalışması için kullanılan handle alanlarına kernel nesneleri denilmektedir.
Kernel nesnelerininen önemlilerinden biri proses bilgilerinin tutulduğu handle alanıdır.
İşletim sistemleri her proses için bir handle alanı oluşturmakta ve bu alanda prosese ilişkin
önemli bilgileri saklamaktadır. Prosesler için yaratılan bu handle alanlarına Win32
sistemlerinde “process database”, Unix/Linux sistemlerinde “proses tablosu” denilmektedir.
Proses yaratıldığında proses tablosuna ilişkin handle değeri elde edilmektedir. Örneğin Win32
sistemlerinde bu handle değeri HANDLE türüyle temsil edilmiştir. Bu tür normal olarak
void* biçimindedir. Unix/Linux sistemelerinde ise prosese ilşkin handle değeri pid_t türüyle
temsil edilmiştir ve tipik olarak bu tür int biçimindedir.

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.

open FONKSİYONU VE ERİŞİM KONTROLLERİ


open fonksiyonu dosyayı açarken belirtilen açış modları ile erişim haklarını sorgulamaktadır.
Örneğin açış modunun O_RDWR olduğunu düşünelim dosyayı dosyanın sahibiyle aynı
grupta olan kişi açıyor olsun. Fakat dosyanın erişim hakları rw- r—r— olsun, bu dosya
belirtilen modda açılamayacaktır. Erişim başarısızlığından dolayı open fonksiyonu başarısız
olursa errno EPERM biçiminde set edilir. Görüldüğü gibi erişim kontrollerinin çoğu daha
dosya açılmadan open fonksiyonu içerisinde yapı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.

read ve write FONKSİYONLARI


Bu fonksiyonlar dosya göstericisinin gösterdiği yerden n byte ı dosyadan okumak ve n byte
dosyadan okumak için kullanılırlar.

ssize_t read(int fd , void *buf, size_t n);


ssize_t write(int fd ,const void *buf, size_t n);

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

int main(int argc, char *argv[])


{
if((fs = open(argv[1], O_RDONLY)) < 0){
perror(“open”);

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.

DOSYA BETIMLEYICILERININ ANLAMI


open fonksiyonundan elde ettigimiz dosya betimleyicisi aslinda dosya betimleyicisi dedigimiz
bir tabloda index belirtmektedir. Dosya betimleyici tablosu aslinda bir gösterici dizisidir.
Dosya üzerinde islem yapilmasini saglayan gerçek dosya bilgileri dosya nenesi diye
isimlendirebilecegimiz yapilar içerisinde saklidir. Dosya betimleyici tablosunun herbir adresi
bir dosya nesnesini göstermektedir. Dosya betimleyici tablosunun baslangiç adresi de proses
tablosunda tutulmaktadir. Dosya betimleyicisi aslinda dosya betimleyici tablosunda bir index
belirtmektedir.
Dosya betimleyici file nesnesi
stdin
0
pDe
sTa
b
a.dat

Dosyanin tüm bilgileri dosya nesnesinde tutulmaktadir, örnegin;


* Dosyanin disk üzerinde hangi bloklarda oldugu
* Dosyanin uzunlugu
* Dosya göstericisinin o andaki offset numarasi
* Dosyanin erisim haklari
* Dosyaya iliskin zamansal bilgiler
Dosya göstericisi içindeki bu bilgiler süphesiz diskten alinarak oluturulmustur. Yani open
fonksiyonuyla dosya açildiginda sistem bu bilgileri diskten alarak bellekte bir dosya nesnesi
biçiminde olusturmustur. Bu dosya içindeki bilgilere i-node (information node) bilgileri
denilmektedir.
Login programi çalistirildiginda 3 dosya otomatik olarak açilmis olur. Klavyeyi temsil eden
stdin dosyasinin betimleyici numarasi 0 dir, ekrani temsil eden stdout dosyadinin betimleyici
numarasi 1 dir. Stderr default olarak stdout ile ayni dosya nesnesini görmektedir, yani stderr
dosyasinda bir sey yazildiginda ekrana çikar.
Linux' un kaynak odlarinda dosya nesneleri struct File yapisiyla temsil edilmistir. Bu
durumda dosya betimleyici tablosu herbir elemani struct File türünden bir gösterici olan
gösterici dizisidir. Bu durumda proses tablosundaki betimleyici dizisinin baslangiç adresini
tutan alan bir göstericiyi gösteren göstericidir.(struct File **pDesTable)

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

Dosya betimleyici tablosu file nesnesi


stdin
0
pDesTab

a.dat

SANAL DOSYA SİSTEMİ (Virtual File Sistem)


Bilindiği gibi Unix/Linux sistemleri pekçok dosya sistemini desteklemektedir, üstelik pekçok
aygıtta birer dosya gibi çalışmaktadır. Yani seriport, cdrom, diskler, soket, pipe gibi çeşitli
aygıt yada kavramlar open fonksiyonuyla açılıp, transferler read- write fonksiyonlarıyla
yapılmaktadır. Unix/Linux çekirdek mimarisinde ilk kez BSD sistemleriyle başlayan bir çok
biçimli yapı kullanılmaktadır. Bu yapıya sanal dosya sistemi (virtual file system)
denilmektedir. Sanal dosya sistemi bir dosya sistemi değildir, dosya sistemlerini
oluşturabilmek için çekirdekçe kullanılan bir tekniğin ismidir. Sanal dosya sisteminde open,
read, write ve close fonksiyonları aslında bir noktadan sonra asıl işlemi yapan kendilerinin
bilmediği bir fonksiyonu çağırmaktadır. Yani read fonksiyonun çağırdığı fonksiyon duruma
göre diskten okuma yapabilen bir fonksiyon, klavyeden okuma yapan bir fonksiyon yada pipe
tan okuma yapan bir fonksiyon olabilmektedir. Bu sistemde read gibi bir fonksiyon aslında
betimleyici hangi aygıta ilişkinse o aygıtın read fonksiyonunu çağırmaktadır. Unix/Linux
sistemlerinde aygıt sürücüleri de windows ta olduğu gibi open fonksiyonu ile açılmaktadır.
Aygıt sürücüler open fonksiyonu ile açıldıktan sonra onlarla transfer için read ve write
fonksiyonları kullanılmaktadır. Sanal dosya sisteminin şekilsel mimarisi şöyledir;

Dosya betimleyici tablosu file nesnesi


stdin
0 pOpen
pDesTab
pClose

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

3. Dosya nesnesinden dosya işlemlerine ilişkin fonksiyon gösterici dizsinin başlangıç


adresini elde eder.
4. Dosya fonksiyon gösterici dizisinden read işlemine ilişkin fonksiyon göstericisinin
gösterdiği fonksiyonu çağırır.
Şüphesiz dosya işlem tablosu biçiminde isimlendirilen fonksiyon gösterici dizisinden aslında
her dosya için ayrı bir tane yoktur, her tür aygıt için ayrı bir tane vardır. Örneğin, aynı diske
ilişkin iki dosyanın dosya nesneleri aynı dosya işlem tablosunu göstermektedir. Örneğin stdin
ve stdout dosyalarına ilişkin durum şöyledir;
Dosya betimleyici tablosu file nesnesi
stdin dosya işlem tablosu
0 klavyeden al()
pDesTab pRead

stdout

pWrite

dup ve dup2 Fonksiyonları


dup fonksiyonu, parametre olarak aldığı dosya betimleyicisi ile aynı dosya nesnesini
gösterecek biçimde yeni bir dosya betimleyicisi oluşturur.
int dup(int fd);
Fonksiyonun prototipi <unistd.h> dosyası içerisindedir. Fonksiyon parametresi ile belirtilen
betimleyici ile aynı dosya nesnesini gösteren yeni bir betimleyici oluşturur ve onun
numarasıyla geri döner. dup fonksiyonunun ilk boş betimleyici numarasını vereceği garanti
altına alınmıştır.
Örneğin:

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
... ...
...

Bu durumda, yönlendirme işlemi dup2 fonksiyonuyla şöyle yapılabilir:

if ((fd = open(”x.dat”, O_RDONLY) < 0) {


perror(“open”)
exit(EXIT_FAILURE);
}
dup2(fd, STDIN_FILENO);

Dosya sistemi ve Aygıt Sürücüleri


İşletim sistemlerinde genellikle aygıt sürücüleri dosya sistemi üzerine oturtulmuşlardır. Aygıt
sürücüsü(device driver) çekirdek modunda çalışan özel programlardır. Normal programlar,
kullanıcı modunde (user mode) çalışmaktadır. Örneğin intel işlemcilerinde tipik olarak dört
güvenlik seviyesi vardır. Bu seviyelere ring 0, ring 1, ring 2, ring 3 denilmektedir. En yüksek
öncelik ring 0, en düşük öncelik ring 3’tür. Ancak, intel mimarisinde çalışan win32 ve
unix/linux sistemleri yalnızca ring 0 ve ring 3 seviyelerini kullanmaktadır. Sıradan programlar
ring 3, işletim sistemi seviyesindeki programlar ring 0’ı kullanır. ring 0’da, hiçbir koruma
engeline takılınmaz, bütün belleğe erişilebilir, in ve out komutlarıyla bütün donanım birimleri
programlanabilir. Ancak ring 3 programları, bunları yapamamaktadır. Yine intel mimarisinde
belirli bellek bölgeleri sadece ring 0’da çalışan programların ulaşabilmesi sağlanmıştır.işletim
sisteminin kendi kod ve verileri ring 0 ile erişilebilen bölgelerdedir. Sıradan programlar
buralara erişemezler.
Madem sıradan programlar ring 3’te çalışıyorlar, o zaman sıradan bir programın bir sistem
fonksiyonunu çağırdığında, o sistem fonksiyonu nasıl sistem alanına erişebiliyor?
İşte bir sistem fonksiyonu çağırıldığında, process’in önceliği geçici süre ring 0’a
çekilmektedir. Akış sistem fonksiyonundan çıktığında yeniden ring 3 seviyesine geri döner.
unix/linux sistemlerinde tüm sistem fonksiyonları için öncelik yükseltmesi yapılmaktadır.
Programcı istediği bir kodu ring 0’da çalıştıramaz. Yalnızca belirlenen fonksiyonları ring 0’da
çalıştırabilmektedir. Intel terminolojisinde bu mekanizmaya kapı denilmektedir.

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.

AYNI DOSYANIN BİRDEN FAZLA KEZ AÇILMASI


Open fonksiyonu her defasında yeni bir dosya nesnesi oluşturmaktadır. Aynı dosya aynı
process tarafından birden fazla kez açılabilir. Bu durumda o dosyaya ilişkin birden fazla
dosya nesnesi yaratılacaktır. Dosya göstericisinin konumu dosya nesnesinde saklandığına
göre iki betimleyicinin dosya gösterici değeri farklı olacaktır. Örneğin;
fd1 = open(“a.dat”); dosya nesnesi
fd2 = open(“a.dat”);

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

i-node DOSYA SİSTEMLERİNİN TEMEL DİSK ORGANİZASYONU


Unix/Linux sistemlerinin dosya sistemleri çeşitli farklılar göstersede bunlar birbirine oldukça
benzemektedir. Tipik bir i-node dosya sisteminin disk organizasyonu şu bölümlerden
oluşmaktadır.

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

Unix sistemlerinde de dizinler tamamen bir dosya görünümündedir. Dosyaların içerisinde


dosya bilgileri vardır ama dizinlerin içerisinde dizin içerisindeki dosyalara ilişkin bilgiler
bulunur. Dizinlerde bir çeşit dosya olduğuna göre onlarında birer i-node numarası vardır.
dosya ile dizinlerin sadece içindeki bilgiler farklıdır. Bir dizinin içeriği dosya ismi ve i-node
numarasından oluşan kayıtlar biçimindedir.

Dosya ismi i-node no


Dosya ismi i-node no
Dosya ismi i-node no
...... .......

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.)

stat ve fstat FONKSİYONLARI


Bu fonksiyonlar en çok kullanılan POSIX fonksiyonlarındandır. stat fonksiyonu bir path ile
verilen dosyanın i-node bilgilerini fstat ise açılmış bir dosyanın i-node bilgilerini vermektedir.
int stat(const char *path, struct stat *buf);
int fstat(int fd, struct stat *buf);
Fonksiyonların prototipleri <sys/stat.h> içerisindedir, stat yapısı aşağıdaki gibidir:
struct stat{
dev_t st_dev;

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

bulunmaktadır. Maalesef Unix/Linux sistemlerinde blok kavramı iki anlamda


kullanılmaktadır, stat yapısındaki blok 512 byte’ lık disk bölümlerini yani sektörleri belirtir.
Bu kavrama sıklıkla 512 byte blok denilmektedir. Diğer durumda blok FAT dosya
sistemindeki cluster uzunluğu gibi anlam ifade etmektedir. Böyle blok kavramına sıklıkla i-
node blok denilmektedir.örneğin çok küçük bir dosya için du komutu uygulandığında 4 gibi
bir değer elde edilmiş olsun. Buradaki cluster anlamına gelen blok uzunluğu 4 sektördür.
time_t st_atime, st_mtime, st_ctime: time_t signed long int olan hem <sys/types.h> ‘ ta
hemde standart <time.h> dosyasında belirtilen bir typedef ismidir. st_atime bir dosyaya
okumak yada yazmak amaçlı son erişilme zamanıdır. st_mtime dosyaya yazma amaçlı son
erişilme zamanıdır. st_ctime dosyanın son i-node bilgilerinin değiştirildiği zamandır. time_t
01.01.1970’ den itibaren geçen saniye sayısı biçimindedir.

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

int link(const char *path, const char *newpath);


Fonksiyonun prototipi <unistd.h> dosyası içerisindedir. Bir dosyanın link inden
oluşturabilmek için link ini oluşturacağımız dizine yazma hakkımız olması yeterlidir.
Dosyanın linkinden oluşturmak demek o dosyayı açma hakkına sahip olmak demek değildir.
Zaten dosyanın linkinden oluşturulduğunda dosyanın erişim haklarında yada sahiplik
bilgilerinde bir değişiklik olmaz. Link fonksiyonu başarı durumunda 0 değerine, başarısızlık
durumunda –1 değerine geri dönmektedir.

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.

DİZİNLERİN ERİŞİM HAKLARI


Bir dizinin dizin elemanlarından oluştuğu önceki bölümde ele alınmıştı. Bir dizinde yeni bir
dosya yaratmak yahut ta olan bir dosyayı dizinden silmek aslında o dizine bir çeşit yazma
işlemi yapmaktır. O halde bir dizine yazma hakkımızın olması oradaki bir dizin girişini
silebileceğimiz (tabii silmek için başka koşullarda gerekmektedir) yada o dizinde yeni bir
giriş yaratabileceğimiz anlamına gelir. Bir dizinden okuma yapmak demek dizin
elemanlarının readdir gibi fonksiyonlarla okunması demektir. Yani biz bir dizindeki i-node
numarasına gereksinim duyacak bir işlem yapıyorsak dizine okuma hakkımızın olması
gerekir.
Dizinler için x hakkı özel bir anlama gelmektedir, bir path ifadesinde ilerleyebilmek için path
ifadesinin sonundaki dizinde dahil olmak üzere geçişteki her dizin için processin x hakkına
sahip olması gerekir. Örneğin, open fonksiyonu ile /a/b/c/d.dat dosyasına O_RDONLY
moduyla açmak isteyelim;
1. a ve b dizinleri için yalnızca x hakkına sahip olmamız yeterlidir.
2. c dizini için de x ve r haklarına sahip olammız gerekir.
3. d.dat dosyası için r hakkına sahip olmamız gerekir.
Görüldüğü gibi path ifadesinde dizinlerin arasından geçmek o dizinler için okuma işlemi
yapmak anlamına gelmemektedir. Bu nedenle içinden geçme işlemi için r hakkına sahip
olmanın bir önemi yoktur, x hakkına sahip olmak gerekir.

Sayfa 29 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

/* Klavye ayarlarını gösteren komut: stty –a */

stdin, stdout, stderr Dosyaları


Standart kütüphanede üç dosya başlangıçta açık kabul edilir. Bunlar programcı tarafından
açılmaz yada kapatılmaz. Stdin, stdout ve stderr isimleri C de <stdio.h> içerisinde belirtilmiş
birer makrodur. Bu ifadeler FILE* türünden birer adres belirtmektedir. Tipik olarak
Unix/Linux sistemlerindeki standart C kütüphanesinde stdin dosyası kullanıldığında işlemler
0 numaralı betimleyici ile stdout dosyası kullanıldığında 1 numaralı betimleyici ile stderr
dosyası kullanıldığında 2 numaralı betimleyici ile işlemler yapılmaktadır. Standart
fonksiyonlardan başı f ile başlamayanlar default olarak stdin yada stdout dosyasıyla işlem
yapmaktadır. Program çalışmaya başladığında stdin dosyası default olarak klavyeye, stdout
dosyası ise ekrana yönlendirilmiştir. Standart C fonksiyonlarında kullanılan tamponlama
işlemleri bu fonksiyonlar içinde geçerlidir.

Standart C Fonksiyonlarının Tamponlama Biçimleri ve FILE Yapısı


Bilindiği gibi FILE yapı ismi standart olmakla birlikte bu yapının içeriği standart değildir,
yani her işletim sisteminde FILE yapısının içeriği farklı olabilir. Tipik olarak FILE yapısının
içeriği işletim sisteminin sistem fonksiyonlarını kullanmak için gereken handle ve
tamponlama işlemlerini gerçekleştirebilmek için gereken bilgiler biçimindedir. Örneğin, biz
fopen fonkisyonunu çağırdığımızda fopen Unix/Linux sistemlerinde open fonksiyonunu
çağırır ve buradan elde ettiği betimleyiciyi FILE yapısının içine yazar.
Standart C fonksiyonları üç tür tamponlama modunda çalışabilmektedir.
1. Tam tamponlamalı mod: Burada okuma sırasında bir tamponluk bilgi dosyadan
tampona çakilir, ondan sonra mümkünse tampondan bilgiler verilir. Yazma işlemi sırasında da
yazılacak kısım tampondaysa tampona yazılır. Tampon dolduğunda yada dosyanın başka
bölümü tampona çekileceği zaman tazeleme yapılır.
2. Satır tamponlaması: Okuma sırasında bir tamponluk bilgi değil bir satırlık bilgi (\n
dahil) tampona çekilmektedir. Yazma sırasında doğal olarak yada \n karakteri dosyaya
yazıldığında tazeleme işlemi yapılır.
3. 0 tamponlama: Bu modda tampon hiç kullanılmaz, okuma yada yazma işlemi
sistem fonksiyonu çağırılarak hemen yapılır.
Standartlarda default olarak stdin ve stdout dosyaları için şunlar söylenmiştir. “Bu dosyalar
eğer klavye ekran gibi karşılılı etkileşimli aygıtlara yönlendirilmişse kesinlikle tam
tamponlamalı olamaz, fakat satır tamponlamalı yada 0 tamponlamalı olabilir. çift
gerektirmeli olarak eğer bu dosyalar tam tamponlamlı durumdalarsa kesinlikle karşılıklı
etkileşimli aygıtlara yönlendirilmiş durumda değillerdir.” Standartlardaki bu ifadeler işin
başında yani tamponlama değiştirilmediyse söz konusu olan default durumu anlatmaktadır.
Burada özetle, stdin ve stdout dosyalarının işin başında tam tamponlamalı olamayacağı ama
derleyicileri yazanların isteğine göre satır tamponlamalı yada 0 tamponlamalı olabileceği
belirtilmektedir. Standartlar stderr dosyasının hiçbir zaman tam tamponlamalı olamayacağını
(yani karşılıklı etkileşimli bir aygıta bağlı olmasalar bile) belirtmiştir. Yani buradan şu sonuç
çıkmaktadır: İşin başında stdin ve stdout tam tamponlamalı değildir ama biz bunları bir
dosyaya yönlendirmişsek sistem onları tam tamponlamalı moda geçirebilir. Ancak stderr
dosyası hiçbir zaman tam tamponlamalı moda sistemin kendisi tarafından geçirilmemektedir.
Bu durumda örneğin aşağıdaki gibi bir işlem yapılmış olsun.
printf(“deniz”);
Bu durumda deniz yazısının henüz ekranda görülmesi garanti değildir. Çünkü sistem stdout
dosyası için default olarak satır tamponlaması kullanabilir ve henüz \n karakteri yazılmadığı
için tazeleme yapılmamış olabilir. (örneğin gcc derleyicileri default satır tamponlama, borland

Sayfa 30 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

derleyicileri 0 tamponlama kullanmaktadır.) Yukarıdaki yazının ekrana yazılmasını


garantilemek için şunlar yapılabilir.
1. printf(“deniz\n”);
2. printf(“deniz”);
fflush(stdout);
Derleyicilerin çoğu standartlarda böyle bir belirtme olmamasına karşın stdin dosyasından
okuma yapılırken stdout dosyasını tazelemektedir. Örneğin,
printf(“deniz”);
getchar();
Burada derleyici default olarak satır tamponlamasını kullanıyor olsa bile getchar ile klavyeden
okuma yapılırken deniz yazısı ekranda görüntülenebilir.

Standart Okuma Fonksiyonları ve Satır Tabanlı Tamponlama Mekanizması


Hemen hemen bütün C derleyicilerinde stdin dosyası işin başında satır tamponlamalıdır.
getchar, gets ve scanf gibi fonksiyonlar stdin dosyasından okuma yaparlar ve aynı tamponu
kullanırlar. Bu durumda bu fonksiyonlar önce tampona bakarlar, tamponda bilgi varsa oradan
bilgiyi alırlar. Tamponda hiç bilgi yoksa bir satırın tamamını klavyeden alarak tampona
yerleştirirler.
stdin tamponunu pratik bir biçimde boşaltacak hiçbir standart yöntem yoktur. Bazı
sistemlerde fflush(stdin); bu işi yapıyorsa da bunun hiçbir garantisi yoktur. Çünkü yalnız
okunabilen dosyaların fflush işlemi yapılabilmesi sözkonusu değildir.
Bilindiği gibi standart C fonksiyonları genel olarak dosyanın sonuna gelindiyse yada error
durumu oluştuysa EOF değerine geri dönerler. Peki, stdin dosyasının sonuna gelmek ne
demektir? Eğer stdin bir dosyaya yönlendirilmişse dosyanın sonuna gelme kavramı geçerlidir,
fakat dosya klavyeye yönlendirilmiş bir biçimdeyse EOF etkisin yaratan özel tuşlar kullanılır,
bu özel tuşlar tipik olarak dos ve windows sistemlerinde Ctrl+Z, Unix/Linux sistemlerinde
Ctrl+D dir. stdin dosyasının sonunu temsil eden tuş basımı sistemdem sisteme değişik olabilir.
Standartlarda bu konu hakkında bir şey söylenmemiştir. Stdin tamponunu temizlemek için şu
kalıplar kullanılabilir.
1. while((ch = getchar()) != EOF && ch != ‘\n’)
;
2. while(ch != EOF && ch != ‘\n’)
ch = getchar();

Çeşitli Standart Giriş Fonksiyonlarının Davranışları


getchar() Fonksiyonu: Tampondaki karakteri alır ve onunla geri döner. Tamponda hiçbir
karakter yoksa kullanıcıdan giriş bekler. Dosyanın sonuna gelindiğinde yada okuma hatası
olduğunda EOF değerine geri döner.
gets() Fonksiyonu: \n karakterine kadar bu karakter dahil olmak üzere karakterleri okuyarak
diziye yerleştirir. \n yi okur fakat onun yerine NULL karakteri diziye yerleştirir. gets diziye
hiçbir karakter yerleştiremeden dosya sonunu görürse NULL değerine diğer durumlarda
yerleştirdiği dizinin başlangıç adresine geri döner.
scanf() Fonksiyonu: Geri dönüş değeri int biçimindedir. Fonksiyon eğer dosyadaki
karakterler formata uygunsa onu alıp yerleştirir, formata uygun değilse işlemi sonlandırır.
Okuyabildiği birim sayısına geri döner, eğer hiçbir karakter okumadan dosya sonunu
görmüşse EOF değerine geri dönmektedir. Bu fonksiyon baştaki boşluk karakterlerini
atmaktadır.

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

int fileno(FILE *f);


int main(void)
{
FILE *f;
if((f = fopen(“data”, “r”)) == NULL){
fprintf(stderr, “cannot open file\n”);
exit(1);
}
/*...*/
ch = fgetc(f);
putchar(f);
fflush(f);
write(fileno(f), “deniz”, 5);
ch = fgetc(f);
fclose(f);
}
Şüphesiz, standart C fonksiyonlarıyla sistem fonksiyonlarını aynı anda kullanmak tehlikeli
olabilir, programcının bunu dikkate alması gerekir.

Dosya Tamponlama Modunun Değiştirilmesi


stdin, stdout ve stderr dışındaki dosyaların başlangıçtaki tamponlama modu standartlarda
belirtilmiş olmasa da derleyicilerin hemen hepsinde tam tamponlamalı biçimdedir. Programcı
isterse herhangi bir dosyanın tamponlama modunu değiştirebilir. Bunun için setvbuf
fonksiyonu kullanılmaktadır.
int setvbuf(FILE *f, char *buf, int mode, size_t size );
Fonksiyonun birinci parametresi tamponlama modu değiştirilecek dosyaya ilişkin dosya bilgi
göstericisidir. Default tampon büyüklüğü <stdio.h> içerisinde BUFSIZ sembolik sabitiyle
belirtilmiştir. Fonksiyonun ikinci ve dördüncü parametreleri kullanılacak tamponun adresi ve
uzunluğudur. fopen ile dosya açıldığında tampon alan fopen fonksiyonu tarafından BUFSIZ
uzunlukta tahsis edilir. işte bu fonksiyon alanında default tampon uzunluğunu değiştirebiliriz.
Yeni set edilen tampon alanının dosya kapatılana kadar yaşaması gerekir. İkinci parametre
NULL girilirse tampon setvbuf fonksiyonu tarafından tahsis edilir. (İkinci parametre NULL,
son parametre BUFSIZ girilirse şüphesiz fonksiyonun bir tahsisat yapmaması beklenir.)
Fonksiyonun üçüncü parametresi dosyanın yeni tamponlama modudur ve şunlardan biri
olabilir: _IOFBF, _IOLBF, _IONBF. Örneğin, aşağıda dosyanın tamponlama modu 0
tamponlamaya çevrilmiştir.
setvbuf(f, NULL, _IONBF, BUFSIZ);

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

tamponlaması 0 tamponlama moduna geçirilse bile işlevsel bir değişiklik görülmeyebilir.


Çünkü bu durumda standart C fonksiyonları her defasında read fonksiyonunu
çağıracaklardır, fakat read fonksiyonu 0 numaralı betimleyiciden okuma yaparken işletim
sistemi düzeyinde başka bir satır tamponlaması kullanılır. Yani, biz 0 numaralı betimleyici
kullanarak read fonksiyonuyla 1 byte okumaya çalışsak bile sistem bizden bir satırlık bilgi
alacaktır.
int main(void)
{
setvbuf(stdout, NULL, _IOFBF, 0);
printf(“deniz\n”);
sleep(5);
}

Ç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.

Unix/Linux Sistemlerinin Process Modeli


Unix/Linux sistemlerinde processler arası alt-üst ilişkisi oldukça kuvvetlidir. Yani, alt process
yaratıldığında üst process ten pek çok özelliği almaktadır. Unix/Linux sistemlerinde
processler yalnızca fork sistem fonksiyonuyla yaratılırlar.
pid_t fork(void);
fork sistem fonksiyonuyla yeni bir process yaratıldığında process e ilişkin bir handle alanı
oluşturulur, bu handle alanına process tablosu denilmektedir. Process tablosuna erişmek için
kullanılan handle değeri pid_t türüyle temsil edilmiştir.
Pid_t Process Tablosu

.....

pid_t türü tipik olarak int biçimde typedef edilmiştir.


typedef int pid_t;
fork fonksiyonunu çağıran process e üst process, yaratılan process e de alt process denir. fork
fonksiyonunun geri dönüş değeri yaratılan alt process in handle değeridir. Unix/Linux
sistemlerinde process tablosunun handle değerine “process ID” de denilmektedir. Bir process
istediği zaman kendi process ID değerini getpid fonksiyonu ile, üst fonksiyonun process ID
değerini getppid fonksiyonuyla alabilir.
pid_t getpid(void);
pid_t getppid(void);

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

for (i = 0; i < 100; ++i) {


printf("%d\n", i);
Sleep(1000);
}
return 0;
}

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)

Fork İşlemiyle Dosya Betimleyici Tablosunun Alt Processe Geçirilmesi


Fork işlemiyle dosya betimleyici tablosunun da bir kopyası çıkartılmaktadır. Yani fork
işleminden önce bir takım dosyalar açılmış olsun. Fork işleminden sonra hem üst process
hemde alt process aynı dosyaları aynı betimleyicilerle açık görmektedir. Örneğin, üst process
n numaralı bir dosya betimleyicisine sahip olsun ve bu dosya betimleyicisi bu dosya
betimleyicisini gösteriyor olsun. Fork işlemiyle alt processin de n numaralı betimleyicisi aynı
dosya nesnesini gösteriyor olacaktır. Sistem fork işlemi sırasında dosya nesnelerinin
sayaçlarını bir arttırmaktadır. Aşağıdaki şekilde üst ve alt processin fork işleminden sonraki
dosya betimleyici tablolarının durumu belirtilmiştir.

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);
}

C’ nin Standart Dosya Fonksiyonları ve Fork İşlemi


Standart C fonksiyonlarının kullandığı tamponlar processin adres alanı içerisindedir. O halde
fork işlemi ile bu tamponlarda alt processe kopyalanacaktır. Eğer stdio kütüphanesiyle açılan
dosyalar tazelenmeden fork yapılırsa, tazelenmemiş olan tamponlar alt processe geçecektir.
Böylesi bir durum potansiyel bir bozukluk oluşturmaktadır, çünkü tazeleme işleminin hem üst

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.

processin hayat döngüsü

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

GetExitCodeProcess ile çıkış kodunu alır, CloseHandle fonksiyonuyla da handle alanını


yokeder.

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;

if(waitpid(pid, &stat, WNOHANG) != 0){


if(WIFEXITED(stat))
printf(“Child terminated normally. Exit code: %d\n”,
WEXITSTATUS(stat));
else
printf(“Child terminated via signal\n” );
}
else
printf(“child not terminated\n”);
}
else{
int i;
for(i = 0; i < 10; ++i){
printf(“child : %d\n”, i);
sleep(1);
}
exit(10);
}
return 0;
}

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;

for(i = 0; i < 10; ++i){


printf(“parent : %d\n”, i);
sleep(1);
}
exit(0);
}
else{
int i;

Sayfa 45 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

for(i = 0; i < 10; ++i){


printf(“child : %d\n”, i);
sleep(1);
}

}
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

çağırılabileceği anlamına gelir. Bu tür fonksiyonların üç nokta dışında en az bir parametresi


olmak zorundadır. Örneğin, printf böyle bir fonksiyondur ve protopi şöyledir:
int printf(const char *format, ...);
Değişken sayıda parametre alan fonksiyonu yazan kişi fonksiyonun kaç parametreyle
çağırıldığını bilmek zorundadır. Bunun için birinci parametre kullanılabilir yada parametre
listesinin sonuna özel bir parametre girilir. Örneğin, printf fonksiyonu % karakterlerinin
sayısına bakarak parameterlerin sayısını tespit etmektedir.

POSIX sistemlerinde altı exec fonksiyonu vardır.


execl
execv
execle
execve
execlp
execvp
Bütün bu fonksiyonların yaptığı iş aynıdır ama parametreleri aynıdır. Exec isminin sonundaki
l (list) komut satırı argümanlarının tek tek sonu NULL ile bitecek şekilde girileceği analmına
gelir. Exec isminin sonundaki v (vector) komut satırı argümanlarının bir gösterici dizisinin
içine yerleştirileceği ve o dizinin başlangıç adresinin fonksiyona geçirileceği anlamına gelir. e
son eki (environment) fonksiyonun çevre değişkenlerinde aldığı, p son eki (path) fonksiyonun
path çevre değişkenine baktığı anlamına gelmektedir.

Programın Komut Satırı Argümanları


Programın komut satırı argümanları shell programı tarafından alınarak exec fonksiyonuna
parametre yapılmaktadır. Yani programın komut satırı argümanları aslında exec
fonksiyonunda belirlenmektedir. Bu komut satırı argümanları C de main fonksiyonunun
parametresi olarak programcı tarafından kullanılmaktadır. C standartlarında argv[0] program
ismi olduğu belirtilmiştir, fakat exec fonksiyonunu çağıran kişi argv[0] ı yani programın ilk
komut satırı argümanını program ismi yapmak zorunda değildir. Ayrıca standartlarda
argv[argc] nin mevcut ve NULL değerinde olması gerektiği belirtilmiştir. Bu durumda
programın komut satırı argümanları aşağıdaki iki biçimden biriyle yazdırılabilir.
1. for(i = 0; i < argc; ++i)
puts(argv[i]);
2. for(i = 0; argv[i] != NULL; ++i)
puts(argv[i]);

Komut satırı argümanları exec fonksiyonundan main fonksiyonuna nasıl geçirilmektedir?


İşletim sistemlerinin çoğunda program çalıştıran fonksiyonlar (POSIX sistemlerinde exec
fonksiyonları, win32 sistemlerinde CreateProcess fonksiyonu) programın komut satırı
argümanlarını processin bellekteki adres alanı içinde belli bir bölgeye yerleştirirler. C
derleyicilerinin başlangıç kodu komut satırı argümanlarını processin bellek alanından alarak
bunu bir gösterici dizisine yerleştirmekte ve gösterici dizisinin başlangıç adresini de main
fonksiyonuna yerleştirmektedir.

Processin Çevre Değişkenleri


Çevre değişkenleri kavramı yaygın işletim sistemlerinin hemen hepsinde kullanılan bir
kavramdır. Çevre değişkeni aslında bir yazı çiftidir, bir yazı başka bir yazıyla
ilişkilendirilerek processin bellek alanında tutulmaktadır. Yazı çiftinin anahtar olarak
kullanılan birincisine çevre değişkeni, ikincisine ise çevre değişkeninin değeri denilmektedir.
Processin çevre değişkenleri işletim sistemlerinin çoğunda processin komut satırı

Sayfa 47 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

argümanlarında olduğu gibi processin bellek alanında saklanmaktadır. Çevre değişkenlerinin


tamamı fork işlemiyle alt processe aktarılmaktadır.
Programcı Unix/Linux sistemlerinde getenv fonksiyonu ile bir çevre değişkeninin değerini
alabilir. getenv aynı zamanda bir standart C fonksiyonudur yani tüm C derleyicilerinde vardır.
char *getenv(const char *name);
Fonksiyon çevre değişkenini parametre olarak alır ve bunun değerini belirten yazının
başlangıç adresine geri döner. getenv fonksiyonu statik ömürlü bir alanın adresine geri
dönmektedir. Fonksiyonun prototipi <stdlib.h> dosyası içerisindedir. Fonksiyon başarısız
olduğunda NULL değerine geri döner.

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.

Örneğin path çevre değişkeninin değeri şöyle yazdırılabilir.


int main(void)
{
char *pVal;

if ((pVal = getenv("PATH")) == NULL) {


fprintf(stderr, "Environment variable not found!..\n");
exit(EXIT_FAILURE);
}

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

zaman ezmektedir) Fonksiyon başarılıysa 0, başarısızsa –1 değerine geri döner.


Fonksiyonların prototipleri <stdlib.h> içerisindedir.

Unix/Linux Sistemlerinde Processlerin Çevre Değişkenleri Nasıl Oluşturulur?


Unix/Linux sistemlerinde pekçok çevre değişkeni login işlemi sırasında login programı
tarafından oluşturulmaktadır. Login programı tarafından oluşturulan bu çevre değişkenleri
çalıştırılan bu shell processine aktarılmakta, shell processi de bazı çevre değişkenlerini listeye
eklemektedir. Bu durumda shell üzerinden bir program çalıştırıldığında çalıştırılan programa
hem login programının yarattığı hemde shell programının yarattığı çevre değişkenleri
geçirilmektedir. POSIX standartlarında shell processine gelindiğinde bazı standart çevre
değişkenlerinin oluşturulmuş olması gerekir. Programcı bu değişkenleri getenv fonksiyonuyla
alıp kullanabilir. Bu çevre değişkenlerinden bazıları şunlardır:
HOME: shell processinin çalışma düzenini vermektedir.
LANG: Kullanılan local yani dil ayarını vermektedir.
TERM: Kullanılan terminalin türünü vermektedir.
PATH: exec fonksiyonlarında kullanılan arama dizinlerini vermektedir.
PATH Çevre Değişkeni
PATH çevre değişkeni yalnızca Unix/Linux sistemlerinde değil, Win32 ve Dos sistemlerinde
de benzer biçimde kullanılmaktadır. PATH çevre değişkeni execp yada exec fonksiyonlarının
p li versiyonları tarafından kullanılmaktadır. Bu fonksiyonlar çalıştırılacak dosyayı PATH
çevre değişkeniyle belirtilen dizinlerde ararlar. PATH çevre değişkeninin değeri : lerle
ayrılmış bir takım path ifadeleri biçimindedir.(PATH ayarıca olarak win32 ve dos
sistemlerinde” ; “kullanılmaktadır.)

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.

Shell Üzerinde Çevre Değişkenleri ile İlgili İşlemler


Shell üzerinden çevre değişkeninin başına $ getirilerek çevre değişkeninin değeri elde
edilebilir. Bu durumda PATH çevre değişkenini shell üzerinden yazdırmak için echo $PATH
yazmak gerekir. Aynı işlem dos ta echo %çevredeğişkeni ismi% kullanılarak yani echo
%PATH% işlemiyle yapılır.
Shell üzerinden x = y işlemi ile x çevre değikeni yaratılıp buna y değeri yerleştirilmiş olur.
Örneğin:
ALI = 100
echo $ALI
100
Aynı işlem dos ve win32 sistemlerinde set ALI=100 biçiminde yapılır.
Bilindiği gibi bir çevre değişkenine yeni bir değer yerleştirildiğinde eskisi gider. Şimdi biz
PATH çevre değişkenine “:.” gibi bir yazıyı ekleyecek olalım işlem şöyle yapılmalıdır:

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.

Çevre Değişkenlerinin Kalıcılığının Sağlanması ve shell Başlangıç Dosyaları


Tipik olarak işletim sistemlerinin shell programları sistem açıldığında yada login
olunduğunda bir takım başlangıç dosyalarını çalıştırmaktadır. Kullanıcılar bu başlangıç
dosyaları içerisine shell komutları yazarlar işte bu dosyaların içerisine çevre değişkeni yaratan
shell komutları yazılırsa bu çevre değişkenlerinin kalıcılığı sağlanabilir. Bu başlangıç dosyası
dos ve windows95, 98 ve ME sistemlerinde autoexec.bat dosyayıdır. Bu dosyanın C nin kök
dizininde bulunması gerekmektedir. (Windows2000, NT ve XP sistemlerinde autoexec.bat
işlevi kalmamıştır, çevre değişkenleri denetim masasından girilmektedir.) Unix/Linux
sistemlerinde shellin çalıştırdığı başlangıç dosyalarının isimleri sistemdem sisteme
değişmektedir. Linux sistemlerinde .login ve bash shell için .bash_login dosyaları bu amaçla
kullanılabilir.

Çevre Değişkenleri Neden Kullanılır?


Çevre değişkenleri tipik olarak bir programdaki çeşitli parametrelerin dışarıdan
değiştirilmesine olanak sağlaması için kullanılmaktadır. Böylece çeşitli parametreler çevre
değişkenleriyle kolaylıkla belirlenebilir. Bu amaçla dosya yerine çevre değişkenlerinin
kullanılması daha esnektir. Örneğin;
if((pVal = getenv(“CACHESIZE”))== NULL)
cachesize= 100;
else
cachesize = atoi(pVal);
Yada bir veri tabanı programı veri tabanı dosyasının yeri için bir çevre değişkenine bakabilir.
Ayrıca çevre değişkenleri üst processten alt processe veri aktarmak içinde kullanılmaktadır.

Exec Fonksiyonlarının Kullanımı


POSIX sistemlerinde toplam 6 tane exec fonksiyonu vardır. Bu fonksiyonları kullanımı
arasında bazı farklılıklar olmakla birlikte birbirine benzerdir. Tüm exec fonksiyonlarının
kullanımı <unistd.h> içerisindedir. Benzer biçimde fonksiyonların p li versiyonları eğer path
ifadesi göreli ise path çevre değişekeni ile belirtilen dizinleri inceler ve verilen path ifadesini
bu dizinlerin sonuna ekleyerek aramayı yapar. Örneğin, path çevre değişkeninin değeri
“bin:/home/student” biçiminde olsun. Bizde dosya ismini “temp/den” biçiminde girmiş
olalım. Path ifadesi göreli olduğu için path çevre değişkenine bakılacaktır ve aşağıdaki
aramalar yapılacaktır. /bin /temp/den ve /home/student/temp/den
Exec fonksiyonalrının p li biçimlerine mutlak path ifadesi girilirse bu fonksiyonlar path çevre
değişkenine bakmazlar.
Exec fonksiyonlarının p siz biçimleri hiçbir biçimde path e bakmaz.
int execl(const char *path, const char *arg,...);
int execlp(const char *file, const char *arg,...);
Fonksiyonların birinci parametreleri çalıştırılacak dosyanın path ifadeleri, ikinci parametreleri
ilk komut satırı argümanıdır. Görüldüğü gibi fonksiyonlara en azından bir komut satırı
argümanı girmek gerekir. Bu argümanında programın ismini belirten bir ifade olması
önerilmektedir. Bundan sonra programcı isteği kadar char* türünden komut satırı
parametresini girebilir. Listenin sonunun NULL göstericiyle bitmesi gerekir. Fonksiyon
başarılıysa geri dönmez başarısızsa –1 değerine geri döner. Örneğin;

Sayfa 50 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

if(execlp(“den”, “den”, “ali”, ”veli”, (char *) NULL) < 0){


perror(“execl”);
exit(EXIT_FAILURE);
}

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);

exec İşlemi ve Dosya Betimleyici Tablosu


Normal olarak dosya betimleyici tablosu fork işlemiyle alt processe kopyalanmaktadır. Peki,
exec işlemi ile bu dosya tablosu geçerliliğini korumakta mıdır? İşte iki durum sözkonusu
olabilir.
1. Default olarak exec işlemi sırasında da dosya tablosunun korunması söz konusudur.
Yani exec işleminde önce açılmış olan dosyalar açık olmaya devam etmektedir.
2. Exec işlemi ile belirli dosyaların otomatik kapatılması söz konusu olabilir. Modern
Unix/Linux sistemleri her dosya için bir tane bu durumu belirleyen bayrak tutmaktadır. Bu
bayrağa “close on exec” bayrağı denilmektedir. Bir dosyanın bu bayrağını set etmek için fcntl
fonksiyonu kullanılmaktadır.
Peki, bu bayrak dosya temelinde olduğuna göre nerede tutulmaktadır? Bu bayrak aslında
dosya betimleyici tablosunda tutulmaktadır. Aslında dosya betimleyici tablosu aşağıdaki
gibidir.(şekil)

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;

stdin ve stdout Dosyalarının Exec İşlemi İçin Yönlendirilmesi


Bilindiği gibi dos ve Unix sistemlerinin shell programları > ve < işlemleriyle çalıştırdıkları
programların stdout ve /veye stdin betimleyicilerini (yani 0 ve 1 numaralı betimleyicilerini)
dosyaya yönlendirebilmektedir. Aslında bu yönlendirme işlemi oldukça basittir. Bunun için
fork işleminden sonra ama exec işleminden önce dup2 fonksiyonu ile 0 yada 1 numaralı
betimleyiciler açılmış bir dosyaya yönlendirilir, sonra exec uygulanır.
if((pid = fork()) < 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

int fcntl(int fd, int cmd, …);


Fonksiyonun birinci parametresi dosya betimleyicisidir. Ikinci parametre ne yapılmak
istenildiğini anlatan bir sembolik sabittir. Bu parametre çok çeşitli değerler alabilmektedir.
Fonksiyonun ikinci parametresinde belirtilen komutun ne olduğuna göre üçüncü parametre
daha girilebilir. Üçüncü parametrenin anlamı ikinci parametrede girilen komuta göre
değişebilmektedir. Fonksiyonun geri dönüş değeride içinci parametredeki komuta göre
değişebilmektedir. Fakat ne olursa olsun başarısızlık durumunda fonksiyon –1’ e geri
dönmektedir.
Dosyayı open fonksiyonu ile açarken kullandığımız açış modunu F_GETFL ile alıp F_SETFL
ile değiştirebiliriz. Örneğin;
int mode;
mode = fcntl(fd, F_GETFL);

fcntl(fd,F_SETFL, mode|O_APPEND );
Açılmış olan betimleyicinin “close_on_exec” bayrağının ne durumda olduğunu anlamak için
fonksiyonun ikinci parametresine F_GETFD girilir ve fonksiyonun geri dönüş değerinde
FD_CLOEXEC biti varmı diye bakılır. Örneğin;
if(fcntl(fd, F_GETFD) & FD_CLOEXEC){

}
else{

}
F_GETFD dosya betimliyicisine ilişkin başka özelliklebit düzeyinde kopyalayarak vermek
amacıyla tasarlanmıştır. Bizde “close_on_exec” bitini temsil eden FD_CLOEXEC bitini
kontrol etmeliyiz. Benzer biçimde dosyanın “close_on_exec” bayrağını set etmak için
işlemler şöyle yapılabilir.
flag =fcntl(fd, F_GETFD);

fcntl(fd, F_SETFD, flag | FD_CLOEXEC);
Her ne kadar bugünkü sistemlerde F_GETFD ile alınabilecek yada F_SETFD ile set
edilebilecek başka bir bayrak yoksa da ileriye doğru uyumu sağlamak için FD_CLOEXEC
biti tanımlanmıştır. Yukarıdaki örnekte bitlere dokunulmadan “close_on_exec” bayrağı set
edilmiştir.

Processler Arası Haberleşmenin Temelleri


Modern çok işlemli işletim sistemlerinin çoğunda processlerin bellek alanları birbirinden izole
edilmiştir. Bu durumda bir processin başka bir processe bilgi göndermesi karmaşık bir süreç
oluşturmaktadır. Bir processin başka bir processe bilgi göndermesi durumuna genel olarak
processler arası haberleşme (Inter Process Communication) denilmektedir. Processler arası
haberleşme yöntemleri her işletim sisteminde farklı olabilmektedir, yani bu konu işletim
sistemine özgü bir konudur.

Sayfa 55 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Processler arası haberleşme yöntemleri aynı makinadaki processlerin haberleşmesi ve network


altında herhangi iki makinadaki processlerin haberleşmesi biçiminde ikiye ayrılmaktadır.
Şüphesiz network altında herhangi iki makinadaki processlerin haberleşmesi işlevsel olarak
aynı makinadaki iki processin haberleşmesi durumunu kapsamaktadır. Genel olarak network
altındaki haberleşmenin zaman ve yazılım maaliyeti (yani kodlama ve hızlı çalışma durumu)
diğerinden yüksektir.
Her ne kadar processler arası haberleşme yöntemleri işletim sisteminden işletim sistemine
farklılık gösteriyorsa da bazı yöntemler biçim olarak neredeyse birbirinin aynısıdır. Örneğin,
paylaşılmış bellek alanlarının kullanımı (pipe) pekçok işletim sisteminde birbirine benzer
biçimde uygulanmaktadır.
Bir processin bir bilgiyi başka bir processe iletebilmesi processler arası haberleşme
problemini tam olarak çözmez. Çünkü bilgiyi ileten ve alan processlerin bir eş zamanlılık
oluşturması gerekir. Yani alan tarafın bilgi gönderilene kadar beklemesi ve bilgi gönderilince
bunu alması gerekir. Bu nedenle processler arası haberleşme processlerin senkronize edilmesi
konusuyla iç içedir. Bazı processler arası haberleşme yöntemleri kendi içinde bir
senkronizasyon içermektedir.
Processler arası haberleşme özellikle son 10 yıldır popüler olan client-server yazılımlarının
oluşturulması için kullanılmaktadır.

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

int pipe(int pipedes[2]);

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

read Fonksiyonunun Pipe Okumasındaki Davranışı


pipe tan blokeli yada blokesiz okuma yapılabilir. Ancak burada default olarak blokeli okuma
üzerinde durulacaktır. Blokeli modda read fonksiyonu ile n byte okumak isteyelim. Eğer pipe
üzerinde yazma yapabilecek en az bir betimleyici söz konusuysa (yani karşı taraf pipe
kapatmadıysa) en az 1 byte okunana kadar read blokede kalır. Eğer pipe a yazma yapabilecek
hiçbir betimleyici kalmadıysa (yani karşı taraf pipe kapattıysa) read fonksiyonu eğer
okuyabileceğinin hepsi pipe ta ise hepsini okur, eğer pipe ta talep edilenden daha az bilgi
kaldıysa okuyabildiği byte sayısıyla geri döner. Yani özetle, talep edilenden daha az bilginin
oknabilmesi ancak bir giriş/çıkış problemiyle (ki bu durumda read –1 le geri döner) yada karşı
tarafın pipe kapatmasıyla mümkün olabilir. Bu durumda örneğin sürekli pipe tan BUFSIZE
kadar blok okuyup bunu bir işleme sokmak isteyelim bunu yapan kalıp şöyle olabilir.
char buf[BUFSIZE];
int n;
while((n = read(pipedes, buf, BUFSIZE)) > 0){
process(buf, n);
}
if(n == -1){
perror(“read”);
exit(EXIT_FAILURE);
}
Görüldüğü gibi döngüden ya bir giriş çıkış hatası olduğunda yada pipe kapatıldığında
çıkabiliriz.
Pipe ın kapatılmış gibi etki göstermesi için o pipe a yazma potansiyelinde olan hiçbir
betimleyicinin kalmaması gerekir.
Normal bir pipe haberleşmesinde pipe yazan tarafın pipe ı kapatması gerekir. Yani yazan taraf
pipe ı kapatacak ve okuyan tarafta döngüden çıkacak.

write fonksiyonun Pipe Yazmasındaki Davranışı


write() fonksiyonu ile pipe’a n byte yazma isteyelim. Eğer pipe doluysa, write() fonksiyonu
pipe’da yeterli yer açılana kadar process’i bloke edecektir. write fonksiyonu ile pipe’a n
byte’ın yazılamamasıının iki nedeni olabilir:
1) Giriş çıkış hatası ya da yanlış betimleyicinin kullanılması gibi temel bir hata
2) pipe’tan okuma yapma potansiyelinde hiçbir betimleyicinin kalmaması durumu. Yani
okuyan tarafın pipe’ı kapatması durumu. Bu durum fatal bir error kabul edilir ve yazma yapan
process’e SIGPIPE sinyali gönderilir. SIGPIPE sinyali, default olarak process’i sonlandırır.
Pipe’ların byte uzunluğu sistemden sisteme değişmektedir. O sistemdeki pipe uzunluğu
<limits.h> dosyası içerisinde PIPE_BUF sembolik sabitiyle define edilmek zorundadır.
piep uzunluğundan daha fazla bilginin okunmaya veya yazılmaya çalışılması bir
senkonizasyon
problemi yaratabilmektedir. Örneğin, write fonksiyonuyla pipe uzunluğunun iki katı kadar
bilgiyi yazmak isteyelim ve iki farklı process PIPE_BUF uzunuluğunun iki katı kadar bilgiyi
okumak istesin. read fonksiyonu en fazla PIPE_BUF kadar bilgiyi tek hamlede
okuyabilmektedir. Tabi böylesi bir problem, çok kişinin aynı pipe’tan okumaya çalışması

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.

Pipe Haberleşmesinin Gerçekleştirilmesi


Tipik bir isimsiz pipe haberleşmesinde, üst process fork isşleminden önce pipe’ı
yaratır ve iki betimleyiciyi elde eder. fork() işleminden sonra bu betimleyiciler alt process’e
de kopyalanacaktır. Yani fork() işleminden sonra, pipe’a yazma yapan ve pipe’dan okuma
yapan ikişer betimleyici bulunacaktır. fork() işleminden sonra bir process pipe’a yazar, bir
process pipe’dan okur ve haberleşme böyle gerçekleşir. Fakat okuyan tarafın, yazan tarafın
betimleyicisini kapattığını anlayabilmesi için kendi yazma betimleyicisini kapatması gerekir.
Aynı durum yazan için hayati bir önem taşımamakla birlikte yazan tarafın da okuma
betimleyicisini kapatması gerekir. örneğin, üst process’in pipe’a yazdığı, alt process’in de
okuduğu tipik bir kalıp şöyledir:

pid_t pid;
int pipedes[2];

if (pipe (pipedes) < 0) {


perrro(“pipe”);
exit(EXIT_FAILURE);
}

if ((pid == fork()) < 0) {


perror(“fork”);
exit(EXIT_FAILURE);
}

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>

#define BUFSIZE 100

void parent(int pipedes);


void child(int pipedes);

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);
}
}

void parent(int pipedes)


{
char buf[BUFSIZE];

for (;;) {
printf("Enter text: ");
fgets(buf, BUFSIZE, stdin);
if (!strcmp(buf, "exit\n"))
break;
write(pipedes, buf, strlen(buf));
sleep(1);
}
close(pipedes);
}

void child(int 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;

while ((n = read(pipedes, &ch, 1)) > 0) {


putchar(ch);
}
if (n < 0) {
perror("read");
exit(EXIT_FAILURE);
}
close(pipedes);
}

Shell Komutu Olarak Pipe İşlemi


a ve b birer program olmak üzere, shell üzerinden a | b işlemi yapılmış olsun. Bu işlem ile bir
pipe yaratılır, a programının stdout dosyası pipe’a, b programının da stdin dosyası pipe’a
yönlendirilir. Yani, a’nın stdout dosyasına yazdıkları pipe’a yazılacaktır. b’nin de stdin
dosyasından okudukları pipe’dan okunacaktır. Böylece a’nın stdout dosyasına yazdıklarını b,
stdin dosyasından okumuş olur.
Örneğin:
ls | sort
sort programı bir dosyayı satır satır sıraya sokup göstermektedir. Fakat sort programına bir
komut satırı argümanı verilmezse, sort dosya yerine stdin dosyasını kullanır. ls programı da,
dosyaları stdout dosyasına yazdığına göre ls programının çıktısı sort programının girdisi
olacaktır. sort, ls programının çıktısını sıraya sokmuş olacaktır. UNIX/LINUX sistemlerinde,
bir programa komut satırı argümanı verilmediğinde dosya olarak stdin dosyasını
kullanmaktadır. Bu durum, pipe kullanımını mümkün hale getirmektedir.

/*lcount.c*/
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>

int main(int argc, char *argv[])


{
FILE *f;
int ch, count = 0;

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;

while ((ch = fgetc(f)) != EOF)


if (ch == '\n')
++count;
printf("%d\n", count);

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.

İsimli pipeların Açılması İşlemleri


İsimli pipelarla haberleşmek için iki processin de pipe’ ı open fonksiyonuyla açması gerekir.
open fonksiyonunda açış modu olarak O_RDONLY, O_RDWR, O_WRONLY,
O_NONBLOCK. O_NONBLOCK diğer açım moduyla birleştirilerek kullanılır ve isimli
pipe’ ın blokesiz modda açılmasını sağlar. Blokesiz pipe işlemleri ileride ele alınacaktır.
Blokeli modda open fonksiyonu eğer okuma O_RDONLY modunda açılma yapılmışsa
herhangi bir process pipe’ ı O_WRONLY yada O_RDWR modunda açana kadar processi
bloke eder. Benzer biçimde process pipe’ ı O_WRONLY modunda açmışsa herhangi bir
process pipe’ ı O_RDONLY yada O_RDWR modunda açana kadar open içerisinde bloke
oluşur.

İsimli pipelarda Okuma ve Yazma İşlemleri


İsimli pipelarda okuma ve yazma işlemleri read ve write fonksiyonlarıyla yapılmaktadır.
Haberleşme işlemi tamamen isimsiz pipelarda olduğu gibidir. Yazan tarafın öncelikle pipe’ ı
kapatması gerekir, yazan taraf pipe’ ı kapattığında read fonksiyonu pipe ta kalanı okur, pipe
ta hiç bilgi yoksa 0 değeriyle geri döner. read fonksiyonu yazan tarafların hepsi pipe’ ı
kapatana kadar yada pipe boşsa bloke olur. Benzer biçimde write fonksiyonu da pipe
dolduğunda bloke olmaktadır. Tıpkı isimsiz pipelarda olduğu gibi okuyan processler pipe’ ı
kapatmışsa yazan process kapatılmış pipe’ a yazma yapmaya çalıştığında SIGPIPE sinyaliyle
yazan process sonlandırılır. Pipelarda read fonksiyonu ile n byte okunmak istense read
fonksiyonunun blokesinin çözülmesi için pipe en az 1 byte’ın yazılması gerekir. Örneğin karşı
taraf pioe 10 byte yazmış olsun, read fonksiyonu bu 10 byte’ ı okuyacak ve 10 değeriyle geri
dönecektir.

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;
}

İsimli pipeların Kapatılması


İsimli pipelar yine close fonksiyonuyla normal dosyalarda olduğu gibi kapatılabilir.

İsimli pipe Kullanımında Dikkat Edilecek Durumlar


Şüphesiz en normal durum bir processin pipe yazma yapması ve başka bir processin okuması
durumudur. Ancak bazı uygulamalarda birden fazla processin pipe’ a yazma yaptığı fakat tek
bir processin okuma yapmasıyla karşılaşılmaktadır. Birden fazla processin aynı pipe’ a

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.

İsimli pipelarla Client-Server Programların Yazılması


Bilindiği gibi client-server programların yazılabilmesi için işletim sisteminin processler arası
haberleşme yöntemleri kullanılır. Aynı makinadaki processler arasında client-server
uygulaması isimli pipe kullanılarak yapılabilir.
Client-server uygulamalarda tüm clientlar server ile haberleşmek için aynı isimli pipe’ ı
kullanabilirler. Fakat server programın sonucu client programa iletmesi için her client
program için ayrı bir isimli pipe kullanması gerekir.
İsimli pipelarala client-server uygulamalar yazılırken pipelar server tarafından yaratılabilir.
İşin başında clientların server ile haberleşmede kullanacakları pipe yaratılmış olmalıdır.
Clientlara ilişkin pipelar client program server programa bağlandığında server tarafından
yaratılabilir. Benzer biçimde client program bağlantıyı kestiğinde pipe server tarafından
kapatılır. Client-server programlar mesaj tabanlı oluşturulur, yani client programlar serverdan
istediklerini ve gerekli olan verileri bir mesaj yoluyla server programa iletirler. Benzer
biçimde server programda sonuçları bir mesaj biçiminde client programlara iletir. Mesaj C de
bir yapıyla ifade edebileceğimiz bilgi topluluğudur, profesyonel uygulamalarda mesajların
uzunlukları değişebilir.

client
server
client
client

Örneğin tipik bir mesaj yapısı şöyle olabilir:


typedef struct tagHEADER{
int cmd_type;
int size;
}HEADER;
typedef struct tagMSG{
HEADER header;
char data[1];
}MSG;
Burada mesaj ne olursa olsun, değişmeyen kısım HEADER kısmıdır. Burada data elemanı
sadece yer belirlemek için kullanılmıştır. Yani HEADER kısmından sonraki değişken
uzunlukta bilginin başlangıç adresini belirtmektedir. Bu durumda mesajı okuyan kişi mesajı
iki ayrı read işlemi ile okur. Birincide HEADER okunmalı, ikincide size kadar bilgi

Sayfa 64 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

okunmalıdır. Mesajın hangi clienttan geldiğininde server tarafından tespit edilmesi


gerekebilir. Bunun için HEADER kısmına client’ ı belirten ek bir ID yerleştirilebilir yada bu
bilgi data kısmına alınabilir.
/* kod eksik*/
chatserver.h

#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*/

typedef int BOOL

/*Message Types*/

enum {CONNECT_MSG, SEND_MSG, DISCONNECT_MSG};


enum {TALK, LISTEN};

/*Structure Declarations*/

typedef struct tagHEADER{


int type;
int clid;
int size;
}HEADER;

typedef struct tagMSG{


HEADER header;
Char data[1];
}MSG, *PMSG;

typdef struct tagCLIENT_INFO{


char nick[NICK_SIZE];
int cpipe;
int empty_flag;
}CLIENT_INFO;

/* Function prototypes*/

Sayfa 65 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

void check_args(int argc, char *argv[]);


void init_prog(void);
void listen_clients(void);
void connect_client(HEADER *pheader);
void disconnect_client(HEADER *pheader);
void get_client_msg(HEADER *pheader , char *ptext);
void display_message(const char *pnick, const char *ptext);
int get_free_slot(void);
void end_prog(void);

#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*/

typedef int BOOL

/*Message Types*/

enum {CONNECT_MSG, SEND_MSG, DISCONNECT_MSG};


enum {TALK, LISTEN};

/*Structure Declarations*/

typedef struct tagHEADER{


int type;
int clid;
int size;
}HEADER;

typedef struct tagMSG{


HEADER header;
Char data[1];
}MSG, *PMSG;

/* Function prototypes*/

void check_args(int argc, char *argv[]);


void init_prog(void);

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

sprintf(clpipe_name, “client%02d”, pheader->clid);


if(remove(clpipe_name) < 0){
fprintf(stderr, “warning client pipe cannot remove\n”);
return;
}
g_clinfo[pheader->clid].empty_flag = FALSE;
}
void get_client_msg(HEADER *pheader, char *ptext)
{

}
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;

for(i = 0; i < 5; ++i){


if(access(g_nick, F_OK) == 0){
if((cpipe = open(g_nick, O_RDONLY)) < 0){
perror(“open”);
exit(EXIT_FAILURE);
}
break;
}
sleep(1);
}
if(i == 5){
fprintf(stderr, “fatal error: server cannot ”);
exit(EXIT_FAILURE);

}
}
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.

Yüksek Seviyeli İsimsiz Pipe İşlemleri


İsimsiz pipelar üzerinde işlem yapmayı kolaylaştıran ayrı bir mekanizma da mevcuttur.
Yüksek seviyeli pipe işlemleri için öncelikle popen fonksiyonuyla pipe açılır.
FILE *popen(const char *command, const char *type);

Sayfa 71 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Fonksiyonun birinci parametresi çalıştırılacak shell komutudur. İkinci parametre açış


modudur, bu mod “r” yada “w” olabilir. “r” programcının pipe’ ı okuyacağı, “w” pipe’ a
yazacağı anlamına gelir. Eğer “r” modu kullanılmışsa popen şu işlemleri yapar;
1. pipe‘ ı pipe fonksiyonuyla yaratır.
2. fork işlemi yaparak alt process’ i yaratır ve alt process’ in stdout betimleyicisini
pipe’ a yönlendirir.
3. sh –c ile belirtilen komutu çalıştırır. Böylece çalıştırılan komutun stdout dosyasına
yazacağı şeyler pipe’ a yazılmış olur.
4. Pipetan okuma yapmak için kullanılan betimleyiciyi FILE * ile ilişkilendirir ve onun
adresiyle geri döner.
Görüldüğü gibi “r” modunda açıldığında fonksiyonun geri verdiği dosyadan okuma
yaptığımızda komutun pipe’ a yazdığı verileri pipetan okumuş oluruz.
popen fonksiyonu “w” ile kullanılırsa sırasıyla şunlar yapılır.
1. Fonksiyon pipe’ ı pipe fonksiyonuyla yaratır.
2. Fork işlemini yapar ve alt processin stdin dosyasını pipe’ a yönlendirir.
3. Shell programını sh –c ile belirtilen komutu vererek çalıştırır.
4. Pipe a yazmakta kullanılan dosya bilgi göstericisini FILE nesnesiyle ilişkilendirerek
onun adresiyle geri döner.
Görüldüğü gibi biz verilen dosya bilgi göstericisini kullanarak yazma yapınca çalıştırılan
komut bunu stdin dosyasından okur.

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.

Belli Başlı Sinyaller


Bu bölümde önemli sinyaller neler olduğu bunların nasıl oluştuğu ve default davranışlarının
ne olduğu ele alınacaktır.
SIGKILL: Bu sinyal bir processi sonlandırmak amacıyla kullanılır. Bu sinyal için sinyal
fonksiyonu yazılamaz ve gözardı etme durumuna sokulamaz. Processe SIGKILL sinyali
gönderildiğinde process sonlandırılır. Bu sinyal her zaman default durumda bulunur. Bir
processi garantili bir biçimde sonlandırmak için ona SIGKILL sinyalini göndermek gerekir.
Bu sinyal yazılım yoluyla yani kill sistem fonksiyonula oluşturulabilir.
SIGTERM: Bu sinyalde kill sistem fonksiyonuyla oluşturulur ve SIGKILL de olduğu gibi
processi sonlandırmkata kullanılır. Fakat bu sinyal için sinyal fonksiyonu yazılabilir ve bu
sinyal göz ardı etme durumuna sokulabilir. Default olarak bu sinyalde processi
sonlandırmaktadır.
SIGINT: Bu sinyal klavye yoluyla klavyeden Ctrl+C (yada bazı sistemlerde delete) tuşlarına
basıldığında gönderilir. Bu sinyal için sinyal fonksiyonu yazılabilir yada sinyal göz ardı
edilebilir. Bu sinyalin default davranışı processin sonlandırılması biçimindedir. Bu durumda
Unix/Linux sistemlerinde Ctrl+C tuşuna basılarak bir processin sonlandırılması aslında o
processe SIGINT sinyalinin gönderilmesiyle olur. Anlatımdan da anlaşıldığı gibi Ctrl+C
tuşuyla programın sonlandırılmasının bir garantisi yoktur.
SIGPIPE: pipe işlemlerinde önce yazan tarafın pipe’ ı kapatması normal ve istenen bir
durumdur. Eğer okuyan taraf pipe’ ı kapatırsa pipe’ a yazma yapıldığı zaman bu sinyal oluşur.
Bu sinyal için bir sinyal fonksiyonu yazılabilir yada sinyal göz ardı edilebilir. Bu sinyalin
default durumu processin sonladırılmasıdır. Bu durumda okuyan tarafın pipe’ ı kapatması
durumunda yazma yapan processin pipe’ ı kapatmasına yol açacaktır.
SIGTSTP: Bu sinyal klavyeden Ctrl+Z tuşlarına basıldığında processe gönderilir. Bu sinyal
için sinyal fonksiyonu yazılabilir yada sinyal görmemezlikten gelinebilir. Default olarak
çalışmkata olan processin çalışmasına ara verilir. Fakat çalışmasına ara verilmiş olan process
gaha sonra SIGCONT sinyali gönderilerek çalışmasına devam ettirilebilir.
SIGCONT: Bu sinyal çalışmasına ara verilmiş olan processlerin çalışmasına devam etmesini
sağlamak için kullanılır. default davranış olarak process zaten çalışıyorsa sinyal göz ardı
edilir, processin çalışmasına ara verilmişse çalışmasına devam ettirilir. Bu durumda Ctrl+Z
tuşuyla çalışmasına ara verdiğimiz processlere SIGCONT sinyali göndererek onların
çalışmasına devam etmesini sağlayabiliriz. SIGCONT sinyali içinde sinyal fonksiyonu
yazılabilir yada bu sinyal göz ardı edilebilir.
SIGCHLD: Bu sinyal bir alt processin çalışması sonlandığında üst processe gönderilen bir
sinyaldir. Bu sinyal tipik olarak programcı tarafından zombie process oluşmasını otomatik
engellemek amacıyla yazılabilir. Bu sinyal için sinyal fonksiyonu yazılabilir yada sinyal
görmemezliktan gelinebilir. Sinyalin default davranışı sinyalin görmemezliktan gelinmesidir.

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

typedef void (*sighandler_t) (int)


sighandler_t signal (int signum, sighandler_t handler);
signal fonksiyonunun ikinci parametresi geri dönüş değeri void parametresi int olan bir
fonksiyondur. Fonksiyon birinci parametresinde belirtilen sinyal oluştuğunda ikinci
parametresinde belirtilen fonksiyonun çağırılmasını sağlar. Fonksiyonun geri dönüş değeri
eski set edilmiş olan fonksiyonun adresidir. Bu fonksiyon aynı zamanda birinci parametreyle
belirtilen sinyalin default duruma getirilmesi için yada göz ardı edilmesi için de
kullanılmaktadır. ikinci parametre olarak bir fonksiyonun adresi geçilmezde SIG_IGN özel
değeri geçilirse sinyal göz ardı edilir, SIG_DFL özel değeri geçilirse sinyal default duruma
geçirilir. Fonksiyonun geri dönüş değeri başarısızlık durumunda SIG_ERR özel değeridir.
Örneğin,
void Handler(int sno)
{
printf(“SIGINT handler called....\n”);
}
int main(void)
{
if (signal(SIGINT, Handler) == SIG_ERR){
perror(“signal”);
exit(EXIT_FAILURE);
}
for(;;){
getchar();
}
return 0;
}
Sinyal çağırıldığında çalıştırılacak fonksiyonun parametresine oluşan sinyalin numarası
geçirilir. Böylece programcı aynı fonksiyonu farklı sinyaller için set edebilir ve fonksiyon
içerisinde hangi sinyalin oluştuğunu parametreye bakarak öğrenebilir.

Sinyallerin Oluşma ve Ele Alınma Süreci


Bir sinyal oluştuğunda işletim sistemi devreye girerek sinyalin oluşumunu sinyalin
gönderildiği processin process tablosunda not alır. POSIX standartlarında sinyalin
oluşmasıyla sinyal fonksiyonunun çağırılması arasındaki süre hakkında bir belirlemede
bulunulmamıştır. Fakat işletim sistemi tasarımcıları oluşan sinyali bir an önce işleme koymak
isterler. Bu nedenle POSIX sistemlerinin çoğunda sinyal mümkün olduğu kadar çabuk ve hızlı
bir biçimde işleme konur. Sinyalin işleme sokulması (signal delivery) sinyal fonksiyonun
çağırılması yada belirtilen default işlemin yapılmasına başlanması anlamına gelir. sinyalin
oluşumuyla sinyalin işlem sokulması arasında geçen sürede “sinyal askıda” (signal pending)
denir. Bir sinyal oluştuğunda sinyal bir an önce işleme sokulur ve akış sinyal fonksiyonuna
geçirilir. Sinyal fonksiyonu çalıştırıldıktan sonra akış yine kalınan yerden devam edecektir.
Eskiden Unix sistemlerinde sinyalin bloke edilmesi (signal blocking) söz konusu değildi.
Sonra bu özellik pekçok Unix sistemine eklenmiştir. Bir sinyalin bloke edilmesi onun bloke
çözülene kadar askıda bekletilmesi demektir. Bloke edilmiş bir sinyalin blokesi çözüldüğünde
sinyal işleme sokulur. Yani sinyalin bloke edilmesi programcıya sinyalin programın bir
bölümünde işleme sokulmamasını daha sonra işleme sokulmasını sağlama olanağını
vermektedir.

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.

Fonksiyonun parametresi kaç saniye sonra SIGALRM sinyalinin oluşturulacağını belirtir.


Eğer daha önce set edilmiş bir zaman varsa bu zaman reset edilir ve yeni zaman set edilir.
Fonksiyonun geri dönüş değeri eski set etme işlemindeki kaln saniye sayısıdır. Örneğin:
alarm(10);
....
alarm(50);
Burada ikinci alarm fonksiyonunun 3 saniye sonra çağırıldığını varsayalım. Yeni alarm değeri
50 olacaktır ve ikinci fonksiyon 7 değeriyle geri dönecektir. Eğer daha önce set edilmiş bir
alarm yoksa fonksiyon 0 değeriyle geri döner. aşağıda her bir saniye de bir SIGALRM sinyali
oluşturan ve bu oluşum sırasında pause fonksiyonuyla çizelge dışı bekleyen basit bir örnek
görülmektedir.
void Handler(int sno)
{
printf(“alarm...\n”);
alarm(1);
}
int main(void)
{
if(signal(SIGALRM, Handler) == SIG_ERR) {
perror(“signal”);
exit(EXIT_FAILURE);
}
alarm(1);
for(;;)
pause();
...
return 0;
}

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.

signal Fonksiyonunun Kusurları


signal fonksiyonunun en önemli davranışı sinyal oluşupta sinyal fonksiyonu çağırıldığında o
sinyalin yeniden default duruma çakilmesidir. Böylece sinyal fonksiyonu çalışırken aynı
sinyalin oluşması durumunda process sonlandırılmaktadır. Aslında signal fonksiyonunun
orijinal biçiminde defaulta çekme işlemi yalnızca signal fonksiyonunun çalıştığı süre

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.

void Handler(int sno)


{
if(signal(SIGINT, Handler) == SIG_ERR) {
perror(“signal”);
exit(EXIT_FAILURE);
}
/*...*/
}
int main(void)
{
if(signal(SIGINT, Handler) == SIG_ERR) {
perror(“signal”);
exit(EXIT_FAILURE);
}
for(;;)
pause();
printf(“main ends...\n”);
return 0;
}

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

BSD sistemleri signal fonksiyonunun çalışmasını düzeltmişlerdir. Bu sistemlerde sinyal


defaulta çekilmez, yalnızca sinyal fonksiyonu çalıştığı süre içinde bloke edilir. Sinyal
fonksiyonu bittikten sonra bloke otomatik olarak çözülür ve bekletilmiş olan sinyal işleme
sokulur. Unix sistemlerindeki signal fonksiyonunun davranışı BSD sistemlerine
benzetilmiştir.
signal eski tip sinyal fonksiyonlarının tasarımında ikinci bir kusurda signal fonksiyonuyla bir
sinyal set edilir, pause fonksiyonuyla bir sinyalin oluşumunun beklenmesi gibi çok yaygın
kullanılan bir kalıp ile ilgilidir.
signal(...);
pause();
Burada signal fonksiyonu bittiğinde fakat henüz akışın pause fonksiyonuna girmediği
durumda sinyal gelirse maalesef pause fonksiyonu umulmadık bir biçimde beklemeye yol
açar. Bu problemin aşağıdaki gibi çözüleceği sanılabilir.
g_flag=0;
signal(....);
while(g_flag==0)
pause();
void handler(int sno){
g_flag=1;
...
}
Fakat burada yine bir atomiklik problemi vardır. Eğer “g_flag==0” testi ile programın akışı
pause fonksiyonun çağırıldığı noktaya gelirken bu arada sinyal oluşursa yine aynı kilitlenme
durumuyla karşılaşılır. İşte yukarıda açıklanan sinyal set edilip pause ile bekleme işleminin
atomik bir biçimde yapan yeni bir fonksiyona gereksinim vardır. POSIX standartlarına böyle
bir fonksiyon eklenmiştir.
Kalsik Unix sistemlerinde sinyallerin bloke edilmesine yönelik bir fonksiyon yoktu. BSD
sistemlerine sigblock gibi bu işlemi yapan bir fonksiyon eklenmiştir. POSIX standartlarında
bloke işlemi için daha ayrıntılı olacak biçimde sigprocmask fonksiyonu düşünülmüştür.

Sinyallerin Bloke Edilmesi ve sigprocmask Fonksiyonu


Bir sinyalin bloke edilmesi onun blokesi çözülene kadar askıda kalması anlamına gelir.
Sinyalin askıda kaldığı süre içerisinde aynı sinyalden yine oluştuğunda bunların biriktirilip
biriktirilmeyeceği POSIX standartlarında işletim sistemini yazanlara bırakılmıştır. Sistemlerin
çoğu böyle bir biriktime işlemi yapmamaktadır. (Örneğin, SIGINT sinyalini bloke etmiş
olalım sonra 5 kez Ctrl+C tuşlarına basmış olalım. Blokeyi çözdüğümüzde biriktirme durumu
varsa 5 kez yoksa yalnızca bir kez SIGINT sinyali oluşacaktır.)
Unix sistemlerinde işletim sistemi her process için process tablosunda tüm sinyallerin bloke
durumda olup olmadığını tutan bir veri yapısı bulundurmaktadır. (process signal mask)
sigprocmask fonksiyonuyla şunlar yapılabilmektedir.
• Processin bloke edilmiş sinyal listesine yeni bloke edilmesi istenen sinyaller eklenebilir.
• Processin bloke edilmiş sinyal listesinden bloke edilmiş bir sinyal çıkartılabilir.
• Processin bloke edilmiş sinyal kümesi tamamen değiştirilebilir.
int sigprocmask(int how, const sigset_t *set, sigset_t *oldset);

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.

Sinyallerin Özelliklerinin Alt Processlere Aktarılması


Bir alt process fork ile yaratıldığında processin tüm sinyal özellikleri alt processe kopyalanrı.
Yani örneğin, bir sinyal için bir sinyal fonksiyonu set edilmişse yada bir sinyal için
görmemezlikten gelme durumu oluşturulmuşsa bu belirlemeler alt processte de devam eder.
exec işlemi ile sinyal fonksiyonu set edilmiş olan sinyaller otomatik olarak default duruma
çekilir. Örneğin biz SIGINT sinyali için bir sinyal fonksiyonu set etmiş olalım. exec
uyguladığımızda artık set etmiş olduğumuz fonksiyon çalışan programda mevcut
olmayacaktır. İşte bu sinyal default duruma çekilecektir. Bunun dışında exec işlemiyle diğer
sinyal özelliklerinde bir değişiklik olmaz. Yani, örneğin bir sinyal görmemezlikten gelinme
durumuna sokulmuşsa exec işleminden sonra bu durum devam ettirilir.

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.

Çok Girişli Fonksiyonlar


Bazı standart C fonksiyonları çok girişli değildir. Çok girişlilik (re-entrancy) bir fonksiyonun
içerisinden çıkmadan bir biçimde yine aynı fonksiyonun çağırılması durumunda problem
çıkmaması demektir. Bir fonksiyonun içinden çıkmadan yine aynı fonksiyonun çağırılması iki
durumda söz konusu olabilir.
1. O fonksiyonun içerisinde bir sinyal oluşması ve akışın sinyal fonksiyonuna geçmesi.
Sinyal fonksiyonu içerisinde yine o fonksiyonun çağırılması.
2. Çok threadli çalışma da bir fonksiyonun içerisindeyken threadler arası geçiş
oluşarak diğer bir threade geçilmesi ve orada o fonksiyonun çağırılması durumu.
Çok girişli fonksiyonlara aynı zamanda thread güvenliği olan fonksiyon (thread safe) da
denilmektedir. Fonksiyonların çok girişli olmasını bozabilecek en önemli nedenler şunlardır:
1. Fonksiyonun global yada static yerek değişkenleri kullanması.
2. Fonksiyonların senkronize etmeden kaynaklara ulaşması.
POSIX standartlarında çok girişli fonksiyonların neler olduğu açıkça belirtilmiştir. Fakat çok
girşli fonksiyonlar sayıca fazla olduğuna göre çok girişli olmayan fonksiyonların neler
olduğunu belirtmek daha kolaydır. localtime, rand, srand, strdup, malloc, free gibi
fonksiyonlar çok girişli değildir. programcı sinyal fonksiyonu içerisinde kullanacağı
fonksiyon içerisinde ne yapılacağını araştırmalıdır.

Yavaş Sistem Fonksiyonları ve Sinyaller


Çağırdığımız bir sistem fonksiyonu içerisinde sinyal oluşursa ne olur? Örneğin, read
fonksiyonunu çağırmış olalım ve bu fonksiyon içerisindeyken processe bir sinyal gelmiş
olsun. Şimdi ne olacaktır? Sistem fonksiyonunun çalışmasına ara verilmesi sinyal fonksiyonu
çalıştırıldıktan sonra yeniden dönülmesi çeşitli teknik sebeplerden dolayı mümkün
olmayabilir. Bu durumda işletim sistemi iki tercihte bulunabilir.
1. Sinyal bir an önce processe teslim edilir. Sinyal fonksiyonu çalıştırılır, fakat sinyal
fonksiyonunun çalışması bitince sistem fonksiyonuna geri dönülmez. Sistem fonksiyonu
başarısızlıkla geri döndürülür.
2. İşletim sistemi sistem fonksiyonu bitene kadar sinyali işleme sokmadan bekletebilir.
Yukarıdaki yöntemlerden hangisinin seçileceği işletim sistemi tasarımcısına bağlıdır. İşte
tasarımcılar bu bakımdan sistem fonksiyonlarını iki gruba yaırmaktadırlar.
1. Hızlı sistem fonksiyonları: Bu fonksiyonlar bloke olasılığı olmayan hızlı bir
biçimde çalışıp sonlanan fonksiyonlardır. İşletim sistemi, bu fonksiyonların çalışması
bitinceye kadar sinyali bekletir.
2. Yavaş sistem fonksiyonları: Bu sistem fonksiyonlar aygıtlarla ilişkili olan ve
blokeye yol açabilen fonksiyonlardır. İşte bu tür fonksiyonlar içinde sinyal oluştuğunda sinyal
fonksiyonu çağırılır. Fakat fonksiyona geri dönüş yapılmaz fonksiyon başarısızlıkla
sonlandırılır.
Görüldüğü gibi yavaş fonksiyon olarak bilinen bazı sistem fonksiyonlarının içerisindeysek
sinyal geldiğinde bu fonksiyonlar başarısız olabilir. Bu tür durumlarda sistem fonksiyonu
başarısız olur ve ERRNO değeri EINTR biçiminde set edilir. POSIX standartlarında hangi

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);
}

Fonksiyonlar Arası Dallanma İşlemleri


C’ de ve C++’ da go to deyimi ile aynı fonksiyon içerisinde bir noktaya dallanılabilir. Başka
bir fonksiyona dallanma yapabilmek için dallanılacak yere ilişkin CPU yazmaçlarının ve stack
bilgilerinin kayıt edilmesi gerekmektedir. Ayrıca bugünkü bilgisayar sistemlerinde daha önce
geçilmemiş olan bir noktaya sağlıklı bir dallanma yapılamamaktadır.
C’ de standart setjmp ve longjmp fonksiyonları ile fonksiyonlar arası daha önce geçilen bir
noktaya geri dönülebilmektedir. setjmp ve longjmp fonksiyonlarının prototipleri <setjmp.h>
içerisindedir.
int setjmp(jmp_buf env);
int longjmp(jmp_buf env, int value);
Fonksiyonların parametrelerinde jmp_buf türünden bir değişken bulunmaktadır. jmp_buf türü
derleyicinin istediği gibi seçeceği bir tür olmakla beraber şüphesiz en anlamlı durum bunun
bir yapı olmasıdır. Örneğin pekçok derleyicide jmp_buf aşağıdaki gibi typedef edilmiştir.
typedef struct{
...
} jmp_buf[1];
Örneğin bu durumda programcı
jmp_buf a;
işlemi ile aslında bir elemanlık bir yapı dizisi tanımlamıştır. a ise bu dizinin yani yapının
başlangıç adresidir.
Geriye dönülmek istenen nokta, o noktadan geçilirken setjmp fonksiyonuyla kaydedilmelidir.
setjmp işlemi yaparken jmp_buf türünden nesnenin global olması en mantıklı durumdur.
Çünkü bu nesne longjmp fonksiyonunda başka yerden kullanılacaktır. longjmp işlemi ile daha
önce geçilen noktada setjmp fonksiyonunun içerisine atlanmaktadır. Bu durumda setjmp
fonksiyonunun içerisinden iki nedenle çıkılıyor olabilir.
1. Doğal işleyiş içerisinde kayıt işlemi yapılırken, bu durumda setjmp 0 değeriyle geri
döner.
2. longjmp işlemi yapılmıştır, o nedenle akış setjmp fonksiyonundan çıkmıştır. Bu
durumda setjmp fonksiyonunun geri dönüş değeri longjmp fonksiyonunun ikinci
parametresindeki değer olur. O halde programcının longjmp fonksiyonunun ikinci
parametresini 0 dan farklı vermesi gerekmektedir.
jmp_buf g_pos;
...
if(setjmp(g_pos) != 0){
printf(“long jumped..\n”);
}
longjmp(g_pos, 1);
...
longjmp(g_pos, 2);
Burada ilk kez kayıt yapma amaçlı setjmp fonksiyonundan 0 ile sonraki longjmp işlemleri
dolayısıyla çıkıldığında sırasıyla 1 ve 2 değerleriyle dönülmektedir.

Fonksiyonlar Arası Dallanmaya Neden Gerek Duyulur?


Başka bir fonksiyona dallanma bazı durumlarda ve bazı tasarımlarda gerekebilmektedir.
Örneğin, bir hata oluştuğunda longjmp işlemiyle hatalar tek bir noktadan yönetilebilir. Zaten

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);
}

Sinyal Fonksiyonlarında longjump İşlemleri


Bilindiği gibi bir sinyal oluştuğunda sinyal fonksiyonunun çalışması bittikten sonra akış
kalınan yerden devam etmektedir. Fakat pekçok uygulamada sinyal fonksiyonları içerisinde

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.

Blokesiz Okuma/ Yazma İşlemleri


Unix/Linux sistemlerinde blokeli yada blokesiz modda yapılabilmektedir. Blokeli modda
okuma işleminde eğer okunacak hiçbir byte yoksa thread çizelge dışı bırakılarak bekletilir.
Okunacak en az bir byte varsa read fonksiyonu en az bir byte okur ve okuduğu byte la geri
döner. Örneğin pipe tan blokeli modda read fonksiyonuyla 10 byte okumak isteyelim. Pipe
boşsa blokede beklenir, fakat pipe ta en az bir byte varsa okunabilen miktar kadar okur ve
read fonksiyonu geri döner. Blokeli yazma işlemlerinde fonksiyon belirtilen tüm byteları
yazana kadar blokede kalır.
Hızlı aygıtlarda çalışrken örneğin disk dosyalarında çalışırken blokeli modla blokesiz mod
arasında bir farklılık yoktur. Yani biz read fonksiyonu ile bir disk dosyasından 100 byte
okumak istesek dosyanın sonuna gelmiş olma gibi bir problemin dışında kesinlikle bize bu
100 byte verilecektir. Blokeli modda 100 byte okumak isteyipte daha az okunabilmesi ancak
yavaş aygıtlar için söz konusudur. Bazı uygulamalrda programcı döngü içerisinde arka plan
uygulamaları izlemek isteyebilir. Bu durumda blokeli modda çalışılması istenmeyen bir
durumdur. Çünkü bu durumda okunacak bilgi oluşmamışsa akış read fonksiyonunda
bekletilmesi gerekir. programcı bu tür durumlarda bilgi oluşmuşsa read fonksiyonundan onu
alarak, oluşmamışsa belirli bir hata koduyla bilgiyi almadan çıkmak ister.
İşte böyle bir çalışma için blokesiz modda işlem yapmak gerekir. Blokesiz modda çalışmak
istemenin en önemli nedenlerinden biri birden fazla yavaş aygıtın döngü içerisinde ele
alınmasının gerekebilmesidir. Örneğin, birden fazla pipetan döngü içerisinde okuma yapmak
isteyelim. Programcı eğer okuma sırasında bloke olursa ikinci pipe tan gelen bilgiyi

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

int size, index,n;


...

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.

Blokesiz Okumalar ve select Fonksiyonu


Blokesiz modda birden fazla betimleyiciden okuma yapıyor olalım. Eğer, hiçbir betimleyicide
okunacak bilgi oluşmamışsa döngü boşuna devam edecektir. Tabi betimleyicilerden okuma
yapan betimleyicinin içerisinde arka plan bir işlem yapılıyorsa bu durum bir problem olarak
algılanmaz. Ancak amacımız arka plan işlem yapmak değil fakat birden fazla betimleyiciden
bloke olmadan bilgi almak ise döngü gereksiz bir biçimde CPU zamanı harcanmasına yol
açacaktır. Şüphesiz en iyi çözüm hiçbir betimleyicide bilgi yoksa threadi çizelge dışında

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.

select Fonksiyonu ve Zaman Aşımı


select fonksiyonunun son parametresi en kötü olasılıkla hiçbir betimleyicide bu olay
gerçekleşmemişse beklenecek zaman miktarını belirtir. Bu parametre NULL geçildiğinde
zaman aşımı uygulanmaz. Örneğin, iki betimleyiciden okuma yapma amacıyla select
içerisinde beklemek isteyelim, zaman aşımı olarak 1 sn vermiş olalım. Hiçbir betimleyicide
okuma olayı gerçekleşmemişse 1sn sonra select fonksiyonu blokeyi çözer. select
fonksiyonunun geri dönüş değeri olay gerçekleşmiş betimleyici sayısını belirtir. Fonksiyon 0
ile geri dönmüşse zaman aşımı dolayısıyla fonksiyondan çıkılmıştır. Zaman aşımı timeval
türünden bir yapı ile belirtilir.
struct timeval{
long tv_sec;
long tv_usec;
};
timeval yapısı <sys/time.h> içerisinde tanımlanmıştır. Görüldüğü gibi bu yapıyla mikro
saniyeler mertebesinde (saniyenin milyonda biri) bir zaman aşımı verilebilmektedir. Zaman
aşımı 0 olarak girilirse fonksiyon yalnızca test yapar. O andaki betimleyicilerin durumlarına
bakar ve geri döner. Biz bir betimleyicide okuma yada yazma olayının gerçekleşip
gerçekleşmediğini bloke olmadan anlamak istiyorsak zaman aşımını 0 girmemiz gerekir.

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.

Processler Arası Haberleşmede Klasik Yöntemler


Şimdiye kadar processler arası haberleşme yöntemi olarak isimli ve isimsiz pipelar
görülmüştür. Unix/Linux sistemlerinde pipeların dışında başka haberleşme yöntemleri de
vardır. processler arası haberleşmede klasik yöntemler mesaj sistemi ve paylaşılan bellek
alanları oluşturma yöntemidir.
Klasik processler arası haberleşme yöntemi ilk kez AT&T tabanlı Unix sistemlerinde
tanımlanmıştır. Bu yöntemler diğer Unix sistemleri tarafından da desteklenmiştir. Fakat
POSIX standartlarıyla birlikte systemV in bu fonksiyonları korunmakla birlikte bunların daha
gelişmiş daha iyi biçimleri tanımlanmıştır. Bugün kullandığımız sistemlerin çoğu hem
systemV in klasik yöntemlerini hemde POSIX in önerdiği kalsik yöntemleri desteklemektedir.
Maalesef POSIX standartlarının önerdiği fonksiyonlar hala geniş bir yaygınlıkta
kullanılmamaktadır. SystemV in klasik yöntemleri programcılar tarafından daha çok tercih
edilmektedir. Yani bugün mesaj kuyrukları yönteminin hem systemV hemde POSIX biçimleri
vardır. Benzer biçimde paylaşımlı bellek alanları yöntenmininde systemV ve POSIX biçimleri
bulunmaktadır.

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.

SystemV’ in Klasik Haberleşme Yöntemleri


SystemV in klasik haberleşme yöntemleri mesaj kuyruğu oluşturma (message queue)
paylaşımlı bellek alanları oluşturma (shared memory) dır.
Processler arası haberleşme yöntemleri işletim sisteminin yardımıyla sağlanmaktadır. Bir
processler arası haberleşme nesnesi yaratıldığında iki processinde aynı nesneyi görmesi
gerekir. İki processin aynı haberleşme nesnesini belirterek onlar üzerinde anlaşabilmesi için
işletim sistemlerinde ya isimsel yada sayısal bir belirteç kullanılmaktadır. systemV in klasik
yöntemleri sayısal belirleme, POSIX in klasik belirleme, win32 sistemleri isimsel belirleme
kullanmaktadır.
SystemV in klasik haberleşme yöntemlerinde önce haberleşme nesnesi bir sayı belirterek
yaratılır. Sonra iki processte aynı sayıyı belirterek bu nesneyi kullanırlar. Haberleşme
nesnelerinin belirlenmesinde kullanılan bu sayısal değer key_t türüyle temsil edilmektedir.
key_t türü Linux sistemlerinde tipik olarak int biçimindedir.
key_t ile belirtilen sayısal belirteç yalnızca o haberleşme nesnesi için tek olmak zorundadır.
Örneğin, bir mesaj kuyruğu nesnesinin sayısal belirteci ile paylaşımlı alan nesnesinin sayısal
belirteci çakışsa bile problem oluşmaz.
Haberleşme işlemini sağlayacak processlerin öncelikle belirteç olarak kullanılan sayısal
değerde anlaşmaları gerekir. Tabii bu sayısal değer daha önce başka bir process tarafından
benzer amaç için kullanılmışsa problemli bir durum oluşabilir. Bu çakışmanın

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.

ftok Fonksiyonunun Kullanımı


ftok aslında bir dosyanın inode numarasından hareketle bir sayı üreten basit bir fonksiyondur.
İki farklı process ftok fonksiyonuyla aynı dosyayı belirtirse aynı sayıları elde eder. Yani ftok
fonksiyonu sayı üretme işlemini bir dosya isminden hareketle yapmaktadır. Şüphesiz
haberleşme nesnesi yaratan herkes kendine ait bir dosyayı belirterek ve ftok fonksiyonunu
kullanarak sayıyı elde etse sistem genelinde bir tek olma durumu oluşturulabilir. Fakat
herkesin ftok fonksiyonunu kullanarak böyle bir işlemi yapacağınında garantisi yoktur. Bu
nedenle ftok fonksiyonu çakışma problemini çözmez yalnızca onu daha esnek bir hale getirir.
key_t ftok(const char *pathname, int proj_id);
Fonksiyon bir dosya path ifadesini ve bir ID değerini parametre olarak alır. Fonksiyon
dosyanın inode numarasını kullanarak yüksek anlamlı byte değeri ID olan pozitif bir sayı
üretir. Verilen dosya ismi ve ID aynı ise fonksiyon aynı değeri üretmektedir. Fonksiyonun
prototipi <sys/ipc.h> içerisindedir. key_t türü ise <sys/types.h> içerisinde typedef edilmiştir.

Klasik Processler Arası Haberleşme Nesnelerinin Kalıcılığı


Kalsik processler arası haberleşme nesneleri sistem genelinde kalıcıdır. Reboot işlemi
yapılana kadar eğer bilinçi olarak programcı tarafından silinmemişlerse kalmaya devam
ederler. POSIX sistemlerinde bir process klasik haberleşme nesnelerini yarattığında eğer onu
silmediyse processin sonlanmış olması nesnenin otomatik sislnmesine yol açmaz. (Oysa
örneğin win32 sistemlerinde process sonlandığında kernel nesneleri için otomatik kapatma
yapılmaktadır.)
ipcs isimli shell komutu ile o an sistemde yaşamakta olan haberleşme nesnelerine ilişkin
bilgiler alınabilir.

Klasik Haberleşme Nesnelerine İlişkin Fonksiyonların İsimlendirilmesi


Klasik haberleşme nesnelerinde handle olarak ID değeri kullanılmaktadır. Bu ID değeri
nesneyi yaratan ve açan xxxget isimli fonksiyonlarla elde edilir. Örneğin mesaj kuyruklarını
yaratan ve açan fonksiyon msgget, paylaşımlı bellek alanlarını yaratıp açan fonksiyon ise
shmget biçiminde isimlendirilmiştir. Bu xxxget fonksiyonları nesneye ilişkin sayısal belirteçi
parametre olarak alıp programcıya handle olarak kullanılacak olan ID değerini verirler.
Klasik haberleşme nensnelerinin ortak bir diğer biçimide xxxctl biçimindeki fonksiyonlardır.
Örneğin; msgctl, shmctl gibi. Bu fonksiyonlar nesneleri silmek için ve bir takım bilgiler almak
için kullanılmaktadır. Bu fonksiyonlar dışında nesneye özgü başka fonksiyonlarda vardır.

Mesaj Kuyruğu Yöntemi


Mesaj kuyrukları bir FIFO kuyruk sistemidir. Kuyruğa mesaj bırakmak msgsnd fonksiyonuyla
yapılır. n byte uzunluğundaki bilgi bir mesaj olarak kuyruğun sonuna eklenir. Kuyruktaki her
mesajın uzunluğu farklı olabilir. İşletim sistemine bağlı olarak mesaj kuyruklarında şu kısıtlar
vardır.
1. Kuyruktaki tüm mesajlardaki toplam byte sayısı

Sayfa 97 / 138
C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

2. Bir mesajdaki maximum byte sayısı


3. Tüm sistemdeki mesaj kuyruklarının sayısı(Yaratılacak maximum mesaj kuyruğu
sayısı)
4. Tüm mesaj kuyruklarındaki mesajların toplam sayısı
Bu değerler <sys/msg.h> dosyası içerisinde sırasıyla MSGMNB, MSGMAX, MSGMNI,
MSGTQL sembolik sabitleriyle belirtilmiştir.
Mesaj kuyruğuna mesaj gönderip alma tıpkı pipelarda olduğu gibi bloke esasına göre
yapılmaktadır. msgrcv fonksiyonuyla kuyruktan mesaj almak istediğimizde kuyrukta mesaj
yoksa kuyruğa yeni bir mesaj gelene kadar bloke işlemi yapılmaktadır. Benzer biçimde
msgsnd fonksiyonu da eğer mesaj kuyruğu doluysa yer açılana kadar threadi bloke etmektedir.
Mesaj kuyruğundaki her mesaj long bir ID değeriyle başlar. Örneğin, biz 10 byte’ ı mesaj
olarak kuyruğa bırakacak olsak bu 10 byte’ ın tepesine long bir ID yerleştirerek gönderme
işlemini yapamak zorundayız. Mesajın ID’ si mesaj kyuruğu içerisindeki mesajları birbirinden
ayırmak için kullanmaktadır. Mesaj kuyruğundan mesaj alırken alan kişi özellikle belirli bir
ID’ ye ilişkin mesajları alabilir. Örneğin, bir process bir kuyruktan 5 numaralı ID’ye sahip
mesajları alırken diğeri aynı kuyruktan 10 numaralı ID’ ye sahip mesajları alabilir. böylece bir
server programın tek bir kuyrukla tüm client programlara bilgi göndermesi mümkün olabilir.
Klasik haberleşme yöntemlerine ilişkin genel sembolik sabitler <sys/ipc.h> dosyası içerisinde
mesaj kuyruklarına ilişkin fonksiyonların prototipleri ve özel sembolik sabitler <sys/msg.h>
dosyası içerisindedir.

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.

Mesaj Kuyruğuna Mesaj Gönderilmesi


Bu işlem için kuyruğun yaratılmış olması gerekmektedir ve programcının kuyruğun ID
değerini bilmesi gerekmektedir. Esaj göndermek için msgsnd fonksiyonu kullanılır.
int msgsnd(int msqid, struct msgbuf *msgp, size_t msgsz, int msgflg);
Fonksiyonun birinci parametresi mesaj kuyruğunun ID değeridir. İkinci parametre mesaj
bilgilerinin başlangıç adresini belirtir. Mesaj bilgileri long bir alanla başlatılmak zorundadır.
Bu long değer mesajın ID’ sini belirtir. Fonksiyonun üçüncü parametresi mesajın byte
uzunluğudur. Bu uzunluğa mesajın başındaki ID değeri dahil değildir, yani uzunluk ID’ den
sonraki kısmın uzunluğudur. Fonksiyonun son parametresi 0 geçilebilir yada IPC_NOWAIT
biçiminde geçilebilir. Eğer 0 geçilirse blokeli çalışma söz konusudur, mesaj kuyruğu dolu ise
fonksiyon blokede bekler. Eğer IPC_NOWAIT geçilirse mesaj kuyruğu dolu olduğunda bloke
oluşmaz fonksiyon başarısızlıkla geri döner ve errno ENOMSG ile set edilir. Fonksiyonun
geri dönüş değeri başarı durumunda 0, başarısızlık durumunda –1 dir.

Paylaşımlı Bellek Alanlarının Kullanımı


Unix/Linux sistemleri tipik olarak bir sanal bellek modeli içinde çalışmaktadır. Örneğin,
Linux sistemleri intel işlemcilerinin korumalı modunda sanal bellek mekanizmasına göre
çalıştırılmaktadır. Tipik olarak Linux sistemleri ne kadar fiziksel RAM olursa olsun, sanki her
process 3GB’ lik bir alanı tek başına kullanıyormuş gibi bir ortam sunmaktadır. Her
processler arası geçişte bir process sanal bellekten atılarak diğeri sanal belleğe
yüklenmektedir. Örneğin tipik Linux sistemlerindeki durum şöyledir.
Bellek
Process
3GB

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.

Paylaşımlı Bellek Alanının Yaratılması


Yaratma işlemi shmget fonksiyonuyla yapılır.
int shmget(key_t key, int size, int flag);
Fonksiyonun birinci parametresi yaratılacak bellek alanının sayısal belirteç değeridir. Bu
parametre IPC_PRIVATE olarak geçilebilir, bu durumda sistem olmayan bir anahtar değerini
tespit ederek kullanır. Fonksiyonun ikinci parametresi paylaşılan bellek alanının byte
cinsinden uzunluğudur. (İntel işlemcilerinin kullanıldığı sistemlerinde bu değer 4Klık sayfa
bellek alanlarına yuvarlanmaktadır.) Fonksiyonun üçüncü parametresi IPC_CREAT yada
IPC_EXCL bayraklarıyla oluşturulabilir. IPC_CREAT kullanılmış ise 9 bitlik erişim bilgileri
de bu parametrede kodlanmalıdır. Bu parametre 0 olarak geçilirse olan IPC nesnesi açılır.
Fonksiyonun geri dönüş değeri paylaşımlı bellek alanının ID değeridir. Örneğin;
int shmid;
shmid = shmget(KEYVAL, size, IPC_CREAT|0666);

Paylaşılan Bellek Alanı için Adres Tahsisatının Yapılması


shmget fonksiyonu ile paylaşılan bellek alanı yaratılır, fakat henüz bu alan hiçbir processin
sanal adres alanıyla ilişkilendirilmemiştir. shmat fonksiyonu ile ilişkilendirme işlemi
yapılarak processe özgü bir adres elde edilir.
int *shmat(int shmid, const void *shmaddr, int shmflg);
Fonksiyonun birinci parametresi paylaşımlı bellek alanının ID değeridir. İkinci parametre
paylaşımlı bellek alanı için tahsis edilecek önerilen adres değeridir. Yani programcı belirli bir

Sayfa 100 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Paylaşılan Bellek Alanı için Tahsis Edilen Alanın Geri Bırakılması


Tahsis edilmiş alan paylaşımlı bellek alanı shmdt fonksiyonuyla boşaltılabilir.
int shmdt(const void *shmaddr);
Fonksiyonun parametresi tahsis edilmiş olan processe özgü adrestir. Fonksiyon başarılıysa 0
değerine başarısızsa –1 değerine geri döner.

Paylaşılan Bellek Alanı Nesnesinin Silinmesi


Paylaşımlı bellek alanı nesne yine processlerden bağımsız olarak varlığını sürdürür. Silme
işlemi için shmctl fonksiyonu kullanılabilir.
int shmctl(int shmid, int cmd, struct shmid_ds *buf);
Bu fonksiyon yalnızca nesneyi silmek için değil nesne özelliklerini elde edip değiştirmek
içinde kullanıllır. Silme işlemi için ikinci parametre IPC_RMID girilir. Bu durumda üçüncü
parametre NULL girilmelidir. Fonksiyon başarılıysa 0 değerine başarısızsa –1 değerine geri
döner.
IPC_ ile başlayan tüm sembolik sabitler <sys/ipc.h> içerisinde, SHM_ ile başlayan tüm
sembolik sabitler ve tüm fonksiyonların prototipleri <sys/shm.h> içerisindedir.

POSIX Thread İşlemleri


Unix sistemleri klasik olarak process temeline dayandırılmıştır. Threadler göreli olarak yeni
bir konudur. Thread kavramı Unix sistemlerine çeşitli farklılıklarla sokulmuştur, yani her
sistemin threadleri destekleme biçimi başlangıçta farklıydı. Sonra thread işlemleri POSIX
tarafından standardize edilmiştir. POSIX’ in standart thread fonksiyonlarının hepsi pthread_
önekiyle isimlendirilmiştir. Genellikle derleyici sistemlerinde thread fonksiyonları ayrı bir
kütüphanede tututlmaktadır. Bu kütüphaneye pthread kütüphanesi denilmektedir. Linux gcc
derleyicileri default olarak pthread kütüphanesini işleme sokmamaktadır. Bu nedenle thread
kullanana programlar derlenirken –lpthread seçeneğiyle bu kütüphanenin link işlemine dahil
edilmesi gerekir. derleme işlemi aşağıdaki gibi yapılabilir:
gcc –o sample –lpthread sample.c

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

Sayfa 101 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Sayfa 102 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Bir Threadin Sonlanmasının Beklenmesi


Bir thread pthread_join fonksiyonuyla herhengi bir threadin sonlanmasına kadar bloke
edilebilir. pthread_join fonksiyonu işlevsel olarak wait fonksiyonuna benzemektedir.
pthread_join fonksiyonu ile processin herhangibir threadi aynı processin başka bir threadini
bekleyebilir. Yani yalnızca threadi yaratan threadin beklemesi gibi bir zorunluluk yoktur.
int pthread_join(pthread_t th, void **thread_return);

Sayfa 103 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Thread Fonksiyonuna geçirilen Parametreler


Thread fonksiyonunun parametresi void* türündendir. Fakat biz thread fonksiyonuna bir
nesnenin adresini geçirmek zorunda değiliz. Örneğin int bir bilgiyi bir adresmiş gibi void *
türüne dönüştürerek fonksiyona geçirebiliriz. Bu değer thread fonksiyonunun içerisinde
yeniden void * türünden int türüne dönüştürerek kullanılabilir. Thread fonksiyonuna birden
fazla bilgi geçirmek istiyorsak bu bilgileri bir yapı yada dizi biçiminde oluşturup bu yapı yada
dizinin başlangıç adresini geçirebiliriz.
Bir threadin yerel bir nesnesinin adresi prensib olarak başka bir threade parametre biçiminde
aktarılmamalıdır. Bunun iki sakıncası olabilir.
1. Yerel nesneye sahip olan thread sonlanırsa threade geçirilen adres geçersiz bir
duruma düşürülebilir.
2. Yerel nesneye sahip olan thread sonlanmasa bile nesnenin yerel olmasından dolayı
nesne bellekten yok edilebilir ve adres yine geçersiz duruma düşürülebilir.
Thread fonksiyonuna statik yerel bir nesnenin adresi geçilebilir. Fakat bu durumda bu adres
birden fazla threade parametre olarak geçilirse tüm threadler aynı bilgileri kullanır hale gelir.
Bu nedenle en iyi teknik parametre olarak geçirilecek nesneyi malloc fonksiyonu ile heap

Sayfa 104 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

üzerinde tahsis etmektir. Bu alanlar thread fonksiyonu sonlanırken thread fonksiyonu


tarafından free hale getirilebilirler.

Threadlerin Kullanım Alanları


Threadler özellikle arka plan işlemlerin yapıldığı durumlarda kullanılırlar. Örneğin, normal
işlemler yürütülürken aynı zamanda porttan gelen bir bilgiyi de almak isteyelim. Burada porta
bakma işlemi threade bırakılırsa çok önemli algoritmik kolaylık sağlanır. Birden fazla blokeye
yol açan kaynak ile işlem yapılırken pekçok durumda threadli çalışma tercih edilebilir. Birden
fazla pipe, soket gibi blokesiz işlem yapabilen aygıt üzerinde işlem yapmanın iki yolu olabilir.
1. Kaynakların hepsi blokesiz modda açılır ve tek threadle select fonksiyonu
kullanılarak işlemler yapılır.
2. Her kaynak için bir thread yaratılır ve blokeli işlem yapılabilir.
Threadler client-server uygulamalarda da tercih edilebilmektedir. Bu tür uygulamaların
karakteristikleri şunlardır.
1. client-server haberleşmesi blokeli modda yapılır.
2. Servera aynı anda bağlanacak client sayısı çok fazla değildir.(örneğin, client sayısı
100e kadar bu yöntem uygulanabilir, fakat daha fazla client için yöntem verimsiz hale gelir.)
3. Server programının çalıştığı makine güçlü bir makinadır.
Threadli client-server uygulamalarda server programın clientlar ile haberleşen bir thread
fonksiyonu bulunur. Thread fonksiyonunun parametresi clienta özgü bilgilerin bulunduğu
heap üzerinde tahsis edilmiş bir yapının adresi olabilir. Yeni bir client bağlandığında server
program clientın bilgilerini heap üzerinde tahsis edilmiş bir yapı içerisine doldurarak yeni bir
threadi yaratır. Böylece her thread aynı kodu çalıştıracak ama her threade geçirilen yapı farklı
olduğu için her thread farklı bir client ile haberleşmiş olacaktı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;
}

Joinable ve Detached Threadler


Threadlerinde tıpkı processler gibi birer handle alanları vardır. Thread fonksiyonunun geri
dönüş değeri handle alanında saklanmaktadır. pthread_join fonksiyonu tıpkı wait fonksiyonu
gibi yalnızca threadin sonlanmasını beklemez aynı zamanda thread fonksiyonunun geri dönüş
değerini alarak handle alanını da yok eder. Fakat çok fazla threadin yaratıldığı uygulamalarda
pthread_join ile threadlerin beklenmesi istenmeyebilir. Bu durumda threadlerin handle
alanlarının thread fonksiyonu bittiğinde otomatik boşaltılması tercih edilebilir. İşte thread
fonksiyonu bittiğinde handle alanları otomatik olarak boşaltılan threadlere “detached” thread
denir. Detached threadler pthread_join fonksiyonuyla beklenemezler. Eğer bir detached

Sayfa 105 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

threadin ID si pthread_join fonksiyonuna verilirse fonksiyon başarısızlık durumuyla geri


döner.
Bir thread yaratıldığında joinable mı yoksa detached mi olacağı pthread_create
fonksiyonunun thread özelliklerine ilişkin parametresinde belirlenebilir. Bu paramtere NULL
geçilirse thread default olarak joinable kabul edilir. joinable threadler pthread_detached
fonksiyonuyla detached fonksiyonuna çevirilebilirler.

Threadler Arası Atomik Fonksiyonlar


Atomik fonksiyon demek sistemin genelinde tüm threadler dikkate alındığında tam olarak
bitmeden aynı fonksiyonun başka bir threadde çalıştırılamaması demektir. Örneğin, read ve
write fonksiyonları atomiktir. Yani bir thread read fonksiyonunu çağırdığında akış onun
içerisindeyken başka bir thread aynı fonksiyonun içine giremez. Örneğin iki farklı processin
threadi aynı offsetten başalayarak aynı dosyaya write işlemi yapmaya çalışırsa içiçe geçme
hiçbir zaman olmaz. Önce threadlerden birisi yazma işlemini tamamen bitirir sonra diğeri
yazma işlemi yapar. Fonksiyonların bu atomikliği tmamen işletim sistemi tarafından içsel
olarak tasarlanmıştır. Yani programcının özel bir şey yapmasına gerek yoktur. threadler arası
atomikliği garanti altına alınmış fonksiyonlar şunlardır: chmod,close,fchmod, fcntl, fstat,
ftruncate, lseek, open, read, readlink, stat, symlink, write.
Ancak POSIX standartlarında bu fonksiyonların atomikliği yalnızca sıradan disk dosyaları ile
sınırlandırılmıştır. Yani aygıt dosyalarını kapsamamaktadır.

Thread Safe Fonksiyonlar


Atomiklik bir fonksiyon çalışırken başka bir threadin o fonksiyonu çalıştıramaması ancak o
fonksiyon bittikten sonra çalıştırabilmesi anlamına gelmektedir. Bir fonksiyonun thread safe
olması ise birden fazla threadin o fonksiyon içerisinde ilerleyebileceği ama bunun bir
olumsuzluğa yol açmayacağı anlamına gelir. Şüphesiz fonksiyon atomik yapılarak thread safe
olma durumu sağlanabilir. Fonksiyonu thread safe yapmak için atomik hale getirmek verimli
bir yöntem değildir.
Bir fonksiyonun thread safe olabilmesi için o fonksiyonun statik yerel nesneleri yada global
nesneleri kullanmaması gerekir. Örneğin, localtime, rand gibi fonksiyonlar thread safe
fonksiyonlar değildir. C standartlarında thread safe olma gibi bir durum tamamen göz ardı
edilmiştir. Bu durumda bir derleyici bu fonksiyonları thread safe biçiminde sunabilir yada
sunmayabilir.
POSIX standartlarında thread safe olmak zorunda olmayan fonksiyonların listesi verilmişitir.
Bu fonksiyonların çoğu standart C fonksiyonlarıdır. Bu fonksiyonlardan önemlileri şunlardır:
asctime, ctime, getenv, localtime, rand, readdir, setenv.
Thread safe olmayan pekçok fonksiyonun POSIX standartlarında “_r” son ekli thread safe
versiyonları vardır. Örneğin, eğer biz thread safe çalışmasını garanti etmek istiyorsak rand_r
fonksiyonunu kullanmamız gerekir.
<stdio.h> içerisinde bulunan fonksiyonların hepsi thread safe fonksiyonlardır. Örneğin, biz
fprintf fonksiyonu ile iki farklı threadden aynı dosyaya bir şeyler yazacak olsak yazdığımız
şeyler içiçe geçmez. Bilindiği gibi bu fonksiyonlar sistem fonksiyonlarını az çağırmak için
tamponlama uygulamaktadır. Dosya bilgi göstericisi global olsa biz de iki farklı threadden bu
dosya bilgi göstericisini kullanacak olsak aynı tamponu kullanıyor oluruz, fakat herhangibir
problem oluşmaz. Bu problen standart C kütüphanesinin kendi içerisinde senkronize edilerek
çözülmüştür.
Global yada statik yerel nesneleri kullanan fonksiyonları nasıl thread safe yapabiliriz? İşte
bunu sağlayabilmek için threade özgü global bir alanın bulunması gerekir. Yani hem

Sayfa 106 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Threadler Arası Çalışma ve fork İşlemi


fork fonksiyonu thread kavramından önce tasarlanmıi bir fonksiyondur. Thread konusu
uygulamaya girdiğinde fork fonksiyonun sematiğinde de düzenlemeler yapılmıştır. Bir thread
fork işlemi yaptığında alt processte yalnızca fork işlemini yapan thread varolur. Yani alt
process her zaman tek thread ile çalışmaya başlamaktadır. O thread de fork işlemini yapan
threaddir.

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.

Threadlerin Dışarıdan Sonlandırılması


Normal olarak bir threadin işini bitirdikten sonra kendini sonlandırması gerekir. Çalışmakta
olan bir threadin dışarıdan sonlandırılması threadin o anda yaptığı işe bağlı olarak
problemlere yol açabilir. Fakat ne olursa olsun thredlerin programlama yoluyla dışarıdan
sonlandırılmasına bazen gereksinim duyulabilir. Bunun için POSIX te pthread_cancel
fonksiyonu kullanılır. pthread_cancel fonksiyonu sonlandırma işlemini hemen değil belirli
sistem fonksiyonlarının içerisinde gerçekleştirmektedir. Yani biz pthread_cancel fonksiyonu
ile bir threadi sonlandırmak isteyelim. Akış o threaddeyken o thread belirlenmiş olan
fonksiyonların içerisine girdiğinde sonlandırma yapılacaktır.
int pthread_cancel(pthread_t thread);
Threadin sonlanmasına olanak sağlayan sistem fonksiyonlarından bazıları şunlardır: accept,
close, connect, creat, open, pause, read, select, send, sleep, wait,waitpid, write.

Threadlerin Sonlandırılma Durumları


Önceki konudan da görüldüğü gibi pthread_cancel fonksiyonu yalnızca sonlandırma isteğini
iletmekte sonlandırma işlemi aslında önceden belirlenmiş bazı fonksiyonların içerisinde
yapılmaktadır. Fakat aslında bu durum deafult durumdur ve değiştirilebilir. Yani
pthread_cancel fonksiyonu ile istenirse thread herhangi bir zaman sonlandırılabilir.
Threadlerin sonlandırma modu hemen ya da belirli fonksiyonların içerisinde olmak üzere
ikiye ayrılmaktadır. Sonlandırma modu pthread_setcanceltype fonksiyonu ile
değiştirilebilmektedir.
int pthread_setcanceltype(int type, int *oldtype);
Fonksiyonun birinci parametresi ya PTHREAD_CANCEL_ASYNCHRONOUS ya da
PTHREAD_CANCEL_DEFERRED olabilir. PTHREAD_CANCEL_ASYNCHRONOUS
threadin herhangi bir zaman aniden yani hiçbir fonksiyonun içerisine girmesine gerek
kalmadan sonlandırılabileceğini belirtir. PTHREAD_CANCEL_DEFERRED default

Sayfa 107 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Thread Sonlandırma Mekanizmasının Açılıp Kapatılması


Programcı isterse threadlerin sonlandırılabilirlik durumunu değiştirebilir. Bu işlem
pthread_setcancelstate fonksiyonu ile yapılmaktadır.
int pthread_setcancelstate(int state, int *oldstate);
Fonksiyonun birinci parametresi PTHREAD_CANCEL_ENABLE yada
PTHREAD_CANCEL_DISABLE biçiminde girilir. İkinci parametre daha önceki durumla
ilgilidir. Fonksiyon başarı durumunda 0 değerine geri döner. Bu fonksiyonda yalnızca
fonksiyonu çağıran threadin durumunu değiştirir. Threadler yaratıldığında default olarak
sonlandırılabilir durumdadır. Threadin sonlandırılabilirliği pthread_setcancelstate fonksiyonu
ile kapatılmış olsun. Bu durumda başka bir threadin pthread_cancel fonksiyonunu
uyguladığını düşünelim. Bu istek tıpkı sinyal mekanizmasında olduğu gibi askıda
bırakılacaktır. Örneğin, bir thread geçici bir süre dışarıdan sonlandırılmaya kapatılabilir.
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, NULL);
...
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, NULL);

Threadin Bitiş Fonksiyonları


POSIX thread sisteminde bir threadin bitiş fonksiyonu belirlenebilmektedir. Bir thread
pthread_exit fonksiyonuyla ya da pthread_cancel fonksiyonuyla sonlandığında sonlanmadan
önce belirlenen bir fonksiyonu çağırabilir. Threadin bitiş fonksiyonu pthread_cleanup_push
fonksiyonuyla set edilmektedir.
void pthread_cleanup_push(void (*routine) (void*), void *arg);
Fonksiyonun birinci parametresi bitiş fonksiyonunun adresi, ikinci parametresi ise bitiş
fonksiyonuna geçirilecek parametredir. Bitiş fonksiyonu olarak birden fazla fonksiyon set
edilebilir. Bu durumda bu fonksiyonlar ters sırada çağırılırlar. Bitiş fonksiyonları istenirse
pthread_cleanup_pop fonksiyonuyla çekilebilir.
void pthread_cleanup_pop(int execute);
Fonksiyonun parametresi 0 ya da 0 dışı bir değer olabilir. 0 dışı bir değer ise son set edilen
fonksiyon listeden çıkartılır ve çalıştırılır. 0 ise listeden çıkartılır ama çalıştırılmaz.

Threadler ve Sinyal Mekanizması


Sinyal sistemi threadli bir sistem için düşünülmemiştir. Threadler POSIX sistemlerine
eklendiğinde sinyal konusuyla ilgili bazı belirlemelere ihtiyaç duyulmuştur. Örneğin;
• Processe sinyal gönderildiğinde processi hangi sinyal akışı çalıştıracaktır?
• Sinyal processe mi yoksa threade mi gönderilmektedir?
kill fonksiyonu ile her zaman processe sinyal gönderilir. Processe gönderilen bu sinyallere
asenkron sinyaller denilmektedir. Processe gönderilen sinyalin hangi thread tarafından ele
alnıacağı belirlenmemiştir. Yani programcı processe gönderilen sinyalin herhangi bir thread
akışı tarafından ele alınabileceğini düşünmektedir.
POSIX standartlarına göre işletim sistemini yazanlar hangi mesajların threade gönderildiğini
hangilerinin processin bütününe gönderildiğini tespit etmek zorundadır. Standartlarda thread

Sayfa 108 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Threadler ve Sinyal Fonksiyonları


Bilindiği gibi sigaction fonksiyonu sinyalleri set etmek için kullanılan güvenli bir
fonksiyondur. Bir thread için ayrıca sinyal set eden bir fonksiyon yoktur, sigaction ile yada
signal fonksiyonuyla process bütününde sinyal set edilir.
Bir threadin bir sinyale yanıt vermesini istediğimizi düşünelim. Önce sinyal fonksiyonu
process bütününde sigaction fonksiyonuyla set edilmelidir. Artık threade pthread_kill ile
sinyal gönderilirse, o thread sinyal fonksiyonunu işler. Fakat processe gönderilen bir sinyalin
belirli bir thread tarafından ele alınması isteniyorsa bir tek thread dışında bütün threadler o
sinyale pthread_sigmask ile kapatılmalıdı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.

Sayfa 109 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Kilit Yöntemi (read, write lock)


Kilit mekanizmasında kilitleyen ve kilidi açan fonksiyonlar vardır. Kritik kod, kilitleyen
fonksiyon ile kilidi açan fonksiyon arasına yerleştirilir.
Kilitleyen Fonksiyon
.....
..... Kritik Kod
.....
Kilidi Açan Fonksiyon
Kilitleyen fonksiyon eğer kilide başka bir thread sahip ise o thread kilidi açana kadar threadi
bloke eder. Eğer kilide hiçbir thread sahip değilse kilidin sahipliğini alarak kritik koda giriş
yapar. Kritik kodun sonunda kilidi açan fonksiyon çağırılarak kilit açılır.

Kilitleme İşlemleri için Hazırlık İşlemlerinin Yapılması


Kilit işlemleri için önce pthread_rwlock_t ile bir kilit nesnesi alınır. Bu kilit nesnesinin birden
fazla thread tarafından kullanılabilmesi için global olarak tanımlanması gerekir.
pthread_rwlock_t türü pthread_h içerisinde bildirilmiş olan bir yapıdır. Daha sonra bu kilit
nesnesine ilk değer verme işleminin yapılması gerekir. İlk değer verme işlemi
PTHREAD_RWLOCK_INITIALIZER makrosu ile yada pthread_rwlock_init fonksiyonuyla
yapılabilir. Örneğin,
pthread_rwlock_t g_lock = PTHREAD_RWLOCK_INITIALIZER;
int pthread_rwlock_init(pthread_rwlock_t *rwlock, const pthread_rwlockattr_t *attr);
Fonksiyonun birinci parametresi kilit nesnesinin adresidir, ikinci parametre NULL geçilebilir.
Kilit nesnesine PTHREAD_RWLOCK_INITIALIZER ile ilk değer verilmesi daha
kullanışlıdır.

Kilitleme ve Kilidi Açma İşlemleri


Aynı bölge üzerinde iki threadin okuma işlemi yapmasının bir sakıncası yoktur. Fakat bir
thread okuma yaparken diğerinin yazma yapması yada bir thread yazma yaparken diğerinin de
yazma yapması probleme yol açar. Bu nedenle kilitleme işlemi okuma için kilitleme ve yazma
için kilitleme olmak üzere ikiye ayrılmaktadır. İki thread aynı kilidi okuma amaçlı geçebilir.
Fakat okuma amacıyla kilitlenmiş olan bir bölge yazma amacıyla kilitlenmeye çalışılırsa
bloke oluşur. Yine yazma için kilitleme yapıldığında başka bir thread ne okuma için nede
yazma için kilitli bölgeye giremez. Özetle aynı anda yalnızca okuma amaçlı kilit
alınabilmektedir.
int pthread_rwlock_rdlock(pthread_rwlock_t *rwlock);
int pthread_rwlock_wrlock(pthread_rwlock_t *rwlock);

Sayfa 110 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Mutex Nesnesinin Yaratılması


Mutex nesnesi pthread_mutex_t türüyle temsil edilmiştir. Bu tür gerçekte bir yapı
belirtmektedir. Mutex nesneleri de threadler arası kullanımı mümkün hale getirmek için
global alınır. Mutex nesnesine ilk değer verme işlemi yine makro yoluyla yada fonksiyon
yoluyla yapılabilmektedir. İlk değer verme işlemi için PTHREAD_MUTEX_INITIALIZER
makrosu kullanılır. Örneğin,
pthread_mutex_t g_mutex = PTHREAD_MUTEX_INITIALIZER;
Mutex nesnesine aynı zamanda pthread_mutex_init fonksiyonuyla da ilk değer verilebilir.
int pthread_mutex_init(pthread_mutex_t *mutex, const pthread_rwlockattr_t
*mutexattr);
Fonksiyonun birinci parametresi mutex nesnesinin adresidir. İkinci parametre özellik bilgisine
ilişkindir, NULL olarak geçilebilir.

Mutex Nesnesinin Kullanımı


Programcı kritik kodu pthread_mutex_lock ve pthread_mutex_unlock fonksiyonlarıyla
oluşturur.
pthread_mutex_lock(...);
.....
..... Kritik Kod
.....
pthread_mutex_lock(...);
Birinci thread kritik koda girerek mutex nesnesinin sahipliğini alır. Sahipliği bırakmadan
başka bir thread kritik koda girmeye çalışırsa bloke oluşur.
int pthread_mutex_lock(pthread_mutex_t *mutex);
int pthread_mutex_unlock(pthread_mutex_t *mutex);
Fonksiyonlar başarı durumunda 0, başarısızlık durumunda hata koduna geri dönerler.
POSIX, mutex sisteminde mutex nesnesinin sahipliğini bir thread bırakmadan yine
almamalıdır. Örneğin, Win32 sistemlerinde durum böyle değildir. Win32 sistemlerinde
mutex nesnesinin sahipliği aynı thread tarafından birden fazla kez alınabilir. Sahiplik kaç kez
alınmışsa o kadar kez bırakılmalıdır. Hem POSIX sistemlerinde hemde Win32 sistemlerinde
mutex nesnesinin sahipliği ancak sahipliğini almış olan thread tarafından bırakılabilir.

Mutex Nesnelerinin Prosesler Arası Kullanımı


POSIX standartlarına göre mutex nesneleri prosesler arasında paylaşılabilir. Bunun için mutex
nesnelerinin paylaşılan bir bellek alanında yaratılması gerekir. Yani programcı mutex nesnesi

Sayfa 111 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Mutex Nesnesinin Özellik Bilgisi


Mutex nesnesinin özelliklerini belirlemek için pthread_mutexattr_t türünden bir nesne
kullanılmalıdır. Programcı pthread_mutexattr_t türünden bir nesne tanımlar, daha sonra bu
nesne içerisinde özellikleri belirler ve nesnenin adresini pthread_mutex_init fonksiyonuna
parametre olarak geçirir. Eğer mutex nesnesi PTHREAD_MUTEX_INITIALIZER makrosu ile
ilk değer verilerek yaratılmışsa yada pthread_mutex_init fonksiyonunda özellik bilgisi için
NULL geçilerek yaratılmışsa default özellikte yaratılır. Default özellik prosesler arası
paylaşımın olmadığı durumdur.
Mutex özellikleri fazla değildir. Programcı pthread_mutexattr_setpshared ve
pthread_mutexattr_getpshared fonksiyonlarıyla mutex nesnesinin prosesler arası paylaşılıp
paylaşılmayacağını belirleyebilir.
int pthread_mutexattr_setpshared(pthread_mutexattr_t *attr, int pshared);
Fonksiyonun birinci parametresi mutex özellik nesnesinin adresidir. İkinci parametresi
PTHREAD_PROCESS_SHARED yada PTHREAD_PROCESS_PRIVATE biçiminde girilir.
int pthread_mutexattr_getpshared(const pthread_mutexattr_t *attr, int *pshared);
Fonksiyon processler arası paylaşım özellik bilgisinin ikinci parametreyle belirtilen int
nesnenin içine yerleştirir.
Mutex özellik nesnesi pthread_mutexattr_init fonksiyonu ile yaratılır ve
pthread_mutexattr_destroy fonksiyonu ile yok edilir.
int pthread_mutexattr_init(pthread_mutexattr_t *attr);
int pthread_mutexattr_destroy(pthread_mutexattr_t *attr);
Özellik nesnesinin mutex nesnesi yaratıldıktan sonra kalmasının bir önemi yoktur. Mutex
özellik nesnesiyle mutexin processler arası paylaşımı için hazırlıklar şöyle yapılabilir.
pthread_mutexattr_t attr;
...
pthread_mutexattr_init(&attr);
pthread_mutexattr_setpshared(&attr, PTHREAD_PROCESS_SHARED);
pthread_mutex_init(&g_mutex, &attr);
pthread_mutexattr_destroy(&attr);

Semafor Senkronizasyon Nesneleri


Unix sistemlerindeki semafor nesneleri SystemV in klasik semafor nesneleri ve POSIX
standartlarında bahsedilen semafor nesneleri olmak üzere ikiye ayrılmaktadır. Maalesef
SystemV in klasik semafor nesnelerinin kullanımı zordur. POSIX semafor nesneleri isimli ve
isimsiz olmak üzere ikiye ayrılmaktadır. İsimsiz semafor nesneleri aynı processin threadleri
arasındaki senkronizasyon için isimli semafor nesneleri ise farklı processlerdeki threadlerin
senkronizasyonu için kullanılmaktadır.

İsimsiz Semafor Nesneleri


Semafor nesneleri sayaçlı senkronizasyon nesneleridir. Bir kritik koda en fazla n tane threadin
girmesini sağlamak için kullanılırlar.
Semafor nesneleri yaratılırken sem_init fonksiyonuyla bir sayaç değeri verilerek yaratılırlar.
Kritik kod sem_wait ile sem_post fonksiyonları arasındaki kod olarak oluşturulur.
sem_wait(...);

Sayfa 112 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

.... Kritik Kod


sem_post(...);
sem_wait fonksiyonu semafor sayacı 0 ise threadi bloke eder. 0’ dan büyükse sayacı azaltarak
geçiş sağlar. Semafor sayacı 3 olsun, bu durumda 3 thread semafora girecektir ve sayaç 0’ a
kadar düşecektir. Dördüncü thread sem_wait fonksiyonunda bloke olur. sem_post semafor
sayacını bir arttırır, böylece bir thread kritik koddan çıktığında sayaç yeniden bir olacak ve
yeni bir thread kritik koda girebilecektir. Semafor sayacının değeri istenildiği zaman
sem_getvalue ile alınabilir. Nihayet semafor nesnesi kullanıldıktan sonra herhangi bir thread
tarafından sem_destroy fonksiyonuyla yok edilir.
int sem_init(sem_t *sem, int pshared, unsigned int value);
Fonksiyonun birinci parametresi sem_t türünden semafor nesnesinin başlangıç adresidir.
İkinci parametre isimsiz semafor nesnesinin processler arasında paylaşılıp paylaşılmayacağını
belirtir. Görüldüğü gibi isimsiz semafor nesneleri de processler arasında paylaşılabilmektedir.
Tabi bunun için semafor nesnesinin gene paylaşılan bellek alanında temsil edilmesi gerekir.
İkinci parametre 0 ise nesne processler arasında paylaşılamaz, 0 dışı ise paylaşılabilir. Üçüncü
parametre semafor nesnesinin başlangıçtaki sayaç değeridir. Fonksiyon başarı durumunda 0,
başarısızlık durumunda –1 değerine geri döner ve errno set edilir.
int sem_destroy(sem_t *sem);
Semafor nesnesini yok etmek için kullanılır. Fonksiyon semafor nesnesini boşaltmaktadır.
int sem_wait(sem_t *sem);
int sem_post(sem_t *sem);
sem_wait fonksiyonu her zaman 0’ a geri döner. Bu fonksiyonlar dışında birde sem_trywait
isimli bir fonksiyonda vardır. Bu fonksiyon semafor sayacı 0 ise blokeye yol açmadan
başarısızlıkla geri döner.
int sem_getvalue(sem_t *sem, int *sval);

Üretici-Tüketici Probleminin Semafor Nesneleriyle Çözümü


Üretici-Tüketici probleminde üreticinin tüketici bilgiyi almadan yeni bir bilgi
yerleştirmemesi, tüketicinin de üretici bilgiyi yerleştirmeden bilgiyi almaya çalışmaması
gerekir. Üretici tüketici probleminde iki semafor alınır. Üretici tüketiciyi tüketici de üreticiyi
blokeden kurtararak işlemlerini yapar.
sem_t g_sem_producer, g_sem_consumer;
...
sem_init(&g_sem_producer, 0, 1);
sem_init(&g_sem_consumer, 0, 1);
Üretici tüketici probleminde üretici ve tüketici için birer semafor nesnesi yaratılır. Üreticinin
semafor sayacı başlangıçta 1, tüketicinin ise başlangıçta 0 olmalıdır. Böylece işin başında
üretici beklemezken tüketici blok başında bekler.

Ü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

Sayfa 113 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

İsimli Semafor Nesneleri


İsimli semafor nesneleri processler arasındaki senkronizasyonlar için kullanılabilir. İsimli
semafor nesnelerini yaratmak için sem_open fonksiyonuyla yaratılır ve sem_destroy
fonksiyonuyla yok edilir. isimli semafor nesnelerini beklemek için yine isimsiz nesnelerde
olduğu gibi sem_wait ve sem_post fonksiyonları kullanılmaktadır. İsimli semafor nesneleri
dosya sisteminde bir dosya gibi tutulmaktadır. Oluşturulan bu semafor dosyası sem_unlink
fonksiyonuyla silinebilir. İsimli ve isimsiz semafor fonksiyonları aşağıdaki gibi özetlenebilir.

İ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.

Threade Özgü Data Alanlarının Oluşturulması


Bazı uygulamalarda threade özgü global değişken kullanmak gerekebilir. Örneğin, bir
fonksiyon grubu global bir değişkeni kullanacak olsa fonksiyonu thread safe yapabilmek için
global değişkenin threade özgü olması gerekir. böylece her thread adeta başka bir global
değişken üzerinde çalışacaktır. Threade özgü global data alanı yaratmak için manuel bazı
yöntemler denenebilir. Örneğin, thread yaratıldığında ID belirtilerek bir alan tahsis edilip
global bir bağlı listeye eklenebilir. O alana her defasında her thread kendi ID sini vererek
erişebilir. Fakat bu durum Unix sistemlerinde bir grup POSIX fonksiyonuyla
basitleştirilmiştir. Aynı konu Win32 sistemlerinde TLS(Thread Local Storage) olarak
bilinmektedir. Win32 deki TLS kullanımı biçim bakımından POSIX sistemlerine oldukça
benzer.
Threade özgü data alanlarının oluşturulması için önce data alanı pthread_data_create
fonksiyonuyla yaratılır. Daha sonra bu işlemden bir handle değeri elde edilir. bu handle değeri
pthread_key_t türüyle temsil edilmektedir. Yaratılan handle için bir göstericilik alan tahsis
edilmektedir. Programcı threade özgü global değişkenleri bir yapı içerisinde toplayabilir. Bu
yapıyı malloc fonksiyonuyla heap üzerinde tahsis edebilir ve yapının başlangıç adresini
handle ile belirlenen alanda saklayabilir.

Sayfa 114 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

int pthread_key_create(pthread_key_t key, void (destr_function) (void *));


Fonksiyonun birinci parametresi threade özgü handle alanı için gerekli olan handle değerini
yerleştirileceği pthread_key_t türünden nesnenin adresidir. Fonksiyonun ikinci parametresi
thread pthread_exit yada pthread_cancel fonksiyonuyla sonlandırıldığında çağırılacak bir
bitiş fonksiyonunun adresidir. POSIX tasarımcıları bu fonksiyonda threade özgü yapılan bir
takım tahsisatların geri alınabilmesine olanak sağlamıştır. Bu parametre NULL olarak
geçilebilir. fonksiyonun geri dönüş değeri diğer thread fonksiyonlarında olduğu gibi başarı
durumunda 0, başarısızlık durumunda hata kodudur.
Uygulamada programcı handle değerinin saklanacağı pthread_key_t türünden nesneyi global
olarak tanımlar ve pthread_key_create fonksiyonunu threadleri yaratmadan önce bir kez
çağırır. pthread_key_create fonksiyonundan elde edilen bu handle değeri tek bir threade ögü
değildir. Yani bütün threadler threade özgü data alanına erişemek için aynı handle değerini
kullanırlar. Ama her thread kendine özgü data alanına erişir.
pthread_key_create fonksiyonu ile birden fazla threade özgü datalar için handle elde
edilebilir. Fakat genellikle böyle bir şeye gereksinim duyulmamaktadır.
Handle değeri elde edildikten sonra threade özgü bilgi void bir adres biçiminde
pthread_setspecific fonksiyonuyla set edilebilir. Fonksiyonun prototipi aşağıdaki gibidir.
int pthread_setspecific(pthread_key_t key, const void *pointer);
Fonksiyonun birinci parametresi daha önce elde edilmiş olan handle değeri ikinci parametresi
threade özgü alana yerleştirilecek adres bilgisidir. O adres normal olarak programcı tarafından
tahsis edilen dinamik alanın adresi olmalıdır. pthread_setspecific fonksiyonuyla set edilen bu
değer pthread_getspecific fonksiyonuyla elde edilebilir.
void * pthread_getspecific(pthread_key_t key);
Fonksiyonun parametresi yaratılan threade özgü alanın handle değeri geri dönüş değeri daha
önce set edilen adrestir.

Thread Özelliklerinin Değiştirilmesi


Thread yaratılırken thread özellikleri pthread_create fonksiyonunda belirlenebilmektedir.
pthread_create fonksiyonunun ikinci parametresi threadin özelliklerini belirlemek için
kullanılır. Programcı pthread_attr_t türünden bir nesne tanımlar, pthread_attr_t bir yapı
türünü belirtmektedir. Önce pthread_attr_init fonksiyonu ile nesneye ilk değerler verilir.
Daha sonra pthread_attr_xxx fonksiyonları ile thread özellikleri belirlenir. Nesnenin adresi
pthread_create fonksiyonunda kullanıldıktan sonra özellik nesnesi pthread_attr_destroy
fonksiyonuyla boşaltılır.
pthread_attr_t tattr;
pthread_attr_init(&tattr);
pthread_attr_xxx(...);
pthread_create(&tid, &tattr,...);
pthread_attr_destroy(&tattr);

Threadin Stack Uzunluğunun Belirlenmesi


Threadinm stack default uzunluğu sistemden sisteme değişebilmektedir. Threadin default
stack uzunluğu sisteme özgü çeşitli fonksiyonlarla alınabilir. pthread_attr_setstacksize,
pthread_attr_t yapısı içerisinde stack uzunluğunu belirler. Bugünkü Linux sistemlerinde
default stack uzunluğu en küçük 64MB dir.

Sayfa 115 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

pthread_attr_setstacksize(pthread_attr_t *attr,size_t stacksize);


pthread_attr_getstacksize fonksiyonu ile de stack genişliği elde edilir. Stack’ in adresi yani
bellekteki yeri default olarak sistem tarafından belirlenmektedir. Fakat
pthread_attr_setstackaddr fonksiyonu ile de programcı stack olarak kullanılacak alanı
belirleyebilir. Stack adresi pthread_attr_setstackaddr fonksiyonuyla da alınabilir. Ayrıca
default olarak joinable olan threadlerin joinable yada detach durumu
pthread_attr_setdetachstate fonksiyonuyla belirlenebilir.

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.

Çizelgeleme Kapsamının Değiştirilmesi


Yaratılacak bir threadin çizelgelemesi sistem genelinde yada process genelinde işleme
sokulabilir. Tüm sistem genelinde çizelgelenen threadler kendi aralarında işleme sokulur.
Process genelinde çizelgelenen threadler o processin threadleri arasındaki çizelgeleme
işlemlerinde etkili olur. Threadin çizelgeleme kapsamı pthread_attr_setscope fonksiyonu ile
set edilebilir. pthread_attr_getscope fonksiyonu ile alınabilir.
int pthread_attr_setscope(pthread_attr_t *attr,int scope);
int pthread_attr_getscope(const pthread_attr_t *attr, int scope);
pthread_attr_setscope fonksiyonunun ikinci parametresi PTHREAD_SCOPE_PROCESS
yada PTHREAD_SCOPE_SYSTEM biçiminde olabilir. pthread_create fonksiyonunda
özellik olarak NULL geçildiğinde default kapsamın ne olacağı belirlenmemiştir.
pthread_attr_init fonksiyonu default durumu özellik bilgisi olarak zaten set etmektedir.
Örneğin Linux sistemlerinde çizelgeleme kapsamı default olarak
PTHREAD_SCOPE_SYSTEM biçimindedir. Default scope şöyle elde edilebilir.
pthread_attr_init(&thread_attr);
pthread_attr_getscope(&thread_attr, &scope);
if(scope == PTHREAD_SCOPE_SYSTEM)
printf(“system scope\n”);
else(scope == PTHREAD_SCOPE_PROCESS)
printf(“process scope\n”);
else
printf(“Unknown scope\n”);

Çizelgeleme Algoritmasının Belirlenmesi


Thread çizelgeleme algoritması üç biçimde olabilmektedir.
1. SCHED_FIFO: Bu çizelgeleme biçimi gerçek zamanlı işlemler için düşünülmüştür. Bu
çizelgeleme biçiminde her threadin bir öncelik derecesi vardır. threadin öncelik derecesi en

Sayfa 116 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

azından 32 kademeli olmak zorundadır. Bu çizelgeleme sisteminde threadler arası geçiş


oluşup pre-emptive bir biçimde thread geçişi yapılmaz. Önceliği yüksek olan thread
çalıştırılır, yüksek öncelikli birden fazla thread varsa ilk çizelgeye giren threade çalışma
verilir. Threadin çalışması quanta süresiyle sınırlı değildir. bir threadin çalışmasına şu
durumlarda ara verilir.
• Threadin çalışması bittiğinde
• Thread bloke olduğunda (örneğin sleep yada usleep gibi fonksiyonlarda dahildir.)
• Sisteme daha yüksek öncelikli bir thread geldiğinde
Bu çizelgeleme algoritmasının en önemli tarafı threadin daha yüksek öncelikli thread
olmadığı sürece istediği kadar çalışabilmesidir.
2. SCHED_RR: Bu çizelgeleme biçimi Win32’ deki öncelik sınıflı döngüsel çizelgelemenin
aynısıdır. Bu sistemde aynı öncelikli threadler ayrı listelerde tutulur. En yüksek öncelikli liste
kendi arasında döngüsel çizelgelemeye tutulur. O öncelik grubundaki tüm threadler bloke
olmuş yada sonlamışsa bu kez bir alt gruba geçilir. Düşük öncelikli threadlerin çalışması bu
biçimde mümkün olabilmektedir.
3. SCHED_OTHER: Bu grup işletim sistemine özgü çizelgeleme algoritmalarını içerir.
Çizelgeleme algoritmasını değiştirebilmek için pthread_attr_setschedpolicy ve
pthread_attr_getschedpolicy fonksiyonları kullanılır.
int pthread_attr_setschedpolicy(pthread_attr_t *attr,int policy);
int pthread_attr_getschedpolicy(const pthread_attr_t *attr,int *policy);
policy parametresi SCHED_FIFO, SCHED_RR yada SCHED_OTHER olabilir.
pthread_attr_init(&thread_attr);
pthread_attr_getschedpolicy(&thread_attr, &policy);
if(policy == SCHED_FIFO)
printf(“fifo\n”);
else if(policy == SCHED_RR)
printf(“round robin\n”);
else if(policy == SCHED_OTHER)
printf(“other\n”);
else
printf(“Unknown \n”);
Threadin çizelgeleme kapsamı ve algoritması thread çalışırkende değiştirilebilir. Bu işlem
pthread_getschedparam ve pthread_setschedparam fonksiyonlarıyla yapılır.
int pthread_attr_setschedparam(pthread t target_thread,int policy, const struct
sched_param *param);
int pthread_attr_getschedparam(pthread_t target_thread,int *policy, const struct
sched_param *param);
Fonksiyonun birinci parametresi threadin ID değeridir. İkinci parametresi çizelgeleme
algoritmasını belirtir. Üçüncü parametre ise diğer parametrik bilgileri belirtmektedir. diğer
çizelgeleme parametreleri sched_param parametresiyle belirtilmiştir. sched_param yapısının
yalnızca int sched_priority; elemanı standart olarak belirlenmiştir. Yapının diğer elemanları
standart olarak belirlenmemiştir.

Sayfa 117 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Çizelgeleme Konusundaki Belirsizlikler


POSIX standartlarında çizelgeleme konusundaki davranışın büyük bölümü işletim sistemine
özgü bırakılmıştır. Örneğin, sistem düzeyinde yada process düzeyindeki çizelgelemelerin ne
anlam ifade edeceği tamamen işletim sistemi yazarlarına bırakılmıştır. Bir thread
yaratıldığında çizelgeleme konusundaki default özellikler sistemden sisteme
değişebilmektedir.

Dosya Kilitleme İşlemleri


POSIX dosya fonksiyonlarının büyük çoğunluğu atomik fonksiyonlardır. Örneğin, iki write
işlemi sistemde aynı anda gerçekleştirilmez. Özellikle veri tabanı işlemlerinde bir process
dosyanın bir bölümü ile işlem yaparken diğer processlerin dosyanın o bölümüne erişmesi
engellenmelidir. Örneğin bir kayıt birden fazla write işlemi ile güncelleniyor olsun. Bu write
işlemlerinin arasında başka bir process tarafından read işlemi yapılırsa yanlış bilgiler okunur.
Yada örneğin bir veri tabnı programında bir kaydın güncellenmesi sürecinde kayıt önce read
fonksiyonuyla okunur, kullanıcıcnın önüne getirilmeli sonra kullanıcı o kayıt üzerinde
işlemler yapar. Kaydın okunup kullanıcıya gösterilmesi işleminden sonra başka bir process o
kaydı değiştirirse güncelleme işlemi kaydın eski durumuna göre yapılmalıdır. Bu tür
paylaşımlı kayıtlara erişmek gerektiğinde kayda ilk erişen processin o kaydı kilitlemesi ve
başka bir processin o kaydın kilidi açılana kadar o kayda erişmemesi gerekir.
Genel olarak veri tabanı programları okuma yada yazma amaçlı bir kayda erişecekleri zaman
o kaydı kilitlemeleri gerekir. Kilitleme işlemi yapılmazsa veri tabanı programının yazımına
bağlı olarak kayıtlar bozulabilir yada kullanım problemleri oluşabilir. Modern işletim
sistemlerinin hemen hepsinde bir dosya kilitleme mekanizması vardır.
Dosya kilitleme işlemi dosyanın bütünü üzerinde değil iki offset arasında yapılmaktadır.
kilitleme işlemi EOF noktasından öteye doğruda yapılabilir. Örneğin, dosyaya 100 bytelık bir
kayıt eklenecekse dosyanın sonundaki 100 byte kilitlenebilir. Kayıt kilitleme işlemleri fcntl
fonksiyonu ile yapılır. Bilindiği gibi bu fonksiyon yalnızca kilitleme işlemi için değil dosya
açış modlarının alınması ve değiştirilmesi, CLOSE_ON_EXEC bayraklarının durumunun
alınması ve değiştirlmesi içinde alınmaktadır. fcntl fonksiyonunun prototipi şöyledir:
int fcntl(int fildes, int cmd, ...);
Fonksiyonun birinci parametresi dosya betimleyicisi, ikinci parametresi dosya üzerinde ne
yapılacağına ilişkin komuttur. Fonksiyon bir üçüncü parametre daha alabilmektedir. Bu
üçüncü parametrenin nasıl düzenleneceği ikinci parametredeki komuta bağlıdır. Kayıt
kilitleme işlemleri için ikinci parametrede F_SETLK yada F_SETLKW kullanılır.
Unix/Linux sistemlerinde kayıt kilitleme işlemleri zorunlu olmayan (advisory) ve zorunlu
olan (mandatory) kilitleme işlemleri olmak üzere ikiye ayrılmaktadır. Zorunlu olmayan
kilitleme işlemlerinde bir bölgenin kilitlenmiş olması read ve write fonksiyonlarını etkilemez.
Yani biz bir bölgeyi kilitlemiş olsak bile başka bir process o bölge için read ve write işlemleri
yapabilir. Fakat zorunlu olmayan kilitleme de bir bölgenin kilitlenmiş olup olmadığı fcntl
fonksiyonunda o bölgenin kilitlenmeye çalışılması sırasında anlaşılmaktadır. Bu tür kilitleme
işlemleri yalnızca aynı biçimde yazılmış bir grup process için yapılabilir. Bu tür kilitleme
meaknizmasında kayıtlara erişilmeden önce fcntl fonksiyonuyla kilitleme uygulanmalıdır.
Eğer kilit varsa işlem bir biçimde engellenmeli yoksa gerçekleştirilmelidir.

Zorunlu Olmayan Kilitleme İşlemleri


Zorunlu olamayan kilitlemeler için fcntl fonksiyonunun ikinci parametresi F_SETLK yada
F_SETLKW olarak girilir. F_SETLKW, F_SETLK işleminin blokeli biçimidir. F_SETLK ile

Sayfa 118 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Sayfa 119 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Kullanıcı ve Group ID’ leri ile ilgili Ayrıntılı Bilgiler


Bilindiği gibi /etc/passwd dosyasında kullanıcının kullanıcı ID’ si ve grup ID’ si
bulunmaktadır.(kullanıcı ID’ sine gerçek kullanıcı ID’ si, grup ID’ sine de gerçek grup ID’ si
denilmektedir.) Login programı shell programını çalıştırdığı zaman shell programının
kullanıcı ID’ si ve grup ID’ si /etc/passwd dosyasında belirtildiği gibi olmalıdır. Normal
olarak tüm test işlemlerine process etkin kullanıcı ID’ si ve grup ID’ si girmektedir. Processin
etkin kullanıcı ID’ si ve grup ID’ si normal olarak gerçek kullanıcı ID’ si ve gerçek grup ID’
sine eşittir. Fork işleminde bütün bu ID ler alt processe aktarılmaktadır. Etkin ID’ lerin gerçek
ID’ lerden farklılaşabilmesi ancak exec işlemiyle gerçekleşebilir. Exec işleminde eğer
çalıştırılacak program dosyasının SET_USER_ID bayrağı set edilmişse exec işleminden sonra
processin etkin kullanıcı ID’ si program dosyasının kullanıcı ID’ si olur. benzer biçimde
program dosyasının SET_GROUP_ID bayrağı set edilmişse exec işlemi sonrasında processin
etkin grup ID’si program dosyasının grup ID’ si olmaktadır. Görüldüğü gibi exec işlemi
gerçek ID’ ler üzerinde bir değişikliğe yol açmaz, yalnızca etkin ID’ leri değiştirebilmektedir.

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

Sayfa 120 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

/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.

Kullanıcı ve Grup ID’ lerinin Değiştirilmesi


setuid fonksiyonu process eğer root processi ise processin hem gerçek kullanıcı ID’ sini hem
etkin kullanıcı ID’ sini hemde saklanmış kullanıcı ID’ sini set eder.
int setuid(uid_t uid);
Örneğin login programı password doğrulamasını yaptıktan sonra önce bir fork işlemi uygular,
alt processte setuid fonksiyonunu uygulayarak bütün ID’ leri /etc/passwd dosyasında
belirtildiği duruma çeker.
setuid fonksiyonu root dışındaki herhangi bir kullanıcı tarafından çağırılıyorsa eğer
fonksiyonu çağıran processin gerçke kullanıcı ID’ si yada saklanmış kullanıcı ID’ si
fonksiyonun parametresiyle belirtilen ID’ ye eşit ise processin etkin kullanıcı ID’ sini bu
değere çekmektedir. Yani bu fonksiyon ile sıradan bir kullanıcı için etkin kullanıcı ID’ si
gerçek kullanıcı ID’ sine yada saklanmış kullanıcı ID’ sine çekilebilir. Yani biz herhangi bir
kullanıcı olarak etkin kullanıcı ID’ mizi A’ ya çekmek isteyelim. Bu işlemi başarabilmemiz
için ancak bizim gerçek kullanıcı ID mizin yada saklanmış kullanıcı ID mizin A olması
gerekir. Yani biz setuid fonksiyonuyla etkin kullanıcı ID mizi herhengi bir ID yapamayız.
setgid fonksiyonu da tamamen aynı biçimde çalışmaktadır. Yani fonksiyonu root çağırıyorsa
bu fonksiyon processin gerçek grup ID sini hem etkin grup ID sini hemde saklanmış grup ID
sini değiştirmektedir. Fakat herhangi bir kullanıcı bu fonksiyonla yalnızca etkin grup ID sini
eğer değiştrimek istediği ID kendisinin gerçek grup ID si yada saklanmış grup ID si ise
değişikliği yapabilmektedir.

Saklanmış Kullanıcı ID’ si ve Saklanmış Grup ID’ sinin Anlamı


Saklanmış kullanıcı ID si ve saklanmış grup ID si tamamen şu senaryo için düşünülmüştür.
Kullanıcı ID miz A olsun. Biz setuid bayrağı set edilmiş B’ nin bir programını çalıştıralım.
Çalıştırılan programda etkin kullanıcı ID’ si B olacak ve artık erişim testine bu değer
girecektir. Yani çalıştırılan program A’ nın dosyalarına erişemeyecektir. A’ nın dosyalarına
erişebilmek için setuid fonksiyonuyal etkin kullanıcı ID’ si A’ ya dönüştürülebilir. Fakat etkin
kullanıcı ID’ si bir yerde saklanmazsa bir daha B durumuna dönüş mümkün olmaz. Daha açık
bir anlatımla;
1. Başlangıç durumu:
id = euid = suid =A
2. B’ nin bir programı fork ve exec ile çalıştırılır. Çalışma işleminden sonraki
durum:
uid = A
euid = suid = B
3. Çalıştırılan program A’ nın dosyasına erişemiyor. Erişmek için şunu yapıyor:
setuid (A);
yeni durum
uid = A
euid = A
suid = B
4. Çalıştırılan program şimdi ilk duruma dönmek için şu yapılabilir:
setuid (B);

Sayfa 121 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Unix/Linux Sistemlerinin Başlatılma Süreci


Sistem açıldığında boot işlemiyle birlikte ilk yaratılan process işletim sistemini temsil eden ve
genellikle ismine swaper yada pager denilen processtir. Bu processin process ID’ si 0 dır, 0 ID
li bu process fork yaparak bir numaralı ID ye sahip olan init processini yaratmaktadır. İnit
processi üst penceresi yok olmuş tüm processlere üst processlik yapmaktadır. İnit processinin
kullanıcı ID’ si etkin kullanıcı ID’ si ve diğer tüm ID leri 0 yani root biçimindedir.

Sayfa 122 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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

getty getty getty

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 login login

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

Sayfa 123 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

şifreler ve /etc/passwd yada /etc/shadow dosyasına başvururlar. Login password


doğrulamasını yaparsa önce fork ile bir alt process oluşturur. Alt processte setuid
fonksiyonunu çağırarak gerçek kullanıcı ID’ sini, etkin kullanıcı ID’ sini, saklanmış kullanıcı
ID’ sini ve setguid fonksiyonuyla gerçek grup ID’ sini, etkin grup ID’ sini ve saklanmış grup
ID’ sini passwd dosyasındaki kullanıcı için belirlenen ID’ lerle set eder. Login alt processte
bu işlemlerden sonra exec yaparak passwd dosyasında belirtilen shell programını çalıştırır.

swapper

init

getty getty getty

login login login

shell shell shell

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

Sayfa 124 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

tarafından belirlenmiştir. Fonksiyonun diğer parametreleri komuta bağlı olarak


değişebilmektedir. Örneğin CD-ROM’u eject etmek isteyelim. Eject işlemi bunu yapabilmek
için CD-ROM aygıtını yöneten yöneticiye komut göndermek gerekir.
int fd;
...
fd = open(“/dev/hdc”, O_RDONLY);
...
ioctl(fd, CDROMEJECT);
...
close(fd);
Standart aygıt sürücülere ilişkin komut kodları <sys/ioctl.h> içerisindedir.
Örneğin, Unix/Linux sistemlerinde seri port işlemlerini yapacak olalım. programcı doğrudan
UART işlemcisinin haberleşme portlarına erişemez. Fakat /dev dizini altında bu işlemler için
bir aygıt sürücü bulunmaktadır. o halde tipik olarak işlemler şöyle yapılır.
1. Seriporta ilişkin aygıt open fonksiyonuyla açılır.
2. ioctl çağırmalarıla fonksiyon set edilir.
3. read fonksiyonu ile seriporttan bilgi alınır, write fonksiyonuyla bilgi yazılır.
Bazı sistemlerde haberleşme portlarına in-out işlemi yapabilmek için basit bir aygıt sürücü
bulundurulur. Unix sistemlerinde /dev/port aygıt dosyası genel olarak haberleşme portlarına
in-out yapmak için kullanılır. Programcı bu dosyayı open fonksiyonuyla açar, sonra write
fonksiyonuyla porta bilgi göndeririz.
Şüphesiz /dev/port dosyası yalnızca root tarafından kullanılabilen bir dosyadır. Yani bu
dosyanın sahibi roottur ve biz normal bir kullanıcı olarak bu dosyayı açmaya çalışırsak open
fonksiyonu başarısızlıkla sonuçlanır. Şüphesiz root kullanıcısı isterse bu dosyanın erişim
haklarını başka kişilere açabilir. Programcı portlara erişen programı root hakkıyla sisteme
girip yazabilir ve sonra programın SET_USER_ID bayrağını set edebilir. Böylece sıradan bir
kullanıcı da yazılan yaralı programı kullanabilecektir.
NCURSES-programming-HOWTO.pdf

Örneğin, 3F8 numaralı porta değer yazmak isteyelim;


if((fd = open(“/dev/port”, O_RDWR)) < 0){
perror(“open”);
exit(1);
}
lseek(fd, 0x3F8, SEEK_CUR);
write(fd, &val, 1);
...
close(fd);
Unix/Linux Sistemlerinde Konsol Ekran İşlemleri ve Curses Kütüphanesi
Konsol ekranında çalışan ticari programlarda pencereler, menüler gibi pekçok görsel öğeler
kullanılmaktadır. Standart C fonksiyonlarıyla bu işlemleri gerçekleştirmenin bir yolu yoktur.
Dos’ ta bu tür işlemler doğrudan ekran belleği kullanılarak aşağı seviyeli bir biçimde
yapılabilmektedir. Halbuki Unix/Linux sistemleri korumalı modda çalıştıkları için sıradan bir
programcı aygıt sürücü yazmadan ekran belleğine erişemez. İşte bu tür işlemleri yapmayı
sağlayan çeşitli standart kütüphaneler düşünülmüştür.

Sayfa 125 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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↵

Curses İşlemlerinin Başlatılması ve Bitirilmesi


Curses kütüphanesi kullanılmadan önce programcı initscr fonksiyonunu çağırmalı işlemi
bitince de endwin fonksiyonu ile oturumu kapatmalıdır.
WINDOW *initscr(void);
int endwin(void);
Curses kütüphanesinde işlemler pencere kavramı esas alınarak yapılır. İşlemlere başlarken
ekranın tamamı bir penceredir. Bir pencerenin handle değeri WINDOW yapısıyla temsil
edilir. Ekranın tamamı da (windows’ un masaüstünde olduğu gibi) bir penceredir. initscr
fonksiyonu ekranı temsil eden bu pencerenin handle değerini parametre olarak vermektedir.
Curses fonksiyonlarının bir grubu “w” ön ekiyle başaltılmıştır. “w” ön ekiyle başlatılan bu
fonksiyonlar belirli bir pencere üzerinde işlem yaparlar. “w” ön ekiyle başlatılmayan
fonksiyonlar ana pencere üzerinde işlem yaparlar. stdscr her zaman ana pencereyi temsil eden
bir handle belirtir. initscr fonksiyonu başarısız olduğunda NULL değerine geri döner.

Cursor’ ın Taşınması
Cursor’ın taşınması için move ve wmove fonksiyonları kullanılır.
int move(int x, int y);

Sayfa 126 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

int wmove(WINDOW *window, 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.

Cursos Fonksiyonlarının Geri Dönüş Değerleri


Cursos fonksiyonlarının pek çoğunun geri dönüş değeri int biçimindedir. Fonksiyonların
genellikle geri dönüş değerleri ya ERR yada OK biçimindedir. ERR hatayı, OK başarıyı
temsil etmektedir.

Klavyeden Karakter Alan Temel Fonksiyonlar


Genel olarak curses fonksiyonlarında “mv” önekli fonksiyonlar x ve y koordinat bilgilerini
alıp, önce cursor’ ı taşıyıp daha sonra işlemi yapmaktadır. Klavyeden karakter alan temel
fonksiyonlar şunlardır:
int getch(void);
int wgetch(WINDOW *win);
int mvgetch(int y, int x);
int mvwgetch(WINDOW *win, int y, int x);
int ungetch(int ch);
int has_key(int ch);
getch fonksiyonları default olarak enter tuşuna gereksinim duymaz. Enter tuşuna gereksinim
duyup duymayacağı cbreak ve nocbreak fonksiyonlarıyla belirlenmektedir. nocbreak
fonksiyonu enter tuşuna gereksinim duyulmasını sağlar, cbreak fonksiyonu duyulmamasını
sağlar. getch grubu fonksiyonlarla özel tuşlar okunabilir. Özel tuşlar için bu tuşları anlatan
KEY_XXX biçiminde sembolik sabitler tanımlanmıştır. Örneğin, KEY_HOME, home
tuşunu; KEY_DOWN, alt ok tuşunu temsil etmektedir. Bu tuşalar ilişkin sembolik sabitler
için man getch biçiminde yardım alınabilir. getch grubu fonksiyonların verdikleri geri dönüş
değerinin düşük anlamlı byte’ ında basılan kodun ASCII kodu bulunmaktadır. F tuşları
KEY_F(n) makrosu ile ifade edilmektedir. Örneğin;
if(ch == KEY_F(1)){
...
}
Giriş Fonksiyonlarının Yansıma Modu
Default olarak klavye giriş fonksiyonları basılan tuşu aynı zamanda ekranda göstermektedir.
Fakat istenirse echo ve noecho fonksiyonlarıyla bu durum açılıp kapatılabilir.

Sayfa 127 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Ekrana Formatlı Yazma Yapan Fonksiyonlar


printw fonksiyonu tamamen printf fonksiyonu gibi çalışan bir curses fonksiyonudur. Bu
fonksiyonunun “w” ve ”mv” önekli biçimleri de vardır.

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);

Ekrana Diğer Yazma Yapan Fonksiyonlar


printw fonksiyon ailesinin dışında addch fonksiyonu cursor’ ın bulunduğu yere bir karakter
yazmakta addstr fonksiyonu ise cursor’ ın bulunduğu yere bir string yazmakta kullanılabilir.
Bu fonksiyonlarında “w” ve “mv” önekli biçimleri vardır.

Klavyeden Okuma Yapan Diğer Fonksiyonlar


Aslında curses kütüphanesinde standart C fonksiyonlarının benzerlerinin birer curses
biçimleri vardır. Örneğin scanf yerine scanw grubu fonksiyonlar, gets yerine getstr
fonksiyonu bulunmaktadır.

Monochrome Ekranda Özellikler


Bilindiği gibi monochrome ekranlarda renk kavramı bulunmamakla birlikte bold, blink,
reverse gibi özellikler vardır. Tek bir özelliği açmak için attron, kapatmak için ise attroff
fonksiyonunu kullanırız. attrset fonksiyonu ise özellik kümesini tümden değiştirmektedir.
int attron(int attrs);
int attroff(int attrs);
int attrset(int attrs);
Bu fonksiyonların “w” ile başlayan pencereli biçimleri de vardır. Değiştirilecek özellikler
şunlardır:
A_NORMAL, A_STANDART, A_UNDERLINE, A_REVERSE, A_BLINK, A_DIM, A_BOLD,
A_PROTECT, A_INVIS, A_ALTCHARSET, A_CHARTEXT.
Örneğin bir yazıyı bold göstermek için çöyle yapılabilir:
attron(A_BOLD);
printw(“Deniz”);
attroff(A_BOLD);
Renkli Yazdırma İşlemleri
Renkli yazdırma işlemleri için öncelikle sistemin ve monitörün genel olarak renkli yazımı
destekleyip desteklemediği sorgulanmalıdır. Buınun için has_colors fonksiyonu kullanılır.

Sayfa 128 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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);

Sayfa 129 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Çerçeve Çizme İşlemleri


box fonksiyonu ile çerçeve çizilebilir.
int box(WINDOW *win, chtype verch, chtype horch);
Ayrıca hline ve vline fonksiyonları sırasıyla yatay ve düşey çizgi çizmekte de kullanılır.

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
}
}

Diğer Curses Kütüphaneleri


Yukarıda açıklanan temel curses kütüphanesi dışında curses kütüphanesinin menü işlemlerini
yapan form işlemlerini yapan başka bölümleri de vardır.

Unix/Linux Sistemlerinde Statik ve Dinamik Kütüphaneler


Unix/Linux sistemlerinde statik kütüphaneler .a (archive) uzantılı dosyalardır. Kütüphane
oluşturabilmek için ar isimli program kullanılmaktadır. ar programının genel kullanımı
şöyledir:
ar<_seçenek> <.a dosya ismi> <.o dosya ismi>
Kütüphaneye bir .o dosyasını ekleme işlemi şöyle yapılabilir.
ar -r mylib.a x.o
Bir kütüphane dosyasını link aşamasına dahil etmek için ld programında belirleme yapılabilir.
Fakat zaten gcc programı uzantısı .a ve .o olan dosyaları ld linkerını çağırırken ona
iletmektedir. Bu durumda örneğin yukarıda oluşturulan mylib.a kütüphanesi aşağıdaki gibi
link işlemine sokulabilir.
gcc –o test test.c mylib.a ↵
Unix/Linux sistemlerinde uzun süredir dinamik kütüphanelerde kullanılmaktadır. dinamik
kütüphanelerin kullanımı windows win32 sistemlerine benzer. Unix/Linux sistemlerinde de
dinamik kütüphanelerin program çalışırken yada çalışma zamanı sırasında yüklenmesi
mümkündür. Unix/Linux sistemlerinde dinamik kütüphanelerin uzantısı .sa(shared archive)
biçimindedir.

Sayfa 130 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Dinamik Kütüphanelerin Oluşturulması


Dinamik kütüphaneler gcc derleyicisinde –shared seçeneği kullanılarak oluşturulabilir.
gcc –o mylib.sa -shared mylib.c↵
Görüldüğü gibi –shared seçeneğiyle gcc derleyici sistemi önde dosyayı derlemekte ve sonra
dinamik kütüphane oluşturarak derlenmiş dosyayı kütüphane içerisine yerleştirmektedir.
Unix/Linux sistemlerinde win32 sistemlerinde olduğu gibi import kütüphanesi kavramı
yoktur. Dinamik kütüphanenin kendisi aynı zamanda import kütüphanesi yerinede
geçmektedir. Link işlemi için doğrudan sa dosyası kullanılır. Örneğin: gcc –o test test.c
mylib.sa
Unix/Linux sistemlerinde dinamik kütüphanelerin özel dizinlerde olması lazımdır. Linux
sistemleri önce LD_LIBRARY_PATH isimli çevre değişkeni ile belirtilen dizinde aramayı
yaparlar. Dinamik kütüphaneyi burada bulamazlarsa bu kez sırasıyla /usr/lib ve /lib
dizinlerine bakarlar. Ancak dinamik kütüphanenin arandığı dizinler sistemden sisteme
değişebilir. Bir kütüphaneyi gcc derleyicisi ise static yada dinamik kütüphane dosyaları
oluşturmak içinderleme yaparken kütüphane dosyaları ya doğrudan komut satırınada yazılır
yada bunun için –l seçeneği kullanılır.
gcc –o test test.c libmylib.a
gcc –o test test.c –lmylib.a
-l seçeneğinde dosyanın isminin başındaki lib yazılmaz yani –lx belirtmesi ile libx.a yada
libx.sa dosyaları araştırılır. Kütüphane dosyasının isminin doğrudan yazılması sırasında path
yada yol ifadesi kullanıcı tarafından belirtilmesi gerekir. Halbuki –l seçeği ile /usr/lib ve /lib
dizinlerine otomatik olarak bakılır. –L bu dizinlere ek olarak başka dizinlerde aranmasını
sağlar. Örneğin libmylib.a bulunduğumuz dizinde olsun aşağıdaki işlemde dosya
bulunamayacaktır.
gcc-o test –lmylib test.c
gcc –o test –L. –lmylib test.c

Dinamik Kütüphanelerin Dinamik Yüklenmesi


Unix/Linux Sistemlerindede bir dinamik kütüphane program çalıştırıldıktan sonra
istenildiği bir zaman yüklenebilir. Kütüphanelerin dinamik yüklenmesi için şunlar
yapılmalıdır.
1- yükleme işlemi dlopen fonksiyonu ile yapılır. (Bu fonksiyon win32 sistemlerindeki
LoadLibrary fonksiyonunun karşılığıdır)
2- Dinamik kütüphanenin içerisindeki fonksiynun adresi dlsym fonksiyonu ile elde
edilir. (bu fonksiyon win32’deki get proc adres fonksiyonunun karşılığıdır)
3- Kütüphane dlclose fonksiyonu ile adres alanından silinir. Bu fonksiyon win32
sistemlerindeki FreeLibrary fonksiyonunun karşılığıdır.
void *dlopen(const char *file, int mode);
void *dlsym(void *handle, const char *name);
int dlclose(void *handle);

Sayfa 131 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

dlopen fonksiyonunun 1. Parametresi yüklenecek dinamik kütüphanenin yol ifadesidir. Eğer


buradaki yol ifadesi / ile başlatılmadıysa yani göreli ise önce LD_LIBRARY_PATH çevre
değişkeni ile belirtilen dizinlerde sonra /lib ve /usr/lib dizinlerine bakılır.
Fonksiyonun ikinci parametresi RTLD_LAZY yada RTLD_NOW olabilir. Bu parametre
normal olarak RTLD_NOW olarak girilir. Bu durumda sembol çözümlemesi hemen
yapılmaktadır. Fonksiyonun geri dönüş değeri dinamik kütüphanenin sanal belleğe yüklenme
adresidir. Bu adres değeri handle olarak kullanılır.
Kütüphane yüklendikten sonra programcı dlsym fonksiyonunu kullanarak dinamik kütüphane
içerisindeki çağırmak istediği fonksiyonun adresini elde eder. Bu adresi programcı bir
fonksiyon göstericisine atamalı ve bu gösterici yoluyla fonksiyonu çağırmalıdır. Nihayet
kullanım işi bittikten sonra dinamik kütüphane dlclose fonksiyonu ile kapatılır.
Bu fonksiyonların prototipleri <dlfnc.h> dosyasının içerisindedir. Dinamik yükleme
fonksiyonları libdl.a dosyası içerisindedir. Dolayısıyla gcc derleyicisinde derleme yaparken –
ldl parametresi ile derleme yapılması gerekir.

Proc Dosya Sistemi


Unix/Linux sistemlerinde sistem hakkında detaylı bilgiler edinmeye yarayan POSIX
fonksiyonları yoktur. Örneğin bu sistemlerde çalışan processlerin listesini almakta
kullanılabilecek taşınabilir fonksiyonlar yoktur. İşte sistem hakkında bilgilerin sanki bir
dosyadan okunuyormuş gibi elde edilebileceği bir yapı düşünülmüştür. Proc dosya sistemi
kök dizinin altında /proc dizini içerisinde organize edilen bir dosya sistemidir. Proc sistemi
disk tabanlı gerçek bir dizin değildir. Sistem açılırken bu dizin ve içerisindeki dosyalar
bellekte oluşturulmaktadır. Fakat buradaki dosyalar open, read, close gibi sistem
fonksiyonlarıyla işleme tabi tutulabilir. Proc dosya sistemi henüz POSIX standartlarına
eklenmemiş olsada yeni pek çok sistem tarafından aynı biçimde desteklenmektedir. Proc
dosya sistemi içerisindeki dizinlerin ve dosyaların ne anlama geldikleri ve bu dosyalar
içerisindeki yazıların nasıl organize edildikleri dökümante edilmiştir. Bu durumda programcı
bu dizin içerisindeki dosyayı open fonksiyonu ile açabilir. Read fonksiyonu ile dosyanın
içeriğini okuyarak parse edilebilir.
Proc dosya sistemindeki önemli girişler şunlardır.
- cpuinfo o an çalışılan makinadaki işlemci hakkında bilgi verir.
- filesystems işletim sistemi tarafından desteklenen dosya sistemlerinin neler
olduğunun bilgisini tutar.
- kcore o anda kernel bellek alanını göstermektedir.
- meminfo sistemin bellek kullanımı hakkında temel bilgileri verir.
- partitions harddiskin bölümlemeleri hakkında bilgi verir.
- version işletim sisteminin versiyonu hakkında bilgi vermektedir.
- Sistemde her process taratıldığında /proc dizini altında o processin ID’si ile aynı
isimli olacak biçimde bir dizin yaratılır. Bu dizin içerisinde process hakkında detaylı bilgi
veren dosyalar bulunmaktadır. Örneğin bir process dizini içerisindeki mem dosyası o
processin kullandığı bellek miktarını, fd dizini açılmış olan dosya betimleyicileri hakkında
bilgi verir.
- cmdline programın çalıştırıldığı komut satırı argumanlarını belirtmektedir. Status
dosyası ise process hakkında çok çeşitli bilgileri vermektedir.

Sayfa 132 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

- /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.

Linux Sistem Fonksiyonlarının Çağırılması


Sistem foksiyonu kavramı ile POSIX fonksiyonları farklı kavramlardır. Sistem fonksiyonu
kavramı genel olarak işletim sisteminin içerisinde bulunan ve kernel moda geçilerek
çalıştırılan kodlar içinde bulunmaktadır. POSIX fonksiyonları aslında birer kütüphane
fonksiyonudur. Bu fonksiyonlar çeşitli sistem fonksiyonlarını çağırarak işlem yapmaktadır.
Her Unix sisteminin sistem fonksiyonları birbirinden farklı olabilmektedir. Linux sistem
fonksiyonlarının tüm listesi çeşitli kaynaklarda verilmektedir.
Linux kaynak kodlarında sistem fonksiyonlarının hepsi sys_ önekiyle başlatılmıştır.
Linux sistemlerinde her sistem fonksiyonunun numarası vardır. Örneğin, sys_exit fonksiyonu
bir numralaı fonksiyon, sys_fork fonksiyonu iki numaralı fonksiyondur. Linux sistemlerinde
tüm sistem fonksiyonları 80h yazılım kesmesiyle çağırılır. Bu kesme çağırılmadan önce eax
yazmacına çağırılacak sistem fonksiyonunun numarası yerleştirilir. Diğer parametreler
sırasıyla ebx, ecx, edx, esi, edi yazmaçlarına yerleştirilir. İntel işlemcilerinin bulunduğu PC’
lerde 80h kesmesine bir tuzak kapısı yerleştirilmiştir. Böylece kesmeye girildiğinde process
otomatik olarak kernel moda geçer. 80h kesmesine yerleştirilen kod Linux kaynak
kodlarındaki system_call isimli fonksiyondur. Tüm sistem fonksiyonlarının adresleri bir
dizide saklanmıştır. 80h kesmesinin girişinde eax yazmacının değeri bu diziye index yapılarak
uygun sistem fonksiyonu çağırılır.
Sistem fonksiyonları programcı tarafından nasıl çağırılır? Sistem fonksiyonu çağırmanın en
pratik yolu basit bir sembolik makine dili kullanmak olabilir. Örneğin, nasm derleyicilerinde
sistem fonksiyonunu çağıran örnek bir program şöyle yazılabilir:
[BITS 32]
[section.data]
message db “bu bir denemedir”
[section.text]
GLOBAL start;
start:
mov eax, 4
mov ebx, 1
mov ecx, message
mov edx, 16
int 0x80
mov eax, 1
mov ebx, 0
int 0x80
Bu program şöyle derlenebilir.
nasm –f elf sample.asm ↵
ld –o sample –e start sample.o ↵
Burada görüldüğü gibi sembolik makina dili programı önce nasm derleyicisinde derlenmistir,
sonra ld ile link edilmiştir. Nasm(netwide assebmly) hem dos hem windows hemde linux
versiyonları olan bedava bir sembolik makina dili derleyicisidir. Linux sistemlerinde binutil
paketinde bulunmaktadır. Nasm derleyicisi pekçok object modül formatını desteklemektedir.
Linux temel olarak ELF formatını kullanmaktadır. Derleme işlemindeki –f elf üretilecek

Sayfa 133 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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 ↵

MAKE Dosyalarının Oluşturulması


Büyük projeler tek modül halinde yazılmazlar. Projeyi pekçok .c dosyası oluşturabilir. Çok
modüllü çalışmalarda her defasında tüm dosyaların derlenmesi yerine yalnızca değişmiş olan
dosyanın derlenmesi tercih edilir. Yalnızca değişen dosya derlendikten sonra tüm derlenmiş
olan .o dosyaları hep birlikte link edilir.
Tüm kaynak dosyalar gcc programıyla derlenerek birlikte link edilebilir.
gcc –o test a.c b.c c.c ↵

Sayfa 134 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

Bu durumda gcc derleyicisi bu dosyaların hepsini derleyecek ve birlikte link işlemine


sokulacaktır. Fakat istenen şey yalnızca değişmiş olan dosyaların derlenmesidir. Yalnızca
değişmiş olan dosyaların derlenmesi için derleyicilerin tümleşik çevreli versiyonların proje
dosyası kavramı kullanılmaktadır. bu işlemi gerçekleştirmek için komut satırı uyarlamalarında
MAKE dosyası kavramı kullanılmaktadır.
MAKE dosyası programcı tarafından oluşturulmuş bir text dosyadır. Bu dosyanın içerisinde
hangi dosyalarda değişiklik olduğunda nelerin yapılması gerektiği basit bir sentaksa göre
belirtilmektedir. MAKE dosyasını alarak işlemleri yapan make isimli bir program vardır.
Make programı parametresiz olarak çalıştırılırsa make dosyası olarak sırasıyla Makefile ve
makefile isimli dosyaları arar. İstenilen bir dosyanın make işlemine sokulabilmesi için –f
kullanılabilir.
make –f dosya_ismi ↵
Make dosyalarının oluşturulma biçimi ve genel sentaksı dos, Windoes, Unix sistemlerinde
birbirine benzerdir. Fakat bazı farklar bulunmaktadır. Programcı hangi sistemde çalışıyorsa ve
hangi derleyici ailesiyle çalışıyorsa onun make dosyası oluşturma sentaksını incelemelidir.
Make dosyaları kural (rule) denilen cümleciklerden oluşturulur. Bir kuralın genel biçimi
şöyledir:
dosya_isimleri: dosya isimleri bağımlılık(dependency)
yapılacak işlem
Bağımlılık (dependency) satırında : sembolünün solundaki dosyalar sağındaki dosyalara
bağımlıdır. Make programı : sembolünün sağındaki dosyaların herhangi birinin tarihi,
solundaki dosyaların herhangibirinin tarihinden yaniyse aşağıdaki satırda belirtilen işlemi
yapar. Örneğin;
factorial.o: factorial.cpp
g++ -c factorial.cpp
Burada factorial.cpp dosyasının tarihi factorial.o dosyasının tarihinden daha yeniyse bu dosya
yeniden derlenmiştir. Kısacası dosya değişmişse yeniden derleme işleminin yapılması
istenmiştir.
Make dosyası birbirleriyle bağlantılı kurallar içerir. Make programı önce kurallar arasındaki
bağlantıyı çözümler daha sonra işlemini gerçekleştirir. Örneğin:
hello: main.o factorial.o hello.o
g++ main.o factorial.o hello.o –o hello
main.o: main.cpp
g++ -c main.cpp
factorial.o: factorial.cpp
g++ -c factorial.cpp
hello.o: hello.cpp
g++ -c hello.cpp
Burada birinci satır diğer satırlara bağımlıdır. Örneğin hello.cpp değiştirildiğinde yeniden
hello.o elde edilir. bu işlem birinci satırı tetikler ve yeniden link işlemi yapılır.

Sayfa 135 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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

Veri Tabanı İşlemleri


Veri tabanı kütüphanesi (database library) ile veri tabanı yönetim sistemi (database
management system) farklı kavramlar olarak kullanılmaktadır. veri tabanı yönetim sistemi
tamamen yüksek seviyeli araçlar içeren pekçok data formatını destekleyen SQL gibi bir
sorgulama aracına sahip olan genel ürünlerdir. Halbuki veri tabanı kütüphaneleri belirli bir
data formatı üzerine işlem yapan aşağı seviyeli sistemlerden oluşur. SQL veri tabanı
sistemlerinde standart hale getirilmiş bir sorgulama dilidir. SQL veri tabanı işlemlerini yapan
gerçek bir araç değildir. bu dil yalnızca ne yapılmak istendiğinin ifade edildiği bir dildir.
Yapılmak istenenler yorumlandıktan sonra veri tabanı yönetim sisteminin motoru (engine)
diye isimlendirilen aşağı seviyeli C’ de yazılmış fonksiyonlar devreye sokulur. Oracle, SQL
Server, Sysbase, Progress gibi ürünler birer veri tabanı yönetim sistemi ürünleridir. Halbuki C
tree _dbvista, btrieve, db gibi kütüphaneler birer veri tabanı kütüphanesidir.
Unix/Linux sistemlerinde kullanılan en basit veri tabanı kütüphanesi db kütüphanesidir. db
kütüphanesi ilk kez BSD sistemlerinde kullanılmaya başlanmıştır. Bu kütüphane klasik sıralı
erişim, hashing yada Btree algoritmalarını kullanacak biçimde tasarlanmıştır. Linux
sistemlerinde kütüphane için “/db1/db.h” başlık dosyası kullanılmaktadır. kütüphanedeki
fonksiyonlar “/lib/libdb1.a” yada “/lib/libdb1.so” isimli dosyalardan kullanılır. yani bu
kütüphaneyi kullanacak olan programcılar “db1/db.h” dosyasını include etmenin yanı sıra link
işlemi için –ldb1 seçeneğini kullanmaları gerekir.

Sayfa 136 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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.

Kayıtların Elde Edilmesi


Kayıtların okunması için seq fonksiyonu kullanılır.
int (*seq)(const DB *db, DBT *key, DBT *data, u_int flags);

Sayfa 137 / 138


C ve Sistem Programcıları Derneği – Unix/Linux Sistem Programlama Ders Notları(Kaan Aslan) –
www.csystem.org

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

Sayfa 138 / 138

You might also like