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
ioctl简介 2007-09-17 08:43
字号:
大
中
小
我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,
所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被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,就知道用户程序的io
ctl是怎么和驱动程序中的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控制都是
通过这一部分的代码实现的。
本文来自ChinaUnix博客,如果查看原文请点:http://blog.chinaunix.net/u3/97642/showart_2006252.html
参考资料:
1.《Linux设备驱动程序》,鲁宾尼著,中国电力出版社。
2.《write的奥秘》
阅读(1321) | 评论(0) | 转发(0) |