Process Kavramı ve xv6 Çekirdeğindeki Gerçekleştirimi
Şimdi gelin bir işletim sisteminin en temel kavramlarından biri olan process kavramına bakalım.
Not
Bu kelime Türkçe’ye genelde proses olarak çevriliyor ama tam Türkçe bir kelime
gibi durmuyor. Süreç
denebilir belki ama o da orijinal anlamından kaymış mı
oluyor bilemiyorum, terimler.org’da süreç
olarak
çevrilmiş. Bilgisayar
Kavramları
sitesinde ve
Vikipedi’de
işlem
olarak belirtilmiş. Ben tahmin ediyorum ki el alışkanlığı process
yazmaya devam edeceğim ama benim kulağıma işlem
daha doğru geliyor.
Bilgisayar programı dediğimiz şeyler genelde sabit diskte duran ve işletim
sistemi tarafından çalıştırılabilen dosyalar. İşletim sistemine göre bu
çalıştırılabilir dosyaların formatları ve uzantıları değişebiliyor. Örneğin
Windows sistemlerde tipik olarak .exe
uzantısından ve
PE formatından
bahsediyoruz, xv6 gibi Unix temelli sistemlerde, Linux dahil olmak üzere, bu
formatın adı ELF
oluyor. Bu tarz sistemlerde dosya uzantısının pek bir önemi yok. Biz bir
programı çalıştırdığımız zaman, masaüstü ortamında çift tıklayarak, ya da komut
satırından çalıştırarak bir dizi işlem gerçekleşiyor. İşletim sisteminin çeşitli
bileşenleri ve çekirdeği bu dosyayı okuyor, ana belleğe yani RAM’e açıyor ve
programın içerisinde bulunan CPU komutlarını çalıştırmaya başlıyor. İşte
çalışan programlara temel olarak process diyoruz.
Process’ler kernel tarafından oluşturulan ve takip edilen birimler. Kernel
içerisinde her process için bir veri yapısı içerisinde çeşitli bilgiler
tutuluyor. Bu tutulan bilgilerin ne olduğu işletim sisteminden işletim sistemine
değişiyor. xv6, Linux gibi sistemlerde process’e ait temel bilgiler arasında
PID yani Process ID geliyor. İşletim sistemi, her process’e herhangi
bir t
anında sistemde tekil yani unique olan bir tam sayı atıyor, TC kimlik no
gibi düşünün ama çok daha kısa. Yani herhangi bir anda aynı PID değerine sahip
iki farklı process sistemde yer almıyor. Fakat TC kimlik no’dan bir farkı var,
ölen bir kişinin kimlik numarası yeni doğan birine verilmiyor yani evrensel
olarak tekil gibi düşünebiliriz. Sistem uzun süre açık olursa ve sürekli bir
process oluşturulması ve process’in sonlanması gerçekleşirse PID değerleri
tükenebilir, o yüzden Linux gibi sistemlerde eski fakat kullanılmayan PID
değerleri tekrar başka process’lere verilebilir. Sistem açıldıktan kapanana
kadar bir PID değeri sadece tek bir process’i gösterecek diye bir kıstas yok ama
elbette herhangi bir anda bir PID değeri bir adet process’i göstermeli. Bu
dediklerim Linux gibi çekirdekler için geçerli. xv6’nın çekirdek kodundan
anladığım kadarıyla böyle bir tedbire gidilmemiş. Bu kadar fazla process’in
yaratılıp, ölüp, tekrar yaratılacağı düşünülmemiş gibi anlıyorum ama yanılıyor
da olabilirim, ilerleyen bölümlerde deneriz, belki bir fork
bomb saldırısı yaparız kernel’e.
Bununla beraber bir process ile tutulan en önemli bilgilerden biri de file
descriptor table yani o process’e açık olan dosyalar ile ilgili bilgiler.
Örneğin biz open()
ile dosya açtıkça aslında kernel bu tablonun bir satırında
dosya ile ilgili bilgiler tutuyor.
Elbette tahmin edersiniz ki C’de adı tablo
olan bir tür ya da veri yapısı yok.
Ama satırlardan oluşan tabloyu çok güzel modelleyebilen bir C aracımız var:
array yani diziler!
xv6: proc
, struct proc
ve struct file
Şimdi xv6 kernel kodunda bu veri yapılarını bulmaya çalışalım. xv6 minimal ve temiz yazılmış kodlara sahip, o yüzden bulmak zor olmuyor.
Not
Yazının ilerleyen kısımlarında f5b93ef
nolu
commit’i
referans alacağım.
Önce şu koda bakalım:
9struct cpu cpus[NCPU];
10
11struct proc proc[NPROC];
12
13struct proc *initproc;
Her bir process için tutulan veri yapısının türü struct proc
. xv6 basit bir
kernel olduğu için aynı anda sistemde bulunabilecek process sayısı limitlenmiş.
Bu maximum sayı, NPROC
, kadar struct proc
türünden eleman tutabilecek proc
isimli bir dizi oluşturuluyor. Yani kernel maximum sayıda process olacakmış gibi
bu alanı en baştan ayırıyor. Elbette bu yöntem en optimum yöntem değil. Çünkü
biz çoğu zaman 1-2 process çalıştırıyor oluyoruz. Bu da bellek tüketimi
açısından verimli bir yaklaşım değil, kullanmadığımız birçok alan kalıyor. Diğer
bir problemli yanı da genişlemeye imkan sağlamıyor olması. Yani derleme
zamanında belli olan NPROC
değerini çalışma zamanında yani runtime sırasında
değiştiremiyoruz. Yine de basit bir kernel için mantıklı bir tasarım tercihi
diyebiliriz.
NPROC
ise şurada tanımlı:
1#define NPROC 64 // maximum number of processes
2#define NCPU 8 // maximum number of CPUs
3#define NOFILE 16 // open files per process
Varsayılan olarak bu değer 64. Elbette ilgili dosyayı değiştirerek bu değerle
oynayabiliriz. Peki struct proc
nasıl bir şey?
84// Per-process state
85struct proc {
86 struct spinlock lock;
87
88 // p->lock must be held when using these:
89 enum procstate state; // Process state
90 void *chan; // If non-zero, sleeping on chan
91 int killed; // If non-zero, have been killed
92 int xstate; // Exit status to be returned to parent's wait
93 int pid; // Process ID
94
95 // wait_lock must be held when using this:
96 struct proc *parent; // Parent process
97
98 // these are private to the process, so p->lock need not be held.
99 uint64 kstack; // Virtual address of kernel stack
100 uint64 sz; // Size of process memory (bytes)
101 pagetable_t pagetable; // User page table
102 struct trapframe *trapframe; // data page for trampoline.S
103 struct context context; // swtch() here to run process
104 struct file *ofile[NOFILE]; // Open files
105 struct inode *cwd; // Current directory
106 char name[16]; // Process name (debugging)
107};
Oldukça kalabalık bir veri yapısı. Elbette Linux gibi “gerçek” işletim
sistemlerinde daha da çok eleman oluyor fakat mantık olarak aynı. Burada
dikkat ederseniz başka kullanıcı tanımlı veri yapıları da var, enum
ve diğer
struct
lar.
int pid
elemanı, process id’yi tutuyor. char name[16]
ile process’in adı
tutuluyormuş ama yorumda debug amaçlı olduğu söyleniyor. Yani insanlar için
konmuş, kernel bir process’i pid
değerinden tanıyor, isim onun için önemli
değil.
Burada bulunan tüm elemanlara şu an bakmamız pek doğru olmaz, zamanı geldikçe inceleriz. Ama birkaç tanesini görelim:
82enum procstate { UNUSED, USED, SLEEPING, RUNNABLE, RUNNING, ZOMBIE };
enum procstate
ile process’in state’i yani durumu belirtiliyor. Bu kavramlara
ilerleyen kısımlarda muhtemelen scheduler başlığı altında değiniriz. Bir process,
çalışmayı bekliyor (RUNNABLE), aktif olarak CPU’da çalışıyor (RUNNING) gibi
durumlarda olabilir.
1// Saved registers for kernel context switches.
2struct context {
3 uint64 ra;
4 uint64 sp;
5
6 // callee-saved
7 uint64 s0;
8 uint64 s1;
9 uint64 s2;
10 uint64 s3;
11 uint64 s4;
12 uint64 s5;
13 uint64 s6;
14 uint64 s7;
15 uint64 s8;
16 uint64 s9;
17 uint64 s10;
18 uint64 s11;
19};
Bir diğer ilginç eleman da struct context
türünden olan. Bu tür, context
switch işlemleri sırasında CPU registerlarını depolamak için kullanılıyor.
Bunlar RISC-V işlemciye ait register’lar.
Şimdi gelelim file descriptor table a yani struct file *ofile[NOFILE];
elemanına. Burada NOFILE
isminde sembolik bir sabit var.
1#define NPROC 64 // maximum number of processes
2#define NCPU 8 // maximum number of CPUs
3#define NOFILE 16 // open files per process
Burada da NPROC
gibi bir mantık var aslında. Bir process’in bir t
anında
açık tutabileceği maksimum dosya sayısı NOFILE
ile limitlenmiş durumda.
NPROC
için söylediğimiz her şey burada da geçerli (bellek verimliliği konuları
ve limitler).
struct file *ofile[NOFILE];
ifadesine bir bakalım. Burada ofile
isminde bir
dizi var, NOFILE
kadar eleman tutabiliyor, yani varsayılan değeri ile 16 adet.
Her bir elemanın türü struct file*
, yani struct file
türünden bir nesneye
bir pointer. Yani burada bir pointer dizisi var.
1struct file {
2 enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type;
3 int ref; // reference count
4 char readable;
5 char writable;
6 struct pipe *pipe; // FD_PIPE
7 struct inode *ip; // FD_INODE and FD_DEVICE
8 uint off; // FD_INODE
9 short major; // FD_DEVICE
10};
struct file
türü bu şekilde tanımlanmış. Örneğin her bir dosyanın bir türü,
yani type
değeri var. Ya da dosya readable
mı ve writable
mı? Bunlar da
burada tutuluyor. Tüm elemanlara şimdi bakmayalım, dosya sistemi bölümüne
saklayalım. Buradaki önemli elemanlardan biri de uint off;
elemanı yani,
offset değeri. Biz dosyaya okuma ve yazma yaptığımız zaman bir sonraki okuma ve
yazmanın nereye yapılacağı bu off
elemanında saklanıyor. Örneğin arka arkaya
write(fd, "Merhaba Dunya!\n", 15)
çağırırsak aslında bunlar aynı dosyada üst
üste değil, ard arda yazılacaktır. Çünkü her yazmada ve okumada kernel bu değeri
değiştirmekte daha doğrusu arttırmaktadır. Bu sayede biz write()
ve read()
çağrıları yaptıkça otomatik olarak dosyada ilerleriz. Burada her iki fonksiyon
için ayrı birer offset değeri olmadığına dikkat edelim. Yani write()
yaptıkça off
değerinin değişmesi ardından yapılacak read()
in nereden
yapılacağını etkiliyor.

