ethtool源码分析

ethtool功能十分强大,本文就其源码进行简单的分析,一来很久没好好分析过别人家的代码,和代码几乎都脱节了;二来趁机整理下自己最近所搞的东西。

本文使用的版本下载地址:http://sourceforge.net/projects/gkernel/files/ethtool/2.6.35/

最新的代码在linux kernel官网上:https://www.kernel.org/pub/software/network/ethtool/

代码树结构

主代码:所有主要代码在ethtool.c这个文件,

头文件:类型定义和打印各网卡的寄存器函数在ethtool-util.h头文件,另外该工程没有使用系统提供的ethtool.h头文件,而是在ethtool-copy.h重新定义了ethtool_cmd结构体,也定义了ioctl函数使用到的命令ETHTOOL_GSET、ETHTOOL_SSET等,另外也定义了如SUPPORTED_10baseT_Half、SUPPORTED_10baseT_Full、SUPPORTED_100baseT_Half、SUPPORTED_100baseT_Full、ADVERTISED_10baseT_Half这类宏,总之,从其文件名称可以知道,其实它就是系统的ethtool.h的拷贝。

其它文件:其它大部分文件是打印不同网卡寄存器的实现代码,比如intel的有e100.c、e1000.c、igb.c等,realtek的有realtek.c文件。

代码讲解

主函数十分简单,如下:

int main(int argc, char **argp, char **envp)
{
	parse_cmdline(argc, argp);
	return doit();
}

其中用以分析用户传入的参数,而doit是真正执行的函数,它们使用全局变量来传递参数,所以显示主函数十分简单。

在这里先总结一下ethtool使用的形式,以便有一个认识:

struct ifreq ifr; // 定义ifreq
struct ethtool_cmd ecmd; // 定义ethtool_cmd结构体

strcpy(ifr.ifr_name, devname); //指定网卡名称
fd = socket(AF_INET, SOCK_DGRAM, 0); // 打开socket
ecmd.cmd = ETHTOOL_GSET; // ethtool的某一命令
ifr.ifr_data = (caddr_t)&ecmd;
ioctl(fd, SIOCETHTOOL, &ifr); // 执行SIOCETHTOOL

变量及函数

上面提到真正执行的函数是doit(),其实它是根据不同的mode来执行不同的函数的,这些函数一般是do_XX的形式。其中第一个参数为socket描述符,第二个是ifreq结构体指针,ifreq是所有socket的ioctl用到的结构体,具体可以看头文件的定义。这类的函数列举几个,如下:

static int do_gdrv(int fd, struct ifreq *ifr); // 获取驱动信息
static int do_gset(int fd, struct ifreq *ifr); // 获取网卡参数
static int do_sset(int fd, struct ifreq *ifr); // 设置网卡参数

这些函数没有看到SIOCETHTOOL,是因为调用了send_ioctl:

static int send_ioctl(int fd, struct ifreq *ifr)
{
	return ioctl(fd, SIOCETHTOOL, ifr);
}

mode为枚举,如下:

static enum {
	MODE_HELP = -1,
	MODE_GSET=0,
	MODE_SSET,
	MODE_GDRV,
	MODE_GREGS,
	MODE_NWAY_RST,
	MODE_GEEPROM,
	MODE_SEEPROM,
	MODE_TEST,
	MODE_PHYS_ID,
	MODE_GPAUSE,
	MODE_SPAUSE,
	MODE_GCOALESCE,
	MODE_SCOALESCE,
	MODE_GRING,
	MODE_SRING,
	MODE_GOFFLOAD,
	MODE_SOFFLOAD,
	MODE_GSTATS,
	MODE_GNFC,
	MODE_SNFC,
	MODE_GRXFHINDIR,
	MODE_SRXFHINDIR,
	MODE_SNTUPLE,
	MODE_GNTUPLE,
	MODE_FLASHDEV,
	MODE_PERMADDR,
} mode = MODE_GSET;

