一般来讲ioctl在用户程序中的调用是:ioctl(int fd,int command, (char*)argstruct)。ioctl调用与网络编程有关(本文只讨论这一点),文件描述符fd实际上是由socket()系统调用返回的。参数command的取值由/usr/include/linux/sockios.h所规定。这些command的由于功能的不同,可分为以下几个小类:
• 改变路由表 (例如 SIOCADDRT, SIOCDELRT),
• 读/更新 ARP/RARP 缓存(如:SIOCDARP, SIOCSRARP),
与网络接口有关的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部分。
• 改变路由表 (例如 SIOCADDRT, SIOCDELRT),
• 读/更新 ARP/RARP 缓存(如:SIOCDARP, SIOCSRARP),
• 一般的与网络接口有关的(例如 SIOCGIFNAME, SIOCSIFADDR 等等)
与网络接口有关的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部分。