从老式MP3到行车记录仪:聊聊FAT16/FAT32目录结构如何影响嵌入式设备开发与选型
2026/5/11 18:04:34
想象一下, 如果网络世界是一个庞大的城市交通系统, 那么MAC(Media Access Control)层就是这座城市的交通管理局. 它位于OSI模型的第二层(数据链路层), 负责管理 “谁在什么时候可以占用道路(物理介质)” 以及 “如何识别不同的车辆(设备)”
在Linux的网络协议栈中, MAC层坐落在网络层(IP层)和物理层之间, 扮演着承上启下的关键角色:
应用层 (HTTP, FTP) → 传输层 (TCP/UDP) → 网络层 (IP) → [MAC层] → 物理层没有MAC层, 网络世界会陷入混乱:
MAC地址是一个48位(6字节)的全局唯一标识符, 格式为XX:XX:XX:XX:XX:XX. 它分为两部分:
// Linux内核中的MAC地址表示structmac_addr{unsignedcharaddr[6];// 6字节MAC地址};// 更常见的表示方式(在net/ethernet.h中)typedefstruct{__u8 addr[ETH_ALEN];// ETH_ALEN = 6}eth_addr_t;生活比喻: MAC地址就像车辆的VIN(车辆识别号)——全球唯一, 出厂时固化, 即使车辆从北京开到上海, 这个标识也不会改变
以太网帧是MAC层的基本传输单位, 就像标准化的快递箱:
// 以太网帧头部结构(14字节)structethhdr{unsignedcharh_dest[ETH_ALEN];// 目的MAC地址 (6字节)unsignedcharh_source[ETH_ALEN];// 源MAC地址 (6字节)__be16 h_proto;// 上层协议类型 (2字节)// 接下来是数据载荷 (46-1500字节)// 最后是CRC校验码 (4字节)};帧结构可视化:
交换机/网桥维护的MAC地址表, 记录了哪个MAC地址通过哪个端口可达:
// 简化版net_device结构(实际有200+成员)structnet_device{charname[IFNAMSIZ];// 设备名, 如"eth0"unsignedchar*dev_addr;// MAC地址指针structnet_device_ops*netdev_ops;// 设备操作函数集// 设备特性unsignedintmtu;// 最大传输单元unsignedshorttype;// 设备类型unsignedcharaddr_len;// MAC地址长度// 统计信息structrtnl_link_stats64stats;// 链表管理structlist_headdev_list;structhlist_nodename_hlist;// 协议相关structnet_device*master;// 主设备(用于桥接、绑定)structnetdev_queue*_tx;// 发送队列};// sk_buff关键结构(数据包在协议栈中的载体)structsk_buff{// 数据区管理unsignedchar*head;// 数据区起始位置unsignedchar*data;// 当前协议层数据起始位置unsignedchar*tail;// 当前协议层数据结束位置unsignedchar*end;// 数据区结束位置// 协议信息__be16 protocol;// 上层协议(从MAC头解析)// 网络设备相关structnet_device*dev;// 接收/发送的设备// MAC层特定union{__be16 inner_protocol;__u16 inner_transport_header;};// 链表管理structsk_buff*next;structsk_buff*prev;};数据结构关系图:
接收流程关键代码:
// 网络设备驱动接收函数示例staticintethernet_receive(structsk_buff*skb,structnet_device*dev){structethhdr*eth=(structethhdr*)skb->data;// 检查帧长度if(skb->len<sizeof(structethhdr)){dev->stats.rx_length_errors++;kfree_skb(skb);returnNET_RX_DROP;}// 更新统计信息dev->stats.rx_packets++;dev->stats.rx_bytes+=skb->len;// 移除以太网头部skb_pull(skb,sizeof(structethhdr));// 根据协议类型分发到上层switch(ntohs(eth->h_proto)){caseETH_P_IP:returnnetif_receive_skb(skb);// 传递给IP层caseETH_P_ARP:returnarp_rcv(skb);// 传递给ARP模块caseETH_P_8021Q:returnvlan_rcv(skb);// VLAN处理default:kfree_skb(skb);returnNET_RX_DROP;}}// 发送流程关键函数intdev_queue_xmit(structsk_buff*skb){structnet_device*dev=skb->dev;structnetdev_queue*txq;// 选择发送队列txq=netdev_pick_tx(dev,skb,NULL);// 检查流控if(netif_tx_queue_stopped(txq)){dev->stats.tx_dropped++;kfree_skb(skb);returnNET_XMIT_DROP;}// 添加以太网头部structethhdr*eth=(structethhdr*)skb_push(skb,ETH_HLEN);memcpy(eth->h_dest,neigh->ha,ETH_ALEN);memcpy(eth->h_source,dev->dev_addr,ETH_ALEN);eth->h_proto=htons(ETH_P_IP);// 调用驱动发送函数intret=dev->netdev_ops->ndo_start_xmit(skb,dev);if(ret==NETDEV_TX_OK){dev->stats.tx_packets++;dev->stats.tx_bytes+=skb->len;}else{dev->stats.tx_errors++;}returnret;}地址学习实现:
// 简化的MAC地址学习逻辑voidlearn_mac_address(structswitch*sw,unsignedchar*src_mac,intin_port){structmac_table_entry*entry;// 查找现有条目entry=find_mac_entry(sw->mac_table,src_mac);if(entry){// 更新现有条目(端口可能变化)if(entry->port!=in_port){entry->port=in_port;entry->timestamp=jiffies;// 更新时间戳entry->static_entry=0;// 动态学习}}else{// 创建新条目entry=kmalloc(sizeof(*entry),GFP_KERNEL);memcpy(entry->mac_addr,src_mac,ETH_ALEN);entry->port=in_port;entry->timestamp=jiffies;entry->static_entry=0;// 添加到哈希表add_mac_entry(sw->mac_table,entry);// 如果表满, 删除最老的条目if(sw->mac_count>=MAX_MAC_ENTRIES){remove_oldest_entry(sw->mac_table);}else{sw->mac_count++;}}}802.1Q VLAN在标准以太网帧中插入4字节的VLAN标签:
structvlan_ethhdr{unsignedcharh_dest[ETH_ALEN];unsignedcharh_source[ETH_ALEN];__be16 h_vlan_proto;// 总是0x8100__be16 h_vlan_TCI;// VLAN标签信息__be16 h_vlan_encapsulated_proto;// 原始协议类型// 数据载荷};// VLAN TCI字段分解structvlan_tci{uint16_tpcp:3;// 优先级代码点uint16_tdei:1;// 丢弃资格指示器uint16_tvid:12;// VLAN ID (1-4094)};生活比喻: VLAN就像一栋大楼里的不同公司, 物理上在同一个建筑里(同一台交换机), 但逻辑上完全隔离, 各有自己的电梯和门禁系统
允许一个物理网卡拥有多个MAC地址:
# 创建MACVLAN接口iplinkaddlinketh0 macvlan0typemacvlan mode bridgeiplinksetmacvlan0 address 00:11:22:33:44:55iplinksetmacvlan0 up内核数据结构:
structmacvlan_port{structnet_device*dev;// 主设备structhlist_headvlan_hash[MACVLAN_HASH_SIZE];structlist_headvlans;// macvlan设备列表intcount;// macvlan设备数量};structmacvlan_dev{structnet_device*dev;// macvlan虚拟设备structmacvlan_port*port;// 所属端口structlist_headlist;enummacvlan_modemode;// 模式: private/vepa/bridge/passthru};创建一个简单的用户空间桥接器, 演示MAC层帧转发的基本原理
#include<stdio.h>#include<stdlib.h>#include<string.h>#include<unistd.h>#include<sys/socket.h>#include<linux/if_packet.h>#include<net/ethernet.h>#include<net/if.h>#defineMAX_MAC_ENTRIES1024#defineAGING_TIME300// 5分钟老化时间structmac_table_entry{unsignedcharmac[6];intport;// 0: eth0, 1: eth1time_ttimestamp;structmac_table_entry*next;};structbridge{intsockfd[2];// 两个端口的socketchar*ifnames[2];// 接口名structmac_table_entry*mac_table[MAX_MAC_ENTRIES];};// 计算MAC地址哈希值unsignedintmac_hash(constunsignedchar*mac){unsignedinthash=0;for(inti=0;i<6;i++){hash=(hash<<5)+hash+mac[i];}returnhash%MAX_MAC_ENTRIES;}// 学习MAC地址voidlearn_mac(structbridge*br,constunsignedchar*mac,intport){unsignedinthash=mac_hash(mac);structmac_table_entry*entry=br->mac_table[hash];// 查找现有条目while(entry){if(memcmp(entry->mac,mac,6)==0){// 更新现有条目entry->port=port;entry->timestamp=time(NULL);return;}entry=entry->next;}// 创建新条目entry=malloc(sizeof(structmac_table_entry));memcpy(entry->mac,mac,6);entry->port=port;entry->timestamp=time(NULL);entry->next=br->mac_table[hash];br->mac_table[hash]=entry;}// 查找MAC地址intfind_mac(structbridge*br,constunsignedchar*mac){unsignedinthash=mac_hash(mac);structmac_table_entry*entry=br->mac_table[hash];while(entry){if(memcmp(entry->mac,mac,6)==0){if(time(NULL)-entry->timestamp>AGING_TIME){// 条目已老化return-1;}returnentry->port;}entry=entry->next;}return-1;// 未找到}// 转发帧voidforward_frame(structbridge*br,intin_port,unsignedchar*frame,intlen){structethhdr*eth=(structethhdr*)frame;intout_port;// 学习源MAC地址learn_mac(br,eth->h_source,in_port);// 检查目的MACif(memcmp(eth->h_dest,"\xff\xff\xff\xff\xff\xff",6)==0){// 广播帧: 泛洪到所有其他端口out_port=1-in_port;}else{// 单播帧: 查找目的端口out_port=find_mac(br,eth->h_dest);if(out_port<0){// 未知MAC: 泛洪out_port=1-in_port;}}// 发送帧(避免回环)if(out_port!=in_port){send(br->sockfd[out_port],frame,len,0);printf("转发帧: 端口%d -> 端口%d\n",in_port,out_port);}}intmain(intargc,char*argv[]){structbridgebr;unsignedcharbuffer[2048];// 初始化桥接器strcpy(br.ifnames[0],"eth0");strcpy(br.ifnames[1],"eth1");// 创建raw socketfor(inti=0;i<2;i++){br.sockfd[i]=socket(AF_PACKET,SOCK_RAW,htons(ETH_P_ALL));// 绑定到接口structsockaddr_llsll;memset(&sll,0,sizeof(sll));sll.sll_family=AF_PACKET;sll.sll_ifindex=if_nametoindex(br.ifnames[i]);sll.sll_protocol=htons(ETH_P_ALL);bind(br.sockfd[i],(structsockaddr*)&sll,sizeof(sll));}printf("桥接器启动, 监听 %s 和 %s\n",br.ifnames[0],br.ifnames[1]);// 主循环fd_set readfds;while(1){FD_ZERO(&readfds);FD_SET(br.sockfd[0],&readfds);FD_SET(br.sockfd[1],&readfds);intmax_fd=(br.sockfd[0]>br.sockfd[1])?br.sockfd[0]:br.sockfd[1];select(max_fd+1,&readfds,NULL,NULL,NULL);for(inti=0;i<2;i++){if(FD_ISSET(br.sockfd[i],&readfds)){intlen=recv(br.sockfd[i],buffer,sizeof(buffer),0);if(len>0){forward_frame(&br,i,buffer,len);}}}}return0;}| 工具 | 用途 | 示例命令 |
|---|---|---|
| ip | 综合网络配置工具 | ip link show,ip addr add |
| ethtool | 网卡信息查询 | ethtool eth0,ethtool -S eth0 |
| bridge | 桥接管理 | bridge fdb show,bridge vlan show |
| mac | MAC地址管理 | mac address show |
| tc | 流量控制 | tc qdisc show dev eth0 |
# 查看内核MAC地址表(桥接)bridge fdb show# 查看ARP缓存(IP-MAC映射)ipneigh show# 查看网络接口统计ip-slinkshow eth0# 抓取指定数量的以太网帧tcpdump-ieth0-c10ether# 抓取特定MAC地址的流量tcpdump-ieth0 etherhost00:11:22:33:44:55# 抓取广播帧tcpdump-ieth0 ether broadcast# 详细显示MAC层信息tcpdump-ieth0-e-vv# 查看MAC层相关统计cat/sys/class/net/eth0/statistics/rx_packetscat/sys/class/net/eth0/statistics/tx_packets# 启用网络调试日志echo7>/proc/sys/net/core/message_costecho7>/proc/sys/net/core/message_burst# 查看网络设备注册信息dmesg|grep-ieth# 实时监控网络接口iftop-ieth0# 监控网络队列cat/proc/net/dev_queue# 监控网络软中断watch-n1'cat /proc/softirqs | grep NET'Linux网络栈采用经典的分层设计, 每层只关心自己的职责:
现代Linux MAC层大量使用无锁数据结构:
// NAPI处理循环voidnet_rx_action(structsoftirq_action*h){structlist_head*list=&__get_cpu_var(softnet_data).poll_list;while(!list_empty(list)){structnapi_struct*n=list_first_entry(list,structnapi_struct,poll_list);intwork=0;intweight=n->weight;// 处理数据包work=n->poll(n,weight);if(work<weight){// 处理完成, 关闭轮询__napi_complete(n);}}}# 查看网卡队列数量ethtool-leth0# 设置队列数量ethtool-Leth0 combined8# 配置RSS哈希字段ethtool-Neth0 rx-flow-hash udp4 sdfn# 启用MAC地址过滤iplinksetdev eth0 address 00:11:22:33:44:55iplinksetdev eth0 addrgenmode none# 配置ebtables防止MAC欺骗ebtables-AFORWARD-s!00:11:22:33:44:55-jDROP# 创建VLAN接口iplinkaddlinketh0 name eth0.100typevlanid100iplinkaddlinketh0 name eth0.200typevlanid200# 配置VLAN过滤bridge vlanadddev eth0 vid100bridge vlanadddev eth0 vid200| 概念 | 比喻 | 作用 | 关键技术 |
|---|---|---|---|
| MAC地址 | 车辆VIN号 | 设备唯一标识 | 48位地址, OUI分配 |
| 以太网帧 | 标准快递箱 | 数据传输单元 | 帧结构, CRC校验 |
| MAC地址表 | 交通导航图 | 转发决策依据 | 学习, 老化, 泛洪 |
| VLAN | 大楼内分公司 | 逻辑隔离 | 802.1Q标签 |
| 链路聚合 | 多车道高速 | 带宽聚合 | LACP, 负载均衡 |
net_device抽象不同硬件