【嵌入式linux】修改网口MAC地址

本文介绍了在不同层级修改网络设备MAC地址的方法,包括应用层、kernel层及uboot层,并详细展示了通过命令行和编程接口修改MAC的具体步骤。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

使用带有网络功能的设备时,如果局域网内可能会连接很多台设备,为了保证网络通信正常,要确保每台设备的硬件MAC地址都不相同,一般在批量生产的时候可以根据产品序列号(sn号)固化每台设备的MAC地址,防止出现MAC冲突的问题。

修改MAC地址方法:
一、应用层
1.通过命令ifconfig修改:
ifconfig xxx(网口名) down
ifconfig xxx(网口名) hw ether xx:xx:xx:xx:xx:xx(MAC地址)
ifconfig xxx up(重启网卡)

如下图
在这里插入图片描述

2.通过linux的socket接口修改(上面命令行本质上也是这样实现的)
如下部分实现代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <net/if.h>
#include <arpa/inet.h>
#include <sys/ioctl.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <linux/route.h>


int fd = -1;
struct ifreq ifr;

fd = socket(AF_INET, SOCK_DGRAM, 0);
if(fd < 0)
{
	printf("socket error\n");
}
memset(&ifr,0,sizeof(ifr));
strcpy(ifr.ifr_name,eth_name);
setsockopt(fd,SOL_SOCKET,SO_BINDTODEVICE,(char*)&ifr,sizeof(ifr));
if(ioctl(fd,SIOCGIFHWADDR,&ifr) < 0)  //获得MAC地址
{
	printf("ioctl SIOCSIFHWADDR error\n");
}
unsigned char mac[6]={12,23,34,45,56,67};
memcpy(ifr.ifr_hwaddr,mac,6);
if(ioctl(fd,SIOCSIFHWADDR,&ifr) < 0)  //设置MAC地址
{
	printf("ioctl SIOCSIFHWADDR error\n");
}

在应用层修改MAC比较灵活方便,由app可随时设置和管理MAC地址。

二、kernel层修改
修改网卡的设备驱动,原理和下面uboot修改相似。在此不叙述。

三、uboot修改
kernel的网卡设备驱动最终是根据读网卡的MAC地址寄存器来向上层传递MAC地址的,因此在uboot修改相关寄存器也可以实现修改MAC地址。

这里以2019.01的uboot为例子。首先在uboot源码找到网络设备驱动的核心层,看看是怎么配置和初始化网络设备的。
uboot/ /net/eth-uclass.c (新的驱动基本使用DM框架,驱动核心层一般在xxx-uclass.c,老的在xxx_legacy.c一般不使用)。在网口eth_post_probe入口函数找到关于MAC地址的配置接口。

/* Check if the device has a MAC address in ROM */
if (eth_get_ops(dev)->read_rom_hwaddr)
	eth_get_ops(dev)->read_rom_hwaddr(dev);

eth_env_get_enetaddr_by_index("eth", dev->seq, env_enetaddr);
if (!is_zero_ethaddr(env_enetaddr)) {
	if (!is_zero_ethaddr(pdata->enetaddr) &&
	memcmp(pdata->enetaddr, env_enetaddr, ARP_HLEN)) {
		printf("\nWarning: %s MAC addresses don't match:\n",
		dev->name);
		printf("Address in ROM is %pM\n",
		pdata->enetaddr);
		printf("Address in environment is %pM\n",
		env_enetaddr);
}

/* Override the ROM MAC address */
memcpy(pdata->enetaddr, env_enetaddr, ARP_HLEN);
} else if (is_valid_ethaddr(pdata->enetaddr)) {
	eth_env_set_enetaddr_by_index("eth", dev->seq, pdata->enetaddr);
	printf("\nWarning: %s using MAC address from ROM\n",
	dev->name);
} else if (is_zero_ethaddr(pdata->enetaddr) ||
!is_valid_ethaddr(pdata->enetaddr)) {
#ifdef CONFIG_NET_RANDOM_ETHADDR
net_random_ethaddr(pdata->enetaddr);
printf("\nWarning: %s (eth%d) using random MAC address - %pM\n",
dev->name, dev->seq, pdata->enetaddr);
#else
printf("\nError: %s address not set.\n",
dev->name);
return -EINVAL;
#endif
}

