找回密码
立即注册
搜索
热搜: Java Python Linux Go
发回帖 发新帖

1029

积分

0

好友

140

主题
发表于 3 天前 | 查看: 8| 回复: 0

LWIP 作为一个轻量级 TCP/IP 协议栈,其内部许多操作需要保证线程或中断安全,因此移植时必须实现临界段保护接口。由于这些接口调用极为频繁,其实现效率直接关系到整个协议栈的运行性能。本文将深入解析 LWIP 的临界段保护接口、其默认实现中存在的 BUG,并从效率角度给出使用建议。

临界段保护接口定义

相关接口宏定义在 sys.h 头文件中:

  • SYS_ARCH_DECL_PROTECT(lev): 声明一个变量,用于记录进入临界段前的状态。
  • SYS_ARCH_PROTECT(lev): 进入临界段保护,并返回进入前的状态。
  • SYS_ARCH_UNPROTECT(lev): 退出临界段保护,并恢复进入前的状态。

这里所说的“状态”通常指中断使能状态(开/关)或中断优先级等级。关键原则是:退出时必须恢复到进入前的状态。

LWIP 提供了两种实现方式:

  1. 用户可以直接定义上述三个宏。
  2. 若未直接定义,则需在 lwipopts.h 中配置 SYS_LIGHTWEIGHT_PROT 为 1,并在 sys_arch.c 中实现 sys_arch_protect()sys_arch_unprotect(lev) 函数。

如果既未定义宏,SYS_LIGHTWEIGHT_PROT 也未使能,则这些宏被定义为空,即无保护。

lwip临界段保护机制流程图

sys_arch.c 中的标准实现

如果选择第二种方式(通过配置宏),则需要在 sys_arch.c 文件中实现以下核心函数:

接口函数
  • void sys_init(void): 初始化保护机制所需资源(如互斥量)。
  • sys_prot_t sys_arch_protect(void): 进入保护,可通过获取互斥量或关中断实现。
  • void sys_arch_unprotect(sys_prot_t pval): 退出保护,并恢复状态。

当使能 LWIP_SYS_ARCH_PROTECT_SANITY_CHECK 时,sys_arch_protect() 应返回嵌套计数,否则可返回 1。

异常检测机制

使能 LWIP_SYS_ARCH_PROTECT_SANITY_CHECK 后,LWIP 会使用一个嵌套计数器来检测临界段保护的异常使用:

  • 进入时计数器加1,sys_arch_protect() 返回加1前的值。
  • 退出时检查计数器是否大于0(防止重复退出),然后减1,并断言减1后的值应与进入时返回的值相等,以此确保进入和退出匹配。
默认实现的 BUG 与修复

标准实现存在一个关键问题:按照设计,sys_arch_protect() 应返回原始的保护状态(如中断开关状态),以便 sys_arch_unprotect() 能够精确恢复。然而:

  1. 当使能 LWIP_SYS_ARCH_PROTECT_SANITY_CHECK 时,函数返回的是嵌套计数值,而非中断状态。
  2. 当使用关中断方式(未定义 LWIP_SYS_ARCH_PROTECT_USES_MUTEX)时,通常使用的 taskENTER_CRITICAL() 类函数不返回原始状态。

因此,若使用关中断方式,不能使能 LWIP_SYS_ARCH_PROTECT_SANITY_CHECK,且必须实现能返回并恢复原始中断状态的函数。修正后的实现示例如下:

sys_prot_t sys_arch_protect(void) {
#if LWIP_SYS_ARCH_PROTECT_USES_MUTEX
  LWIP_ASSERT("sys_arch_protect_mutex != NULL", sys_arch_protect_mutex != NULL);
  os_acquire_mutex(sys_arch_protect_mutex);
#if LWIP_SYS_ARCH_PROTECT_SANITY_CHECK
  {
    /* 返回递增的嵌套计数 */
    sys_prot_t ret = sys_arch_protect_nesting;
    sys_arch_protect_nesting++;
    LWIP_ASSERT("sys_arch_protect overflow", sys_arch_protect_nesting > ret);
    return ret;
  }
#else
  return 1;
#endif
#else /* LWIP_SYS_ARCH_PROTECT_USES_MUTEX */
  /* 中断方式必须返回原始中断状态,因此不能使能异常检测 */
  sys_prot_t ret = cpu_disable_irq();
  return ret;
#endif /* LWIP_SYS_ARCH_PROTECT_USES_MUTEX */
}

