Fork me on GitHub

linux驱动:[3]高级字符设备驱动之ioctl

linux驱动:[3]高级字符设备驱动之ioctl

测试平台: x86 PC linux-4.4.0

1.实验目的:

  • 学习并编写ioctl linux高级字符设备驱动程序。
  • 编写驱动 scull ,使用5个指令实现对设备数据的清零,读取,写入操作。

2.驱动代码:(解析见下方)

scull.c:

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
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
#include <linux/module.h>
#include <linux/types.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/errno.h>
#include <linux/cdev.h>
#include <asm/uaccess.h>
#include <linux/device.h>
#include "scull.h"
//设备私有数据
struct scull_dev {
int data;
struct cdev cdev;
} dev;
//最大IOCTL命令号
#define SCULL_IOC_MAXNR 4
//默认自动分配主设备号
#define SCULL_DEV_MAJOR 0
static int scull_major = SCULL_DEV_MAJOR;
module_param(scull_major, int, S_IRUGO);
struct class *scull_class;
struct cdev cdev;
long scull_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, retval = 0;
//判断命令幻数是否匹配
if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
//判断命令序号是否非法
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
//判断空间是否可访问
/* VERIFY_WRITE 是 VERIFY_READ 超集 */
if (_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
if (err)
return -EFAULT;
switch (cmd) {
case SCULL_IOC_CLEAR://数据清零
dev.data = 0;
printk("SCULL_IOC_CLEAR data: 0\n");
break;
case SCULL_IOC_GET://获取数据(通过指针)
retval = __put_user(dev.data, (int __user *)arg);
printk("SCULL_IOC_GET data: %d\n", dev.data);
break;
case SCULL_IOC_QUERY://获取数据(通过返回值)
printk("SCULL_IOC_QUERY data: %d\n", dev.data);
retval = dev.data;
break;
case SCULL_IOC_SET://设置数据(通过指针)
retval = __get_user(dev.data, (int __user *)arg);
printk("SCULL_IOC_SET data: %d\n", dev.data);
break;
case SCULL_IOC_TELL://设置数据(通过直接引用参数值)
dev.data = arg;
printk("SCULL_IOC_TELL data: %d\n", arg);
break;
default:
retval = -EINVAL;
break;
}
return retval;
}
static const struct file_operations scull_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = scull_ioctl,//linux 2.6.36内核之后unlocked_ioctl取代ioctl
};
static int scull_init(void)
{
//设备号
dev_t devno = MKDEV(scull_major, 0);
int result;
if (scull_major)//静态分配设备号
result = register_chrdev_region(devno, 1, "scull");
else {//动态分配设备号
result = alloc_chrdev_region(&devno, 0, 1, "scull");
scull_major = MAJOR(devno);
}
if (result < 0)
return result;
//用于udev/mdev自动创建节点
scull_class = class_create(THIS_MODULE, "scull");
device_create(scull_class, NULL, devno, NULL, "scull");
//静态添加cdev
cdev_init(&cdev, &scull_fops);
cdev.owner = THIS_MODULE;
cdev_add(&cdev, devno, 1);
printk("scull init success\n");
return 0;
}
static void scull_exit(void)
{
cdev_del(&cdev);
device_destroy(scull_class, MKDEV(scull_major, 0));
class_destroy(scull_class);
unregister_chrdev_region(MKDEV(scull_major, 0), 1);
printk("scull exit success\n");
}
MODULE_AUTHOR("Ziping Chen <techping.chan@gmail.com>");
MODULE_LICENSE("GPL");
module_init(scull_init);
module_exit(scull_exit);

scull.h:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
#ifndef SCULL_H_
#define SCULL_H_
//定义幻数
#define SCULL_IOC_MAGIC '$'
//定义命令->
//数据清零
#define SCULL_IOC_CLEAR _IO(SCULL_IOC_MAGIC, 0)
//获取数据(通过指针)
#define SCULL_IOC_GET _IOR(SCULL_IOC_MAGIC, 1, int)
//获取数据(通过返回值)
#define SCULL_IOC_QUERY _IO(SCULL_IOC_MAGIC, 2)
//设置数据(通过指针)
#define SCULL_IOC_SET _IOW(SCULL_IOC_MAGIC, 3, int)
//设置数据(通过直接引用参数值)
#define SCULL_IOC_TELL _IO(SCULL_IOC_MAGIC, 4)
#endif

Makefile:

1
2
3
4
5
6
7
8
9
10
obj-m := scull.o #编译进模块
KERNELDIR := /lib/modules/4.4.0-59-generic/build #此处为linux内核库目录
PWD := $(shell pwd) #获取当前目录
OUTPUT := $(obj-m) $(obj-m:.o=.ko) $(obj-m:.o=.mod.o) $(obj-m:.o=.mod.c) modules.order Module.symvers
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
clean:
rm -rf $(OUTPUT)

linux c应用程序测试:

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
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "scull.h"
int main(void)
{
int fd;
int data;
//打开设备
fd = open("/dev/scull", O_RDWR);
if (fd == -1) {
printf("open scull device failed!\n");
return -1;
}
//数据清零
ioctl(fd, SCULL_IOC_CLEAR);
//直接传值测试
data = ioctl(fd, SCULL_IOC_QUERY);
printf("app data %d\n", data);
data = 100;
ioctl(fd, SCULL_IOC_TELL, data);
//指针传值测试
ioctl(fd, SCULL_IOC_GET, &data);
data = 122;
ioctl(fd, SCULL_IOC_SET, &data);
return 0;
}

测试结果:

result

result


3.代码解析:

代码的大部分解析都位于上面测试,这里我只是提一下程序编写过程中可能出现的问题:

  • unknown field 'ioctl' specified in initializer```
    1
    2
    3
    4
    这个错误是因为在linux 2.6.36内核之后,去掉了原来的ioctl,添加两个新的成员:
    ```long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);

    long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

    将scull_fops中.ioctl替换为.unlocked_ioctl,另外scull_ioctl()要去掉inode参数,返回类型为long。

  • 没有编写scull_open()函数,设备默认成功打开。
  • 编译没有成功可能是没有包含对应的头文件,头文件可以通过查阅手册或者网络搜索得知。
  • 查看printk信息可通过 dmesg shell指令。
  • 每个函数的参数都是确定的不变的,不要自己擅自改动。