/*
  Bu belgenin telif hakları Necati Ersen ŞİŞECİ'ye aittir.
  Kök: http://acikkod.org
  İlk baskı: 2004-04-22
  Son değişiklik: 2004-04-22
  Bu döküman Açıkkod.ORG Belge Yazım ve Dağıtım Lisansı ile dağıtılmaktadır.
*/

/dev/null Aygıtı ve Sürücü Programlama

Bu bolumde, /dev/null aygıtının sürücüsünün nasıl yazıldığını inceleyeceğiz.

Linux çekirdeğinde her aygıt için yapılması gereken belli başlı işlemler vardır.
 Okuma (read), yazma(write), açma (open) gibi.

Linux'de bir sistem çağrısı oluştuğu zaman (örneğin open), hangi aygıt için
oluşturtu ise, o aygıtın ilgili fonksiyonu çalıştırılır.

Bu işlemler, Linux çekirdeğinde karakter aygıtları için file_operations isimli
bir yapıda tutulur. linux/fs.h ta tanımlanan file_operations yapısı aşağıdaki
gibidir.

struct file_operations {
    loff_t (*llseek) (struct file *, loff_t, int);
    ssize_t (*read) (struct file *, char *, size_t, loff_t *);
    ssize_t (*write) (struct file *, const char *, size_t, loff_t *);
    int (*readdir) (struct file *, void *, filldir_t);
    unsigned int (*poll) (struct file *, struct poll_table_struct *);
    int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);
    int (*mmap) (struct file *, struct vm_area_struct *);
    int (*open) (struct inode *, struct file *);
    int (*flush) (struct file *);
    int (*release) (struct inode *, struct file *);
    int (*fsync) (struct file *, struct dentry *);
    int (*fasync) (int, struct file *, int);
    int (*check_media_change) (kdev_t dev);
    int (*revalidate) (kdev_t dev);
    int (*lock) (struct file *, int, struct file_lock *);
} ;

Biz bunlardan sadece gerekenleri kullanacağız.

Bu yapının amacı, sistem bu aygıt ile ilgili oluşan sistem çağrılarını yerine
getirirken hangi fonksiyonları kullanacağını belirtmektir.

Hangi fonksiyonları kullanacağımızı belirledikten sonra kullanacağımız aygıtı
seçmeliyiz. Bu aygıtın bir major ve minor numarası olması gerektiğini biliyoruz.
Yapmamız gerek şey, sisteme bu aygıt için yapılan sistem çağrılarında kullanılacak
fonksiyonların tutulduğu, tanımladığımız file_operations yapısını kullanması
gerektiğini belirtmek. Bu işlemi init_module() fonksiyonu içinde yapmamız gerekiyor.

Sisteme bir karakter aygıt eklemek istediğimizde;

int register_chrdev(unsigned int, const char *, struct file_operations *)

şeklinde tanımlanan register_chrdev() fonksiyonunu kullanıyoruz. Burada birinci
parametre, aygıtın major numarası, ikinci parametre aygıtın ismi, üçüncüsü ise
tanımladığımız file_operations yapısına pointerdir.

 i = register_chrdev (201, "bdevice", &fops);

register_chrdev() fonksiyonunu major numarası olarak 0 verirsek, sistem otomatik
olarak aygıta bir major numarası verecek ve fonksiyonun geri dönüş değeri aygıt
için kullanılacak olan major numarası olacaktır.

register_chrdev() fonksiyonu ikinci parametre ile belirttiğimiz aygıt olup olmadığını
kontrol etmez. Bu yüzden işlem başarılı ise verdiğimiz parametreye göre 0 yada major
numarası ile geri dönecektir.

int init_module (void)
{
    int i = 0;
    i = register_chrdev (device_major_number, DEVICE_NAME , &fops);
    if (i < 0) return i;
    if (device_major_number == 0) device_major_number = i;
    return 0;
}

İşlerimiz bitti ve modülü bellekten atacağız. Bu durumda yapılması gereken şey
cleanup_module() fonksiyonu ile kullandığımız aygıtı unregister etmek.

int unregister_chrdev(unsigned int major, const char * name);

unregister_chrdev() fonksiyonundaki ilk parametre aygıtın major numarası, ikincisi
ise aygıtın ismidir.

void cleanup_module (void)
{
    unregister_chrdev (201, "bdevice");
}