void sys_arch_unprotect(sys_prot_t pval) {
#if LWIP_SYS_ARCH_PROTECT_USES_MUTEX
#if LWIP_SYS_ARCH_PROTECT_SANITY_CHECK
  LWIP_ASSERT("unexpected sys_arch_protect_nesting", sys_arch_protect_nesting > 0);
  sys_arch_protect_nesting--;
  LWIP_ASSERT("unexpected sys_arch_protect_nesting", sys_arch_protect_nesting == pval);
#endif
  LWIP_ASSERT("sys_arch_protect_mutex != NULL", sys_arch_protect_mutex != NULL);
  os_release_mutex(sys_arch_protect_mutex);
  LWIP_UNUSED_ARG(pval);
#else /* LWIP_SYS_ARCH_PROTECT_USES_MUTEX */
  /* 中断方式必须恢复原始中断状态 */
  cpu_restore_irq(pval);
#endif /* LWIP_SYS_ARCH_PROTECT_USES_MUTEX */
}

临界段保护的使用场景

1. 全局变量与状态更新

最常见的是保护全局变量或结构体成员的读写,防止数据竞争。例如在 netconn_err 函数中:

err_t netconn_err(struct netconn *conn) {
  err_t err;
  SYS_ARCH_DECL_PROTECT(lev);
  if (conn == NULL) {
    return ERR_OK;
  }
  SYS_ARCH_PROTECT(lev);
  err = conn->pending_err;
  conn->pending_err = ERR_OK;
  SYS_ARCH_UNPROTECT(lev);
  return err;
}
2. TCP/IP 核心锁 (TCP/IP Core Locking)

sockets.c 中,根据 LWIP_TCPIP_CORE_LOCKING 配置选择不同的保护机制:

#if LWIP_TCPIP_CORE_LOCKING
/* 使用核心锁保护 */
#define LWIP_SOCKET_SELECT_PROTECT(lev)   LOCK_TCPIP_CORE()
#define LWIP_SOCKET_SELECT_UNPROTECT(lev) UNLOCK_TCPIP_CORE()
#else /* LWIP_TCPIP_CORE_LOCKING */
/* 使用轻量级保护(SYS_ARCH_PROTECT) */
#define LWIP_SOCKET_SELECT_DECL_PROTECT(lev)  SYS_ARCH_DECL_PROTECT(lev)
#define LWIP_SOCKET_SELECT_PROTECT(lev)       SYS_ARCH_PROTECT(lev)
#define LWIP_SOCKET_SELECT_UNPROTECT(lev)     SYS_ARCH_UNPROTECT(lev)
#endif /* LWIP_TCPIP_CORE_LOCKING */
3. 内存管理 (mem.c)

当使能 LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT 时,内存的分配和释放操作会使用临界段进行保护。

4. 桥接接口 (bridgeif.h) 与 PPPoS

在这些模块中,根据特定配置,也可能复用 SYS_ARCH_PROTECT 系列的宏来实现模块内的资源保护。

效率优化建议

临界段保护的选择对性能至关重要:

  • 对于保护全局变量、执行时间极短的操作,强烈建议使用关中断方式。其开销极小,效率最高。需要注意的是,许多互斥量的底层实现本身也依赖于关中断,但增加了额外的逻辑开销。
  • 对于需要保护执行时间较长的代码段(例如某些 TCP/IP 核心操作),则应使用互斥量或其他调度器友好的同步机制,以避免长时间关闭中断影响系统实时性。此时可以像 LWIP_TCPIP_CORE_LOCKING 那样,为这些特定场景实现独立的保护接口。

总结

  1. 按场景选择实现:短操作用关中断,长操作用互斥量,分类实现以最大化系统效率。
  2. 注意默认实现的 BUG:标准 sys_arch.c 中关中断方式的实现,在使能了异常检测或使用不返回状态的关中断函数时,无法正确保存和恢复中断状态,需按前述方案修正。
  3. 理解使用模式:临界段保护广泛应用于 LWIP 内部各个模块,理解其配置和调用场景有助于进行更精准的移植和性能调优。



上一篇:渗透测试完整流程解析:从信息搜集到取证溯源
下一篇:T-Shell开源终端工具详解:Windows平台高效SSH与SFTP管理方案
您需要登录后才可以回帖 登录 | 立即注册

手机版|小黑屋|网站地图|云栈社区 ( 苏ICP备2022046150号-2 )

GMT+8, 2025-12-17 20:30 , Processed in 0.150823 second(s), 39 queries , Gzip On.

Powered by Discuz! X3.5

© 2025-2025 云栈社区.

快速回复 返回顶部 返回列表