ioctl函数详解(Linux内核 )

ioctl函数详解(Linux内核 )1.概念ioctl是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设ioctl()命令的方式实现。在文件I/O中,ioctl扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析ioctl函数。2.用户空间ioctl#include<sys/ioctl.h>intioctl(intfd,intcmd,…);参数描述

大家好,又见面了,我是你们的朋友全栈君。如果您正在找激活码,请点击查看最新教程,关注关注公众号 “全栈程序员社区” 获取激活教程,可能之前旧版本教程已经失效.最新Idea2022.1教程亲测有效,一键激活。

Jetbrains全系列IDE稳定放心使用

1. 概念

ioctl 是设备驱动程序中设备控制接口函数,一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。

在文件 I/O 中,ioctl 扮演着重要角色,本文将以驱动开发为侧重点,从用户空间到内核空间纵向分析 ioctl 函数。

在这里插入图片描述

2. 用户空间 ioctl

#include <sys/ioctl.h> 

int ioctl(int fd, int cmd, ...) ;
参数 描述
fd 文件描述符
cmd 交互协议,设备驱动将根据 cmd 执行对应操作
可变参数arg,依赖 cmd 指定长度以及类型

ioctl() 函数执行成功时返回 0,失败则返回 -1 并设置全局变量 errorno 值,因此在用户空间使用 ioctl 时,可以做如下的出错判断以及处理:

int ret;
ret = ioctl(fd, MYCMD);
if (ret == -1) { 
   
    printf("ioctl: %s\n", strerror(errno));
}

在实际应用中,ioctl 最常见的 errorno 值为 ENOTTY(error not a typewriter),顾名思义,即第一个参数 fd 指向的不是一个字符设备,不支持 ioctl 操作,这时候应该检查前面的 open 函数是否出错或者设备路径是否正确。

3. 驱动程序 ioctl

long (*unlocked_ioctl) (struct file *, unsigned int, unsigned long);
long (*compat_ioctl) (struct file *, unsigned int, unsigned long);

在字符设备驱动开发中,一般情况下只要实现 unlocked_ioctl 函数即可,因为在 vfs 层的代码是直接调用 unlocked_ioctl 函数。

// fs/ioctl.c

static long vfs_ioctl(struct file *filp, unsigned int cmd,
              unsigned long arg)
{ 
   
    int error = -ENOTTY;

    if (!filp->f_op || !filp->f_op->unlocked_ioctl)           
        goto out;

    error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
    if (error == -ENOIOCTLCMD) { 
   
        error = -ENOTTY;
    }   
 out:
    return error;
}

4. ioctl 用户与驱动之间的协议

前文提到 ioctl 方法第二个参数 cmd 为用户与驱动的 “协议”,理论上可以为任意 int 型数据,可以为 0、1、2、3……,但是为了确保该 “协议” 的唯一性,ioctl 命令应该使用更科学严谨的方法赋值,在linux中,提供了一种 ioctl 命令的统一格式,将 32 位 int 型数据划分为四个位段,如下图所示:

在这里插入图片描述

//ioctl.h
#define _IOC(dir,type,nr,size) \ (((dir) << _IOC_DIRSHIFT) | \ ((type) << _IOC_TYPESHIFT) | \ ((nr) << _IOC_NRSHIFT) | \ ((size) << _IOC_SIZESHIFT))
  • dir(direction),ioctl 命令访问模式(数据传输方向),占据 2 bit,可以为 _IOC_NONE、_IOC_READ、_IOC_WRITE、_IOC_READ | _IOC_WRITE,分别指示了四种访问模式:无数据、读数据、写数据、读写数据;
  • size,涉及到 ioctl 函数第三个参数 arg ,占据14bit,指定了 arg 的数据类型及长度;
  • type(device type),设备类型,占据 8 bit,可以为任意 char 型字符,例如
    ‘a’、’b’、’c’ 等等,其主要作用是使 ioctl 命令有唯一的设备标识;
  • nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;

通常而言,为了方便会使用宏 _IOC() 衍生的接口来直接定义 ioctl 命令:

