xxx
130-012625
Gelelim device driver yani aygıt sürücü kavramına. Şimdiye kadar biraz kernel modüllerine baktık ama bizim modüllerimiz user moddan erişilmez durumdadır.
Device driver’ın bir tanımı da user moddan erişilebilen, kullanılabilen kernel modüldür diye yapılmaktadır. Ama bu tanım bence biraz eksiktir. Bir device driver’ın adı üstünde biraz da gerçek bir cihazla ilişkisi olması gerekir. Elbette LDD3’te de örnek verildiği gibi hayali cihazlar da yaratılabilir.
Her kernel modül bir device driver değildir ama her device driver bir kernel modüldür. (statik derlenmediyse ?)
Aygıt sürücüler, open() fonksiyonu ile bir dosya gibi açılırlar. Buradan bir
dosya betimleyici elde edilir. Bu betimleyici read(), write(), lseek(),
close() gibi fonksiyonlarla kullanılabilir. Aygıt sürücülere ilişkin bir dosya
betimleyicisi böyle bir fonksiyonla kullanıldığında aslında sürücü içerisindeki
belirlenen fonksiyonlar çağrılır. Ayrıca ioctl() POSIX fonksiyonu ile (o da
sys_ioctl() isimli sistem fonksiyonunu çağırmaktadır) sürücü içerisindeki
foo(), bar() gibi “rastgele” fonksiyonları da çağırabiliyoruz. Aslında bir
nevi user moddan, kernel modda “istediğimiz” fonksiyonları kullanabiliyoruz.
Böylece prosesimiz normalde user modda yapamayacağı şeyleri kernel modda yapar
hale geliyor. Aygıt sürücü ile user moddaki proses arasındaki veri transferi
sanki bir dosya varmış gibi yapılabilmektedir. Driver’ın fonksiyonlarını
çağırmak arka planda aslında mekanizma olarak bir sistem çağrısı yapmaya
benzemektedir.
131-000030
Peki aygıt sürücüyü open() ile açıyorsak, biz yol ifadesi olarak ne
vereceğiz? Yani “nereyi açacaklar?”
Device file yani aygıt dosyası olan dizin girişleri var. Bunlar gerçek
disk dosyası değiller ama arka planda inode elemanı tutuluyor kernel tarafından
bunlar için. O inode elemanı aslında aygıt sürücüyü gösteriyor. Tipik olarak
/dev altındaki dosyalar böyle dosyalardır.
ls ile listelediğimiz zaman c character device, b ise block device
demektir.
Aygıt sürücüler bu iki tipe ayrılmaktadır. Karakter aygıt sürücüleri daha sık kullanılmaktadır. Blok aygıt sürücüleri tipik olarak disk gibi medyalara erişmek için kullanılır. Bu sürücülerde aktarım blok blok yapılır. Bu işlemlerin daha etkin gerçekleştirilmesi için çekirdeğin buffer cache ya da page cache gibi alt sistemleri de devreye sokulur. Karakter aygıt sürücüleri de blok blok aktarım yapMAyan aygıtlar için kullanılır. Seri port sürücüleri karakter aygıtı olarak çalışırken RAM disk oluşturan sürücüler blok aygıtı sürücüleridir. Karakter aygıtı sürücüsü yazmak blok aygıtına göre daha kolaydır.
Major ve Minor Numara
Aygıt dosyalarında major ve minör numara diye bir kavram vardır.
Örnek:
$ ls -lah /dev/tty
crw-rw-rw- 1 root tty 5, 0 Dec 27 14:42 /dev/tty
Burada c bir character device olduğunu göstermektedir. Major numara 5,
minör numara ise 0 dır.
Dosya ile ilgili bilgiler stat(), fstat(), lstat() gibi POSIX
fonksiyonları ile elde edilmektedir. struct stat yapısının dev_t türünden
st_rdev elemanı eğer dosya bir aygıt dosyası ise dosyanın major ve minör
numaralarını belirtir. <sys/stat.h> dosyasındaki S_ISCHR ve S_ISBLK
makroları ile ilgili dosyanın bir aygıt dosyası olup olmadığını öğrenebiliriz.
[1] [2]
Genelde major numara onun türünü anlatırken minör numara da onun aynı türden hangi cihaz olduğunu gösterir. Mesela bilgisayarımızda iki seri port varsa tipik olarak major numaraları aynı, minörleri farklı olacaktır.
Biz device driver yazarken diyoruz ki: Bu sürücünün major numarası şu minörü de
bu olsun. Sonra aynı numaralarda bir aygıt dosyası oluşturuyoruz. open() ile
açınca bizim driver’a gidiyor. Yani biz bu numaraları bir adres olarak da
düşünebiliriz. Ama aynı major ve minor numaraya sahip iki farklı aygıt dosyası
Linux üzerinde oluşturulabilir, partikte pek görmesek de. Bu durumda yapılan
işlemler, yani o dosyaya yapılan sistem çağrıları, aynı sürücüye gidecektir.
mknod() Fonksiyonu
Biz user tarafta open() ile disk dosyası, mkfifo() de FIFO dosyası
oluşturabiliyoruz. Benzer şekilde aygıt dosyaları için de ayrı bir fonksiyon
vardır. mknod() isimli POSIX fonksiyonu da aygıt dosyası oluşturmak için
kullanılır. [3] Linux’ta doğrudan aslında sys_mknod() isimli syscall
cağrılır bu fonksiyon tarafından.
#include <sys/stat.h>
int mknod(const char *path, mode_t mode, dev_t dev);
Birinci parametre aygıt dosyasının yol ifadesi, ikincisi erişim haklarını ve
üçüncüsü de major ve minör numarayı belirtmektedir. Major ve minör numara,
dev_t türü içerisinde beraber belirtilir.
İkinci izin parametresi ile beraber aslında dosyanın türünü de belirtmeliyiz.
Karakter mi, blok mu, FIFO mu vs. mknod() fonksiyonunu aslında mkfifo()
yerine de kullanabiliriz.
Aşağıdaki sembolik sabitlerden biri OR ile girilebilir:
S_IFCHR //(Karakter Aygıt Sürücüsü)
S_IFBLK //(Blok Aygıt Sürücüsü)
Belirttiğimiz gibi diğer dosya türlerini de oluşturabiliriz, her ne kadar bu
amaçla kullanmayacak olsak da. Bu durumda üçüncü parametre, dev_t, anlamsız
olmaktadır ve kullanılmaz.
Girilebilecek diğer değerler:
S_IFREG //(Disk dosyası yaratmak için)
S_IFIFO //(İsimli boru dosyası yaratmak için)
S_IFSOCK //(UNIX domain soket dosyası yaratmak için)
Fonksiyon başarı durumda 0, başarısızlık durumunda -1 döner ve errno
set edilir.
mknodat() Fonksiyonu
Bu fonksiyonun da at li (🐴) versiyonu vardır.
#include <fcntl.h>
int mknodat(int fd, const char *path, mode_t mode, dev_t dev);
Bildiğimiz at’li versiyonlar gibidir. Fonksiyon ilgili dizine ait dosya
betimleyicisini ve göreli yol ifadesini parametre olarak alır. O dizinden
göreli biçimde yol ifadesi oluşturulur. Birinci parametreye AT_FDCWD gibi
özel bir değer geçersek fonksiyon at’siz gibi çalışır. Benzer şekilde ikinci
parametre mutlak yol ifadesi ise, /xx, bu durumda birinci parametredeki dizin
yine kullanılmaz.
Her iki fonksiyon da prosesin umask değerini dikkate alır.
İlerleyen kısımlarda kod yazacağız ama önce mknod isimli komuta bakalım.
mknod Komutu
mknod isimli bir de shell fonksiyonu vardır. Shell komutlarını man 1 den
görebiliriz:
https://man7.org/linux/man-pages/man1/mknod.1.html
Böylece direk shell’den de böyle bir dosya yaratabiliriz. Örnek:
$ sudo mknod device c 123 321
$ ls -lah device
crw-r--r-- 1 root root 123, 321 Dec 27 19:13 device
Bu sayede device isminde istediğimiz major ve minör numaralarda bir aygıt
dosyası yarattık, türü de character, c. Peki sudo ya ihtiyacımız var mı?
Önce rm device ile dosyamızı silip sudo olmadan tekrar yaratmaya çalışalım.
rm için sudo yetkisi gerekmiyor.
$ mknod device c 123 321
mknod: device: Operation not permitted
Olmadı. O zaman strace ile bir bakalım.
$ strace mknod device c 123 321
...
mknodat(AT_FDCWD, "device", S_IFCHR|0666, makedev(0x7b, 0x141)) = -1 EPERM (Operation not permitted)
...
mknodat() bir sistem
çağrısı, sys call. EPERM açıklamasına bakalım.
EPERM mode requested creation of something other than a regular
file, FIFO (named pipe), or UNIX domain socket, and the
caller is not privileged (Linux: does not have the
CAP_MKNOD capability); also returned if the filesystem
containing path does not support the type of node
requested.
Yani root olmamız gerekiyormuş, teyit etmiş olduk.
Yardımcı Fonksiyon ve Makrolar
User Space
Major ve minör numaraların dev_t içerisinde beraber tutulduğunu söylemiştik.
Bunlar için user space’te sunulan çeşitli yardımcı araçlar vardır.
makedev() fonksiyonu,
major ve minör numaradan dev_t üretmemizi sağlar.
#include <sys/sysmacros.h>
dev_t makedev(unsigned int maj, unsigned int min);
unsigned int major(dev_t dev);
unsigned int minor(dev_t dev);
Benzer şekilde yukarıda prototipleri gösterilen major() ve minor() isimli
fonksiyonlar da dev_t türünden bir nesneden major ve minör numaraları
“ayıklamamızı” sağlar.
Aslında major ve minör numaralar, dev_t nin belli bitlerinde bulunmaktadır.
Fakat hangi bitlerinde bulunduğu sistemden sisteme değişeceği için bizim her
zaman bu fonksiyonları kullanmamız gerekir.
Kernel Space
Kernel space/mode yani modül/driver kısmında ise aynı işler için tanımlanmış makrolar vardır.
7#define MINORBITS 20
8#define MINORMASK ((1U << MINORBITS) - 1)
9
10#define MAJOR(dev) ((unsigned int) ((dev) >> MINORBITS))
11#define MINOR(dev) ((unsigned int) ((dev) & MINORMASK))
12#define MKDEV(ma,mi) (((ma) << MINORBITS) | (mi))
Gösterilen MAJOR(), MINOR() ve MKDEV() makroları aynı amaçla kullanılır.
Görüldüğü üzere aslında düşük 20-bit minör numarayı tutarken geri kalan bitler,
güncel durumda dev_t nin 32-bittir yani üst 12-bit, major numarayı tutar. Ama
hem user hem kernel tarafında bu bilgiyi doğrudan kullanmamamız gerekir.
18typedef u32 __kernel_dev_t;
19
20typedef __kernel_fd_set fd_set;
21typedef __kernel_dev_t dev_t;
mymknod Programımız
Şimdi POSIX fonksiyonu olan mknod() a geri dönüp mymknod isminde kendi
mknod programımızı yazalım.
Yapılacaklar
Yazı henüz bitmemiştir. 131-011945