linux中ioctl的工作流程以及ethtool是如何获取网络设备信息的

文章目录

一、前言

假设输入命令sudo ethtool eth0,那么ethtool是怎么工作的?在分析之前先编译一个可执行文件,方便我们调试

二、通过ethtool源码编译可执行文件

1.准备工作

bash 复制代码
sudo apt-get install automake autoconf

注意查看文件autogen.sh里的描述,看下载的版本是否支持

2.编译步骤

  • 生成配置文件
    • 进入源码目录,执行以下命令生成配置文件:
bash 复制代码
./autogen.sh
  • 如果遇到automake工具未安装的错误,请确保已安装automake

  • 配置编译选项

    • 执行configure脚本,配置编译信息并生成Makefile文件。对于本地编译,可以直接使用以下命令:
bash 复制代码
./configure
  • 编译源码
    • 执行make命令,开始编译,需要指定linux的头文件位置
bash 复制代码
make CFLAGS="-g -O0 -I/usr/src/linux-2.6.10/include"
  • 编译完成后,可以在当前目录下看到生成的ethtool可执行文件

3.验证编译结果

运行测试

  • 可以直接运行ethtool命令来测试其功能。例如:
bash 复制代码
sudo ./ethtool -h
  • 这将显示ethtool的帮助信息,验证其是否正常运行

三、ethtool入口函数

c 复制代码
int main(int argc, char **argp, char **envp)
{
        parse_cmdline(argc, argp);
        return doit();
}

四、解析命令行参数parse_cmdline

