用户空间:int ioctl(int fd, unsigned long cmd, ...);
内核空间:int (unlocked_ioctl)(struct file *filep, unsigned int cmd, unsigned long arg);
ioctl命令定义:
<linux/ioctl.h>
4个位字段:
type设备类型(幻数),8bit
number(序数),8bit
direction(方向),2bit,包括:_IOC_NONE(没有数据传输)、_IOC_READ、_IOC_WRITE、及_IOC_READ | _IOC_WRITE(双向数据传输)
size(用户数据大小)8~14bit,通常13或14bit
构造命令编号宏:
<linux/ioctl.h>
_IO(type, nr) 用于构造无参数的命令编号;
_IOR(type, nr, datatype) 用于构造从驱动程序中读取数据的命令编号;
_IOW(type, nr, datatype) 用于构造写入数据的命令编号;
_IOWR(type, nr, datatype) 用于构造写双向传输命令编号;
type:幻数,nr:序数,datatype:数据类型
解开位字段的宏:
_IOC_DIR(cmd) 获取方向;
_IOC_TYPE(cmd) 获取幻数;
_IOC_NR(cmd) 获取序数;
_IOC_SIZE(cmd) 获取数据大小;
使用ioctl参数:
验证地址:int access_ok(int type, const void *addr, unsigned long size);
type:VERIFY_READ或VERIFY_WRITE,取决于要执行的动作是读取还是写入用户空间内存区。
addr:用户空间的地址
size:数据大小
返回值:1成功,0失败
如果在指定地址既要读取又要写入,则type为VERIFY_WRITE,它是VERIFY_READ的超集。
数据传输:
<linux/uaccess.h>
使用为最常用的数据大小(1、2、4、8个字节)优化过的一组函数。
把数据写到用户空间:
put_user(dataum, ptr) (调用access_ok进行地址检查)
__put_user(dataum, ptr) (不调用access_ok,使用前要调用access_ok)
成功返回0,失败返回-EFAULT
从用户空间接收一个数据:
get_user(local, ptr)
__get_user(local, ptr)
除了传输方向不同,和put_user、__put_user差不多。
上面函数只适合传输1、2、4、8字节大小的数据,如果需要传输其他大小的数据必须使用copy_to_user或copy_from_user
权能与受限操作:
对设备的访问由设备文件的权限控制,驱动程序通常不进行权限检查。
权能操作定义在<linux/capacity.h>
如:CAP_SYS_ADMIN 截获的能力,提供了访问许多系统管理操作的途径
检查权能:int capable(int capacity); 声明在<linux/sched.h>,失败返回0
非ioctl的设备控制:
通过向设备写入控制序列控制设备,适合不传送数据而只响应命令的设备,如机器人。
ioctl例程:
#ifndef _HELLOCMD_H_
#define _HELLOCMD_H_
#define HELLO_IOC_MAGIC 'Q'
#define HELLO_MAX_NR 2
#define HELLO_IOC_GET_VAL _IOR(HELLO_IOC_MAGIC, 0, int)
#define HELLO_IOC_SET_VAL _IOW(HELLO_IOC_MAGIC, 1, int)
#endif
#include <linux/init.h>
#include <linux/module.h>
#include <linux/moduleparam.h>
#include <linux/kdev_t.h>
#include <linux/fs.h>
#include <linux/cdev.h>
#include <linux/uaccess.h>
#include <linux/io.h>
#include <linux/sched.h>
#include "helloCmd.h"
static int major = 277;
static int minor = 0;
static dev_t helloNum;
// static struct cdev helloCdev;
struct class *helloClass = NULL;
struct device *helloDev = NULL;
static int helloVal = 7;
int hello_open(struct inode *pinode, struct file *pfile)
{
printk("hello_open, minor:%d, major:%d\n", iminor(pinode), imajor(pinode));
return 0;
}
int hello_release(struct inode *pinode, struct file *pfile)
{
printk("hello_release, minor:%d, major:%d\n", iminor(pinode), imajor(pinode));
return 0;
}
long hello_ioctl(struct file *filep, unsigned int cmd, unsigned long arg)
{
int err = 0;
if (HELLO_IOC_MAGIC != _IOC_TYPE(cmd))
{
pr_err("bad magic.\n");
return -ENOTTY;
}
if (_IOC_NR(cmd) > HELLO_MAX_NR)
{
pr_err("bad nr.\n");
return -ENOTTY;
}
if (_IOC_DIR(cmd) & _IOC_READ)
{
err = !access_ok(VERIFY_WRITE, (void __user *)arg, _IOC_SIZE(cmd));
}
else if (_IOC_DIR(cmd) & _IOC_WRITE)
{
err = !access_ok(VERIFY_READ, (void __user *)arg, _IOC_SIZE(cmd));
}
if (err)
{
pr_err("bad address.\n");
return -EFAULT;
}
switch(cmd)
{
case HELLO_IOC_GET_VAL:
printk("HELLO_IOC_GET_VAL.\n");
err = put_user(helloVal, (int __user *)arg);
break;
case HELLO_IOC_SET_VAL:
if (!capable(CAP_SYS_ADMIN))
{
pr_err("without permission.\n");
return -EPERM;
}
printk("HELLO_IOC_SET_VAL.\n");
err = get_user(helloVal, (int __user *)arg);
break;
default:
pr_err("bad cmd.\n");
err = -ENOTTY;
break;
}
return err;
}
static struct file_operations hello_ops = {
.open = hello_open,
.release = hello_release,
.unlocked_ioctl = hello_ioctl,
};
static int hello_init(void)
{
int ret = 0;
printk("hello_init\n");
ret = register_chrdev( major, "hello", &hello_ops);
if(ret < 0)
{
printk("register_chrdev failed.\n");
return ret;
}
helloClass = class_create(THIS_MODULE, "hellocls");
if (IS_ERR(helloClass))
{
printk("class_create failed.\n");
ret = PTR_ERR(helloClass);
goto error_exit1;
}
helloNum = MKDEV(major,minor);
printk("major:%d, minor:%d\n", MAJOR(helloNum), MINOR(helloNum));
helloDev = device_create(helloClass, NULL, helloNum, NULL, "hello0");
if (IS_ERR(helloDev))
{
printk("device_create failed.\n");
ret = PTR_ERR(helloDev);
goto error_exit2;
}
return 0;
error_exit2:
class_destroy(helloClass);
error_exit1:
unregister_chrdev(major,"hello");
return ret;
}
static void hello_exit(void)
{
printk("hello_exit\n");
device_destroy(helloClass, helloNum);
class_destroy(helloClass);
unregister_chrdev(major,"hello");
}
MODULE_LICENSE("GPL");
module_init(hello_init);
module_exit(hello_exit);
#include <stdio.h>
#include <stdlib.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h>
#include "helloCmd.h"
int main(int argc, char * argv [ ])
{
int fd = open("/dev/hello0", O_RDWR, 666);
if (fd < 0)
{
perror("open faied");
return -1;
}
printf("open successed: %d\n", fd);
int num = 0;
int ret = ioctl(fd, HELLO_IOC_GET_VAL, &num);
if (ret < 0)
{
printf("HELLO_IOC_GET_VAL failed\n");
}
printf("HELLO_IOC_GET_VAL:%d\n", num);
num = 77;
ret = ioctl(fd, HELLO_IOC_SET_VAL, &num);
if (ret < 0)
{
printf("HELLO_IOC_SET_VAL failed\n");
}
printf("HELLO_IOC_SET_VAL:%d\n", num);
ret = ioctl(fd, HELLO_IOC_GET_VAL, &num);
if (ret < 0)
{
printf("HELLO_IOC_GET_VAL failed\n");
}
printf("HELLO_IOC_GET_VAL:%d\n", num);
sleep(2);
close(fd);
return 0;
}