可得出在uboot设置MAC地址大概流程:通过在外部存储介质(ROM)、环境变量env读取或随机生成MAC地址保存到匹配到的网卡外设的驱动数据dev->platdata->enetaddr(6个字节,16进制数格式存放)里,在后续uboot初始化硬件外设的时候调用向MAC控制器的相关寄存器写MAC地址的接口,实现将enetaddr写入硬件。这里有三种方法获得MAC地址:
1.若设备驱动有实现read_rom_hwaddr接口则执行,实现从ROM中读取MAC地址保存到设备的驱动数据eth_pdata结构体中。这里以zynq-7010平台、ROM为qspi flash W25q512为例,网口的设备驱动在uboot/drivers/net/zynq_gem.c里面实现。

static const struct eth_ops zynq_gem_ops = {
.start = zynq_gem_init,
.send = zynq_gem_send,
.recv = zynq_gem_recv,
.free_pkt = zynq_gem_free_pkt,
.stop = zynq_gem_halt,
.write_hwaddr = zynq_gem_setup_mac,
.read_rom_hwaddr = zynq_gem_read_rom_mac,
};

__weak int zynq_board_read_rom_ethaddr(unsigned char *ethaddr)
{
	return -ENOSYS;
}


static int zynq_gem_read_rom_mac(struct udevice *dev)
{
	struct eth_pdata *pdata = dev_get_platdata(dev);
	
	if (!pdata)
		return -ENOSYS;
	
	return zynq_board_read_rom_ethaddr(pdata->enetaddr);
}

*若有定义和实现int zynq_board_read_rom_ethaddr(unsigned char ethaddr
)则会调用该接口实现从rom读取MAC地址,否则执行上述末默认弱连接的接口。这里在
uboot/board/xilinx/zynq/board.c自定义该接口的实现。

int zynq_board_read_rom_ethaddr(unsigned char *ethaddr)
{
	int ret = 0;
	/*这里为了方便测试,手动设置MAC地址*/
	#if 1
	ethaddr[0]==0x00;
	ethaddr[1]==0xa8;
	ethaddr[2]==0x22;
	ethaddr[3]==0x12;
	ethaddr[4]==0x34;
	ethaddr[5]==0x56#endif
	/*实际使用下面接口读取spi flash特定区域的内容,可自由添加读写flash、emmc、epprom等ROM的接口实现在ROM读取mac地址*/
	#if 0 
	unsigned int mac_offset=0x210000;//在flash的0x210000存取MAC地址
	unsigned int len=6;
	ret = board_spi_flash_probe();  //匹配spi flash
	if (ret)
	{
		printf("spi_flash_probe fail \n");
		return -1;
	}
	
	ret = spi_flash_read(board_spi_flash, mac_offset, len, ethaddr); //从spi flash
	if (ret)
	{
		return -2;
	}
	
	
	#endif
	return ret;
}

2.MAC地址从环境变量env中获得。在上面的eth_env_get_enetaddr_by_index(“eth”, dev->seq, env_enetaddr)接口实现获得MAC地址,这种方法需要在env中定义ethaddr=xx:xx:xx:xx:xx:xx。

int eth_env_get_enetaddr_by_index(const char *base_name, int index,
uchar *enetaddr)
{
	char enetvar[32];
	sprintf(enetvar, index ? "%s%daddr" : "%saddr", base_name, index);
	return eth_env_get_enetaddr(enetvar, enetaddr);
}

3.当上述都不能获得有效的MAC地址,可以使用net_random_ethaddr(pdata->enetaddr);根据定时器产生的随机数生成有效、随机的MAC地址。

static inline void net_random_ethaddr(uchar *addr)
{
	int i;
	unsigned int seed = get_timer(0);
	
	for (i = 0; i < 6; i++)
	addr[i] = rand_r(&seed);
	
	addr[0] &= 0xfe; /* clear multicast bit */
	addr[0] |= 0x02; /* set local assignment bit (IEEE802) */
}