c 复制代码
static void parse_cmdline(int argc, char **argp)
{
	int i, k;

	for (i = 1; i < argc; i++) {
		switch (i) {
		case 1:
			for (k = 0; args[k].srt; k++)
				if (!strcmp(argp[i], args[k].srt) ||
				    !strcmp(argp[i], args[k].lng)) {
					mode = args[k].Mode;
					break;
				}
			if (mode == MODE_HELP ||
			    (!args[k].srt && argp[i][0] == '-'))
				show_usage(0);
			else
				devname = argp[i];
			break;
		...
}

1.函数框架

c 复制代码
static void parse_cmdline(int argc, char **argp)
{
    int i, k;

    for (i = 1; i < argc; i++) {
        switch (i) {
        case 1:  // 第一个参数处理
            // ...
        case 2:  // 第二个参数处理  
            // ...
        case 3:  // 第三个参数处理
            // ...
        default: // 其他参数处理
            // ...
        }
    }
    // 后处理逻辑
}

2.第一个参数处理 (case 1)

c 复制代码
case 1:
    for (k = 0; args[k].srt; k++)
        if (!strcmp(argp[i], args[k].srt) ||
            !strcmp(argp[i], args[k].lng)) {
            mode = args[k].Mode;
            break;
        }
    if (mode == MODE_HELP ||
        (!args[k].srt && argp[i][0] == '-'))
        show_usage(0);
    else
        devname = argp[i];
    break;

2.1.遍历选项表

c 复制代码
for (k = 0; args[k].srt; k++)
  • 遍历 args[] 数组,直到遇到 srt 为 NULL 的结束标记

  • args[] 包含了所有支持的命令行选项,每一个参数是如下结构体

c 复制代码
static struct option {
    char *srt, *lng;
    int Mode;
    char *help;
    char *opthelp;
} args

2.2.选项匹配检查

c 复制代码
if (!strcmp(argp[i], args[k].srt) ||
    !strcmp(argp[i], args[k].lng)) {
    mode = args[k].Mode;
    break;
}
  • !strcmp(argp[i], args[k].srt) : 匹配短选项(如 -s
  • !strcmp(argp[i], args[k].lng) : 匹配长选项(如 --change
  • mode = args[k].Mode: 设置对应的操作模式
  • break: 找到匹配后立即退出循环

2.3. 特殊条件处理

c 复制代码
if (mode == MODE_HELP ||
    (!args[k].srt && argp[i][0] == '-'))
    show_usage(0);

两种情况下显示帮助信息:

  • 模式是帮助 : mode == MODE_HELP
  • 未知选项 : !args[k].srt(遍历完都没找到匹配)且 argp[i][0] == '-'(以破折号开头)

2.4.默认情况

c 复制代码
else
    devname = argp[i];
  • 如果不是选项,就认为是设备名 (如 eth0

五、开始操作doit

c 复制代码
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);
	} ...
}

1.函数框架

c 复制代码
static int doit(void)
{
    struct ifreq ifr;
    int fd;

    // 初始化网络接口请求结构
    // 创建控制socket
    // 根据模式分发到具体处理函数
}

2.网络接口初始化

2.1.初始化 ifreq 结构

c 复制代码
memset(&ifr, 0, sizeof(ifr));
strcpy(ifr.ifr_name, devname);
  • memset: 清空结构体,避免未初始化内存
  • strcpy : 设置网络设备名(如 eth0
  • ifreq 结构体: 用于内核与用户空间传递网络接口信息

2.2.创建控制socket

c 复制代码
fd = socket(AF_INET, SOCK_DGRAM, 0);
if (fd < 0) {
    perror("Cannot get control socket");
    return 70;
}
  • AF_INET: IPv4 协议族
  • SOCK_DGRAM: 数据报socket(UDP)
  • 返回值70: Unix惯例,表示系统配置错误
  • 用途 : 通过这个socket发送ioctl命令到内核

2.3.查询网卡信息

bash 复制代码
sudo ./ethtool eth0
c 复制代码
// 流程:
mode = MODE_GSET (默认模式)
→ 调用 do_gset(fd, &ifr)
→ 发送 SIOCETHTOOL ioctl 获取设置
→ 显示速度、双工、自动协商等信息

六、获取网络设备的各项设置do_gset

c 复制代码
static int do_gset(int fd, struct ifreq *ifr)
{
	int err;
	struct ethtool_cmd ecmd;
	struct ethtool_wolinfo wolinfo;
	struct ethtool_value edata;
	int allfail = 1;

	fprintf(stdout, "Settings for %s:\n", devname);

	ecmd.cmd = ETHTOOL_GSET;
	ifr->ifr_data = (caddr_t)&ecmd;
	err = send_ioctl(fd, ifr);
	if (err == 0) {
		err = dump_ecmd(&ecmd);
		if (err)
			return err;
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get device settings");
	}

	wolinfo.cmd = ETHTOOL_GWOL;
	ifr->ifr_data = (caddr_t)&wolinfo;
	err = send_ioctl(fd, ifr);
	if (err == 0) {
		err = dump_wol(&wolinfo);
		if (err)
			return err;
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get wake-on-lan settings");
	}

	edata.cmd = ETHTOOL_GMSGLVL;
	ifr->ifr_data = (caddr_t)&edata;
	err = send_ioctl(fd, ifr);
	if (err == 0) {
		fprintf(stdout, "	Current message level: 0x%08x (%d)\n"
			"			       ",
			edata.data, edata.data);
		print_flags(cmdline_msglvl, ARRAY_SIZE(cmdline_msglvl),
			    edata.data);
		fprintf(stdout, "\n");
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get message level");
	}

	edata.cmd = ETHTOOL_GLINK;
	ifr->ifr_data = (caddr_t)&edata;
	err = send_ioctl(fd, ifr);
	if (err == 0) {
		fprintf(stdout, "	Link detected: %s\n",
			edata.data ? "yes":"no");
		allfail = 0;
	} else if (errno != EOPNOTSUPP) {
		perror("Cannot get link status");
	}

	if (allfail) {
		fprintf(stdout, "No data available\n");
		return 75;
	}
	return 0;
}

1.函数概述

c 复制代码
static int do_gset(int fd, struct ifreq *ifr)
  • 目的:获取网络设备的多种配置信息
  • 参数
    • fd:网络设备文件描述符
    • ifr:指向接口请求结构的指针
  • 返回值:成功返回0,失败返回错误码

2.函数执行流程

2.1. 初始化阶段

c 复制代码
int err;
struct ethtool_cmd ecmd;
struct ethtool_wolinfo wolinfo;
struct ethtool_value edata;
int allfail = 1;  // 初始假设所有操作都会失败

fprintf(stdout, "Settings for %s:\n", devname);

2.2. 获取设备基本设置 (ETHTOOL_GSET)

c 复制代码
ecmd.cmd = ETHTOOL_GSET;
ifr->ifr_data = (caddr_t)&ecmd;
err = send_ioctl(fd, ifr);
  • 功能:获取网卡速度、双工模式、自适应等基本设置
  • 处理逻辑
    • 成功:调用 dump_ecmd() 输出详细信息,标记 allfail = 0
    • 失败但非不支持:报错"Cannot get device settings"
    • 不支持操作:静默跳过

2.3. 获取Wake-on-LAN设置 (ETHTOOL_GWOL)

c 复制代码
wolinfo.cmd = ETHTOOL_GWOL;
ifr->ifr_data = (caddr_t)&wolinfo;
err = send_ioctl(fd, ifr);
  • 功能:获取网络唤醒配置
  • 处理逻辑 :同上,成功时调用 dump_wol() 输出WOL设置

2.4. 获取消息级别 (ETHTOOL_GMSGLVL)

c 复制代码
edata.cmd = ETHTOOL_GMSGLVL;
ifr->ifr_data = (caddr_t)&edata;
err = send_ioctl(fd, ifr);
  • 功能:获取驱动调试消息级别

  • 成功输出示例

    复制代码
    Current message level: 0x00000007 (7)
                       drv probe link

2.5. 获取链路状态 (ETHTOOL_GLINK)

c 复制代码
edata.cmd = ETHTOOL_GLINK;
ifr->ifr_data = (caddr_t)&edata;
err = send_ioctl(fd, ifr);
  • 功能:检测网络链路状态(是否连接)
  • 输出:"Link detected: yes" 或 "Link detected: no"

七、sys_ioctl系统调用

c 复制代码
asmlinkage long sys_ioctl(unsigned int fd, unsigned int cmd, unsigned long arg)
{
        struct file * filp;
        unsigned int flag;
        int on, error = -EBADF;

        ...
        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);
        }
        ...
}

1.代码执行流程

1.1. 初始错误设置

c 复制代码
error = -ENOTTY;
  • -ENOTTY : "对于设备不适当的ioctl"错误
  • 这是默认的错误返回值,表示该命令不被支持

1.2. 文件类型检查和处理

c 复制代码
if (S_ISREG(filp->f_dentry->d_inode->i_mode))
    error = file_ioctl(filp, cmd, arg);

条件 : S_ISREG(...) 检查文件是否是普通文件(regular file)

  • 处理 : 调用 file_ioctl(filp, cmd, arg)
  • 含义 : 对于普通文件(如文本文件、二进制文件等),使用通用的文件ioctl处理函数

2.3. 设备文件检查和处理

c 复制代码
else if (filp->f_op && filp->f_op->ioctl)
    error = filp->f_op->ioctl(filp->f_dentry->d_inode, filp, cmd, arg);

条件:

  • filp->f_op 存在(文件操作表不为空)
  • filp->f_op->ioctl 存在(设备提供了ioctl处理方法)

处理 : 调用设备特定的ioctl处理函数

  • 参数 : inode, file, cmd, arg
  • 含义 : 将ioctl请求转发给具体的设备驱动程序处理

八、sock_ioctl处理套接字相关的ioctl请求

c 复制代码
static int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
                      unsigned long arg)
{
        struct socket *sock;
        void __user *argp = (void __user *)arg;
        int pid, err;

        ...
        switch (cmd) {
                ...
                default:
                        err = sock->ops->ioctl(sock, cmd, arg);
                        break;
        }
        ...
}

