本函数影响由fd 参数引用的一个打开的文件。
#include<unistd.h>
int ioctl( int fd, int request, .../* void *arg */ );
返回0 :成功 -1 :出错
第三个参数总是一个指针,但指针的类型依赖于request 参数。
我们可以把和网络相关的请求划分为6 类:
套接口操作
文件操作
接口操作
ARP 高速缓存操作
路由表操作
流系统
下表列出了网络相关ioctl 请求的request 参数以及arg 地址必须指向的数据类型:
类别 |
Request |
说明 |
数据类型 |
套 接 口 |
SIOCATMARK SIOCSPGRP SIOCGPGRP |
是否位于带外标记 设置套接口的进程ID 或进程组ID 获取套接口的进程ID 或进程组ID |
int int int |
文
件
|
FIONBIN FIOASYNC FIONREAD FIOSETOWN FIOGETOWN
|
设置/ 清除非阻塞I/O 标志 设置/ 清除信号驱动异步I/O 标志 获取接收缓存区中的字节数 设置文件的进程ID 或进程组ID 获取文件的进程ID 或进程组ID |
int int int int int |
接 口
|
SIOCGIFCONF SIOCSIFADDR SIOCGIFADDR SIOCSIFFLAGS SIOCGIFFLAGS SIOCSIFDSTADDR SIOCGIFDSTADDR SIOCGIFBRDADDR SIOCSIFBRDADDR SIOCGIFNETMASK SIOCSIFNETMASK SIOCGIFMETRIC SIOCSIFMETRIC SIOCGIFMTU SIOCxxx |
获取所有接口的清单 设置接口地址 获取接口地址 设置接口标志 获取接口标志 设置点到点地址 获取点到点地址 获取广播地址 设置广播地址 获取子网掩码 设置子网掩码 获取接口的测度 设置接口的测度 获取接口MTU (还有很多取决于系统的实现) |
struct ifconf struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq struct ifreq |
ARP |
SIOCSARP SIOCGARP SIOCDARP |
创建/ 修改ARP 表项 获取ARP 表项 删除ARP 表项 |
struct arpreq struct arpreq struct arpreq |
路 由 |
SIOCADDRT SIOCDELRT |
增加路径 删除路径 |
struct rtentry struct rtentry |
流 |
I_xxx |
|
|
套接口操作:
明确用于套接口操作的ioctl 请求有三个, 它们都要求ioctl 的第三个参数是指向某个整数的一个指针。
SIOCATMARK: 如果本套接口的的度指针当前位于带外标记,那就通过由第三个参数指向的整数返回一个非0 值;否则返回一个0 值。POSIX 以函数sockatmark 替换本请求。
SIOCGPGRP : 通过第三个参数指向的整数返回本套接口的进程ID 或进程组ID ,该ID 指定针对本套接口的SIGIO 或SIGURG 信号的接收进程。本请求和fcntl 的F_GETOWN 命令等效,POSIX 标准化的是fcntl 函数。
SIOCSPGRP : 把本套接口的进程ID 或者进程组ID 设置成第三个参数指向的整数,该ID 指定针对本套接口的SIGIO 或SIGURG 信号的接收进程,本请求和fcntl 的F_SETOWN 命令等效,POSIX 标准化的是fcntl 操作。
文件操作:
以下5 个请求都要求ioctl 的第三个参数指向一个整数。
FIONBIO : 根据ioctl 的第三个参数指向一个0 或非0 值分别清除或设置本套接口的非阻塞标志。本请求和O_NONBLOCK 文件状态标志等效,而该标志通过fcntl 的F_SETFL 命令清除或设置。
FIOASYNC : 根据iocl 的第三个参数指向一个0 值或非0 值分别清除或设置针对本套接口的信号驱动异步I/O 标志,它决定是否收取针对本套接口的异步I/O 信号(SIGIO )。本请求和O_ASYNC 文件状态标志等效,而该标志可以通过fcntl 的F_SETFL 命令清除或设置。
FIONREAD : 通过由ioctl 的第三个参数指向的整数返回当前在本套接口接收缓冲区中的字节数。本特性同样适用于文件,管道和终端。
FIOSETOWN : 对于套接口和SIOCSPGRP 等效。
FIOGETOWN : 对于套接口和SIOCGPGRP 等效。
接口配置:
得到系统中所有接口由SIOCGIFCONF 请求完成,该请求使用ifconf 结构,ifconf 又使用ifreq
结构,如下所示:
Struct ifconf{
int ifc_len; // 缓冲区的大小
union{
caddr_t ifcu_buf; // input from user->kernel
struct ifreq *ifcu_req; // return of structures returned
}ifc_ifcu;
};
#define ifc_buf ifc_ifcu.ifcu_buf //buffer address
#define ifc_req ifc_ifcu.ifcu_req //array of structures returned
#define IFNAMSIZ 16
struct ifreq{
char ifr_name[IFNAMSIZ]; // interface name, e.g., “le0”
union{
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
short ifru_flags;
int ifru_metric;
caddr_t ifru_data;
}ifr_ifru;
};
#define ifr_addr ifr_ifru.ifru_addr // address
#define ifr_dstaddr ifr_ifru.ifru_dstaddr // otner end of p-to-p link
#define ifr_broadaddr ifr_ifru.ifru_broadaddr // broadcast address
#define ifr_flags ifr_ifru.ifru_flags // flags
#define ifr_metric ifr_ifru.ifru_metric // metric
#define ifr_data ifr_ifru.ifru_data // for use by interface
再调用ioctl 前我们必须先分撇一个缓冲区和一个ifconf 结构,然后才初始化后者。如下图
展示了一个ifconf 结构的初始化结构,其中缓冲区的大小为1024 ,ioctl 的第三个参数指向
这样一个ifconf 结构。
ifc_len |
Ifc_buf |
1024
---------------------> 缓存
假设内核返回2 个ifreq 结构,ioctl 返回时通过同一个ifconf 结构缓冲区填入了那2 个ifreq 结构,ifconf 结构的ifc_len 成员也被更新,以反映存放在缓冲区中的信息量
• 改变路由表 (例如 SIOCADDRT, SIOCDELRT),
• 读/更新 ARP/RARP 缓存(如:SIOCDARP, SIOCSRARP),
• 一般的与网络接口有关的(例如 SIOCGIFNAME, SIOCSIFADDR 等等)
在 Gooodies目录下有很多样例程序展示了如何使用ioctl。当你看这些程序时,注意参数argstruct是与参数command相关的。例如,与 路由表相关的ioctl使用rtentry这种结构,rtentry定义在/usr/include/linux/route.h(参见例子 adddefault.c)。与ARP有关的ioctl调用使用arpreq结构,arpreq定义在/usr/include/linux /if_arp.h(参见例子arpread.c)
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就
是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数
如下:
int ioctl(int fd, ind cmd, …);
其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设
备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和
cmd的意义相关的。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支
持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。
二、 ioctl的必要性
如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可
以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,
那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会
导致代码分工不明,程序结构混乱,程序员自己也会头昏眼花的。
所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告
诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要
做的事情。
三、 ioctl如何实现
这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这
里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感
兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知
道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。
我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚
了,但是得化一些时间来看。
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对
应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事
情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中
命令码是唯一联系用户程序命令和驱动程序支持的途径。
命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不
会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的
命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇
怪的事情的时候,再来调试程序查找错误,那将是非常困难的事情。
所以在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型 | 序列号 | 方向 |数据尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|--------|
这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以
Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从
命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数
据传送方向和数据传输尺寸。
这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给
除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。
幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一
个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。
更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读《Linux 设备驱动程序》所
带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细
节。
四、 cmd参数如何得出
这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、
数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解
码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过
switch{case}结构进行相应的操作。
要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。Cmd参数的组织
还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最
难的是对中断的理解。
五、 小结
ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成
并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的
I/O控制都是通过这一部分的代码实现的。