文章主架构参考:Linux内核IOCTL网络控制框架实现实例分析
其中的标准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);
- …
- }
点击(此处)折叠或打开
- int main(int argc,char **argv)
- {
- …
- sockfd = socket(AF_INET,SOCK_DGRAM,0);
- …
- if(ioctl(sockfd,SIOCGIFADDR,&ifr))
- …
- }
点击(此处)折叠或打开
- 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);
- }
- ...
- }
点击(此处)折叠或打开
- /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域是代表文件类型的位域标志字段)
1.字符设备ioctl
点击(此处)折叠或打开
- struct file {
- ...
- struct file operations *f_op;
- ...
- }
- static const struct file_operations spidev_fops = {
- ...
- .open = spidev_open;
- .unlocked_iotctl = spidev_ioctl;
- ...
- }
点击(此处)折叠或打开
- struct cdev {
- ...
- const struct file operations *ops;
- ...
- }
- static const struct file_operations spidev_fops = {//同上
- ...
- .open = spidev_open;
- .unlocked_iotctl = spidev_ioctl;
- ...
- }
点击(此处)折叠或打开
- struct file {
- ...
- struct file operations *f_op;
- ...
- }
- static const struct file_operations socket_file_ops = {
- ...
- .unlocked_ioctl = sock_ioctl;
- ...
- };
接着看,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);
- ...
- }
点击(此处)折叠或打开
- /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;
- }
点击(此处)折叠或打开
- /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 */
点击(此处)折叠或打开
- /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{}
点击(此处)折叠或打开
- 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;
- }
而上面的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);
点击(此处)折叠或打开
- /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的很多处理都是通过这里实现的)
- ...
- }
- ...
- }
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.