1.函数概述

c 复制代码
static int sock_ioctl(struct inode *inode, struct file *file, unsigned int cmd,
                      unsigned long arg)
  • 作用 : 处理套接字文件的ioctl系统调用
  • 参数 :
    • inode: 文件的inode结构
    • file: 文件结构指针
    • cmd: ioctl命令
    • arg: 命令参数(用户空间指针)

2.默认分支处理

c 复制代码
default:
    err = sock->ops->ioctl(sock, cmd, arg);
    break;

执行逻辑:

  1. 获取套接字操作表 : sock->ops 指向特定协议族的操作函数表
  2. 调用协议特定的ioctl : sock->ops->ioctl(sock, cmd, arg)
  3. 返回执行结果 : 错误码通过err变量返回

九、inet_ioctl处理IPv4套接字的ioctl请求

c 复制代码
int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
{
        struct sock *sk = sock->sk;
        int err = 0;

        switch (cmd) {
                ...
                default:
                        if (!sk->sk_prot->ioctl ||
                            (err = sk->sk_prot->ioctl(sk, cmd, arg)) ==
                                                                -ENOIOCTLCMD)
                                err = dev_ioctl(cmd, (void __user *)arg);
                        break;
        }
        return err;
}

1.函数概述

c 复制代码
int inet_ioctl(struct socket *sock, unsigned int cmd, unsigned long arg)
  • 作用 : 处理IPv4协议族的套接字ioctl命令
  • 参数 :
    • sock: 套接字结构
    • cmd: ioctl命令
    • arg: 命令参数

