Chinaunix首页 | 论坛 | 博客
  • 博客访问: 581963
  • 博文数量: 146
  • 博客积分: 5251
  • 博客等级: 大校
  • 技术积分: 1767
  • 用 户 组: 普通用户
  • 注册时间: 2006-11-10 15:58
文章分类
文章存档

2010年(12)

2008年(129)

2007年(5)

我的朋友

分类: LINUX

2008-11-17 14:13:16

一、 什么是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,就知
      道用户程序的ioctl是怎么和驱动程序中的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控制都是通过这一部分的代码实现的。
 

IOCTL向内核传递参数

一、应用层

uint16 data16;

if ((fd = socket(AF_INET, SOCK_STREAM, 0)) < 0)

{

printf("socket failed\n\r");

}

if(ioctl(fd, SIOCSIFVLAN_PVID_PRI, &data16) < 0)

{

printf("ioctl pvid failed\n\r");

}

二、linux内核

1、 在sockios.h中定义

#define SIOCSIFVLAN_PVID_PRI 0x8985 /* Set 802.1Q VLAN pvid */

2、在af_inet.c中

添加

extern int VLAN1QEN(unsigned int ,void *arg);

在inet_ioctl()函数中添加

case SIOCSIFVLAN_PVID_PRI:

return VLAN1QEN(cmd, arg);

3、另外定义:

static unsigned int VLAN_PVID_PRI = 0;

int VLAN1QEN(unsigned int cmd,void *arg)

{

unsigned int data;

if (copy_from_user(&data, arg, sizeof(int)))

return -EFAULT;

switch (cmd) {

case SIOCSIFVLAN_PVID_PRI:

VLAN_PVID_PRI = data;

break;

default:

return -EINVAL;

}

}

//end

在copy_from_user()和copy_to_user()的过程中,使用了arg这个参数指向用户空间某命令参数,在用户空间使用ioctl(int fd,int cmd, *bcf)想内核空间传递这个参数

这里,找不到bcf和arg的联系。

这里我把我的理解写在这里:

首先,ioctl的功能我就不说了,网上比较多,大致是用来控制IO的。

其次,第一个参数fd是一个文件描述符,我们这里是建立的一个套接字描述符

再次,第二个参数,是在sockios.h中定义的一个32位描述符,我们也可以在这里添加新的类型,用来扩展实际需求。

最后,第三个参数,是一个指针,用来指向某些我们实际应用中的参数。

在af_inet.c中的inet_ioctl()函数中的switch——case中,对上面第二个参数分别进行了描述。

例如需要的是这样的一个类型:

ioctl( fd, SIOCSIFBR, &bcf )

在inet_ioctl()函数中如下表示: Linux/net/ipv4/af_inet.c

int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)

switch (cmd)

{……

case SIOCGIFBR:
case SIOCSIFBR:
#if defined(CONFIG_BRIDGE) || defined(CONFIG_BRIDGE_MODULE)
#ifdef CONFIG_KMOD
if (br_ioctl_hook == NULL)
request_module("bridge");
#endif
if (br_ioctl_hook != NULL)
return br_ioctl_hook(arg);
879 #endif
return -ENOPKG;
……

}//switch end here

这里inet_ioctl()中的第三个参数arg,正好对应在ioctl中的的三个参数&bcf,其实,inet_ioctl属于ioctl在linux内核中的网络设备ioctl的底层函数,用来处理网络设备的一些IO操作。

上面的一小段程序显示,我们用户层ioctl函数中的第三个参数对应在br_ioctl_hook(arg)中的arg上。

而在我们的另外一个函数中定义了一个函数叫br_ioctl(arg)函数,并且其中使用了copy_to_user 和copy_from_user 用来将用户层中ioctl函数传下来的 命令在内核中进行相应的处理。最后,用一句:br_ioctl_hook = br_ioctl 将这个函数送进inet_ioctl中。

这里才明白ioctl —— br_ioctl之间的关系

阅读(2377) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~