You are on page 1of 11

UNIX/Linux Sistemlerinde Dosya Betimleyicilerinin Anlamı

Kaan Aslan
14 Temmuz 2008

UNIX/Linux sistemlerinde her prosesin proses tablosu yoluyla erişilen bir dosya
betimleyici tablosu (file descriptor table) vardır. Dosya betimleyici tablosu bir gösterici
dizisi biçimindedir. Betimleyci tablo içersindeki her gösterici açılmış bir dosyanın
bilgilerinin tutulduğu ve ismine dosya nesnesi (file object) denilen bir veri yapısını
gösterir. open fonksiyonundan elde edilen dosya betimleyicisi (file descriptor)
prosesin dosya betimleyici tablosunda bir indeks belirtmektedir.

Dosya nesnesi dosya işlemlerini gerçekleştirmek için gerekli tüm bilgileri tutmaktadır.
Dosya betimleyicisi bir sistem fonksiyonuna parametre olarak geçirildiğinde ilgili
fonksiyon betimleyici tablosunun betimleyici değeri ile belirtilen indeksinden dosya
nesnesinin adresini elde eder ve açık olan dosyanın bilgilerine erişir. Her dosya açma
işlemi sonucunda yeni bir dosya nesnesi oluşturulmakta ve betimleyici tablosunda
yeni bir giriş tahsis edilmektedir. Örneğin yukarıdaki şekilde belirtilen durumda open
fonksiyonuyla yeni bir dosyayı daha açtığımızı düşünelim:

int fd;
...
fd = open("test.dat", O_RDONLY);

Dosya betimleyici tablosunun yeni durumu şöyle olacaktır:

1
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Yeni yaratılan dosya nesnesini kesikli çizgilerle gösterdik. Şekilden de gördüğünüz
gibi dosya açma işlemi sonucunda open fonksiyonundan 5 numaralı betimleyici
değeri elde edilmiştir. Yani örneğimizde fd’nin içerisinde 5 değeri bulunacaktır.

Disk tabanlı bir dosya açıldığında işletim sistemi diskten dosya bilgilerini elde eder
ve dosyayı yönetebilmek için bellekte çeşitli veri yapılarını oluşturur. Yukarıda da
belirttiğimiz gibi, dosyalar üzerinde işlem yapabilmek için gerekli tüm bilgiler dosyanın
açılması sırasında işletim sistemi tarafından dosya nesnelerinin içerisine
yerleştirilmektedir. Dosya betimleyicilerinin yalnızca birer anahtar değer (handle)
işlevi gördüğüne dikkat ediniz. Şimdi farklı iki dosya betimleyicisinin aynı dosya
nesnesini gösterdiğini düşünelim. Bu durumda aslında her iki dosya betimleyicisi de
aynı dosyaya ilişkin olur değil mi? Örneğin, yukarıdaki şekilde 1 ve 2 numaralı
betimleyicileri aynı dosya nesnesini gösteriyor. O halde biz o dosya üzerinde işlem
yapmak için 1 numaralı betimleyiciyi de 2 numaralı betimleyiciyi de kullanabiliriz.

Dosya nesnelerinin içerisinde hangi bilgilerin bulunduğunu merak ediyor


olabilirsiniz. Bazılarını söyleyelim:

- Dosya işlemleri için gerekli dosyanın bloklarına ilişkin tüm disk bilgileri.
- Dosyanın sahiplik bilgileri ve erişim bilgileri.
- Dosya göstericisinin konumu.
- Dosya nesnesinin kullanım sayacı.
- ...

open fonksiyonunun dosya betimleyici tablosundaki ilk boş betimleyiciyi tahsis


edeceği POSIX standartlarında garanti altına alınmıştır. Örneğin, prosesin betimleyici
tablosunun yalnızca 0, 1, 2, 3, 4, 7 numaaralı betimleyicileri dolu olsun. open
fonksiyonuyla bir dosya açmak istediğimizi düşünelim. Başarı durumunda open
fonksiyonu kesinlikle 5 numaralı betimleyiciyi tahsis edecek ve 5 değeriyle geri
dönecektir. Bu örnekte aynı zamanda dosyaların açılıp kapatılmasıyla zamanla
betimleyici tablosunda çeşitli boşlukların oluşabileceğine dikkatinizi çekmek istiyoruz.

