Süreç 1 Kavramı ve Oluşturma Yöntemleri



Benzer belgeler
PROCESS YARATIMI (TEKRAR):

Multicore/Multithread Programlama

Bölüm 5: İşlemci Zamanlaması. Operating System Concepts with Java 8 th Edition

Bölüm 4: İş Parçacıkları. Operating System Concepts with Java 8 th Edition

Bil101 Bilgisayar Yazılımı I. M. Erdem ÇORAPÇIOĞLU Bilgisayar Yüksek Mühendisi

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

Bölüm 4: İş Parçacıkları. Operating System Concepts with Java 8 th Edition

Bölüm 4: Threads (İş Parçaları)

İŞLETİM SİSTEMLERİ (POSIX THREADS v1)

Uzaktan Eğitim Uygulama ve Araştırma Merkezi

Temel Bilgisayar Programlama Final Sınavı Çalışma Notları

ELN1001 BİLGİSAYAR PROGRAMLAMA I

Windows'da çalışırken pek çok durumda bir işe başlamadan önce işletim sisteminin o işe ilişkin bilgileri depolayacağı bir alan yaratması gerekir.

Bilgi ve İletişim Teknolojileri (JFM 102) Ders 7. LINUX OS (Sistem Yapısı) BİLGİ & İLETİŞİM TEKNOLOJİLERİ. LINUX Yapısı

BİLG Dr. Mustafa T. Babagil 1

YZM 3102 İşletim Sistemleri

İşletim Sistemlerinde Proseslerin Çevre Değişkenleri

İşletim Sistemlerine Giriş

NESNEYE YÖNELİK PROGRAMLAMA

HSancak Nesne Tabanlı Programlama I Ders Notları

Teknikleri. Önsöz. iskender atasoy

Örnek: İki fonksiyondan oluşan bir program. Fonksiyon Tanımı

İşletim Sistemleri-II

Fonksiyonlar (Altprogram)

Fonksiyonlar. C++ ve NESNEYE DAYALI PROGRAMLAMA 51. /* Fonksiyon: kup Bir tamsayının küpünü hesaplar */ long int kup(int x) {

Sistem Programlama. Kesmeler(Interrupts): Kesme mikro işlemcinin üzerinde çalıştığı koda ara vererek başka bir kodu çalıştırması işlemidir.

PROGRAMLAMAYA GİRİŞ DERS 2

İşletim Sistemleri. Hazırlayan: M. Ali Akcayol Gazi Üniversitesi Bilgisayar Mühendisliği Bölümü

Yrd. Doç. Dr. Caner ÖZCAN

PROGRAMLAMAYA GİRİŞ FONKSİYONLAR

Temel Bilgisayar Programlama

Sınav tarihi : Süre : 60 dak.

Yrd. Doç. Dr. Caner ÖZCAN

Programlama Dilleri. C Dili. Programlama Dilleri-ders02/ 1

Önemli noktalar. Paradigma Nesnelere Giriş Mesajlar / Ara bağlantılar Bilgi Gizleme (Information Hiding ) Sınıflar(Classes) Kalıtım/Inheritance

Hafta 12 Karakter Tutan Diziler

10. DOSYA GİRİŞ ÇIKIŞ FONKSİYONLARI

Eln 1002 Bilgisayar Programlama II

Proses. Prosesler 2. İşletim Sistemleri

YAPILAR BİRLİKLER SAYMA SABİTLERİ/KÜMELERİ. 3. Hafta

Bölüm 10: PHP ile Veritabanı Uygulamaları

HSancak Nesne Tabanlı Programlama I Ders Notları

C Dersleri Bölüm 3 : Program akışı

WebInstaller. 1. Kurulum Đçin Gereksinimler

Bigisayar Programlama

YZM 3102 İşletim Sistemleri

ELN1002 BİLGİSAYAR PROGRAMLAMA 2

PROSESLER. Proses. Proses

Pointer Kavramı. Veri Yapıları

Mantıksal Kontrol ve Döngü Komutları

int faktoriyel(int sayi) { int sonuc = 1; for(int i=sayi;i>0;i--) sonuc*=i; return sonuc; } int main() { int sayi = faktoriyel(5); }

İşletim Sistemleri (Operating Systems)

Bilgisayar İşletim Sistemleri BLG 312

Algoritmalar ve Programlama. Algoritma

C++ Dersi: Nesne Tabanlı Programlama

BLM-112 PROGRAMLAMA DİLLERİ II. Ders-3 İşaretçiler (Pointer) (Kısım-2)

BULANIK MANTIK VE SİSTEMLERİ BAHAR DÖNEMİ ÖDEV 1. Müslüm ÖZTÜRK Bilişim Teknolojileri Mühendisliği ABD Doktora Programı

Bölüm 3: İşlemler Operating System Concepts with Java 8th Edition 3.1 Silberschatz, Galvin and Gagne 2009

Bölüm 3: İşlemler Operating System Concepts with Java 8th Edition 3.1 Silberschatz, Galvin and Gagne 2009

YZM 3102 İşletim Sistemleri

Gereksiz Kodlar. burada if deyiminin else bölümüne gerek var mı? İfade doğruysa zaten fonksiyon geri dönüyor. Bu aşağıdakiyle tamamen eşdeğerdir:

8. İŞARETCİLER (POINTERS)

1.1. Yazılım Geliştirme Süreci

ANA SINIF TÜRETİLEN BİRİNCİ SINIF TÜRETİLEN İKİNCİ SINIF

Sınav tarihi : Süre : 60 dak. a) strstr b) strchr c) strcat d) strcpy e) strlen. a) b) d) e) 0