/dev/null aygıtı adından da anlaşıldığı gibi hiç bir şey yapmayan, herhangi bir
bellek blogu kullanmayan okuma ve yazma işlemleri yapılabilen bir aygıttır. Yazmak
istediğimizde yazmak istediğimiz tüm data nın yazıldığını, okumak istediğimizde
ise 0 dondurur.

siseci:~/kernel# cat /dev/null
siseci:~/kernel# cat /etc/passwd > /dev/null
siseci:~/kernel# cat /dev/null
siseci:~/kernel#

Aygıtımız bir karakter aygıt olduğu için open ve close sistem çağrılarını yanıtlayacak
fonksiyonlara ihtiyacımız olmayacak.

read ve write işlemleri için iki fonksiyon yazmamız gerekecek.

Sistemde çalışan null aygıtı var olduğu için aygıtın adını null2 olarak kullanalım.

/dev/null2 aygıtımız için read system cağrısı oluştuğunda bu çağrıyı
null2_read() fonksiyonu ile cevaplayalım. read işlemi sonucunda kullanıcıya
dosya sonunda gelindi yada okunacak bilgi yok anlamına gelen 0 döndermemiz
gerekiyor.

read() fonksiyonunun nasıl kullanıldığını zaten file_operations yapısından biliyoruz.

static ssize_t
null2_read (struct file * file, char * buf, size_t count, loff_t *ppos)
{
    return 0;
}

write işlemi için ise, kullanıcı kaç bayt yazmak istediyse, o kadar bilginin yazıldığını
doğrulamak için, kullanıcının bize parametre olarak verdiği uzunluğu, kullanıcıya geri
döndermemiz yeterli olacaktır. Fonksiyonumuz null2_write() olsun.

static ssize_t
null2_write (struct file * file, const char * buf, size_t size, loff_t * ppos)
{
    return size;
}

file_operations yapımıza baktığımızda,

static struct file_operations null2_fops = {
    NULL,                /* lseek   */
    null2_read,          /* read    */
    null2_write,         /* write   */
    NULL,                /* readdir */
    NULL,                /* select  */
    NULL,                /* ioctl   */
    NULL,                /* mmap    */
    NULL,                /* open    */
    NULL,                /* release */
    NULL,                /* fsync   */
} ;

şeklinde olacaktır.
Tüm koda bakacak olursak;

#ifndef __KERNEL__
  #define __KERNEL__
  #define MODULE
  #define LINUX
#endif

#include <linux/module.h>
#include <linux/version.h>
#include <linux/fs.h>

static ssize_t null2_read  (struct file *, char *      , size_t, loff_t *);
static ssize_t null2_write (struct file *, const char *, size_t, loff_t *);

static struct file_operations null2_fops = {
    read:       null2_read,
    write:      null2_write,
};

static ssize_t
null2_read (struct file * file, char * buf, size_t count, loff_t *ppos)
{
    return 0;
}

static ssize_t
null2_write (struct file * file, const char * buf, size_t size, loff_t * ppos)
{
    return size;
}

int init_module (void)
{
    int i = 0;
    i = register_chrdev (222, "null2", & null2_fops);
    if (i < 0) return i;
    return 0;
}

void cleanup_module (void)
{
    unregister_chrdev (222, "null2");
}


Derleyip sisteme yükleyelim.

siseci:~/kernel# mknod /dev/null2 c 222 0
siseci:~/kernel# gcc -Wall -c null2.c
siseci:~/kernel# ls -al null2.o
-rw-------   1 root     root         1468 Jan  1 03:07 null2.o
siseci:~/kernel# insmod null2
siseci:~/kernel# echo Deneme > /dev/null2
siseci:~/kernel# cat /dev/null2
siseci:~/kernel# ls -al /dev/null2
crw-------   1 root     root     222,   0 Jan  1  1998 /dev/null2
siseci:~/kernel# rmmod null2

Böylece sisteme yüklemiş ve testini de yapmış olduk. Yukardaki testlerden de anlaşıldığı
üzere null2 aygıtı, sistemdeki null aygıtı gibi davranmaktadır.

Linux çekirdek kodlarına baktığımızda null (linux/drivers/char/mem.c ) aygıtının,
lseek() sistem çağrısı için de bir fonksiyon yazılmış. Bu dosyaya göz atmanızı tavsiye
ederim.


Necati Ersen ŞİŞECİ

25 Şubat 2004