初めてのLinuxデバイスドライバーの書き方

こんにちは、ハブラチタテリ。



この記事の目的は、単純なキャラクタードライバーの例を使用して、Linuxシステムにデバイスドライバーを実装する原理を示すことです。



私にとっての主な目標は、将来のカーネルモジュールを作成するための基本的な知識を要約して形成することです。また、一般向けに技術文献を提示する経験を積むことです。 半年後、卒業プロジェクトと話します(はい、私は学生です)。



これは私の最初の記事です。厳密に判断しないでください!



PS

文字が多すぎたため、記事を3つの部分に分けることにしました。



パート1-カーネルモジュールの紹介、初期化、クリーニング。

パート2-関数のオープン、読み取り、書き込み、およびトリミング。

パート3-Makefileを作成し、デバイスをテストします。



入る前に、ここで基本的なことを述べ、より詳細な情報はこの記事の2番目と最後の部分で提示されると言いたいです。



それでは始めましょう。



準備作業



UPD。

明確にしてくれたKolyuchkinに感謝します。



キャラクタードライバー(Charドライバー)は、キャラクターデバイスで動作するドライバーです。

キャラクターデバイスは、バイトストリームとしてアクセスできるデバイスです。

キャラクターデバイスの例は、/ dev / ttyS0、/ dev / tty1です。



UPD。

カーネル検証に関する質問:

~$ uname -r 4.4.0-93-generic
      
      





ドライバーはscull_dev構造で各キャラクターデバイスを表し、カーネルへのcdevインターフェースも提供します。



 struct scull_dev { struct scull_qset *data; /*      */ int quantum; /*     */ int qset; /*    */ unsigned long size; /*    */ struct semaphore sem; /*   */ struct cdev cdev; /* ,    */ }; struct scull_dev *scull_device;
      
      





デバイスはポインターのリンクされたリストを提示し、各ポインターはscull_qset構造体を指します。



 struct scull_qset { void **data; struct scull_qset *next; };
      
      





明確にするために、写真を見てください。



画像



デバイスを登録するには、特別な番号を設定する必要があります。



MAJOR-メジャー番号(システム内で一意です)。

マイナー-マイナー番号(システム内で一意ではありません)。



カーネルには特殊な番号を手動で登録できるメカニズムがありますが、このアプローチは望ましくなく、カーネルにそれらを動的に割り当てるよう丁寧に依頼することをお勧めします。 サンプルコードは以下になります。



デバイスの番号を決定したら、これらの番号とドライバー操作の間の接続を確立する必要があります。 これは、file_operations構造体を使用して実行できます。



 struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write, .open = scull_open, .release = scull_release, };
      
      





カーネルには、モジュールの初期化/削除機能へのパスを示す特別なmodule_init / module_exitマクロがあります。 これらの定義がなければ、初期化/削除関数は呼び出されません。



 module_init(scull_init_module); module_exit(scull_cleanup_module);
      
      





ここでは、デバイスに関する基本情報を保存します。



 int scull_major = 0; /* MAJOR */ int scull_minor = 0; /* MINOR */ int scull_nr_devs = 1; /*    */ int scull_quantum = 4000; /*     */ int scull_qset = 1000; /*    */
      
      





準備作業の最後のステップは、ヘッダーファイルを含めることです。

以下に簡単な説明を示しますが、もう少し詳しく調べたい場合は、美しいサイトへようこそ: lxr



 #include <linux/module.h> /*          */ #include <linux/init.h> /*       */ #include <linux/fs.h> /*       */ #include <linux/cdev.h> /*       */ #include <linux/slab.h> /*       */ #include <asm/uaccess.h> /*      */
      
      





初期化



次に、デバイスの初期化関数を見てみましょう。



 static int scull_init_module(void) { int rv, i; dev_t dev; rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); if (rv) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return rv; } scull_major = MAJOR(dev); scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_device) { rv = -ENOMEM; goto fail; } memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev)); for (i = 0; i < scull_nr_devs; i++) { scull_device[i].quantum = scull_quantum; scull_device[i].qset = scull_qset; sema_init(&scull_device[i].sem, 1); scull_setup_cdev(&scull_device[i], i); } dev = MKDEV(scull_major, scull_minor + scull_nr_devs); return 0; fail: scull_cleanup_module(); return rv; }
      
      





まず、alloc_chrdev_regionを呼び出すことにより、デバイスシンボル番号の範囲を登録し、デバイス名を指定します。 MAJOR(dev)を呼び出した後、メジャー番号を取得します。

次に、戻り値がチェックされ、エラーコードである場合、関数を終了します。 実際のデバイスドライバーを開発する際には、戻り値、および要素へのポインター(NULL?)を常に確認する必要があることに注意してください。



 rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); if (rv) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return rv; } scull_major = MAJOR(dev);
      
      





返された値がエラーコードではない場合、初期化を続行します。



kmalloc関数を呼び出してメモリを割り当て、必ずNULLへのポインターを確認してください。



