0%

Linux驱动学习实战(4) 从 0 写字符设备驱动

做什么

  之前我们已经学习了 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");

  至此,我们完成了一份最小字符设备驱动,但这份驱动还有许多不足,比如使用固定设备号等,我们会在日后更好地完善它。