• 【lwip】005-lwip内核框架剖析



    前言

    本笔记主要记录lwip框架部分,目的是为了对lwip的源码实现有初步了解,方便后面细节分析。
    参考:

    5.1 lwip初始化

    协议栈初始化lwip_init()init.c文件中。

    如果移植入带系统的工程中,则调用tcpip_init()

    • 调用lwip_init()进行内核初始化。
    • 配置初始化后的钩子函数,在新内核线程tcpip_thread中跑。
    • 创建一个tcpip_mbox邮箱,成员个数为TCPIP_MBOX_SIZE。主要用于接收从底层或者上层传递过来的消息。
    • 创建一个lock_tcpip_core内核锁。
    • 创建一个tcpip_thread线程。这个线程就是LwIP在操作系统中作为一个独立的线程运行,所有处理的数据都要这个线程去处理。

    如果在裸机中,可以直接调用lwip_init()

    lwip_init()init.c文件中。

    lwip_init()源码:

    • 在NO_SYS模式下才能直接调用lwip_init(),否则,用户只能调用tcpip_init()来实现初始化,但是最终都是会调用到lwip_init()函数。
    • 通过该函数可以大概了解下lwip核心有什么组件。

    5.2 内核超时

    tcpip协议栈的超时机制也是很重要的一部分。

    ARP缓存表项的时间管理、IP分片数据报的重装等待超时、TCP中的建立连接超时、重传超时机制等都会用到。

    LwIP为每个与外界网络连接的任务都有设定了timeout属性。

    其实现源码主要在timeouts.ctimeouts.h

    5.2.1 内核超时机制

    内核只有一条超时链表static struct sys_timeo *next_timeout;

    该链表的数据结构是一个有序单向非循环的非通用链表。

    把需要超时处理的事件按唤醒时间升序插入到该链表中。

    通过sys_timeouts_sleeptime()函数获取下次唤醒的时间,到唤醒的时间后就会通过sys_check_timeouts()遍历next_timeout超时链表。

    这个只是底层的内核超时机制,另外lwip还基于这个机制再实现一套周期定时机制。

    5.2.2 周期定时机制

    周期定时机制时基于内核超时机制而实现的。

    初始化内核超时机制时,把周期定时函数lwip_cyclic_timer()作为超时函数,lwip_cyclic_timers[]数组保存的回调函数作为lwip_cyclic_timer()的参数,让其周期回调。

    因为超时链表next_timeout中的事件超时后会出队,但是lwip_cyclic_timers()函数里面会将自己再次入队,这样实现周期回调。

    5.2.3 内核超时链表数据结构

    内核超时链表:

    内核超时链表数据结构:

    5.2.4 内核超时初始化

    初始化内核超时模块:

    lwip_cyclic_timers数组:

    5.2.6 超时的溢出处理

    看过freertos内核实现都知道,这个rtos是通过两条延时链表(一条当前延时链表和一条溢出延时链表)来处理延时溢出的。

    而lwip内核超时机制就只有一条超时链表,溢出,只能靠其它逻辑判断了。

    TIME_LESS_THAN()宏函数来处理溢出。

    我还是举个例子说明下吧:

    例子1,正常情况下(时间轴t在c前):t和c比较,c还没溢出,t-c为负,16进制表示为0x8xxxxxxx,比0x7fffffff大,返回1。

    例子2,溢出情况下(时间轴t在c前):t和c比较,c溢出,由于限制了c到t的差距必须在LWIP_MAX_TIMEOUT内,t-c的差值一定大于0xFFFFFFFF-LWIP_MAX_TIMEOUTLWIP_MAX_TIMEOUT,还是比0x7fffffff大,返回1。

    5.2.7 注册超时事件

    注册超时事件使用sys_timeout(),内部会调用sys_timeout_abs()把超时事件插入到超时链表next_timeout中。

    超时事件插入超时链表sys_timeout_abs()

    5.2.8 注销超时事件

    注销超时事件的主要步骤:

    • 从超时链表中找出该事件,将其提除。
    • 释放该事件的内存资源。

    5.2.9 超时检查处理

    在裸机中,可以直接定时调用sys_check_timeouts()函数来实现超时检查处理。

    在系统中,超时检查处理在tcpip_timeouts_mbox_fetch(sys_mbox_t *mbox, void **msg);函数被调用。

    tcpip_timeouts_mbox_fetch()这个函数会在tcpip_thread()被一直调用。主要内容是等待tcpip_mbox消息,是可阻塞的,如果在等待tcpip_mbox的过程中发生超时事件,则会同时执行超时事件处理。

    5.2.10 把超时链表的时间域更新到当前时间

    就是超时链表首节点的唤醒时间从时间轴上移到当前时间,后续节点按差跟上。

    5.2.11 获取到下次超时的时间差

    5.2.12 周期定时机制实现

    周期定时机制基于内核超时机制

    利用lwip_cyclic_timer()做超时函数:

    • 函数的参数就是周期定时机制的数据结构,内含周期定时回调函数。
    • 函数的内容就是执行周期定时回调函数,计算下一个唤醒时间,重新把自己插回超时链表,以此实现周期定时回调。

    周期定时机制的数据结构:

    周期定时机制的基函数:

    • 注意:如果当前定时事件的下一个唤醒时间也过期了,那就重新计时,以当前时间为基准。

    5.3 lwip中的消息

    lwip消息就是其它线程把业务外包到lwip内核主线程tcpip_thread()去执行。

    LwIP中必须存在的消息,整个内核的运作都要依赖他们:

    • 数据包消息。
    • API消息。

    5.3.1 lwip消息数据结构

    因为lwip中有多种消息类型,所以数据结构使用联合体。

    5.3.2 消息类型

    5.3.3 API消息

    API消息是有用户线程发出,与内核进行交互。

    用户调用应用层接口,往南执行协议栈内核函数,需要tcpip_thread线程执行时,通过API消息告知tcpip_thread线程执行。

    在线程间通信中的api消息类型对应的数据结构伪代码如下:

    其中,void* msg;这个参数才是真正的API消息数据结构,对应是struct api_msg

    要注意区分,前者是用于线程间通信的API消息的数据结构。后者是API消息内容的数据结构。

    5.3.3.1 API消息内容数据结构

    struct api_msg包含3个字段:

    1. 描述连接信息的struct netconn *conn;
    2. 内核执行的结果err_t err;
    3. API接口数据结构union msg;

    struct api_msg

    5.3.3.2 lwipAPI消息流图

    待画:后续分析完socket API源码实现后再补上

    5.3.4 数据包消息

    消息类型为TCPIP_MSG_INPKT

    数据包消息是底层网卡接收到数据后需要往北交给协议栈处理时需要构造的消息。

    tcpip_inpkt()函数中构造。

    主要将收到的数据包传递到tcpip_thread线程执行。并告知要传入哪个内核函数处理。

    tcpip_inpkt()

    • 实现分两种:

      1. 没有tcpip内核锁权限,就需要把input的业务通过消息转交给tcpip_thread处理。
      2. 如果有tcpip内核锁权限,那就获取tcpip内核锁,在本线程处理即可。

    lwip数据包南向收包流图:

    5.4 tcpip_thread线程

    LwIP内核是作为操作系统的一个线程运行的,在协议栈初始化的时候就会创建tcpip_thread线程。

    该线程主要处理超时检查和接收各种消息进行处理。

    tcpip内核锁是维护tcpip内核函数的原子性。

    tcpip_thread内核线程和其它线程对内核tcpip的函数存在竞争关系。

    5.4.1 lwip内核主线程流程图

    5.4.2 lwip内核主线程

    lwIP主线程。

    这个线程独占地访问lwIP核心函数(除非对它们的访问没有被锁定)。

    其他线程使用消息框与该线程通信。

    该线程还启动所有计时器,以确保它们在正确的线程上下文中运行。

    5.4.3 等待消息或超时事件代码实现

    5.4.4 tcpip_thread处理消息

    其它线程需要发消息到tcpip_thread()线程来处理内核操作。

    如果开启了tcpip内核锁,客户端对lwip的操作也可以不用外包tcpip_thread()线程处理。

    5.5 lwip数据流图(全栈)

    后续分析完socket API源码实现后再补上(还有最后一层 socket API源码实现还没分析,所以全局数据库流图还有一层未画)


    __EOF__

  • 本文作者: 李柱明
  • 本文链接: https://www.cnblogs.com/lizhuming/p/16634848.html
  • 关于博主: 嵌入式从业者。RTOS、Linux lwip mbedtls...
  • 版权声明: 版权归博主所有
  • 声援博主: 学习笔记分享
  • 相关阅读:
    ubuntu20.04 nerf开山之作
    2018 Journal of cheminformatics | 基于条件变分自编码器分子生成模型
    SpringMvc静态资源映射
    win7电脑开机后找不到explorer. exe怎么办
    显卡天梯图2022年11月新版 显卡性能排行榜天梯图
    【云计算 | OpenStack】Centos7通过源码快速安装Open vSwitch
    亚商投资顾问 早餐FM/1206进一步健全资本市场功能
    Centos7下zabbix安装与部署,设置中文(保姆级图文)【网络工程】
    【学习推荐】极客时间-左耳听风专栏
    ESP8266-Arduino网络编程实例-Web页面WiFi配置管理
  • 原文地址:https://www.cnblogs.com/lizhuming/p/16634848.html