ARTICLE DETAIL

资讯详情

深耕网站建设、视觉设计与SEO优化的一线实战洞察。

Debian 13 上编译 RT 内核与 IgH EtherCAT 主站实战

Debian 13 上编译 RT 内核与 IgH EtherCAT 主站实战

1. 为什么要在 Debian 13 上折腾 RT 内核与 EtherCAT 主站

工业现场总线这块,EtherCAT 算是这几年的热门选手。它不像传统以太网那样靠 TCP/IP 协议栈层层封装,而是直接在以太网帧上跑自己的协议,靠"飞读飞写"(Processing on the Fly)的机制把每个从站的数据在帧经过时顺手处理掉,一圈下来延迟能压到微秒级。这个特性决定了它对主站的实时性要求极高——主站如果抖动个几百微秒,整个控制环就可能崩掉。

我这次做的项目,就是在一台 x86 工控机上,用 Debian 13 打底,编译一个带 RT 补丁的内核,再把 IgH EtherCAT Master 跑起来,最终实现对一个伺服驱动器的周期位置控制。选 Debian 13 是因为它自带的 GCC、glibc 版本比较新,编译 RT 内核和 IgH 源码时少踩很多依赖坑;选 RT 内核是因为标准 Linux 内核的调度延迟在几百微秒到毫秒级波动,根本喂不饱 EtherCAT 的 1ms 甚至 500us 周期。

这套方案适合谁?如果你正在做运动控制、机器人关节驱动、CNC 或者任何需要硬实时的工业项目,又不想花大价钱买商业主站授权,那 IgH 这条开源路线值得走一遍。当然,前提是你得能接受命令行编译内核、手动配置网卡驱动这些"脏活"。下面我把整个流程拆开讲,包括我踩过的坑和最后验证有效的参数。

2. 整体方案设计与选型背后的逻辑

2.1 为什么是 Debian 13 而不是 Ubuntu 或 CentOS

Debian 13(代号 Trixie)目前是 testing 分支,但它的软件包版本对编译 RT 内核非常友好。内核源码树用的是 6.12 系列,RT 补丁(PREEMPT_RT)已经基本合入主线,打补丁的冲突比 5.x 时代少了很多。Ubuntu 虽然用户多,但它的 HWE 内核和 RT 补丁经常对不上号,编译时容易报patch does not apply。CentOS 系更麻烦,glibc 版本偏旧,IgH 的configure脚本有时会卡在clock_gettime检测上。

另一个原因是 Debian 的apt源里直接有linux-source包,下载下来就是干净的内核源码,不用去 kernel.org 手动拉。我实测下来,Debian 13 的linux-source-6.12配合patch-6.12-rt补丁,一次打成功,没有出现 hunk 失败。

2.2 RT 内核补丁的选择:PREEMPT_RT 还是 Xenomai

工业实时方案里,Xenomai 是另一条路,它用双内核架构(Cobalt 核 + Linux 核)来保证硬实时。但 Xenomai 的 IgH 适配需要额外打xenomai-igh补丁,配置复杂度高一个量级。PREEMPT_RT 则是把 Linux 内核本身改造成可抢占的,虽然最坏延迟比 Xenomai 稍大(实测在 30-50us 左右),但对于 1ms 周期的 EtherCAT 应用完全够用。

我选 PREEMPT_RT 的另一个理由是调试方便。Xenomai 的双内核一旦出问题,日志分散在两个核里,排查起来很痛苦。PREEMPT_RT 就是标准内核,ftrace、perf这些工具直接能用,cyclictest跑出来的延迟直方图一目了然。

2.3 IgH EtherCAT Master 的版本考量

IgH 官方仓库的稳定版是 1.5.2,但它的网卡驱动只支持到 Linux 4.x 的内核接口。Debian 13 的内核是 6.12,直接编译 1.5.2 会报net_device结构体成员找不到。解决办法有两个:一是用社区维护的igh-ethercat-master分支(比如某些工业发行版里的补丁版),二是自己改驱动代码适配新内核。

我选了后者,因为改驱动虽然麻烦,但能彻底搞清楚 IgH 和网卡驱动的交互逻辑。具体改动集中在master/ethernet.c和devices/目录下的网卡驱动文件,主要是把ndo_start_xmit的返回值处理、netif_napi_add的参数列表按 6.12 的 API 调整。这部分后面会详细说。

