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


原文链接:

相关文章
|
3天前
|
缓存 Linux 程序员
老程序员分享:linux下netlink的使用简介
老程序员分享:linux下netlink的使用简介
|
3天前
|
机器学习/深度学习 关系型数据库 Shell
老程序员分享:Linux之pushd、popd和dirs的使用讲解,比cd
老程序员分享:Linux之pushd、popd和dirs的使用讲解,比cd
|
2天前
|
开发工具 git Docker
老程序员分享:linux试题
老程序员分享:linux试题
|
3天前
|
网络协议 程序员 Linux
老程序员分享:linux基础
老程序员分享:linux基础
|
4天前
|
域名解析 网络协议 程序员
程序员必知:【转】adns解析库——域名解析实例(C++、linux)
程序员必知:【转】adns解析库——域名解析实例(C++、linux)
11 0
|
4天前
|
Linux 程序员 Perl
老程序员分享:Linux查看系统开机时间
老程序员分享:Linux查看系统开机时间
|
Linux
Linux系统调用二、open()函数与close()函数介绍
Linux系统调用二、open()函数与close()函数介绍
308 0
Linux系统调用二、open()函数与close()函数介绍
|
Linux C++ Unix
|
1天前
|
Linux 数据处理
探索Linux下的readelf命令:深入了解ELF文件
`readelf`是Linux下分析ELF文件的命令行工具,用于查看文件头、节区、符号表等信息。支持可执行文件、共享库等多种类型。常用选项有`-h`(文件头)、`-l`(程序头)、`-S`(节区)、`-s`(符号表)、`-r`(重定位)和`-d`(动态节区)。结合其他工具如`objdump`,能深入理解二进制文件,助力开发和调试。
|
2天前
|
IDE Linux 数据处理
探索Linux中的`pydoc`命令:Python文档生成器的力量
`pydoc`是Linux上Python的文档生成和查看工具,尤其对数据科学家有价值。它从docstring生成模块、函数和类的文档,提供快速API参考。主要特点包括易用性、支持标准库和第三方库、跨平台。命令行示例:`pydoc pandas` 查看库文档,`pydoc numpy.array` 查看类详情,`pydoc -k 关键字` 进行搜索。使用时注意正确安装Python,编写清晰的docstring,并结合IDE以提升效率。