2.默认分支执行逻辑

c 复制代码
default:
    if (!sk->sk_prot->ioctl ||
        (err = sk->sk_prot->ioctl(sk, cmd, arg)) == -ENOIOCTLCMD)
        err = dev_ioctl(cmd, (void __user *)arg);
    break;

2.1.执行流程图

复制代码
默认分支开始
    ↓
检查sk->sk_prot->ioctl是否存在?
    ↓
不存在 或 存在但返回-ENOIOCTLCMD → 调用dev_ioctl()
    ↓  
存在且处理成功 → 返回协议处理结果
    ↓
返回错误码

3.详细逻辑分解

条件1: 检查协议是否支持ioctl

c 复制代码
if (!sk->sk_prot->ioctl || ... )
  • 含义 : 如果传输层协议没有实现ioctl操作
  • 示例 : 某些简单的协议可能没有特定的ioctl处理

条件2: 协议处理返回特定错误

c 复制代码
(err = sk->sk_prot->ioctl(sk, cmd, arg)) == -ENOIOCTLCMD
  • -ENOIOCTLCMD : "没有这样的ioctl命令"错误码
  • 含义: 协议层表示不认识这个命令,需要继续向下传递

最终处理:

c 复制代码
err = dev_ioctl(cmd, (void __user *)arg);
  • dev_ioctl : 网络设备相关的ioctl处理函数
  • 含义: 将命令传递给网络设备层处理

十、dev_ioctl 处理网络设备相关的ioctl命令

c 复制代码
// net/core/dev.c
int dev_ioctl(unsigned int cmd, void __user *arg)
{
        struct ifreq ifr;
        int ret;
        char *colon;

       	...

        switch (cmd) {
                ...

                case SIOCETHTOOL:
                        dev_load(ifr.ifr_name);
                        rtnl_lock();
                        ret = dev_ethtool(&ifr);
                        rtnl_unlock();
                        if (!ret) {
                                if (colon)
                                        *colon = ':';
                                if (copy_to_user(arg, &ifr,
                                                 sizeof(struct ifreq)))
                                        ret = -EFAULT;
                        }
                        return ret;

                ...
        }
}

1.函数概述

c 复制代码
int dev_ioctl(unsigned int cmd, void __user *arg)
  • 作用 : 处理网络设备相关的ioctl命令
  • 参数 :
    • cmd: ioctl命令
    • arg: 用户空间参数指针

2.SIOCETHTOOL分支执行流程

2.1. 用户空间数据拷贝