//ioctl.h
/* used to create numbers */
#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)))
_IOC_NONE:值为0,无数据传输。
_IOC_READ:值为1,从设备驱动读取数据。
_IOC_WRITE:值为2,往设备驱动写入数据。
_IOC_READ|_IOC_WRITE:双向数据传输。
_IO(type,nr):       定义不带参数的 ioctl 命令
_IOR(type,nr,size):      定义带写参数的 ioctl 命令(copy_from_user)
_IOW(type,nr,size):      定义带读参数的ioctl命令(copy_to_user)
_IOWR(type,nr,size):     定义带读写参数的 ioctl 命令
//同时,内核还提供了反向解析 ioctl 命令的宏接口:
/* used to decode ioctl numbers */
#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)

5. ioctl_test 实例分析

本例假设一个带寄存器的设备,设计了一个 ioctl 接口实现设备初始化、读写寄存器等功能。在本例中,为了携带更多的数据,ioctl 的第三个可变参数为指针类型,指向自定义的结构体 struct msg。

(1)ioctl-test.h,用户空间和内核空间共用的头文件,包含 ioctl 命令及相关宏定义,可以理解为一份 “协议” 文件,代码如下:

// ioctl-test.h
#ifndef __IOCTL_TEST_H__
#define __IOCTL_TEST_H__
#include <linux/ioctl.h> // 内核空间
#include <sys/ioctl.h> // 用户空间
/* 定义设备类型 */
#define IOC_MAGIC 'c'
/* 初始化设备 */
#define IOCINIT _IO(IOC_MAGIC, 0)
/* 读寄存器 */
#define IOCRREG _IOR(IOC_MAGIC, 1, int)
/* 写寄存器 */
#define IOCWREG _IOW(IOC_MAGIC, 2, int)
#define IOC_MAXNR 3
struct msg { 

int addr;
unsigned int data;
};
#endif

(2)ioctl-test-driver.c,字符设备驱动,实现了unlocked_ioctl 接口,根据上层用户的 cmd 执行对应的操作(初始化设备、读寄存器、写寄存器)。在接收上层 cmd 之前应该对其进行充分的检查,流程及具体代码实现如下:

// ioctl-test-driver.c
static const struct file_operations fops = { 

.owner = THIS_MODULE,
.open = test_open,
.release = test_close,
.read = test_read,
.write = test_write,
.unlocked_ioctl = test_ioctl,
};
......
static long test_ioctl(struct file *file, unsigned int cmd, \
unsigned long arg)
{ 

//printk("[%s]\n", __func__);
int ret;
struct msg my_msg;
/* 检查设备类型 */
if (_IOC_TYPE(cmd) != IOC_MAGIC) { 

pr_err("[%s] command type [%c] error!\n", \
__func__, _IOC_TYPE(cmd));
return -ENOTTY; 
}
/* 检查序数 */
if (_IOC_NR(cmd) > IOC_MAXNR) { 
 
pr_err("[%s] command numer [%d] exceeded!\n", 
__func__, _IOC_NR(cmd));
return -ENOTTY;
}    
/* 检查访问模式 */
if (_IOC_DIR(cmd) & _IOC_READ)
ret= !access_ok(VERIFY_WRITE, (void __user *)arg, \
_IOC_SIZE(cmd));
else if (_IOC_DIR(cmd) & _IOC_WRITE)
ret= !access_ok(VERIFY_READ, (void __user *)arg, \
_IOC_SIZE(cmd));
if (ret)
return -EFAULT;
switch(cmd) { 

/* 初始化设备 */
case IOCINIT:
init();
break;
/* 读寄存器 */
case IOCRREG:
ret = copy_from_user(&msg, \
(struct msg __user *)arg, sizeof(my_msg));
if (ret) 
return -EFAULT;
msg->data = read_reg(msg->addr);
ret = copy_to_user((struct msg __user *)arg, \
&msg, sizeof(my_msg));
if (ret) 
return -EFAULT;
break;
/* 写寄存器 */
case IOCWREG:
ret = copy_from_user(&msg, \
(struct msg __user *)arg, sizeof(my_msg));
if (ret) 
return -EFAULT;
write_reg(msg->addr, msg->data);
break;
default:
return -ENOTTY;
}
return 0;
}

(3)ioctl-test.c,运行在用户空间的测试程序:

