ioctl 使用

 

ioctl简介

kernel3.0之前,叫ioctl,之后改名为unlocked_ioctl。功能和接口基本相同,名字发生了变化

ioctl既可以往内核读也可以写,read/write在执行大数据量读/写时比较有优势。

在应用层调用ioctl函数时,内核会调用对应驱动中的ublocked_ioctl函数,向内核读写数据。

驱动内的unlocked_ioctl函数

unlocked_ioctl函数属于file_operations文件操作集的一个成员,结构体内函数的定义为:

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
//参数:句柄;接口命令;从应用层传入的据
//返回值:返回给应用层的数据

应用层的ioctl函数

定义在头文件<sys/ioctl.h>中,是个可变参数的函数

int ioctl(int fd, ind cmd, …);
//参数和返回值,和文件操作集里的unlocked_ioctl函数一致
//传给内核的参数可以省略

unlocked_ioctl接口命令规则

命令是一个整型参数(32位)

第一个分区:0-7,命令的编号,范围是0-255
第二个分区:8-15,命令的幻数
第三个分区:16-29,表示传递的数据的大小
第四分区:30-31,代表读写的方向


第一和第二分区,主要用来区分命令,不能有两个完全一样的“一+二”
第四分区:00:没有数据传递;10:用户从驱动读数据;01:用户向驱动写数据;11:先写数据到驱动再把数据读出来

接口命令相关的宏

合成宏,返回一个接口命令,可传入ioctl函数

_IO(type,nr)   没有数据传递的命令
_IOR(type, nr, size)  从驱动中读取数据的命令
_IOW(type, nr, size)  向驱动中写入数据的命令
_IOWR(type, nr, size) 交换数据的命令
//type表示数据的幻数,8-15位
//nr命令的编号,0-7位
//size参数传递的大小,传递的是数据类型:如果传递4字节,可以写成int

分解宏,将接口命令解析出需要的部分

_IOC_DIR(nr)   方向
_IOC_TYPE(nr)  幻数
_IOC_NR(nr)    编号
_IOC_SIZE(nr)   大小

//nr要分解的命令

示例代码

以杂项设备为例

驱动部分

#include <linux/init.h>
#include <linux/module.h>
#include <linux/miscdevice.h>
#include <linux/fs.h>
#include <linux/uaccess.h>
#include <linux/io.h>

#define CMD_TEST_0 _IO('A', 0)       //不需要读写的命令
#define CMD_TEST_1 _IOR('A', 1, int) //从内核读取一个int的命令
#define CMD_TEST_2 _IOW('A', 2, int) //向内核写入一个int的命令
#define CMD_TEST_3 _IOWR('A', 3, int) //读写一个int的命令

int misc_open(struct inode *a,struct file *b){
    printk("misc open \n");
    return 0;
}

int misc_release (struct inode * a, struct file * b){
    printk("misc file release\n");
    return 0;
}

long misc_ioctl(struct file *fd, unsigned int cmd, unsigned long b){
    /*将命令按内容分解,打印出来*/
    printk("cmd type=%c\t nr=%d\t dir=%d\t size=%d\n", _IOC_TYPE(cmd), _IOC_NR(cmd), _IOC_DIR(cmd), _IOC_SIZE(cmd));

    switch(cmd){
        case CMD_TEST_0:
            printk("CMD_TEST_0\n");
            break;
        case CMD_TEST_1:
            printk("CMD_TEST_1\n");
            return 1;
            break;
        case CMD_TEST_2:
            printk("CMD_TEST_2 date=%d\n",b);
            break;
        case CMD_TEST_3:
            printk("CMD_TEST_3 date=%d\n",b);
            return b+1;
            break;
    }

    return 0;
}

//文件操作集
struct file_operations misc_fops = {
    .owner = THIS_MODULE,
    .open = misc_open,
    .release = misc_release,
    .unlocked_ioctl = misc_ioctl
};

