最初のLinuxデバイスドライバーの作成方法。 パート2

読者の皆さん、こんにちは!



前のパートでは、基本構造を調べ、デバイスの初期化と削除も記述しました。



この記事では、scull_openを開く機能、scull_read / scull_writeを読み書きする機能をドライバーに追加し、最初の動作するデバイスドライバーを取得します。







前の記事を読んで、気に入って、コメントしてくれたすべてのユーザーに感謝したい。 Kolyuchkindlinyjを明確にしてくれありがとう







前回は、各機能の内部を詳細に検討しないという提案があったため、この記事ではそれらをより広い意味で紹介するようにします。



ポイントに直行してください!



前回の記事では、scull_cleanup_moduleの一部であるscull_trimという関数を考慮しませんでした。 関数で確認できるように、リンクされたリストを通過してカーネルにメモリを返すループがあります。 ここでは注意を集中しません。 主なものは先です!



scull_trim
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; }
      
      







sull_open関数を検討する前に、少し余談をしたいと思います。



Linuxシステムの多くはファイルとして表すことができます。 ファイルに対して最も頻繁に実行される操作-開く、読み取り、書き込み、閉じる。 また、デバイスドライバーを使用して、デバイスの開閉、読み取り、書き込みを行うことができます。



したがって、file_operations構造には、.read、.write、.open、.releaseなどのフィールドがあります。これらは、ドライバーが実行できる基本的な操作です。



Scull_open関数



そしてすぐにコード:



 int scull_open(struct inode *inode, struct file *flip) { struct scull_dev *dev; dev = container_of(inode->i_cdev, struct scull_dev, cdev); flip->private_data = dev; if ((flip->f_flags & O_ACCMODE) == O_WRONLY) { if (down_interruptible(&dev->sem)) return -ERESTARTSYS; scull_trim(dev); up(&dev->sem); } printk(KERN_INFO "scull: device is opened\n"); return 0; }
      
      





この関数は2つの引数を取ります。



  1. iノード構造体へのポインター。 iノード構造は、ファイル、ディレクトリ、およびファイルシステムオブジェクトに関する情報を格納するiノードです。

  2. ファイル構造へのポインター。 ファイルを開くたびにカーネルが作成する構造には、カーネルの上位レベルで必要な情報が含まれています。


scull_openの主な機能は、デバイスを初期化し(デバイスが初めて開かれた場合)、正しい操作に必要な構造体フィールドに入力することです。

デバイスは何もしないので、初期化するものは何もありません:)



ただし、作業の外観を作成するには、いくつかのアクションを実行します。



 dev = container_of(inode->i_cdev, struct scull_dev, cdev); flip->private_data = dev;
      
      