3. 核心细节解析与实操要点

3.1 内核编译的关键配置项

RT 内核的配置不是简单开个CONFIG_PREEMPT_RT就完事。下面这几个选项直接决定 EtherCAT 主站的抖动表现:

配置项推荐值作用
CONFIG_PREEMPT_RTy开启完全可抢占
CONFIG_HZ1000提高时钟中断频率,降低调度粒度
CONFIG_HZ_1000y同上
CONFIG_NO_HZ_FULLy隔离 CPU 上的时钟中断
CONFIG_HIGH_RES_TIMERSy高精度定时器,IgH 依赖
CONFIG_CPU_ISOLATIONy允许isolcpus隔离核心
CONFIG_RCU_NOCB_CPUy把 RCU 回调移出实时核

CONFIG_HZ这里有个坑:很多人以为设成 100 或 250 能减少开销,但 EtherCAT 的 DC(分布式时钟)同步需要高精度时间戳,HZ=1000下clock_gettime的分辨率才能到纳秒级。我试过HZ=250,cyclictest的最大延迟直接飙到 200us 以上,换成 1000 后降到 40us 左右。

3.2 网卡驱动的适配要点

IgH 主站需要直接操作网卡的 DMA 描述符,所以不能用内核自带的e1000e或igb驱动,得用 IgH 自带的ec_e1000e或ec_igb。这些驱动在 6.12 内核上编译时,主要报错集中在三处:

第一处是netif_napi_add的参数。6.12 里这个函数的第三个参数从int (*poll)(struct napi_struct *, int)变成了int (*poll)(struct napi_struct *, int),但权重参数weight被移到了netif_napi_add_weight里。改法是把netif_napi_add(dev, napi, poll, 64)改成netif_napi_add_weight(dev, napi, poll, 64)。

第二处是ndo_start_xmit的返回值。老内核返回NETDEV_TX_OK或NETDEV_TX_BUSY,新内核要求返回netdev_tx_t类型,并且NETDEV_TX_BUSY被废弃了。IgH 的驱动里如果返回NETDEV_TX_BUSY,得改成NETDEV_TX_OK并手动处理队列停止。

第三处是skb_shinfo的访问。6.12 里skb_shinfo变成了skb_shinfo宏,但内部字段nr_frags的类型从int变成了unsigned int,比较时要注意符号问题。

注意:改驱动时一定要用git diff记录每一处改动,因为 IgH 的驱动代码和内核版本强相关,下次换内核还得重新适配。

3.3 实时性调优的实操参数

内核编译完只是第一步,系统启动后的调优才是决定抖动大小的关键。我用的参数如下:

# 内核启动参数 isolcpus=2,3 nohz_full=2,3 rcu_nocbs=2,3

这行参数把 CPU 2 和 3 从调度器里隔离出来,专门跑 EtherCAT 主站线程。nohz_full让这两个核上的时钟中断尽量少触发,rcu_nocbs把 RCU 回调移到其他核。

启动后还要做几件事:

  1. 关闭 CPU 频率调节:cpupower frequency-set -g performance
  2. 关闭超线程:BIOS 里禁用,或者用nosmt启动参数
  3. 把 EtherCAT 主站线程绑到隔离核:taskset -c 2 ./my_ecat_app
  4. 设置线程优先级:chrt -f 80 ./my_ecat_app

我实测下来,不做这些调优时cyclictest最大延迟在 150us 左右,做完后能压到 35us。对于 1ms 周期的 EtherCAT,这个余量足够了。

4. 实操过程与核心环节实现

4.1 编译 RT 内核的完整步骤

先装依赖:

apt update apt install build-essential libncurses-dev bison flex libssl-dev libelf-dev \ git wget patch bc rsync kmod cpio debhelper

下载内核源码和 RT 补丁:

cd /usr/src apt source linux-source-6.12 wget https://cdn.kernel.org/pub/linux/kernel/projects/rt/6.12/patch-6.12-rt.patch.xz xz -d patch-6.12-rt.patch.xz

打补丁:

cd linux-source-6.12 patch -p1 < ../patch-6.12-rt.patch

如果补丁有冲突,用patch --dry-run先试跑,看哪些文件失败。我这次只在一个Makefile的版本号行上冲突,手动改一下就行。