struct miscdevice misc_dev = {
    .minor = MISC_DYNAMIC_MINOR,
    .name = "test_ioctrl",   //设备节点名
    .fops = &misc_fops
};

static int hello_init(void){
    int ret;

    ret = misc_register(&misc_dev);  //注册杂项设备
    if(ret < 0){
        printk("misc regist failed\n");
        return -1;
    }

    printk("misc regist succeed\n");
    return 0;
}

static void hello_exit(void){
    misc_deregister(&misc_dev);
}

MODULE_LICENSE("Dual BSD/GPL");
MODULE_AUTHOR("TAXUE");

module_init(hello_init); 
module_exit(hello_exit);  

应用层部分

#include<stdio.h>
#include<stdlib.h>
#include<fcntl.h>
#include<string.h>
#include<unistd.h>
#include<sys/ioctl.h>

#define CMD_TEST_0 _IO('A', 0)
#define CMD_TEST_1 _IOR('A', 1, int)
#define CMD_TEST_2 _IOW('A', 2, int)
#define CMD_TEST_3 _IOWR('A', 3, int)

int main(int argc, char *argv[]){
    int fd=0;
    int revData=0;

    fd = open("/dev/test_ioctrl", O_RDWR);
    if(fd < 0){
        printf("open failed\n");
        exit(1);
    }
    printf("open success\n");

    /*依次调用四个命令*/
    ioctl( fd, CMD_TEST_0);
    
    revData = ioctl( fd, CMD_TEST_1);
    printf("receive 1 data=%d\n", revData);

    ioctl( fd, CMD_TEST_2, 99);

    revData = ioctl( fd, CMD_TEST_3, 101);
    printf("receive 3 data=%d\n", revData);

    close(fd);
    return 0;
}


=========================================================

ioctl
在用户空间, ioctl 系统调用有下面的原型:

int ioctl(int fd, unsigned long cmd, ...);
1
ioctl 驱动方法有和用户空间版本不同的原型:

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);
1
inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数. cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针. 如果调用程序不传递第 3 个参数, 被驱动操作收到的 arg 值是无定义的. 因为类型检查在这个额外参数上被关闭, 编译器不能警告你如果一个无效的参数被传递给 ioctl, 并且任何关联的错误将难以查找.

ioctl 命令数字应当在这个系统是唯一的, 为了阻止向错误的设备发出正确的命令而引起的错误. 为帮助程序员创建唯一的 ioctl 命令代码, 这些编码已被划分为几个位段.

定义 ioctl 命令号的正确方法使用 了4 个位段, 它们有下列的含义.
type
幻数(魔数). 占了8位,可以用数字或者字符来标记类型。
number
序(顺序)号. 它是 8位宽,代表是该类型命令下的第几个命令.
direction
数据传送的方向,占2位。如果这个特殊的命令涉及数据传送. 可能的值是 _IOC_NONE(没有数据传输), _IOC_READ, _IOC_WRITE, 和 _IOC_READ|_IOC_WRITE (数据在2个方向被传送). 数据传送是从应用程序的观点来看待的; _IOC_READ 意思是从设备读, 因此设备必须写到用户空间. 注意这个成员是一个位掩码, 因此 _IOC_READ 和 _IOC_WRITE 可使用一个逻辑 AND 操作来抽取.
size
涉及到的用户数据的大小. 这个成员的宽度是依赖体系的, 但是常常是 13 或者 14 位. 你可为你的特定体系在宏 _IOC_SIZEBITS 中找到它的值. 你使用这个 size 成员不是强制的 - 内核不检查它 – 但是它是一个好主意. 正确使用这个成员可帮助检测用户空间程序的错误并使你实现向后兼容, 如果你曾需要改变相关数据项的大小. 如果你需要更大的数据结构, 但是, 你可忽略这个 size 成员. 我们很快见到如何使用这个成员.

定义宏来帮助建立命令号, 如下:
_IO(type,nr)(给没有参数的命令),
_IOR(type, nre, datatype)(给从驱动中读数据的),
_IOW(type,nr,datatype)(给写数据),
_IOWR(type,nr,datatype)(给双向传送).
type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到.

