做什么
之前我们已经学习了 Linux 字符设备驱动的基本框架,今天我们来试着手搓一份自己的字符驱动。
怎么做
一份最小字符驱动需要有如下几个内容:
1、所有用到的头文件
2、宏:设备名、类名
3、全局变量,如dev_t、cdev、class*等等
4、一些基本的 open/release/read/write 函数
5、fileoperation 结构体
6、__init 函数、__exit函数
7、module_init/module_exit + MODULE_LICENSE
接下来我们来一步一步,写出一个最小字符设备驱动。
进入文件夹,新建 chrdev.c 文件。
首先包含必要的头文件,如下
1 2 3 4 5 6 7
| #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/uaccess.h>
|
接下来,我们定义主设备号和设备名称,如下
1 2
| #define CHRDEVBASE_MAJOR 200 #define CHRDEVBASE_NAME "chrdevbase"
|
接下来我们写上 open 和 release 函数的基本框架。在函数的参数中,struct inode inode 表示设备对象本身,可以通过函数操作 inode 获得设备本身的信息,如主设备号、次设备号等。struct file filp 表示当前这一次 open 的文件实例。每执行一次 open(“/dev/xxx”),内核都会创建一个 struct file,如果两个进程同时打开设备,inode 是共享的,filp 不共享。
1 2 3 4 5 6 7 8 9
| static int chedevbase_open(struct inode *inode, struct file *filp) { return 0; }
static int chrdevbase_release(struct inode *inode, struct file *filp) { return 0; }
|
然后我们来实现设备的注册与注销函数,框架如下。
1 2 3 4 5 6 7 8 9 10 11 12 13 14
| static int __init chrdevbase_init(void) { return 0; }
static void __exit chrdevbase_exit(void) {
} module_init(chrdevbase_init); module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL"); MODULE_AUTHOR("hcw");
|
接下来,我们完成字符设备的 open 和 read 函数框架。char __user *buf 表示用户空间传进来的数据缓冲区的地址,buf 在用户空间,驱动在内核空间,因此访问时必须调用 copy_from_user()函数,size_t cnt表示用户希望写入的字节数,loff_t *offt表示文件偏移量,即当前写入位置。返回值ssize_t表示实际写入的字节数。为了完成这两个函数,我们还需要创建两个内核层缓冲区和一个存放写入数据的区域。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| static char readbuf[100]; static char writebuf[100]; static char kerneldata[] = {"kernel data!"};
············
static ssize_t chedevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
return 0; }
static ssize_t chedevbase_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
return 0; }
|
接下来,创建 file_operation 结构体
1 2 3 4 5 6 7
| static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .release = chrdevbase_release, .write = chrdevbase_write, .read = chrdevbase_read, };
|
目前为止,所有写了的代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61
| #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/fs.h>
#define CHRDEVBASE_MAJOR 200 #define CHRDEVBASE_NAME "chrdevbase"
static char readbuf[100]; static char writebuf[100]; static char kerneldata[] = {"kernel data!"};
static int chrdevbase_open(struct inode *inode, struct file *filp) { return 0; }
static int chrdevbase_release(struct inode *inode, struct file *filp) { return 0; }
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
return 0; }
static ssize_t chrdevbase_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) {
return 0; }
static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .release = chrdevbase_release, .write = chrdevbase_write, .read = chrdevbase_read, };
static int __init chrdevbase_init(void) { return 0; }
static void __exit chrdevbase_exit(void) {
} module_init(chrdevbase_init); module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL"); MODULE_AUTHOR("hcw");
|
接下来我们来依次完善这些函数。
首先我们来完善注册函数。
1 2 3 4 5 6 7 8 9 10 11
| static int __init chrdevbase_init(void) { int retvalue = 0; retvalue = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdevbase_fops); if(retvalue < 0) { printk("chrdevbase driver register failed"); } printk("chrdevbase_init()\r\n"); return 0; }
|
注销函数只需要添加一句话即可:
unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME);
open 函数和 close 函数我们都不需要动,只要修改 write 和 read 函数完成基本功能即可。一般情况下,write 函数返回正数表示成功写入的字节数,0表示没有字节被写入,负数表示错误码。read 函数返回正数表示成功读取的字节数,0表示数据已经读完,返回负数表示错误码。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28
| static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { size_t n;
if (*offt > 0) return 0;
n = min(cnt, (size_t)sizeof(kerneldata));
if (copy_to_user(buf, kerneldata, n)) return -EFAULT;
*offt += n; printk("kernel senddata ok!\n"); return n; }
static ssize_t chrdevbase_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { size_t n = min(cnt, (size_t)sizeof(writebuf) - 1);
if (copy_from_user(writebuf, buf, n)) return -EFAULT;
writebuf[n] = '\0'; printk("kernel recvdata: %s\n", writebuf); return n; }
|
完整代码如下:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88
| #include <linux/types.h> #include <linux/kernel.h> #include <linux/delay.h> #include <linux/init.h> #include <linux/module.h> #include <linux/device.h> #include <linux/uaccess.h> #include <linux/fs.h>
#define CHRDEVBASE_MAJOR 200 #define CHRDEVBASE_NAME "chrdevbase"
static char readbuf[100]; static char writebuf[100]; static char kerneldata[] = {"kernel data!"};
#define min(a, b) ((a) < (b) ? (a) : (b))
static int chrdevbase_open(struct inode *inode, struct file *filp) { return 0; }
static int chrdevbase_release(struct inode *inode, struct file *filp) { return 0; }
static ssize_t chrdevbase_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { size_t n;
if (*offt > 0) return 0;
n = min(cnt, (size_t)sizeof(kerneldata));
if (copy_to_user(buf, kerneldata, n)) return -EFAULT;
*offt += n; printk("kernel senddata ok!\n"); return n; }
static ssize_t chrdevbase_write(struct file *filp, char __user *buf, size_t cnt, loff_t *offt) { size_t n = min(cnt, (size_t)sizeof(writebuf) - 1);
if (copy_from_user(writebuf, buf, n)) return -EFAULT;
writebuf[n] = '\0'; printk("kernel recvdata: %s\n", writebuf); return n; }
static struct file_operations chrdevbase_fops = { .owner = THIS_MODULE, .open = chrdevbase_open, .release = chrdevbase_release, .write = chrdevbase_write, .read = chrdevbase_read, };
static int __init chrdevbase_init(void) { int retvalue = 0; retvalue = register_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME,&chrdevbase_fops); if(retvalue < 0) { printk("chrdevbase driver register failed"); return -1; } printk("chrdevbase_init()\r\n"); return 0; }
static void __exit chrdevbase_exit(void) { unregister_chrdev(CHRDEVBASE_MAJOR,CHRDEVBASE_NAME); } module_init(chrdevbase_init); module_exit(chrdevbase_exit);
MODULE_LICENSE("GPL"); MODULE_AUTHOR("hcw");
|
至此,我们完成了一份最小字符设备驱动,但这份驱动还有许多不足,比如使用固定设备号等,我们会在日后更好地完善它。