ioctl函数详细说明

本函数影响由fd 参数引用的一个打开的文件。node

#include<unistd.h>linux

int ioctl( int fd, int request, .../* void *arg */ );ios

返回0 :成功    -1 :出错编程

 

第三个参数老是一个指针,但指针的类型依赖于request 参数。缓存

咱们能够把和网络相关的请求划分为6 类:网络

套接口操做数据结构

文件操做app

接口操做异步

ARP 高速缓存操做socket

路由表操做

流系统

下表列出了网络相关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与内核交换数据

1. 前言
 
使用ioctl系统调用是用户空间向内核交换数据的经常使用方法之一,从ioctl这个名称上看,本意是针对I/O设备进行的控制操做,但实际并不限制是真正的I/O设备,能够是任何一个内核设备便可。

2. 基本过程
 
在内核空间中ioctl是不少内核操做结构的一个成员函数,如文件操做结构struct file_operations(include/linux/fs.h)、协议操做结构struct proto_ops(include/linux/net.h)等、tty操做结构struct tty_driver(include/linux/tty_driver.h)等,而这些操做结构分别对应各类内核设备,只要在用户空间打开这些设备, 如I/O设备可用open(2)打开,网络协议可用socket(2)打开等,获取一个文件描述符后,就能够在这个描述符上调用ioctl(2)来向内核 交换数据。
 
3. ioctl(2)
 
ioctl(2)函数的基本使用格式为:
int ioctl(int fd, int cmd, void *data)
第一个参数是文件描述符;cmd是操做命令,通常分为GET、SET以及其余类型命令,GET是用户空间进程从内核读数据,SET是用户空间进程向内核写数据,cmd虽然是一个整数,可是有必定的参数格式的,下面再详细说明;第三个参数是数据起始位置指针,
cmd命令参数是个32位整数,分为四部分:
dir(2b)  size(14b)  type(8b) nr(8b)
详细定义cmd要包括这4个部分时可以使用宏_IOC(dir,type,nr,size)来定义,而最简单状况下使用_IO(type, nr)来定义就能够了,这些宏都在include/asm/ioctl.h中定义
本文cmd定义为:
#define NEWCHAR_IOC_MAGIC   'M'
#define NEWCHAR_SET    _IO(NEWCHAR_IOC_MAGIC, 0)
#define NEWCHAR_GET    _IO(NEWCHAR_IOC_MAGIC, 1)
#define NEWCHAR_IOC_MAXNR   1

要定义本身的ioctl操做,能够有两个方式,一种是在现有的内核代码中直接添加相关代码进行支持,好比想经过socket描述符进行 ioctl操做,可在net/ipv4/af_inet.c中的inet_ioctl()函数中添加本身定义的命令和相关的处理函数,从新编译内核便可, 不过这种方法通常不推荐;第二种方法是定义本身的内核设备,经过设备的ioctl()来操做,能够编成模块,这样不影响原有的内核,这是最一般的作法。
 
4. 内核设备
 
为进行ioctl操做最一般是使用字符设备来进行,固然定义其余类型的设备也能够。在用户空间,可以使用mknod命令创建一个字符类型设备文件,假设该设备的主设备号为123,次设备号为0:
mknode /dev/newchar c 123 0
若是是编程的话,能够用mknode(2)函数来创建设备文件。
 
创建设备文件后再将该设备的内核模块文件插入内核,就可使用open(2)打开/dev/newchar文件,而后调用ioctl(2)来传递数据,最后用close(2)关闭设备。而若是内核中尚未插入该设备的模块,open(2)时就会失败。
 
因为内核内存空间和用户内存空间不一样,要将内核数据拷贝到用户空间,要使用专用拷贝函数copy_to_user();要将用户空间数据拷贝到内核,要使用copy_from_user()。
要最简单实现以上功能,内核模块只须要实现设备的open, ioctl和release三个函数便可,
下面介绍程序片段:
static int newchar_ioctl(struct inode *inode, struct file *filep, 
   unsigned int cmd, unsigned long arg);
static int newchar_open(struct inode *inode, struct file *filep);
static int newchar_release(struct inode *inode, struct file *filep);
// 定义文件操做结构,结构中其余元素为空
struct file_operations newchar_fops = 
{
 owner:  THIS_MODULE,
 ioctl:  newchar_ioctl,
 open:  newchar_open,
 release: newchar_release,
};
// 定义要传输的数据块结构
struct newchar{
 int a;
 int b;
};
#define MAJOR_DEV_NUM 123
#define DEVICE_NAME "newchar"
 
打开设备,很是简单,就是增长模块计数器,防止在打开设备的状况下删除模块,
固然想搞得复杂的话可进行各类限制检查,如只容许指定的用户打开等:
static int newchar_open(struct inode *inode, struct file *filep)
{
 MOD_INC_USE_COUNT;
 return 0;
}

关闭设备,也很简单,减模块计数器:
static int newchar_release(struct inode *inode, struct file *filep)
{
 MOD_DEC_USE_COUNT;
 return 0;
}

进行ioctl调用的基本处理函数
static int newchar_ioctl(struct inode *inode, struct file *filep, 
      unsigned int cmd, unsigned long arg)
{
 int  ret;
// 首先检查cmd是否合法
 if (_IOC_TYPE(cmd) != NEWCHAR_IOC_MAGIC) return -EINVAL;
 if (_IOC_NR(cmd) > NEWCHAR_IOC_MAXNR) return -EINVAL;
// 错误状况下的缺省返回值
 ret = EINVAL;
 switch(cmd)
 {
 case KNEWCHAR_SET:
// 设置操做,将数据从用户空间拷贝到内核空间
  {
   struct  newchar  nc;
   if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
   ret = do_set_newchar(&nc);
  }
  break;
 case KNEWCHAR_GET:
// GET操做一般会在数据缓冲区中先传递部分初始值做为数据查找条件,获取所有
// 数据后从新写回缓冲区
// 固然也能够根据具体状况什么也不传入直接向内核获取数据
  {
   struct  newchar  nc;
   if(copy_from_user(&nc, (const char*)arg, sizeof(nc)) != 0)
    return -EFAULT;
   ret = do_get_newchar(&nc);
   if(ret == 0){
    if(copy_to_user((unsigned char *)arg, &nc, sizeof(nc))!=0)
     return -EFAULT;
   }
  }
  break;
 }
 return ret;
}
模块初始化函数,登记字符设备
static int __init _init(void)
{
 int  result;
// 登记该字符设备,这是2.4之前的基本方法,到2.6后有了些变化,
// 是使用MKDEV和cdev_init()来进行,本文仍是按老方法
 result = register_chrdev(MAJOR_DEV_NUM, DEVICE_NAME, &newchar_fops);
 if (result < 0) {
  printk(KERN_WARNING __FUNCTION__ ": failed register character device for /dev/newchar/n");
  return result;
 }
 return 0;
}

模块退出函数,登出字符设备
static void __exit _cleanup(void)
{
 int  result;
 result = unregister_chrdev(MAJOR_DEV_NUM, DEVICE_NAME);
 if (result < 0)
  printk(__FUNCTION__ ": failed unregister character device for /dev/newchar/n");
 return;
}
module_init(_init);
module_exit(_cleanup);
 
5. 结论
 
用ioctl()在用户空间和内核空间传递数据是最经常使用方法之一,比较简单方便,并且能够在同一个ioctl中对不一样的命令传送不一样的数据结构,本文只是为描述方便而在不一样命令中使用了相同的数据结构。
相关文章
相关标签/搜索