另外,有许多静态全局变量用于传递参数,如:

static int speed_wanted = -1; // 手动指定的网速,比如百兆,则此值为100,千兆为1000
static int duplex_wanted = -1; // 全双工或半双工
static int autoneg_wanted = -1; // 自动协商
static int advertising_wanted = -1; 
static int gset_changed = 0; /* did anything in GSET change? */ // 在设置网卡信息时,此标志是否需要先读取网卡再进行设置

像网速、双工这类的参数,在ethtool-copy.h定义有,如:

/* The forced speed, 10Mb, 100Mb, gigabit, 2.5Gb, 10GbE. */
#define SPEED_10		10
#define SPEED_100		100
#define SPEED_1000		1000
#define SPEED_2500		2500
#define SPEED_10000		10000

/* Duplex, half or full. */
#define DUPLEX_HALF		0x00
#define DUPLEX_FULL		0x01

自动协商的定义:

#define AUTONEG_DISABLE		0x00
#define AUTONEG_ENABLE		0x01


其它的函数、变量还有很多,不一一列举出来了。

参数解析

ethtool的帮助信息使用结构体option来管理。通过show_usage来打印。定义如下:

static struct option {
    char *srt, *lng;
    int Mode;
    char *help;
    char *opthelp;
}
这个结构体内容比较多,具体参考代码。
先看看解析用户参数的parse_cmdline函数,

操作函数

doit函数如下:

static int doit(void)
{
	struct ifreq ifr;
	int fd;

	/* Setup our control structures. */
	memset(&ifr, 0, sizeof(ifr));
	strcpy(ifr.ifr_name, devname);

	/* Open control socket. */
	fd = socket(AF_INET, SOCK_DGRAM, 0);
	if (fd < 0) {
		perror("Cannot get control socket");
		return 70;
	}

	/* all of these are expected to populate ifr->ifr_data as needed */
	if (mode == MODE_GDRV) {
		return do_gdrv(fd, &ifr);
	} else if (mode == MODE_GSET) {
		return do_gset(fd, &ifr);
	} else if (mode == MODE_SSET) {
		return do_sset(fd, &ifr);
	} else if (mode == MODE_GREGS) {
		return do_gregs(fd, &ifr);
	} else if (mode == MODE_NWAY_RST) {
		return do_nway_rst(fd, &ifr);
	} else if (mode == MODE_GEEPROM) {
		return do_geeprom(fd, &ifr);
	} else if (mode == MODE_SEEPROM) {
		return do_seeprom(fd, &ifr);
	} else if (mode == MODE_TEST) {
		return do_test(fd, &ifr);
	} else if (mode == MODE_PHYS_ID) {
		return do_phys_id(fd, &ifr);
	} else if (mode == MODE_GPAUSE) {
		return do_gpause(fd, &ifr);
	} else if (mode == MODE_SPAUSE) {
		return do_spause(fd, &ifr);
	} else if (mode == MODE_GCOALESCE) {
		return do_gcoalesce(fd, &ifr);
	} else if (mode == MODE_SCOALESCE) {
		return do_scoalesce(fd, &ifr);
	} else if (mode == MODE_GRING) {
		return do_gring(fd, &ifr);
	} else if (mode == MODE_SRING) {
		return do_sring(fd, &ifr);
	} else if (mode == MODE_GOFFLOAD) {
		return do_goffload(fd, &ifr);
	} else if (mode == MODE_SOFFLOAD) {
		return do_soffload(fd, &ifr);
	} else if (mode == MODE_GSTATS) {
		return do_gstats(fd, &ifr);
	} else if (mode == MODE_GNFC) {
		return do_grxclass(fd, &ifr);
	} else if (mode == MODE_SNFC) {
		return do_srxclass(fd, &ifr);
	} else if (mode == MODE_GRXFHINDIR) {
		return do_grxfhindir(fd, &ifr);
	} else if (mode == MODE_SRXFHINDIR) {
		return do_srxfhindir(fd, &ifr);
	} else if (mode == MODE_SNTUPLE) {
		return do_srxntuple(fd, &ifr);
	} else if (mode == MODE_GNTUPLE) {
		return do_grxntuple(fd, &ifr);
	} else if (mode == MODE_FLASHDEV) {
		return do_flash(fd, &ifr);
	} else if (mode == MODE_PERMADDR) {
		return do_permaddr(fd, &ifr);
	}

	return 69;
}

