老程序员分享:linux驱动开发笔记_ioctl函数

简介: 老程序员分享:linux驱动开发笔记_ioctl函数

1.相关概念


ioctl 是设备驱动程序中设备控制接口函数。某些设备除了打开、关闭、读出和写入功能外,可能还有其它的功能,比如说设置串口波特率、设置马达的转速等等。


1.用户空间函数


#include


int ioctl (int fd, unsigned int cmd, ...)


1


2


参数 描述


fd 打开文件描述符


cmd 交互协议,设备驱动将根据cmd执行相应的操作


… 可变参数arg,依赖cmd中指定的长度以及类型


ioctl()函数执行成功之后则会返回0;失败的话就会返回 -1;


2.驱动程序函数


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


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


1


2


unlocked_ioctl,顾名思义,应该在无大内核锁(BKL)的情况下调用;


compat 全称 compatible(兼容的),主要目的是为 64 位系统提供 32 位 ioctl 的兼容方法,也是在无大内核锁的情况下调用。


其中第二个参数是 cmd 从用户空间无改变的传过来的,第三个参数是传过来的用户空间的可寻址地址;


3. ioctl中的cmd(交互协议)


交互协议,就是用户空间和驱动程序之间达成的一致性协议。需要完成什么功能都在这个协议中得到包含。cmd是一个32位的数据,其中不同的位置代表了不同的段。


在内核当中,有对于生成ioctl命令相关的宏,可以很方便直接生成cmd:


#define _IOC(dir,type,nr,size) \