驱动中来解码这个号: _IOC_DIR(cmd), _IOC_TYPE(cmd), _IOC_NR(cmd), 和 _IOC_SIZE(cmd).

使用 ioctl 参数
我们需要涉及的另一点是如何使用这个额外的参数. 如果它是一个整数, 就容易,它可以直接使用. 但是如果它是一个指针, 必须小心些.
当用一个指针引用用户空间, 我们必须确保用户地址是有效的. 试图存取一个没验证过的用户提供的指针可能导致不正确的行为, 一个内核 oops, 系统崩溃, 或者安全问题. 它是驱动的责任来对每个它使用的用户空间地址进行正确的检查, 并且返回一个错误如果它是无效的.
copy_from_user 和 copy_to_user 函数, 它们可用来安全地移动数据到和从用户空间. 这些函数也可用在 ioctl 方法中, 但是 ioctl 调用常常包含小数据项, 可通过其他方法更有效地操作. 开始, 地址校验(不传送数据)由函数 access_ok 实现, 它定义在 :
int access_ok(int type, const void *addr, unsigned long size);
在较新一点得内核中是
int access_ok(const void *addr, unsigned long size);
第一个参数应当是 VERIFY_READ 或者 VERIFY_WRITE, 依据这个要进行的动作是否是读用户空间内存区或者写它. addr 参数持有一个用户空间地址, size 是一个字节量. 例如, 如果 ioctl 需要从用户空间读一个整数, size 是 sizeof(int). 如果你需要读和写给定地址, 使用 VERIFY_WRITE, 因为它是 VERIRY_READ 的超集.
不象大部分的内核函数, access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题). 如果它返回假, 驱动应当返回 -EFAULT 给调用者.

示例代码:
mioctl.c

#include <linux/module.h>
#include <linux/fs.h>
#include <linux/slab.h>
#include <linux/device.h>
#include <linux/cdev.h>
#include <linux/ioctl.h>
#include <linux/uaccess.h>
#include "cmd.h"

#define DEV_NAME "ioctl_test"
#define DEV_NUM (1)


static int kernel_num = 10;

static struct my_st
{
int major;
int minor;
dev_t dev_num;
struct cdev cdev;
struct class *class;
struct device *device;
}*my;

static int ioctl_open(struct inode *inode,struct file *flip)
{
printk("open()\r\n");
return 0;
}

static int ioctl_release(struct inode *inode,struct file *flip)
{
printk("close()\r\n");
return 0;
}

static long ioctl_ioctl(struct file *flip,unsigned int cmd,unsigned long arg)
{
int err;
void __user *argp = (void __user *)arg;

if(_IOC_DIR(cmd) & _IOC_READ)
err = !access_ok(argp,_IOC_SIZE(cmd));
else if(_IOC_DIR(cmd) & _IOC_WRITE)
err = !access_ok(argp,_IOC_SIZE(cmd));
if(err)
return -EFAULT;

switch(cmd)
{
case DEV_CMD:
printk("cmd\r\n");
break;
case DEV_GET_CMD:
copy_to_user(argp,&kernel_num,_IOC_SIZE(cmd));
printk("get cmd\r\n");
break;
case DEV_SET_CMD:
copy_from_user(&kernel_num,argp,_IOC_SIZE(cmd));
printk("data = %d\r\n",kernel_num);
printk("set cmd\r\n");
break;
default:
printk("error");
return -EINVAL;
}

return 0;
}

 

static ssize_t ioctl_write(struct file *flip,const char __user *buf,size_t count,loff_t *offset)
{
return 0;
}


static ssize_t ioctl_read(struct file *flip,char __user *buf,size_t count,loff_t *offset)
{
return 0;
}

static const struct file_operations fops ={
.owner = THIS_MODULE,
.open = ioctl_open,
.read = ioctl_read,
.write = ioctl_write,
.unlocked_ioctl = ioctl_ioctl,
.release = ioctl_release,
};


