Linux内核的ioctl函数学习

from :http://bbs.chinaunix.net/forum.php?mod=viewthread&tid=1967512


ioctl 函数 参数 详解
2009-04-24 11:55
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部分。


关于linux内核的ioctl函数
trangod发布于 2007-4-07 |
4744次阅读 字号:





(网友评论 3 条)
我要评论

  一般的说,,用户空间的IOCTL系统调用如下所示: ioctl(int fd, int command, (char *) argstruct)因为这个调用拥有与网络相关的代码,所以文件描述符号fd就是socket()系统调用所返回的,而command参数可以是/usr/include/linux/sockios.h头文件中的任何一个,这些个命令根据它可以解决的问题所涉及的方面被分为多种的类型.
  比如:
  改变路由表(SIOCADDRT, SIOCDELRT)
  
  读取或更新ARP/RARP缓存(SIOCDARP, SIOCSRARP)
一般的和网络有关的函数(SIOCGIFNAME, SIOCSIFADDR等等)
  Goodies目录中包含了很多展示ioctl用法的示例程序,看这些程序的时候,注意根据ioctl的命令类型来学习具体的调用参数结构,比如:和路由表相关的IOCTL用RTENTRY结构, rtentry结构是被定义在/usr/include/linux/route.h文件中的,再一个和ARP相关的ioctl调用用到的arpreq结构被定义在/usr/include/linux/if_arp.h文件之中.网络接口相关的ioctl命令最具有代表性的特征为都是以S或G开头,其实就是设置或得到数据, getifinfo.c程序用这些命令去读取IP地址信息,硬件地址信息,广播地址信息,和与网络接口相关的标志.对于这些ioctl,第三个参数是一个IFREQ结构体,这个结构体被定义在/usr/include/linux/if.h头文件中,在一些情况下,新的ioctl命令可能被需要(除了在那个头文件中被定义的之外),比如 WAVELAN无线网卡保持着无线信号强度的信息,这些信西可能要 对用户程序有用.用户程序是怎么访问到这些信息的呢?我们的第一反应就是定义一个新的命令在sockios.h头文件中,比如SIOCGIFWVLNSS,不幸的是,这个命令在其他的网络接口上是根本没有意义的,另外试图在其他接口上用这个名另而并非是在无线网口上用会出现违规访问,我们需要的是定义新特性接口命令的机理。幸运的是,LINUX操作系统为此目的内置了钩子,如果你再看一下那个头文件sockios.h你会注意到每一个设备都有一个预定义的SIOCDEVPRIVATE命令,实现它的任务就全权交给了写这个设备驱动的程序员了.根据常规约定,一个用户程序调用一个特定的ioctl命令如下: ioctl(sockid, SIOCDEVPRIVATE, (char *) &ifr)这里ifr是一个ifreq结构体变量,它用一个和这个设备联系的接口名称填充ifr的ifr NAME域,比如,前述的无线网卡接口名称为eth1。
  不失一般性,一个用户程序将同样要与内核交换命令参数和操作结果,而这些已经通过一个域ifr.ifr_data的填充而做到了,比如,这个网卡的信号强度信息被返回到这个域当中。LINUX源代码已经包含了两个特殊设备de4x5和ewrk3,他们定义和实现了特殊的ioctl命令.,这些设备的源代码在以下的文件中:de4x5.h, de4x5.c, ewrk3.h, ewrk3.c, 他们两个设备都为在用户空间和驱动间交换数据定义了他们自己的私有结构,在ioctl之前,用户程序填充了需要的数据并且将ifr.ifr_data指向这个结构体.
  我们在两个驱动中走的更远些从而进入代码前,让我们跟踪一下处理ioctl系统调用的若干步骤,,所有接口类型的ioctl请求都导致dev_ioctl()被调用,这个ioctl仅仅是个包装,大部分的真实的操作留给了dev_ifsioc().,这个dev_ioctl()要做的唯一一个事情就是检查调用过程是否拥有合适的许可去核发这个命令,然后dev_ifsioc()首先要做的事情之一就是得到和名字域ifr.ifr_name中所对应的设备结构,这在一个很大的switch语块的代码后实现。
  SIOCDEVPRIVATE命令和SIOCDEVPRIVATE+15的命令参数全部交给了默认操作,这些都是switch的分支语句.这里发生的是,内核检查是否一个设备特殊的ioctl的回调已经在设备结构中被设置,这个回调是保持在设备结构中的一个函数指针。如果回调已经被设置了.内核就会调用它.
  所以,为了实现一个特殊的ioctl,需要做的就是写一个特殊ioctl的回调,然后让device结构中的do_ioctl域指向它,对于EWK3设备,这个函数叫做ewrk3_ioctl(),对应的设备结构在ewrk3_init()中被初始化,ewrk3_ioctl()的代码清晰的展示了ifr.ifr_data的作用 ,是为了在用户程序和驱动之间交换信息。注意,内存的这个区域有双方向交换数据的作用,例如,在ewrk3驱动代码中,ifr.ifr_data最初的2个字节被用做向驱动传递预想要的动作。同样第五个字节指向的缓冲区用于交换其他的信息。
  当你浏览ewrk3_ioctl()代码的时候,记住在一个应用中用户空间的指令是无法访问内核空间的,由于这个原因 ,2个特殊的步骤提供给了驱动编写人员.他们是memcpy_tofs()和memcpy_fromfs()。内核里的做法是用memcpy_tofs() 拷贝内核数据到用户空间,类似的memcpy_fromfs()也是这样的,只是他拷贝用户数据到内核空间.。这些程序步骤是由于调用verify_area()而被执行的,目的是确认数据访问不会违法。同样记住printk()的用法是打印调试信息,这个函数和printf()很相象,但是它不能处理浮点数据,printf()函数在内核中是不能被使用的。由printk()产生的输出被转储到了一个目录./usr/adm/messages。