// ioctl-test.c
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <sys/ioctl.h> 
#include "ioctl-test.h"
int main(int argc, char **argv)
{ 

int fd;
int ret;
struct msg my_msg;
fd = open("/dev/ioctl-test", O_RDWR);
if (fd < 0) { 

perror("open");
exit(-2);
}
/* 初始化设备 */
ret = ioctl(fd, IOCINIT);
if (ret) { 

perror("ioctl init:");
exit(-3);
}
/* 往寄存器0x01写入数据0xef */
memset(&my_msg, 0, sizeof(my_msg));
my_msg.addr = 0x01;
my_msg.data = 0xef;
ret = ioctl(fd, IOCWREG, &my_msg);
if (ret) { 

perror("ioctl read:");
exit(-4);
}
/* 读寄存器0x01 */
memset(&my_msg, 0, sizeof(my_msg));
my_msg.addr = 0x01;
ret = ioctl(fd, IOCRREG, &my_msg);
if (ret) { 

perror("ioctl write");
exit(-5);
}
printf("read: %#x\n", my_msg.data);
return 0;
}
版权声明:本文内容由互联网用户自发贡献,该文观点仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 举报,一经查实,本站将立刻删除。

发布者:全栈程序员-用户IM,转载请注明出处:https://javaforall.cn/180890.html原文链接:https://javaforall.cn

【正版授权,激活自己账号】: Jetbrains全家桶Ide使用,1年售后保障,每天仅需1毛

【官方授权 正版激活】: 官方授权 正版激活 支持Jetbrains家族下所有IDE 使用个人JB账号...

(0)
blank

相关推荐

  • Android Bundle类

    Android Bundle类

  • 深度学习笔记(三):激活函数和损失函数

    深度学习笔记(三):激活函数和损失函数这一部分来探讨下激活函数和损失函数。在之前的logistic和神经网络中,激活函数是sigmoid,损失函数是平方函数。但是这并不是固定的。事实上,这两部分都有很多其他不错的选项,下面来一一讨论3.激活函数和损失函数3.1激活函数关于激活函数,首先要搞清楚的问题是,激活函数是什么,有什么用?不用激活函数可不可以?答案是不可以。激活函数的主要作用是提供网络的非线性建模能力。如果没有激活函数,那么

  • 移动文件到文件夹 linux_centos移动文件

    移动文件到文件夹 linux_centos移动文件Linux移动或复制文件(文件夹)命令格式:cp-rf/home/backup/default/Public/Public/复制/home/backup/default/Public文件夹到当前文件夹下补充cp该命令的各选项含义如下-a该选项通常在拷贝目录时使用。它保留链接、文件属性,并递归地拷贝目录,其作用等于dpR选项的组合。  -d拷贝时保留链接。…

  • java的输入和输出语句_c++输入输出语句

    java的输入和输出语句_c++输入输出语句输入语句:psvm输出语句:sout

  • 手把手教你制作一个简单的聊天机器人(图灵api)「建议收藏」

    手把手教你制作一个简单的聊天机器人(图灵api)「建议收藏」前言:在无聊的时候打打游戏、听听歌还不如来找个人来陪你聊天,今天来教大家制作一个聊天机器人,这样就不会无聊了,在线聊天机器人地址借愁哥哥机器人(可能有点丑,大家将就一下(????))这个接口就目前的一天100次聊天机会,大家要珍惜哦,源码在文章末尾哦!效果图:目录:一.准备工作二.项目开始1.页面布局:2.样式层:3.逻辑实现:一.准备工作通过分析我们需要以下的具体准备:对于界面的分析,我们需要用到的插件:jQuery,我们采用的是flex弹性布局,既然使用的是图灵机器人

  • 【python】蒙特卡洛树搜索(MCTS)简单实现

    过程包括以下四步:选择Selection:从根节点R开始,递归选择最优的子节点(后面会解释)直到达到叶子节点L。扩展Expansion:如果L不是一个终止节点(也就是,不会导致博弈游戏终止)那么就创建一个或者更多的字子节点,选择其中一个C。模拟Simulation:从C开始运行一个模拟的输出,直到博弈游戏结束。反向传播Backpropagation:用模拟的结果输…

发表回复

您的电子邮箱地址不会被公开。

关注全栈程序员社区公众号