ioctl相关使用

ioctl(),你懂的!

IOCTL(2)                  Linux Programmer's Manual                 IOCTL(2)

NAME         top

       ioctl - control device

SYNOPSIS         top

       #include <sys/ioctl.h>

       int ioctl(int d, int request, ...);

DESCRIPTION         top

       The ioctl() function manipulates the underlying device parameters of
       special files.  In particular, many operating characteristics of
       character special files (e.g., terminals) may be controlled with
       ioctl() requests.  The argument d must be an open file descriptor.

       The second argument is a device-dependent request code.  The third
       argument is an untyped pointer to memory.  It's traditionally char
       *argp (from the days before void * was valid C), and will be so named
       for this discussion.

       An ioctl() request has encoded in it whether the argument is an in
       parameter or out parameter, and the size of the argument argp in
       bytes.  Macros and defines used in specifying an ioctl() request are
       located in the file <sys/ioctl.h>.

RETURN VALUE         top

       Usually, on success zero is returned.  A few ioctl() requests use the
       return value as an output parameter and return a nonnegative value on
       success.  On error, -1 is returned, and errno is set appropriately.

ERRORS         top

       EBADF  d is not a valid descriptor.

       EFAULT argp references an inaccessible memory area.

       EINVAL Request or argp is not valid.

       ENOTTY d is not associated with a character special device.

       ENOTTY The specified request does not apply to the kind of object
              that the descriptor d references.

 

ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用个数如下:int ioctl(int fd, int cmd, …);其中fd就是用户程序打开设备时使用open函数返回的文件标示符,cmd就是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,有或没有是和cmd的意义相关的。ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就能在用户程序中使用ioctl函数控制设备的I/O通道。

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

这里确实要说一说,cmd参数在用户程式端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成,这个整数通过系统调用传递到内核中的驱动程式,再由驱动程式使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息,然后通过switch{case}结构进行相应的操作。要透彻理解,只能是通过阅读原始码,我这篇文章实际上只是个引子。Cmd参数的组织还是比较复杂的,我认为要搞熟他还是得花不少时间的,不过这是值得的,驱动程式中最难的是对中断的理解。

在驱动程式中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程式员自己的事情,因为设备都是特定的,这里也没法说。关键在于怎么样组织命令码,因为在ioctl中命令码是唯一联系用户程式命令和驱动程式支持的途径。命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或是把错误的命令发给正确的设备,或是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程式员发现了这些奇怪的事情的时候,再来调试程式查找错误,那将是非常困难的事情。所以在Linux核心中是这样定义一个命令码的:
____________________________________
| 设备类型 | 序列号 | 方向 |数据尺寸|
|----------|--------|------|--------|
| 8 bit | 8 bit |2 bit |8~14 bit|
|----------|--------|------|-------|
这样一来,一个命令就变成了一个整数形式的命令码。不过命令码非常的不直观,所以Linux Kernel中提供了一些宏,这些宏可根据便于理解的字符串生成命令码,或是从命令码得到一些用户能理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。这些宏我就不在这里解释了,具体的形式请读者察看Linux核心原始码中的和,文件里给除了这些宏完整的定义。这里我只多说一个地方,那就是"幻数"。幻数是个字母,数据长度也是8,所以就用一个特定的字母来标明设备类型,这和用一个数字是相同的,只是更加利于记忆和理解。就是这样,再没有更复杂的了。更多的说了也没有,读者还是看一看原始码吧,推荐各位阅读《Linux 设备驱动程式》所带原始码中的short一例,因为他比较短小,功能比较简单,能看明白ioctl的功能和细节。
short程序代码:
  View Code

如果不用IOCTL的话,也能实现对设备I/O通道的控制,但那就是蛮拧了。例如,我们可以在驱动程式中实现WRITE的时候检查一下是否有特别约定的数据流通过,如果有的话,那么后面就跟着控制命令(一般在SOCKET编程中常常这样做)。不过如果这样做的话,会导致代码分工不明,程式结构混乱,程式员自己也会头昏眼花的。所以,我们就使用IOCTL来实现控制的功能。要记住,用户程式所作的只是通过命令码告诉驱动程式他想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程式要做的事情。

