blogernice

导航

深入理解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,就知道用户程序的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控制都是通过这一部分的代码实现的。
再给你列一个ioctl支持的request的集合,内容相当的多。

 

大 部分驱动需要 -- 除了读写设备的能力 -- 通过设备驱动进行各种硬件控制的能力. 大部分设备可进行超出简单的数据传输之外的操作; 用户空间必须常常能够请求, 例如, 设备锁上它的门, 弹出它的介质, 报告错误信息, 改变波特率, 或者自我销毁. 这些操作常常通过 ioctl 方法来支持, 它通过相同名子的系统调用来实现.

在用户空间, ioctl 系统调用有下面的原型:

int ioctl(int fd, unsigned long cmd, ...);

ioctl 驱动方法有和用户空间版本不同的原型:

int (*ioctl) (struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg);

inode 和 filp 指针是对应应用程序传递的文件描述符 fd 的值, 和传递给 open 方法的相同参数.

cmd 参数从用户那里不改变地传下来, 并且可选的参数 arg 参数以一个 unsigned long 的形式传递, 不管它是否由用户给定为一个整数或一个指针.

 

实现ioctl有两步

第一步:选择 ioctl 命令

为 了防止向错误的设备使用正确的命令,命令号应该在系统范围内唯一。为方便程序员创建唯一的 ioctl 命令代号, 每个命令号被划分为多个位字段。要按 Linux 内核的约定方法为驱动选择 ioctl 的命令号, 应该首先看看 include/asm/ioctl.h 和 Documentation/ioctl-number.txt。 要使用的位字段符号定义在 <linux/ioctl.h> :

type(幻数):8 位宽(_IOC_TYPEBITS),参考ioctl-number.txt选择一个数,并在整个驱动中使用它。

number(序数):顺序编号,8 位宽(_IOC_NRBITS)。

direction(数据传送的方向):可 能的值是 _IOC_NONE(没有数据传输)、_IOC_READ、 _IOC_WRITE和 _IOC_READ|_IOC_WRITE (双向传输数据)。该字段是一个位掩码(两位), 因此可使用 AND 操作来抽取_IOC_READ 和 _IOC_WRITE。

size(数据的大小):宽度与体系结构有关,ARM为14位.可在宏 _IOC_SIZEBITS 中找到特定体系的值. 

 <linux/ioctl.h> 中包含的 <asm/ioctl.h>定义了一些构造命令编号的宏:

_IO(type,nr)/*没有参数的命令*/
_IOR(type, nr, datatype)/*从驱动中读数据*/
_IOW(type,nr,datatype)/*写数据*/
_IOWR(type,nr,datatype)/*双向传送*/
/*type 和 number 成员作为参数被传递, 并且 size 成员通过应用 sizeof 到 datatype 参数而得到*/

这个头文件还定义了用来解开这个字段的宏:

_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)

这个一般在头文件中设定。如书上#define SCULL_IOC_MAGIC 'k'

#define SCULL_IOCRESET _IO(SCULL_IOC_MAGIC, 0)
#define SCULL_IOCSQUANTUM _IOW(SCULL_IOC_MAGIC, 1, int)
#define SCULL_IOCSQSET _IOW(SCULL_IOC_MAGIC, 2, int)
#define SCULL_IOC_MAXNR 3

第2部,设置返回值、使用 ioctl 参数、ioctl 命令的实现

(1)返回值首先要检测命令的有效性。

if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC)
        return -EINVAL;
    if (_IOC_NR(cmd) > SCULL_IOC_MAXNR)
        return -EINVAL;

(2)使用 ioctl 参数

 

在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小心。当用一个指针引用用户空间, 我们必须确保用户地址是有效的,其校验(不传送数据)由函数 access_ok 实现,定义在 <asm/uaccess.h> :

int access_ok(int type, const void *addr, unsigned long size);

第 一个参数应当是 VERIFY_READ(读)或VERIFY_WRITE(读写);addr 参数为用户空间地址,size 为字节数,可使用sizeof()。access_ok 返回一个布尔值: 1 是成功(存取没问题)和 0 是失败(存取有问题)。如果它返回假,驱动应当返回 -EFAULT 给调用者。

 

注意:首先, access_ok不做校验内存存取的完整工作; 它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用 access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回 0, 并且在错误时返回 -EFAULT.。

put_user(datum, ptr)
__put_user(datum, ptr)
get_user(local, ptr)
__get_user(local, ptr)

这 些宏它们相对copy_to_user 和copy_from_user快, 并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址. 传送的数据大小依赖 prt 参数的类型, 并且在编译时使用 sizeof 和 typeof 等编译器内建宏确定。他 们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested". 在这些情况中,必须使用 copy_to_user 或者 copy_from_user。

__put_user和__get_user 进行更少的检查(不调用 access_ok), 但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用 access_ok 检查过的时候。作为通用的规则:当实现一个 read 方法时,调用 __put_user 来节省几个周期, 或者当你拷贝几个项时,因此,在第一次数据传送之前调用 access_ok 一次。

 

