sayfa başı

8 Mart 2015 Pazar

Adım Adım Linux Driver Yazıyoruz -2-

Adım Adım Linux Driver Yazıyoruz

    Adım Adım Linux Driver Yazıyoruz başlıklı yazıma devam ediyorum. Eğer bu yazının ilk kısmını okumadıysanız buradan ulaşabilirsiniz. Bu yazımda biraz daha teorik bilgiler ile açıklama yaptıktan sonra iştahla beklediğimiz kod örneklerine ulaşacağız. Biraz sabırla devam edelim ki konuyu tam anlamıyla öğrenelim.

Kernel Fonksiyonlarının Geri Dönüş Değerleri

    Kernel fonksiyonları kendini çağırana belli bir değer ile geri döner. Bu değerler belli bir kurala tabi tutulmuştur. Burada oluşturulan yöntem oldukça basittir, hatta kullanıcı katmanında da(user space) bu yöntemi uyguladığımızda oluyor. Bu yüzden bu konuya uzak değiliz.
  • Hata Durumu: Kernel fonksiyonları hata durumlarını iletmek için negatif değer döner. Fonksiyonun geri dönüş değerini sorguladığınızda eğer bir negatif değer ile karşılaşırsanız işler rast gitmiyor demektir. Tıpkı user space olduğu gibi Kernel seviyesinde de aldığınız hata mesajlarını açıklayan bir başlık dosyası bulunmaktadır. <linux/errno.h> İşte bu dosyada hata numaraları açıklanmaktadır. Ayrıca kendi yazdığınız fonksiyonların da geri dönüş değeri olacaktır. Kernel fonksiyonları gibi hata durumlarını iletmeniz gerekecektir. Bu yüzden fonksiyonun geri dönüş tipi int olmalıdır. Şunu da söylemek gerekir başlık dosyasını incelediğinizde tüm hata mesajlarının pozitif sayı olduğunu görürsünüz. Bu yüzden kendi fonksiyonlarınızda hata durumlarınızı dönerken bu başlık dosyasında tanımlı hata makrolarının önüne "-" işareti koyarak sayıyı negatif yapmalısınız.
  • Başarı durumu: Kernel fonksiyonları işlerini başarı ile yaptıklarını iletmek için sıfır değeri ile geri döner. Tabi bu durumun bazı istisnaları vardır. Örneğin read ve write fonksiyonları gibi. Bu fonksiyonlar başarı sağladıkları karakter sayısı ile geri dönerler. Ama sonuçta negatif değildir. 
    Not: Konuya devam etmeden önce bu başlık için bir referans kitabı belirtmek istiyorum. Jonathan Corbet, Alessandro Rubini, Greg Kroah-Hartman tarafından yazılmış “Linux Device Drivers” kitabını okumanızı tavsiye ederim. Zaten pdf olarak bulmanız çok kolay. Hatta bu linkten hızlıca temin edebilirsiniz.

    Linux cihazları üç farklı türde farklılaştırmıştır ve modül yazımında bu farklılığa göre kodunuzu oluşturmalısınız. İlk olarak cihazın türünü belirlemek ve sonra o türe ait modül yazılımını yapmak gerekiyor. char module, block module, network module işte ayrıma sebep olan modül isimleri. Biz yazılacak olan modül için char device ele alacağız.