UPD

2つの関数kmallocとmemsetを呼び出す代わりに、メモリ領域を割り当ててゼロで初期化するkzallocの呼び出しを1回使用できることに言及する価値があります。



 scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_device) { rv = -ENOMEM; goto fail; } memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev));
      
      





初期化を継続します。 ここでの主な機能はscull_setup_cdevです。これについては後ほど説明します。 MKDEVは、シニアデバイス番号とマイナーデバイス番号を格納するために使用されます。



 for (i = 0; i < scull_nr_devs; i++) { scull_device[i].quantum = scull_quantum; scull_device[i].qset = scull_qset; sema_init(&scull_device[i].sem, 1); scull_setup_cdev(&scull_device[i], i); } dev = MKDEV(scull_major, scull_minor + scull_nr_devs);
      
      





値を返すか、エラーを処理してデバイスを削除します。



 return 0; fail: scull_cleanup_module(); return rv; }
      
      





デバイスとカーネルの間のインターフェースを実装するscull_devおよびcdev構造は上に示しました。 scull_setup_cdev関数は、システムを初期化し、構造を追加します。



 static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding scull %d", err, index); }
      
      





削除する



scull_cleanup_module関数は、デバイスモジュールがカーネルから削除されるときに呼び出されます。

初期化の逆のプロセスでは、デバイス構造を削除し、メモリを解放し、カーネルによって割り当てられた低い数値と高い数値を削除します。



 void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); if (scull_device) { for (i = 0; i < scull_nr_devs; i++) { scull_trim(scull_device + i); cdev_del(&scull_device[i].cdev); } kfree(scull_device); } unregister_chrdev_region(devno, scull_nr_devs); }
      
      





完全なコード
 #include <linux/module.h> #include <linux/init.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/slab.h> #include <asm/uaccess.h> int scull_major = 0; int scull_minor = 0; int scull_nr_devs = 1; int scull_quantum = 4000; int scull_qset = 1000; struct scull_qset { void **data; struct scull_qset *next; }; struct scull_dev { struct scull_qset *data; int quantum; int qset; unsigned long size; unsigned int access_key; struct semaphore sem; struct cdev cdev; }; struct scull_dev *scull_device; int scull_trim(struct scull_dev *dev) { struct scull_qset *next, *dptr; int qset = dev->qset; int i; for (dptr = dev->data; dptr; dptr = next) { if (dptr->data) { for (i = 0; i < qset; i++) kfree(dptr->data[i]); kfree(dptr->data); dptr->data = NULL; } next = dptr->next; kfree(dptr); } dev->size = 0; dev->quantum = scull_quantum; dev->qset = scull_qset; dev->data = NULL; return 0; } struct file_operations scull_fops = { .owner = THIS_MODULE, //.read = scull_read, //.write = scull_write, //.open = scull_open, //.release = scull_release, }; static void scull_setup_cdev(struct scull_dev *dev, int index) { int err, devno = MKDEV(scull_major, scull_minor + index); cdev_init(&dev->cdev, &scull_fops); dev->cdev.owner = THIS_MODULE; dev->cdev.ops = &scull_fops; err = cdev_add(&dev->cdev, devno, 1); if (err) printk(KERN_NOTICE "Error %d adding scull %d", err, index); } void scull_cleanup_module(void) { int i; dev_t devno = MKDEV(scull_major, scull_minor); if (scull_device) { for (i = 0; i < scull_nr_devs; i++) { scull_trim(scull_device + i); cdev_del(&scull_device[i].cdev); } kfree(scull_device); } unregister_chrdev_region(devno, scull_nr_devs); } static int scull_init_module(void) { int rv, i; dev_t dev; rv = alloc_chrdev_region(&dev, scull_minor, scull_nr_devs, "scull"); if (rv) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return rv; } scull_major = MAJOR(dev); scull_device = kmalloc(scull_nr_devs * sizeof(struct scull_dev), GFP_KERNEL); if (!scull_device) { rv = -ENOMEM; goto fail; } memset(scull_device, 0, scull_nr_devs * sizeof(struct scull_dev)); for (i = 0; i < scull_nr_devs; i++) { scull_device[i].quantum = scull_quantum; scull_device[i].qset = scull_qset; sema_init(&scull_device[i].sem, 1); scull_setup_cdev(&scull_device[i], i); } dev = MKDEV(scull_major, scull_minor + scull_nr_devs); printk(KERN_INFO "scull: major = %d minor = %d\n", scull_major, scull_minor); return 0; fail: scull_cleanup_module(); return rv; } MODULE_AUTHOR("Your name"); MODULE_LICENSE("GPL"); module_init(scull_init_module); module_exit(scull_cleanup_module);
      
      







建設的な批判に喜んで耳を傾け、フィードバックをお待ちしています。



エラーを見つけた場合、または資料を正しく提示しなかった場合は、これを指摘してください。

反応を速くするには、PMに書き込みます。



よろしくお願いします!



文学






All Articles