一般是以下模式

if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
    if (err)
        return -EFAULT;

 

(3)ioctl 命令的实现

ioctl的实现采用switch语句的形式实现,具体的就要看书了。呵呵  在《Linux设备驱动程序》的147页。

ioctl函数

 

本函数影响由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成员也被更新,以反映存放在缓冲区中的信息量。

接口操作:

SIOCGIFCONF 请求为每个已配置的接口返回其名字以及一个套接口地址结构。我们接着可以发出多个接口类其他请求以设置或获取每个接口的其他特征。这些请求的获取 (get)版本(SIOCGxxx)通常由netstat程序发出,设置(set)版本(SIGOCSxxx)通常由ifconfig程序发出。任何用户 都可以获取接口信息,设置接口信息却要求有超级用户权限。

 

这些请求汲取或返回一个一个ifreq结构中的信息,而这个结构的地址则作为ioctl调用的第三个参数制定。接口总是以其名标志,在ifreq结构的ifr_name成员中指定,如le0,lo0,ppp0等。

这些请求中有许多使用套接口地址结构在应用进程和内核之间指定或返回具体接口的IP地址或地址掩码。对于IPV4,这个地址或掩码放在一个网际套接口地址结构的sin_addr成员中;对于IPV6,它是一个IPV6套接口地址结构的sin6_addr成员。

 

SIOCGIFADDR:  在ifr_addr成员中返回单播地址。

SIOCSIFADDR:用ifr_addr成员设置接口地址,这个接口的初始化函数也被调用。

 

SIOCGIFFLAGS: 在ifr_flags成员中返回接口标志。这些接口标志的名字格式为IFF_XXX,在<net/if.h>头文件中定义。举例来说,这些标 志指示接口是否处于UP即在工状态(IFF_UP),是否为一个点到点接口(IFF_POINTOPOINT),是否支持广播 (IFF_BROADCAST),等等。

SIOCSIFFLAGS:用ifr_flags成员设置接口标志。

SIOCGIFDSTADDR:在ifr_dstaddr成员中返回点到点地址。

SIOCSIFDSTADDR:  在ifr_dstaddr成员中设置点到点地址

SIOCGIFBRDADDR:  在ifr_broadaddr成员中返回广播地址。应用进程必须首先获取接口标志,然后发出正确的请求;对于广播接口为SIOCGIFBRDADDR,对于点到点接口为SIOCGIFDSTADDR

SIOCSIFBRDADDR:用ifr_broadaddr成员设置广播地址。

SIOCGIFNETMASK:在ifr_addr成员中返回子网掩码。

SIOCSIFNETMASK:在ifr_addr成员中设置子网掩码。

SIOCGIFMETRIC:用ifr_metric成员返回接口测度。接口测度由内核为每个接口维护,不过使用他的是路由守护进程routed。接口测度被routed加到跳数上。

SIOCSIFMETRIC:用ifr_metric成员设置接口的路由测度。

 

 

 

ARP高速缓存操作

ARP告诉缓存也通过ioctl函数操纵。使用路由域套接口的系统往往改用路由套接口访问

ARP高速缓存。这些请求使用如下的arpreq结构,定义在<net/if_arp.h>

 

struct arpreq {

    struct sockaddr      arp_pa;

    struct sockaddr      arp_ha;

    int                  arp_flags;

};

 

#define ATF_INUSE      0x01   //entry in use

#define ATF_COM         0x02   //completed entry (hardware addr valid)

#define ATF_PERM       0x04   // permanent entry

#define ATF_PUBL 0x08   // published entry (respond for other host )

 

Ioctl的第三个必须指向某个arpreq结构,操纵ARP高速缓存的ioctl请求有以下三个:

 

SIOCSARP:           把一个新的表项加入ARP告诉缓存中区,或者修改其中已经存在的一个表项,其中arp_pa是一个含有IP地址的网际套接口地址结构,arp_ha则是一 个通用套接口地址结构,他的sa_family值为AF_unspec,sa_data中含有硬件地址(例如6直接的以太网地址)。ATF_PERM和 ATF_PUBL这两个标志也可以由应用进程指定。另外两个标志(ATF_INUSE和ATF_COM)则由内核设置。

SIOCDARP:        从ARP告诉缓存中删除一个表项。调用者指定要删除表项的网际地址。

SIOCGARP:        从ARP高速缓存中获取一个表项。调用者指定网际地址,相应的硬件地址(例外以太网地址)随标志一起返回。

 

只有超级用户才能增加或删除表项。这三个请求通常由arp程序发出。

注意ioctl没有办法列出ARP高速缓存中的所有表项。当指定-a标志执行arp命令时,大多

数版本的arp程序通过读取内核的内存( /dev/kmem )获得ARP高速缓存的当前内容。

 

路由表操作 

有些系统提供2个用于操纵路由表的ioctl请求。这2个请求要求ioctl的第三个参数是指向

某个rtentry结构的一个指针,该结构定义在<net/route.h>头文件中。这些请求通常由route