(((dir) [ _IOC_DIRSHIFT) | \


((type) [ _IOC_TYPESHIFT) | \


((nr) [ _IOC_NRSHIFT) | \


((size) [ _IOC_SIZESHIFT))


#define _IOC_NRSHIFT 0


#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS)


#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS)


#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS)


#define _IOC_NRBITS 8


#define _IOC_TYPEBITS 8


# define _IOC_SIZEBITS 14


1.dir(direction), ioctl命令的访问模式。定义数据的传输方向。可以为 _IOC_NONE 、 _IOC_READ、 _IOC_WRITE、 //代码效果参考:http://www.jhylw.com.cn/590730185.html

_IOC_READ|_IOC_WRITE。分别代表了无数据、读数据、写数据、读写数据。读写是从用户空间来看的,写是往驱动写,读是往用户读。

2.type(device type),设备类型,有些地方叫做 幻数 。可以为任意的char类型的字符。比如说设置为 ’a‘ , ‘b’ , ’ D’等等。主要作用是使 ioctl 命令有唯一的设备标识;


3. nr(number),命令编号/序数,占据 8 bit,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增;


4. size涉及到 ioctl 函数 第三个参数 arg ,占据 13bit 或者 14bit(体系相关,arm 架构一般为 14 位),指定了 arg 的数据类型及长度,如果在驱动的 ioctl 实现中不检查,通常可以忽略该参数;


为了更加方便的使用,在上述宏定义的基础上又衍生出了更加方便的ioctl命令:


#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),(//代码效果参考:http://www.jhylw.com.cn/010821485.html

_IOC_TYPECHECK(size)))

#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))


1


2


3


4


_IO: 定义不带参数的 ioctl 命令


_IOW: 定义带写参数的 ioctl 命令(copy_from_user)


_IOR: 定义带读参数的ioctl命令(copy_to_user)


_IOWR: 定义带读写参数的 ioctl 命令


在内核驱动代码中,还提供了分离出不同的段的宏定义接口,供内核的驱动代码使用。


#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)


1


2


3


4


比如:


if( _IOC_TYPE(cmd) != ‘c’) 判断是否设备种类正确


if( _IOC_NR(cmd) > 2) 判断命令的数值是不是大于2


if( _IOC_DIR(cmd) & _IOC_READ) 判断该命令是否为可读命令


4.ioctl例子分析


这个例子用ioctl来实现内核空间和用户空间的数据交互。


1.定义用户程序和驱动程序共同定义的协商文件ioctl_menu.h。


#ifndef IOCTLMENU


#define IOCTLMENU


#include // 内核空间


/定义设备类型/


#define IOC_MAGIC 'c'


/ 初始化设备/


#define IOCINIT _IO(IOC_MAGIC, 0)


/ 写寄存器 /


#define IOCWREG _IOW(IOC_MAGIC, 1, int)


/ 读寄存器 /


#define IOCRREG _IOR(IOC_MAGIC, 2, int)


#define IOC_MAXNR 2


#endif


该文件中定义了三个操作方式: 初始化、读出数据、写入数据。


2.设备驱动程序代码


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include


#include "ioctl_menu.h"


struct ioctl_struct{


dev_t devid; / 设备号/


int major; / 主设备号/


int minor; / 次设备号 /


struct cdev cdev; / cdev /


struct class class; //


struct device device; / 设备 /


int data; /设备的一个储存空间/


};


struct ioctl_struct mytest;


long test_ioctl(struct file file, unsigned int cmd, unsigned long arg)


{


int ret;


//如果设备种类不正确


if(_IOC_TYPE(cmd) != IOC_MAGIC)


{


pr_err("【%s】 command type 【%c】 error!\n", func, _IOC_TYPE(cmd));


return -1;


}


//检查序号是否超限


if(_IOC_NR(cmd) > IOC_MAXNR )


{


pr_err("【%s】 command number 【%d】 exceeded!\n", func, _IOC_NR(cmd));


return -1;


}


//检查访问模式


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 -1;


switch(cmd)


{


//初始化


case IOCINIT:


printk(KERN_ALERT "hello ,i am a register!\n");


break;


//写数据


case IOCWREG:


ret = copy_from_user(&mytest.data, (int user )arg, sizeof(int));


break;


//读数据


case IOCRREG:


ret = copy_to_user((int __user )arg, &mytest.data, sizeof(int));


break;


default: break;


}


return 0;


}


static struct file_operations mytest_fops =


{


.owner = THIS_MODULE,


.unlocked_ioctl = test_ioctl,


};


static int init ioctl_init(void)


{


//1.构建设备号


if(mytest.major)


{


mytest.devid = MKDEV(mytest.major, 0);


register_chrdev_region(mytest.devid , 1, "mytest");


}


else


{


alloc_chrdev_region(&mytest.devid, 0, 1, "mytest");


mytest.major = MAJOR(mytest.devid);


mytest.minor = MINOR(mytest.devid);


}


//2.注册字符设备


cdev_init(&mytest.cdev , &mytest_fops);


cdev_add(&mytest.cdev , mytest.devid, 1);


//3.创建类


mytest.class = class_create(THIS_MODULE, "mytest");


//4.创建设备文件


mytest.device = device_create(mytest.class, NULL, mytest.devid, NULL, "mytest");


return 0;


}


static void exit ioctl_exit(void)


{


printk(KERN_ALERT "ioctl Goodbye!\n");


cdev_del(&mytest.cdev);


unregister_chrdev_region(mytest.devid, 1);


device_destroy(mytest.class, mytest.devid);


class_destroy(mytest.class);


}


module_init(ioctl_init);


module_exit(ioctl_exit);


MODULE_LICENSE("GPL");


MODULE_AUTHOR("linyuwang");


其中的access_ok()函数原型如下,用来判断用户空间的地址是否可读或者可以写。


int access_ok (int type, const void addr, unsigned long size);


1


3.用户测试代码


#include


#include


#include


#include


#include


#include


#include


#include


#include "ioctl_menu.h"


#define READ 0


#define WRITE 1


int main(int argc , char argv【】)


{


int fd;


int method;


int data;


int ret;


//判断用法是否正确


if(argc < 3)


{


printf("error usage!\r\n");


return -1;


}


//打开设备文件


fd = open(argv【1】, O_RDWR);


if(fd < 0)


{


printf("file %s open failed!\r\n", argv【1】);


return -2;


}


//判断是读还是写


if(memcmp(argv【2】, "read", sizeof("read")) == 0)


{


method = READ;


}


else if(memcmp(argv【2】, "write", sizeof("write")) == 0)


{


method = WRITE;


}


else


{


printf("methord error!");


return -3;


}


//完成对应的操作


if(method == READ)


{


ret = ioctl(fd, IOCRREG, &data);


if(ret == 0)


{


printf("%d\r\n", data);


}


else


{


printf("Read failed!");


return -4;


}


}


else if(method == WRITE)


{


data = atoi(argv【3】);


ret = ioctl(fd, IOCWREG, &data);


if(ret == 0)


{


printf("Success!");


}


else


{


printf("Write failed!");


return -5;


}


}


return 0;


}


————————————————


版权声明:本文为CSDN博主「一人一城506」的原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接及本声明。


原文链接:

相关文章
|
JavaScript Linux 网络安全
Termux安卓终端美化与开发实战:从下载到插件优化,小白也能玩转Linux
Termux是一款安卓平台上的开源终端模拟器,支持apt包管理、SSH连接及Python/Node.js/C++开发环境搭建,被誉为“手机上的Linux系统”。其特点包括零ROOT权限、跨平台开发和强大扩展性。本文详细介绍其安装准备、基础与高级环境配置、必备插件推荐、常见问题解决方法以及延伸学习资源,帮助用户充分利用Termux进行开发与学习。适用于Android 7+设备,原创内容转载请注明来源。
4617 77
|
Ubuntu 搜索推荐 Linux
详解Ubuntu的strings与grep命令:Linux开发的实用工具。
这就是Ubuntu中的strings和grep命令,透明且强大。我希望你喜欢这个神奇的世界,并能在你的Linux开发旅程上,通过它们找到你的方向。记住,你的电脑是你的舞台,在上面你可以做任何你想做的事,只要你敢于尝试。
529 32
|
存储 Linux
linux中的目录操作函数
本文详细介绍了Linux系统编程中常用的目录操作函数,包括创建目录、删除目录、读取目录内容、遍历目录树以及获取和修改目录属性。这些函数是进行文件系统操作的基础,通过示例代码展示了其具体用法。希望本文能帮助您更好地理解和应用这些目录操作函数,提高系统编程的效率和能力。
473 26
|
Linux
【Linux】System V信号量详解以及semget()、semctl()和semop()函数讲解
System V信号量的概念及其在Linux中的使用,包括 `semget()`、`semctl()`和 `semop()`函数的具体使用方法。通过实际代码示例,演示了如何创建、初始化和使用信号量进行进程间同步。掌握这些知识,可以有效解决多进程编程中的同步问题,提高程序的可靠性和稳定性。
1023 19
|
Linux Android开发 开发者
linux m、mm、mmm函数和make的区别
通过理解和合理使用这些命令,可以更高效地进行项目构建和管理,特别是在复杂的 Android 开发环境中。
993 18
|
存储 监控 Linux
嵌入式Linux系统编程 — 5.3 times、clock函数获取进程时间
在嵌入式Linux系统编程中,`times`和 `clock`函数是获取进程时间的两个重要工具。`times`函数提供了更详细的进程和子进程时间信息,而 `clock`函数则提供了更简单的处理器时间获取方法。根据具体需求选择合适的函数,可以更有效地进行性能分析和资源管理。通过本文的介绍,希望能帮助您更好地理解和使用这两个函数,提高嵌入式系统编程的效率和效果。
751 13
|
Linux API 开发工具
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
ijkplayer是由B站研发的移动端播放器,基于FFmpeg 3.4,支持Android和iOS。其源码托管于GitHub,截至2024年9月15日,获得了3.24万星标和0.81万分支,尽管已停止更新6年。本文档介绍了如何在Linux环境下编译ijkplayer的so库,以便在较新的开发环境中使用。首先需安装编译工具并调整/tmp分区大小,接着下载并安装Android SDK和NDK,最后下载ijkplayer源码并编译。详细步骤包括环境准备、工具安装及库编译等。更多FFmpeg开发知识可参考相关书籍。
869 0
FFmpeg开发笔记(五十九)Linux编译ijkplayer的Android平台so库
|
监控 Linux Shell
这年头linux竟然成了程序员标配(下)
这年头linux竟然成了程序员标配(下)
这年头linux竟然成了程序员标配(下)
|
运维 前端开发 Linux
这年头linux竟然成了程序员标配(上)
这年头linux竟然成了程序员标配(上)
这年头linux竟然成了程序员标配(上)
|
8月前
|
Linux 应用服务中间件 Shell
二、Linux文本处理与文件操作核心命令
熟悉了Linux的基本“行走”后,就该拿起真正的“工具”干活了。用grep这个“放大镜”在文件里搜索内容,用find这个“探测器”在系统中寻找文件,再用tar把东西打包带走。最关键的是要学会使用管道符|,它像一条流水线,能把这些命令串联起来,让简单工具组合出强大的功能,比如 ps -ef | grep 'nginx' 就能快速找出nginx进程。
966 1
二、Linux文本处理与文件操作核心命令