Char Devices

    Char Devices byte akışı ile erişim sağlanan cihazlardır. Bu cihazlara ait modül de bu davranışa göre oluşturulmalıdır. Byte akışı ile erişim olduğundan bu cihazların sürücüleri(driver) open, close, read, ve write sistem çağrıların içerirler. Konsol ekranı(dev/console), seri port(dev/ttyS0) ... karakter cihazlara örnektir.
    Karakter cihaz sürücüleri normal dosyalar gibi kullanıcı katmanında karşımıza çıkar. Karakter cihaz sürücüleri(Char Driver) üzerinde open, close, read, ve write işlemleri yapabilmemize rağmen normal dosyalardan ayıran fark ise onların normal dosyalar gibi kalıcı bilgiler taşımaması ve normal dosyaların kullanıcı iznine bağlı olarak silme, yer değiştirme ve isim verme gibi işlemlerinin yapılamamasıdır. Durumu daha iyi açıklamak için bir audio device dosyasını ele alalım. Bu dosyaya ne yazarsak direk cihaza doğru yönlendirilir ve bu yazılanların etkisiyle cihazdan çıktı alırız. Fakat normal dosya gibi okumak istediğimizde ise bir önceki yazdığımız verilere ulaşamayız. Durumun tam tersini düşünelim ve bir mikrofon ile kayıt yapıyor olalım bu sefer okuma fonksiyonu ile mikrofondan gelen verileri alıyor olacaktık.
  Bu konuda daha fazla bilgiye referans olarak belirttiğim “Linux Device Drivers” kitabının 42 sayfasında ulaşabilirsiniz. 

Kullanıcı Katmanından Cihaza Kadar Olan Bağlantının Sağlanması

Kullanıcı Katmanından başlayarak char device nasıl erişildiğini aşağıdaki resim açıklamaktadır.

Figure 7: Character driver overview
Char Driver Çalışması

    Görüldüğü gibi kullanıcı katmanında cihazı kontrol etmek için(ulaşmak için) cihaza ait dosya kullanılır.(char device file -CDF-) Uygulamalar sadece bu dosyalar üzerinde bildiğimiz dosya işlemlerini yaparak basitçe cihazı kontrol edebilirler. Bu durumun bir güzel yanı da tüm cihazlar için kullanıcı katmanında aynı fonksiyonların kullanılması. Cihazımız ne olursa olsun o cihazı okumak ya da yazmak için hep aynı fonksiyonları kullanırız.
    Peki bu nasıl oluyor? Yukarıdaki resmi incelersek her char device file -CDF- tek bir noktada birleşiyor. Virtual File System. Virtual file system uygulama seviyesindeki CDF gelen isteğin hangi cihaz için olduğunu çözümler ve isteği o cihazın sürücüsüne yönlendirir. Bu sayede istek doğru cihazı bulmuş olur. İsteğin çözümlenmesi ve yönlendirilip ilgili cihaz sürücüsüne ulaşma işlemleri Kernel seviyesinde olur. Son olarak artık isteğin cihaz üzerinde işlenmesi donanım seviyesinde(Hardware Space) olur.
    Uygulamalar  open fonksiyonu ile device file erişirler. Device file VFS ile Device Driver bağlanırlar. Device Driver cihaza özgü olan alt seviye özel işlemler ile cihaza ulaşır ve cihaz üzerinde yapılmak istenen işlem tamamlamış olur.


Major ve Minor Numaraları

     Major ve minor numaraları konusuna giriş için ilk olarak şu komutu koşalım; $ls -l /dev/ Bu komutu koştuğumuzda sistemimizdeki cihazlara ait dosyaları (device file) görürüz. Burada dosya isimlerinden başka konumuz olan major ve minor numaralarını da görürüz. Major ve minor cihazı tanımlayan özel bir sayı çiftidir.


    Resimde /dev/ altında cihazlara ait major ve minor numarı görülmektedir. Major numarası cihazları gruplandırmak için kullanılır. Resimde tüm cihazların major numarası dörttür. Çünkü bu cihazlar serial interface grubunda olup, o gruba ait major numarasını taşırlar. Minor numarası ise tekildir. Yani bir grup içinde her cihazın minor numarası ayrıdır. Kernel hangi cihaza erişim olacağını minor numarası ayrımı ile yapar.
  Kullanıcı katmanındaki uygulamalar sadece device file gördüklerinden onlara isimleri ile ulaşırlar. Her ne kadar kullanıcı katmanında dosya ismi kullanılsa da cihaz sürücüsüne (device driver) ulaşmak için artık numaralar kullanılır. Bu numaraları kullanma kullanıcı seviyesinde yapılan bir iş değildir. Bizim kullandığımız dosya ismi kernel seviyesinde cihaz sürücüsüne ait olan major ve minor numaralarına dönüşür.
  Not: Major ve Minor her ikisi Kernel 2.6 versiyonundan sonra desteklenmektedir.
    Kernel modülü yazılırken bu major ve minor numaralarının nasıl ele alındığına bakalım. İlk olarak <linux/types.h> dosyasını modül yazımında dahil edilmelidir. Çünkü cihaza yüklenecek olan major ve minor numaraları dev_t türündeki bir değişkenin içinde saklanacaktır. dev_t türü 32 bitlik bir değişken olup 12 bitinde major numarası, 20 bitinde de minor numarasını içerir.
