Lwip 奔溃掉线内存申请不出来也许大部分是竞争问题!

 

系列文章目录

 


 


前言

这段时间接手一个使用lwip的项目,各种不稳定,异常crash、内存申请不出来、ping不通。当然我使用的版本比较老,本身也有bug,这个可以网上找找,但是这篇博客肯定不是谈这些。当你已经按照网友修复了各种bug,他依旧不稳定。

其实对程序而言,一个程序行为诡异,往往和竞争有关系。大家做个os层面的多线程开放,应该都会有这种感悟。一个程序时好时坏,有时奔溃有时不奔溃,往往都是竞争惹的祸。


 

一、程序架构

      1.当前我的程序架构是,没有操作系统的裸机程序,说白了就是一个单片机程序,while(1)中做一切。但是注意,不要把裸机就考虑的那么单纯,第一次出现bug 我就考虑到裸机的中断和正常程序的执行流之间可能有竞争关系。从下图程序架构可以看出:

发送: 数据包从主循环中调用lwip的发送函数(tcp、udp无所谓)然后通过lwip协议栈添加各种帧头形成以太网数据包发送出去,发送的时候调用网卡底层驱动,发送完成后网卡驱动会给出中断。

接收:接收的时候是数据包到达网卡,网卡产出中断,然后进入中断执行函数,中断执行函数里面调用lwip协议栈的接收,然后一系列解帧,最后解出的裸数据调用我们注册的回调函数。

接收的时候架构其实很“诡异”(经验之谈),因为整个接收函数都在中断服务函数里面,包括你注册的回调函数。我从大学学微机原理就知道,中断资源极其紧张,中断里面是“宁抢一秒,不停三分”。但是奈何

lwip官方给出的一些例子,和网上的例子都是如此就没有多虑了。后来翻阅国外的一些论坛,的确被大家诟病。

 

二、Bug所在

不知道你看了上面的架构有没有怀疑一种bug,那就是对于一个裸机程序,没有相关的锁来保证安全,接收在中断,发送在主循环,他们有没有在使用一些数据结构上的竞争。

比如lwip的内存池pbuf。其实是有的,lwip其实官方是有说明的:

https://www.nongnu.org/lwip/2_0_x/pitfalls.html

lwip官方的一段话:

lwIP can be used in two basic modes: Mainloop mode ("NO_SYS") (no OS/RTOS running on target system) or OS mode (TCPIP thread) (there is an OS running on the target system).

Mainloop Mode

In mainloop mode, only Callback-style APIs can be used. The user has two possibilities to ensure there is only one exection context at a time in lwIP:

1) Deliver RX ethernet packets directly in interrupt context to lwIP by calling netif->input directly in interrupt. This implies all lwIP callback functions are called in IRQ context, which may cause further problems in application code: IRQ is blocked for a long time, multiple execution contexts in application code etc. When the application wants to call lwIP, it only needs to disable interrupts during the call. If timers are involved, even more locking code is needed to lock out timer IRQ and ethernet IRQ from each other, assuming these may be nested.

2) Run lwIP in a mainloop. There is example code here: Mainloop mode ("NO_SYS"). lwIP is ONLY called from mainloop callstacks here. The ethernet IRQ has to put received telegrams into a queue which is polled in the mainloop. Ensure lwIP is NEVER called from an interrupt, e.g. some SPI IRQ wants to forward data to udp_send() or tcp_write()!

lwIP可以在两种基本模式下使用。 主环模式("NO_SYS")(目标系统上没有OS/RTOS运行)或OS模式(TCPIP线程)(目标系统上有OS运行)。

主回路模式
在mainloop模式下,只能使用Callback风格的API。用户有两种可能来保证lwIP中一次只有一个exection上下文。

1) 直接在中断上下文中调用netif->input,将RX以太网包直接传送到lwIP。这意味着所有的lwIP回调函数都是在IRQ上下文中调用的,这可能会给应用代码带来进一步的问题。IRQ被长时间阻塞,应用程序代码中存在多个执行上下文等。当应用程序要调用lwIP时,只需要在调用过程中禁用中断即可。如果涉及到定时器,甚至需要更多的锁定代码,将定时器IRQ和以太网IRQ相互锁定,假设这些可能是嵌套的。

2)在mainloop中运行lwIP。这里有示例代码。 lwIP只能在主环调用堆栈中调用. 以太网IRQ必须把收到的报文放到一个队列中,这个队列在主环中被轮询。确保lwIP永远不会从中断中被调用,例如一些SPI IRQ想要将数据转发给udp_send()或tcp_write()!

通过www.DeepL.com/Translator(免费版)翻译

当然这种竞争,国外网友也讨论了

http://lwip.100.n7.nabble.com/Data-sending-problem-over-TCP-td513.html

场景:

       我们正在主循环中调用lwip的发送函数,lwip正在调用全局的数据结构申请内存,移动相关的指针,突然网络来了数据包,产生中断,程序跳转中断,中断里面也申请内存,也操作这个全局数据结构(至于为什么会破坏我就不讲了,大家应该都明白)。其实这个时候就有可能把这个内存池的数据结构破坏,在实际中我也确实抓到相关的现象,比如内存的ref计数不正确。其实做过Linux下或者windows下相关驱动的都知道,Linux底层有spin_lock_irq 这种api,写驱动调用这种api就可以避免程序被中断打断。当然我们也可以调用相关的api来屏蔽外设中断。针对目前这种架构我们有两种实现方案:

三、修复bug

其实我们就是要解决发送和接收的竞争问题。

方案1:我在发送的使用调用相关的api屏蔽中断,是个cpu或者mpu都支持中断屏蔽。

方案2:方案1的最大问题就是锁区间太大,我看好多人写代码一把大锁加上,其实都是不合理的,我们要分析真正的竞争点,把锁粒度变小,更何况是“”锁”的中断(这里的锁表示屏蔽中断)。

方案2的实现是接收发送都放到主循环,首先接收和发送串行了,接收的时候只是把网卡数据包指针(入指针而非数据包拷贝,减小消耗)入队,在主循环循环到接收函数的时候取出数据包指针(注意取出的时候屏蔽中断),

然后放到lwip里面处理。


总结

那我们回到开始,为什么lwip的例子没有问题,其实lwip和我们有区别,lwip没有主动发送,都是接收,即使是发送也是在接收内部发送的,相当于一直在中断服务函数里面。
 

 

  • 9
    点赞
  • 38
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值