老程序员分享: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版权协议,转载请附上原文出处链接及本声明。


原文链接:

相关文章
|
2天前
|
存储 网络协议 Ubuntu
【Linux开发实战指南】基于UDP协议的即时聊天室:快速构建登陆、聊天与退出功能
UDP 是一种无连接的、不可靠的传输层协议,位于IP协议之上。它提供了最基本的数据传输服务,不保证数据包的顺序、可靠到达或无重复。与TCP(传输控制协议)相比,UDP具有较低的传输延迟,因为省去了建立连接和确认接收等过程,适用于对实时性要求较高、但能容忍一定数据丢失的场景,如在线视频、语音通话、DNS查询等。 链表 链表是一种动态数据结构,用于存储一系列元素(节点),每个节点包含数据字段和指向下一个节点的引用(指针)。链表分为单向链表、双向链表和循环链表等类型。与数组相比,链表在插入和删除操作上更为高效,因为它不需要移动元素,只需修改节点间的指针即可。但访问链表中的元素不如数组直接,通常需要从
|
2天前
|
SQL 自然语言处理 网络协议
【Linux开发实战指南】基于TCP、进程数据结构与SQL数据库:构建在线云词典系统(含注册、登录、查询、历史记录管理功能及源码分享)
TCP(Transmission Control Protocol)连接是互联网上最常用的一种面向连接、可靠的、基于字节流的传输层通信协议。建立TCP连接需要经过著名的“三次握手”过程: 1. SYN(同步序列编号):客户端发送一个SYN包给服务器,并进入SYN_SEND状态,等待服务器确认。 2. SYN-ACK:服务器收到SYN包后,回应一个SYN-ACK(SYN+ACKnowledgment)包,告诉客户端其接收到了请求,并同意建立连接,此时服务器进入SYN_RECV状态。 3. ACK(确认字符):客户端收到服务器的SYN-ACK包后,发送一个ACK包给服务器,确认收到了服务器的确
|
9天前
|
Unix Shell Linux
技术笔记:linux中SIGHUP与nohup的关系
技术笔记:linux中SIGHUP与nohup的关系
|
2天前
|
运维 监控 大数据
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
部署-Linux01,后端开发,运维开发,大数据开发,测试开发,后端软件,大数据系统,运维监控,测试程序,网页服务都要在Linux中进行部署
|
6天前
|
NoSQL Linux 开发工具
【linux】在linux操作系统下快速熟悉开发环境并上手开发工具——体验不一样的开发之旅
【linux】在linux操作系统下快速熟悉开发环境并上手开发工具——体验不一样的开发之旅
|
9天前
|
开发工具 git Docker
老程序员分享:linux试题
老程序员分享:linux试题
15 0
|
9天前
|
Unix 关系型数据库 Linux
技术笔记:linux学习心得
技术笔记:linux学习心得
11 0
|
2天前
|
Linux 网络安全 开发工具
linux 常用命令【编程必备】
linux 常用命令【编程必备】
14 4
|
2天前
|
存储 Linux
Linux文件的上和下,FinalShell文件右键可下文件,先选择root文件夹,然后把他文件往里面拖动,就可以下载了,命令下载,ls -l可以看当前文件目录,sz 文件名可下载,tab补,rz出上
Linux文件的上和下,FinalShell文件右键可下文件,先选择root文件夹,然后把他文件往里面拖动,就可以下载了,命令下载,ls -l可以看当前文件目录,sz 文件名可下载,tab补,rz出上
|
2天前
|
安全 Linux 测试技术
Linux命令setpriv详解
`setpriv` 是Linux下的命令行工具,用于调整进程权限以增强安全性,尤其适用于自动化和非交互式权限切换。它不使用PAM,支持管理能力集、GID/UID及SELinux上下文。例如,`setpriv --reuid=1000 script.sh` 可以以低权限用户运行脚本,而`--selinux-label`可设定SELinux标签。在使用时,应最小化权限、充分测试、保持与其他安全机制的兼容性,并定期审核权限设置。