Yorumlama Dosyalarının exec İle Çalıştırılması
Yorumlama dosyasının ismi x olsun ve bu dosyanın ilk satırında
#! /home/kaan/y str3 str4
olsun. x dosyası aşağıdaki gibi exec yapılmış olsun.
execl(“/home/kaan/x”, “str1”, “str2”, NULL);
Bu işlem sonrasında /home/kaan/y çalıştırılır. Y programın komut satırı argümanları şöyle
olur:
argv[0]: /home/kaan/y
argv[1]: str3 str4
argv[2]: /home/kaan/x
argv[3]: str1
argv[4]: str2
Görüldüğü gibi çalışabilen y programına argv[1] olarak yorumlama dosyasının ilk satırındaki
argümanların hepsi birlikte geçirilmiştir.
Sınıf çalışması: Bir x text dosyası oluşturunuz. Bunun birinci satırına
#! /home/.../y [l] ya da [s]
yazınız. Sonra bu dosyanın içerisine her satırına bir yazı yazınız. Birinci satırdaki seçenek l
ise satırlardaki bilgiyi satır satır yazdırınız, s ise yan yana yazdırınız. x dosyasını chmod
komutuyla çalışabilir hale getiriniz. x’i shell üzerinde çalıştırınız.
signal Kavramı
signal Unix/Linux programlamanın en önemli konularından biridir. signal bir çeşit yazılım
kesmesidir. signal bir takım olaylar oluştuğunda işletim sisteminin process’in belirlenmiş olan
bir fonksiyonunu çağırması durumudur. signal mekanizması içerisinde belirlenen fonksiyon
asenkron bir biçimde çağırılır. Yani örneğin işletim sistemi A process’ini çalıştırıyor olsun. O
anda B process’ine ilişkin bir signal durumu bildirilsin. Muhtemelen işletim sistemi
çalışmakta olan A process’ine ara verecek, B process’ine geçecek, ancak B process’inde
kalınan yerden devam etmeyecek, yalnızca belirtilen fonksiyonu çağıracaktır. Bundan sonra
işletim sistemi kendisinin uygun gördüğü çizelgeleme algoritmasıyla devam edecektir.
Aslında bir process çalıştırılırken başka bir process için signal oluştuğunda işletim sisteminin
nasıl davranacağı standart olarak belirtilmemiştir. Ancak sistemlerin çoğu signal işlemlerini
bir an önce gerçekleştirmek için process’ler arası geçiş yaparlar.
signal Oluşturan Durumlar
signal temel olarak üç durum karşısında oluşturulur.
1) Klavye yoluyla: Unix sistemlerinde terminallerdeki bazı özel tuşlar bazı signal’lerin
oluşmasına yol açar. Örneğin tipik olarak Ctrl+C tuşu SIGINT signal’inin oluşmasına neden
olur. Benzer biçiminde genellikle Ctrl+Z tuşu TSTP (task stop) signal’ini, Ctrl+\ tuşu ABRT
signal’ini oluşturmaktadır. Bu signal’leri oluşturan tuşlar Unix'te sistemden sisteme
değişebilir. En iyi yöntem stty –a komutuyla kullanılan terminal için bu tuş
kombinasyonları görmektir. İşletim sistemlerinin çoğunda klavyeden hareketle signal benzeri
bir çağırma yapan mekanizmalar bulunur. Örneğin, DOS sisteminde klavyeden okuma yapan
bazı DOS fonksiyonları Ctrl+C’ye basıldığında Control Break denilen bir kesmeyi
çağırmaktadır.
2) Shell üzerinden signal yollamak: Shell üzerinden kill komutu uygulanarak signal
yollanabilir. Kill komutunun genel biçimi şöyledir:
kill –signal <process id>
Her signal’in bir sembolik ismi vardır. Örneğin,
kill –ABRT 1003
Komutu process ID’si 1003 olan process’e ABRT signal’ini gönderir. – belirleyicisinden
sonra sembolik isim yerine doğrudan signal numarası da kullanılabilir. Eğer signal kısmı hiç
yazılmazsa default olarak process’e TERM signal’i gönderilir. Bu da process’i sonlandırır.
Sınıf çalışması: Bir console’dan getchar ile bekleme yapan basit bir C programı yazınız.
Başka bir console açarak o process’i kill komutuyla yok ediniz.
Bazı shell programlarında fg gibi bir komut suspend edilmiş bir process’i CONT signal’ini
göndererek devam ettirmektedir.
3) Sistem fonksiyonları kullanarak signal göndermek: Signal göndermekte kullanılan en
önemli iki sistem fonksiyonu kill ve raise fonksiyonlarıdır. kill fonksiyonu herhangi bir
process’e signal gönderebilirken, raise kendi process’ine signal gönderebilmektedir. Zaten
shell üzerinde kill komutunu uyguladığımızda kill fonksiyonunu çağırılmaktadır.
kill ve raise Fonksiyonları
kill fonksiyonu process ID’si bilinen bir process’e mesaj göndermekte kullanılır. Prototipi
<signal.h> içerisindedir.
int kill(pid_t pid, int signal);
Fonksiyonun birinci parametresi signal gönderilecek process’in ID’si, ikinci parametresi
gönderilecek signal’in numarasıdır. Signal numaraları <signal.h> dosyası içerisinde SIGXXX
biçimindeki sembolik sabitler olarak define edilmiştir. Fonksiyon başarılıysa 0 değerine,
başarısızsa –1 değerine geri döner. root her process’e signal gönderebilir. Normal bir
kullanıcı, kullanıcı ID’si ya da etkin kullanıcı ID’si aynı olan process’lere mesaj gönderebilir.
Örneğin process’in kullanıcı ID’si 10 etkin kullanıcı ID’si 20 olsun. Biz bu process içerisinde
kill fonksiyonu ile kullanıcı ID’si 10 ya da 20, etkin kullanıcı ID’si 10 ya da 20 olan
process’lere mesaj gönderebiliriz. Örneğin bir process’imiz kilitlenmiş olsun ve onu
sonlandırmak isteyelim. Başka bir console’dan girerek kill komutuyla process’imizi yok
edebiliriz.
raise fonksiyonu şöyledir:
int raise(int signal);
raise kendi process’ine ilgili signal’i gönderir.
raise(signal);
kill(getpid(), signal);
ifadeleri benzerdir, ama aynı değildir. kill fonksiyonundaki pid değeri dört seçenekten biri
olabilir.
1) pid > 0 : Bu durumda signal belirtilen ID’ye ilişkin process’e gönderirilir.
2) pid == 0 : Bu durumda signal process grup ID’si fonksiyonu çağıranın process
ID’sine eşit olan tüm process’lere gönderilir. Yani bu biçimde process grup lideri bütün
üyelerine signal gönderebilmektedir.
3) pid < 0 : Bu durumda signal process grup ID’si |pid|’ye eşit olan process’lere
gönderilir.
4) pid == -1 : Bu POSIX sistemlerinde belirsiz bırakılmıştır.
signal Gönderildiğinde Neler Olur?
Bir signal oluştuğunda prensip olarak sistem mümkün olduğu kadar çabuk signal
fonksiyonunu çağırmaya çalışır. signal oluştuğunda sistemin davranışı standart olarak
belirlenmemiştir. Ancak bu prensip altında bir signal oluştuğunda sistem o anda
çalıştırılmakta olan process’e ara verir, signal gönderilen process’e geçilerek signal
fonksiyonunu çağırır. Ancak signal konusunda önemli bir problem vardır. Signal gönderilen
process yavaş bir sistem fonksiyonu içerisindeyse (örneğin read/write gibi fonksiyonlar) ne
olacaktır? Sistem fonksiyonlarının yarıda kesildiği durumda signal fonksiyonu çağrılırsa,
signal fonksiyonunda da benzer işlemlerin yapılması durumunda sistem çökebilir. Bu
durumda iki seçenek söz konusudur.
1) Sistem fonksiyonuna ara verilir, ama sistem fonksiyonunun başarısız olduğu kabul
edilerek sistem fonksiyonundan çıkılır. Bu durumda signal hızlı bir biçimde işlem görmüş
olur, ama sistem fonksiyonu başarısız olur. Eski AT&T SystemV bu biçimde çalışıyordu. Bu
sistemlerde yavaş sistem fonksiyonları başarısız olduğunda errno değişkenine bakılarak
başarısızlığın signal dolayısıyla olup olmadığı tespit edilmeye çalışılır. Eğer başarısızlık
signal dolayısıyla olmuşsa sistem fonksiyonu programcı tarafından yeniden çağrılmalıdır.
2) Sistem, sistem fonksiyonu içerisindeyken signal geldiğinde işlemine ara verir, signal
işlemini ele alır, ama signal işlemi bittikten sonra sistem fonksiyonunu yeniden kendisi
çağırır. BSD sistemleri bu yöntemi kullanmaktadır. Unix sistemleri genel olarak sistem
fonksiyonu içerisinde signal’i yasaklayan bir tasarımı benimsememişlerdir.
Klasik AT&T SystemV’in diğer bir problemi bir signal oluştuğunda sistemin o signal’in
durumunu default’a çekmesidir. Yani bir signal oluştuğunda çağırılacak fonksiyon Func
olsun. Func fonksiyonunun çalıştırıldığı noktada sistem signal’ı default değere çeker. O
noktada tekrar aynı signal’in oluşması bu signal için default işlem yapılmasına, yani
process’in sonlanmasına neden olur. Bu tür durumlarda iç içelik sağlamak için signal
fonksiyonunun başında yeniden set işlemi yapsak bile maalesef öyle bir zaman aralığı oluşur
ki akış signal geldiğinde çağrılacak fonksiyona girmiştir, ama set işlemini yapacak fonksiyona
henüz girmemiştir. Böyle zaman aralığına giren durumlarda signal default değere
çekilebileceğinden process sonlanabilmektedir. Görüldüğü gibi eski SystemV’lerde bir signal
geldiğinde çalıştırılacak fonksiyon içerisinde kötü bir tesadüf ile aynı signal’den oluştuğunda
process’in sonlanması gibi bir komplikasyon oluşur.
SystemV’in signal mekanizması maalesef güvenli değildir ve problemlidir. Daha sonra bu
problemi çözmek üzere çeşitli gelişmeler olmuştur. Berkeley sistemleri (BSD) problemin
çözümü için signal oluştuğunda signal’i default değere çekmeyi kaldırmıştır ve yavaş sistem
fonksiyonları içerisinde signal oluştuğunda bu sistem fonksiyonlarını kendi çağrılmasını
otomatikleştirmiştir. SystemV daha sonraki sürümlerinde yeni güvenli sistem fonksiyonları
tanımlayarak bu işlemi güveli hale getirmiştir. Ancak eski signal fonksiyonları da çalışmaya
devam etmiştir. POSIX standartları da yeni güvenli signal fonksiyonları tanımlamıştır. Özetle
signal işlemlerinde kullanılan fonksiyonlar iki kısma ayrılabilir:
1) Güvensiz fonksiyonlar. Bu güvensiz fonksiyonlar BSD sistemlerinde güvenli hale
getirilmiştir. Ancak SystemV sistemlerinde hala güvensizdir.
2) POSIX standartlarında tanımlanmış güvenli fonksiyonlar.
signal Oluştuğunda Karşılaşılan Üç Durum
Bir signal oluştuğunda üç şey olabilir.
1) signal dikkate alınmaz, yani hiçbir şey yapılmaz.
2) signal oluştuğunda sistem tarafından default bir fonksiyon çağrılır. Hangi signal
karşılığında default olarak ne yapılacağı daha önceden belirlenmiştir.
3) signal oluştuğunda bizim belirlediğimiz bir fonksiyon çağrılır.
Hangi signal’lara karşı hangi durumun belirlendiği process handle tablosunda tutulmaktadır.
Bu bilgi fork işlemi sırasında alt process’e aktarılır. Bu durumda shell üzerindeki belirleme
yani shell process’inin belirlemesi bizim için önemli olmaktadır. Shell process’inin birkaç
istisna dışında signal durumları default değerdedir. Bilindiği gibi exec işlemi sırasında
process’in bellek alanı değiştirilmektedir. Bu durumda görmemezlikten gelinen ya da
default’a çekilen signal’ler için problemli bir durum oluşmaz. Ancak belirli bir fonksiyona set
edilmiş signal’lerde problem oluşabilir. İşte exec fonksiyonu belirli bir fonksiyon ile set
edilmiş signal’leri default değere çeker.
signal Fonksiyonu
Bir signal oluştuğunda sistemin ne yapacağı signal fonksiyonuyla belirlenir. signal
fonksiyonu AT&T’nin eski bir fonksiyonudur. Yani SystemV ve türevlerinde bu fonksiyon ile
set işlemi yapıldığında yukarıda açıklanmış olan komplikasyonlar oluşabilir. signal
fonksiyonları BSD sistemlerinde güvenlidir. Linux sistemlerinde signal fonksiyonu
SystemV’te olduğu gibi güvensizdir.
signal fonksiyonu POSIX standartlarına sokulmamıştır. signal fonksiyonu yerine POSIX
standartlarında bir grup farklı signal işlemlerinde kullanılan yeni fonksiyon tanımlanmıştır.
signal fonksiyonunun prototipi şöyledir:
void (*signal(int signum, void (*sighandler)(int)))(int);
signal fonksiyonunun birinci parametresi set edilecek signal'ın numarasıdır. POSIX
sistemlerinde 31 signal vardır. Her signal SIGXXX biçiminde sembolik sabitle belirtilmiştir.
Fonksiyonun ikinci parametresi signal fonksiyonu olarak set edilecek fonksiyonun başlangıç
adresidir. Signal geldiğinde çağırılacak fonksiyonun (signal handler) geri dönüş değeri void,
parametresi int olmak zorundadır. Fonksiyon daha önceki signal fonksiyonunun başlangıç
adresine geri döner. Fonksiyon başarısızsa SIG_ERR değerine geri döner. Bu değer tipik
olarak şöyle define edilmiştir:
#define SIG_ERR ((void(*)(int))-1)
Anahtar Notlar: 70'li ve 80'li yılların ortalarına kadar prototip kavramı C'de yoktu. O
devirlerde fonksiyon parametreleri eski biçimde tanımlanıyordu, yani parametre parantezinin
içerisine bir tür bilgisi yazılmıyordu. Fonksiyon bildirimleri parametre parantezinin içi boş
bırakılarak yapılıyordu. Örneğin:
long Func();
Bu biçimde bildirilen fonksiyonlar herhangi bir parametrik yapıyla çağırılabiliyordu. 80'li
yılların ortalarına doğru prototip kavramı C'ye sokuldu. Yeni biçimdeki parametre bildirimi
ile prototip kavramı beraber C'ye girmiştir. Ancak derleyiciler eski biçimdeki parametre
bildirimlerini ve fonksiyon bildirimlerini kabul etmeye devam ettiler (C90 standartlarında bu
durum deprecated yapılmıştır). Özetle C90'da fonksiyon parametre parantezinin içi boş
bırakıldığında parametre kontrolü yapılmayacağı anlamına gelmektedir. C99'da ve C++'ta
eski biçimdeki parametre bildirimi tamamen kaldırılmıştır. Bu dillerde parametre
parantezinin içi boş bırakıldığında void kabul edilir. Ayrıca C90'da fonksiyon göstericileri
tanımlanırken parametre parantezinin içinin boş bırakılması da bu göstericiye geri dönüş
değeri uygun olmak şartıyla parametrik yapısı herhangi bir biçimde olan fonksiyonun
adresinin atanabileceği anlamına gelir.
signal fonksiyonunun ikinci parametresi özel olarak SIG_DFL ya da SIG_IGN olabilir. Bu
sembolik sabitler tipik olarak aşağıdaki gibi tanımlanmıştır:
#define SIG_DFL (void (*)(int)) 0)
#define SIG_IGN (void (*)())1)
SIG_DFL signal durumunu default'a çekmek için, SIG_IGN signal'ı görmemek için kullanılır.
Signal oluştuğunda çağırılacak fonksiyonun parametresi int türdendir. Bu parametreye oluşan
signal'ın numarası geçirilir.
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
void SignalHandler(int sno)
{
printf("signal!..\n");
}
int main(void)
{
if (signal(SIGINT, SignalHandler) == SIG_ERR) {
perror("signal");
exit(1);
}
pause();
printf("signal sonrası\n");
C ve Sistem Programcıları Derneği 88
return 0;
}
pause Fonksiyonu
pause fonksiyonu herhangi bir signal oluşana kadar process'i bloke eder.
int pause(void);
Fonksiyon başarısızlık durumunda -1 değerine geri döner. Başarı durumunda herhangi bir
değer öngörülmemiştir. Örneğin bir process'te aşağıdaki gibi bir işlem yapılsın:
for(;;)
pause();
Bu durumda process yalnızca signal oluşumunda aktif hale gelecektir. Şüphesiz process'in
sonlanması da signal oluştuğunda çağırılacak fonksiyon tarafından yapılacaktır.
alarm Fonksiyonu ve SIGALRM Signal'ı
alarm fonksiyonu belirli bir saniye sonrasında signal üreten temel bir fonksiyondur.
unsigned int alarm(unsigned int seconds);
Fonksiyon belirlenen saniye dolduktan sonra SIGALRM signal'ını oluşturmaktadır.
Programcı tipik olarak SIGALRM numaralı signal'ı set eder. Signal oluştuğunda çağırılacak
fonksiyon içerisinde yeniden alarm fonksiyonunu çağırır. Ana programda da genellikle döngü
içerisinde pause ile bekleme yapar.
Sınıf Çalışması: UNIX'in ünlü at komutuna benzer bir programı yazınız. Program aşağıdaki
gibi çalıştırılacaktır:
myat saat:dakika:saniye prog
Açıklamalar: Program SIGALRM signal'ını set eder, sonra alarm fonksiyonu ile 1 saniyelik
aralıklarla signal oluşmasını sağlar. Ana programda döngü içerisinde pause ile beklenebilir. at
standart bir POSIX komutudur. Şüphesiz bu program başka biçimlerde daha etkin
düzenlenebilir.
Process'in toplam bir tane alarm zamanlayıcısı vardır. Örneğin alarm fonksiyonu önce 5,
sonra 10 parametresiyle çağırılırsa zamanlayıcı 10'a set edilir. Örnek:
#include <stdio.h>
#include <stdlib.h>
#include <signal.h>
#include <unistd.h>
void OnTime(int sno)
{
static int i = 0;
C ve Sistem Programcıları Derneği 89
printf("%d\n", i);
if (i == 20)
exit(1);
++i;
alarm(1);
}
int main(void)
{
if (signal(SIGALRM, OnTime) == SIG_ERR) {
perror("signal");
exit(1);
}
alarm(1);
for (;;)
pause();
return 0;
}
alarm fonksiyonuna parametre olarak 0 girilirse daha önce set edilmiş olan alarm iptal edilir.
Ancak signal oluştuğunda çağırılacak fonksiyon çağırılmaz.