BLM 112- Programlama Dilleri II. Hafta 5 İşaretçiler (Pointers)

DÖNGÜLER (LOOPS) while(), do-while(), for(), foreach()

Ders Adı Kodu Yarıyılı T+U Saati Ulusal Kredisi AKTS

BLM 111 ALGORİTMA VE PROGRAMLAMA I

Üst düzey dillerden biri ile yazılmış olan bir programı, makine diline çeviren programa derleyici denir. C++ da böyle bir derleyicidir.

Linux ta komutlar hakkında yardım almak için aşağıdaki komutlar kullanılır : - man - info - whatis - apropos

man komut man ls (ls komutu hakkında bilgi verir.) man pwd (pwd komutu hakkında bilgi verir.)

UNIX/Linux Sistemlerinde exec İşlemleri

Program Nedir? Program, bir problemin çözümü için herhangi bir programlama dilinin kuralları ile oluşturulmuş komut kümesidir.

Dr. Fatih AY Tel: fatihay@fatihay.net

İşletim Sistemi. BTEP205 - İşletim Sistemleri

Öğr. Gör. Serkan AKSU 1

ALGORİTMA VE PROGRAMLAMA II

C Programlama Dilininin Basit Yapıları

Linux Assembly Programlamaya Giriş

İŞLETİM SİSTEMLERİ. (Operating Systems)

SIMAN KULLANIM KILAVUZU

BM-209 Nesne Yönelimli Programlama. Yrd. Doç. Dr. İbrahim Alper Doğru Gazi Üniversitesi Teknoloji Fakültesi Bilgisayar Mühendisliği Bölümü

Üst Düzey Programlama

İŞLETİM SİSTEMİ KATMANLARI (Çekirdek, kabuk ve diğer temel kavramlar) Bir işletim sisteminin yazılım tasarımında ele alınması gereken iki önemli konu

MAT213 BİLGİSAYAR PROGRAMLAMA I DERSİ Ders 1: Programlamaya Giriş

C Konsol ve Komut Satırı

for döngüsü for (başlangıç değeri; şart; artım) ifade; for (başlangıç değeri; şart; artım) { ifadeler; }

Yazılım Kodlama ve İ simlendirme Standartları v1.0

Pardus 2013 te Metin Editörleri

ALGORİTMA VE PROGRAMLAMA II

Dosya Yönetim Sistemi Hazırlayan : mustafa kaygısız Kaynak: megep.meb.gov.tr

2 ALGORİTMA VE AKIŞ DİYAGRAMLARI

Assembly Dili Nedir? Assembly dili biliyorum derken hangi işlemci ve hangi işletim sistemi için olduğunu da ifade etmek gerekir.

Operatörlere Yeni İşlevler Yüklenmesi (Operator Overloading)

Sunum İçeriği. Programlamaya Giriş

$ rm dosya1 dosya2 dosya3 dosya4 dosya5 dosya6 dosya7 dosya8

while(), do-while(), for() M.İLKUÇAR 2010 MAKU-MYO

sayi=3 harf=a reelsayi=8.72 Bellek durumu 5. İşaretç iler (pointers)

Transkript:

İçindekiler Süreç Kavramı ve Oluşturma Yöntemleri...2 UNIX / Linux Sistemlerinde fork ve exec Sistem Çağrıları İle Süreç Kopyalama ve Çalıştırma...3 Thread Kavramı...7 Thread lerin İşletim Sistemleri Tarafından Desteklenmesi...8 Çekirdek Thread leri (Kernel Threads)...8 Kullanıcı Thread leri (User Threads)...8 Çoklu Thread Modelleri/Mekanizmaları...8 Çoktan Bire Çoklu Thread Mekanizması...8 Birebir Çoklu Thread Mekanizması...9 Çoktan Çoğa Çoklu Thread Mekanizması...9 Linux Thread leri...10 Pthreads...11 pthread_create...13 pthread_t...13 pthread_join...13 Thread Senkronizasyonu...14 Kaynaklar...16 1 / 16

Süreç 1 Kavramı ve Oluşturma Yöntemleri Süreç, programın çalışmakta olan haline denmektedir. Yani disk üzerinde pasif halde duran yapıya program, bu yapının belleğe yüklenerek çalıştırılmasıyla ortaya çıkan yeni yapıya ise süreç denmektedir. Bilindiği gibi Von Neumann mimarisine sahip tüm bilgisayarlarda herhangi bir program çalışabilmesi için belleğe yüklenmek zorundadır. Bir süreç çalışması esnasında işletim sisteminin sunduğu sistem çağrılarını kullanarak yeni yeni süreçler yaratabilmektedir. Bu konuda izlenecek stratejiler, süreçlerin aynı anda çalışıp çalışmamasına ve yeni sürecin adres alanına göre farklılıklar gösterebilmektedir. Burada adres alanı (adress space) ile kastedilen bir sürecin bellekte sahip olduğu alandır. (İleride süreç kopyalama konusunda bu tanımdan bolca faydalanılacaktır.) Şekilsel olarak aşağıdaki gibi gösterilebilir. RAM Süreç Sürecin adres alanı Şekil 1. Adres Alanı Yeni süreç(ler) yaratan sürece ebeveyn (parent), yeni süreçlere ise çocuk (child) denilmektedir. Süreçlerin aynı anda çalışıp çalışmaması konusunda iki yaklaşım söz konusudur; 1. Ebeveyn süreç çocuk(lar) ile aynı anda çalışmaya devam edebilir. 2. Ebeveyn bazı ya da bütün çocukları bitene kadar bekleyebilir. Adres alanı konusunda da iki yaklaşım söz konusudur; 1. Çocuğun adres alanı ebeveynin tam bir kopyası olabilir. 2. Çocuk süreç, belleğe yüklenen ayrı bir süreç olabilir. 1 Süreç kelimesi İngilizce process kelimesi yerine kullanılmaktadır ve yazının devamında da bu şekilde kullanılmaya devam edilecektir. 2 / 16