struct proc
ve struct file
arasındaki ilişki görüldüğü gibidir. ofile[]
dizisinin elemanları birer struct file*
dır yani pointer’dır. Her biri
bir struct file
türünü gösterir. Elbette açık olan dosya sayısı kaç ise,
o kadar ofile
elemanın gösterdiği yer geçerlidir. Yukarıdaki örnekte 3 adet
açık dosya varmış gibi düşünebilirsiniz.
struct file
içerisindeki uint off
üyesini sadece bir örnek olsun diye yazdım.
Yeşil okların gösterdiği yerler sanki bu elemanın adresi gibi gözüküyor, bu
şekilde değil. ofile[]
elemanları struct file
nesnelerinin başlangıcını,
yani ilk elemanını gösteriyor. Göbekten bir elemanı göstermiyor yani.
Verilerin Bellekte Tahsis Edilmesi
struct file *ofile[NOFILE];
ifadesini hatırlayalım. Bir process oluşturulduğu
zaman bu pointer dizisinin gösterdiği elemanların bellekte tahsis edilmesi
gerekmektedir. Çünkü bunlar birer pointer fakat gösterdikleri yerde gerçekten
de struct file
türünden nesneler olmalı ki dereference edebilelim, yani *ptr
şeklinde erişebilelim.
Aşağıdaki koda bakalım:
344if((f = filealloc()) == 0 || (fd = fdalloc(f)) < 0){
345 if(f)
346 fileclose(f);
347 iunlockput(ip);
348 end_op();
349 return -1;
350}
Yukarıdaki kodda f
, struct file *f
şeklinde tanımlanmış. Burada minik bir C
hilesi var. if
içerisindeki ||
yani logical OR operatörü bir sequence
point tanımlıyor ve ayrıca bu operatörün short circuit özelliğinden
faydalanılmış. Burada ilk olarak f = filealloc()
işleminin yapılacağı C
standartları tarafından garanti ediliyor. Eğer == 0
ile karşılaştırma doğru
olursa yani f
aslında null-pointer olursa, fd = fdalloc(f)
işlemi ||
operatörünün short-circuit özelliğinden dolayı yapılmıyor. Böylece, fd = fdalloc(f)
ifadesinin sadece f
in null-pointer olmadığı yani filealloc()
işleminin başarılı olduğu durumda yapılacağı garanti ediliyor.
28// Allocate a file structure.
29struct file*
30filealloc(void)
31{
32 struct file *f;
33
34 acquire(&ftable.lock);
35 for(f = ftable.file; f < ftable.file + NFILE; f++){
36 if(f->ref == 0){
37 f->ref = 1;
38 release(&ftable.lock);
39 return f;
40 }
41 }
42 release(&ftable.lock);
43 return 0;
44}
45
46// Increment ref count for file f.
47struct file*
48filedup(struct file *f)
49{
50 acquire(&ftable.lock);
51 if(f->ref < 1)
52 panic("filedup");
53 f->ref++;
54 release(&ftable.lock);
55 return f;
56}
filealloc()
yani file allocate fonksiyonu bize bir yerlerden bir
struct file
nesnesi buluyor ve bunun adresini dönüyor. Peki nereden?
ftable
denen bir yerden. Bu da yine aynı dosyada tanımlı.
17struct {
18 struct spinlock lock;
19 struct file file[NFILE];
20} ftable;
Yine bir sembolik sabit ile karşlaştık, NFILE
.
4#define NFILE 100 // open files per system
Bu sefer de sistem genelinde açık olabilecek toplam dosya sayısının bir limiti
olduğunu gördük. Yani varsayılan olarak bir process 16 adet dosya açabiliyordu,
NOFILE
, fakat sistem genelinde toplamda 100 adet açık dosya olabiliyor, NFILE
.
Elbette bunlar varsayılan değerler, değiştirebilirsiniz.
Yani filealloc()
aslında zaten derleme sırasında statik olarak allocate
edilmiş bir diziden bize bir adres dönüyor.
37// Allocate a file descriptor for the given file.
38// Takes over file reference from caller on success.
39static int
40fdalloc(struct file *f)
41{
42 int fd;
43 struct proc *p = myproc();
44
45 for(fd = 0; fd < NOFILE; fd++){
46 if(p->ofile[fd] == 0){
47 p->ofile[fd] = f;
48 return fd;
49 }
50 }
51 return -1;
52}
fdalloc()
ise bu struct file
türünden nesnenin adresini o processin boş
olan ilk ofile[]
elemanına yerleştiriyor ve bu elemanın indeks değerini
dönüyor. Yani günün sonunda open()
ile bu şekilde bir file descriptor değeri
almış oluyoruz. Bu kod sayesinde open()
ile alacağımız fd değerinin olabilecek
en düşük değer olacağı garanti ediliyor.
Hatırlarsanız bir önceki örneklerde fd
değeri olarak 3
değerini alıyorduk.
Çünkü ofile[0]
, ofile[1]
ve ofile[2]
dolu oluyordu. Bunlar sırası ile
stdin
, stdout
ve stderr
dosyalarını gösteriyorlar.
Hadi Gözlemleyelim! 👀
Madem elimizde QEMU ve GDB var, e bunlara bir bakalım.
Okumadıysanız: GDB ile Hata Ayıklama (Debug 🐛)
Standart debug altyapımız ile sistemi boot edip, gdb
den kernel içerisinde
bulunan bu veri yapılarına bakmaya çalışalım.
(gdb) file kernel/kernel
Reading symbols from kernel/kernel...
(gdb)
(gdb) c
Continuing.
Şimdi şu ftable
ı bir görmeye çalışalım. ctrl-c
ile durduruyoruz ve print
ediyoruz.
(gdb) p ftable
$1 = {lock = {locked = 0, name = 0x80008648 "ftable", cpu = 0x0}, file = {{
type = FD_DEVICE, ref = 6, readable = 1 '\001', writable = 1 '\001',
pipe = 0x0, ip = 0x80016f08 <itable+160>, off = 0, major = 1}, {
type = FD_NONE, ref = 0, readable = 1 '\001', writable = 1 '\001',
pipe = 0x0, ip = 0x80016f08 <itable+160>, off = 0, major = 1}, {
type = FD_NONE, ref = 0, readable = 0 '\000', writable = 0 '\000',
pipe = 0x0, ip = 0x0, off = 0, major = 0} <repeats 98 times>}}
(gdb)
ftable
yapısını hatırlayalım:
struct {
struct spinlock lock;
struct file file[NFILE];
} ftable;
İlk olarak lock
elemanının içeriğini görüyoruz, türü struct lock
. Şimdilik
bununla ilgilenmiyoruz. Daha sonra file[0]
ın içeriğini görüyoruz.
struct file
ı hatırlayalım:
struct file {
enum { FD_NONE, FD_PIPE, FD_INODE, FD_DEVICE } type;
int ref; // reference count
char readable;
char writable;
struct pipe *pipe; // FD_PIPE
struct inode *ip; // FD_INODE and FD_DEVICE
uint off; // FD_INODE
short major; // FD_DEVICE
};
Türü, FD_DEVICE
olan bir dosya sistemde açık, şimdilik bununla ilgilenmiyoruz.
Onun dışında sanki açık dosyamız yok. Di mi? Diğerleri hep FD_NONE
. Daha
doğrusu ref=0
bize o elemanın bir dosyayı göstermediğini bize söylüyor.
Bu çok garip değil çünkü hiç bir program çalıştırmıyoruz, hiç bir dosya açmadık.
Şimdi bir tane dosya açıp biz programı sonlandırana kadar bekleyen bir program yazalım.
1#include "kernel/types.h"
2#include "user/user.h"
3#include "kernel/fcntl.h" //O_WRONLY vs
4
5static char buf[1];
6
7int main() {
8 int fd;
9
10 fd = open("not.txt", O_RDWR|O_CREATE|O_TRUNC);
11
12 if (fd < 0){
13 fprintf(2, "fd = %d not.txt acilamadi!\n", fd);
14 exit(1);
15 }
16
17 printf("fd = %d\n", fd);
18
19 read(0, buf, 1);
20
21 exit(0);
22}
Not
acbekle.c
de Aç Bitir gibi oldu, ürün yerleştirme yaptım!
Yukarıdaki program, not.txt
isimli bir dosyayı açıyor ve kullanıcıdan
stdin
den bir girdi gelene kadar bekliyor. Bu noktada not.txt
hep açık
kalıyor.
Şimdi tekrar debug başlatalım. Programımızı çalıştıralım ve program read()
kısmında beklerken gdb
üzerinden ftable
içeriğine tekrar bakalım.
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ acbekle
fd = 3
GDB:
(gdb) print ftable
$1 = {lock = {locked = 0, name = 0x80008648 "ftable", cpu = 0x0}, file = {{
type = FD_DEVICE, ref = 9, readable = 1 '\001', writable = 1 '\001',
pipe = 0x0, ip = 0x80016f08 <itable+160>, off = 0, major = 1}, {
type = FD_INODE, ref = 1, readable = 1 '\001', writable = 1 '\001',
pipe = 0x0, ip = 0x80016f90 <itable+296>, off = 0, major = 1}, {
type = FD_NONE, ref = 0, readable = 0 '\000', writable = 0 '\000',
pipe = 0x0, ip = 0x0, off = 0, major = 0} <repeats 98 times>}}
(gdb)
Burada gördüğünüz üzere şimdi file[1]
de dolu, FD_INODE
tipinde ve ref = 1
olmuş. Dosya sistemi konusuna ileride bakarız ama bu bizim dosyamız mı bunu
görebilir miyiz?
struct file
ın ip
elemanı struct inode
türünden bir nesneye bir pointer.
Burada adresinin değerinin ip = 0x80016f90
olduğunu görüyoruz. Bu adresi,
struct inode
türüne cast edip içeriğine bakalım.
(gdb) p (struct inode)*(0x80016f90)
$2 = {dev = 1, inum = 25, ref = 1, lock = {locked = 0, lk = {locked = 0,
name = 0x80008638 "sleep lock", cpu = 0x0}, name = 0x80008570 "inode",
pid = 0}, valid = 1, type = 2, major = 0, minor = 0, nlink = 1, size = 0,
addrs = {0 <repeats 13 times>}}
Şimdi dediğim gibi dosya sistemi konusuna gelmedik ama burada inum = 25
değerine dikkat edelim.
Şimdi programımızı klavyeden bir şeye basarak sonlandıralım. Tabii öncesinde
GDB’de c
ile devam edelim. xv6 üzerinde bir ls
çekelim:
$ ls
. 1 1 1024
.. 1 1 1024
README 2 2 2305
xargstest.sh 2 3 93
cat 2 4 32848
echo 2 5 31696
forktest 2 6 15824
grep 2 7 36224
init 2 8 32192
kill 2 9 31656
ln 2 10 31480
ls 2 11 34784
mkdir 2 12 31712
rm 2 13 31696
sh 2 14 54144
stressfs 2 15 32584
usertests 2 16 180624
grind 2 17 47536
wc 2 18 33800
zombie 2 19 31056
merhaba 2 20 31160
loop 2 21 31792
not 2 22 31912
acbekle 2 23 31512
console 3 24 0
not.txt 2 25 0
not.txt
nin yanındaki 25
değeri dikkatinizi çekti mi? İşte bu değer o dosyanın
i-node değeri. Yani ftable.file[1]
gerçekten de bu dosyaymış!
İşte bu da ls
in bastığı 25 değerinin i-node olduğunun kanıtı (açık kaynağın
gücü 🤪):
46case T_FILE:
47 printf("%s %d %d %l\n", fmtname(path), st.type, st.ino, st.size);
48 break;
Bu sefer process’imize ait struct proc
içeriğindeki ofile[3]
ün, çünkü
fd = 3
görüyoruz, ftable.file[1]
nesnesini gösterdiğinden emin olalım.
struct proc proc[NPROC]
içerisinden bizim process’in bilgilerini bulmak için
PID
değerine ihtiyacımız olacak. Neyse ki xv6’nın int getpid(void);
şeklinde
bildirilen bir fonksiyonu var. Bu bizim processimizin PID değerini bizi söylecek.
Programımızı modifiye edelim:
1#include "kernel/types.h"
2#include "user/user.h"
3#include "kernel/fcntl.h" //O_WRONLY vs
4
5static char buf[1];
6
7int main() {
8 int fd;
9
10 fd = open("not.txt", O_RDWR|O_CREATE|O_TRUNC);
11
12 if (fd < 0){
13 fprintf(2, "fd = %d not.txt acilamadi!\n", fd);
14 exit(1);
15 }
16
17 printf("fd = %d\n", fd);
18 printf("PID = %d\n", getpid());
19
20 read(0, buf, 1);
21
22 exit(0);
23}
Bunu derleyip biraz önceki gibi debug edelim:
xv6 kernel is booting
hart 1 starting
hart 2 starting
init: starting sh
$ acbekle
fd = 3
PID = 3
Ne tesadüftür ki PID
değerimiz de 3 imiş. O zaman gdb’den proc
a bir bakalım.
(gdb) p proc
...
{lock = {locked = 0, name = 0x800081b8 "proc", cpu = 0x0},
state = SLEEPING, chan = 0x80021d28 <cons+152>, killed = 0, xstate = 0,
pid = 3, parent = 0x80008ed8 <proc+360>, kstack = 274877878272,
sz = 16384, pagetable = 0x87f43000, trapframe = 0x87f60000, context = {
ra = 2147488962, sp = 274877882000, s0 = 274877882048, s1 = 2147520576,
s2 = 2147518784, s3 = 1, s4 = 4096, s5 = 1, s6 = 1, s7 = 4,
s8 = 18446744073709551615, s9 = 10, s10 = 361700864190383365,
s11 = 361700864190383365}, ofile = {0x80018a70 <ftable+24>,
0x80018a70 <ftable+24>, 0x80018a70 <ftable+24>, 0x80018a98 <ftable+64>,
0x0 <repeats 12 times>}, cwd = 0x80016e80 <itable+24>,
name = "acbekle\000\000\000\000\000\000\000\000"},
...
Bunun çıktısı biraz kalabalık oluyor, ben sizin için temizledim. Şimdi, pid=3
olan struct proc
nesnesini bulduk. Burada ofile
kısmına bakalım.
Dikkat edersek ilk 4 elemanı dolu, sonrası 0. İlk 3 eleman process’in
standart akımları. Fakat 4. eleman yani [3]
olan bizim not.txt
dosyamız.
Değerinin 0x80018a98
olduğuna dikkat edelim. Bu ilgili struct file
nesnesinin adresi olmalı. Peki öyle mi? Bir önceki GDB çıktısında bu dosyanın
aslında ftable.file[1]
olduğunu görmüştük. GDB’ye bunun adresini soralım:
(gdb) print &ftable.file[1]
$2 = (struct file *) (gdb) print &ftable.file[1]
$2 = (struct file *) 0x80018a98 <ftable+64> <ftable+64>
Ve bize 0x80018a98
değerini döndü.
Yani process’in 3 nolu file descriptor değeri gerçekten de not.txt’yi gösteriyormuş
Siz dilerseniz başka şeyleri de gözlemleyebilirsiniz. Mesela yazma ve okuma
yaptıkça struct file
ın off
üyesinin değeri nasıl değişiyor, buna
bakabilirsiniz. Ben artık burada kesiyorum.
Özet
Bu bölümde, genel olarak process kavramına ve xv6 üzerindeki implementasyonuna
baktık. GDB ile de çalışan bir sistem üzerinde gözlemledik. Process’lerle ilgili
kernelde saklanan en önemli verilerden biri de file descriptor table dır.
Elbette struct proc
içerisindeki
Buna vurgu yapmamın sebebi ilerleyen bölümlerde göreceğimiz fork()
ve exec()
sistem çağrıları ve bu arada yapılan I/O redirection işlemleridir.