2
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Her prosesin ayrı bir proses tablosuna dolayısıyla da ayrı bir betimleyici tablosuna
sahip olduğunu belirtmiştik. İşte bu nedenle dosya betimleyicileri proses düzeyinde
anlamlı değerlerdir. Örneğin biz X prosesinde open fonksiyonuyla bir betimleyici elde
edip o betimleyicinin değerini Y prosesine proseslerarası haberleşme yöntemlerinden
biriyle göndermiş olalım. Bu betimleyicinin artık Y prosesinde bir anlamı olmayacaktır.
Çünkü bu betimleyici Y prosesinde kullanılırken artık Y prosesinin dosya betimleyici
tablosunda bir indeks belirtecektir. Bu nedenle gönderilen bu betimleyici Y prosesi
için ya başka bir dosyaya ilişkin betimleyici durumundadır ya da geçersiz bir
betimleyici durumundadır.

Aynı dosya open fonksiyonuyla birden fazla kez açılabilir. open fonksiyonu her
çağrıldığında yeni bir dosya nesnesi ve yeni bir betimleyici tahsis edilir. Böylesi bir
durum aynı dosya üzerinde birden fazla dosya nesnesi ile işlem yapmak anlamına
gelir. Dosya göstericisinin dosya nesnesinin içerisinde tutulduğunu anımsayınız. Bu
durumda iki betimleyicinin gösterdiği dosya nesnelerindeki dosya göstericileri farklı
konumlarda olabilirler değil mi? Örneğin:

int fd1, fd2;


...
if ((fd1 = open("test.dat", O_RDONLY)) < 0) {
perror("open");
exit(EXIT_FAILURE);
}

if ((fd2 = open("test.dat", O_RDONLY)) < 0) {


perror("open");
exit(EXIT_FAILURE);
}

lseek(fd1, 100, SEEK_SET);


lseek(fd2, 200, SEEK_SET);

Oluşan durumu şekilsel olarak şöyle gösterebiliriz:

3
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Burada fd1 ve fd2 betimleyicilerinin her ikisi de “test.dat” dosyasına ilişkindir. Biz fd1
betimleyici ile bu dosyanın 100 numaralı offset'inden, fd2 betimleyici ile de 200
numaralı offset'inden işlem yapabiliriz.

UNIX/Linux sistemlerinde eldeki bir dosya betimleyicisini kullanarak o dosya


betimleyicisi ile aynı dosya nesnesini gösteren başka bir betimleyici oluşturulabilir. Bu
işlem dup fonksiyonuyla yapılmaktadır:

#include <unistd.h>

int dup(int filedes);

Fonksiyonun parametresi daha önce açılmış olan bir dosyaya ilişkin betimleyicidir.
Fonksiyon parametresiyle aldığı betimleyici ile aynı dosya nesnesini gösteren yeni bir
betimleyici tahsis eder ve bu betimleyici ile geri döner. dup fonksiyonunun da başarı
durumunda betimleyici tablosundaki ilk boş betimleyici ile geri döneceği POSIX
standartlarında garanti altına alınmıştır. Fonksiyon başarısızlık durumunda -1
değerine geri döner. Örneğin:

int fd1, fd2;

if ((fd1 = open("sample.dat", O_RDONLY)) < 0) {


perror("open");
exit(EXIT_FAILURE);
}

if ((fd2 = dup(fd1)) < 0) {


perror("dup");
exit(EXIT_FAILURE);
}

Burada fd1 ve fd2 aynı dosya nesnesini gösteren betimleyicilerdir. Oluşan durumu
aşağıdaki şekille gösterebiliriz:

4
Kaan Aslan Makale Arşivi – www.kaanaslan.net
dup fonksiyonu başarısız olduğunda errno değişkeninin alabileceği önemli değerler
şunlardır:

EBADEF : Fonksiyonun parametresi açık bir dosyaya ilişkin geçerli bir dosya
betimleyicisi değildir.
EINTR : Fonksiyon bir sinyalle sonlandırılmıştır.
... : ....

fd1 ve fd2'nin aynı dosya nesnesine ilişkin iki betimleyici olsun. close fonksiyonu ile
fd1 betimleyicisini kapatmış olalım. Bu durumda dosya nesnesi fd2 betimleyicisi
tarafından da kullanıldığı için yok edilmez. Bir dosya nesnesinin kaç betimleyici
tarafından kullanıldığı sistem tarafından dosya nesnesinin içerisindeki bir sayaç
yoluyla izlenmektedir. close fonksiyonu parametresiyle belirtilen betimleyiciyi boşaltır
ve dosya nesnesinin sayacını bir eksiltir. Ancak nesne sayacı sıfıra geldiğinde dosya
nesnesi yok edilmektedir.

open fonksiyonu ile “test.txt” isminde bir dosya açtığımızı ve fd isimli bir
betimleyici elde ettiğimizi varsayalım. Aşağıdaki kod parçası çalıştıktan sonra ne
olacaktır?

close(1);
dup(fd1);
printf("bu yazi nereye yazilacak?\n");

1 numaralı dosya betimleyicisinin stdout dosyasına ilişkin olduğunu belirtmiştik. dup


