一个设备除了读写操作来收发数据或返回保存还有其他的操作。内核将读写之外的I/O操作都给了ioctl函数接口。文件I/O还具备多种模型,如阻塞、非阻塞、I/O口多路复用、异步I/O、异步通知等。
ioctl设备操作
iotcl是一个系统调用函数
int ioctl(int d, int request,...);
d:操作文件的文件描述符
request:不同操作的数值(命令)
第三个参数可选 ,是unsigned long类型,可以传递数值,还可以传递一个指针。
file_operations结构体和ioctl系统调用对应的驱动接口函数是unlocked_ioctl和compat_ioctl。
compat_ioctl:为了处理32位程序和64位内核兼容的一个函数接口,与体系结构相关
unlocked_ioctl:
long (* unlocked_ioctl) (struct file *,unsigned int,unsigned long);
第一个参数表示打开文件的file结构指针
第二个参数和系统调用的第二个参数request对应
第三个参数 对应系统调用函数的第三个参数
之前的ioctl接口调用之前要获得大内核锁(BLK,全局的粗粒度锁),如果ioctl的执行时间过长,会导致内核其他也需要大内核锁的代码要延迟很久,严重降低了效率,因此之后的内核废除了该接口(之前内核版本中,同ioctl系统调用的驱动接口也是ioctl)。
ioctl命令遵从的一种编码规则(内核源码版本中查看)
比特位 | 说明 |
31-30 | 00-命令不带参数 |
10-命令需要从驱动中获取数据,读方向 | |
01-命令需要把数据写入驱动,写方向 | |
11-命令既需要写入数据又需要获取数据,读写双向 | |
29-16 | 如果命令带参数,则指定参数所占的内存空间大小 |
15-8 | 每个驱动全局唯一的幻数(魔数) |
7-0 | 命令码 |
内核提供了一组宏来定义、提取命令中的字段信息
#define _IOC(dir,type,nr,size)\
((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT | \
((nr) << _IOC_NRSHIFT | \
((size) << _IOC_SIZESHIFT))
#ifndef __KERNRL__
#define _IOC_TYPECHECK(t) (SIZEOF(T))
#endif
#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 _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)
定义命令使用的是最底层的宏_IOC,它将4个部分通过移位合并在一起。
部分代码示例:
.h
#ifndef _VSER_H
#define _VSER_H
struct option {
unsigned int datab;
unsigned int parity;
unsigned int stopb;
};
#define VS_MAGIC 's'
#define VS_SET_BAUD _IOW(VS_MAGIC, 0, unsigned int)
#define VS_GET_BAUD _IOW(VS_MAGIC, 1, unsigned int)
#define VS_SET_FFMT _IOW(VS_MAGIC, 2, struct option)
#define VS_GET_FFMT _IOW(VS_MAGIC, 3, struct option)
#endif
.c
#include <linux/init.h>
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/kfifo.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include "vser.h"
#define VSER_MAJOR 256
#define VSER_MINOR 0
#define VSER_DEV_CNT 1
#define VSER_DEV_NAME "vser"
struct vser_dev {
unsigned int baud;
struct option opt;
struct cdev cdev;
};
DEFINE_KFIFO(vsfifo, char, 32);
static struct vser_dev vsdev;
...
static long vser_ioctl(struct file *filp, unsigned int cmd, unsigned long arg)
{
if (_IOC_TYPE(cmd) != VS_MAGIC)
return -ENOTTY;
switch (cmd) {
case VS_SET_BAUD:
vsdev.baud = arg;
break;
case VS_GET_BAUD:
arg = vsdev.baud;
break;
case VS_SET_FFMT:
if (copy_from_user(&vsdev.opt, (struct option __user *)arg, sizeof(struct option)))
return -EFAULT;
break;
case VS_GET_FFMT:
if (copy_to_user((struct option __user *)arg, &vsdev.opt, sizeof(struct option)))
return -EFAULT;
break;
default:
return -ENOTTY;
}
return 0;
}
static struct file_operations vser_ops = {
.owner = THIS_MODULE,
.open = vser_open,
.release = vser_release,
.read = vser_read,
.write = vser_write,
.unlocked_ioctl = vser_ioctl,
};
static int __init vser_init(void)
{
int ret;
dev_t dev;
dev = MKDEV(VSER_MAJOR, VSER_MINOR);
ret = register_chrdev_region(dev, VSER_DEV_CNT, VSER_DEV_NAME);
if (ret)
goto reg_err;
cdev_init(&vsdev.cdev, &vser_ops);
vsdev.cdev.owner = THIS_MODULE;
vsdev.baud = 115200;
vsdev.opt.datab = 8;
vsdev.opt.parity = 0;
vsdev.opt.stopb = 1;
ret = cdev_add(&vsdev.cdev, dev, VSER_DEV_CNT);
if (ret)
goto add