通过 ioctls 控制硬件

作者:Lisa Corsetti

几年前,我有一台笔记本电脑,我在工作和家里都使用它。为了简化我的网络配置,并且不必根据我在哪里手动更改它,我决定在两处都使用 DHCP。这是工作场所的标准配置,所以我在家里也实施了一个 DHCP 服务器。这效果很好,除非我在没有连接到任何网络的情况下启动系统。当这样做时,笔记本电脑会花费大量时间尝试查找 DHCP 服务器但未成功,然后才继续启动过程的其余部分。

我得出结论,解决这种延迟时间的理想方案是让系统在以太网接口关闭的情况下启动,并且仅当电缆连接到集线器时才启动,也就是说,如果我的以太网接口上有链路指示灯。实现此目的的最佳方法似乎是让 shell 脚本调用一个程序,该程序的返回代码将指示特定网络接口上是否已建立链路。因此,我开始了寻找一种方法来确定我的 10/100Base-T 接口的链路状态的探索。

由于之前没有做过太多底层 Linux 编程,我花了一些时间才发现,与设备驱动程序的大多数此类交互通常是通过 ioctl 库调用(I/O 控制的缩写)完成的,该调用在 sys/ioctl.h 中声明。

int ioctl(int, int, ...)

第一个参数是文件描述符。由于 Linux 中所有设备都像文件一样被访问,因此使用的文件描述符通常是已打开的,并将您要连接的设备作为目标。但是,对于以太网接口,fd 只是一个打开的套接字。显然,不需要将此套接字绑定到有问题的接口。

ioclt.h 中的第二个参数是一个整数,表示 ioctl 的特定请求的标识号。请求本质上必须因设备而异。例如,您可以设置串行设备的速度,但不能设置打印机设备的速度。当然,网络接口存在一组特定的命令。

其他参数是可选的,并且可能因一个设备上的 ioctl 实现而异于另一个设备上的实现。据我所知,始终存在第三个参数,而且我还没有找到超过第三个参数的情况。第三个参数通常似乎是指向结构的指针。这允许在两个方向上传递任意数量的数据,数据由指针指向的结构定义,只需传递指针即可。

以下简单程序显示了 ioctl 如何工作的基本示例,该程序检查串行端口上一个信号的状态

#include <termios.h>
#include <fcntl.h>
#include <errno.h>
#include <sys/ioctl.h>

main()
{
  int fd, status;

   fd = open("/dev/ttyS0", O_RDONLY);
   if (ioctl(fd, TIOCMGET, &status) == -1)
      printf("TIOCMGET failed: %s\n",
             strerror(errno));
   else {
      if (status & TIOCM_DTR)
         puts("TIOCM_DTR is not set");
      else
         puts("TIOCM_DTR is set");
   }
   close(fd);
}

此程序打开一个 tty(串行端口),然后使用串行端口的 fd 调用 ioctl,命令为 TIOCMGET(列为获取调制解调器位的状态),以及指向将返回结果的整数的指针。

然后检查 ioctl 结果,以查看在处理请求时是否发生错误。如果没有问题,我们检查通过与 TIOCM_DTR 进行与运算而返回的值。此步骤分别产生真或假,非零或零。

为以太网驱动程序使用 ioctl 的过程类似。套接字 ioctl 调用的 ioctl 调用的第三个参数(其中 fd 是套接字句柄)通常是指向 ifreq(接口请求)结构的指针。ifreq 结构的类型声明可以在 net/if.h 中找到。

不幸的是,许多 ioctl 接口的文档很难找到,并且至少有三个不同的 API 用于访问网络接口。我最初使用 MII(媒体独立接口)方法编写了这个程序。在编写本文时,在我机器上安装了最新的内核后,我发现我必须添加 ETHTOOL 方法。

在添加 ETHTOOL 后,我修改了程序,并将每个接口方法编写为子例程。修改后的程序尝试一种方法,如果失败,则尝试另一种方法。第三种方法早于 MII API,我还没有遇到需要它的机器,因此代码未包含在内。

关于使用 MII 接口的信息主要是通过检查 Donald Becker 编写的 mii-diag 程序(ftp.scyld.com/pub/diag/mii-diag.c)获得的,该程序我在 Scyld Computing Corporation 的网站上找到的。该网站还包含一个优秀的页面(www.scyld.com/diag/mii-status.html),解释了 ioctl 函数可能返回的 MII 状态字的详细信息。但是,在这里,我重点介绍 ETHTOOL 接口,因为它是较新的方法。该程序和两个接口都可以从Linux Journal FTP 站点 ftp.linuxjournal.com/pub/lj/listings/issue117/6908.tgz 获取。