fonksiyonu da ilk boş betimleyiciyi elde ettiğine göre şöyle bir durumla
karşılaşılacaktır:

printf fonksiyonu aygıta aktarım sırasında stdout dosyasını (yani 1 numaralı


betimleyciyi) kullandığına göre artık yazılanlar "test.txt" dosyasına yazılacaktır değil
mi?

5
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Bir dosyaya ilişkin betimleyicinin başka bir dosya nesnesini gösterir duruma
getirilmesine yönlendirme (redirection) denilmektedir. Yukarıdaki örneğimizde stdout
dosyasını “test.txt” isimli bir dosyaya yönlendirmiş olduk. Aşağıdaki programı
çalıştırarak oluşturulan "test.txt" dosyasını inceleyiniz:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
int fd;
int i;

if ((fd = open("test.txt", O_WRONLY|O_CREAT|O_TRUNC)) < 0) {


perror("open");
exit(EXIT_FAILURE);
}

close(1);
dup(fd);

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


printf("Test %d\n", i);

close(fd);

return 0;
}

Yukarıdaki gibi yapılan yönlendirme işleminde küçük bir sorundan bahsedelim.


close(1) işlemi ile dup(fd) işlemi arasında bir kesilme olur da başka bir dosya open
fonksiyonu ile açılırsa stdout yanlışlıkla o dosyaya yönlendirilebilir. Tabi böyle bir
sorunun ortaya çıkması için bu kesilmenin bizim prosesimizde oluşması gerekir. Her
prosesin ayrı bir betimleyici tablosu olduğunu biliyorsunuz. Eğer kendi programınızda
böyle bir kesilme olasılığı yoksa ya da kesilme oluştuğunda başka bir dosyanın
açılması söz konusu değilse kaygılanmanıza da gerek yok. Ancak çok thread'li
uygulamalarda ya da sinyal mekanizmasının kullanıldığı uygulamalarda bu durumu
göz önüne almanız gerekebilir.

dup2 fonksiyonu close ile çiftleme işlemlerinin atomik bir biçimde yapıldığı daha
gelişmiş bir dup fonksiyonudur:

#include <unistd.h>

int dup2(int fd1, int fd2);

Fonksiyon önce fd2 betimleyicisi üzerinde close işlemi uygular. Daha sonra fd2
betimleyicisinin fd1 betimleyicisi ile aynı dosya nesnesini göstermesini sağlar. Yani
çağırma sonrasında fd2 betimleyicisi fd1 betimleyicisi ile aynı dosya nesnesini
gösteriyor olacaktır. Bu durumu şekilsel olarak şöyle açıklayabiliriz:
6
Kaan Aslan Makale Arşivi – www.kaanaslan.net
dup2 fonksiyonu size biraz karışık gelmiş olabilir. İşlevini şöyle aklınızda tutabilirsiniz:
dup2, fd1 betimleyicisini çiftlemektedir. Fakat elde edilen betimleyici en düşük
numaralı betimleyici değil fd2 numaralı betimleyici olur. Ayrıca dup2 fonksiyonunda
ikinci parametreyle belirtilen fd2 betimleyicnin açık bir dosyaya ilişkin olması da
gerekmemektedir. Yani fd2 boş bir betimleyici değerini belirtiyor olabilir. dup2
fonksiyonu eğer fd2 betimleyicisi boş değilse o betimleyiciyi kapatmaya
çalışmaktadır.

dup2 fonksiyonu başarı durumunda fd2 betimleyici değeriyle başarısızlık durumunda


-1 değeriyle geri döner. Eğer fd1 ve fd2 aynı değerdeyse dup2 dosyayı kapatmaz.
fd2 değerine (fd1 ile aynı değerdir) geri döner. Başarısızlık durumunda errno
değişkeninin alacağı önemli değerler şunlardır:

EBADEF : Fonksiyonun parametresi açık bir dosyaya ilişkin geçerli bir dosya
betimleyicisi değildir.
EINTR : Fonksiyon bir sinyalle sonlandırılmıştır.
... : ...

stdout dosyasının yönlendirilmesini dup2 fonksiyonunu kullanarak şöyle de


yapabiliriz:

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>

int main(void)
{
int fd;
int i;

if ((fd = open("test.txt",
O_WRONLY|O_CREAT|O_TRUNC,S_IRUSR|S_IWUSR)) < 0) {
perror("open");
exit(EXIT_FAILURE);
}
7
Kaan Aslan Makale Arşivi – www.kaanaslan.net
dup2(fd, 1);

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


printf("Test %d\n", i);

close(fd);

return 0;
}

Şimdi prosesin dosya betimleyici tablosunun ve dosya nesnelerinin veri yapılarını