这个函数先是将网卡名称赋值给ifreq结构体的ifr_name,再打开socket,然后再调用do_XX形式的函数。虽然调用的函数很多,但结构很清晰,只需了解我们当前是执行哪一种模式的,跟进该函数即可一步一步分析。

下面看看执行ethtool eth0的过程,这是输出指定网卡信息的命令。首先看其输出结果,如下:

root@localhost:~# ethtool eth0
Settings for eth0:
        Supported ports: [ TP ]
        Supported link modes:   10baseT/Half 10baseT/Full  // 支持十兆/百兆/千兆半双工、全双工模式
                                100baseT/Half 100baseT/Full
                                1000baseT/Full 
        Supported pause frame use: Symmetric // 当前支持的暂停帧模式为Symmetric
        Supports auto-negotiation: Yes // 支持自动协商,一般都是支持的
        Advertised link modes:  10baseT/Half 10baseT/Full // 同上
                                100baseT/Half 100baseT/Full
                                1000baseT/Full
        Advertised pause frame use: Symmetric // 这里暂时还不知道是什么意思
        Advertised auto-negotiation: Yes // 同上
        Speed: 1000Mb/s // 当前是千兆,如网络断开,此处显示Unknown
        Duplex: Full    // 双全工
        Port: Twisted Pair // 双绞线
        PHYAD: 1 // PHY地址
        Transceiver: internal
        Auto-negotiation: on // 自动协商
        MDI-X: off (auto)
        Supports Wake-on: pumbg
        Wake-on: g
        Current message level: 0x00000007 (7)
                               drv probe link
        Link detected: yes // 当前已经连接到网络中(如交换机),如果拨掉网线,则显示on
(说实话,这里及手册中提到的“Advertised”我还不太知道该怎么理解。)

各种的信息都有,连接模式、暂停帧、自动协商、当前的网速、双工、接口类型、是否已连接上,等等。

不详细分析代码了,主要流程如下所示:

main
-> doit
  -> do_gset
     -> send_ioctl(ETHTOOL_GSET)
     -> dump_ecmd
        -> dump_supported (打印支持的端口、速率)
           -> 打印支持端口 "	Supported ports: [ "
           -> 打印支持速率 "	Supported link modes:   "
           -> 打印自动协商 "	Supports auto-negotiation: "
        -> dump_advertised
           -> 打印支持速率" Advertised link modes:  "
           -> 打印支持暂停帧" Advertised pause frame use:  " 
           -> 打印自动协商" Advertised auto-negotiation:   "
        -> 打印速率"	Speed: "
        -> 打印双工"	Duplex: "
        -> 打印端口"	Port: "
        -> 打印地址"	PHYAD: %d\n"
        -> 打印"	Transceiver: "
        -> 打印自动协商"	Auto-negotiation: %s\n"
        -> 如果是PORT_TP,打印"	MDI-X: "
     -> send_ioctl(ETHTOOL_GWOL)
     -> dump_wol
        -> 打印WOL支持"Supports Wake-on:"
     -> send_ioctl(ETHTOOL_GMSGLVL)
     -> 打印信息等级"Current message level"
     -> send_ioctl(ETHTOOL_GLINK)
     -> 打印连接状态"Link detected:"

李迟 2015年3月30日写,4月上旬补充



评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值