配置内核:

cp /boot/config-$(uname -r) .config make olddefconfig make menuconfig

在menuconfig里按前面表格的配置项逐项检查。特别注意CONFIG_PREEMPT_RT在General setup -> Preemption Model里选Fully Preemptible Kernel (Real-Time)。

编译:

make -j$(nproc) deb-pkg

用deb-pkg而不是make install,因为前者会生成.deb包,方便在多台机器上部署。编译时间取决于 CPU,我用的 8 核机器大概 25 分钟。

安装:

dpkg -i ../linux-image-6.12-rt_*.deb ../linux-headers-6.12-rt_*.deb update-grub reboot

重启后uname -a应该能看到6.12-rt字样。

4.2 编译 IgH EtherCAT Master

从官方仓库拉源码:

git clone https://gitlab.com/etherlab.org/ethercat.git cd ethercat git checkout stable-1.5

配置:

./bootstrap ./configure --prefix=/usr/local/ethercat \ --with-linux-dir=/usr/src/linux-source-6.12 \ --enable-generic \ --enable-8139too=no \ --enable-e1000e=yes \ --enable-igb=yes

--enable-generic是通用网卡驱动,适合没有专用驱动的场景,但性能不如e1000e或igb。我用的 Intel I210 网卡,对应igb驱动。

编译:

make -j$(nproc) make modules make install

make modules会编译网卡驱动模块,make install把主站库和工具装到/usr/local/ethercat。

加载模块:

modprobe ec_master modprobe ec_igb

如果ec_igb加载失败,用dmesg | tail看报错。常见问题是网卡被内核自带的igb驱动占用了,需要先rmmod igb。

4.3 配置主站和从站

主站配置文件在/usr/local/ethercat/etc/sysconfig/ethercat,关键内容:

MASTER0_DEVICE="00:1b:21:xx:xx:xx" DEVICE_MODULES="igb"

MASTER0_DEVICE填网卡的 MAC 地址,用ip link show查。

启动主站:

/usr/local/ethercat/etc/init.d/ethercat start

用ethercat master命令查看状态,应该输出Master0和Phase: Idle。

扫描从站:

ethercat slaves

如果能看到从站列表,说明物理层通了。我接的是一个伺服驱动器,显示0 0:0 PREOP EK1100之类的信息。

4.4 写一个简单的周期控制程序

IgH 提供了ecrt.h库,下面是一个最小化的周期控制示例:

#include <ecrt.h> #include <stdio.h> #include <unistd.h> #include <time.h> #define PERIOD_NS 1000000 // 1ms int main() { ec_master_t *master = ecrt_request_master(0); ec_domain_t *domain = ecrt_master_create_domain(master); ec_slave_config_t *sc = ecrt_master_slave_config(master, 0, 0, 0x00000002); // 配置 PDO 映射 ecrt_slave_config_pdos(sc, EC_END, my_pdo_config); ecrt_domain_reg_pdo_entry_list(domain, my_pdo_entries); ecrt_master_activate(master); struct timespec next; clock_gettime(CLOCK_MONOTONIC, &next); while (1) { next.tv_nsec += PERIOD_NS; if (next.tv_nsec >= 1000000000) { next.tv_nsec -= 1000000000; next.tv_sec++; } clock_nanosleep(CLOCK_MONOTONIC, TIMER_ABSTIME, &next, NULL); ecrt_master_receive(master); ecrt_domain_process(domain); // 读写 PDO 数据 EC_WRITE_S32(domain_data + offset, target_pos); ecrt_domain_queue(domain); ecrt_master_send(master); } }

编译:

gcc -o ecat_app ecat_app.c -I/usr/local/ethercat/include \ -L/usr/local/ethercat/lib -lethercat -lrt

运行:

chrt -f 80 taskset -c 2 ./ecat_app

这个程序每 1ms 发一帧,读写伺服的位置指令和实际位置。实测下来,ecrt_master_send到ecrt_master_receive的往返时间在 80-120us 之间,抖动小于 20us。

5. 常见问题与排查技巧实录

5.1 主站启动失败:Failed to open /dev/EtherCAT0

这个报错通常是ec_master模块没加载,或者设备节点没创建。先lsmod | grep ec_确认模块在不在。如果不在,modprobe ec_master手动加载。如果加载时报Unknown symbol,说明内核版本和模块不匹配,需要重新编译 IgH。

