
ioctl函数
管理信息系统课程设计-现代远程教育研究
2023年2月19日发(作者:李德胜是谁)【Linux】【驱动】ioctl介绍和应⽤场景
1.什么是ioctl
ioctl是⽤于设备(⽂件或者套接字)控制的公共接⼝,除了读取和写⼊设备之外,⼤部分驱动程序还需要另外⼀种能⼒,即通过设备驱动程
序执⾏各种类型的硬件控制。简单数据传输外,⼤部分设备可以执⾏其他⼀些操作,⽐如,⽤户空间经常会请求设备状态,弹出设备,报告
错误信息,改变波特率或者执⾏⾃破坏,等。这些操作通常通过ioctl⽅法执⾏,该⽅法实现了同名的系统调⽤。
2.1函数原型
在⽤户空间,ioctl系统调⽤原型如下:
#include
intioctl(intd,intrequest,...);
参数中使⽤的…表⽰可变数码的参数,但是在实际系统找中,系统调⽤不会真正使⽤可变数⽬的参数,⽽是必须具有精确定义的原型。
2.2参数说明
该参数是SOCK_DGRAM类型的套接字,⼀般初始化⽅式为:
sock=socket(AF_INET,SOCK_DGRAM,0);
uest
该参数是请求命令编号。这些编号⼀部分是已经编订好的,另外⼀部分是⽤户⾃⼰通过调⽤系统接⼝宏编制的。早期的Linux版本,为
了⽅便程序员创建唯⼀的ioctl命令号,每⼀个命令号划分为多个位字段,Linux第⼀个版本使⽤⼀个16bit的⽆符号整数。⾼8bit是
与设备相关的幻数,地8位是⼀个序列号嘛,在该设备内唯⼀。
已经编码好的命令,可以参考⽂件kernel/Documentation/ioctl/中的说明,下⾯列举出对私有设备的编码:
0x8900-06arch/x86/include/asm/sockios.h
0x890B-DFlinux/sockios.h
0x89E0-EFlinux/ROTOPRIVATErange
0x89E0-EFlinux/PRIVATErange
0x89F0-FFlinux/EVPRIVATErange
对于⽐较新的设备的请求命令编码,或者是⽤户⾃⼰添加的某类设备(⽐如gpio)命令编码,则使⽤32bit的请求码,这32位的编码
有对应的规则:
bitsmeaning
31-3000-noparameters:uses_IOmacro
10-read:_IOR
01-write:_IOW
11-read/write:_IOWR
29-16sizeofarguments
15-8asciicharactersupposedly,uniquetoeachdriver
7-0function
#kernel中提供宏接⼝⽤于计算出唯⼀的命令请求码
#define_IO(type,nr)_IOC(_IOC_NONE,(type),(nr),0)
#define_IOR(type,nr,size)_IOC(_IOC_READ,(type),(nr),(_IOC_TYPECHECK(size)))
#define_IOW(type,nr,size)_IOC(_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define_IOWR(type,nr,size)_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),(_IOC_TYPECHECK(size)))
#define_IOR_BAD(type,nr,size)_IOC(_IOC_READ,(type),(nr),sizeof(size))
#define_IOW_BAD(type,nr,size)_IOC(_IOC_WRITE,(type),(nr),sizeof(size))
#define_IOWR_BAD(type,nr,size)_IOC(_IOC_READ|_IOC_WRITE,(type),(nr),sizeof(size))
常见的16bit的命令编码在⽂件kernelincludelinuxsocketios.h中定义:
/*Socketconfigurationcontrols.*/
#defineSIOCGIFNAME0x8910/*getifacename*/
#defineSIOCSIFLINK0x8911/*setifacechannel*/
#defineSIOCGIFCONF0x8912/*getifacelist*/
#defineSIOCGIFFLAGS0x8913/*getflags*/
#defineSIOCGIFFLAGS0x8913/*getflags*/
#defineSIOCSIFFLAGS0x8914/*setflags*/
#defineSIOCGIFADDR0x8915/*getPAaddress*/
#defineSIOCSIFADDR0x8916/*setPAaddress*/
`
3.可变参数
通常使⽤到的可变参数类型如下:
多个设备配置信息
structifconfifconf_st;/⽤于保存多个配置/
structifconf{
intifc_len;/*sizeofbuffer*/
union{
char__user*ifcu_buf;
structifreq__user*ifcu_req;
}ifc_ifcu;
};
某个接⼝的配置信息
structifreqifr;
structifreq{
#defineIFHWADDRLEN6
union
{
charifrn_name[IFNAMSIZ];/*ifname,e.g."en0"*/
}ifr_ifrn;
union{
structsockaddrifru_addr;
structsockaddrifru_dstaddr;
structsockaddrifru_broadaddr;
structsockaddrifru_netmask;
structsockaddrifru_hwaddr;
shortifru_flags;
intifru_ivalue;
intifru_mtu;
structifmapifru_map;
charifru_slave[IFNAMSIZ];/*Justfitsthesize*/
charifru_newname[IFNAMSIZ];
void__user*ifru_data;
structif_settingsifru_settings;
}ifr_ifru;
};
⽆线设备接⼝信息:
structiwreqiwr;
structiwreq
{
union{
charifrn_name[IFNAMSIZ];/*ifname,e.g."eth0"*/
}ifr_ifrn;
/*Datapart(definedjustabove)*/
unioniwreq_datau;
};
/*------------------------IOCTLREQUEST------------------------*/
/*
*Thisstructuredefinesthepayloadofanioctl,andisused
*below.
*
*Notethatthisstructureshouldfitonthememoryfootprint
*ofiwreq(whichisthesameasifreq),whichmeanamaxsizeof
*16octets=g,pointersmightbe64bitswide...
*Youshouldcheckthiswhenincreasingthestructuresdefined
*aboveinthisfile...
*/
`
unioniwreq_data
{
/*Config-generic*/
charname[IFNAMSIZ];
/*Name:usedtoverifythepresenceofwirelessextensions.
*Nameoftheprotocol/provider…*/
structiw_pointessid;/*Extendednetworkname*/
structiw_paramnwid;/*networkid(ordomain-thecell)*/
structiw_freqfreq;/*frequencyorchannel:
*0-1000=channel
*>1000=frequencyinHz*/
structiw_paramsens;/*signallevelthreshold*/
structiw_parambitrate;/*defaultbitrate*/
structiw_paramtxpower;/*defaulttransmitpower*/
structiw_paramrts;/*RTSthresholdthreshold*/
structiw_paramfrag;/*Fragmentationthreshold*/
__u32mode;/*Operationmode*/
structiw_paramretry;/*Retrylimits&lifetime*/
structiw_pointencoding;/*Encodingstuff:tokens*/
structiw_parampower;/*PMduration/timeout*/
structiw_qualityqual;/*Qualitypartofstatistics*/
structsockaddrap_addr;/*Accesspointaddress*/
structsockaddraddr;/*Destinationaddress(hw/mac)*/
structiw_paramparam;/*Othersmallparameters*/
structiw_pointdata;/*Otherlargeparameters*/`
};
/*
*Foralldatalargerthan16octets,weneedtousea
*pointertomemoryallocatedinuserspace.
*/
structiw_point
{
void/*__user*/*pointer;/*Pointertothedata(inuserspace)*/
__u16length;/*numberoffieldsorsizeinbytes*/
__u16flags;/*Optionalparams*/
};
上⾯的参数中,最常⽤的是:
=FLAG;
r=(void*)(buffer);
=sizeof(buf);
⼀定要注意传⼊buffer的长度⼀定要⼤于等于和驱动中cop_to_user的长度,否则出现段错误。
时间戳
structtimevaludp_arrival;
if(ioctl(usd,SIOCGSTAMP,&udp_arrival)<0)
_ioctl函数
该函数中⽂件kernelnetsocket.c中定义:
staticlongsock_ioctl(structfile*file,unsignedcmd,unsignedlongarg)
{
structsocket*sock;
structsock*sk;
void__user*argp=(void__user*)arg;
intpid,err;
structnet*net;
sock=file->private_data;
sk=sock->sk;
net=sock_net(sk);
printk("%s%dcmd=0x%xrn",__FUNCTION__,__LINE__,cmd);
printk("%s%dSIOCDEVPRIVATE=0x%x,SIOCIWFIRST=0x%x,SIOCIWLAST=0x%xrn",__FUNCTION__,__LINE__,SIOCDEVPRIVATE,SIOCIWFIR
ST,SIOCIWLAST);
if(cmd>=SIOCDEVPRIVATE&&cmd<=(SIOCDEVPRIVATE+15)){/*SIOCDEVPRIVATE=0x8BE0*/
err=dev_ioctl(net,cmd,argp);/*查找设备的ioctl函数*/
}else
#ifdefCONFIG_WEXT_CORE
if(cmd>=SIOCIWFIRST&&cmd<=SIOCIWLAST){/*SIOCIWFIRST=0x8B00,SIOCIWLAST=0x8BFF*/
err=dev_ioctl(net,cmd,argp);/*查找设备的ioctl函数*/
}else
#endif
switch(cmd){
caseFIOSETOWN:
caseSIOCSPGRP:
err=-EFAULT;
if(get_user(pid,(int__user*)argp))
break;
err=f_setown(sock->file,pid,1);
break;
caseFIOGETOWN:
caseSIOCGPGRP:
err=put_user(f_getown(sock->file),
(int__user*)argp);
break;
caseSIOCGIFBR:
caseSIOCSIFBR:
caseSIOCBRADDBR:
caseSIOCBRDELBR:
err=-ENOPKG;
err=-ENOPKG;
if(!br_ioctl_hook)
request_module("bridge");
mutex_lock(&br_ioctl_mutex);
if(br_ioctl_hook)
err=br_ioctl_hook(net,cmd,argp);
mutex_unlock(&br_ioctl_mutex);
break;
caseSIOCGIFVLAN:
caseSIOCSIFVLAN:
err=-ENOPKG;
if(!vlan_ioctl_hook)
request_module("8021q");
mutex_lock(&vlan_ioctl_mutex);
if(vlan_ioctl_hook)
err=vlan_ioctl_hook(net,argp);
mutex_unlock(&vlan_ioctl_mutex);
break;
caseSIOCADDDLCI:
caseSIOCDELDLCI:
err=-ENOPKG;
if(!dlci_ioctl_hook)
request_module("dlci");
mutex_lock(&dlci_ioctl_mutex);
if(dlci_ioctl_hook)
err=dlci_ioctl_hook(cmd,argp);
mutex_unlock(&dlci_ioctl_mutex);
break;
default:
err=sock_do_ioctl(net,sock,cmd,arg);/*调⽤sock的ioctl函数sock->ops->ioctl(sock,cmd,arg)*/
break;
}
returnerr;
}
如果ioctl的命令不在SIOCIWFIRST和SIOCIWLAST范围内,则匹配其他命令,如果匹配不上,则进⼊
default:
err=sock_do_ioctl(net,sock,cmd,arg);
printk("%s%derr=0x%xrn",__FUNCTION__,__LINE__,err);
break;
调⽤sock的ioctl函数,⽽不是设备的ioctl函数。如果ioctl命令编码不在范围内,那么返回错误:
sock_ioctl953cmd=0x8C02
sock_ioctl954SIOCDEVPRIVATE=0x89f0,SIOCIWFIRST=0x8b00,SIOCIWLAST=0x8bff
ioctlerror::Operationnotsupported
添加命令
根据功能需求,有时候需要在现有的命令上添加新ioctl功能接⼝,根据上⾯的描述,有两种情况
第⼀:在现有的命令编码上添加新的接⼝,⽐如在⽆线驱动中要添加⼀些新的命令,考虑到预留的编码范围:
#defineSIOCIWFIRSTPRIV0x8BE0
#defineSIOCIWLASTPRIV0x8BFF
如果每⼀条命令都是⽤⼀个编码,很容易⽤完,所以,⼀般在实现时,使⽤2级编码的⽅法进⾏添加。
⽐如定义#defineMY_PRIV_IOCTL(SIOCIWFIRSTPRIV+0x01)
⽤于添加私有的ioctl命令,那么可以在此基础上添加subcmd,作为多个命令的区分。有两种⽅法:
⼀是通过请求参数中的成员,⽐如=MY_DRIVER_GET_STATION_LIST_INFO;
⼆是通过参数r指向的数据结构变量中定义subcmd进⾏区分不同的命令;
常⽤例⼦
1.获取接⼝的MAC地址:
structifreqifr;
structethhdr*ethh=(structethhdr*)pkt;
memset(&device,0,sizeof(device));
if((_ifindex=if_nametoindex(interface))==0){
return-1;
return-1;
}
if((fd=socket(PF_PACKET,SOCK_RAW,htons(ETH_P_ALL)))<0){
return-1;
}
memset(&ifr,0,sizeof(ifr));
snprintf(_name,sizeof(_name),"%s",interface);
if(ioctl(fd,SIOCGIFHWADDR,&ifr)<0){
close(fd);
return(EXIT_FAILURE);
}
close(fd);
memcpy(ethh->h_source,__data,6);
2.获取⽹络设备序号
structifreqifr;
memset(&ifr,0,sizeof(ifr));
strncpy(_name,ifname,sizeof(_name));/*获取⽹络设备ifname的序号*/
if(-1==ioctl(sockfd,SIOCGIFINDEX,&ifr))
{
if(errbuf!=NULL)
{
perror("ioctlerror");
}
return-1;
}
_ifindex;
3.获取和设置设备的flags
structifreqifr;
memset(&ifr,0,sizeof(ifr));
strncpy(_name,ifname,sizeof(_name));
if(-1==ioctl(sockfd,SIOCGIFFLAGS,&ifr))
{
perror("ioctlerror");
return-1;
}
_setbits(_flags,(IFF_UP|IFF_BROADCAST|IFF_MULTICAST));
_clrbits(_flags,(IFF_ALLMULTI|IFF_PROMISC));
if(-1==ioctl(sockfd,SIOCSIFFLAGS,&ifr))
{
perror(“ioctlerror”);
return-1;
}
return0;
4.读取某个GPIO设备
#defineHARDWARE_GPIO_IOCTL_BASE0x01
#defineHARDWARE_GPIO_IOCTL_CMD1HARDWARE0_GPIO_IOCTL_BASE
#defineHARDWARE_GPIO_MAGIC0xB2
#defineHARDWARE_GPIO_BTN_READ_IOR(HARDWARE_GPIO_MAGIC,HARDWARE_GPIO_IOCTL_CMD1,int)
intfd=open("/dev/hardware_gpio_chrdev",O_RDONLY|O_CREAT);
intval=0;
rt=ioctl(fd,HARDWARE_GPIO_BTN_READ,&val);/*读取某个gpio的状态*/
5.设置⾮阻塞套接字
intsock_set_blocking(constsock_tsock)
{
intrc;
rc=fcntl(sock,F_GETFL,NULL);
if(rc>=0)
{
rc=fcntl(sock,F_SETFL,rc&(~O_NONBLOCK));
rc=fcntl(sock,F_SETFL,rc&(~O_NONBLOCK));
}
returnrc;
}