正确获取到MAC地址后写入MAC控制器相关寄存器调用流程:
uboot/common/board_r.c的initr_net()->eth-uclass.c的eth_initialize()->eth-uclass.c的eth_write_hwaddr(dev);->设备驱动zynq_gem.c的zynq_gem_ops.write_hwaddr →最后通过zynq_gem_setup_mac实现写入MAC控制器的MAC地址寄存器的操作。

static int zynq_gem_setup_mac(struct udevice *dev)
{
	u32 i, macaddrlow, macaddrhigh;
	struct eth_pdata *pdata = dev_get_platdata(dev);
	struct zynq_gem_priv *priv = dev_get_priv(dev);
	struct zynq_gem_regs *regs = priv->iobase;
	
	/* Set the MAC bits [31:0] in BOT */
	macaddrlow = pdata->enetaddr[0];
	macaddrlow |= pdata->enetaddr[1] << 8;
	macaddrlow |= pdata->enetaddr[2] << 16;
	macaddrlow |= pdata->enetaddr[3] << 24;
	
	/* Set MAC bits [47:32] in TOP */
	macaddrhigh = pdata->enetaddr[4];
	macaddrhigh |= pdata->enetaddr[5] << 8;
	
	for (i = 0; i < 4; i++) {
	writel(0, &regs->laddr[i][LADDR_LOW]);
	writel(0, &regs->laddr[i][LADDR_HIGH]);
	/* Do not use MATCHx register */
	writel(0, &regs->match[i]);
	}
	
	writel(macaddrlow, &regs->laddr[0][LADDR_LOW]);
	writel(macaddrhigh, &regs->laddr[0][LADDR_HIGH]);
	
	return 0;
}
寄存器结构体
/* Device registers */
struct zynq_gem_regs {
u32 nwctrl; /* 0x0 - Network Control reg */
u32 nwcfg; /* 0x4 - Network Config reg */
u32 nwsr; /* 0x8 - Network Status reg */
u32 reserved1;
u32 dmacr; /* 0x10 - DMA Control reg */
u32 txsr; /* 0x14 - TX Status reg */
u32 rxqbase; /* 0x18 - RX Q Base address reg */
u32 txqbase; /* 0x1c - TX Q Base address reg */
u32 rxsr; /* 0x20 - RX Status reg */
u32 reserved2[2];
u32 idr; /* 0x2c - Interrupt Disable reg */
u32 reserved3;
u32 phymntnc; /* 0x34 - Phy Maintaince reg */
u32 reserved4[18];
u32 hashl; /* 0x80 - Hash Low address reg */
u32 hashh; /* 0x84 - Hash High address reg */
#define LADDR_LOW 0
#define LADDR_HIGH 1
u32 laddr[4][LADDR_HIGH + 1]; /* 0x8c - Specific1 addr low/high reg */
u32 match[4]; /* 0xa8 - Type ID1 Match reg */
u32 reserved6[18];
#define STAT_SIZE 44
u32 stat[STAT_SIZE]; /* 0x100 - Octects transmitted Low reg */
u32 reserved9[20];
u32 pcscntrl;
u32 rserved12[36];
u32 dcfg6; /* 0x294 Design config reg6 */
u32 reserved7[106];
u32 transmit_q1_ptr; /* 0x440 - Transmit priority queue 1 */
u32 reserved8[15];
u32 receive_q1_ptr; /* 0x480 - Receive priority queue 1 */
u32 reserved10[17];
u32 upper_txqbase; /* 0x4C8 - Upper tx_q base addr */
u32 reserved11[2];
u32 upper_rxqbase; /* 0x4D4 - Upper rx_q base addr */
};

下图为MAC控制器的寄存器表
在这里插入图片描述

