一、前言
在第三章,我们已经构建了一个结构完整的可读写设备驱动程序。但一个实际可用的设备除了提供同步读取和写入之外,还会提供更多的功能。而现在我们拥有调试工具,掌握相关的调试方法,并且对并发问题有了坚实的理解。这样,构建更高级的驱动程序相对容易了。
在本节,我们实现ioctl系统调用,他用于设备控制的公共接口。
二、ioctl
除了读取和写入设备,大部分驱动程序还需要另外一种能力,即通过设备驱动程序执行各种类型的硬件控制。
在用户空间,ioctl系统调用具有如下原型:
int ioctl(int fd,unsigned long cmd,...);
由于使用一连串的“…”的缘故,这个模型在Unix系统调用中显得比较特别,通常这些点表示可变树木的参数表,代表可选参数,习惯上用char *arg定义。使用指针可以向ioctl调用传递任意数据,这样设备可以与用户空间交换任意数量的数据。
驱动程序的ioctl方法模型:
int (*ioctl)(struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
选择ioctl命令
在编写ioctl代码之前,需要选择对应不同命令的编号。为了防止对错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的ioctl命令号,每个命令号被分成多个位字段。linux的第一个版本使用了一个16位整数:高八位是与设备相关的“幻数”,低八位是一个序列号吗,在设备内是唯一的。
type:幻数。选择一个号码,并在整个驱动程序中使用这个号码,这个字段有8位宽。
number:序数(顺序编号)。它也是8位宽。
derection:该字段定义数据传输方向。数据传输方向是由应用程序方向看的。
size:涉及的用户数据大小。与体系结构有关,通常是13位或14位。系统并不强制使用这个位字段。
包含定义一些构造命令编号的宏
_IO(type,nr)/*没有参数的命令*/
_IOR(type, nr, datatype)/*从驱动中读数据*/
_IOW(type,nr,datatype)/*写数据*/
_IOWR(type,nr,datatype)/*双向传送*/
/*type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到*/
/*获取对应数据命令*/
_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)
使用ioctll参数
在分析scull驱动程序的ioctl程序之前,怎么使用附加参数。如果它是一个整数,那么很简单,直接使用即可。如果使用指针,就需要注意一些问题。
当一个指针指向用户空间时,必须确保指向的用户空间是合法的。对未验证的用户空间指针的访问,可能导致内核oops,系统崩溃和安全问题。驱动程序应该负责对每个用到的用户空间地址做适当的检查。
首先通过函数access_ok验证地址(不是传输数据),定义如下(面向内核的):
int access_ok(int type,const void *addr,unsigned long size);
关于access_ok,有两点有趣之处需要注意。第一,它并没有完成验证内存的全部工作,而只检查所引用的内存是否为进程对应访问权限的区域内,特别是确保访问地址没有指向内核空间的内存区。第二,大部分驱动程序代码中不需要真正调用access_ok,因为后面讲到的内存管理程序会处理它。
在调用access_ok之后,驱动程序可以安全的进行实际的数据传输。除了copy_from_user和 copy_to_user之外,程序员还可以使用最常用的数据大小(1,2,4和8)优化过的程序。
/*将datum写到用户空间,确保进程可以写入指定的内存地址,并在成功时返回0,出错时返回-EFAULT。需要使用access_ok后调用*/
put_user(datum,ptr);
__put_user(datum,ptr);
/*从用户空间接收一个数据。除了传输方向相反之外,它们与put_user差不多同样,__get_user应该在操作地址已被access_ok检查后使用*/
get_user(local,ptr);
__get_user(local,ptr);
三、实例-ioctl
scull_ioctl.h
ifndef SCULL_IOCTL_H
#define SCULL_IOCTL_H
//定义幻数
#define SCULL_IOC_MAGIC 'J'
#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC,0)
/*
* S means "Set" through a ptr
* T means "Tell" directly with the argument value
* G means "Get" reply by setting through a pointer
* Q menas "Query" response is on thereturn value
* X means "eXchange" switch G and S atomically
* H means "sHift" switch T and Q atomically
*/
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC,1,int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC,2,int)
#define SCULL_IOCTQUANTUM _IO(SCULL_IOC_MAGIC,3)
#define SCULL_IOCTQSET _IO(SCULL_IOC_MAGIC,4)
#define SCULL_IOCGQUANTUM _IOR(SCULL_IOC_MAGIC,5,int)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC,6,int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,7)
#define SCULL_IOCGQSET _IOR(SCULL_IOC_MAGIC,6,int)
#define SCULL_IOCQQUANTUM _IO(SCULL_IOC_MAGIC,7)
#define SCULL_IOCQQSET _IO(SCULL_IOC_MAGIC,8)
#define SCULL_IOCXQUANTUM _IOWR(SCULL_IOC_MAGIC,9,int)
#define SCULL_IOCXQSET _IOWR(SCULL_IOC_MAGIC,10,int)
#define SCULL_IOCHQUANTUM _IO(SCULL_IOC_MAGIC,11)
#define SCULL_IOCHQSET _IO(SCULL_IOC_MAGIC,12)
/*
* the other entities only have "Tell" and "Query",because they're
* not printed in the book,and there's no need to have all six.
* the previous stuff was only these to show different ways to do it.
* */
#define SUCLL_P_IOCTSIZE _IO(SCULL_IOC_MAGIC,13)
#define SCULL_P_IOCQSIZE _IO(SCULL_IOC_MAGIC,14)
#define SCULL_IOC_MAXNR 14
#endif
scull_ioctl.c
#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 <linux/uaccess.h>
#include <linux/device.h>
#include "scull_ioctl.h"
MODULE_LICENSE("Dual BSD/GPL");
//设备私有变量
struct scull_dev {
int data;
struct cdev cdev;
}dev;
#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;
int temp;
//判断命令幻数是否匹配
if(_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
return -ENOTTY;
//判断命令序号是否非法
if(_IOC_NR(cmd) > SCULL_IOC_MAXNR)
return -ENOTTY;
//判断空间是否可访问
if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(VERIFY_WRITE,(void *)arg,_IOC_SIZE(cmd));
if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(VERIFY_READ,(void *)arg,_IOC_SIZE(cmd));
if(err)
return -EFAULT;
switch(cmd){
case SCULL_IOCRESET://数据清零
dev.data = 0;
printk(KERN_EMERG "SCULL_IOCRESET\n");
break;
case SCULL_IOCSQUANTUM://指针赋值
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
retval=__get_user(dev.data,(int __user *)arg);
printk(KERN_EMERG "SCULL_IOCSQUANTUM\n");
break;
case SCULL_IOCTQUANTUM://数值赋值
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
dev.data=arg;
printk(KERN_EMERG "SCULL_IOCTQUANTUM\n");
break;
case SCULL_IOCGQUANTUM://读取指针
retval=__put_user(dev.data,(int __user *)arg);
printk(KERN_EMERG "SCULL_IOCGQUANTUM\n");
break;
case SCULL_IOCQQUANTUM://读取数值
printk(KERN_EMERG "SCULL_IOCQQUANTUM\n");
return dev.data;
case SCULL_IOCXQUANTUM://交换“s”和“g”
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
temp = dev.data;
retval = __get_user(dev.data,(int __user *)arg);
if(0 == retval)//返回成功
retval = __put_user(temp,(int __user *)arg);
printk(KERN_EMERG "SCULL_IOCXQUANTUM\n");
return retval;
case SCULL_IOCHQUANTUM://交换"t"和“q”
if(!capable(CAP_SYS_ADMIN))
return -EPERM;
temp = dev.data;
dev.data = arg;
arg = temp;
printk(KERN_EMERG "SCULL_IOCHQUANTUM\n");
return temp;
default://其余暂时去除
printk(KERN_EMERG "SCULL_default\n");
return -ENOTTY;
}
return retval;
}
int scull_ioctl_open(struct inode *inode,struct file *filp)
{
dev.data = 0;
printk(KERN_EMERG "scull_ioctl_open success!\n");
return 0;
}
int scull_ioctl_release(struct inode *inode,struct file *filp)
{
printk(KERN_EMERG "scull_ioctl_release success!\n");
return 0;
}
static const struct file_operations scull_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = scull_ioctl,
.open = scull_ioctl_open,
.release = scull_ioctl_release,
};
static int scull_init(void)
{
dev_t devno = MKDEV(scull_major,0);
int result;
if(scull_major)
result = register_chrdev_region(devno,1,"scull_ioctl");
else {
result = alloc_chrdev_region(&devno,0,1,"scull_ioctl");
scull_major = MAJOR(devno);
}
if(result < 0)
return result;
//用于udev/mdev自动创建节点
scull_class = class_create(THIS_MODULE,"scull_ioctl");
device_create(scull_class,NULL,devno,NULL,"scull_ioctl");
//静态添加cdev
cdev_init(&cdev,&scull_fops);
cdev.owner = THIS_MODULE;
cdev_add(&cdev,devno,1);
printk(KERN_EMERG "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(KERN_EMERG "scull_exit success!\n");
}
module_init(scull_init);
module_exit(scull_exit);
Makefile
# Comment/uncomment the following line to disable/enable debugging
#DEBUG = y
# Add your debugging flag (or not) to CFLAGS
#ifeq ($(DEBUG),y)
# DEBFLAGS = -O -g -DSBULL_DEBUG # "-O" is needed to expand inlines
#else
# DEBFLAGS = -O2
#endif
#CFLAGS += $(DEBFLAGS)
#CFLAGS += -I..
ifneq ($(KERNELRELEASE),)
# call from kernel build system
obj-m := scull_ioctl.o
else
KERNELDIR ?= /lib/modules/$(shell uname -r)/build
PWD := $(shell pwd)
default:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
endif
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c .tmp_versions
#depend .depend dep:
# $(CC) $(CFLAGS) -M *.c > .depend
#ifeq (.depend,$(wildcard .depend))
#include .depend
#endif
app_ioctl.c
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "scull_ioctl.h"
#define NAME "/dev/scull_ioctl"
int main(void)
{
int fd;
int data;
//打开设备
fd = open(NAME,O_RDWR);
if(fd == -1)
{
printf("打开设备失败!\n");
return -1;
}
//复位
ioctl(fd,SCULL_IOCRESET);
//读取
data = ioctl(fd,SCULL_IOCQQUANTUM);
printf("1:data is %d\n",data);
//数值赋值
data = 10;
ioctl(fd,SCULL_IOCTQUANTUM,data);
data = ioctl(fd,SCULL_IOCQQUANTUM);
printf("2:data is %d\n",data);
//指针赋值
data = 20;
ioctl(fd,SCULL_IOCSQUANTUM,&data);
data = 1; //测试
ioctl(fd,SCULL_IOCGQUANTUM,&data);//读取到内核值
printf("3:data is %d\n",data);
//数值交换
data = 30;
data = ioctl(fd,SCULL_IOCHQUANTUM,data);
printf("4:data is %d\n",data);
data = 40;
ioctl(fd,SCULL_IOCXQUANTUM,&data);
printf("5:data is %d\n",data);
close(fd);
return 0;
}
四、实验步骤
1、首先先在主目录下make,编译模块,生成.ko文件。
出现如下:
#make
make -C /lib/modules/2.6.35.3-571-gcca29a0/build M=/root/Cdev_Driver/Ldd-6/IOCTL modules
make[1]: 正在进入目录 `/usr/src/linux-2.6.35.3'
CC [M] /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.o
Building modules, stage 2.
MODPOST 1 modules
CC /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.mod.o
LD [M] /root/Cdev_Driver/Ldd-6/IOCTL/scull_ioctl.ko
make[1]:正在离开目录 `/usr/src/linux-2.6.35.3'
2、编译模块到内核
insmod scull_ioctl.ko
3、查看/dev是否成功挂载设备
# find /dev/ -name "scull*"
/dev/scull_ioctl
4、将用户程序app_ioctl.c编译,生产可执行文件app_ioctl.exe
gcc app_ioctl.c -o app_ioctl.exe
5、执行用户程序,在主界面上显示
./app_ioctl.exe
1:data is 0
2:data is 10
3:data is 20
4:data is 20
5:data is 30
以及在内核上打印数据
cat /proc/kmsg
<0>[12649.683393] scull_ioctl_open success!
<0>[12649.683533] SCULL_IOCRESET
<0>[12649.683537] SCULL_IOCQQUANTUM
<0>[12649.683627] SCULL_IOCTQUANTUM
<0>[12649.683631] SCULL_IOCQQUANTUM
<0>[12649.683640] SCULL_IOCSQUANTUM
<0>[12649.683643] SCULL_IOCGQUANTUM
<0>[12649.683650] SCULL_IOCHQUANTUM
<0>[12649.683658] SCULL_IOCXQUANTUM
<0>[12649.683667] scull_ioctl_release success!
五、总结
ioctl其实没有什么非常难的东西需要理解,关键是理解cmd命令码如何传入,并在内核对应是如何实现的,程序员最主要的工作量在switch结构中,因为对设备的I/O控制都是通过这一部分的代码实现的,在每个cmd下都有不同的操作函数入口。