static int __init mioctl_init(void)
{
my = kzalloc(sizeof(struct my_st),GFP_KERNEL);
if(my == NULL)
return -ENOMEM;

int ret = alloc_chrdev_region(&my->dev_num,DEV_NUM,0,DEV_NAME);
if(ret < 0)
goto alloc_chrdev_failed;

my->major = MAJOR(my->dev_num);
my->minor = MINOR(my->dev_num);

cdev_init(&my->cdev,&fops);
my->cdev.owner = THIS_MODULE;
ret = cdev_add(&my->cdev,my->dev_num,DEV_NUM);
if(ret < 0)
goto cdev_add_failed;

my->class = class_create(THIS_MODULE,DEV_NAME);
if(IS_ERR(my->class))
{
ret = PTR_ERR(my->class);
goto class_create_failed;
}

my->device = device_create(my->class,NULL,my->dev_num,NULL,DEV_NAME);
if(IS_ERR(my->device))
{
ret = PTR_ERR(my->device);
goto device_create_failed;
}

printk("ioctl device is ok !\r\n");
printk("主设备号:%d\r\n",my->major);
return 0;

device_create_failed:
class_destroy(my->class);
class_create_failed:
cdev_del(&my->cdev);
cdev_add_failed:
unregister_chrdev_region(my->dev_num,DEV_NUM);
alloc_chrdev_failed:
kfree(my);
return ret;
}

static void __exit mioctl_exit(void)
{
device_destroy(my->class,my->dev_num);
class_destroy(my->class);
cdev_del(&my->cdev);
unregister_chrdev_region(my->dev_num,DEV_NUM);
kfree(my);
printk("ioctl device was remove\r\n");
}
module_init(mioctl_init);
module_exit(mioctl_exit);
MODULE_LICENSE("GPL");
MODULE_AUTHOR("Chen");
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
Makefile

ifneq ($(KERNELRELEASE),)
obj-m:=mioctl.o
else
CURRENT_DIR:=$(shell pwd)
KERNEL_DIR:=/lib/modules/$(shell uname -r)/build
ccflags-y:=-std=gnu99 -Wno-declaration-after-statement
all:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) modules
clean:
$(MAKE) -C $(KERNEL_DIR) M=$(CURRENT_DIR) clean
endif
1
2
3
4
5
6
7
8
9
10
11
cmd.h

#ifndef __CMD_H
#define __CMD_H

#define DEV_TYPE 'c'
#define DEV_CMD _IO(DEV_TYPE,0)
#define DEV_GET_CMD _IOR(DEV_TYPE,1,int)
#define DEV_SET_CMD _IOW(DEV_TYPE,2,int)

#endif
1
2
3
4
5
6
7
8
9
测试代码app.c

#include <stdio.h>
#include <stdlib.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include "cmd.h"

int main()
{
int arg = 30;
int fd = open("/dev/ioctl_test",O_RDWR);
if(fd < 0)
{
perror("");
exit(1);
}

int ret = ioctl(fd,DEV_GET_CMD,&arg);
if(ret < 0)
{
perror("");
exit(1);
}

int ret = ioctl(fd,DEV_GET_CMD,&arg);
if(ret < 0)
{
perror("");
exit(1);
}

printf("arg = %d\r\n",arg);

arg = 100;

ret = ioctl(fd,DEV_SET_CMD,&arg);
if(ret < 0)
{
perror("");
exit(1);
}

close(fd);
exit(1);
}
————————————————

版权声明:本文为博主原创文章,遵循 CC 4.0 BY-SA 版权协议,转载请附上原文出处链接和本声明。

原文链接:https://blog.csdn.net/weixin_45619852/article/details/120833505

 

参考:

https://blog.csdn.net/weixin_45619852/article/details/120833505

https://www.cnblogs.com/TaXueWuYun/p/15315594.html

posted @ 2024-07-16 11:58  redrobot  阅读(4)  评论(0编辑  收藏  举报