简介:ioctl有针对IO设备的ioctl,还有网络ioctl,本文就来跟踪下ioctl的系统调用。而由于本文中的IO设备是以Linux设备中使用最多的字符设备为例来跟踪代码的,所以建议先彻读之前写的Linux 字符设备和SPI系列文章;而网络部分不仅跟踪了标准socket控制,而且重点跟踪了无线扩展socket控制。
文章主架构参考:
其中的标准socket控制参考:
linux网络协议栈分析——ioctl的调用流程 linux内核与用户之间的通信方式——虚拟文件系统、ioctl以及netlink
其中的无线扩展socket控制参考:
WIFI设备管理工具iwconfig/iwpriv及对应内核态的实现机制
在linux中,设备大致可分为:字符设备,块设备,和网络接口(字符设备包括那些必须以顺序方式,像字节流一样被访问的设备;如字符终端,串口等。块设备是指那些可以用随机方式,以整块数据为单位来访问的设备,如硬盘等;网络接口,就指通常网卡和协议栈等复杂的网络输入输出服务)。
一、用户态
1.字符设备ioctl
-
int main(int argc,char **argv)
-
{
-
…
-
fd = open(“/dev/spidev1.1”,O_RDWR);
-
…
-
ret = ioctl(fd, SPI_IOC_WR_MAX_SPEED_HZ, &speed);
-
…
-
}
2.标准socket控制ioctl
-
int main(int argc,char **argv)
-
{
-
…
-
sockfd = socket(AF_INET,SOCK_DGRAM,0);
-
…
-
if(ioctl(sockfd,SIOCGIFADDR,&ifr))
-
…
-
}
3.无线扩展socket控制ioctl
-
int main(int argc,char **argv)
-
{
-
…
-
sockfd = socket(AF_INET,SOCK_DGRAM,0);
-
…
-
if(ioctl(sockfd,IEEE80211_IOCTL_STA_INFO,&ifr))
-
…
-
}
二、内核态
进入系统调用。先看个老代码作为辅助参考:
-
/linux/fs/ioctl.c
-
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
-
{
-
struct file * filp;
-
...
-
filp = fget(fd);//通过文件句柄fd来获得需要操作的文件的指针
-
...
-
switch (cmd) {
-
...
-
default:
-
error = -ENOTTY;
-
if (S_ISREG(filp->f_dentry->d_inode->i_mode))//普通文件
-
error = file_ioctl(filp, cmd, arg);
-
else if (filp->f_op && filp->f_op->ioctl)
-
error = filp->f_op->ioctl(filp->f_dentry->d_inode, filp, cmd, arg);
-
}
-
...
-
}
而,我本地openWRT中代码实现:
-
/linux/fs/ioctl.c
-
int do_vfs_ioctl(struct file *filp, unsigned int fd, unsigned int cmd,
-
unsigned long arg)
-
{
-
int error = 0;
-
int __user *argp = (int __user *)arg;
-
struct inode *inode = file_inode(filp);//filp->f_inode
-
-
switch (cmd) {
-
...
-
default:
-
if (S_ISREG(inode->i_mode))
-
error = file_ioctl(filp, cmd, arg);
-
else if (filp->f_op && filp->f_op->unlocked_ioctl)
-
error = filp->f_op->unlocked_ioctl(filp, cmd, arg);
-
break;
-
}
-
return error;
-
}
-
SYSCALL_DEFINE3(ioctl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
-
{
-
int error;
-
struct fd f = fdget(fd);
-
...
-
error = security_file_ioctl(f.file, cmd, arg);
-
if (!error)
-
error = do_vfs_ioctl(f.file, fd, cmd, arg);
-
fdput(f);
-
return error;
-
}
这里注释下关健宏定义
-
include/linux/stat.h
-
#define S_ISLNK(m) (((m) & S_IFMT) == S_IFLNK)//符号连接文件
-
#define S_ISREG(m) (((m) & S_IFMT) == S_IFREG)//普通文件
-
#define S_ISDIR(m) (((m) & S_IFMT) == S_IFDIR)//目录文件
-
#define S_ISCHR(m) (((m) & S_IFMT) == S_IFCHR)//字符设备文件
-
#define S_ISBLK(m) (((m) & S_IFMT) == S_IFBLK)//块设备文件
-
#define S_ISFIFO(m) (((m) & S_IFMT) == S_IFIFO)//管道文件
-
#define S_ISSOCK(m) (((m) & S_IFMT) == S_IFSOCK)//socket套接字文件(linux内核把socket套接字当作文件来处理,内核在创建socket套接字时,为套接字分配文件id以及生成与id对应的文件节点,节点的i_mode域是代表文件类型的位域标志字段)
接下来我们就是关健的,filp->f_op->ioctl是如何找到和区分:字符设备ioctl、
标准socket控制ioctl、无线扩展socket控制ioctl的呢?这个问题我将在下一文章顺着讲解。本文直捣黄龙,直接看调用关健点:
1.字符设备ioctl
-
struct file {
-
...
-
struct file operations *f_op;
-
...
-
}
-
static const struct file_operations spidev_fops = {
-
...
-
.open = spidev_open;
-
.unlocked_iotctl = spidev_ioctl;
-
...
-
}
而上面的spidev_fops是来自:(怎么来的看下一文章)
-
struct cdev {
-
...
-
const struct file operations *ops;
-
...
-
}
-
static const struct file_operations spidev_fops = {//同上
-
...
-
.open = spidev_open;
-
.unlocked_iotctl = spidev_ioctl;
-
...
-
}
2.socket控制ioctl
-
struct file {
-
...
-
struct file operations *f_op;
-
...
-
}
-
static const struct file_operations socket_file_ops = {
-
...
-
.unlocked_ioctl = sock_ioctl;
-
...
-
};
(而上面的socket_file_ops是怎么来的
看下一文章)
接着看,socket_ioctl,先看个老代码:
-
/linux/net/socket.c
-
int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd, unsigned long arg)
-
{
-
struct socket *sock;
-
...
-
sock = socki_lookup(inode);//通过inode找到套接字对应的socket结构
-
err = sock->ops->ioctl(sock, cmd, arg);
-
...
-
}
而我本地包含Wireless Externsions的代码实现:
-
/linux/net/socket.c
-
static long sock_ioctl(struct file *file, unsigned cmd, unsigned long arg)
-
{
-
struct socket *sock;
-
struct sock *sk;
-
void __user *argp = (void __user *)arg;
-
int pid, err;
-
struct net *net;
-
-
sock = file->private_data;
-
sk = sock->sk;
-
net = sock_net(sk);
-
if (cmd >= SIOCDEVPRIVATE && cmd <= (SIOCDEVPRIVATE + 15)) {
-
err = dev_ioctl(net, cmd, argp);
-
} else
-
#ifdef CONFIG_WEXT_CORE
-
if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) {
-
err = dev_ioctl(net, cmd, argp);
-
} else
-
#endif
-
switch (cmd) {
-
...
-
default:
-
err = sock->ops->ioctl(sock, cmd, arg);
-
-
/*
-
* If this ioctl is unknown try to hand it down
-
* to the NIC driver.
-
*/
-
if (err == -ENOIOCTLCMD)
-
err = dev_ioctl(net, cmd, argp);//whj_note:这里应该不会跑到
-
break;
-
}
-
return err;
-
}
2.1.
无线扩展socket控制ioctl
-
/linux/net/core/dev_ioctl.c
-
int dev_ioctl(struct net *net, unsigned int cmd, void __user *arg)//所有的和接口相关的ioctl请求(SIOCxIFyyyy 和 SIOCDEVPRIVATE)将会调用dev_ioctl(),而实际的动作将由dev_ifsioc()来实现。
-
{
-
struct ifreq ifr;
-
...
-
switch (cmd) {
-
...
-
/*
-
* Unknown or private ioctl.
-
*/
-
default:
-
if (cmd == SIOCWANDEV ||
-
cmd == SIOCGHWTSTAMP ||
-
(cmd >= SIOCDEVPRIVATE &&
-
cmd <= SIOCDEVPRIVATE + 15)) {
-
dev_load(net, ifr.ifr_name);
-
rtnl_lock();
-
ret = dev_ifsioc(net, &ifr, cmd);
-
rtnl_unlock();
-
if (!ret && copy_to_user(arg, &ifr,
-
sizeof(struct ifreq)))
-
ret = -EFAULT;
-
return ret;
-
}
-
/* Take care of Wireless Extensions */
-
if (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST)
-
return wext_handle_ioctl(net, &ifr, cmd, arg);
-
return -ENOTTY;
-
}
-
}
这里注释下关健宏定义:
-
/linux/user_headers/include/linux/sockios.h
-
/* Linux-specific socket ioctls */
-
#define SIOCINQ FIONREAD
-
#define SIOCOUTQ TIOCOUTQ /* output queue size (not sent + not acked) */
-
-
/* Routing table calls. */
-
#define SIOCADDRT 0x890B /* add routing table entry */
-
#define SIOCDELRT 0x890C /* delete routing table entry */
-
#define SIOCRTMSG 0x890D /* call to routing system */
-
-
/* Socket configuration controls. */
-
#define SIOCGIFNAME 0x8910 /* get iface name */
-
#define SIOCSIFLINK 0x8911 /* set iface channel */
-
#define SIOCGIFCONF 0x8912 /* get iface list */
-
#define SIOCGIFFLAGS 0x8913 /* get flags */
-
#define SIOCSIFFLAGS 0x8914 /* set flags */
-
...
-
/* ARP cache control calls. */
-
/* 0x8950 - 0x8952 * obsolete calls, don't re-use */
-
#define SIOCDARP 0x8953 /* delete ARP table entry */
-
#define SIOCGARP 0x8954 /* get ARP table entry */
-
#define SIOCSARP 0x8955 /* set ARP table entry */
-
-
/* RARP cache control calls. */
-
#define SIOCDRARP 0x8960 /* delete RARP table entry */
-
#define SIOCGRARP 0x8961 /* get RARP table entry */
-
#define SIOCSRARP 0x8962 /* set RARP table entry */
-
-
/* Driver configuration calls */
-
-
#define SIOCGIFMAP 0x8970 /* Get device parameters */
-
#define SIOCSIFMAP 0x8971 /* Set device parameters */
-
-
/* DLCI configuration calls */
-
-
#define SIOCADDDLCI 0x8980 /* Create new DLCI device */
-
#define SIOCDELDLCI 0x8981 /* Delete DLCI device */
-
-
#define SIOCGIFVLAN 0x8982 /* 802.1Q VLAN support */
-
#define SIOCSIFVLAN 0x8983 /* Set 802.1Q VLAN options */
-
-
/* bonding calls */
-
-
#define SIOCBONDENSLAVE 0x8990 /* enslave a device to the bond */
-
#define SIOCBONDRELEASE 0x8991 /* release a slave from the bond*/
-
#define SIOCBONDSETHWADDR 0x8992 /* set the hw addr of the bond */
-
#define SIOCBONDSLAVEINFOQUERY 0x8993 /* rtn info about slave state */
-
#define SIOCBONDINFOQUERY 0x8994 /* rtn info about bond state */
-
#define SIOCBONDCHANGEACTIVE 0x8995 /* update to a new active slave */
-
-
/* bridge calls */
-
#define SIOCBRADDBR 0x89a0 /* create new bridge device */
-
#define SIOCBRDELBR 0x89a1 /* remove bridge device */
-
#define SIOCBRADDIF 0x89a2 /* add interface to bridge */
-
#define SIOCBRDELIF 0x89a3 /* remove interface from bridge */
-
-
/* hardware time stamping: parameters in linux/net_tstamp.h */
-
#define SIOCSHWTSTAMP 0x89b0 /* set and get config */
-
#define SIOCGHWTSTAMP 0x89b1 /* get config */
-
-
/* Device private ioctl calls */
-
-
/*
-
* These 16 ioctls are available to devices via the do_ioctl() device
-
* vector. Each device should include this file and redefine these names
-
* as their own. Because these are device dependent it is a good idea
-
* _NOT_ to issue them to random objects and hope.
-
*
-
* THESE IOCTLS ARE _DEPRECATED_ AND WILL DISAPPEAR IN 2.5.X -DaveM
-
*/
-
-
#define SIOCDEVPRIVATE 0x89F0 /* to 89FF */
-
-
/*
-
* These 16 ioctl calls are protocol private
-
*/
-
-
#define SIOCPROTOPRIVATE 0x89E0 /* to 89EF */
2.1.1.
-
/linux/net/core/dev_ioctl.c
-
/*
-
* Perform the SIOCxIFxxx calls, inside rtnl_lock()
-
*/
-
static int dev_ifsioc(struct net *net, struct ifreq *ifr, unsigned int cmd)
-
{
-
int err;
-
struct net_device *dev = __dev_get_by_name(net, ifr->ifr_name);//找到与ifr->ifr_name相匹配的设备结构
-
-
if (!dev)
-
return -ENODEV;
-
-
switch (cmd) {
-
...
-
/*
-
* Unknown or private ioctl
-
*/
-
default:
-
if ((cmd >= SIOCDEVPRIVATE &&
-
cmd <= SIOCDEVPRIVATE + 15) ||
-
cmd == SIOCBONDENSLAVE ||
-
cmd == SIOCBONDRELEASE ||
-
cmd == SIOCBONDSETHWADDR ||
-
cmd == SIOCBONDSLAVEINFOQUERY ||
-
cmd == SIOCBONDINFOQUERY ||
-
cmd == SIOCBONDCHANGEACTIVE ||
-
cmd == SIOCGMIIPHY ||
-
cmd == SIOCGMIIREG ||
-
cmd == SIOCSMIIREG ||
-
cmd == SIOCBRADDIF ||
-
cmd == SIOCBRDELIF ||
-
cmd == SIOCSHWTSTAMP ||
-
cmd == SIOCGHWTSTAMP ||
-
cmd == SIOCWANDEV) {
-
err = -EOPNOTSUPP;
-
if (dev->netdev_ops->ndo_do_ioctl) {
-
if (netif_device_present(dev))
-
err = dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);
-
else
-
err = -ENODEV;
-
}
-
} else
-
err = -EINVAL;
-
-
}
-
return err;
-
}
这里注释下关健结构的定义:
-
/linux/user_headers/include/linux/if.h
-
struct ifreq{}//PS:iwreq结构体,其对应的普通数据结构类型是ifreq。ifreq结构体专门用于往socket句柄传递ioctl控制参数
-
-
/linux/user_headers/include/linux/netdevice.h
-
struct net_device{}
2.1.2.
-
int wext_handle_ioctl(struct net *net, struct ifreq *ifr, unsigned int cmd,
-
void __user *arg)
-
{
-
struct iw_request_info info = { .cmd = cmd, .flags = 0 };
-
int ret;
-
-
ret = wext_ioctl_dispatch(net, ifr, cmd, &info,
-
ioctl_standard_call,
-
ioctl_private_call);
-
if (ret >= 0 &&
-
IW_IS_GET(cmd) &&
-
copy_to_user(arg, ifr, sizeof(struct iwreq)))
-
return -EFAULT;
-
-
return ret;
-
}
-
static int wext_ioctl_dispatch(struct net *net, struct ifreq *ifr,
-
unsigned int cmd, struct iw_request_info *info,
-
wext_ioctl_func standard,
-
wext_ioctl_func private)
-
{
-
int ret = wext_permission_check(cmd);
-
-
if (ret)
-
return ret;
-
-
dev_load(net, ifr->ifr_name);
-
rtnl_lock();
-
ret = wireless_process_ioctl(net, ifr, cmd, info, standard, private);
-
rtnl_unlock();
-
-
return ret;
-
}
-
static int wireless_process_ioctl(struct net *net, struct ifreq *ifr,
-
unsigned int cmd,
-
struct iw_request_info *info,
-
wext_ioctl_func standard,
-
wext_ioctl_func private)
-
{
-
struct iwreq *iwr = (struct iwreq *) ifr;
-
struct net_device *dev;
-
iw_handler handler;
-
-
/* Permissions are already checked in dev_ioctl() before calling us.
-
* The copy_to/from_user() of ifr is also dealt with in there */
-
-
/* Make sure the device exist */
-
if ((dev = __dev_get_by_name(net, ifr->ifr_name)) == NULL)//找到与ifr->ifr_name相匹配的设备结构
-
return -ENODEV;
-
-
/* A bunch of special cases, then the generic case...
-
* Note that 'cmd' is already filtered in dev_ioctl() with
-
* (cmd >= SIOCIWFIRST && cmd <= SIOCIWLAST) */
-
if (cmd == SIOCGIWSTATS)
-
return standard(dev, iwr, cmd, info,
-
&iw_handler_get_iwstats);
-
-
#ifdef CONFIG_WEXT_PRIV
-
if (cmd == SIOCGIWPRIV && dev->wireless_handlers)
-
return standard(dev, iwr, cmd, info,
-
iw_handler_get_private);
-
#endif
-
-
/* Basic check */
-
if (!netif_device_present(dev))
-
return -ENODEV;
-
-
/* New driver API : try to find the handler */
-
handler = get_handler(dev, cmd);
-
if (handler) {
-
/* Standard and private are not the same */
-
if (cmd < SIOCIWFIRSTPRIV)
-
return standard(dev, iwr, cmd, info, handler);
-
else if (private)
-
return private(dev, iwr, cmd, info, handler);
-
}
-
/* Old driver API : call driver ioctl handler */
-
if (dev->netdev_ops->ndo_do_ioctl) {//whj_note:这里应该不会跑到
-
#ifdef CONFIG_COMPAT
-
if (info->flags & IW_REQUEST_FLAG_COMPAT) {
-
int ret = 0;
-
struct iwreq iwr_lcl;
-
struct compat_iw_point *iwp_compat = (void *) &iwr->u.data;
-
-
memcpy(&iwr_lcl, iwr, sizeof(struct iwreq));
-
iwr_lcl.u.data.pointer = compat_ptr(iwp_compat->pointer);
-
iwr_lcl.u.data.length = iwp_compat->length;
-
iwr_lcl.u.data.flags = iwp_compat->flags;
-
-
ret = dev->netdev_ops->ndo_do_ioctl(dev, (void *) &iwr_lcl, cmd);
-
-
iwp_compat->pointer = ptr_to_compat(iwr_lcl.u.data.pointer);
-
iwp_compat->length = iwr_lcl.u.data.length;
-
iwp_compat->flags = iwr_lcl.u.data.flags;
-
-
return ret;
-
} else
-
#endif
-
return dev->netdev_ops->ndo_do_ioctl(dev, ifr, cmd);
-
}
-
return -EOPNOTSUPP;
-
}
-
static iw_handler get_handler(struct net_device *dev, unsigned int cmd)
-
{
-
/* Don't "optimise" the following variable, it will crash */
-
unsigned int index; /* *MUST* be unsigned */
-
const struct iw_handler_def *handlers = NULL;
-
-
#ifdef CONFIG_CFG80211_WEXT
-
if (dev->ieee80211_ptr && dev->ieee80211_ptr->wiphy)
-
handlers = dev->ieee80211_ptr->wiphy->wext;
-
#endif
-
#ifdef CONFIG_WIRELESS_EXT
-
if (dev->wireless_handlers)
-
handlers = dev->wireless_handlers;
-
#endif
-
-
if (!handlers)
-
return NULL;
-
-
/* Try as a standard command */
-
index = IW_IOCTL_IDX(cmd);
-
if (index < handlers->num_standard)
-
return handlers->standard[index];
-
-
#ifdef CONFIG_WEXT_PRIV
-
/* Try as a private command */
-
index = cmd - SIOCIWFIRSTPRIV;
-
if (index < handlers->num_private)
-
return handlers->private[index];
-
#endif
-
-
/* Not found */
-
return NULL;
-
}
2.2.
标准socket控制ioctl
而上面的sock->ops->ioctl是来自:(怎么来的看下一文章的第二章节)
-
struct socket {
-
...
-
const struct proto_ops *ops;
-
...
-
}
-
const struct proto_ops inet_stream_ops = {
-
...
-
.ioctl = inet_ioctl;
-
...
-
}
-
EXPORT_SYMBOL(inet_stream_ops);
接着看inet_ioctl:
-
/linux/net/ipv4/af_inet.c
-
static int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
-
{
-
...
-
switch(cmd)
-
{
-
...
-
case SIOCADDRT:
-
case SIOCDELRT:
-
case SIOCRTMSG:
-
return(ip_rt_ioctl(cmd,(void *) arg));//IP路由配置
-
case SIOCDARP:
-
case SIOCGARP:
-
case SIOCSARP:
-
return(arp_ioctl(cmd,(void *) arg));//arp配置
-
case SIOCGIFADDR:
-
case SIOCSIFADDR:
-
case SIOCGIFBRDADDR:
-
case SIOCSIFBRDADDR:
-
case SIOCGIFNETMASK:
-
case SIOCSIFNETMASK:
-
case SIOCGIFDSTADDR:
-
case SIOCSIFDSTADDR:
-
case SIOCSIFPFLAGS:
-
case SIOCGIFPFLAGS:
-
case SIOCSIFFLAGS:
-
return(devinet_ioctl(cmd,(void *) arg));//网络接口相关配置(linux内核自带的ifconfig的很多处理都是通过这里实现的)
-
...
-
}
-
...
-
}
三、关于unlocked_ioctl()与compat_ioctl()取代ioctl()
1.选自
ioctl()分析——从用户空间到设备驱动,其中有如下片段直接选用:
在新版内核中,unlocked_ioctl()与compat_ioctl()取代了ioctl()。unlocked_ioctl(),顾名思义,应该在无大内核锁(BKL)的情况下调用;compat_ioctl(),compat全称compatible(兼容的),主要目的是为64位系统提供32位ioctl的兼容方法,也是在无大内核锁的情况下调用。在《Linux Kernel Development》中对两种ioctl方法有详细的解说。
tips:在字符设备驱动开发中,一般情况下只要实现unlocked_ioctl()即可,因为在vfs层的代码是直接调用unlocked_ioctl()。
2.关于
unlocked_ioctl()取代ioctl()的历史由来,可见对于struct file_operations中ioctl消失的学习笔记
四、课后测试
如果你真的看懂了本篇关于“设备ioctl”和“网络ioctl”,你对网上的两篇帖子中的话,毫无疑问:
filp->f_op->ioctl()函数,调用设备对应的ioctl函数;对于使用socket创建文件描述符,它应该调用sock_ioctl()函数
/*如果filp本身是一个设备,则执行filp->f_op->ioctl()函数,对设备进行ioctl函数操作,该指针在初始化时就已经指向了设备函数接口中的ioctl函数,因此在设备初始化时,只要向内核提交了file_operations{}结构或block_device_operations{},其中的ioctl函数就会被调用到*/
另外,附上我自己画的函数调用关系图,和如上所参考文章中的函数调用关系图:
1.
2.
和
阅读(9889) | 评论(0) | 转发(0) |