控制I/O设备 ,提供了一种获得设备信息和向设备发送控制参数的手段。用于向设备发控制和配置命令 ,有些命令需要控制参数,这些数据是不能用read / write 读写的,称为Out-of-band数据。也就是说,read / write 读写的数据是in-band数据,是I/O操作的主体,而ioctl 命令传送的是控制信息,其中的数据是辅助的数据。
用 法: int ioctl(int handle, int cmd,[int *argdx, int argcx]);
返回值:成功为0,出错为-1
usr/include/asm-generic/ioctl.h中定义的宏的注释:
#define _IOC_NRBITS 8 //序数(number)字段的 字位宽度,8bits
#define _IOC_TYPEBITS 8 //幻数(type)字段的 字位宽度,8bits
#define _IOC_SIZEBITS 14 //大小(size)字段的 字位宽度,14bits
#define _IOC_DIRBITS 2 //方向(direction)字段的 字位宽度,2bits
#define _IOC_NRMASK ((1 << _IOC_NRBITS)-1) //序数字段的掩码,0x000000FF
#define _IOC_TYPEMASK ((1 << _IOC_TYPEBITS)-1) //幻数字段的掩码,0x000000FF
#define _IOC_SIZEMASK ((1 << _IOC_SIZEBITS)-1) //大小字段的掩码,0x00003FFF
#define _IOC_DIRMASK ((1 << _IOC_DIRBITS)-1) //方向字段的掩码,0x00000003
#define _IOC_NRSHIFT 0 //序数字段在整个字段中的位移,0
#define _IOC_TYPESHIFT (_IOC_NRSHIFT+_IOC_NRBITS) //幻数字段的位移,8
#define _IOC_SIZESHIFT (_IOC_TYPESHIFT+_IOC_TYPEBITS) //大小字段的位移,16
#define _IOC_DIRSHIFT (_IOC_SIZESHIFT+_IOC_SIZEBITS) //方向字段的位移,30
/*
* Direction bits.
*/
#define _IOC_NONE 0U //没有数据传输
#define _IOC_WRITE 1U //向设备写入数据,驱动程序必须从 用户空间读入数据
#define _IOC_READ 2U //从设备中读取数据, 驱动程序必须向 用户空间写入数据
#define _IOC(dir,type,nr,size) \
(((dir) << _IOC_DIRSHIFT) | \
((type) << _IOC_TYPESHIFT) | \
((nr) << _IOC_NRSHIFT) | \
((size) << _IOC_SIZESHIFT))
/*
* used to create numbers
*/
//构造无参数的命令编号
#define _IO(type,nr) _IOC(_IOC_NONE,(type),(nr),0)
//构造从 驱动程序中读取数据的命令编号
#define _IOR(type,nr,size) _IOC(_IOC_READ,(type),(nr),sizeof(size))
//用于向 驱动程序写入数据命令
#define _IOW(type,nr,size) _IOC(_IOC_WRITE,(type),(nr),sizeof(size))
//用于双向传输
#define _IOWR(type,nr,size) _IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
/*
*used to decode ioctl numbers..
*/
//从命令参数中解析出数据方向,即写进还是读出
#define _IOC_DIR(nr) (((nr) >> _IOC_DIRSHIFT) & _IOC_DIRMASK)
//从命令参数中解析出幻数type
#define _IOC_TYPE(nr) (((nr) >> _IOC_TYPESHIFT) & _IOC_TYPEMASK)
//从命令参数中解析出序数number
#define _IOC_NR(nr) (((nr) >> _IOC_NRSHIFT) & _IOC_NRMASK)
//从命令参数中解析出用户数据大小
#define _IOC_SIZE(nr) (((nr) >> _IOC_SIZESHIFT) & _IOC_SIZEMASK)
/* ...and for the drivers/sound files... */
#define IOC_IN (_IOC_WRITE << _IOC_DIRSHIFT)
#define IOC_OUT (_IOC_READ << _IOC_DIRSHIFT)
#define IOC_INOUT ((_IOC_WRITE|_IOC_READ) << _IOC_DIRSHIFT)
#define IOCSIZE_MASK (_IOC_SIZEMASK << _IOC_SIZESHIFT)
#define IOCSIZE_SHIFT (_IOC_SIZESHIFT)
程序例:
复制代码
 1 #include <stdlib.h>
 2 #include <stdio.h>
 3 #include <sys/ioctl.h>
 4 int main(void) {
 5 ..int stat;
 6 /* use func 8 to determine if the default drive is removable */
 7 ..stat = ioctl(0, 8, 0, 0);
 8 ..if (!stat)
 9 ....printf("Drive %c is removable.\n", getdisk() + 'A');
10 ..else
11 ....printf("Drive %c is not removable.\n", getdisk() + 'A');
12 ..return 0;
13 }
复制代码
int ioctl( int fd, int request, .../* void *arg */ ) 详解
第三个参数总是一个 指针,但指针的类型依赖于request 参数。我们可以把和网络相关的请求划分为6 类:
套接口操作
文件操作
接口操作
ARP 高速缓存操作
路由表操作
流系统
下表列出了网络相关ioctl请求的request 参数以及arg 地址必须指向的 数据类型
类别
Request
说明
数据类型
SIOCATMARK
SIOCSPGRP
SIOCGPGRP
是否位于带外标记
设置套接口的进程ID 或进程组ID
获取套接口的进程ID 或进程组ID
int
int
int
FIONBIO
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 : 根据ioctl 的第三个参数指向一个0 值或非0 值分别清除或设置针对本套接口的信号驱动异步I/O 标志,它决定是否收取针对本套接口的异步I/O 信号(SIGIO )。本请求和O_ASYNC 文件状态标志等效,而该标志可以通过fcntl 的F_SETFL 命令清除或设置。
FIONREAD : 通过由ioctl的第三个参数指向的整数返回当前在本套接口接收缓冲区中的字节数。本特性同样适用于文件,管道和终端。
FIOSETOWN : 对于套接口和SIOCSPGRP 等效。
FIOGETOWN : 对于套接口和SIOCGPGRP 等效。

 

大部分驱动除了需要具备读写设备的能力之外,还需要具备对硬件控制的能力。

 一、在用户空间,使用ioctl系统调用来控制设备,原型如下:

int ioctl(int fd,unsigned long cmd,...);
/*
fd:文件描述符
cmd:控制命令
...:可选参数:插入*argp,具体内容依赖于cmd
*/

  用户程序所作的只是通过命令码告诉驱动程序它想做什么,至于怎么解释这些命令和怎么实现这些命令,这都是驱动程序要做的事情

二、驱动ioctl方法

int (*ioctl) (struct inode *inode,struct file *filp,unsigned int cmd,unsigned long arg);
/*
inode与filp两个指针对应于应用程序传递的文件描述符fd,这和传递open方法的参数一样。
cmd 由用户空间直接不经修改的传递给驱动程序
arg 可选。
*/

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

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

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

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

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

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

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

1、定义命令:
  内核提供了一些宏来帮助定义命令:

//nr为序号,datatype为数据类型,如int
_IO(type, nr ) //没有参数的命令
_IOR(type, nr, datatype) //从驱动中读数据
_IOW(type, nr, datatype) //写数据到驱动
_IOWR(type,nr, datatype) //双向传送

  定义命令例子:

#define MEM_IOC_MAGIC 'm' //定义类型
#define MEM_IOCSET _IOW(MEM_IOC_MAGIC,0,int)
#define MEM_IOCGQSET _IOR(MEM_IOC_MAGIC, 1, int)

 

2、实现命令:
  定义好了命令,下一步就是要实现ioctl函数了,ioctl的实现包括三个技术环节:
1)返回值;
  ioctl函数的实现是根据命令执行的一个switch语句,但是,当命令不能匹配任何一个设备所支持的命令时,通常返回-EINVAL(非法参数);
2)参数使用;
  用户使用  int ioctl(int fd,unsinged long cmd,...)  时,...就是要传递的参数;
  再通过  int (*ioctl)(struct inode *inode, struct file *filp, unsigned int cmd, unsigned long arg)  中的arg传递;
  如果arg是一个整数,可以直接使用;
  如果是指针,我们必须确保这个用户地址是有效的,因此,使用之前需要进行正确检查。
  内部有检查的,不需要检测的:

copy_from_user
copy_to_user
get_user
put_user

  需要检测的:

__get_user
__put_user

 检测函数access_ok():

复制代码
复制代码
static inline int access_ok(int type, const void *addr, unsigned long size)
/*
type :是VERIFY_READ 或者VERIFY_WRITE用来表明是读用户内存还是写用户内存;
addr:是要操作的用户内存地址;
size:是操作的长度。如果ioctl需要从用户空间读一个整数,那么size参数就等于sizeof(int);

返回值:Access_ok返回一个布尔值:1,是成功(存取没问题);0,是失败,ioctl返回-EFAULT;

*/
复制代码
复制代码

3)命令操作;

switch(cmd)
{
     case:
     ... ...
}

 

三、ioctl实例分析

(1)memdev.h:

View Code
复制代码
复制代码
/*mem设备描述结构体*/
struct mem_dev                                     
{                                                        
  char *data;                      
  unsigned long size;       
};

/* 定义幻数 */
#define MEMDEV_IOC_MAGIC  'k'

/* 定义命令 */
#define MEMDEV_IOCPRINT   _IO(MEMDEV_IOC_MAGIC, 1)
#define MEMDEV_IOCGETDATA _IOR(MEMDEV_IOC_MAGIC, 2, int)
#define MEMDEV_IOCSETDATA _IOW(MEMDEV_IOC_MAGIC, 3, int)