UNIX / Linux Sistemlerinde fork ve exec Sistem Çağrıları İle Süreç Kopyalama ve Çalıştırma Klasik olarak UNIX/Linux sistemlerinde süreç kopyalamak için fork sistem çağrısı kullanılmaktadır. En eski fork çağrıları ebeveyn sürecin birebir kopyasını çıkararark bu işlemi yapmaktaydılar. Yani bellek gösterimi aşağıdaki gibi olmaktaydı. RAM Ebeveyn süreç Kopyalanan çocuk süreç Şekil 2. fork işlemi sonucu oluşan bellek görüntüsü Ancak bilindiği gibi her bir program kod, veri, yığıt (stack) gibi bölümlerden oluşmaktadır. Durum aşağıdaki şekil ile daha iyi anlaşılabilir. Kod Veri Yığıt ve diğer bölümler Şekil 3. Bir programın/sürecin bölümleri Daha da açık söylemek gerekirse Şekil 1 ve 2 de adres alanı ve süreç olarak gösterilen bellek bölgelerinin detaylı görüntüsünün bu şekilde olduğunu söyleyebiliriz. Bir sürecin bellekte kopyasının çıkartılması demek ise tüm bu bölümlerin kopyasının çıkarılması demektir. Tahmin edilebileceği gibi bellekte kopya çıkarma işlemi hem zaman olarak hem de bellek alanı olarak maliyetli bir iştir ve bir şekilde bu kopya çıkarma işleminden kurtulunabilirse ya da geciktirilebilirse daha yüksek performans elde edilebilir. İlk olarak 3 / 16

açıkladığımız en eski ve kötü kopya çıkarma işlemine dayanan fork mekanizması artık günümüz UNIX/Linux sistemlerinde terkedilmiştir. Onun yerine kullanılan mekanizma devam eden kısımda açıklanmaktadır. Şekil 3 te görülen bölümlerin bazıları sadece okunur özelliktedir. Dolayısıyla sadece okunabilen bir bölümde değişiklik yapılamaz. Bu durumda bu özelliği gösteren bir bölümün tekrar tekrar kopyasını çıkarmak yerine bir tanesini ebeveyn ve çocuk süreçler arasında bölüştürmek daha kolay ve tasarruflu olacaktır. Buna en güzel örnek kod bölümüdür. Çalışmakta olan bir programın kod bölümü program çalışmakta iken değiştirilemez ve dolayısıyla (bellek yönetim tekniği olarak sayfalama kullanılan bir sistemde) sadece okunabilir sayfa özelliği ile bellekte saklanabilir. Yukarıda açıklanan sadece okunabilen ve değişmeyen kısımların dışında üzerinde değişiklik yapılabilen kısımlar için de hemen kopya çıkarma işlemi aslında geciktirilebilir. Ebeveyn süreç ve kopyası olan çocuk süreç, ikisinden biri herhangi bir veri üzerinde bir değişiklik yapmadıkları sürece aynı verileri aynı değerler ile kullanmaktadırlar. Bu durumda herhangi bir güncelleme işlemi yapılana kadar tüm alanlar ebeveyn ve çocuk süreçler arasında ortak olarak kullanılabilir ve kopya çıkarmanın gereği kalmaz. Böylece fork işlemi de hızlı biçimde gerçekleştirilebilir. Ancak çocuk ya da ebeveyn süreçlerden herhangi biri, bir veri üzerinde değişiklik yaptığı anda verinin içinde bulunduğu bölümün (veri ya da yığıt gibi) kopyasının çıkarılması gerekir. Günümüz UNIX/Linux sistemleri bu metodu kullanmakta ve buna da yazma olunca kopyalama tekniği (copy-on-write) denilmektedir. Bu noktaya kadar teorik olarak anlatılan yapı kod olarak aşağıdaki gibidir. #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { pid_t pid; if ((pid = fork()) > 0) { // EBEVEYN SÜREÇ KODU. Ebeveyn süreç bu kısmı çalıştıracaktır. else if (pid == 0) { // ÇOCUK SÜREÇ KODU. Çocuk süreç bu kısmı çalıştıracaktır. else { // pid < 0 // fork işlemi başarısız return 0; fork sistem çağrısı geriye adına pid_t denilen bir tür ile dönmektedir. UNIX/Linux sistemlerinde her sürecin süreç tanımlayıcı (process identifier) denilen ve her süreç için farklı olan değerler vardır. Aslında fork sistem çağrısının geri dönüş değeri de bu süreç tanımlayıcı değerdir. pid_t türü <sys/types.h> içinde süreç tanımlayıcı değerleri tutmak üzere bildirilmiştir. Ebeveyn sürece sistem tarafından çocuk sürecin pid_t değeri verilir, çocuk sürece verilen pid_t değeri ise 0 dır. Dolayısıyla her iki process in kodları tamamen aynı olsa bile fork sistem çağrısının geri dönüş değeri iki sürece ayrı ayrı verilmiştir. Bu durumdan faydalanarak if else blokları yardımıyla ebeveyn ve çocuk süreçlere ne yaptırılmak isteniyorsa uygun yerlere kodlanabilirler. Eğer fork işlemi herhangi bir sebep ile başarılı olmazsa ebeveyn sürece pid_t değeri sıfırdan küçük bir değer olarak döndürülür. Durumu yeni bir örnek kod ile açıklamak gerekirse aşağıdaki kod parçası kullanılabilir. 4 / 16

