本函数影响由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 成员也被更新,以反映存放在缓冲区中的信息量
在咱们进一步讨论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中对不一样的命令传送不一样的数据结构,本文只是为描述方便而在不一样命令中使用了相同的数据结构。
|