#define MEMDEV_IOC_MAXNR 3

#endif /* _MEMDEV_H_ */
复制代码
复制代码

 

(2)memdev.c:(驱动程序)

View Code
复制代码
复制代码
static int mem_major = MEMDEV_MAJOR;

module_param(mem_major, int, S_IRUGO);

struct mem_dev *mem_devp; /*设备结构体指针*/

struct cdev cdev; 

/*文件打开函数*/
int mem_open(struct inode *inode, struct file *filp)
{
    struct mem_dev *dev;
    
    /*获取次设备号*/
    int num = MINOR(inode->i_rdev);

    if (num >= MEMDEV_NR_DEVS) 
            return -ENODEV;
    dev = &mem_devp[num];
    
    /*将设备描述结构指针赋值给文件私有数据指针*/
    filp->private_data = dev;
    
    return 0; 
}

/*文件释放函数*/
int mem_release(struct inode *inode, struct file *filp)
{
  return 0;
}

/*IO操作*/
int memdev_ioctl(struct inode *inode, struct file *filp,
                 unsigned int cmd, unsigned long arg)
{

    int err = 0;
    int ret = 0;
    int ioarg = 0;
    
    /* 检测命令的有效性 */
    if (_IOC_TYPE(cmd) != MEMDEV_IOC_MAGIC) 
        return -EINVAL;
    if (_IOC_NR(cmd) > MEMDEV_IOC_MAXNR) 
        return -EINVAL;

    /* 根据命令类型,检测参数空间是否可以访问 */
    if (_IOC_DIR(cmd) & _IOC_READ)
        err = !access_ok(VERIFY_WRITE, (void *)arg, _IOC_SIZE(cmd));
    else if (_IOC_DIR(cmd) & _IOC_WRITE)
        err = !access_ok(VERIFY_READ, (void *)arg, _IOC_SIZE(cmd));
    if (err) 
        return -EFAULT;

    /* 根据命令,执行相应的操作 */
    switch(cmd) {

      /* 打印当前设备信息 */
      case MEMDEV_IOCPRINT:
          printk("<--- CMD MEMDEV_IOCPRINT Done--->\n\n");
        break;
      
      /* 获取参数 */
      case MEMDEV_IOCGETDATA: 
        ioarg = 1101;
        ret = __put_user(ioarg, (int *)arg);
        break;
      
      /* 设置参数 */
      case MEMDEV_IOCSETDATA: 
        ret = __get_user(ioarg, (int *)arg);
        printk("<--- In Kernel MEMDEV_IOCSETDATA ioarg = %d --->\n\n",ioarg);
        break;

      default:  
        return -EINVAL;
    }
    return ret;

}

/*文件操作结构体*/
static const struct file_operations mem_fops =
{
  .owner = THIS_MODULE,
  .open = mem_open,
  .release = mem_release,
  .ioctl = memdev_ioctl,
};

/*设备驱动模块加载函数*/
static int memdev_init(void)
{
  int result;
  int i;

  dev_t devno = MKDEV(mem_major, 0);

  /* 静态申请设备号*/
  if (mem_major)
    result = register_chrdev_region(devno, 2, "memdev");
  else  /* 动态分配设备号 */
  {
    result = alloc_chrdev_region(&devno, 0, 2, "memdev");
    mem_major = MAJOR(devno);
  }  
  
  if (result < 0)
    return result;

  /*初始化cdev结构*/
  cdev_init(&cdev, &mem_fops);
  cdev.owner = THIS_MODULE;
  cdev.ops = &mem_fops;
  
  /* 注册字符设备 */
  cdev_add(&cdev, MKDEV(mem_major, 0), MEMDEV_NR_DEVS);
   
  /* 为设备描述结构分配内存*/
  mem_devp = kmalloc(MEMDEV_NR_DEVS * sizeof(struct mem_dev), GFP_KERNEL);
  if (!mem_devp)    /*申请失败*/
  {
    result =  - ENOMEM;
    goto fail_malloc;
  }
  memset(mem_devp, 0, sizeof(struct mem_dev));
  
  /*为设备分配内存*/
  for (i=0; i < MEMDEV_NR_DEVS; i++) 
  {
        mem_devp[i].size = MEMDEV_SIZE;
        mem_devp[i].data = kmalloc(MEMDEV_SIZE, GFP_KERNEL);
        memset(mem_devp[i].data, 0, MEMDEV_SIZE);
  }
    
  return 0;

  fail_malloc: 
  unregister_chrdev_region(devno, 1);
  
  return result;
}

/*模块卸载函数*/
static void memdev_exit(void)
{
  cdev_del(&cdev);   /*注销设备*/
  kfree(mem_devp);     /*释放设备结构体内存*/
  unregister_chrdev_region(MKDEV(mem_major, 0), 2); /*释放设备号*/
}

MODULE_AUTHOR("David Xie");
MODULE_LICENSE("GPL");

module_init(memdev_init);
module_exit(memdev_exit);
复制代码
复制代码

 

(3)app-ioctl.c(应用程序)

复制代码
复制代码
#include <stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>

#include "memdev.h"  /* 包含命令定义 */

int main()
{
    int fd = 0;
    int cmd;
    int arg = 0;
    char Buf[4096];
    
    
    /*打开设备文件*/
    fd = open("/dev/memdev0",O_RDWR);
    if (fd < 0)
    {
        printf("Open Dev Mem0 Error!\n");
        return -1;
    }
    
    /* 调用命令MEMDEV_IOCPRINT */
    printf("<--- Call MEMDEV_IOCPRINT --->\n");
    cmd = MEMDEV_IOCPRINT;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCPRINT fail\n");
            return -1;
    }
    
    
    /* 调用命令MEMDEV_IOCSETDATA */
    printf("<--- Call MEMDEV_IOCSETDATA --->\n");
    cmd = MEMDEV_IOCSETDATA;
    arg = 2007;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCSETDATA fail\n");
            return -1;
    }

    
    /* 调用命令MEMDEV_IOCGETDATA */
    printf("<--- Call MEMDEV_IOCGETDATA --->\n");
    cmd = MEMDEV_IOCGETDATA;
    if (ioctl(fd, cmd, &arg) < 0)
        {
            printf("Call cmd MEMDEV_IOCGETDATA fail\n");
            return -1;
    }
    printf("<--- In User Space MEMDEV_IOCGETDATA Get Data is %d --->\n\n",arg);    
    
    close(fd);
    return 0;    
}
复制代码

 

linux系统ioctl使用示例

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

  View Code

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

  View Code

程序3:测试物理连接

  View Code

程序4:调节音量

  View Code

 

下面是一个关于V4L视频采集中用到的用ioctl来配置视频采集设备(USB摄像头)的一些特性参数的例子:

1.   定义设备结构体

    struct vdIn {
    int fd ; //设备描述符
    char *videodevice ; //设备节点,在linux下,通用的视频采集设备节点为/dev/video0
    struct video_mmap vmmap;
    struct video_capability videocap;
    int mmapsize;
    struct video_mbuf videombuf;
    struct video_picture videopict;
    struct video_window videowin;
    struct video_channel videochan;    
    int cameratype ;
    char *cameraname;
    char bridge[9];
    int sizenative; // available size in jpeg
    int sizeothers;    // others palette 
    int palette; // available palette
    int norme ; // set spca506 usb video grabber
    int channel ; // set spca506 usb video grabber
    int grabMethod ; 
    unsigned char *pFramebuffer;
    unsigned char *ptframe[4];
    int framelock[4];
    pthread_mutex_t grabmutex;
    int framesizeIn ;
    volatile int frame_cour;
    int bppIn;
    int  hdrwidth;
    int  hdrheight; 
    int  formatIn;
    int signalquit;    
    };

 

2.   设备节点赋值, "/dev/video0"是真实的物理摄像头设备在linux中的表示

    if (videodevice == NULL || *videodevice == 0)
    {
      videodevice = "/dev/video0";
    }

 

3.  调用 设备 初始化函数

   struct vdIn videoIn;  //在spcav4l.h中定义
   videodevice = "/dev/video0"; //节点
   int width = 352;  //宽
   int height = 288;   //高
   int format = VIDEO_PALETTE_JPEG; //格式
   int grabmethod = 1;

 

   memset (&videoIn, 0, sizeof (struct vdIn));
  if (init_videoIn(&videoIn, videodevice, width, height, format,grabmethod) != 0)
     
    if(debug) printf (" damned encore rate !!\n");

 

4.   设备初始化函数传值

  int init_videoIn (struct vdIn *vd, char *device, int width, int height,
          int format, int grabmethod)
{
  int err = -1;
  int i;
  if (vd == NULL || device == NULL)
    return -1;
  if (width == 0 || height == 0)
    return -1;
  if(grabmethod < 0 || grabmethod > 1)
      grabmethod = 1; //read by default;
    // check format 
  vd->videodevice = NULL;
  vd->cameraname = NULL;
  vd->videodevice = NULL;
  vd->videodevice = (char *) realloc (vd->videodevice, 16);
  vd->cameraname = (char *) realloc (vd->cameraname, 32);


  snprintf (vd->videodevice, 12, "%s", device);
  if(debug) printf("video %s \n",vd->videodevice); 


  memset (vd->cameraname, 0, sizeof (vd->cameraname));
  memset(vd->bridge, 0, sizeof(vd->bridge));
  vd->signalquit = 1;//信号设置
  vd->hdrwidth = width;
  vd->hdrheight = height;
  /*          compute the max frame size   */
  vd->formatIn = format; //传进来的 format = VIDEO_PALETTE_JPEG;
  vd->bppIn = GetDepth (vd->formatIn); 
 

  vd->grabMethod = grabmethod;        //mmap or read 
  vd->pFramebuffer = NULL;
  /* init and check all setting */


  err = init_v4l (vd); // V4L初始化函数 
  ....................................................
}


5.     V4L初始化函数

static int init_v4l (struct vdIn *vd)
{
  int f;
  int erreur = 0;
  int err;
  if ((vd->fd = open (vd->videodevice, O_RDWR)) == -1)//打开 
    exit_fatal ("ERROR opening V4L interface");

  if (ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap) ) == -1)
    exit_fatal ("Couldn't get videodevice capability");

  ....................................

 

    在视频采集之前,先要对Video4Linux进行初始化
初始化阶段用ioctl(int fd, ind cmd, …) 函数和设备进行“对话”。Fd是设备的文件描述符,cmd是用户程序对设备的控制命令 ,省略号一般是一个表示类型长度的参数,也可以没有。初始化步骤如下:
 
1.打开视频:
open (vd->videodevice, O_RDWR))
 
2. 读video_capability 中信息包括设备名称,支持最大最小分辨率,信号源信息等。
  调用函数ioctl (vd->fd, VIDIOCGCAP, &(vd->videocap))成功后可读取vd->capability各分量 
   video_capability是Video4linux支持的数据结构, video_capability 包含设备的基本信息(设备名称、支持的最大最小分辨率、信号源信息等),包含的分量:
•name[32]   //设备名称
•maxwidth ,maxheight,minwidth,minheight
•Channels //信号源个数
•type    //是否能capture,彩色还是黑白,是否能裁剪等等。值如VID_TYPE_CAPTURE等

 
struct video_capability
{
        char name[32];
        int type;
        int channels;        /* Num channels */
        int audios;        /* Num audio devices */
        int maxwidth;        /* Supported width */
        int maxheight;        /* And height */
        int minwidth;        /* Supported width */
        int minheight;        /* And height */
};
3.对采集图象的各种属性进行设置 ,分为两步 首先获取摄象头缓冲区中video_picture中信息调用函数ioctl(vd->fd, VIDIOCGPICT, &(vd->picture));然后改变video_picture中分量的值,为vd->videopict分量赋新值,调用 ioctl (vd->fd, VIDIOCSPICT, &vd->videopict)即可实现
 

设备控制接口(ioctl 函数)
回想一下我们在字符设备驱动中介绍的struct file_operations 结构,这里我们将介绍一个新的方法:

int (*ioctl) (struct inode *, struct file *, unsigned int, unsigned long);


这是驱动程序设备控制接口函数(ioctl函数)的内核原型定义,struct inode * 和struct file* 描述了操作的文件,unsigned int 描述了ioctl命令号,这是一个重要的参数,我们稍后会对它做详细介绍。最后一个参数是unsigned long数据类型,描述了ioctl命令可能带有的参数,它可能是一个整数或指针数据。
  • ioctl命令号
ioctl命令号是这个函数中最重要的参数,它描述的ioctl要处理的命令。Linux中使用一个32位的数据来编码ioctl命令,它包含四个部分:dir:type:nr:size。
  • dir:
代表数据传输的方向,占2位,可以是_IOC_NONE(无数据传输,0U),_IOC_WRITE(向设备写数据,1U)或_IOC_READ(从设备读数据,2U)或他们的逻辑或组合,当然只有_IOC_WRITE和_IOC_READ的逻辑或才有意义。
  • type:
描述了ioctl命令的类型,8位。每种设备或系统都可以指定自己的一个类型号,ioctl用这个类型来表示ioctl命令所属的设备或驱动。一般用ASCII码字符来表示,如 'a'。
  • nr:
ioctl命令序号,一般8位。对于一个指定的设备驱动,可以对它的ioctl命令做一个顺序编码,一般从零开始,这个编码就是ioctl命令的序号。
  • size:
