Linux内核的ioctl函数学习

本文详细介绍了ioctl函数的使用方法,包括其在Linux系统中的作用、参数构成及如何在用户空间和内核空间之间传递数据。同时提供了ioctl的具体应用场景示例。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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

 

#include<unistd.h>

#include<sys/ioctl.h>

 

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

返回0:成功   -1:出错

 

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

我们可以把和网络相关的请求划分为6类:

套接口操作

文件操作

接口操作

ARP高速缓存操作

路由表操作

流系统

下表列出了网络相关ioctl请求的request参数以及arg地址必须指向的数据类型:

 

类别

Request

说明

数据类型

套接口

SIOCATMARKSIOCSPGRPSIOCGPGRP

是否位于带外标记设置套接口的进程ID或进程组ID获取套接口的进程ID或进程组ID

intintint

 文 件 

FIONBINFIOASYNCFIONREADFIOSETOWNFIOGETOWN

设置/清除非阻塞I/O标志设置/清除信号驱动异步I/O标志获取接收缓存区中的字节数设置文件的进程ID或进程组ID获取文件的进程ID或进程组ID

intintintintint

    接口          

SIOCGIFCONFSIOCSIFADDRSIOCGIFADDRSIOCSIFFLAGSSIOCGIFFLAGSSIOCSIFDSTADDRSIOCGIFDSTADDRSIOCGIFBRDADDRSIOCSIFBRDADDRSIOCGIFNETMASKSIOCSIFNETMASKSIOCGIFMETRICSIOCSIFMETRICSIOCGIFMTUSIOCxxx

获取所有接口的清单设置接口地址获取接口地址设置接口标志获取接口标志设置点到点地址获取点到点地址获取广播地址设置广播地址获取子网掩码设置子网掩码获取接口的测度设置接口的测度获取接口MTU(还有很多取决于系统的实现)

struct ifconfstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreqstruct ifreq

 ARP

SIOCSARPSIOCGARPSIOCDARP

创建/修改ARP表项获取ARP表项删除ARP表项

struct arpreqstruct arpreqstruct arpreq

路由

SIOCADDRTSIOCDELRT

增加路径删除路径

struct rtentrystruct 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_reqifc_ifcu.ifcu_req   //array of structuresreturned

 

#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_broadaddrifr_ifru.ifru_broadaddr   // broadcastaddress

#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(intfd,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是structifreq 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 和 structde4x5_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中对不同的命令传送不同的数据结构,本文只是为描述方便而在不同命令中使用了相同的数据结构。

转载:http://yfydz.cublog.cn

 

Linux内核的ioctl函数学习

 

我这里说的ioctl函数是在驱动程序里的,因为我不知道还有没有别的场合用到了ioctl,所以就规定了我们讨论的范围。为什么要写篇文章呢,是因为我前一阵子被ioctl给搞混了,这几天才弄明白它,于是在这里清理一下头脑。

一、 什么是ioctl。

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:

int ioctl(intfd, ind cmd, …);

其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。

ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数控制设备的I/O通道。

二、 ioctl的必要性

如果不用ioctl的话,也可以实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程序中实现write的时候检查一下是否有特殊约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在socket编程中常常这样做)。但是如果这样做的话,会导致代码分工不明,程序结构混 乱,程序员自己也会头昏眼花的。

所以,我们就使用ioctl来实现控制的功能。要记住,用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情。

三、 ioctl如何实现

这是一个很麻烦的问题,我是能省则省。要说清楚它,没有四五千字是不行的,所以我这里是不可能把它说得非常清楚了,不过如果有读者对用户程序怎么和驱动程序联系起来感兴趣的话,可以看我前一阵子写的《write的奥秘》。读者只要把write换成ioctl,就知道用户程序的ioctl是怎么和驱动 程序中的ioctl实现联系在一起的了。

我这里说一个大概思路,因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚了,但是得化一些时间来看。

在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程序命 令和驱动程序支持的途径。

命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常 困难的事情。

所以在Linux核心中是这样定义一个命令码的:

____________________________________

| 设备类型 | 序列号| 方向 |数据尺寸|

|----------|--------|------|--------|

| 8 bit   | 8 bit |2 bit |8~14 bit|

|----------|--------|------|--------|

这样一来,一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观,所以LinuxKernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备 序列号、数据传送方向和数据传输尺寸。

这些宏我就不在这里解释了,具体的形式请读者察看Linux核心源代码中的和,文件里给除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。

幻数是一个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是一样的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。