c 复制代码
/* 在函数开头部分 */
if (copy_from_user(&ifr, arg, sizeof(struct ifreq)))
    return -EFAULT;
  • 作用 : 从用户空间安全地拷贝ifreq结构到内核空间

2.2. 驱动模块加载

c 复制代码
dev_load(ifr.ifr_name);
  • 作用: 确保网络设备驱动模块已加载
  • 逻辑: 如果设备不存在,尝试自动加载对应的内核模块
  • 示例: 对于"eth0"接口,可能加载对应的网络驱动模块

2.3. 网络设备锁保护

c 复制代码
rtnl_lock();
// ... 关键操作
rtnl_unlock();
  • RTNL: Run-Time Network Lock(运行时网络锁)
  • 作用: 保护网络设备配置的并发访问,防止竞态条件
  • 重要性: 网络设备操作必须是原子的

2.4. 核心ethtool处理

c 复制代码
ret = dev_ethtool(&ifr);
  • dev_ethtool : 实际处理所有ethtool命令的函数

2.5. 结果返回用户空间

c 复制代码
if (!ret) {
    if (colon)
        *colon = ':';
    if (copy_to_user(arg, &ifr, sizeof(struct ifreq)))
        ret = -EFAULT;
}
  • 条件 : 只有操作成功(ret == 0)时才拷贝数据回用户空间
  • copy_to_user: 安全地将内核数据拷贝到用户空间
  • colon处理: 恢复接口名中的冒号(如果有的话)

十一、dev_ethtool处理所有ethtool子命令的分发和执行

