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 bit8 bit2 bit8~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 或进程组IDint
套接字SIOCGPGRP设置套接口的进程ID 或进程组IDint
文件FIONBIN设置/ 清除非阻塞I/O 标志int
文件FIOASYNC设置/ 清除信号驱动异步I/O 标志int
文件FIONREAD获取接收缓存区中的字节数int
文件FIOSETOWN设置文件的进程ID 或进程组IDint
文件FIOGETOWN获取文件的进程ID 或进程组IDint
接口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获取接口MTUstruct ifreq
ARPSIOCGIFMTU创建/ 修改ARP 表项struct arpreq
ARPSIOCDARP获取ARP 表项struct arpreq
ARPSIOCGIFMTU删除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);

使用流程图

Created with Raphaël 2.2.0 开始 初始化ifreq/ifconf 创建套接字返回文件描述符 Yes or No? 根据需要调用iotcl函数 Yes or No? 返回信息存入ifreq/ifconf 根据需要进行读取操作 关闭套接字 结束 报错 yes no yes no
  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值