単純なキャラクタ型ドライバを実装した際のメモ
単純なキャラクタ型ドライバsimple_charを作成した。
simple_charの機能概要
キャラクタ型ドライバsimple_charは以下のような機能を持つ。
- ドライバsimple_charは、デバイス毎にバッファを持つ。
- プロセスはread,writeを使って、そのsimple_charのバッファに対して文字列の読み書きを行う。
- バッファサイズはモジュールパラメータを通じて、モジュールロード時に変更出来る。
- バッファに書き込んだ内容は、/proc/simple_charから確認する事が出来る。
ロード、アンロードが出来るだけのカーネルモジュールを作成する
まずロード、アンロードが出来るだけの単純なカーネルモジュールを作成する。
モジュールのロード時処理は、module_initで定義する。
モジュールのアンロード時処理は、module_exitで定義する。
モジュールパラメータは、module_paramで定義する。
また、読み込み可能であれば、S_IRUGOを指定し、書き込み可能であれば、S_IWUSRを指定する。
#include <linux/module.h> /* MODULE macro, THIS_MODULE */ #include <linux/moduleparam.h> #include <linux/kernel.h> /* printk() */ /* #include <linux/fs.h> /\* alloc_chrdev_region(), ... *\/ */ /* #include <linux/types.h> /\* dev_t *\/ */ /* #include <linux/kdev_t.h> /\* MAJOR() *\/ */ /* #include <linux/errno.h> /\* error codes *\/ */ /* #include <linux/cdev.h> /\* cdev_*() *\/ */ /* #include <linux/sched.h> /\* current *\/ */ #include <linux/stat.h> /* S_IRUGO, S_IWUSR */ MODULE_AUTHOR("Masayuki Ito"); MODULE_LICENSE("Dual BSD/GPL"); static int simple_char_bufsize = 1; static int simple_char_init(void) { printk(KERN_ALERT "simple_char: %s\n", __FUNCTION__); printk(KERN_ALERT " simple_char_bufsize = %d\n", simple_char_bufsize); return 0; } static void simple_char_exit(void) { return ; } module_param(simple_char_bufsize, int, S_IRUGO| S_IWUSR); module_init(simple_char_init); module_exit(simple_char_exit);
obj-m := simple_char.o INC := /usr/src/kernels/$(shell uname -r)/include KDIR := /lib/modules/$(shell uname -r)/build PWD := $(shell pwd) default: $(MAKE) -C $(KDIR) M=$(PWD) modules check-syntax: $(MAKE) -C $(KDIR) M=$(PWD) modules clean: $(MAKE) -C $(KDIR) M=$(PWD) modules clean
メジャー番号とマイナー番号の登録
カーネルからドライバを識別するために、デバイス番号を登録する。
メジャー番号: カーネルがドライバを識別するための番号
マイナー番号: ドライバがデバイスを識別するための番号
指定したメジャー番号を登録する場合には、
register_chrdev_region()を使う。
他のデバイスとデバイス番号が被らないように、メジャー番号を動的に確保する場合には、
alloc_chrdev_region()を使う。
マイナー番号は登録しないが、ドライバが扱うデバイスの総数として指定する必要がある。
linuxではデバイス番号をdev_t型で扱う。
dev_t型変数からメジャー番号を取得する場合にはMAJOR(dev_t)を使う。
dev_t型変数からマイナー番号を取得する場合にはMINOR(dev_t)を使う。
整数型変数のメジャー番号とマイナー番号からdev_t型のデバイス番号を作成するには、MKDEV()を使う。
登録したデバイス番号は、モジュールのアンロード時に、
unregister_chrdev_region()で解放しなければならない。
#include <linux/module.h> /* MODULE macro, THIS_MODULE */ #include <linux/moduleparam.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* alloc_chrdev_region(), ... */ #include <linux/types.h> /* dev_t */ #include <linux/kdev_t.h> /* MAJOR() */ /* #include <linux/errno.h> /\* error codes *\/ */ /* #include <linux/cdev.h> /\* cdev_*() *\/ */ /* #include <linux/sched.h> /\* current *\/ */ #include <linux/stat.h> /* S_IRUGO, S_IWUSR */ MODULE_AUTHOR("Masayuki Ito"); MODULE_LICENSE("Dual BSD/GPL"); #ifndef SIMPLE_CHAR_BUFSIZE #define SIMPLE_CHAR_BUFSIZE 10 #endif #ifndef SIMPLE_CHAR_NR_DEVS #define SIMPLE_CHAR_NR_DEVS 4 /* マイナーデバイス番号の総数 */ #endif #ifndef SIMPLE_CHAR_MAJOR #define SIMPLE_CHAR_MAJOR 0 /* 0の場合にはデバイス番号を動的に割り当てる */ #endif #ifndef SIMPLE_CHAR_DEVNAME #define SIMPLE_CHAR_DEVNAME "simple_char" #endif static int simple_char_bufsize = SIMPLE_CHAR_BUFSIZE; static int simple_char_nr_devs = SIMPLE_CHAR_NR_DEVS; static int simple_char_major = SIMPLE_CHAR_MAJOR; static int simple_char_minor = 0; static int simple_char_setup_devnr(void) { int result; dev_t dev; if(simple_char_major) { /* ユーザ指定のデバイス番号を登録する*/ dev = MKDEV(simple_char_major, simple_char_minor); result = register_chrdev_region(dev, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); } else { /* デバイス番号を動的に確保する */ result = alloc_chrdev_region(&dev, simple_char_minor, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); simple_char_major = MAJOR(dev); } if(result < 0) { printk(KERN_WARNING "simple_char: fail to get major %d\n", simple_char_major); return result; } return 0; } static int simple_char_clear_devnr(void) { dev_t dev = MKDEV(simple_char_major, simple_char_minor); unregister_chrdev_region(dev, simple_char_nr_devs); return 0; } static int simple_char_init(void) { int result; printk(KERN_ALERT "simple_char: %s\n", __FUNCTION__); printk(KERN_ALERT " simple_char_bufsize = %d\n", simple_char_bufsize); result = simple_char_setup_devnr(); if(result) { goto fail; } return 0; fail: printk(KERN_WARNING "simple_char: init fail\n"); return result; } static void simple_char_exit(void) { simple_char_clear_devnr(); return ; } module_param(simple_char_bufsize, int, S_IRUGO| S_IWUSR); module_param(simple_char_nr_devs, int, S_IRUGO| S_IWUSR); module_param(simple_char_major, int, S_IRUGO| S_IWUSR); module_init(simple_char_init); module_exit(simple_char_exit);
カーネルモジュールをキャラクタ型デバイスドライバとして登録する
キャラクタ型デバイスを表すデータとデバイス番号を紐付けして、カーネルにキャラクタ型デバイスを登録する。
linuxでは、キャラクタ型デバイスをstruct cdevで表現している。
キャラクタ型デバイスをカーネルに登録する際には、struct cdevのオブジェクトと
デバイス番号を指定して、cdev_add()を呼び出せばよい。
struct cdevにはファイル操作構造体のオブジェクトを設定する。
これは、cdev_init()の引数にファイル操作構造体を指定すればよい。
キャラクタ型デバイスを削除する際には、cdev_del()を使う。
#include <linux/module.h> /* MODULE macro, THIS_MODULE */ #include <linux/moduleparam.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* alloc_chrdev_region(), ... */ #include <linux/types.h> /* dev_t */ #include <linux/kdev_t.h> /* MAJOR() */ /* #include <linux/errno.h> /\* error codes *\/ */ #include <linux/cdev.h> /* cdev_*() */ /* #include <linux/sched.h> /\* current *\/ */ #include <linux/stat.h> /* S_IRUGO, S_IWUSR */ #include <linux/slab.h> /* kmalloc() */ MODULE_AUTHOR("Masayuki Ito"); MODULE_LICENSE("Dual BSD/GPL"); #ifndef SIMPLE_CHAR_BUFSIZE #define SIMPLE_CHAR_BUFSIZE 10 #endif #ifndef SIMPLE_CHAR_NR_DEVS #define SIMPLE_CHAR_NR_DEVS 4 /* マイナーデバイス番号の総数 */ #endif #ifndef SIMPLE_CHAR_MAJOR #define SIMPLE_CHAR_MAJOR 0 /* 0の場合にはデバイス番号を動的に割り当てる */ #endif #ifndef SIMPLE_CHAR_DEVNAME #define SIMPLE_CHAR_DEVNAME "simple_char" #endif static int simple_char_bufsize = SIMPLE_CHAR_BUFSIZE; static int simple_char_nr_devs = SIMPLE_CHAR_NR_DEVS; static int simple_char_major = SIMPLE_CHAR_MAJOR; static int simple_char_minor = 0; static struct cdev *simple_char_cdev_array = NULL; struct file_operations simple_char_fops = { }; static int simple_char_setup_devnr(void) { int result; dev_t dev; if(simple_char_major) { /* ユーザ指定のデバイス番号を登録する*/ dev = MKDEV(simple_char_major, simple_char_minor); result = register_chrdev_region(dev, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); } else { /* デバイス番号を動的に確保する */ result = alloc_chrdev_region(&dev, simple_char_minor, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); simple_char_major = MAJOR(dev); } if(result < 0) { printk(KERN_WARNING "simple_char: fail to get major %d\n", simple_char_major); return result; } return 0; } static int simple_char_clear_devnr(void) { dev_t dev = MKDEV(simple_char_major, simple_char_minor); unregister_chrdev_region(dev, simple_char_nr_devs); return 0; } static void simple_char_cdev_add(int i) { struct cdev *cdev = simple_char_cdev_array + i; int devno = MKDEV(simple_char_major, simple_char_minor + i); int err; cdev_init(cdev, &simple_char_fops); cdev->owner = THIS_MODULE; err = cdev_add(cdev, devno, 1); if(err) { printk(KERN_NOTICE "simple_char: fail to add cdev %d\n", i); } } static int simple_char_setup_cdev(void) { int i; size_t size = sizeof(struct cdev) * simple_char_nr_devs; simple_char_cdev_array = (struct cdev*)kmalloc(size, GFP_KERNEL); for(i = 0; i < simple_char_nr_devs; ++i) { simple_char_cdev_add(i); } return 0; } static void simple_char_clear_cdev(void) { int i; for(i = 0; i < simple_char_nr_devs; ++i) { cdev_del(simple_char_cdev_array+i); } kfree(simple_char_cdev_array); } static int simple_char_init(void) { int result; printk(KERN_ALERT "simple_char: %s\n", __FUNCTION__); printk(KERN_ALERT " simple_char_bufsize = %d\n", simple_char_bufsize); result = simple_char_setup_devnr(); if(result) { goto fail; } result = simple_char_setup_cdev(); if(result) { goto fail; } return 0; fail: printk(KERN_WARNING "simple_char: init fail\n"); return result; } static void simple_char_exit(void) { simple_char_clear_cdev(); simple_char_clear_devnr(); return ; } module_param(simple_char_bufsize, int, S_IRUGO| S_IWUSR); module_param(simple_char_nr_devs, int, S_IRUGO| S_IWUSR); module_param(simple_char_major, int, S_IRUGO| S_IWUSR); module_init(simple_char_init); module_exit(simple_char_exit);
openとcloseを実装する
struct file_operations simple_char_foptsにファイル操作関数を登録することで、
ドライバを操作する事が出来る。
ファイルのオープンとクローズは、struct file_operationsのメンバーopenとreleaseに対応する。
simple_charでは、open時にstruct fileオブジェクトのprivate_dataメンバに、
simple_char_devオブジェクトへのポインタを代入して、
struct fileオブジェクトから、simple_char_devオブジェクトを参照出きるようにした。
release(close)時処理は特に何もしない。
static int simple_char_open(struct inode *inode, struct file *filep) { struct simple_char_dev *dev; unsigned int minor = iminor(inode); dev = container_of(inode->i_cdev, struct simple_char_dev, cdev); /* readやwriteなどで、デバイス番号を参照出来るようにする */ filep->private_data = dev; printk(KERN_INFO "simple_char: %s", __FUNCTION__); printk(KERN_INFO " &inode->i_cdev = %p\n", &inode->i_cdev); printk(KERN_INFO " dev = %p\n", dev); printk(KERN_INFO " &simple_char_devs[%d] = %p\n", minor, simple_char_devs + minor); return 0; } static int simple_char_release(struct inode *inode, struct file *filep) { printk(KERN_INFO "simple_char: %s", __FUNCTION__); return 0; }
readとwrite を使えるようにする。
struct file_operations simple_char_foptsにreadとwriteを登録する。
ユーザー空間とカーネル空間でデータを移動させたい場合には、copy_to_user()とcopy_from_user()を使う。
static ssize_t simple_char_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { struct simple_char_dev *dev = filep->private_data; size_t write_size = count > dev->write_size ? dev->write_size : count; printk(KERN_INFO "simple_char: %s", __FUNCTION__); if(copy_to_user(buf, dev->buf, write_size)) { return -EFAULT; } return write_size; } static ssize_t simple_char_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos) { struct simple_char_dev *dev = filep->private_data; size_t write_size = count > simple_char_bufsize ? dev->write_size : simple_char_bufsize; if(copy_from_user(dev->buf, buf, write_size)) { return -EFAULT; } dev->write_size = write_size; return write_size; }
procからモジュール情報を読み書きする。
/procに表示したい内容を書き出す関数を作成し、
create_proc_read_entryにて、procに登録すればよい。
#include <linux/module.h> /* MODULE macro, THIS_MODULE */ #include <linux/moduleparam.h> #include <linux/kernel.h> /* printk() */ #include <linux/fs.h> /* alloc_chrdev_region(), ... */ #include <linux/types.h> /* dev_t */ #include <linux/kdev_t.h> /* MAJOR() */ #include <linux/errno.h> /* error codes */ #include <linux/cdev.h> /* cdev_*() */ /* #include <linux/sched.h> /\* current *\/ */ #include <linux/stat.h> /* S_IRUGO, S_IWUSR */ #include <linux/slab.h> /* kmalloc() */ #include <asm/uaccess.h> /* copy_to_user, copy_from_user */ #include <linux/proc_fs.h> MODULE_AUTHOR("Masayuki Ito"); MODULE_LICENSE("Dual BSD/GPL"); #ifndef SIMPLE_CHAR_BUFSIZE #define SIMPLE_CHAR_BUFSIZE 10 #endif #ifndef SIMPLE_CHAR_NR_DEVS #define SIMPLE_CHAR_NR_DEVS 4 /* マイナーデバイス番号の総数 */ #endif #ifndef SIMPLE_CHAR_MAJOR #define SIMPLE_CHAR_MAJOR 0 /* 0の場合にはデバイス番号を動的に割り当てる */ #endif #ifndef SIMPLE_CHAR_DEVNAME #define SIMPLE_CHAR_DEVNAME "simple_char" #endif static int simple_char_bufsize = SIMPLE_CHAR_BUFSIZE; static int simple_char_nr_devs = SIMPLE_CHAR_NR_DEVS; static int simple_char_major = SIMPLE_CHAR_MAJOR; static int simple_char_minor = 0; struct simple_char_dev { char *buf; size_t write_size; struct cdev cdev; }; static struct simple_char_dev *simple_char_devs = NULL; static int simple_char_open(struct inode *inode, struct file *filep) { struct simple_char_dev *dev; unsigned int minor = iminor(inode); dev = container_of(inode->i_cdev, struct simple_char_dev, cdev); /* readやwriteなどで、デバイス番号を参照出来るようにする */ filep->private_data = dev; printk(KERN_INFO "simple_char: %s", __FUNCTION__); printk(KERN_INFO " &inode->i_cdev = %p\n", &inode->i_cdev); printk(KERN_INFO " dev = %p\n", dev); printk(KERN_INFO " &simple_char_devs[%d] = %p\n", minor, simple_char_devs + minor); return 0; } static int simple_char_release(struct inode *inode, struct file *filep) { printk(KERN_INFO "simple_char: %s", __FUNCTION__); return 0; } static ssize_t simple_char_read(struct file *filep, char __user *buf, size_t count, loff_t *f_pos) { struct simple_char_dev *dev = filep->private_data; size_t write_size = count > dev->write_size ? dev->write_size : count; printk(KERN_INFO "simple_char: %s", __FUNCTION__); if(copy_to_user(buf, dev->buf, write_size)) { return -EFAULT; } return write_size; } static ssize_t simple_char_write(struct file *filep, const char __user *buf, size_t count, loff_t *f_pos) { struct simple_char_dev *dev = filep->private_data; size_t write_size = count > simple_char_bufsize ? dev->write_size : simple_char_bufsize; if(copy_from_user(dev->buf, buf, write_size)) { return -EFAULT; } dev->write_size = write_size; return write_size; } struct file_operations simple_char_fops = { .open = simple_char_open, .release = simple_char_release, .read = simple_char_read, .write = simple_char_write, }; static int simple_char_setup_devnr(void) { int result; dev_t dev; if(simple_char_major) { /* ユーザ指定のデバイス番号を登録する*/ dev = MKDEV(simple_char_major, simple_char_minor); result = register_chrdev_region(dev, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); } else { /* デバイス番号を動的に確保する */ result = alloc_chrdev_region(&dev, simple_char_minor, simple_char_nr_devs, SIMPLE_CHAR_DEVNAME); simple_char_major = MAJOR(dev); } if(result < 0) { printk(KERN_WARNING "simple_char: fail to get major %d\n", simple_char_major); return result; } return 0; } static int simple_char_clear_devnr(void) { dev_t dev = MKDEV(simple_char_major, simple_char_minor); unregister_chrdev_region(dev, simple_char_nr_devs); return 0; } static void simple_char_cdev_add(struct cdev *cdev, int i) { int devno = MKDEV(simple_char_major, simple_char_minor + i); int err; cdev_init(cdev, &simple_char_fops); cdev->owner = THIS_MODULE; err = cdev_add(cdev, devno, 1); if(err) { printk(KERN_WARNING "simple_char: fail to add cdev %d\n", i); } } static int simple_char_setup_cdev(void) { int i; for(i = 0; i < simple_char_nr_devs; ++i) { simple_char_cdev_add(&simple_char_devs[i].cdev, i); } return 0; } static void simple_char_clear_cdev(void) { int i; for(i = 0; i < simple_char_nr_devs; ++i) { cdev_del(&simple_char_devs[i].cdev); } } static int simple_char_setup_buf(void) { int i; for(i = 0; i < simple_char_nr_devs; ++i) { simple_char_devs[i].write_size = 0; simple_char_devs[i].buf = (char*)kmalloc(simple_char_bufsize, GFP_KERNEL); simple_char_devs[i].buf[0] = '\0'; } return 0; } static void simple_char_clear_buf(void) { int i; for(i = 0; i < simple_char_nr_devs; ++i) { kfree(simple_char_devs[i].buf); } } static int simple_char_read_proc(char *page, char **start, off_t offset, int count, int *eof, void *data) { int i, len = 0; for(i = 0; i < simple_char_nr_devs; ++i) { struct simple_char_dev *dev = simple_char_devs + i; len += sprintf(page + len, "%d: %s \n", i, dev->buf); } *eof = 1; return len; } static void simple_char_setup_proc(void) { /* struct proc_dir_entry *entry; */ create_proc_read_entry("simple_char", 0 /* default mode */, NULL /* 親ディレクトリ */, simple_char_read_proc, NULL); } static void simple_char_clear_proc(void) { remove_proc_entry("simple_char", NULL /* 親ディレクトリ */); } static int simple_char_init(void) { int result; size_t size = sizeof(struct simple_char_dev) * simple_char_nr_devs; printk(KERN_INFO "simple_char: %s\n", __FUNCTION__); printk(KERN_INFO " simple_char_bufsize = %d\n", simple_char_bufsize); simple_char_devs = (struct simple_char_dev*)kmalloc(size, GFP_KERNEL); result = simple_char_setup_buf(); if(result) { goto fail; } result = simple_char_setup_devnr(); if(result) { goto fail; } result = simple_char_setup_cdev(); if(result) { goto fail; } simple_char_setup_proc(); return 0; fail: printk(KERN_WARNING "simple_char: init fail\n"); return result; } static void simple_char_exit(void) { simple_char_clear_buf(); simple_char_clear_cdev(); simple_char_clear_devnr(); kfree(simple_char_devs); simple_char_clear_proc(); return ; } module_param(simple_char_bufsize, int, S_IRUGO| S_IWUSR); module_param(simple_char_nr_devs, int, S_IRUGO| S_IWUSR); module_param(simple_char_major, int, S_IRUGO| S_IWUSR); module_init(simple_char_init); module_exit(simple_char_exit);