1.ioctl 简介
ioctl是Linux专门为用户层控制设备设计的系统调用接口,这个接口具有极大的灵活性,我们的设备打算让用户通过哪些命令实现哪些功能,都可以通过它来实现,ioctl在操作方法集中对应的函数指针是long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
,其中的命令和参数完全由驱动指定,通常命令会写在一个头文件中以供应用层和驱动层遵守同样的通信协议,Linux建议如图所示的方式定义ioctl()命令
设备类型 序列号 方向 数据尺寸
8bit 8bit 2bit 13/14bit
设备类型字段为一个幻数,可以是0~0xff之间的数,内核中的"ioctl-number.txt"给出了一个推荐的和已经被使用的幻数(但是已经好久没人维护了),新设备驱动定义幻数的>时候要避免与其冲突。
序列号字段表示当前命令是整个ioctl命令中的第几个,从1开始计数。
方向字段为2bit,表示数据的传输方向,可能的值是:_IOC_NONE,_IOC_READ,_IOC_W>RITE和_IOC_READ|_IOC_WRITE。
数据尺寸字段表示涉及的用户数据的大小,这个成员的宽度依赖于体系结构,通常是13>或14位。
既然内核这样定义cmd,就肯定有方法让用户方便定义:
_IO(type,nr) //没有参数的命令
_IOR(type,nr,size)//该命令是从驱动读取数据
_IOW(type,nr,size)//该命令是从驱动写入数据
_IOWR(type,nr,size)//双向数据传输
上面的命令已经定义了方向,我们要传的是幻数(type)、序号(nr)和大小(size)。在这里szie的参数只需要填参数的类型,如int,上面的命令就会帮你检测类型的正确然后赋值sizeof(int)。
有生成cmd的命令就必有拆分cmd的命令:
_IOC_DIR(cmd)//从命令中提取方向
_IOC_TYPE(cmd)//从命令中提取幻数
_IOC_NR(cmd) //从命令中提取序数
_IOC_SIZE(cmd)//从命令中提取数据大小
内核中还预定义了一些I/O控制命令,如果某设备驱动中包含了与预定义命令一样的命令码,这些命令会被当做预定义命令被内核处理而不是被设备驱动处理,有如下4种:
FIOCLEX:即file ioctl close on exec 对文件设置专用的标志,通知内核当exec()系统带哦用发生时自动关闭打开的文件
FIONCLEX:即file ioctl not close on exec,清除由FIOCLEX设置的标志
FIOQSIZE:获得一个文件或目录的大小,当用于设备文件时,返回一个ENOTTY错误
FIONBIO:即file ioctl non-blocking I/O 这个调用修改flip->f_flags中的O_NONBLOCK标志
2.ioctl 函数实现模板
- 驱动模块
command.h
#ifndef INCLUDE_COMMAND_H
#define INCLUDE_COMMAND_H
#include <linux/ioctl.h>
//幻数
#define IOCTL_MAGIC 'x'
//定义命令
#define HELLO_RESET _IO(IOCTL_MAGIC, 1)
#define HELLO_READ _IOR(IOCTL_MAGIC, 2, int)
#define HELLO_WRITE _IOW(IOCTL_MAGIC, 3, int)
#endif
hello.c
#include <linux/module.h>
#include <linux/kernel.h>
#include <linux/fs.h>
#include <linux/init.h>
#include <linux/delay.h>
#include <linux/irq.h>
#include <linux/poll.h>
#include <linux/cdev.h>
#include <linux/device.h>
#include "command.h"
#define HELLO_CNT 1
//主设备号为0,表示动态分配设备号
dev_t dev = 0;
static int major = 0;
static int minor = 0;
static struct cdev *hello_cdev[HELLO_CNT];
static struct class *hello_class = NULL;
static struct class_device * hello_class_dev[HELLO_CNT];
static int s_val = 0;
int hello_open(struct inode * pnode, struct file * pfile)
{
printk("open file..
");
int num = MINOR(pnode->i_rdev);
if(num >= HELLO_CNT)
{
return -ENODEV;
}
pfile->private_data = hello_cdev[num];
return 0;
}
int hello_release(struct inode *pnode, struct file *pfile)
{
printk("release file ..
");
return 0;
}
long hello_ioctl(struct file *pfile, unsigned int cmd, unsigned long val)
{
int ret = 0;
switch(cmd)
{
case HELLO_RESET:
{
printk("Rev HELLO_RESET cmd
");
break;
}
case HELLO_READ:
{
printk("Rec HELLO_READ cmd
");
ret = copy_to_user(val, &s_val, sizeof(int));
break;
}
case HELLO_WRITE:
{
printk("Rec HELLO_WRITE cmd
");
ret = copy_from_user(&s_val, val, sizeof(int));
break;
}
default:
{
printk("unkownd cmd...
");
return -EINVAL;
}
}
return ret;
}
//文件操作结构体
static const struct file_operations fops =
{
.owner = THIS_MODULE,
.open = hello_open,
.release = hello_release,
//.read = hello_read,
//.write = hello_write,
.unlocked_ioctl = hello_ioctl,
};
static void setup_cdev(int index)
{
int err, devno = MKDEV(major, index);
cdev_init(hello_cdev[index], &fops);
hello_cdev[index]->owner = THIS_MODULE;
hello_cdev[index]->ops = &fops;
err = cdev_add(hello_cdev[index], devno, 1);
if(err)
{
printk(KERN_NOTICE "Error %d adding hello%d", err, index);
}
}
static void __init hello_init(void)
{
//申请设备号,动态or静态
int ret = 0;
if(major)
{
//为字符设备静态申请第一个设备号
dev = MKDEV(major, minor);
ret = register_chrdev_region(dev, HELLO_CNT, "hello");
}
else
{
//为字符设备动态申请一个设备号
ret = alloc_chrdev_region(&dev, minor, HELLO_CNT, "hello");
major = MAJOR(dev);
}
//构造cdev设备对象
int i = 0;
for(i = 0; i < HELLO_CNT; ++i)
{
hello_cdev[i] = cdev_alloc();
}
//初始化设备对象
for(minor = 0; minor < HELLO_CNT; ++minor)
{
setup_cdev(minor);
}
hello_class = class_create(THIS_MODULE, "hello");
for(minor = 0; minor < HELLO_CNT; ++minor)
{
hello_class_dev[minor] = device_create(hello_class, NULL, MKDEV(major, minor), NULL, "hello%d",minor);
}
}
static void __exit hello_exit(void)
{
for(minor = 0; minor < HELLO_CNT; ++minor)
{
device_destroy(hello_class, MKDEV(major, minor));
}
class_destroy(hello_class);
//从内核注销cdev设备对象
cdev_del(hello_cdev);
//回收设备号
unregister_chrdev_region(dev, HELLO_CNT);
}
module_init(hello_init);
module_exit(hello_exit);
MODULE_LICENSE("GPL");
- 测试模块
command.h
#ifndef INCLUDE_COMMAND_H
#define INCLUDE_COMMAND_H
#include <linux/ioctl.h>
//幻数
#define IOCTL_MAGIC 'x'
//定义命令
#define HELLO_RESET _IO(IOCTL_MAGIC, 1)
#define HELLO_READ _IOR(IOCTL_MAGIC, 2, int)
#define HELLO_WRITE _IOW(IOCTL_MAGIC, 3, int)
#endif
test_ioctl.c
/*
* =====================================================================================
*
* Filename: ioctl.c
*
* Description:
*
* Version: 1.0
* Created: 08/27/17 14:18:42
* Revision: none
* Compiler: gcc
*
* Author: linsheng.pan (), life_is_legend@163.com
* Organization:
*
* =====================================================================================
*/
#include <stdlib.h>
#include <stdio.h>
#include <fcntl.h>
#include "command.h"
int main(int argc, char *argv[])
{
int fd = open("/dev/hello0", O_RDWR);
if(fd < 0)
{
perror("open error:");
return -1;
}
if(ioctl(fd, HELLO_RESET) < 0)
{
perror("error:");
return -1;
}
int val = 1;
if(ioctl(fd, HELLO_WRITE, &val) < 0)
{
perror("write error:");
return -1;
}
val = 2;
if(ioctl(fd, HELLO_READ, &val) < 0)
{
perror("read error");
return -1;
}
printf("val = %d
", val);
return 0;
}