上記のコードでは、container_ofを使用して、inode-> i_cdevを使用して、struct scull_dev型のcdevへのポインターを取得します。 結果のポインターはprivate_dataフィールドに書き込まれます。



 if ((flip->f_flags & O_ACCMODE) == O_WRONLY) { ...
      
      





さらに、ファイルが書き込み用に開いている場合は、さらに簡単です。使用する前にファイルをクリーニングし、デバイスが開いていることを示すメッセージを表示します。



scull_read関数



機能コード
 ssize_t scull_read(struct file *flip, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = flip->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t rv = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) { printk(KERN_INFO "scull: *f_pos more than size %lu\n", dev->size); goto out; } if (*f_pos + count > dev->size) { printk(KERN_INFO "scull: correct count\n"); count = dev->size - *f_pos; } item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if (dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { rv = -EFAULT; goto out; } *f_pos += count; rv = count; out: up(&dev->sem); return rv; }
      
      







次に、読み取り機能を使用する意味を説明します。

デバイスはシンボリックであるため、バイトストリームとして操作できます。 バイトストリームで何ができますか? そうです-読んでください。 そのため、関数の名前が示すように、デバイスからバイトを読み取ります。



読み取り関数が呼び出されると、いくつかの引数が渡されます。そのうちの最初の引数は既に検討したので、残りを見てみましょう。



bufは文字列へのポインターであり、 __ userはこのポインターがユーザー空間にあることを示します。 引数はユーザーによって渡されます。

countは読み込むバイト数です。 引数はユーザーによって渡されます。

f_pos-オフセット。 引数はカーネルを渡します。



つまり、ユーザーがデバイスから読み取りたい場合、読み取り関数(scull_readではない)を呼び出しますが、読み取り先の情報とバイト数が書き込まれるバッファーを示します。

次に、コードを詳しく見てみましょう。



 if (*f_pos >= dev->size) { printk(KERN_INFO "scull: *f_pos more than size %lu\n", dev->size); goto out; } if (*f_pos + count > dev->size) { printk(KERN_INFO "scull: correct count\n"); count = dev->size - *f_pos; }
      
      





最初に行うことはチェックです:



  1. オフセットがファイルサイズよりも大きい場合、明らかな理由により、読み取ることができなくなります。 エラーを推定し、関数を終了します。
  2. 現在のオフセットと読み込むデータのサイズの合計がクォンタムのサイズより大きい場合、読み込むデータのサイズを調整し、メッセージを報告します。


そして、これが会話の主題です。



 if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { rv = -EFAULT; goto out; }
      
      





copy_to_user-サイズcountdptr-> data [s_pos]カーネルによって割り当てられたメモリからbuf(ユーザー空間にある)にデータをコピーします。



これらの変数をすべて理解していない場合: s_pos、q_pos、item、restは問題ではありません。主なことは、read関数の意味を理解することです。すでに記事の第3部でドライバーをテストします。 ただし、今それについて知りたい場合は、printkをいつでも使用できます(意味がわかっている場合は:))。



scull_write関数



scull_write関数はscull_readと非常に似ており、上記との違いが名前から明らかであるという事実を考慮して、この関数については説明しませんが、自分で解釈することをお勧めします。



機能コード
 ssize_t scull_write(struct file *flip, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = flip->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t rv = -ENOMEM; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if (dptr == NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) { rv = -EFAULT; goto out; } *f_pos += count; rv = count; if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return rv; }
      
      







それだけです。完全な役に立たないドライバーを作成しました。完全なコードを以下に示し、次の記事でテストします。



完全なコード
 #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; } int scull_open(struct inode *inode, struct file *flip) { struct scull_dev *dev; dev = container_of(inode->i_cdev, struct scull_dev, cdev); flip->private_data = dev; if ((flip->f_flags & O_ACCMODE) == O_WRONLY) { if (down_interruptible(&dev->sem)) return -ERESTARTSYS; scull_trim(dev); up(&dev->sem); } printk(KERN_INFO "scull: device is opend\n"); return 0; } int scull_release(struct inode *inode, struct file *flip) { return 0; } struct scull_qset *scull_follow(struct scull_dev *dev, int n) { struct scull_qset *qs = dev->data; if (!qs) { qs = dev->data = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs == NULL) return NULL; memset(qs, 0, sizeof(struct scull_qset)); } while (n--) { if (!qs->next) { qs->next = kmalloc(sizeof(struct scull_qset), GFP_KERNEL); if (qs->next == NULL) return NULL; memset(qs->next, 0, sizeof(struct scull_qset)); } qs = qs->next; continue; } return qs; } ssize_t scull_read(struct file *flip, char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = flip->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t rv = 0; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; if (*f_pos >= dev->size) { printk(KERN_INFO "scull: *f_pos more than size %lu\n", dev->size); goto out; } if (*f_pos + count > dev->size) { printk(KERN_INFO "scull: correct count\n"); count = dev->size - *f_pos; } item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if (dptr == NULL || !dptr->data || !dptr->data[s_pos]) goto out; if (count > quantum - q_pos) count = quantum - q_pos; if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) { rv = -EFAULT; goto out; } *f_pos += count; rv = count; out: up(&dev->sem); return rv; } ssize_t scull_write(struct file *flip, const char __user *buf, size_t count, loff_t *f_pos) { struct scull_dev *dev = flip->private_data; struct scull_qset *dptr; int quantum = dev->quantum, qset = dev->qset; int itemsize = quantum * qset; int item, s_pos, q_pos, rest; ssize_t rv = -ENOMEM; if (down_interruptible(&dev->sem)) return -ERESTARTSYS; item = (long)*f_pos / itemsize; rest = (long)*f_pos % itemsize; s_pos = rest / quantum; q_pos = rest % quantum; dptr = scull_follow(dev, item); if (dptr == NULL) goto out; if (!dptr->data) { dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL); if (!dptr->data) goto out; memset(dptr->data, 0, qset * sizeof(char *)); } if (!dptr->data[s_pos]) { dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL); if (!dptr->data[s_pos]) goto out; } if (count > quantum - q_pos) count = quantum - q_pos; if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) { rv = -EFAULT; goto out; } *f_pos += count; rv = count; if (dev->size < *f_pos) dev->size = *f_pos; out: up(&dev->sem); return rv; } 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("AUTHOR"); MODULE_LICENSE("GPL"); module_init(scull_init_module); module_exit(scull_cleanup_module);
      
      







