家具网站asp,门户网站网站制作,广州信息流推广公司排名,东道设计学院关于Linux下的icotl函数 最近接触android开发#xff0c;因为有时间所以就关注了下android的源码#xff0c;在跟踪源码过程中到最后都会遇到icotl函数#xff0c;虽然在Symbian中曾经遇到过RSocket的icotl函数#xff0c;但是当时没有细究#xff0c;今天有时间就搜索了下… 关于Linux下的icotl函数 最近接触android开发因为有时间所以就关注了下android的源码在跟踪源码过程中到最后都会遇到icotl函数虽然在Symbian中曾经遇到过RSocket的icotl函数但是当时没有细究今天有时间就搜索了下原来这个函数是跟驱动相关的。下面这篇文章在很多博客网站都能看到到底是谁写的就不细究了但是他让我了解了这个函数的由来。下面就是转帖。 我这里说的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就知道用户程序的ioctl是怎么和驱动程序中的ioctl实现联系在一起的了。 我这里说一个大概思路因为我觉得《Linux设备驱动程序》这本书已经说的非常清楚了但是得化一些时间来看。 在驱动程序中实现的ioctl函数体内实际上是有一个switch{case}结构每一个case对应一个命令码做出一些相应的操作。怎么实现这些操作这是每一个程序员自己的事情因为设备都是特定的这里也没法说。关键在于怎么样组织命令码因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。 命令码的组织是有一些讲究的因为我们一定要做到命令和设备是一一对应的这样才不会将正确的命令发给错误的设备或者是把错误的命令发给正确的设备或者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生而当程序员发现了这些奇怪的事情的时候再来调试程序查找错误那将是非常困难的事情。 所以在Linux核心中是这样定义一个命令码的 设备类型 序列号 方向 数据尺寸 8 bit 8 bit 2 bit 8~14bit 这样一来一个命令就变成了一个整数形式的命令码。但是命令码非常的不直观所以Linux Kernel中提供了一些宏这些宏可根据便于理解的字符串生成命令码或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。 这些宏我就不在这里解释了具体的形式请读者察看Linux核心源代码中的和文件里给除了这些宏完整的定义。这里我只多说一个地方那就是幻数。 幻数是一个字母数据长度也是8所以就用一个特定的字母来标明设备类型这和用一个数字是一样的只是更加利于记忆和理解。就是这样再没有更复杂的了。 更多的说了也没有读者还是看一看源代码吧推荐各位阅读《Linux 设备驱动程序》所带源代码中的short一例因为它比较短小功能比较简单可以看明白ioctl的功能和细节。 四、 cmd参数如何得出 这里确实要说一说cmd参数在用户程序端由一些宏根据设备类型、序列号、传送方向、数据尺寸等生成这个整数通过系统调用传递到内核中的驱动程序再由驱动程序使用解码宏从这个整数中得到设备的类型、序列号、传送方向、数据尺寸等信息然后通过switch{case}结构进行相应的操作。 要透彻理解只能是通过阅读源代码我这篇文章实际上只是一个引子。Cmd参数的组织还是比较复杂的我认为要搞熟它还是得花不少时间的但是这是值得的驱动程序中最难的是对中断的理解。 五、 小结 ioctl其实没有什么很难的东西需要理解关键是理解cmd命令码是怎么在用户程序里生成并在驱动程序里解析的程序员最主要的工作量在switch{case}结构中因为对设备的I/O控制都是通过这一部分的代码实现的。 通过上述转帖大概知道了ioctl的来龙去脉如果能来几个实例可能相对来说更能让人接受些这不也有人找了些例子具体如下例转帖所述。 linux系统ioctl使用示例 These were writed and collected by kf701, you can use and modify them but NO WARRANTY. Contact with me : kf_70121cn.com 程序1检测接口的 inet_addr,netmask,broad_addr #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 void usage() { printf(usage : ipconfig interface \n); exit(0); } int main(int argc,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(ioctl error),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(ioctl error),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(ioctl error),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 typedef unsigned short u16; typedef unsigned int u32; typedef unsigned char u8; #include linux/ethtool.h #include linux/sockios.h int detect_mii(int skfd, char *ifname) { struct ifreq ifr; u16 *data, mii_val; unsigned phy_id; /* Get the vitals 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); } int detect_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_GLINK failed: %s\n, strerror(errno)); return 2; } return (edata.data ? 0 : 1); } int main(int argc, char **argv) { int skfd -1; char *ifname; int retval; if( argv[1] ) ifname argv[1]; else ifname eth0; /* Open a socket. */ if (( skfd socket( AF_INET, SOCK_DGRAM, 0 ) ) 0 ) { printf(socket error\n); exit(-1); } retval detect_ethtool(skfd, ifname); if (retval 2) retval detect_mii(skfd, ifname); close(skfd); if (retval 2) printf(Could not determine status\n); if (retval 1) printf(Link down\n); if (retval 0) printf(Link up\n); return retval; } 程序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 #define LINKTEST_GLINK 0x0000000a struct linktest_value { unsigned int cmd; unsigned int data; }; static void usage(const char * pname) { fprintf(stderr, usage: %s device\n, pname); fprintf(stderr, returns: \n); fprintf(stderr, \t 0: link detected\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(const char * devname) { struct ifreq ifr; struct linktest_value edata; int fd; /* setup our control structures. */ memset(ifr, 0, sizeof(ifr)); strcpy(ifr.ifr_name, devname); /* open control socket. */ fdsocket(AF_INET, SOCK_DGRAM, 0); if(fd 0 ) { return -ECOMM; } errno0; 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 { errnoENONET; } } perror(linktest); return errno; } int main(int argc, 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 #define? BASE_VALUE 257 int main(int argc,char *argv[]) { int mixer_fd0; char *names[SOUND_MIXER_NRDEVICES]SOUND_DEVICE_LABELS; int value,i; printf(\nusage:%s dev_no.[0..24] value[0..100]\n\n,argv[0]); printf(eg. %s 0 100\n,argv[0]); printf( will change the volume to MAX volume.\n\n); printf(The dev_no. are as below:\n); for (i0;iSOUND_MIXER_NRDEVICES;i) { if (i%30) printf(\n); printf(%s:%d\t\t,names[i],i); } printf(\n\n); if (argc3) exit(1); if ((mixer_fd open(/dev/mixer,O_RDWR))) { printf(Mixer opened successfully,working...\n); valueBASE_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(cant open /dev/mixer error....\n); exit(0); } 原文例子代码前面有段文字太过冗长了我也没去看因为看了下第一篇转帖和例子代码差不离就满足我对icotl函数理解的需要的但是转帖还是要转完整顺便把原文的长篇文转帖如下以后有需要也可以来看看。 一般的说,用户空间的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命令和SIOCDEVPRIVATE15的命令参数全部交给了默认操作,这些都是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。 转载于:https://www.cnblogs.com/franksunny/archive/2011/10/20/2218984.html