更多的说了也没有,读者还是看一看源代码吧,推荐各位阅读《Linux设备驱动程序》所带源代码中的short一例,因为它比较短小,功能比较简单,可以看明白ioctl的功能和细节。

四、 cmd参数如何得出

这里确实要说一说,cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程序,再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。

要透彻理解,只能是通过阅读源代码,我这篇文章实际上只是一个引子。Cmd参数的组织还是比较复杂的,我认为要搞熟它还是得花不少时间的,但是这是值得的,驱动程序中最难的是对中断的理解。

五、 小结

ioctl其实没有什么很难的东西需要理解,关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的,程序员最主要的工作量在switch{case}结构中,因为对设备的I/O控制都是通过这一部分的代码实现的。

 一般的说,,用户空间的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的ifrNAME域,比如,前述的无线网卡接口名称为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。

 


linux系统ioctl使用示例

These werewrited and collected by kf701,

you can useand modify them but NO WARRANTY.

 Contact with me : kf_701@21cn.com

程序1:检测接口的inet_addr,netmask,broad_addr

程序2:检查接口的物理连接是否正常

程序3:更简单一点测试物理连接

程序4:调节音量

***************************程序1****************************************

#include<stdio.h>

#include<string.h>

#include<stdlib.h>

#include<errno.h>

#include<unistd.h>

#include<sys/types.h>

#include<sys/socket.h>

#include<netinet/in.h>

#include<arpa/inet.h>

#include<sys/ioctl.h>

#include<net/if.h>

static voidusage(){

       printf("usage : ipconfig interface\n");

       exit(0);

}

int main(intargc,char **argv)

{

       struct sockaddr_in *addr;

       struct ifreq ifr;

       char *name,*address;

       int sockfd;

if(argc != 2)

               usage();

       else

               name = argv[1];

sockfd =socket(AF_INET,SOCK_DGRAM,0);

       strncpy(ifr.ifr_name,name,IFNAMSIZ-1);

if(ioctl(sockfd,SIOCGIFADDR,&ifr)== -1)

               perror("ioctlerror"),exit(1);

       addr = (struct sockaddr_in*)&(ifr.ifr_addr);

       address = inet_ntoa(addr->sin_addr);

       printf("inet addr: %s",address);

if(ioctl(sockfd,SIOCGIFBRDADDR,&ifr)== -1)

               perror("ioctlerror"),exit(1);

       addr = (struct sockaddr_in*)&ifr.ifr_broadaddr;

       address = inet_ntoa(addr->sin_addr);

       printf("broad addr: %s",address);

if(ioctl(sockfd,SIOCGIFNETMASK,&ifr)== -1)

               perror("ioctlerror"),exit(1);

       addr = (struct sockaddr_in*)&ifr.ifr_addr;

       address = inet_ntoa(addr->sin_addr);

       printf("inet mask: %s",address);

printf("\n");

       exit(0);

}

********************************程序2*****************************************************

#include<stdio.h>

#include<string.h>

#include<errno.h>

#include<fcntl.h>

#include<getopt.h>

#include<sys/socket.h>

#include<sys/ioctl.h>

#include<net/if.h>

#include<stdlib.h>

#include<unistd.h>

typedefunsigned short u16;

typedefunsigned int u32;

typedefunsigned char u8;

#include<linux/ethtool.h>

#include<linux/sockios.h>

intdetect_mii(int skfd, char *ifname)

{

       struct ifreq ifr;

       u16 *data, mii_val;

       unsigned phy_id;

/* Get thevitals from the interface. */

       strncpy(ifr.ifr_name, ifname, IFNAMSIZ);

       if (ioctl(skfd, SIOCGMIIPHY, &ifr)< 0)

       {

               fprintf(stderr,"SIOCGMIIPHY on %s failed: %s\n", ifname,

               strerror(errno));

               (void) close(skfd);

               return 2;

       }

data = (u16*)(&ifr.ifr_data);

       phy_id = data[0];

       data[1] = 1;

if(ioctl(skfd, SIOCGMIIREG, &ifr) < 0)

       {

               fprintf(stderr,"SIOCGMIIREG on %s failed: %s\n", ifr.ifr_name,

               strerror(errno));

               return 2;

       }

mii_val =data[3];

return(((mii_val& 0x0016) == 0x0004) ? 0 : 1);

}

intdetect_ethtool(int skfd, char *ifname)