この記事を読んだ後、コードと余分な機能が多すぎることがわかったため、このドライバーの最も簡単な実装オプションは次のとおりです。



簡略化されたコード
 #include <linux/module.h> #include <linux/kernel.h> #include <linux/fs.h> #include <linux/cdev.h> #include <linux/semaphore.h> #include <linux/uaccess.h> int scull_minor = 0; int scull_major = 0; struct char_device { char data[100]; } device; struct cdev *p_cdev; ssize_t scull_read(struct file *flip, char __user *buf, size_t count, loff_t *f_pos) { int rv; printk(KERN_INFO "scull: read from device\n"); rv = copy_to_user(buf, device.data, count); return rv; } ssize_t scull_write(struct file *flip, char __user *buf, size_t count, loff_t *f_pos) { int rv; printk(KERN_INFO "scull: write to device\n"); rv = copy_from_user(device.data, buf, count); return rv; } int scull_open(struct inode *inode, struct file *flip) { printk(KERN_INFO "scull: device is opend\n"); return 0; } int scull_release(struct inode *inode, struct file *flip) { printk(KERN_INFO "scull: device is closed\n"); return 0; } struct file_operations scull_fops = { .owner = THIS_MODULE, .read = scull_read, .write = scull_write, .open = scull_open, .release = scull_release, }; void scull_cleanup_module(void) { dev_t devno = MKDEV(scull_major, scull_minor); cdev_del(p_cdev); unregister_chrdev_region(devno, 1); } static int scull_init_module(void) { int rv; dev_t dev; rv = alloc_chrdev_region(&dev, scull_minor, 1, "scull"); if (rv) { printk(KERN_WARNING "scull: can't get major %d\n", scull_major); return rv; } scull_major = MAJOR(dev); p_cdev = cdev_alloc(); cdev_init(p_cdev, &scull_fops); p_cdev->owner = THIS_MODULE; p_cdev->ops = &scull_fops; rv = cdev_add(p_cdev, dev, 1); if (rv) printk(KERN_NOTICE "Error %d adding scull", rv); printk(KERN_INFO "scull: register device major = %d minor = %d\n", scull_major, scull_minor); return 0; } MODULE_AUTHOR("Name Surname"); MODULE_LICENSE("GPL"); module_init(scull_init_module); module_exit(scull_cleanup_module);
      
      







世論調査



そのため、現時点では、あるバージョンのカーネルから別のバージョンのデバイスドライバーを移植するというタスクがあります。 特定の例でこのプロセスがどのように行われるかを読んで興味がありますか?



既にそのような経験をお持ちの場合は、それを共有して、デバイスドライバーの転送中に発生した問題/エラーを私に書いてください。 そして、私は今度は記事にあなたの経験を追加しようとします(間違いなくあなたにそれを示します)。

よろしくお願いします! :)



All Articles