程序发出。只有超级用户才能发出这些请求。

SIOCADDRT:往路由表中增加一个表项

SIOCDELRT:从路由表中删除一个表项

Ioctl没有办法列出路由表中的所有表项。这个操作通常由netstat程序在指定-r标志自行四

完成。netstat程序通过读取内核的内存  (/dev/kmem)获得整个路由表。用sysctl同样可

以做到。

 

 

如下是chinaunix论坛飞灰橙给出的一个例子。

 

#include <stdio.h>

#include <stdlib.h>

#include <string.h>

#include <net/if.h>

#include <net/if_arp.h>

#include <sys/ioctl.h>

#include <netinet/in.h>

void err_sys(const char *errmsg);

int main(void)

{               
        int i, sockfd;
        struct ifreq ifr;
        struct arpreq arpr;
      
        strncpy(ifr.ifr_name, "eth0", sizeof(ifr.ifr_name));
      
        if ((sockfd = socket(PF_INET, SOCK_STREAM, 0)) == -1)
                err_sys("socket");      
      
        /* get ip address */
        if (ioctl(sockfd, SIOCGIFADDR, &ifr) == -1)      
                err_sys("1-ioctl");
      
        /* get hardware address */
                ifr.ifr_hwaddr.sa_family = ARPHRD_ETHER;
        if (ioctl(sockfd, SIOCGIFHWADDR, &ifr) == -1)
                err_sys("2-ioctl");
      
        /* output hardware address */
        for (i = 0; i < 6; i++) {
                        unsigned char *mac = (unsigned char *) ifr.ifr_hwaddr.sa_data;
                printf("%x", (int) mac[i]);
                if (i != 5)
                        printf("%c", ':');
        }
        exit(0);
}

 


IOCTL向内核传递参数

一、应用层

uint16 data16;

if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

{

printf("socket failed/n/r");

}

if(ioctl(fd, SIOCSIFVLAN_PVID_PRI, &data16) < 0)

{

printf("ioctl pvid failed/n/r");

}

二、linux内核

1、 在sockios.h中定义

#define SIOCSIFVLAN_PVID_PRI 0x8985 /* Set 802.1Q VLAN pvid */

2、在af_inet.c中

添加

extern int VLAN1QEN(unsigned int ,void *arg);

在inet_ioctl()函数中添加

case SIOCSIFVLAN_PVID_PRI:

return VLAN1QEN(cmd, arg);

3、另外定义:

static unsigned int VLAN_PVID_PRI = 0;

int VLAN1QEN(unsigned int cmd,void *arg)

{

unsigned int data;

if (copy_from_user(&data, arg, sizeof(int)))

return -EFAULT;

switch (cmd) {

case SIOCSIFVLAN_PVID_PRI:

VLAN_PVID_PRI = data;

break;

default:

return -EINVAL;

}

}

//end

在copy_from_user()和copy_to_user()的过程中,使用了arg这个参数指向用户空间某命令参数,在用户空间使用ioctl(int fd,int cmd, *bcf)想内核空间传递这个参数

这里,找不到bcf和arg的联系。

这里我把我的理解写在这里:

首先,ioctl的功能我就不说了,网上比较多,大致是用来控制IO的。

其次,第一个参数fd是一个文件描述符,我们这里是建立的一个套接字描述符

再次,第二个参数,是在sockios.h中定义的一个32位描述符,我们也可以在这里添加新的类型,用来扩展实际需求。

最后,第三个参数,是一个指针,用来指向某些我们实际应用中的参数。

在af_inet.c中的inet_ioctl()函数中的switch——case中,对上面第二个参数分别进行了描述。

例如需要的是这样的一个类型:

ioctl( fd, SIOCSIFBR, &bcf )

在inet_ioctl()函数中如下表示: Linux/net/ipv4/af_inet.c

int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)

switch (cmd)

{……

case SIOCGIFBR:
case SIOCSIFBR:
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
#ifdef CONFIG_KMOD
if (br_ioctl_hook == NULL)
request_module("bridge");
#endif
if (br_ioctl_hook != NULL)
return br_ioctl_hook(arg);
879 #endif
return -ENOPKG;
……

}//switch end here

这里inet_ioctl()中的第三个参数arg,正好对应在ioctl中的的三个参数&bcf,其实,inet_ioctl属于ioctl在linux内核中的网络设备ioctl的底层函数,用来处理网络设备的一些IO操作。

上面的一小段程序显示,我们用户层ioctl函数中的第三个参数对应在br_ioctl_hook(arg)中的arg上。

而 在我们的另外一个函数中定义了一个函数叫br_ioctl(arg)函数,并且其中使用了copy_to_user 和copy_from_user 用来将用户层中ioctl函数传下来的 命令在内核中进行相应的处理。最后,用一句:br_ioctl_hook = br_ioctl 将这个函数送进inet_ioctl中。

这里才明白ioctl —— br_ioctl之间的关系

posted on 2018-10-30 16:31  blogernice  阅读(1918)  评论(0编辑  收藏  举报