高级字符驱动程序ioctl
Linux内核一般会自动地过滤到一些不合法的cmd定义,比如你自己定义的 2
1.ioctl命令格式
幻数 | 序数 | 数据传输方向 | 数据大小 |
---|---|---|---|
8 bit | 8 bit | 2 bit | 14bit |
内核中两个文档有说明ioctl-decoding.txt
、ioctl-number.txt
1.1 幻数
是个0~0xff的数,占8bit(_IOC_TYPEBITS
)。这个数是用来区分不同的驱动的,像设备号申请的时候一样,内核有文档ioctl-number.txt
给出一些推荐的或者已经被使用的幻数 。
1.2 序数
用这个数来给自己的命令编号,占8bit(_IOC_NRBITS
)。
1.3 数据传输方向
占2bit(_IOC_DIRBITS
)。如果涉及到要传参,内核要求描述一下传输的方向,传输的方向是以应用层的角度来描述的。
- _IOC_NONE:值为0,无数据传输。
- _IOC_READ:值为1,从设备驱动读取数据。
- _IOC_WRITE:值为2,往设备驱动写入数据。
- _IOC_READ|_IOC_WRITE:双向数据传输。
1.4 数据大小
与体系结构相关,ARM下占14bit(_IOC_SIZEBITS
),如果数据是int
,内核给这个赋的值就是sizeof(int)
。
2.定义命令
内核提供了一些宏来帮助定义命令
//nr为序号,type为数据类型,如int
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
//获取类型大小
#define _IOC_TYPECHECK(t) (sizeof(t))
//没有参数的命令
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//从驱动中读数据
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
//写数据到驱动
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
//双向传送
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
//和上面一样,一般不使用以下的操作宏
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
定义命令demo
#define MY_IOC_MAGIC 'm' //定义类型
#define MY_IOCSET _IOW(MY_IOC_MAGIC, 0, int)
#define MY_IOCGQSET _IOR(MY_IOC_MAGIC, 1, int)
3.提取命令中的信息
#define _IOC_NRBITS 8
#define _IOC_TYPEBITS 8
#ifndef _IOC_SIZEBITS
# define _IOC_SIZEBITS 14
#endif
#ifndef _IOC_DIRBITS
# define _IOC_DIRBITS 2
#endif
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1)
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1)
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1)
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1)
#define _IOC_NRSHIFT 0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)
//从命令中提取方向
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//从命令中提取幻数
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//从命令中提取序数
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//从命令中提取数据大小
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
4.应用实例
4.1 驱动程序
ioctl_test.h
#ifndef IOCTL_TEST_H_
#define IOCTL_TEST_H_
//定义幻数
#define MY_IOC_MAGIC '%'
//数据清零
#define MY_IOC_CLEAR_DATA _IO(MY_IOC_MAGIC, 0)
//读取数据
#define MY_IOC_GET_DATA _IOR(MY_IOC_MAGIC, 1, int)
//查询数据
#define MY_IOC_QUERY_DATA _IO(MY_IOC_MAGIC, 2)
//设置数据(通过指针)
#define MY_IOC_SET_DATA _IOW(MY_IOC_MAGIC, 3, int)
//设置数据(通过直接引用参数值)
#define MY_IOC_TELL_DATA _IO(MY_IOC_MAGIC, 4)
#endif
ioctl_test.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 <asm/uaccess.h>
#include <linux/device.h>
#include "ioctl_test.h"
//设备私有数据
struct ioctl_dev {
int data;
struct cdev cdev;
} dev;
//最大IOCTL命令号
#define MY_IOC_MAXNR 4
//默认自动分配主设备号
#define IOCTL_DEV_MAJOR 0
static int ioctl_major = IOCTL_DEV_MAJOR;
//加载模块传递参数
module_param(ioctl_major, int, S_IRUGO);
struct class *ioctl_class;
struct cdev cdev;
long my_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
int err = 0, retval = 0;
//判断命令幻数是否匹配
if (_IOC_TYPE(cmd) != MY_IOC_MAGIC)
return -ENOTTY;
//判断命令序号是否非法
if (_IOC_NR(cmd) > MY_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 MY_IOC_CLEAR_DATA://数据清零
dev.data = 0;
printk("MY_IOC_CLEAR_DATA data: 0\n");
break;
case MY_IOC_GET_DATA://读取数据
retval = __put_user(dev.data, (int __user *)arg);
printk("MY_IOC_GET_DATA data: %d\n", dev.data);
break;
case MY_IOC_QUERY_DATA://查询数据
printk("MY_IOC_QUERY_DATA data: %d\n", dev.data);
retval = dev.data;
break;
case MY_IOC_SET_DATA://设置数据(通过指针)
retval = __get_user(dev.data, (int __user *)arg);
printk("MY_IOC_SET_DATA data: %d\n", dev.data);
break;
case MY_IOC_TELL_DATA://设置数据(通过直接引用参数值)
dev.data = arg;
printk("MY_IOC_TELL_DATA data: %ld\n", arg);
break;
default:
retval = -EINVAL;
break;
}
return retval;
}
static const struct file_operations my_fops = {
.owner = THIS_MODULE,
.unlocked_ioctl = my_ioctl,//linux 2.6.36内核之后unlocked_ioctl取代ioctl
};
static int my_init(void)
{
//设备号
dev_t devno = MKDEV(ioctl_major, 0);
int result;
if (ioctl_major)
{
//静态分配设备号
result = register_chrdev_region(devno, 1, "ioctl");
}
else
{
//动态分配设备号
result = alloc_chrdev_region(&devno, 0, 1, "ioctl");
ioctl_major = MAJOR(devno);
}
if (result < 0)
return result;
//用于udev/mdev自动创建节点
ioctl_class = class_create(THIS_MODULE, "ioctl");
device_create(ioctl_class, NULL, devno, NULL, "ioctl");
//静态添加cdev
cdev_init(&cdev, &my_fops);
cdev.owner = THIS_MODULE;
cdev_add(&cdev, devno, 1);
printk("=====my_init success=====\n"); return 0;
}
static void my_exit(void)
{
cdev_del(&cdev);
device_destroy(ioctl_class, MKDEV(ioctl_major, 0));
class_destroy(ioctl_class);
unregister_chrdev_region(MKDEV(ioctl_major, 0), 1);
printk("=====my_exit success=====\n");
}
module_init(my_init);
module_exit(my_exit);
MODULE_LICENSE("GPL");
4.2 测试程序
#include <stdio.h>
#include <fcntl.h>
#include <sys/ioctl.h>
#include "ioctl_test.h"
int main(void)
{
int fd;
int data;
//打开设备
fd = open("/dev/ioctl", O_RDWR);
if (fd == -1)
{
printf("open ioctl device failed!\n");
return -1;
}
//数据清零
ioctl(fd, MY_IOC_CLEAR_DATA);
//查询数据
data = ioctl(fd, MY_IOC_QUERY_DATA);
printf("app data %d\n", data);
//设置数据(通过直接引用参数值)
data = 100;
ioctl(fd, MY_IOC_TELL_DATA, data);
//读取数据
ioctl(fd, MY_IOC_GET_DATA, &data);
//设置数据
data = 122;
ioctl(fd, MY_IOC_SET_DATA, &data);
return 0;
}