ioctl命令的参数大小,一般14位。ioctl命令号的这个数据成员不是强制使用的,你可以不使用它,但是我们建议你指定这个数据成员,通过它我们可以检查用户空间数据的大小以避免错误的数据操作,也可以实现兼容旧版本的ioctl命令。
我们可以自己来直接指定一个ioctl命令号,它可能仅仅是一个整数集,但Linux中的ioctl命令号都是有特定含义的,因此通常我们不推荐这么做。其实Linux内核已经提供了相应的宏来自动生成ioctl命令号:

_IO(type,nr)
_IOR(type,nr,size)
_IOW(type,nr,size)
_IOWR(type,nr,size)


宏_IO用于无数据传输,宏_IOR用于从设备读数据,宏 _IOW用于向设备写数据,宏_IOWR用于同时有读写数据的IOCTL命令。相对的,Linux内核也提供了相应的宏来从ioctl命令号种解码相应的域值:

_IOC_DIR(nr)
_IOC_TYPE(nr)
_IOC_NR(nr)
_IOC_SIZE(nr)


这些宏都定义在<asm/ioctl.h>头文件中(一般在<asm-generic.h>头文件中)。一般在使用中,先指定各个IOCTL命令的顺序编号(一般从0开始),然后根据使用的环境用这些宏来自动生成IOCTL命令号,在后面的例子中你可以了解实际的使用场景。
  • ioctl返回值
ioctl函数的返回值是一个整数类型的值,如果命令执行成功,ioctl返回零,如果出现错误,ioctl函数应该返回一个负值。这个负值会作为errno值反馈给调用此ioctl的用户空间程序。关于返回值的具体含义,请参考<linux/errno.h>和<asm/errno.h>头文件。
  • ioctl参数
这里有必要说明一下ioctl命令的参数,因为它很容易犯错误。如果ioctl命令参数仅仅是一个整数,那么事情很简单了,我们可以在ioctl函数中直接使用它。但如果它是一个指针数据,那么使用上就要小心了。首先要说明这个参数是有用户空间的程序传递过来的,因此这个指针指向的地址是用户空间地址,在Linux中,用户空间地址是一个虚拟地址,在内核空间是无法直接使用它的。为了解决在内核空间使用用户空间地址的数据,Linux内核提供了以下函数,它们用于在内核空间访问用户空间的数据,定义在<asm/uaccess.h>头文件中:

unsigned long __must_check copy_to_user(void __user *to,
const void *from, unsigned long n);
unsigned long __must_check copy_from_user(void *to,
const void __user *from, unsigned long n);


copy_from_user和copy_to_user一般用于复杂的或大数据交换,对于简单的数据类型,如int或char,内核提供了简单的宏来实现这个功能:

#define get_user(x,ptr)
#define put_user(x,ptr)


其中,x是内核空间的简单数据类型地址,ptr是用户空间地址指针。
我们需要牢记:在内核中是无法直接访问用户空间地址数据的。因此凡是从用户空间传递过来的指针数据,务必使用内核提供的函数来访问它们。
这里有必要再一次强调的是,在内核模块或驱动程序的编写中,我们强烈建议你使用内核提供的接口来生成并操作ioctl命令号,这样可以对命令号赋予特定的含义,使我们的程序更加的健壮;另一方面也可以提高程序的可移植性。

举例
好了,是时候举个例子了。我们将扩展我们的helloworld驱动添加ioctl函数。

首先,我们添加一个头文件来定义ioctl接口需要用到的数据(hello.h):

#ifndef _HELLO_H
#define _HELLO_H
#include <asm/ioctl.h>
#define MAXBUF 20
typedef struct _buf_data{
int size;
char data [MAXBUF];
}buf_data;

#define HELLO_IOCTL_NR_BASE 0
#define HELLO_IOCTL_NR_SET_DATA (HELLO_IOCTL_NR_BASE + 1)
#define HELLO_IOCTL_NR_MAX (HELLO_IOCTL_NR_GET_BUFF + 1)

#define HELLO_IOCTL_SET_DATA _IOR('h', HELLO_IOCTL_NR_SET_DATA, buf_data*)

#endif


然后为我们的驱动程序添加ioctl接口hello_ioctl,并实现这个函数:

static int hello_ioctl (struct inode *inode, struct file *filp,
unsigned int cmd, unsigned long arg)
{
int cmd_nr;
int err;
buf_data buff;

err = 0;
cmd_nr = _IOC_NR (cmd);
switch (cmd_nr){
case HELLO_IOCTL_NR_SET_DATA:
if (copy_from_user(&buff, (unsigned char *)arg, sizeof(buf_data)))
{
err = -ENOMEM;
goto error;
}
memset(hello_buf, 0, sizeof(hello_buf));
memcpy(hello_buf, buff.data, buff.size);
break;
default:
printk("hello_ioctl: Unknown ioctl command (%d)\n", cmd);
break;
}

error:
return err;
}

static struct file_operations hello_fops = {
.read = hello_read,
.write = hello_write,
.open = hello_open,
.ioctl = hello_ioctl,
.release = hello_release,
};

 

 

其他两个字段在这里不作深究。

值得一提的是LDD3里这么一段话:

The header also defines macros that may be used in your driver to decode the num-
bers: _IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr), and _IOC_SIZE(nr). We won’t go
into any more detail about these macros because the header file is clear, and sample
code is shown later in this section.

根据ioctl.h文件的定义,很明显_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个宏里面的参数就是一个IOCTL命令,即一个32位的二进制数,而文件中参数居然用nr表示! 当然用nr表示不是逻辑上的错误。但是别忘了文件中还有_IOW(type,nr,size)这样的定义IOCTL命令的宏,这其中的参数nr是IOCTL命令编号中的一个nr字段,一个8位的二进制数!我想很多新人都会对此产生莫大的疑惑!所以我认为把_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)这几个解码宏的参数改用cmd表示更恰当!
但是,我知道这不是LDD3作者的错,因为内核头文件里面也是这么表示的。我想内核开发者不可能没意识到这个问题。因此,我猜测这其中肯定有个历史原因: 大概以前版本的命令不管type是否一样,nr字段的值都是唯一的,于是仅靠nr字段就可以解码出一个IOCTL命令的其他字段吧?!但即使这样_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)也没必要保留这种写法啊!到底谁可以告诉我真相?

LDD3没有对_IOC_DIR(nr), _IOC_TYPE(nr), _IOC_NR(nr),_IOC_SIZE(nr)里面的nr作任何解释,只是实例中有如下用法:

if (_IOC_TYPE(cmd) != SCULL_IOC_MAGIC) return -ENOTTY;
if (_IOC_NR(cmd) > SCULL_IOC_MAXNR) return -ENOTTY;

可见那个nr参数完全就是我所说的32位的IOCTL命令编码。靠,既然这样好歹也对着个confusion作一下简单的解释啊!如果LDD3一书那“既 不承认,也不否认”的暧昧态度让我真让我哭笑不得的话,那么国内某书(具体哪本我就不说了)简直令我抓狂,我摘书中的两段话如下:

_IO(type,nr):定义一个没有数据传输的命令编号。type为幻数,nr为命令编号。...
...
_IOC_DIR(nr): 获得命令编号的命令传输方向(direction)。这个宏的参数就是命令编号。

后记
到这里我们已经向您展示了Linux内核驱动程序的设备控制接口(ioctl接口),详细的介绍了它的使用,并给出了一个实际的例子,尽管它很简单,但已经足够了。到这里你可以写出一个标准的Linux驱动程序了。不过这里还有个问题,那就是我们不得不从/proc/devices文件里读取设备号然后手动创建设备节点。我们是否可以让系统自动的创建这个设备节点文件呢?当然可以。不过在那之前,我们必须深入了解Linux的设备驱动模型。后面的章节我们就详细的介绍Linux的设备驱动模型及Hotplug机制。

 

使用ioctl参数

在使用ioctl的可选arg参数时,如果传递的是一个整数,它可以直接使用。如果是一个指针,,就必须小心。当用一个指针引用用户空间,我们必须确保用户地址是有效的,其校验(不传送数据) 由函数access_ok实现,定义在 :

int access_ok(int type, const void *addr, unsigned long size);

第一个参数应当是VERIFY_READ(读)或VERIFY_WRITE(读写);addr参数为用户空间地址,size 为字节数,可使用sizeof()。access_ok返回一个布尔值:1是成功 (存取没问题) 和0是失败 (存取有问题)。如果它返回假,驱动应当返回-EFAULT给调用者。

注意:首先,access_ok不做校验内存存取的完整工作;它只检查内存引用是否在这个进程有合理权限的内存范围中,且确保这个地址不指向内核空间内存。其次,大部分驱动代码不需要真正调用access_ok,而直接使用put_user(datum, ptr)和get_user(local, ptr),它们带有校验的功能,确保进程能够写入给定的内存地址,成功时返回0,并且在错误时返回-EFAULT。

put_user(datum, ptr) 
__put_user(datum, ptr) 
get_user(local, ptr) 
__get_user(local, ptr)

这些宏它们相对copy_to_user和copy_from_user快,并且这些宏已被编写来允许传递任何类型的指针,只要它是一个用户空间地址,传送的数据大小依赖prt 参数的类型并且在编译时使用sizeof 和typeof 等编译器内建宏确定。他们只传送1、2、4或8 个字节。如果使用以上函数来传送一个大小不适合的值,结果常常是一个来自编译器的奇怪消息,如"coversion to non-scalar type requested"。在这些情况中,必须使用copy_to_user或者copy_from_user。

__put_user和__get_user进行更少的检查 (不调用access_ok),但是仍然能够失败如果被指向的内存对用户是不可写的,所以他们应只用在内存区已经用access_ok检查过的时候。作为通用的规则:当实现一个read方法时,调用__put_user来节省几个周期,或者当你拷贝几个项时,因此,在第一次数据传送之前调用access_ok一次。

 

UNIX网络编程——ioctl 函数的用法详解

1.介绍

       Linux网络程序与内核交互的方法是通过ioctl来实现的,ioctl与网络协议栈进行交互,可得到网络接口的信息,网卡设备的映射属性和配置网络接口。并且还能够查看,修改,删除ARP高速缓存的信息,所以,我们有必要了解一下ioctl函数的具体实现。

 

2.相关结构体与相关函数

 

  1. #include <sys/ioctl.h>  
  2.   
  3. int ioctl(int d,int request,....);  

参数:

d-文件描述符,这里是对网络套接字操作,显然是套接字描述符。

request-请求码

省略的部分对应不同的内存缓冲区,而具体的内存缓冲区是由请求码request来决定的,下面看一下具体都有哪些相关缓冲区。

 

(1)网络接口请求结构ifreq

 

  1. struct ifreq  
  2. {  
  3.     #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC  
  4.     union{  
  5.          char ifrn_name[IFNAMESIZ];//网络接口名称  
  6.          }ifr_ifrn;  
  7.     union{  
  8.          struct sockaddr ifru_addr;//本地IP地址  
  9.          struct sockaddr ifru_dstaddr;//目标IP地址  
  10.          struct sockaddr ifru_broadaddr;//广播IP地址  
  11.          struct sockaddr ifru_netmask;//本地子网掩码地址  
  12.          struct sockaddr ifru_hwaddr;//本地MAC地址  
  13.          short ifru_flags;//网络接口标记  
  14.          int ifru_ivalue;//不同的请求含义不同  
  15.          struct ifmap ifru_map;//网卡地址映射  
  16.          int ifru_mtu;//最大传输单元  
  17.          char ifru_slave[IFNAMSIZ];//占位符  
  18.          char ifru_newname[IFNAMSIZE];//新名称  
  19.          void __user* ifru_data;//用户数据  
  20.          struct if_settings ifru_settings;//设备协议设置  
  21.          }ifr_ifru;  
  22. }  
  23. #define ifr_name ifr_ifrn.ifrn_name;//接口名称  
  24. #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC  
  25. #define ifr_addr ifr_ifru.ifru_addr;//本地IP  
  26. #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP  
  27. #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP  
  28. #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码  
  29. #define ifr_flags ifr_ifru.ifru_flags;//标志  
  30. #define ifr_metric ifr_ifru.ifru_ivalue;//接口侧度  
  31. #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元  
  32. #define ifr_map ifr_ifru.ifru_map;//设备地址映射  
  33. #define ifr_slave ifr_ifru.ifru_slave;//副设备  
  34. #define ifr_data ifr_ifru.ifru_data;//接口使用  
  35. #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号  
  36. #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽  
  37. #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度  
  38. #define ifr_newname ifr_ifru.ifru_newname;//新名称  
  39. #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置  

       如果想获得网络接口的相关信息,就传入ifreq结构体。

 

 

(2)网卡设备属性ifmap

 

  1. struct ifmap{//网卡设备的映射属性  
  2.     unsigned long mem_start;//开始地址  
  3.     unsigned long mem_end;//结束地址  
  4.     unsigned short base_addr;//基地址  
  5.     unsigned char irq;//中断号  
  6.     unsigned char dma;//DMA  
  7.     unsigned char port;//端口  
  8. }  


(3)网络配置接口ifconf

 

 

  1. struct ifconf{//网络配置结构体是一种缓冲区  
  2.     int ifc_len;//缓冲区ifr_buf的大小  
  3.     union{  
  4.          char__user *ifcu_buf;//绘冲区指针  
  5.          struct ifreq__user* ifcu_req;//指向ifreq指针  
  6.          }ifc_ifcu;  
  7. };  
  8. #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址  
  9. #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址  


(4)ARP高速缓存操作arpreq

 

 

  1. /* ARP高速缓存操作,包含IP地址和硬件地址的映射表,操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 */  
  2. struct arpreq{  
  3.    struct sockaddr arp_pa;//协议地址  
  4.    struct sockaddr arp_ha;//硬件地址  
  5.    int arp_flags;//标记  
  6.    struct sockaddr arp_netmask;//协议地址的子网掩码  
  7.    char arp_dev[16];//查询网络接口的名称  
  8. }  

 

 