c 复制代码
int dev_ethtool(struct ifreq *ifr)
{
        struct net_device *dev = __dev_get_by_name(ifr->ifr_name);
        void __user *useraddr = ifr->ifr_data;
        u32 ethcmd;
        int rc;

        ...
        switch (ethcmd) {
        case ETHTOOL_GSET:
                rc = ethtool_get_settings(dev, useraddr);
                break;
        ...

1.函数概述

c 复制代码
int dev_ethtool(struct ifreq *ifr)
  • 作用 : 处理所有ethtool子命令的分发和执行

2.完整函数逻辑

2.1. 网络设备查找

c 复制代码
struct net_device *dev = __dev_get_by_name(ifr->ifr_name);
  • 作用: 根据接口名查找网络设备结构

2.2.用户空间数据指针

c 复制代码
void __user *useraddr = ifr->ifr_data;
  • 作用 : 指向ethtool命令数据的用户空间指针

  • 数据流:

    复制代码
    用户空间: struct ifreq.ifr_data → 指向ethtool_cmd等结构
                ↓
    内核空间: useraddr → 通过copy_from_user/copy_to_user访问

2.3. ETHTOOL_GSET命令详解

c 复制代码
case ETHTOOL_GSET:
    rc = ethtool_get_settings(dev, useraddr);
    break;

3.重要安全机制

3.1. 权限验证

c 复制代码
if (!capable(CAP_NET_ADMIN))
    return -EPERM;
  • 需要CAP_NET_ADMIN能力(通常需要root权限)

3.2. 用户空间指针安全

c 复制代码
if (copy_from_user(&ethcmd, useraddr, sizeof(ethcmd)))
    return -EFAULT;

if (copy_to_user(useraddr, &ecmd, sizeof(ecmd)))
    return -EFAULT;

3.3. 驱动支持检查

c 复制代码
if (!dev->ethtool_ops || !dev->ethtool_ops->get_settings)
    return -EOPNOTSUPP;

十二、ethtool_get_settings负责处理ETHTOOL_GSET命令

c 复制代码
static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
{
        struct ethtool_cmd cmd = { ETHTOOL_GSET };
        int err;

        if (!dev->ethtool_ops->get_settings)
                return -EOPNOTSUPP;

        err = dev->ethtool_ops->get_settings(dev, &cmd);
        if (err < 0)
                return err;

        if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
                return -EFAULT;
        return 0;
}

1.函数概述

c 复制代码
static int ethtool_get_settings(struct net_device *dev, void __user *useraddr)
  • 作用: 获取网络设备的物理层设置(速度、双工模式、自适应等)
  • 调用路径 : dev_ethtool()ethtool_get_settings()
  • 返回值: 成功返回0,失败返回错误码

2.函数执行流程

2.1.完整执行流程图

复制代码
ethtool_get_settings开始
    ↓
初始化ethtool_cmd结构,设置cmd=ETHTOOL_GSET
    ↓
检查驱动是否支持get_settings操作
    ↓
调用驱动特定的get_settings方法
    ↓
将结果拷贝回用户空间
    ↓
返回执行状态

3.代码详细解析

3.1. 初始化ethtool命令结构

c 复制代码
struct ethtool_cmd cmd = { ETHTOOL_GSET };
  • 作用 : 创建并初始化ethtool_cmd结构体
  • ETHTOOL_GSET: 命令常量

3.2. 驱动支持检查

c 复制代码
if (!dev->ethtool_ops->get_settings)
    return -EOPNOTSUPP;
  • 作用 : 验证网络设备驱动是否实现了get_settings方法
  • -EOPNOTSUPP: "操作不支持"错误码

3.3. 调用驱动特定的get_settings方法

c 复制代码
err = dev->ethtool_ops->get_settings(dev, &cmd);
if (err < 0)
    return err;
  • 作用 : 执行具体的硬件访问操作,填充ethtool_cmd结构
  • 参数 :
    • dev: 网络设备指针
    • &cmd: 要填充的ethtool命令结构指针
  • 返回值处理: 如果驱动返回错误,直接传播给调用者

3.4. 结果返回用户空间

c 复制代码
if (copy_to_user(useraddr, &cmd, sizeof(cmd)))
    return -EFAULT;
return 0;
  • 作用: 将填充好的设备设置信息返回给用户空间
  • copy_to_user: 安全地将内核数据拷贝到用户空间
  • -EFAULT: "错误的地址"错误码(值为-14),表示用户空间指针无效

十三、ethtool获取eth0设备信息内核空间完整调用链

ioctlsys_ioctlsock_ioctlinet_ioctldev_ioctldev_ethtoolethtool_get_settings

text 复制代码
用户空间: ioctl(SIOCETHTOOL, &ifr)
    ↓
系统调用: sys_ioctl()
    ↓
套接字层: sock_ioctl()
    ↓  
网络层: inet_ioctl()
    ↓
设备层: dev_ioctl(SIOCETHTOOL)
    ↓
ethtool层: dev_ethtool(&ifr)
    ↓
命令分发: switch(ethcmd) → case ETHTOOL_GSET
    ↓
具体处理: ethtool_get_settings(dev, useraddr)
    ↓
驱动调用: dev->ethtool_ops->get_settings(dev, &ecmd)
    ↓
硬件访问: 读取PHY寄存器等硬件操作
相关推荐
。TAT。2 小时前
Linux - 进程状态
linux·学习
无敌最俊朗@3 小时前
如何把qt + opencv的库按需要拷贝到开发板
linux·qt
NiKo_W4 小时前
Linux 进程通信——匿名管道
linux·运维·服务器
diqiudq6 小时前
用AMD显卡节省nVidia显卡显存占用
linux·深度学习·ubuntu·显存释放
励志不掉头发的内向程序员6 小时前
【Linux系列】并发世界的基石:透彻理解 Linux 进程 — 进程状态
linux·运维·服务器·开发语言·学习
小龙报7 小时前
《KelpBar海带Linux智慧屏项目》
linux·c语言·vscode·单片机·物联网·ubuntu·学习方法
mljy.8 小时前
Linux《线程同步和互斥(下)》
linux
zhangrelay8 小时前
蓝桥云课中支持的ROS1版本有哪些?-2025在线尝试ROS1全家福最方便的打开模式-
linux·笔记·学习·ubuntu
2301_818411559 小时前
rpm软件包管理以及yum,apt的前端软件包管理器
linux·运维·服务器