{

       struct ifreq ifr;

       struct ethtool_value edata;

memset(&ifr,0, sizeof(ifr));

       edata.cmd = ETHTOOL_GLINK;

strncpy(ifr.ifr_name,ifname, sizeof(ifr.ifr_name)-1);

       ifr.ifr_data = (char *) &edata;

if(ioctl(skfd, SIOCETHTOOL, &ifr) == -1)

       {

               printf("ETHTOOL_GLINKfailed: %s\n", strerror(errno));

               return 2;

       }

return(edata.data ? 0 : 1);

}

int main(intargc, char **argv)

{

       int skfd = -1;

       char *ifname;

       int retval;

if( argv[1] )

               ifname = argv[1];

       else

               ifname = "eth0";

/* Open asocket. */

       if (( skfd = socket( AF_INET,SOCK_DGRAM, 0 ) ) < 0 )

       {

               printf("socketerror\n");

               exit(-1);

       }

retval =detect_ethtool(skfd, ifname);

if (retval ==2)

               retval = detect_mii(skfd,ifname);

close(skfd);

if (retval ==2)

               printf("Could not determinestatus\n");

if (retval ==1)

               printf("Link down\n");

if (retval ==0)

               printf("Link up\n");

returnretval;

}

*******************************程序3*****************************************************

#include<stdio.h>

#include<stdlib.h>

#include<string.h>

#include<errno.h>

#include<net/if.h>

#include<linux/sockios.h>

#include<sys/ioctl.h>

#defineLINKTEST_GLINK 0x0000000a

structlinktest_value {

       unsigned int   cmd;

       unsigned int   data;

};

static

void

usage(constchar * pname)

{

       fprintf(stderr, "usage: %s<device>\n", pname);

       fprintf(stderr, "returns:\n");

       fprintf(stderr, "\t 0: linkdetected\n");

       fprintf(stderr, "\t%d: %s\n",ENODEV, strerror(ENODEV));

       fprintf(stderr, "\t%d: %s\n",ENONET, strerror(ENONET));

       fprintf(stderr, "\t%d: %s\n",EOPNOTSUPP, strerror(EOPNOTSUPP));

       exit(EXIT_FAILURE);

}

static

int

linktest(constchar * devname)

{

       struct ifreq ifr;

       struct linktest_value edata;

       int fd;

/* setup ourcontrol structures. */

       memset(&ifr, 0, sizeof(ifr));

       strcpy(ifr.ifr_name, devname);

/* opencontrol socket. */

       fd=socket(AF_INET, SOCK_DGRAM, 0);

       if(fd < 0 ) {

               return -ECOMM;

       }

errno=0;

       edata.cmd = LINKTEST_GLINK;

       ifr.ifr_data = (caddr_t)&edata;

if(!ioctl(fd,SIOCETHTOOL, &ifr)) {

               if(edata.data) {

                       fprintf(stdout,"link detected on %s\n", devname);

                       return 0;

               } else {

                       errno=ENONET;

               }

       }

perror("linktest");

       return errno;

}

int

main(intargc, char *argv[])

{

       if(argc != 2) {

               usage(argv[0]);

       }

       return linktest(argv[1]);

}

*************************************程序4*********************************************************

#include<sys/types.h>

#include<sys/stat.h>

#include<fcntl.h>

#include<sys/ioctl.h>

#include<sys/soundcard.h>

#include<stdio.h>

#include<unistd.h>

#include<math.h>

#include<string.h>

#include<stdlib.h>

#defineBASE_VALUE 257

int main(intargc,char *argv[])

{

       int mixer_fd=0;

       char*names[SOUND_MIXER_NRDEVICES]=SOUND_DEVICE_LABELS;

       int value,i;

printf("\nusage:%sdev_no.[0..24] value[0..100]\n\n",argv[0]);

       printf("eg. %s 0100\n",argv[0]);

       printf("   will change the volume to MAXvolume.\n\n");

       printf("The dev_no. are asbelow:\n");

       for(i=0;i<SOUND_MIXER_NRDEVICES;i++){

               if (i%3==0)printf("\n");

               printf("%s:%d\t\t",names[i],i);

       }

       printf("\n\n");

if(argc<3)

               exit(1);

if ((mixer_fd= open("/dev/mixer",O_RDWR))){

               printf("Mixer openedsuccessfully,working...\n");

               value=BASE_VALUE*atoi(argv[2]);

if (ioctl(mixer_fd,MIXER_WRITE(atoi(argv[1])),&value)==0)

              printf("successfully.....");

               else   printf("unsuccessfully.....");

               printf("done.\n");

        }else

               printf("can't open/dev/mixer error....\n");

exit(0);

}

**********************************************************************************************

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值