#include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { pid_t pid; if ((pid = fork()) > 0) { // EBEVEYN printf("i am parent\n"); printf("my child's id is %d\n", pid); wait(null); else if (pid == 0) { // ÇOCUK printf("i am child\n"); printf("press any key to exit...\n"); getchar(); exit(0); else { perror("fork error"); exit(1); return 0; Burada ekrana ebeveyn ve çocuk süreçlerin yazdıkları yazılar çıkacaktır. Ebeveyn sürecin kod kısmına dikkat edilirse wait(null) biçiminde bir fonksiyon çağırması görülecektir. Bir ebeveyn süreç oluşturduğu çocuk süreçlerin bitmesini bu fonksiyonla bekleyerek onların normal biçimde sonlanmalarını sağlar. Eğer bir ebeveyn wait ile beklemezse ve çocuk süreç önce biterse çocuk süreç zombi (zombie) süreç durumuna düşer ve ps komutu ile bakıldığında yan tarafında Z harfi ile belirlenir. Belli bir çocuk sürecin beklenmesini sağlayan waitpid biçiminde bir fonksiyon daha mevcuttur. Genel olarak fork sistem çağrısının hemen arkasından execxxx biçiminde bir sistem çağrısı yapılır. exec sistem çağrıları parametrik yapılarına göre sonda X ile gösterdiğimiz bazı ekler alabilmektedirler. (Anlatımda kolaylık sağlaması için bu durum şimdilik gözardı edilip exec çağrısı biçiminde kullanılacaktır.) Önce fork ve hemen ardından exec çağrısı ile sistemde kopyalanan süreç yerine istenilen başka bir sürecin belleğe yüklenerek çalıştırılması sağlanır. Yani bir süreç fork ile kendisinin kopyası olan başka bir süreç oluşturur. Ancak biz bu çocuk süreç için ayrılan alana istediğimiz başka bir programı yükleyerek çalıştırabiliriz. Örneğin önce fork dedikten sonra hemen ardından UNIX/Linux sistemlerinde ls komutunu çalıştırmak için exec çağrılarından birinden faydalanabiliriz. Durumun kodlanmış hali aşağıdaki gibidir; #include <stdio.h> #include <sys/types.h> #include <unistd.h> int main(void) { pid_t pid; if ((pid = fork()) > 0) { // EBEVEYN SÜREÇ printf("i am parent\n"); printf("my child's id is %d\n", pid); wait(null); 5 / 16

