Linux 系统资源获取——(二)iotcl使用
LINUX下IOTCL使用
近期在linux下获取系统网络信息的开发过程中看到的使用方法,简单记录iotcl的定义及其在获取网络信息中的用法,操作环境为Ubuntu18.04
什么是IOTCL
ioctl是设备驱动程序中对设备的I/O通道进行管理的函数。所谓对I/O通道进行管理,就是对设备的一些特性进行控制,例如串口的传输波特率、马达的转速等等。它的调用函数如下:
int ioctl(int fd, ind cmd, …);
其中fd
是用户程序打开设备时使用open
函数返回的文件标示符,cmd
是用户程序对设备的控制命令,至于后面的省略号,那是一些补充参数,一般最多一个,这个参数的有无和cmd的意义相关。
ioctl函数是文件结构中的一个属性分量,就是说如果你的驱动程序提供了对ioctl的支持,用户就可以在用户程序中使用ioctl函数来控制设备的I/O通道。
背景
简单来说,操作系统底下分为用户态和内核态,安全起见用户无法直接访问内核,只能通过给定的方式进行通信,其中系统调用是较为常见的一种,其余的还有异常和外设中断,系统调用可以理解为内核提供的一个接口。一个Ioctl接口是一个独立的系统调用,通过它用户空间可以跟设备驱动沟通。对设备驱动的请求是一个以设备和请求号码为参数的Ioctl调用,如此内核就允许用户空间访问设备驱动进而访问设备而不需要了解具体的设备细节,同时也不需要一大堆针对不同设备的系统调用。下面是从百度百科复制来的
传统的操作系统可以分成两层,用户层和内核层。应用程序代码比如编辑器,处于用户层,然而系统底层程序,比如网络栈,处于内核层。内核代码处理敏感资源同时在不同应用程序中间提供了安全且可信的隔离,出于此,操作系统要阻止用户态的程序直接访问内核资源。用户空间的程序通常发出一个给内核的请求,该请求称为系统调用,它的实现代码在内核层。系统调用采取“系统调用增量”的方式,用递增的序号指示系统调用。举个例子,exit()可能是1,write()可能是4。系统调用增量能通过这样的不同数值找到对应的被请求的内核函数,传统的操作系统通常用这种方式给用户空间提供了上百个系统调用。操作系统提供了内核访问标准外部设备的系统调用,因为大多数硬件设备只能够在内核空间内直接寻址,但是当访问非标准硬件设备这些系统调用显得不合适,有时候用户模式可能需要直接访问设备,比如,一个系统管理员可能要修改网卡的配置。现代操作系统提供了各种各样设备的支持,有一些设备可能没有被内核设计者考虑到,如此一来提供一个这样的系统调用来使用设备就变得不可能了。 为了解决这个问题,内核被设计成可扩展的,可以加入一个称为设备驱动的模块,驱动的代码允许在内核空间运行而且可以对设备直接寻址。一个Ioctl接口是一个独立的系统调用,通过它用户空间可以跟设备驱动沟通。对设备驱动的请求是一个以设备和请求号码为参数的Ioctl调用,如此内核就允许用户空间访问设备驱动进而访问设备而不需要了解具体的设备细节,同时也不需要一大堆针对不同设备的系统调用。
ioctl的实现
在网上看到的简要说法,未做更深解读,仅copy供简要理解
在驱动程序中实现的ioctl函数体内,实际上是有一个switch{case}结构,每一个case对应一个命令码,做出一些相应的操作。怎么实现这些操作,这是每一个程序员自己的事情。因为设备都是特定的,这里也没法说。关键在于怎样组织命令码,因为在ioctl中命令码是唯一联系用户程序命令和驱动程序支持的途径。 命令码的组织是有一些讲究的,因为我们一定要做到命令和设备是一一对应的,这样才不会将正确的命令发给错误的设备,或者是把错误的命令发给正确的设备,或 者是把错误的命令发给错误的设备。这些错误都会导致不可预料的事情发生,而当程序员发现了这些奇怪的事情的时候,再来调试程序查找错误,那将是非常困难的 事情。所以在Linux核心中是这样定义一个命令码的:
设备类型 | 序列号 | 方向 | 数据尺寸 |
---|---|---|---|
8 bit | 8 bit | 2 bit | 8~14 bit |
这样一来,一个命令就变成了一个整数形式的命令码;但是命令码非常的不直观,所以Linux Kernel中提供了一些宏。这些宏可根据便于理解的字符串生成命令码,或者是从命令码得到一些用户可以理解的字符串以标明这个命令对应的设备类型、设备序列号、数据传送方向和数据传输尺寸。
网络相关ioctl
函数 : ioctl(int fd, int request, void * arg)
定义 : <sys/ioctl.h>
功能 : 控制I/O设备, 提供了一种获得设备信息和向设备发送控制参数的手段.
参数 :int fd
文件句柄. 用于socket时, 是socket套接字的返回
int request
函数定义的所有操作. 关于socket的操作, 定义在<linux/sockios.h>文件中.
void *arg
指针的类型依赖于request参数.
类别 | Request | 说明 | 数据类型 |
---|---|---|---|
套接字 | SIOCATMARK | 是否位于带外标记 | int |
套接字 | SIOCSPGRP | 设置套接口的进程ID 或进程组ID | int |
套接字 | SIOCGPGRP | 设置套接口的进程ID 或进程组ID | int |
文件 | FIONBIN | 设置/ 清除非阻塞I/O 标志 | int |
文件 | FIOASYNC | 设置/ 清除信号驱动异步I/O 标志 | int |
文件 | FIONREAD | 获取接收缓存区中的字节数 | int |
文件 | FIOSETOWN | 设置文件的进程ID 或进程组ID | int |
文件 | FIOGETOWN | 获取文件的进程ID 或进程组ID | int |
接口 | SIOCGIFCONF | 获取所有接口的清单 | struct ifconf |
接口 | SIOCSIFADDR | 设置接口地址 | struct ifreq |
接口 | SIOCGIFADDR | 获取接口地址 | struct ifreq |
接口 | SIOCSIFFLAGS | 设置接口标志 | struct ifreq |
接口 | SIOCGIFFLAGS | 获取接口标志 | struct ifreq |
接口 | SIOCSIFDSTADDR | 设置点到点地址 | struct ifreq |
接口 | SIOCGIFDSTADDR | 获取点到点地址 | struct ifreq |
接口 | SIOCGIFBRDADDR | 获取广播地址 | struct ifreq |
接口 | SIOCSIFBRDADDR | 设置广播地址 | struct ifreq |
接口 | SIOCGIFNETMASK | 获取子网掩码 | struct ifreq |
接口 | SIOCSIFNETMASK | 设置子网掩码 | struct ifreq |
接口 | SIOCGIFMETRIC | 获取接口的测度 | struct ifreq |
接口 | SIOCSIFMETRIC | 设置接口的测度 | struct ifreq |
接口 | SIOCSARP | 获取接口MTU | struct ifreq |
ARP | SIOCGIFMTU | 创建/ 修改ARP 表项 | struct arpreq |
ARP | SIOCDARP | 获取ARP 表项 | struct arpreq |
ARP | SIOCGIFMTU | 删除ARP 表项 | struct arpreq |
此处我们要获取网络相关信息,例如子网掩码、广播地址、ip地址等,这些信息均存储在ifreq结构体当中,最后需从结构体中读取相应信息
ifreq结构体
ifreq结构定义在/usr/include/net/if.h,用来配置ip地址,激活接口,配置MTU等接口信息的。其中包含了一个接口的名字和具体内容——(是个共用体,有可能是IP地址,广播地址,子网掩码,MAC号,MTU或其他内容),ifreq包含在ifconf结构中,而ifconf结构通常是用来保存所有接口的信息的。
/*
* Interface request structure used for socket
* ioctl's. All interface ioctl's must have parameter
* definitions which begin with ifr_name. The
* remainder may be interface specific.
*/
struct ifreq
{
#define IFHWADDRLEN 6
union
{
char ifrn_name[IFNAMSIZ]; /* if name, e.g. "en0" */
} ifr_ifrn;
union {
struct sockaddr ifru_addr;
struct sockaddr ifru_dstaddr;
struct sockaddr ifru_broadaddr;
struct sockaddr ifru_netmask;
struct sockaddr ifru_hwaddr;
short ifru_flags;
int ifru_ivalue;
int ifru_mtu;
struct ifmap ifru_map;
char ifru_slave[IFNAMSIZ]; /* Just fits the size */
char ifru_newname[IFNAMSIZ];
void __user * ifru_data;
struct if_settings ifru_settings;
} ifr_ifru;
};
#define ifr_name ifr_ifrn.ifrn_name /* interface name */
#define ifr_hwaddr ifr_ifru.ifru_hwaddr /* MAC address */
#define ifr_addr ifr_ifru.ifru_addr /* address */
#define ifr_dstaddr ifr_ifru.ifru_dstaddr /* other end of p-p lnk */
#define ifr_broadaddr ifr_ifru.ifru_broadaddr /* broadcast address */
#define ifr_netmask ifr_ifru.ifru_netmask /* interface net mask */
#define ifr_flags ifr_ifru.ifru_flags /* flags */
#define ifr_metric ifr_ifru.ifru_ivalue /* metric */
#define ifr_mtu ifr_ifru.ifru_mtu /* mtu */
#define ifr_map ifr_ifru.ifru_map /* device map */
#define ifr_slave ifr_ifru.ifru_slave /* slave device */
#define ifr_data ifr_ifru.ifru_data /* for use by interface */
#define ifr_ifindex ifr_ifru.ifru_ivalue /* interface index */
#define ifr_bandwidth ifr_ifru.ifru_ivalue /* link bandwidth */
#define ifr_qlen ifr_ifru.ifru_ivalue /* Queue length */
#define ifr_newname ifr_ifru.ifru_newname /* New name */
#define ifr_settings ifr_ifru.ifru_settings /* Device/proto settings*/
具体实现代码(C/C++)
参考网上的部分实现进行的修改,通过iotcl获取ip地址、mac地址、子网掩码和广播地址,因为是在qt中开发,所以部分代码涉及qt
int k =0;
int fd;
int interfaceNum = 0;
struct ifreq buf[16];
struct ifconf ifc;
struct ifreq ifrcopy;
char mac[16] = {0};
char ip[32] = {0};
char broadAddr[32] = {0};
char subnetMask[32] = {0};
if ((fd = socket(AF_INET, SOCK_DGRAM, 0)) < 0)
{
perror("socket");
close(fd);
}
ifc.ifc_len = sizeof(buf);
ifc.ifc_buf = (caddr_t)buf;
if (!ioctl(fd, SIOCGIFCONF, (char *)&ifc))
{
//确认接口数量
interfaceNum = ifc.ifc_len / sizeof(struct ifreq);
//printf("interface num = %d\n", interfaceNum);
while (interfaceNum-- > 0)
{
//从ifreq结构体中读取网卡名
cards[k].mac_description = QString::fromUtf8(buf[interfaceNum].ifr_name);
//排除未工作接口
ifrcopy = buf[interfaceNum];
if (ioctl(fd, SIOCGIFFLAGS, &ifrcopy))
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
close(fd);
}
//获取mac地址
if (!ioctl(fd, SIOCGIFHWADDR, (char *)(&buf[interfaceNum])))
{
memset(mac, 0, sizeof(mac));
snprintf(mac, sizeof(mac), "%02x%02x%02x%02x%02x%02x",
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[0],
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[1],
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[2],
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[3],
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[4],
(unsigned char)buf[interfaceNum].ifr_hwaddr.sa_data[5]);
cards[k].mac_address = QString::fromUtf8(mac);
//printf("device mac: %s\n", mac);
}
else
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
close(fd);
}
//获取ip地址
if (!ioctl(fd, SIOCGIFADDR, (char *)&buf[interfaceNum]))
{
snprintf(ip, sizeof(ip), "%s",
(char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_addr))->sin_addr));
cards[k].ip_address = QString::fromUtf8(ip);
//printf("device ip: %s\n", ip);
}
else
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
close(fd);
}
//获取子网掩码
if (!ioctl(fd, SIOCGIFNETMASK, &buf[interfaceNum]))
{
snprintf(subnetMask, sizeof(subnetMask), "%s",
(char *)inet_ntoa(((struct sockaddr_in *)&(buf[interfaceNum].ifr_netmask))->sin_addr));
cards[k].ip_mask = QString::fromUtf8(subnetMask);
//printf("device subnetMask: %s\n", subnetMask);
}
else
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
close(fd);
}
QString name = "Card" + QString::number(k+1);
net_card_info_.insert(name, cards[k]);
k++;
}
}
else
{
printf("ioctl: %s [%s:%d]\n", strerror(errno), __FILE__, __LINE__);
close(fd);
}
close(fd);