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寄存器等硬件操作
相关推荐
FoldWinCard3 小时前
D5 Linux 网络及端口命令
linux·运维·服务器
零涂毕业设计4 小时前
毕业设计模块开发-OLED显示屏(IIC协议0.96寸)STM32 ESP32 FPGA Linux驱动免费分享
linux·stm32·单片机·嵌入式·esp32·fpga·oled
tedcloud1234 小时前
OmniRoute怎么部署?开源AI模型路由平台Linux部署教程
linux·服务器·人工智能·开源·音视频
霸道流氓气质5 小时前
KMS 密钥管理服务(Key Management Service)原理与实践
linux·服务器·数据库
SakitamaX5 小时前
LVS(Linux Virtual Server)概念详解
linux·运维·lvs
小张同学a.5 小时前
Linux系统管理
linux·运维
阿龙的工作记录5 小时前
深入理解Linux设备驱动模型
linux·嵌入式硬件·工作记录
代码村新手6 小时前
Linux的基本指令
linux·运维·服务器
hkj88086 小时前
Ubuntu 切换java版本
java·linux·ubuntu
撩得Android一次心动8 小时前
Linux编程笔记3【个人用】
linux·笔记