Major ve minor numaralarını ayrı ayrı görmek için <linux/kdev_t.h> tanımlanan makroları kullanılır.

MAJOR(dev_t dev) // major numrasını verir
MINOR(dev_t dev) // minor numarasını verir
MKDEV(int major, int minor) // major ve minor numarası ile dev oluşturur.
Bu konuda daha fazla bilgiye “Linux Device Drivers” kitabının 43-44 sayfalarından ulaşabilirsiniz.


Cihaz Numaralarını Oluşturmak

    Modül yazılırken ilk olarak cihaz numaraları (major ve minor numaraları) oluşturulmalıdır. Cihaz numaraları oluşturulduktan sonra sitem cihazımızı bu numaralar ile tanıyacaktır.
int register_chrdev_region(dev_t first, unsigned int cnt, char *name);
int alloc_chrdev_region(dev_t *first, unsigned int firstminor, unsigned int cnt, char *name);
    İlk fonksiyonda first, almak istediğimiz device number başlangıcıdır. Bu değere genellikle 0 olarak değer geçilir.  cnt ise en yakın cihaz numarasına ulaşmak için kullanılır. Son olarak name cihaza verdiğimiz isimdir. Bu isim oluşturulan cihaz numarası ile ilişkilendirilir. İlk fonksiyon kullanımımıza sunulmuş olunsa da genellikle bu fonksiyon yerine ikinci fonksiyon kullanılır. Çünkü çoğunlukla cihazımızın hangi major numarasını kullanacağını bilemeyiz. Bu yüzden ikinci fonksiyonun kullanımı daha kolaydır.
    Eğer çağırdığımız fonksiyon başarı ile döner ise verdiğimiz isim major numarası ile /proc/device/ altında görülür.
    Bu fonksiyonlar modülümüzün init fonksiyonu içinde çağrılmalıdır ki modül dinamik olarak yüklendiğinde cihaza ait veriler oluşsun. Şimdi tüm bu anlatılanlara yönelik kodu yazalım.

#include <linux/module.h>
#include <linux/version.h>
#include <linux/kernel.h>
#include <linux/types.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>

static dev_t dev;

static int __init myModule_init(void) /* Constructor */
{
      int retVal;
    
      retVal = alloc_chrdev_region(&dev, 0, 1, "ZAFER");
      if (retVal < 0)
      {
          return retVal;
      }
      printk(KERN_INFO "<Major, Minor>: <%d, %d>\n", MAJOR(dev), MINOR(dev));
      return 0; // success
}

static void __exit myModule_exit(void) /* Destructor */
{
      unregister_chrdev_region(dev, 1);
      printk(KERN_INFO "myModule_exit\n");
}

module_init(myModule_init);
module_exit(myModule_exit);

MODULE_AUTHOR("Zafer Satilmis");
MODULE_DESCRIPTION("First Char Driver");
   Kodu yazdıktan sonra tabi ki derlenmesi gerekiyor. Bir sonraki yazımda derleme işlemini nasıl yapacağımızı (Makefile oluşturma), cihaza nasıl yükleyip modülü nasıl çalıştırılacağını aktaracağım. 

Faydalanacağını düşündüğünüz kişilere iletin, eksik ya da yanlış olduğunu düşündüğünüz yerleri belirtin.

Şimdi Yazmaya Devam ....

Kuşadası

Hiç yorum yok:

Yorum Gönder

Son Ütücü