1. linux设备及相关概念
在了解ioctl函数之前,熟悉下linux的一些概念。
设备:即指linux中的硬件设备,如打印机、鼠标...
设备文件:是出现在文件系统中的设备驱动程序的接口。 在类似Unix的操作系统中,它们通常位于/dev目录下,也称为设备节点。
设备驱动程序:是一块其操作或控制特定类型的设备的软件。在现代的单片内核操作系统上,这些通常是内核的一部分。
在Linux系统中,每个设备都有对应的驱动程序,用于与设备进行通信和控制。驱动程序通常由设备的厂商或社区开发,通过内核模块的形式加载到操作系统中。驱动程序提供了一组接口,即设备文件,供用户程序或其他内核模块调用,以实现对设备的控制和管理。
- 用户空间不能直接对内核进行操作,因此必须使用一个叫做 “系统调用”的方法 来实现从用户空间“陷入” 到内核空间,这样才能实现对底层驱动的操作。
- 系统调用的方法:glibc库函数和syscall函数。open、read、write、ioctl等函数都属于系统调用。
1.1 设备文件分类
- 字符设备:以串行顺序进行数据访问
常见的字符设备:鼠标、键盘(IO设备),LCD、Camera(帧缓冲设备)等
- 块设备:可以任意顺序进行访问,以块为单位进行操作。块设备经过系统的快速缓冲
常见的块设备:硬盘、光盘、SD卡、emmc、flash等存储设备
- 网络设备:网络设备面向数据包的接收和发送设计,它并不对应于文件系统的节点。
说明:
- 块设备以块为最小传输单位,不能按字节处理数据。而Linux则允许块设备传送任意数量的字节,因此块 & 字符设备的区别仅在于内核内部管理数据的方式不同,即内核和驱动之间的软件接口不同。
- 字符设备和块设备都被映射为Linux文件系统中的文件,可以通过文件系统的系统调用接口(open / close / read / write)访问。
1.2. 访问设备文件
1.2.1 字符设备和块设备
在用户空间的/dev目录下,都有一个对应的设备文件(一切皆文件),应用程序通过系统函数调用open、read、write等来操作设备文件从而访问设备驱动来操作硬件设备。
在终端输入ls /dev -l 可以看到设备文件:
字符设备文件-例:鼠标:
crw-r----- 1 root root 13, 33 mouse1
块设备文件-例:硬盘:
brw-rw---- 1 root disk 8, 1 sda1
首字符c表示char,字符设备,首字符d表示block ,块设备。
13和8表示这两个设备的主设备号,用于标识设备的类型;
33和1表示这两个设备的次设备号,用于标识同类设备的不同设备个体
应用程序访问设备流程是,根据用户空间的设备文件,找到对应的设备号,根据设备号去内核找到对应的设备驱动,然后通过设备驱动操作硬件设备。
1.2.2 网络设备
网络设备没有对应设备节点和设备号,网络设备使用套接字来实现网络数据的接收和发送。
最常见的网络设备:网卡。
4. ioctl函数
ioctl 是设备驱动程序中设备控制接口函数。一个字符设备驱动通常会实现设备打开、关闭、读、写等功能,在一些需要细分的情境下,如果需要扩展新的功能,通常以增设 ioctl() 命令的方式实现。
如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
原型:
#include <sys/ioctl.h>
int ioctl(int fd, unsigned long cmd, ...);
- fd:文件描述符,是通过open打开设备返回的值。
- cmd:交互协议,设备驱动将根据 cmd 执行对应操作
- ...:可变参数 arg,依赖 cmd 指定长度以及类型
4.1. cmd参数
命令码(cmd
)是唯一联系用户程序命令和驱动程序支持的途径。cmd的大小为 32位,共分 4 个域:
dir(读/写) | size(数据大小) | type(幻数) | nr(序列号) |
2bit | 14bit | 8bit | 8bit |
- dir:表示读或写:
(1)00 - 命令不带参数。_IOC_NONE
(2)01 - 命令需要把数据写入驱动,写方向。_IOC_WRITE
(3)10 - 命令需要从驱动中获取数据,读方向。_IOC_READ
(4)11 - 命令既要写入数据又要获取数据,读写方向。_IOC_READ | _IOC_WRITE
- size:如果命令带参数,则指定参数所占用的内存空间大小
- type:每个驱动全局唯一的幻数(魔数),用英文字符 "A" ~ "Z" 或者 "a" ~ "z" 来表示,主要作用是使 ioctl 命令有唯一的设备标识;(一般魔数定义在驱动程序的头文件中)
- nr:命令编号/序数,是区分命令的命令顺序序号,可以为任意 unsigned char 型数据,取值范围 0~255,如果定义了多个 ioctl 命令,通常从 0 开始编号递增。
在内核include/uapi/asm-generic/ioctl.h
头文件中提供了一组宏来定义、提取命令中的字符信息:
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
#ifndef __KERNEL__
#define _IOC_TYPECHECK(t) (sizeof(t))
#endif
/* 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)))
#define _IOR_BAD(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
#define _IOW_BAD(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define _IOWR_BAD(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/* 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)
构造ioctl命令:
_IO(type,nr):用于构造无参数的命令号
_IOR(type,nr,datetype):用于构造从驱动程序中读取数据的命令号
_IOW(type,nr,datatype):用于构造向驱动程序写入数据的命令号
_IORW(type,nr,datatype):用于构造双向传输的命令号
ioctl的使用可看看这里 :SPI通信接口(c/c++)