这里分别测试了从ROM读取和在env定义MAC地址的两种情况
1.在flash读取MAC地址(上述方法1)。重新编译、烧录UBOOT。系统启动后网卡配置如下,MAC已成功修改为00:A8:22:12:34:56如下图
在这里插入图片描述
2.在env镜像添加MAC地址00:0A:22:11:22:33。如下。重新编译生成env镜像存入flash中,然后重新启动。
如下图定义ethaddr环境变量属性
在这里插入图片描述
uboot启动时提示MAC定义冲突的警告,从之前的流程分析这里会优先使用env的MAC地址
在这里插入图片描述
系统启动后如下MAC地址为env定义的ethaddr,验证成功。
在这里插入图片描述

<think>嗯,用户问的是如何编写应用程序来测试嵌入式Linux开发板的网口。我需要先理清楚这个问题涉及的关键点。首先,测试网口通常包括哪些方面呢?可能包括网络接口的基本功能测试,比如连通性、带宽、丢包率,还有更底层的比如MAC和PHY层的测试,以及协议兼容性等。 接下来,用户可能是在进行嵌入式开发,需要确保硬件和驱动程序的正常工作。他们可能已经完成了硬件连接,现在需要编写测试程序来验证网口的性能。这时候,我需要考虑不同的测试方法,比如使用现有的工具还是自己编写应用程序。 首先,使用现有的工具可能更高效,比如用ping测试连通性,iperf3测试带宽,netcat进行数据传输测试。这些工具已经存在,用户可能只需要写一个脚本来自动化这些测试。不过,用户特别提到“编写应用程序”,所以可能需要自己实现这些功能,而不是依赖外部工具。 那自己编写应用程序的话,可能需要用C语言,因为嵌入式开发中C比较常见。需要考虑使用socket编程,比如创建TCP或UDP的socket,发送和接收数据。对于连通性测试,可以尝试建立TCP连接或者发送ICMP包,但ICMP在普通用户权限下可能受限,所以TCP可能更可靠。 另外,需要考虑多线程或异步处理,比如一边发送数据,一边接收,同时统计丢包率和延迟。对于带宽测试,可能需要大量数据传输,计算传输速率。这时候要注意缓冲区的大小,以及如何准确测量时间。 用户可能还需要测试不同协议,比如同时测试TCP和UDP的性能差异。UDP不保证交付,所以需要自己实现丢包检测和重传机制,或者至少统计丢包情况。 还有,错误处理也很重要。比如网线断开,网络不可达等情况,程序需要能捕获这些错误并给出提示。此外,获取网络接口的状态信息,比如通过ioctl或者读取/proc/net/dev文件,来查看接口的统计信息,比如发送接收的数据包数量,错误计数等。 自动化测试脚本的话,可以结合Shell和Python,调用各种工具,并解析输出结果。但如果是编写独立的应用程序,可能需要更底层的代码。 另外,用户可能需要交叉编译这个测试程序,因为目标平台是嵌入式Linux,开发环境可能是在主机上。所以需要确保代码的可移植性,避免依赖特定平台库,或者正确配置交叉编译工具链。 测试步骤方面,可能需要先确保网络连接正确,比如网线插入,配置IP地址,然后分步骤测试各个功能。比如先测试链路状态,然后测试连通性,接着是带宽和压力测试。 可能还需要考虑多网口的情况,如果有多个以太网接口,需要分别测试每个接口。这时候程序可能需要接受参数指定要测试的网口名称,比如eth0,eth1等。 总结下来,编写这样一个测试应用程序需要考虑以下几个方面: 1. 使用socket API进行网络通信测试。 2. 实现基本连通性测试(TCP连接或ICMP回显)。 3. 带宽测试,通过传输大量数据计算速率。 4. 丢包率测试,特别是在UDP模式下。 5. 获取网络接口的统计信息以确认数据传输情况。 6. 错误处理和状态检查,确保接口和驱动正常工作。 7. 可能的自动化脚本整合或独立应用程序开发。 现在需要把这些思路整理成一个结构化的回答,涵盖用户的需求,可能他们需要示例代码,比如用C语言写一个简单的测试程序,或者用Python脚本调用现有工具。同时要提醒注意事项,比如权限问题、交叉编译、测试环境配置等。</think>测试嵌入式Linux开发板的网络接口通常涉及硬件功能验证、驱动测试、协议栈及性能评估。以下是分步骤的测试方法及示例: ### 一、基础环境准备 1. **硬件连接** - 使用网线直连开发板与主机,或通过交换机连接 - 确保PHY芯片供电正常(可通过`dmesg | grep eth`查看驱动加载情况) 2. **IP配置** ```bash # 开发板端设置静态IP ifconfig eth0 192.168.1.100 netmask 255.255.255.0 up # 主机端设置同网段IP ifconfig enp0s3 192.168.1.200/24 ``` ### 二、自动化测试脚本示例(Python) ```python import subprocess import time def test_ping(ip, count=10): loss = subprocess.check_output(f"ping -c {count} {ip} | grep loss", shell=True) print(f"Ping测试结果:{loss.decode()}") def test_iperf(server_ip, duration=10): # 开发板端启动服务端:iperf3 -s subprocess.Popen(["iperf3", "-s", "-D"]) # 主机端作为客户端测试 result = subprocess.check_output(f"iperf3 -c {server_ip} -t {duration}", shell=True) print(f"带宽测试结果:\n{result.decode()}") def stress_test(ip): start = time.time() # 持续高压传输60秒 subprocess.run(f"iperf3 -c {ip} -t 60 -P 8", shell=True) # 检查丢包 eth_stats = subprocess.check_output("ifconfig eth0 | grep 'dropped'", shell=True) print(f"压力测试后统计:\n{eth_stats.decode()}") if __name__ == "__main__": target_ip = "192.168.1.100" test_ping(target_ip) test_iperf(target_ip) stress_test(target_ip) ``` ### 三、底层C程序测试(MAC/PHY层) ```c #include <stdio.h> #include <stdlib.h> #include <linux/sockios.h> #include <net/if.h> #include <sys/ioctl.h> #include <string.h> void check_eth_status(const char *ifname) { struct ifreq ifr; int sock = socket(AF_INET, SOCK_DGRAM, 0); strncpy(ifr.ifr_name, ifname, IFNAMSIZ); if (ioctl(sock, SIOCGIFFLAGS, &ifr) != -1) { printf("[PHY状态] %s\n", (ifr.ifr_flags & IFF_RUNNING) ? "已连接" : "未连接"); } if (ioctl(sock, SIOCGIFHWADDR, &ifr) == 0) { unsigned char *mac = (unsigned char *)ifr.ifr_hwaddr.sa_data; printf("[MAC地址] %02X:%02X:%02X:%02X:%02X:%02X\n", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); } close(sock); } int main() { check_eth_status("eth0"); return 0; } ``` ### 四、关键测试指标 1. **协议栈测试** - TCP重传率:`cat /proc/net/netstat | grep TcpExt | awk '{print $15}'` - ARP缓存验证:`arp -n` 2. **中断性能** ```bash # 查看网卡中断计数 watch -d -n 1 "cat /proc/interrupts | grep eth0" ``` 3. **DMA测试** - 使用`memtester`工具检测内存完整性: ```bash memtester 256M 3 ``` ### 五、高级测试场景 1. **VLAN测试** ```bash vconfig add eth0 100 ifconfig eth0.100 10.0.100.2/24 ``` 2. **硬件时间戳** ```c // 启用硬件时间戳 setsockopt(sock, SOL_SOCKET, SO_TIMESTAMPING, (void *)&opt, sizeof(opt)); ``` ### 六、常见问题处理 1. **CRC错误** ```bash ethtool -S eth0 | grep crc # 若持续增长,检查网线质量或电磁干扰 ``` 2. **速度协商异常** ```bash ethtool -s eth0 speed 100 duplex full autoneg off ``` 3. **看门狗超时** ```dmesg [ +0.000001] NETDEV WATCHDOG: eth0 (macb): transmit queue 0 timed out # 需检查DMA配置或降低传输压力 ``` 建议结合`ethtool`、`tshark`进行深度协议分析,并使用`stress-ng`工具制造系统负载测试边界条件。实际测试中建议持续运行24小时压力测试以发现潜在硬件问题。
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

Yeah__binbin

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值