关于使用 ETHTOOL API 的信息也是通过搜索各种源代码获得的,其中最重要的是网络接口驱动程序本身的源代码,特别是 eepro100.c。Tim Hockin 撰写的一封电子邮件也很有帮助,我在 Google 搜索时找到了它。

在编写我的程序时,我将默认接口设置为 eth0,除非向程序传递了参数。接口 ID 存储在 ifname 中。由于我使用的 ioctl 命令特定于网络接口,因此使用其他设备可能会导致返回“无法确定状态”。

在调用 ioctl 之前,我们需要一个文件句柄,因此我们首先必须打开一个套接字

int skfd;
if (( skfd = socket( AF_INET, SOCK_DGRAM, 0 ) ) < 0 )
   {
   printf("socket error\n");
   exit(-1);
   }

在标准的尝试检查所有错误的 C 编码风格中,我将其放在一个 if 语句中,如果套接字未正确打开,则该语句仅打印错误并终止程序,返回 -1。为了我的目的,我宁愿将确定状态中的错误报告为缺少链路,而不是存在链路,因此找到的链路报告为 0,未找到的链路报告为 1。

用于连接驱动程序的新 ETHTOOL API 使确定链路状态比以前的方法容易得多。ioctl 是为 ETHTOOL 接口实现的,因此现在只有一个 ioctl 命令,SIOCETHTOOL(它指定调用是 ETHTOOL 命令),然后传递的数据块包含 ETHTOOL 接口的特定子命令。

标准 ioctl 数据结构(类型 ifreq)需要两个附加项:命令应应用到的接口的名称,以及结构(类型 ethtool_value)的地址,在其中存储特定命令以及返回的信息。

结构和大多数其他信息(包括可用命令)都位于 ethtool.h 头文件中。我需要的命令是 ETHTOOL_GLINK,文档中将其描述为“获取链路状态”,我将其存储在 edata.cmd 中

edata.cmd = ETHTOOL_GLINK;

接口的名称和 edata 结构的地址都需要放入 ifreq 结构中

strncpy(ifr.ifr_name, ifname, sizeof(ifr.ifr_name)-1);
ifr.ifr_data = (char *) &edata;

此时,剩下的就是进行 ioctl 调用,检查返回值(以确保允许命令类型),并检查返回的指针指向的结构中的数据,以查看链路是否已启动或关闭

if (ioctl(skfd, SIOCETHTOOL, &ifr) == -1) {
    printf("ETHTOOL_GLINK failed: %s\n", strerror(errno));
    return 2;
}
return (edata.data ? 0 : 1);

在这种情况下,我的代码为链路启动返回 0,为链路关闭返回 1,为未确定或失败返回 2。此代码允许我从我的 rc.local 调用此函数,启动接口,并且仅当系统插入到功能正常的集线器/交换机时才调用 dhcpcd 或 pump 以获取 IP 地址。以下是 rc.local 的相关部分

/root/sense_link/sense_link | logger
if /root/sense_link/sense_link > /dev/null; then
  logger "No link sense -- downing eth0"
  /sbin/ifconfig eth0 down
else
  logger "Sensed link - dhcping eth0"
  /sbin/dhcpcd eth0
fi

首先,sense_link 的输出被发送到系统日志。然后,如果在 eth0 上未检测到链路,或者无法确定,则将消息写入日志,并且 eth0 被关闭。如果检测到链路,则在 eth0 上执行 dhcpcd。

一旦实现,我的 rc.local 文件现在在未插入网线或找到活动的 DHCP 服务器时执行得非常快。我仍然遇到明显延迟的唯一情况是,我插入到没有活动的 DHCP 服务器的网络中。

我还没有尝试将此代码与我的 802.11b 卡一起使用,以查看它是否可以在尝试联系 DHCP 服务器之前检测到其上的链路,因为我通常只有在我知道有服务器的位置才插入 PCMCIA 卡。然而,对于感兴趣的一方来说,这将是一个有趣的实验和一个有用的扩展。

Lisa Corsetti 目前是一名软件架构师,也是 Anteil, Inc. 的总裁,这是一家专注于为各个行业和政府提供定制的基于 Web 的应用程序的咨询公司。Lisa 获得了 Drexel 大学电气和计算机工程专业的理学学士学位。

加载 Disqus 评论