分类: LINUX
2009-07-24 15:09:05
ioctl函数
本函数影响由fd参数引用的一个打开的文件。 #include 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成员也被更新,以反映存放在缓冲区中的信息量 一般来讲ioctl在用户程序中的调用是: ioctl(int fd,int command, (char*)argstruct) ioctl调用与网络编程有关(本文只讨论这一点),文件描述符fd实际上是由socket()系统调用返回的。参数command的取值由/usr/include/linux/sockios.h所规定。这些command的由于功能的不同,可分为以下几个小类: • 改变路由表 (例如 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调用使用的command参数通常看起来像SIOCxIFyyyy的形式,这里x要么是S(设定set,写write),要么 是G(得到get,读read)。在getifinfo.c程序中就使用了这种形式的command参数来读IP地址,硬件地址,广播地址和得到与网络接 口有关的一些标志(flag)。在这些ioctl调用中,第三个参数是ifreq结构,它在/usr/include/linux/if.h中定义。在某 些情况下, ioctrl调用可能会使用到在sockios.h之外的新的定义,例如,WaveLAN无线网络卡会保存有关无线网络信号强度的信息,这对用户的程序可 能有用。但用户怎么得到这种信息呢?我们的第一个本能是在sockios.h中定义新的ioctl命令,例如SIOCGIFWVLNSS(它的英文缩写表 示WaveLAN的信号强度)。但不幸的是,这种命令不是对所有其他的网络接口(例如:loopback环回接口)有意义,而且不应当允许对于 WAVLAN卡以外的网络接口使用ioctl命令。那么,我们需要的是这样一种机制:它能够定义一种与网络接口相关的ioctl命令。幸运的是,在 Linux操作系统中已经为实现这个目的内建了一种挂钩(hook)机制。当你再次看sockios.h文件时,你将发现每一种设备已经预先定义了 SIOCDEVPRIVATE的ioctl命令。而它的实现将留给开发相应驱动程序的人去完成。 通常,一个用户程序使用ioctl(sockid,SIOCDEVPRIVATE,(char*)&ifr)来调用与某种设备(指像 WaveLAN那样的特殊设备)相关的ioctl命令,这里ifr是struct ifreq ifr形式的变量。用户程序应当在ifr.ifr_name中填充与这个设备相关的名字,例如,假设WaveLAN使用的接口号为eth1。一般的,一个 用户程序还需要与内核互相交换ioctl的command参数和结果,这可以通过ifr.ifr_data这个变量来实现,例如,想得到WaveLAN中 表示信号强度的信息时,可以通过返回这个变量来实现。Linux的源代码已经包括了两种设备de4x5和ewrk3,它们定义并且实现了特定的ioctl 调用。这两个设备的源代码在de4x5.h,de4x5.c,ewrk3.h,ewrk3.c中(在/usr/src/linux/drivers /net/目录中)。这两种设备都定义了它们特有的结构(struct ewrk3_ioctl 和 struct de4x5_ioctl)来方便用户程序和设备驱动之间交换信息。每次调用ioctl前,用户程序应当在相应的结构变量中设定合适的初值,并且将 ifr.ifr_data指向该值。 在我们进一步讨论ewrk3和de4x5的代码前,让我们仔细看看ioctl调用是如何一步步地实现的。所有的和接口相关的ioctl请求 (SIOCxIFyyyy 和 SIOCDEVPRIVATE)将会调用dev_ioctl()(在/usr/src/linux/net/core/dev.c中)。但这只是一个包装 器(wrapper),实际的动作将由dev_ifsioc()(也在dev.c中)来实现。差不多dev_ioctl()这个函数所做的所有工作只是检 查这个调用是否已经有了正当的权限(例如,改变路由表需要有root的权限)。而dev_ifsioc()这个函数首先要做的一些事情包括得到与 ifr.ifr_name相匹配的设备的结构(在/usr/include/linux/netdevice.h中定义)。但这是在实现特定的接口命令 (例如:SIOCGIFADDR)之后。这些特定的接口命令被放置到一个巨大的switch语句之中。其中SIOCDEVPRIVATE命令和其他的在 0x89F0到0x89FF之间的代码将出现在switch语句中的一个分支——default语句中。内核会检查表示设备的结构变量中,是否已经定义了 一个与设备相关的ioctl句柄(handler)。这里的句柄是一个函数指针,它在表示设备的结构变量中do_ioctl部分。如果已经设置了这个句 柄,那么内核将会执行它。 所以,如果要实现一个与设备相关的ioctl命令,所要做的只是编写一个与这个设备相关的ioctl句柄,并且将表示这个设备的结构变量中 do_ioctl部分指向这个句柄。对于ewrk3这个设备,它的句柄是ewrk3_ioctl()(在ewrk3.c里面)并且相应的表示该设备的结构 变量由ewrk3_init()来初始化。在ewrk3_ioctl()的代码中清晰的指出ifr.ifr_data是用作设备驱动程序和用户程序之间交 换信息的。注意,这部分的内存可以双向的交流信息。例如,在ewrk3的驱动程序代码中,if.ifr_data的头两个字节是用来表示特殊的动作(例 如,EWRK3_SET_PROM,EWRK3_CLR_PROM),而这个动作是符合使用者(驱动程序实现了多个与设备相关的、由 SIOCDEVPRIVATE调用的命令)的要求的。另外,ifr.ifr_data中第5个字节指向的缓冲区(buffer)被用来交换其他的信息 (如:当使用EWRK3_SET_HWADDR和EWRK3_GET_HWADDR时为硬件地址) 在你深入ewrk3_ioctl()时,请注意一般情况下一个用户进程不能直接访问内核所在的内存。为此,驱动开发者可以使用两个特殊的函数 memcpy_tofs()和memcpy_fromfs()。内核函数memcpy_tofs(arg1, arg2, arg3) 从地址arg2(用户空间)向地址arg1(内核空间)拷贝arg3个字节。类似的,memcpy_fromfs(arg1,arg2,arg3)从地址 arg2(用户空间)向地址arg1(内核空间)拷贝arg3个字节。在这些调用之前,verify_area()将会检查这个进程是否拥有合适的访问权 限。另外,注意使用printk()函数可以输出debug信息。这个函数与printf()函数类似,但不能处理浮点类型的数。内核代码不能够使用 printf()函数。printk()函数产生的结果将记录在/usr/adm/messages里。如果想知道更多的关于这些函数的或者与它们相关的 信息,可以参考《Linux Kernel Hacker’s Guide》(在Linux文档网站的首页) 这本书中Supporting Functions部分。
ioctl(转载)
2009-03-19 19:02
|
ioctl简介 2007-09-17 08:43
我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,
所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被ioctl给搞混
了,这几天才弄明白它,于是在这里清理一下头脑。
一、 什么是ioctl。
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,就知道用户程序的io
ctl是怎么和驱动程序中的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控制都是
通过这一部分的代码实现的。
参考资料:
1.《Linux设备驱动程序》,鲁宾尼著,中国电力出版社。
2.《write的奥秘》