3. 请求码request

类别

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

 

 



4. 相关例子

(1)网络接口信息
       选项获取填充struct ifreq的ifr_name:

 

  1. #include <stdio.h>  
  2. #include <stdlib.h>  
  3. #include <string.h>  
  4. #include <sys/types.h>  
  5. #include <sys/socket.h>  
  6. #include <netinet/in.h>  
  7. #include <linux/if.h>  
  8. #include <arpa/inet.h>  
  9. #include <linux/sockios.h>  
  10. /** 
  11. ioctl函数是与内核交互的一种方法,使用ioctl函数与内核协议栈进行交互 
  12. ioctl函数可操作I/O请求,文件请求与网络接口请求 
  13. 网络接口请求的几个结构体: 
  14. struct ifreq{ 
  15. #define IFHWADDRLEN 6 //6个字节的硬件地址,即MAC 
  16. union{ 
  17.  char ifrn_name[IFNAMESIZ];//网络接口名称 
  18. }ifr_ifrn; 
  19. union{ 
  20.  struct sockaddr ifru_addr;//本地IP地址 
  21.  struct sockaddr ifru_dstaddr;//目标IP地址 
  22.  struct sockaddr ifru_broadaddr;//广播IP地址 
  23.  struct sockaddr ifru_netmask;//本地子网掩码地址 
  24.  struct sockaddr ifru_hwaddr;//本地MAC地址 
  25.  short ifru_flags;//网络接口标记 
  26.  int ifru_ivalue;//不同的请求含义不同 
  27.  struct ifmap ifru_map;//网卡地址映射 
  28.  int ifru_mtu;//最大传输单元 
  29.  char ifru_slave[IFNAMSIZ];//占位符 
  30.  char ifru_newname[IFNAMSIZE];//新名称 
  31.  void __user* ifru_data;//用户数据 
  32.  struct if_settings ifru_settings;//设备协议设置 
  33.  
  34. }ifr_ifru; 
  35. } 
  36. #define ifr_name ifr_ifrn.ifrn_name;//接口名称 
  37. #define ifr_hwaddr ifr_ifru.ifru_hwaddr;//MAC 
  38. #define ifr_addr ifr_ifru.ifru_addr;//本地IP 
  39. #define ifr_dstaddr ifr_ifru.dstaddr;//目标IP 
  40. #define ifr_broadaddr ifr_ifru.broadaddr;//广播IP 
  41. #define ifr_netmask ifr_ifru.ifru_netmask;//子网掩码 
  42. #define ifr_flags ifr_ifru.ifru_flags;//标志 
  43. #define ifr_metric  ifr_ifru.ifru_ivalue;//接口侧度 
  44. #define ifr_mtu ifr_ifru.ifru_mtu;//最大传输单元 
  45. #define ifr_map ifr_ifru.ifru_map;//设备地址映射 
  46. #define ifr_slave ifr_ifru.ifru_slave;//副设备 
  47. #define ifr_data ifr_ifru.ifru_data;//接口使用 
  48. #define ifr_ifrindex ifr_ifru.ifru_ivalue;//网络接口序号 
  49. #define ifr_bandwidth ifr_ifru.ifru_ivalue;//连接带宽 
  50. #define ifr_qlen ifr_ifru.ifru_ivalue;//传输单元长度 
  51. #define ifr_newname ifr_ifru.ifru_newname;//新名称 
  52. #define ifr_seeting ifr_ifru.ifru_settings;//设备协议设置 
  53.  
  54. struct ifmap{//网卡设备的映射属性 
  55.  unsigned long mem_start;//开始地址 
  56.  unsigned long mem_end;//结束地址 
  57.  unsigned short base_addr;//基地址 
  58.  unsigned char irq;//中断号 
  59.  unsigned char dma;//DMA 
  60.  unsigned char port;//端口 
  61. } 
  62.  
  63. struct ifconf{//网络配置结构体是一种缓冲区 
  64.  int ifc_len;//缓冲区ifr_buf的大小 
  65.  union{ 
  66.   char__user *ifcu_buf;//绘冲区指针 
  67.  struct ifreq__user* ifcu_req;//指向ifreq指针 
  68. }ifc_ifcu; 
  69.  
  70. }; 
  71. #define ifc_buf ifc_ifcu.ifcu_buf;//缓冲区地址 
  72. #define ifc_req ifc_ifcu.ifcu_req;//ifc_req地址 
  73.  
  74. (1)获得配置选项SIOCGIFCONF获得网络接口的配置情况 需要填充struct ifreq中ifr_name变量 
  75. (2)其它选项获取填充struct ifreq的ifr_name 
  76. **/  
  77.   
  78. int main(int argc,char*argv[]){  
  79.     int s;  
  80.     int err;  
  81.     s=socket(AF_INET,SOCK_DGRAM,0);  
  82.     if(s<0){  
  83.     perror("socket error");  
  84.     return;  
  85.     }  
  86.   
  87.     //传入网络接口序号,获得网络接口的名称  
  88.     struct ifreq ifr;  
  89.   
  90.     ifr.ifr_ifindex=2;//获得第2个网络接口的名称  
  91.     err=ioctl(s,SIOCGIFNAME,&ifr);  
  92.   
  93.     if(err)  
  94.         perror("index error");  
  95.     else  
  96.         printf("the %dst interface is:%s\n",ifr.ifr_ifindex,ifr.ifr_name);  
  97.   
  98.     //传入网络接口名称,获得标志  
  99.     memcpy(ifr.ifr_name,"eth0",5);  
  100.     err=ioctl(s,SIOCGIFFLAGS,&ifr);  
  101.     if(!err)  
  102.         printf("SIOCGIFFLAGS:%d\n",ifr.ifr_flags);  
  103.   
  104.     //获得MTU和MAC  
  105.     err=ioctl(s,SIOCGIFMTU,&ifr);  
  106.     if(!err)  
  107.         printf("SIOCGIFMTU:%d\n",ifr.ifr_mtu);  
  108.   
  109.     //获得MAC地址  
  110.     err=ioctl(s,SIOCGIFHWADDR,&ifr);  
  111.     if(!err){  
  112.         unsigned char* hw=ifr.ifr_hwaddr.sa_data;  
  113.         printf("SIOCGIFHWADDR:%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);  
  114.     }  
  115.   
  116.     //获得网卡映射参数 命令字SIOCGIFMAP  
  117.     err=ioctl(s,SIOCGIFMAP,&ifr);  
  118.     if(!err)  
  119.         printf("SIOCGIFMAP,mem_start:%d,mem_end:%d,base_addr:%d,ifr_map:%d,dma:%d,port:%d\n",ifr.ifr_map.mem_start,ifr.ifr_map.mem_end,ifr.ifr_map.base_addr,ifr.ifr_map.irq,ifr.ifr_map.dma,ifr.ifr_map.port);  
  120.   
  121.   
  122.     //获得网卡序号  
  123.     err=ioctl(s,SIOCGIFINDEX,&ifr);  
  124.     if(!err)  
  125.         printf("SIOCGIFINDEX:%d\n",ifr.ifr_ifindex);  
  126.   
  127.     //获取发送队列的长度  
  128.     err=ioctl(s,SIOCGIFTXQLEN,&ifr);  
  129.     if(!err)  
  130.         printf("SIOCGIFTXQLEN:%d\n",ifr.ifr_qlen);  
  131.   
  132.     //获取网络接口IP  
  133.   
  134.     struct sockaddr_in *sin=(struct sockaddr_in*)&ifr.ifr_addr;//保存的是二进制IP  
  135.     char ip[16];//字符数组,存放字符串  
  136.     memset(ip,0,16);  
  137.     err=ioctl(s,SIOCGIFADDR,&ifr);  
  138.     if(!err){  
  139.         inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);//转换的字符串保存到ip数组中,第二个参数是要转换的二进制IP指针,第三个参数是转换完成存放IP的缓冲区,最后一个参数是缓冲区的长度  
  140.         printf("SIOCGIFADDR:%s\n",ip);  
  141.     }  
  142.   
  143.     //查询目标IP地址  
  144.     err=ioctl(s,SIOCGIFDSTADDR,&ifr);  
  145.     if(!err){  
  146.         inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);  
  147.         printf("SIOCGIFDSTADDR:%s\n",ip);  
  148.     }  
  149.   
  150.     //查询子网掩码  
  151.     err=ioctl(s,SIOCGIFNETMASK,&ifr);  
  152.     if(!err){  
  153.         inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);  
  154.         printf("SIOCGIFNETMASK:%s\n",ip);  
  155.     }  
  156.   
  157.     //设置IP地址,设置网络接口  
  158.     inet_pton(AF_INET,"222.27.253.108",&sin->sin_addr.s_addr);//将字符串IP转换成二进制  
  159.     err=ioctl(s,SIOCSIFADDR,&ifr);//发送设置本机ip地址请求命令  
  160.     if(!err){  
  161.         printf("check IP-----");    
  162.         memset(&ifr,0,sizeof(ifr));  
  163.         memcpy(ifr.ifr_name,"eth0",5);  
  164.         ioctl(s,SIOCGIFADDR,&ifr);  
  165.         inet_ntop(AF_INET,&sin->sin_addr.s_addr,ip,16);  
  166.         printf("%s\n",ip);  
  167.     }  
  168.   
  169.     //得到接口的广播地址  
  170.     memset(&ifr,0,sizeof(ifr));  
  171.     memcpy(ifr.ifr_name,"eth0",5);  
  172.     ioctl(s,SIOCGIFBRDADDR,&ifr);  
  173.     struct sockaddr_in *broadcast=(struct sockaddr_in*)&ifr.ifr_broadaddr;  
  174.     //转换成字符串  
  175.     inet_ntop(AF_INET,&broadcast->sin_addr.s_addr,ip,16);//inet_ntop将二进制IP转换成点分十进制的字符串  
  176.     printf("BROADCAST IP:%s\n",ip);  
  177.     close(s);  
  178. }  