ioctl(转载)
2009-03-19 19:02
要按照Linux内核的约定方法为驱动程序选择ioctl编号,应该首先看看include/asm/ioctl.h和Documentation/ioctl-number.txt这两个文件。
_IO(type, nr)
用于构造无参数的命令编号;
_IOR(type, nr, datatype)
用于构造从驱动程序中读取数据的命令编号;
_IOW(type, nr, datatype)
用于写入数据的命令;
_IOWR(type, nr, datatype)
用于双向传输。
返回值
----------------------------------
ioctl的实现通常就是一个基于命令号的switch语句。但是当命令号不能匹配任何合法的操作时,默认的选择是什么呢?对于这个问题颇有争议。有些内核函数会返回-ENVAL("Invalid argument, 非法参数"),这是合理的,因为命令参数的确不是合法的参数。然而,POSIX标准规定,如果使用了不合适的ioctl命令参数,应该返回-ENOTTY。C库将这个错误码解释为“Inappropriate ioctl for device, 不合适的设备ioctl”,这看起来更贴切些。尽管如此,对非法的ioctl命令放回-EINVAL仍然时很普遍的做法。
cd ioctl_test
ioctl_test.c
-------------------------------------------
#include
#include
#include
#include
#include
#include
#include
#include "ioctl_test.h"
typedef struct {
struct class *class;
int major;
char *dev_name;
} MODULE_DATA;
static MODULE_DATA *g_module;
static int test_dev_ioctl(struct inode *inode, struct file *file,
unsigned int cmd, unsigned long arg)
{
int ret, val;
switch (cmd) {
case DEVICE_READ:
{
return put_user(10, (long *) arg);
}
case DEVICE_WRITE:
{
ret = get_user(val, (long *) arg);
if (ret)
return ret;
printk("val = %d\n", val);
break;
}
default:
return -ENOTTY;
}
return 0;
}
static int test_dev_open(struct inode *inode, struct file *file)
{
printk("test_dev open\n");
return 0;
}
static struct file_operations test_dev_fops = {
ioctl: test_dev_ioctl,
open: test_dev_open,
};
static int __init test_dev_init(void)
{
int res;
struct class_device *temp_class;
struct class *dev_class;
int major;
char dev_name[] = "test_dev";
g_module = kmalloc(sizeof (MODULE_DATA), GFP_KERNEL);
if (g_module == NULL) {
PERROR("kmalloc error\n");
return -ENOMEM;
}
memset(g_module, 0, sizeof (MODULE_DATA));
res = register_chrdev(0, dev_name, &test_dev_fops);
if (res major = major;
g_module->class = dev_class;
g_module->dev_name = dev_name;
return 0;
}
static void __exit test_dev_exit(void)
{
struct class *dev_class;
int major;
char *dev_name;
dev_class = g_module->class;
major = g_module->major;
dev_name = g_module->dev_name;
class_device_destroy(dev_class, MKDEV(major, 0));
class_destroy(dev_class);
unregister_chrdev(major, dev_name);
kfree(g_module);
PINFO("test_dev driver removed\n");
return;
}
module_init(test_dev_init);
module_exit(test_dev_exit);
MODULE_LICENSE("GPL");
ioctl_test.h
-------------------------------------------
#ifndef MXC_CMMB_INNO_H
#define MXC_CMMB_INNO_H
#undef PERROR
#define PERROR(fmt, args...) printk(KERN_ERR "cmmb driver error: line %d- %s():" fmt,__LINE__, __FUNCTION__, ## args)
#undef PINFO
#define PINFO(fmt, args...) printk(KERN_INFO fmt, ## args)
#undef PDEBUG /* undef it, just in case */
#ifdef CMMB_INNO_DEBUG
#define PDEBUG(fmt, args...) printk(KERN_DEBUG "cmmb driver line %d - %s():" fmt,__LINE__, __FUNCTION__, ## args)
#else
#define PDEBUG(fmt, args...) /* not debugging: nothing */
#endif
#define DEVICE_READ _IOR('t', 1, int)
#define DEVICE_WRITE _IOW('t', 2, int)
#endif
Makefile
-------------------------------------------
KDIR=/usr/src/linux-headers-2.6.24-19-generic
obj-m += ioctl_test.o
all:
make -C $(KDIR) M=`pwd` modules
clean:
make -C $(KDIR) M=`pwd` clean
cd ..
test_ioctl.c
-------------------------------------------
#include
#include
#include
#include "ioctl_test/ioctl_test.h"
#define INNO_DEV_NAME "/dev/test_dev"
int main()
{
int fd, ret, get_int, put_int = 527;
fd = open(INNO_DEV_NAME, O_RDWR);
ret = ioctl(fd, DEVICE_READ, &get_int);
if (ret 

你可能感兴趣的:(Linux多媒体编程)