【原创】IgH EtherCAT主站详解(十四)--应用层与协议栈调用链
应用层协议栈调用链
4.5 — 从用户空间 API 到内核再到硬件的完整调用路径
概览
从 API 到硬件的数据流
用户应用程序通过 ecrt.h 定义的 API 与 EtherCAT 主站交互。这些 API 调用经过 用户空间库 (lib/) → ioctl 系统调用 → 内核主站模块 (master/) → 网卡驱动 → EtherCAT 总线 的完整路径。本节展示 4 条核心调用链的完整路径。
4 条核心调用链
调用链 1:
ecrt_master_activate()— 主站激活
调用链 2:ecrt_slave_config_reg_pdo_entry()— PDO 注册
调用链 3: SDO 读写 —ecrt_master_sdo_download/upload()
调用链 4: 周期性 IO —ecrt_master_send/receive()
技术详情
调用链 1: 主站激活 (ecrt_master_activate)
sequenceDiagram
participant APP as 用户应用
participant LIB as lib/master.c
participant IOCTL as ioctl 系统调用
participant MST as master/master.c
participant DOM as master/domain.c
participant THREAD as 内核线程
APP->>LIB: ecrt_master_activate(master)
LIB->>IOCTL: ioctl(fd, EC_IOCTL_ACTIVATE)
IOCTL->>MST: ecrt_master_activate()
MST->>MST: 遍历所有 Domain
loop 每个 Domain
MST->>DOM: ec_domain_finish(domain, offset)
DOM->>DOM: 计算 FMMU 逻辑地址
DOM->>DOM: 分配过程数据内存
end
MST->>MST: 停止 IDLE 线程
MST->>MST: 设置应用回调 (send_cb/receive_cb)
MST->>MST: ec_master_enter_operation_phase()
Note over MST: IDLE → OPERATION
MST->>THREAD: 启动 EtherCAT-OP 线程
MST-->>LIB: 返回成功
LIB-->>APP: 激活完成
调用链 2: PDO 入口注册 (ecrt_domain_reg_pdo_entry)
sequenceDiagram
participant APP as 用户应用
participant SC_LIB as lib/slave_config.c
participant D_LIB as lib/domain.c
participant IOCTL as ioctl 系统调用
participant MST as master/master.c
participant SC as master/slave_config.c
participant DOM as master/domain.c
APP->>SC_LIB: ecrt_slave_config_pdos(sc, syncs)
SC_LIB->>IOCTL: ioctl(fd, EC_IOCTL_SC_SYNC)
IOCTL->>SC: ecrt_slave_config_sync_manager()
SC->>SC: 配置 Sync Manager 参数
APP->>D_LIB: ecrt_domain_reg_pdo_entry(domain, sc, index, subindex)
D_LIB->>IOCTL: ioctl(fd, EC_IOCTL_DOMAIN_REG_PDO_ENTRY)
IOCTL->>DOM: ecrt_domain_reg_pdo_entry()
DOM->>DOM: 查找 FMMU 配置
DOM->>DOM: 计算逻辑地址偏移
DOM-->>D_LIB: 返回 bit_position
D_LIB-->>APP: 返回 PDO 偏移量
调用链 3: SDO 读写
sequenceDiagram
participant APP as 用户应用
participant LIB as lib/master.c
participant IOCTL as ioctl 系统调用
participant MST as master/master.c
participant REQ as SDO Request
participant FSM as fsm_coe.c
participant BUS as EtherCAT 总线
APP->>LIB: ecrt_master_sdo_download(master, position, index, subindex, data, size)
LIB->>IOCTL: ioctl(fd, EC_IOCTL_SLAVE_SDO_DOWNLOAD)
IOCTL->>MST: ec_master_sdo_download()
MST->>REQ: 创建 SDO 请求
MST->>MST: 将请求加入从站的 sdo_requests 队列
loop 等待完成
MST->>FSM: ec_fsm_coe_exec() (IDLE/OP 线程)
FSM->>BUS: CoE SDO Download 请求 (邮箱)
BUS-->>FSM: SDO Download 响应
FSM-->>MST: 请求完成
end
MST-->>LIB: 返回结果
LIB-->>APP: 0=成功 / -errno
调用链 4: 周期性过程数据 IO
flowchart TD
subgraph "用户空间 — 实时循环"
A["应用程序周期函数"] --> B["ecrt_master_receive(master)"]
B --> C["ecrt_domain_process(domain)"]
C --> D["读取过程数据<br/>domain_data + offset"]
D --> E["控制逻辑处理"]
E --> F["写入过程数据<br/>domain_data + offset"]
F --> G["ecrt_domain_process(domain)"]
G --> H["ecrt_master_send(master)"]
H --> I["等待下一个周期"]
I --> A
end
subgraph "内核空间 — 数据路径"
B -.->|"ioctl → receive_cb"| J["ec_master_receive_datagrams()"]
J --> K["匹配 Datagram + 更新 WKC"]
K --> L["拷贝数据到 domain_data"]
H -.->|"ioctl → send_cb"| M["ec_master_send_datagrams()"]
M --> N["从 domain_data 拷贝到 Datagram"]
N --> O["填充帧头 + 串联发送"]
O --> P["ec_device_send()"]
end
style A fill:#ebf8ff,stroke:#90cdf4
style E fill:#c6f6d5,stroke:#68d391
API 函数清单 (ecrt.h)
| 分类 | 函数 | 用途 |
|---|---|---|
| Master | ecrt_master_create() |
打开主站设备 |
ecrt_master_activate() |
激活主站进入 OPERATION | |
ecrt_master_deactivate() |
反激活主站回到 IDLE | |
ecrt_master_send() |
发送所有 Datagram | |
ecrt_master_receive() |
接收并处理响应 | |
| Domain | ecrt_master_create_domain() |
创建过程数据域 |
ecrt_domain_reg_pdo_entry() |
注册 PDO 入口 | |
ecrt_domain_data() |
获取过程数据指针 | |
| Slave Config | ecrt_master_slave_config() |
获取从站配置句柄 |
ecrt_slave_config_pdos() |
配置 PDO 映射 | |
ecrt_slave_config_dc() |
配置 DC 同步参数 | |
| SDO | ecrt_master_sdo_download() |
下载 SDO (写从站) |
ecrt_master_sdo_upload() |
上传 SDO (读从站) |
深入源码
用户空间库架构
源文件: lib/master.c, lib/slave_config.c, lib/domain.c
用户空间库是对 ecrt.h API 的封装,每个函数内部通过 ioctl() 系统调用进入内核。库函数主要职责是:
- 打开主站字符设备
/dev/EtherCAT* - 将用户参数打包为 ioctl 命令和数据结构
- 调用
ioctl(fd, EC_IOCTL_*, &data;)进入内核 - 等待内核完成操作并返回结果
ioctl 命令分发
源文件: master/cdev.c
内核侧的字符设备驱动处理 ioctl 命令:
| ioctl 命令 | 内核函数 | 用途 |
|---|---|---|
EC_IOCTL_ACTIVATE |
ecrt_master_activate() |
激活主站 |
EC_IOCTL_DEACTIVATE |
ecrt_master_deactivate() |
反激活 |
EC_IOCTL_SEND |
ecrt_master_send() |
发送帧 |
EC_IOCTL_RECEIVE |
ecrt_master_receive() |
接收帧 |
EC_IOCTL_SC_SYNC |
ecrt_slave_config_sync_manager() |
配置 SM |
EC_IOCTL_DOMAIN_REG |
ecrt_domain_reg_pdo_entry() |
注册 PDO |
EC_IOCTL_SLAVE_SDO_DOWNLOAD |
ec_master_sdo_download() |
SDO 写 |
EC_IOCTL_SLAVE_SDO_UPLOAD |
ec_master_sdo_upload() |
SDO 读 |
ecrt_master_send() 完整调用路径
位置: master/master.c:3057
flowchart TD
A["ecrt_master_send(master)<br/>lib/master.c"] --> B["ioctl(fd, EC_IOCTL_SEND)"]
B --> C["ec_cdev_ioctl_send()<br/>master/cdev.c"]
C --> D["ecrt_master_send(master)<br/>master/master.c:3057"]
D --> E["遍历 dev_idx: MAIN → BACKUP"]
E --> F["ec_master_send_datagrams()<br/>master/master.c:1171"]
F --> G["填充 EtherCAT 帧头<br/>(Length + Type)"]
G --> H["遍历 datagram_queue<br/>串联多个 Datagram"]
H --> I["检查大小限制<br/>(EC_MAX_DATA_SIZE=1486)"]
I --> J["分配 index + 记录时间戳"]
J --> K["ec_device_send()<br/>master/device.c"]
K --> L["dev_queue_xmit() / poll()"]
L --> M["网卡硬件发送"]
ecrt_master_receive() 完整调用路径
位置: master/master.c:3106
flowchart TD
A["ecrt_master_receive(master)"] --> B["遍历 dev_idx: MAIN → BACKUP"]
B --> C["receive_cb → ec_master_internal_receive_cb"]
C --> D["ec_master_receive_datagrams()<br/>master/master.c:1325"]
D --> E["验证 EtherCAT 帧头<br/>(EtherType=0x88A4)"]
E --> F["遍历帧中的 Datagram"]
F --> G["通过 index 匹配已发送队列"]
G --> H["拷贝数据到 datagram->data"]
H --> I["更新 working_counter"]
I --> J["更新 jiffies_received"]
J --> K["将 datagram 移至完成"]
K --> F
Domain 过程数据内存布局
ecrt_master_activate() 激活时为每个 Domain 分配连续的逻辑地址空间:
| 步骤 | 函数 | 说明 |
|---|---|---|
| 1 | ec_domain_finish() |
为 Domain 中每个 FMMU 计算逻辑地址偏移 |
| 2 | domain_offset += domain->data_size |
多个 Domain 的逻辑地址空间连续递增 |
| 3 | 分配 domain->data |
分配物理连续内存(用于 DMA) |
| 4 | mmap 到用户空间 | 通过 ecrt_domain_data() 获取用户可访问指针 |

浙公网安备 33010602011771号