运行结果:

 

 

(2)查看arp高速缓存信息

  1. #include <stdio.h>  
  2. #include <sys/types.h>  
  3. #include <sys/socket.h>  
  4. #include <netinet/in.h>  
  5. #include <arpa/inet.h>  
  6. #include <net/if_arp.h>  
  7. #include <string.h>  
  8. #include <stdlib.h>  
  9. #include <linux/sockios.h>  
  10. /** 
  11. ARP高速缓存操作,包含IP地址和硬件地址的映射表 
  12. 操作ARP高速缓存的命令字 SIOCDARP,SIOCGARP,SIOCSARP分别是删除ARP高速缓存的一条记录,获得ARP高速缓存的一条记录和修改ARP高速缓存的一条记录 
  13. struct arpreq{ 
  14.   struct sockaddr arp_pa;//协议地址 
  15.   struct sockaddr arp_ha;//硬件地址 
  16.   int arp_flags;//标记 
  17.   struct sockaddr arp_netmask;//协议地址的子网掩码 
  18.   char arp_dev[16];//查询网络接口的名称 
  19. } 
  20.  
  21. **/  
  22.   
  23. //根据IP地址查找硬件地址  
  24. int main(int argc,char*argv[]){  
  25.     int s;  
  26.     int err;  
  27.     struct arpreq arpreq;  
  28.     struct sockaddr_in *addr=(struct sockaddr_in*)&arpreq.arp_pa;//IP地址  
  29.     s=socket(AF_INET,SOCK_DGRAM,0);  
  30.     if(s<0)  
  31.         perror("socket error");  
  32.   
  33.     addr->sin_family=AF_INET;  
  34.     addr->sin_addr.s_addr=inet_addr(argv[1]);//转换成二进制IP  
  35.     if(addr->sin_addr.s_addr==INADDR_NONE)  
  36.         printf("IP地址格式错误\n");  
  37.       
  38.     strcpy(arpreq.arp_dev,"eth0");  
  39.     err=ioctl(s,SIOCGARP,&arpreq);  
  40.     if(err==-1){  
  41.         perror("arp");  
  42.         return -1;  
  43.     }  
  44.    
  45.     unsigned char* hw=(unsigned char*)&arpreq.arp_ha.sa_data;//硬件地址  
  46.     printf("%s\n",argv[1]);  
  47.     printf("%02x:%02x:%02x:%02x:%02x:%02x\n",hw[0],hw[1],hw[2],hw[3],hw[4],hw[5]);  
  48.     close(s);  
  49.     return 0;  
  50. }  

运行结果:

                                                         

 

       查看网关的MAC。在查看ARP高速缓存时要传入IP地址与接口信息。而获得接口信息要传入接口名ifr_name,如eth0。

总结:

       本文主要介绍了获得网络接口请求信息,获得网卡设备映射属性、配置网络接口、获得ARP高速缓存等。其它ioctl函数还能对操作文件,操作I/O、操作路由等。最后对于网络接口的操作与ARP高速缓存的操作分别给出了实例。

 

参考:

http://b.baidu.com/view/1081282.htm

http://blog.csdn.net/gemmem/article/details/7268533

http://www.cnblogs.com/geneil/archive/2011/12/04/2275372.html

http://decimal.blog.51cto.com/1484476/447630

http://yehubilee.blog.51cto.com/1373999/963740

http://www.embeddedlinux.org.cn/ldd3note/_36.htm

http://blog.csdn.net/ctthuangcheng/article/details/9746939

http://man7.org/linux/man-pages/man2/ioctl.2.htm

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值