biraz daha somut bir biçimde inceleyelim. Linux 2.6 çekirdeğindeki konuyla ilgili veri
yapıları şöyledir:

Linux sistemlerinde proses tablosu task_struct yapısı ile, proses dosya tablosu
files_struct yapısı ile ve dosya nesnesi de file yapısı ile temsil edilmektedir. Bu yapılar
üzerinde bazı temel bilgileri verelim:

struct task_struct {
/* ... */
struct files_struct *files;
/* ... */
};

8
Kaan Aslan Makale Arşivi – www.kaanaslan.net
struct files_struct {
atomic_t count;
struct fdtable *fdt;
struct fdtable fdtab;

spinlock_t file_lock ____cacheline_aligned_in_smp;


int next_fd;
struct embedded_fd_set close_on_exec_init;
struct embedded_fd_set open_fds_init;
struct file * fd_array[NR_OPEN_DEFAULT];
};

Burada prosesin dosya bilgilerine erişmekte kullanılan ana eleman files_struct yapısı
içerisindeki fdt göstericisidir. Bu gösterici işin başında (yani fork işlemi sırasında) aynı
yapı içerisindeki fdtable türünden fdtab isimli yapı nesnesini gösterir duruma
getirilmektedir. fdtable yapısı şöyledir:

struct fdtable {
unsigned int max_fds;
struct file ** fd; /* current fd array */
fd_set *close_on_exec;
fd_set *open_fds;
struct rcu_head rcu;
struct fdtable *next;
};

fdtable yapısının fd elemanı dosya betimleyici tablosunu göstermektedir. fd_set


türünün bir bit dizisi olarak kullanıldığını belirtelim. close_on_exec göstericisi “close
on exec” bayrağı set edilmiş betimleyicileri, open_fds ise açık dosya betimleyicilerini
tutmakta kullanılır. Her fork işleminde fazla sayıda tahsisat yapmamak için işin
başında bu göstericilerin files_struct yapısı içerisindeki önceden tahsis edilmiş
elemanları göstermesi sağlanmıştır. Yani işin başında fdtable yapısının fd elemanı
files_struct yapısının fd_array elemanını, fdtable yapısının close_on_exec elemanı
files_struct yapısının close_on_exec_init elemanını ve fdtable yapısının open_fds
elemanı files_struct yapısının open_fds_init elemanını göstermektedir. Açılan dosya
sayısının artması durumunda fdtable yapısının elemanları için çekirdek heap alanı
içerisinde yeni dizilerin tahsis edileceğini belirtelim. fork işlemi sonrasındaki
files_struct yapısının durumunu aşağıdaki şekille betimleyebiliriz:

9
Kaan Aslan Makale Arşivi – www.kaanaslan.net
Linux sistemlerindeki file yapısı ise aşağıdaki gibidir:

struct file {
union {
struct list_head fu_list;
struct rcu_head fu_rcuhead;
} f_u;
struct path f_path;

#define f_dentry f_path.dentry


#define f_vfsmnt f_path.mnt

const struct file_operations *f_op;


spinlock_t f_lock;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
struct fown_struct f_owner;
const struct cred *f_cred;
struct file_ra_state f_ra;

u64 f_version;
#ifdef CONFIG_SECURITY

10
Kaan Aslan Makale Arşivi – www.kaanaslan.net
void *f_security;
#endif
void *private_data;

#ifdef CONFIG_EPOLL
struct list_head f_ep_links;
#endif /* #ifdef CONFIG_EPOLL */
struct address_space *f_mapping;
#ifdef CONFIG_DEBUG_WRITECOUNT
unsigned long f_mnt_write_state;
#endif
};

Yapının f_mode dosyanın erişim bilgilerini, f_pos elemanı dosya göstericisinin yerini,
f_count elemanı nesne sayacını belirtmektedir. Bu yapının syrıntılı açıklaması için
başka kaynaklara başvurabilirsiniz.

Kaynaklar

Aslan, K. (2002). UNIX/Linux Sistem Programlama Kurs Notları. Istanbul: C ve


Sistem Programcıları Derneği.

Bovet, D. and Cesati, M. (2005). Understanding the Linux Kernel. Oreilly &
Associates Inc.

Mauerer W. (2008). Professional Linux Kernel Architecture. Wiley Publishing, Inc:


Indianapolis

Maurice, B. (1986). The Design Of The UNIX Operating System. Prentice Hall: New
Jersey.

Rodriguez C., Fisher G., Smolski S. (2006). The Linux Kernel Primer. Prentice Hall.

Stevens, R., Rago, S. A. (2005). Advanced Programming in the UNIX(R)


Environment (2nd Edition). Addison-Wesley Professional.

11
Kaan Aslan Makale Arşivi – www.kaanaslan.net

You might also like