else if (pid == 0) { // ÇOCUK SÜREÇ execlp("ls", "ls", "-l", (char*)0); else { perror("fork error"); exit(1); return 0; Burada da çocuk süreç hemen exec çağırması yaparak ls komutunu çalıştırmaktadır. Böylesi bir durumda ebeveyn sürecin kopyasını çıkarmanın hiç gereği yoktur çünkü çocuk süreç derhal başka bir programı kendi yerine yükleyerek çalıştırmaktadır. Bu gibi durumlar için UNIX/Linux sistemlerine vfork isimli bir fonksiyon eklenmiştir. Bu fonksiyon hemen arkadan exec çağırması yapılacağı durumlarda kullanılmak üzere tasarlanmıştır ve ebeveyn sürecin kopyasını çıkarmadan çalışmaktadır. Bir diğer nokta ise exec çağırması biçiminde genellediğimiz exec foknsiyonlarının PATH çevre değişkenine (environment variable) bakan, komut satırı argümanlarını dizi olarak alan ve diziden farklı biçimde alan, çevre değişkenleri (environment variables) aktaran ve aktarmayan versiyonları bulunmaktadır. int execl(const char *path, const char *arg,...); int execlp(const char *file, const char *arg,...); int execle(const char *path, const char *arg,..., char * const envp[]); int execv(const char *path, char *const argv[]); int execvp(const char *file, char *const argv[]); int execve(const char *filename, char *const argv [],char *const envp[]); Burada p harfini PATH çevre değişkenine bakma, e harfini çevre değişkenleri aktarma, v harfini komut satırı argümanlarını vektör biçiminde aktarma olarak algılamak gerekir. 6 / 16

Thread 2 Kavramı Threadi, programlama olarak akış diye düşünülebilir. Bilindiği gibi programlar çalıştırılmaya başlandığı zaman (fonksiyon çağırmaları vb. dallanmaları göz önüne almazsak) komutlar satır satır icra edilir edilir ve yukarıdan aşağıya doğru bir akış gibi işlem görür. İşte thread ler de bu şekilde çalıştırılan küçük programcıklar olarak değerlendirilebilir. Çünkü ilerleyen kısılarda da görüleceği gibi bir thread, aslında bir fonksiyon olarak hazırlanmakta ve işletim sisteminin sağladığı bir sistem çağrısı ile çalıştırılmaya başlamaktadır. Bu durum ileride örnekler ile daha iyi anlaşılacaktır. Daha önce süreç kavramı verilmişti. Burada şunu söylemek yerinde olacaktır. Bir süreç en az bir thread den oluşmaktadır. Yani işletim sistemi thread mekanizmasını desteklemiyorsa bile en azından programı tek parça halinde çalıştıracaktır. Klasik olarak DOS işletim sistemi bir zamanda bir programı çalıştırabiliyordu. Dolayısıyla bir C programı main den başlayarak gerekli fonksiyon çağırmalarını yapıp bitiyordu. Bu durumu sürecin tek thread den oluşması olarak düşünebiliriz. Ancak işletim sistemi thread mekanizmasını bir biçimde destekliyorsa bir süreç en az bir, en fazla ise sistemin desteklediği kadar thread den oluşur. Bu duruma da çoklu thread mekanizması (multithreading) denilmektedir. Çoklu thread mekanizmasının da farklı türleri bulunmaktadır ve ilerleyen bölümlerde ele alınacaktır. Thread ile süreç arasındaki en önemli fark ise süreçlerin kendlerine ait ayrı birer adres alanlarının olması thread lerin ise aynı adres alanı içinde çalışmalarıdır. Dolayısıyla aynı sürece ait thread ler sürecin kod, veri bölümleri ve diğer bazı kaynakları ortak olarak kullanmaktadırlar. Örneğin sürecin global verisini o sürece ait tüm thread ler görüp kullanabilmektedir. RAM RAM Süreç1 Süreç2 Süreç1 in adres alanı Süreç2 nin adres alanı Süreç1 T 1 T 2 T n Süreç1 ve threadlerine ait adres alanı 2 İngilizce thread kelimesinin Türkçe kelime karşılığı iplikçik olarak verilebilir ancak programlama açısından çok da anlamlı bir çağrışım yapmadığı için yazımızın devamında thread olarak kullanmaya devam edeceğiz. 7 / 16

Thread lerin İşletim Sistemleri Tarafından Desteklenmesi Thread mekanizması işletim sistemleri tarafından iki biçimde desteklenebilmektedir. Bunlardan ilki çekirdek thread leri (kernel threads) ikincisi ise kullanıcı thread leridir (user threads) Çekirdek Thread leri (Kernel Threads) Bilindiği gibi çekirdek bir işletim sistemindeki bellek yönetimi, süreçler arası geçiş gibi en alt seviye ve en önemli işlemleri yerine getiren parçasıdır. Bu sebeple çekirdek tarafından desteklenen thread mekanizması için işletim sistemi tarafından doğrudan destekleniyor diyebiliriz. Windows 95 ve sonrası Microsoft sistemleri, BeOS, Solaris 2 gibi işletim sistemleri çekirdek therad lerini desteklemektedir. Bu thread lerin yaratılması, zamanlama (scheduling) işleminin yapılması ve yönetimi bizzat çekirdek tarafından yapılır. Çekirdek thread lerinin yaratılması ve yönetimi genel olarak kullanıcı thread lerine göre daha yavaştır ancak herhangi bir biçimde bir thread bloke olursa çekirdek başka bir thread i çalıştırmaya başlayarak yoluna devam edebilir. Kullanıcı Thread leri (User Threads) Burada thread mekanizması için destek, işletim sisteminin çekirdeği tarafından verilmemekte onun yerine kullanıcı alanında çalışacak olan bir fonksiyon kütüphanesi tarafından verilmektedir. Bu durumda thread oluşturulması, zamanlaması ve yönetimi kullanıcı alanında çalışmakta olan fonksiyon kütüphanesi tarafından yapılmaktadır ve işletim sistemi çekirdeğinin thread mekanizmasından haberi yoktur. Burada kullanıcı alanı ile çekirdek alanı diye iki kavramın verilmesinin yerinde olacağını düşünüyoruz. İşletim sisteminin çekirdeği de sisteme ilişkin veriler tutmakta ve bunun için bir bellek alanı kullanmaktadır. Bu bellek kısmına çekirdek alanı (kernel space) denir. Tabi bu bellek alanında sadece çekirdek kodları işlem yapabilmekte herhangi bir uygulama programı ya da kullanıcı programının bu alana girmesine ve veriler üzerinde değişiklik yapmasına izin verilmemektedir. Kullanıcı alanı (user space) ise bellekte kalan ve diğer bütün programların kullanmasına izin verilen alan olarak düşünülebilir. Kullanıcı thread leri çekirdek thread lerine göre daha hızlı biçimde oluşturulabilir ancak burada çekirdeğin durumdan haberdar olmaması sebebi ile bir dezavantaj vardır. Eğer bir thread bloke olmaya sebep olacak bir işlem yaparsa çekirdek başka bir thread e geçiş yapamayabilir ve o threadin ait olduğu sürecin bloke olmasına sebep olabilir. Çünkü kernel thread mekanizmasından habersizdir. Kullanıcı thread kütüphaneleri için en güzel örnek UNIX/Linux sistemlerindeki POSIX pthreads kütüphanesi, Mach C-threads ve Solaris 2 UI-threads verilebilir. Yazımızda POSIX pthreads örnekleri verilecektir. Çoklu Thread Modelleri/Mekanizmaları Burada işletim sistemlerinde thread mekanizması ister kullanıcı ister çekirdek thread leri olarak desteklensin kurulabilecek çoklu thread mekanizmaları anlatılacaktır. Başlıca üç farklı sistem bulunmaktadır. Bunlar sırasıyla çoktan bire, birebir ve çoktan çoka biçimindedir. Çoktan Bire Çoklu Thread Mekanizması Burada işletim sisteminin çekirdeği çoklu thread mekanizmasından habersizdir. Yani kullanıcı thread leri aracılığı ile mekanizma kurulmuştur. İşletim sistemi bir süreci ya da 8 / 16

thread i çalıştırdığını sanmaktadır. Ancak kullanıcı alanı thread kütüphanesi sayesinde birden fazla thread oluşturulabilmekte ve kullanılabilmektedir. Şekilsel olarak ise aşağıdaki gibidir. (Şekil konu anlatımında da faydalanılan Operating System Concepts, 6th Edition kitabına aittir.) Linux pthreads kütüphanesi de bu gruba girmektedir. Burada işletim sistemi sadece bir işle uğraştığını sanmakta ancak thread kütüphanesi tarafından thread ler arası geçişler yapılmaktadır. (Yukarıdaki şekilde düğüm gibi görünen kısım kullanıcı alanında yer alan thread kütüphanesini göstermektedir.) Bu şekilde işletim sistemine çoklu thread mekanizması eklenmiş olmaktadır. Birebir Çoklu Thread Mekanizması Oluşturulan her bir thread için kernel tarafından bir thread oluşturulur. Bu durum Microsoft grubu işletim sistemlerinde olduğu gibi çekirdek tarafından thread mekanizması desteklendiğinde ortaya çıkmaktadır. Şekilsel gösterimi aşağıdaki gibidir. Çoktan Çoğa Çoklu Thread Mekanizması Burada kernel tarafından çoklu thread mekanizması desteklenmektedir ancak birebirde olduğu gibi her bir kullanıcı thread ine karşılık bir tane kernel thread i oluşturulmak zorunda değildir. N tane kullanıcı thread ine karşılık M tane (M =< N) kernel thread i oluşturulmakta 9 / 16

ve gerekirse kullanıcı thread i ile önce X kernel thread i ilgileniyorken daha sonra Y kernel thread i ilgilenebilir. Yani kullanıcı için açılan thread değiştirilebilir. Solaris 2, IRIX, HP-UX, ve Tru64 UNIX gibi sistemler bu yöntemi kullanmaktadırlar. Şekilsel olarak ise aşağıdaki gibidir. Linux Thread leri Linux çekirdeği 2.2 versiyonu ile beraber thread deteği vermeye başlamıştır. Geleneksel fork, exec mekanizmasının yanında clone isimli bir sistem çağrısı ile thread oluşturulmasına da izin verilmiştir. clone, fork gibi ayrı bir süreç yaratsa da çocuk süreç ebeveyn süreç ile aynı adres alanını paylaşmaktadır. Adres alanı paylaşıldığı için de bu yeni süreç ayrı bir thread gibi düşünülebilir. clone ile yeni bir thread/süreç tanımlandığında sistem çağrısının bazı flag değerleri ebeveyn süreç ile nelerin paylaşılacağını belirlemekte kullanılmaktadır. Aslında Linux süreçler ile thread ler arasında ayrım yapmamakta ve hepsine birden iş (task) demektedir. clone sistem fonksiyonu dışında, Linux çoklu thread mekanizmasını da desteklememektedir. Bununla birlikte kullanıcı seviyesinde çoklu thread mekanizmasını desteklemek için çeşitli pthreads gerçeklemeleri bulunmaktadır. clone fonksiyonunun parametrik yapısı aşağıdaki gibidir. int clone(int(*fn)(void*), void* child_stack, int flags, void* args); Fonksiyonun ilk parametresi bir fonksiyon pointer'ıdır. Yani çocuk süreç olarak çalıştırılacak olan fonksiyondur. Fonksiyonun geri dönüş değeri int, aldığı parametre ise türü olmayan bir poineter'dır. clone fonksiyonun 4. parametresi fn ile gösterilen fonksiyona aktarılacak olan parametredir. Daha önce söylendiği gibi aynı sürece ait thread'ler aynı kod ve veri alanını kullanırlar fakat yığıtı kullanamazlar. Dolayısıyla her thread'in kendine ait bir yığıtı olmalıdır. Bu sebeple de clone fonksiyonunun 2. parametresi ile yığıt olarak kullanılacak olan bu alan belirlenmektedir. clone fonksiyonunun 3.parametresi ise daha önce sözünü ettiğimiz flag değerleridir ve CLONE_XXX biçiminde bazı öntanımlı değerleri vardır. Bu değer, birbirleri ile mantıksal VEYA işlemine tabi tutularak birden fazla flag'i içerecek biçimde verilebilir. Fonksiyonun prototipi ve bu değerler <sched.h> dosyası içindedir. Aşağıda çok basit bir clone örneği bulunmaktadır. Örnekte amaç yeni bir sürecin fork ile oluşturulması ve clone'un kullanımı arasındaki farkın gösterilmesidir. 10 / 16

#include <stdio.h> #include <sched.h> #include <sys/types.h> #include <sys/wait.h> #include <malloc.h> #include <signal.h> int args[1] = {13; /* 64 KB */ #define STACK_SIZE 65536 int my_thread(void* p) { int *params = (int*)p; int i; printf("thread..: %d\n", params[0]); return 0; int main(void) { int pid_thread; void* stack; stack = malloc(stack_size); if (stack == NULL) { printf("malloc hatasi!!!\n"); exit(1); pid_thread = clone(my_thread, stack, CLONE_FS CLONE_FILES, (void*) args); if (pid_thread == -1) { printf("thread olusturulamadi!!!\n"); exit(1); waitpid(pid_thread, 0, 0); printf("clone deneme :)\n"); return 0; Daha önceki örneklerde akış fork işleminin hemen alt satırından devam etmekte idi. Burada ise açıklandığı gibi belirtilen fonksiyon çalıştırılmak sureti ile yeni bir thread/süreç oluşturulmuştur. Pthreads Bu noktaya kadar adı bolca geçen pthreads ile kastedilen aslında POSIX standardı tarafından belirlenen thread oluşturma ve senkronizasyonu için gerekli API tanımlamalarıdır. Yani pthreads sadece bir belirlemedir, fonksiyonların ne iş yapacağı ve parametrik yapılarının ne olacağı bilgilerini içerir. Kodlama yoluyla belirtilen fonksiyonların yazılması işlemi, işletim sistemi tasarımcıları ve kodlamacılarına kalmıştır ve istedikleri biçimde bunu gerçekleştirebilirler. Yazımızda gitgide daha da yaygınlaşan POSIX pthread kütüphanesi örnekleri yer alacaktır. 11 / 16

pthread fonksiyonlarının hepsi pthread_ öneki ile başlamaktadır. Bu bir fonksiyon kütüphanesi olduğu için pthread fonksiyonlarının kullanıldığı C programlarının Linux ortamında libpthread.so veya libpthread.a kütüphanesi ile beraber bağlama (link) işlemine sokulması gerekmektedir. Bunun içinde -lpthread biçiminde bir seçenek derleme esnasında kullanılmalıdır. Yani gcc o output_file lpthread source.c biçiminde #include <stdio.h> #include <pthread.h> #include <unistd.h> #define MAX 10 int g_arr[max]; int g_i; pthread_mutex_t mutex = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t fir = PTHREAD_MUTEX_INITIALIZER; pthread_mutex_t sec = PTHREAD_MUTEX_INITIALIZER; void* ThreadProc1(void* pvarg) { int i; while (g_i < MAX) { pthread_mutex_lock(&fir); pthread_mutex_lock(&mutex); g_arr[g_i] = *((int*)pvarg); g_i++; pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&sec); //sleep(1); void* ThreadProc2(void* pvarg) { int i; while (g_i < MAX) { pthread_mutex_lock(&sec); pthread_mutex_lock(&mutex); g_arr[g_i] = *((int*)pvarg); g_i++; pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&fir); //sleep(1); int main(void) { pthread_t tid1, tid2; int first, second; int i; g_i = 0; first = 1; second = 2; pthread_mutex_lock(&sec); 12 / 16

pthread_create(&tid1, NULL, ThreadProc1, (void*)&first); pthread_create(&tid2, NULL, ThreadProc2, (void*)&second); pthread_join(tid1, NULL); pthread_join(tid2, NULL); for (i = 0; i < MAX; i++) printf("%d. %d\n", i + 1, g_arr[i]); return 0; Program her yerden erişilebilen (global) bir diziye (g_arr) tek ve çift sayıları (aslında sadece 1 ve 2) yerleştiren iki thread oluşturmaktadır. Diziye eleman eklenirken hangi indeks üzerinde işlem yapılacağını da yine her yerden erişilebilen bir tamsayı değişken göstermektedir(g_i). Thread lerden biri 0, 2, 4, 6 gibi indekslere 1 yazmakta diğeri ise 1, 3, 5, 7 gibi indekslere 2 yazmaktadır. Daha önce de söylendiği gibi thread ler aynı sürece aitseler aynı global verileri kullanbilirler. Ancak burada ortaya çıkan başka bir sorun vardır. O da thread lerin birbirleri ile senkronize çalışabilmeleri. Çünkü her iki thread de aynı veriyi kullandığı için biri, veri üzerindeki işlemini bitirmeden diğeri araya girebilir ve bu durumda verinin uygun olmayan bir değerde kalmasına sebep olabilir. Bu konu da kod/fonksiyonlar açıklanırken senkronizasyon ile ilgili başlık altında açıklanacaktır. Kullanılan fonksiyonlar ve açıklamaları aşağıdaki gibidir. pthread_create int pthread_create(pthread_t *thread, const pthread_attr_t *attr, void *(*start_routine)(void*), void *arg); Thread oluşturmak için kullanılan fonksiyondur. Thread in tanımlayıcı değerinin yerleştirilmesi için kullanılacak olan pthread_t türünde değişkenin adresini, thread e ilişkin özel belirlemeler varsa onları, thread biçiminde çalıştırılacak olan fonksiyonu ve bu fonksiyona aktarılacak olan parametre(leri) vererek çağırılır. pthread_t Daha önce fork işleminde olduğu gibi ebeveyn sürece oluşturulan thread in tanımlayıcısı pthread_create in ilk parametresi olan değer ile döndürülmektedir. Bir thread e ilişkin bütün işlemler bu değer üzerinden yürütülebilir. POSIX styandartlarındaki herhangi bir değişmeye karşı pthreads.h dosyasında tanımlı olan pthread_t türünün bu işlem için tercih edilmesi gerekmektedir. pthread_join int pthread_join(pthread_t thread, void **value_ptr); fork işleminde olduğu gibi thread oluşturan sürecin oluşturduğu thread lerin bitmesini beklemesi için kullanılmaktadır. Fonksiyonun ilk parametresi hangi thread olduğunu gösterir, ikinci parametresi thread in geri dönüş değerini almak için kullanılır. Ancak örneğimizde NULL geçilmiş yani bu değer alınmamıştır. Eğer bir thread pthread_exit fonksşyonu kullanılarak sonlandırılırsa bu fonksiynun tek parametresi olan değer pthread_join fonksiyonundan dönüşte thread in geri döndürdüğü değer olarak alınır. 13 / 16

Thread Senkronizasyonu Aynı sürece ait thread lerin o sürece ait global verileri ortaklaşa kullanımı sırasında bazı problemlerin olabileceğini daha önce belirtmiştik. Bu durumu örneğimiz üzerinden açıklayacağız. Aynı sürece ait thread lerin ortak kullandıkları verilere eriştikleri kod parçasına kritik bölge denmektedir. Thread lerin özellikle bu kod parçalarında senkronize çalışması yani bir tanesi böylesi bir kritik bölgede iken diğerinin gelip çalışmayı bölmemesi gerekir. Bunu sağlayan mekanizmaya ise senkronizasyon denilmektedir. Örneğimizde de g_arr ve g_i biçiminde gösterilen veriler bütün thread leri için ortaktır. Önce g_arr[g_i] ile gösterilen değer değiştirilmekte sonra da g_i++ işlemi ile bir indeks arttırma işlemi yapılmaktadır. İşte bu kod parçası kritik bölgedir. Her iki thread de de aşağıdaki gibidir. g_arr[g_i] = *((int*)pvarg); g_i++; Önce senkronizasyon olmadığını düşünelim. Birinci thread 0 indeksindeki elemana 1 yerleştirdikten sonra global veri olan indeksi arttırmadan evvel thread ler arası geçiş olursa ve ikinci thread gelirse indek güncellenmemiş olacağı için birinci thread in 1 yazdığı yere 2 yazabilir. Bu durumda genel olarak işlemlerin karmakarışık yürümesine neden olabilir. 1.THREAD 2.THREAD ------------ ------------ g_arr[g_i] = *((int*)pvarg); TAM BU NOKTADA THREADLER ARASI GEÇİŞ g_arr[g_i] = *((int*)pvarg); g_i++; Yukarıdaki senaryo ile anlattığımız durum gerçekleşecek ve ikinci thread birinci thread in verilerini bozacak, istenmeyen durumlar ortaya çıkacaktır. Bu durumu engellemek için bir thread kritik bölgede iken diğerinin kritik bölge kodlarını çalıştıramaması gerekir. Bunu engellemek için de mekanizmalar vardır ve mutex (İngilizce MUTual EXclusion ın kısa hali) bunlardan biridir. Sözlük karşılığı olarak karşılıklı olarak birbirini dışlama yani her iki tarafın da birbirini istememesi durumu diyebileceğimiz bu mekanizma ile kritik bölgeye girerken başka bir thread in bu verilere erişmemesi için bir hazırlık işlemi yapılmakta sonra kritik bölgeye girilmekte, kritik bölgeden çıkınca da artık başka bir thread in bu verileri kullanabilmesi için bir bitirme işlemi uygulamaktadır. Böylece senkronizasyon sağlanmaktadır. Örnek programımızda senkronizasyon iki kademeli olarak kullanılmıştır. Öncelikle mutex isimli senkronizasyon nesnesi tam olarak yukarıda anlatılan işemin yapılmasını sağlamıştır. Yani bir thread global verileri kullanırken ikinci bir thread kullanamamaktadır. pthread_mutex_lock(&mutex); /* Hazırlık işlemi*/ g_arr[g_i] = *((int*)pvarg); g_i++; pthread_mutex_unlock(&mutex); /* Bitiş işlemi*/ Buradaki hazırlık ve bitiş işlemi sanki bir kilitleme ve o kilidi açma gibi de düşünülebilir. Zaten fonksiyon isimleri de standartlar tarafından bu şekilde verilmiştir. Mutex sisteminden faydalanarak thread leri sıraya sokma işlemi de kodumuzda gerçekleşmiştir. Burada hangi thread çalışıyorsa kendine ilişkin anhtarı/mutex i açmakta, diğer thread e ilişkin mutex i ise kapatmaktadır. Dolayısıyla ilk işlem ile sadece biri kritik bölgede çalışabilirken ikinci işlem ile sıralı biçimde çalışmaları sağlanmıştır. 14 / 16

1.THREAD --------- pthread_mutex_lock(&fir); pthread_mutex_lock(&mutex); g_arr[g_i] = *((int*)pvarg); g_i++; pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&sec); 2.THREAD --------- pthread_mutex_lock(&sec); pthread_mutex_lock(&mutex); g_arr[g_i] = *((int*)pvarg); g_i++; pthread_mutex_unlock(&mutex); pthread_mutex_unlock(&fir); 15 / 16

Kaynaklar 1 UNIX/Linux Sistem Programlama Kurs Notları, C ve Sistem Programcıları Derneği. 2 - Operating System Concepts 6 th Ed., Abraham Silberschatz, Peter Baer Galvin, Greg Gagne, Wiley. 3 Pthreads Programming, Bradford Nichols, Dick Buttlar, and Jacqueline Proulx Farrell, O'Reilly. 4 Linux man sayfaları. 16 / 16