设备节点/dev/EtherCAT0由ec_master模块创建,如果udev规则没生效,可以手动mknod /dev/EtherCAT0 c 10 0。

5.2 从站扫描不到:No slaves found

先检查网线是不是插在正确的网口上。IgH 会接管指定的网卡,如果插错口,ethercat slaves就是空的。用ethtool -i eth0确认网卡驱动是ec_igb而不是igb。

如果驱动对了还是扫不到,用tcpdump -i eth0 -XX抓包,看有没有 EtherCAT 帧发出。正常情况下应该看到0x88a4的以太网类型。如果没有,说明主站没在发帧,检查ethercat master的Phase是不是Idle。

5.3 周期抖动大:cyclictest最大延迟超过 100us

先确认isolcpus和nohz_full生效了。cat /proc/cmdline看启动参数,cat /sys/devices/system/cpu/isolated看隔离核列表。

如果参数对了但抖动还是大,检查 BIOS 里的 C-State 和 P-State。C-State 深度睡眠会导致唤醒延迟,在 BIOS 里禁用C1E和C6。P-State 频率调节也会引入延迟,设成Performance模式。

还有一个容易被忽略的点:irqbalance服务会把中断分配到隔离核上。systemctl stop irqbalance并禁用,然后手动把网卡中断绑到非隔离核:

echo 0 > /proc/irq/$(cat /sys/class/net/eth0/device/irq)/smp_affinity

5.4 从站进入 OP 状态失败

从站状态机从PREOP到OP需要主站正确配置 PDO 映射和 DC 同步。如果卡在PREOP,用ethercat pdos看 PDO 配置对不对,ethercat sdos看 SDO 读写是否正常。

DC 同步失败常见于MASTER0_DEVICE的 MAC 地址填错,或者从站的 DC 周期和主站不匹配。用ethercat config看 DC 配置,确保AssignActivate和Sync0Cycle参数正确。

5.5 常见问题速查表

现象可能原因排查命令解决
主站启动失败模块未加载lsmod | grep ec_modprobe ec_master
扫不到从站网卡驱动不对ethtool -i eth0重编译 IgH 驱动
抖动大中断未隔离cat /proc/interrupts绑中断到非隔离核
从站不进 OPPDO 映射错ethercat pdos检查ecrt_slave_config_pdos
DC 同步失败MAC 地址错ethercat master改MASTER0_DEVICE

提示:每次改完配置,先ethercat master看主站状态,再ethercat slaves看从站状态,最后ethercat pdos看 PDO 映射。这个顺序能快速定位问题在哪一层。

6. 几个容易被忽略的实操心得

第一个心得是关于内核编译的。很多人编译 RT 内核时喜欢用make localmodconfig来精简配置,但这个命令会关掉很多 IgH 需要的选项,比如CONFIG_UIO和CONFIG_EXTRA_FIRMWARE。我建议直接用发行版的config-$(uname -r)做基础,只改实时相关的选项,别贪图编译快。

第二个心得是关于网卡选择的。IgH 对 Intel 的e1000e和igb支持最好,Realtek 的r8169虽然也能用generic驱动跑,但抖动明显大一圈。我试过用板载 Realtek 网卡,cyclictest最大延迟比 Intel I210 高了 60us。如果项目对实时性要求高,花几十块钱买张 Intel 网卡是值得的。

第三个心得是关于 DC 同步的。EtherCAT 的分布式时钟能让所有从站的时钟对齐到主站,但前提是主站的参考时钟要稳。如果主站跑在隔离核上,clock_gettime(CLOCK_MONOTONIC)的抖动会小很多。我实测过,不隔离核时 DC 同步误差在 200ns 左右,隔离后降到 50ns 以内。

第四个心得是关于代码结构的。IgH 的ecrt_master_receive和ecrt_master_send必须成对出现,中间不能有阻塞操作。我见过有人在中间加printf调试,结果周期直接崩掉。调试信息应该用环形缓冲区记下来,在非实时线程里打印。

这套方案我前后调了大概两周,从内核编译到从站跑起来,中间踩的坑基本都记在上面了。如果你也在做类似的项目,建议先把cyclictest跑稳,再上 EtherCAT 主站,这样能少走很多弯路。

返回列表