2023年12月6日发(作者:)

LWIP_简记(_init()之lwip_init())

LWIP一句话记住就行:

一项工程,两份配置,三种内存分配,四套操作API,五步初始化,六个"数据流",七个数据结构

-------------------------------------------

从这一讲开始我们来剖析一下lwip_init()中那些子系统的初始化.

先上源码:

/**

* @ingroup lwip_nosys

* Initialize all modules.

* Use this in NO_SYS mode. Use tcpip_init() otherwise.

*/

void

lwip_init(void)

{

#ifndef LWIP_SKIP_CONST_CHECK

int a = 0;

LWIP_UNUSED_ARG(a);

LWIP_ASSERT("LWIP_CONST_CAST not implemented correctly. Check your lwIP port.", LWIP_CONST_CAST(void *, &a) == &a);

#endif

#ifndef LWIP_SKIP_PACKING_CHECK

LWIP_ASSERT("Struct packing not implemented correctly. Check your lwIP port.", sizeof(struct packed_struct_test) == PACKED_STRUCT_TEST_EXPEC

TED_SIZE);

#endif

/* Modules initialization */

stats_init();

#if !NO_SYS

sys_init();

#endif /* !NO_SYS */

// mem_init();

lwip_mem_init();

memp_init();

pbuf_init();

netif_init();

#if LWIP_IPV4

ip_init();

#if LWIP_ARP

etharp_init();

#endif /* LWIP_ARP */

#endif /* LWIP_IPV4 */

#if LWIP_RAW

raw_init();

#endif /* LWIP_RAW */

#if LWIP_UDP

udp_init();

#endif /* LWIP_UDP */

#if LWIP_TCP

tcp_init();

#endif /* LWIP_TCP */

#if LWIP_IGMP

igmp_init();

#endif /* LWIP_IGMP */

#if LWIP_DNS

dns_init();

#endif /* LWIP_DNS */

#if PPP_SUPPORT

ppp_init();

#endif

#if LWIP_TIMERS

sys_timeouts_init();

#endif /* LWIP_TIMERS */

}

把一些条件编译先摘掉:

void

lwip_init(void)

{

sys_init(); /*

初始化系统的锁

*/

mem_init(); /*

初始化内存指针

*/

memp_init(); /*

初始化内存池

*/

sys_timeouts_init(); /*

定时任务

*/

}

少了很多,清楚明了,但是条件编译有些还是需要分析的,特别是一些协议的初始化.那么不多说,我们还是逐一来分析吧.

关于LWIP_SKIP_CONST_CHECK没什么解释的,看到check一般就顾名思义吧,应该是做一些检查.

_init()

void

stats_init(void)

{

#ifdef LWIP_DEBUG

#if MEM_STATS

lwip_ = "MEM";

#endif /* MEM_STATS */

#endif /* LWIP_DEBUG */

}

好像也没有做什么重要的事情,就是赋值动作.lwip_stats结构体还是很复杂的,进去一看发现各种state的结构体,放在这里吧,不知道有

啥用.待进一步研究.

struct stats_ lwip_stats;

/** lwIP stats container */

struct stats_ {

#if LINK_STATS

/** Link level */

struct stats_proto link;

#endif

#if ETHARP_STATS

/** ARP */

struct stats_proto etharp;

#endif

#if IPFRAG_STATS

/** Fragmentation */

struct stats_proto ip_frag;

#endif

#if IP_STATS

/** IP */

struct stats_proto ip;

#endif

#if ICMP_STATS

/** ICMP */

struct stats_proto icmp;

#endif

#if IGMP_STATS

/** IGMP */

struct stats_igmp igmp;

#endif

#if UDP_STATS

/** UDP */

struct stats_proto udp;

#endif

#if TCP_STATS

/** TCP */

struct stats_proto tcp;

#endif

#endif

#if MEM_STATS

/** Heap */

struct stats_mem mem;

#endif

#if MEMP_STATS

/** Internal memory pools */

struct stats_mem *memp[MEMP_MAX];

#endif

#if SYS_STATS

/** System */

struct stats_sys sys;

#endif

#if IP6_STATS

/** IPv6 */

struct stats_proto ip6;

#endif

#if ICMP6_STATS

/** ICMP6 */

struct stats_proto icmp6;

#endif

#if IP6_FRAG_STATS

/** IPv6 fragmentation */

struct stats_proto ip6_frag;

#endif

#if MLD6_STATS

/** Multicast listener discovery */

struct stats_igmp mld6;

#endif

#if ND6_STATS

/** Neighbor discovery */

struct stats_proto nd6;

#endif

#if MIB2_STATS

/** SNMP MIB2 */

struct stats_mib2 mib2;

#endif

};

回到lwip_init()主线上来.

_init()

受NO_SYS宏控制,目前是个空函数,暂时也不管.

_mem_init()

/**

* Zero the heap and initialize start, end and lowest-free

*/

void

lwip_mem_init(void)

{

struct mem *mem;

LWIP_ASSERT("Sanity check alignment",

(SIZEOF_STRUCT_MEM & (MEM_ALIGNMENT - 1)) == 0);

/* align the heap */

ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);

/* initialize the start of the heap */

mem = (struct mem *)(void *)ram;

mem->next = MEM_SIZE_ALIGNED;

mem->prev = 0;

mem->used = 0;

/* initialize the end of the heap */

ram_end = ptr_to_mem(MEM_SIZE_ALIGNED);

ram_end->used = 1;

ram_end->next = MEM_SIZE_ALIGNED;

ram_end->prev = MEM_SIZE_ALIGNED;

MEM_SANITY();

/* initialize the lowest-free pointer to the start of the heap */

lfree = (struct mem *)(void *)ram;

MEM_STATS_AVAIL(avail, MEM_SIZE_ALIGNED);

if (sys_mutex_new(&mem_mutex) != ERR_OK) {

LWIP_ASSERT("failed to create mem_mutex", 0);

}

}

内存堆的初始化,初始化了ram和ram_end定义,一般来说,ram只能在ram到ram_end – 1区申请,因为ram_end总是used,如果ram_end

一使用,就溢出,他是最后一个元素+1,永远不能用.

3.1定义一个mem结构体

显然是一个双向链表.

/**

* The heap is made up as a list of structs of this type.

* This does not have to be aligned since for getting its size,

* we only use the macro SIZEOF_STRUCT_MEM, which automatically aligns.

*/

struct mem {

/** index (-> ram[next]) of the next struct */

mem_size_t next;

/** index (-> ram[prev]) of the previous struct */

mem_size_t prev;

/** 1: this area is used; 0: this area is unused */

u8_t used;

#if MEM_OVERFLOW_CHECK

/** this keeps track of the user allocation size for guard checks */

mem_size_t user_size;

#endif

};

3.2对齐操作

ram = (u8_t *)LWIP_MEM_ALIGN(LWIP_RAM_HEAP_POINTER);

3.3初始化堆的start和end

_init()

/**

* Initializes lwIP built-in pools.

* Related functions: memp_malloc, memp_free

*

* Carves out memp_memory into linked lists for each pool-type.

*/

void

memp_init(void)

{

u16_t i;

/* for every pool: */

for (i = 0; i < LWIP_ARRAYSIZE(memp_pools); i++) {

memp_init_pool(memp_pools[i]);

#if LWIP_STATS && MEMP_STATS

lwip_[i] = memp_pools[i]->stats;

#endif

}

#if MEMP_OVERFLOW_CHECK >= 2

/* check everything a first time to see if it worked */

memp_overflow_check_all();

#endif /* MEMP_OVERFLOW_CHECK >= 2 */

}

内存池初始化,这个就不展开讲了,回去看看第四讲,提示一下name,type,size,desc四个主要元素.能想起来吗?

_init()

目前也是空函数

_init()

重要的东西来了,前面也应提到过了,本来计划单独拿出来的.但是放在这里还是系统些.虽然本篇篇幅会相对较长.

void

netif_init(void)

{

#if LWIP_HAVE_LOOPIF

#if LWIP_IPV4

#define LOOPIF_ADDRINIT &loop_ipaddr, &loop_netmask, &loop_gw,

ip4_addr_t loop_ipaddr, loop_netmask, loop_gw;

IP4_ADDR(&loop_gw, 127, 0, 0, 1);

IP4_ADDR(&loop_ipaddr, 127, 0, 0, 1);

IP4_ADDR(&loop_netmask, 255, 0, 0, 0);

#else /* LWIP_IPV4 */

#define LOOPIF_ADDRINIT

#endif /* LWIP_IPV4 */

#if NO_SYS

netif_add(&loop_netif, LOOPIF_ADDRINIT NULL, netif_loopif_init, ip_input);

#else /* NO_SYS */

netif_add(&loop_netif, LOOPIF_ADDRINIT NULL, netif_loopif_init, tcpip_input);

#endif /* NO_SYS */

#if LWIP_IPV6

IP_ADDR6_HOST(loop_6_addr, 0, 0, 0, 0x00000001UL);

loop_6_addr_state[0] = IP6_ADDR_VALID;

#endif /* LWIP_IPV6 */

netif_set_link_up(&loop_netif);

netif_set_up(&loop_netif);

#endif /* LWIP_HAVE_LOOPIF */

}

6.1利用ip4_ADDR()函数设置默认网关,隐码,ip地址(就是回环地址).

6.2利用netif_add()函数添加回环接口到netif链表中.

我们以有OS的为例

netif_add(&loop_netif, LOOPIF_ADDRINIT NULL, netif_loopif_init, tcpip_input);

netif_add函数的原型太过庞大,这里我只分析一下参数

struct netif *netif_add(struct netif *netif,const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,void *state, netif_init_fn init, netif_input_f

n input)

参数:添加的网卡接口,ip地址,隐码,网关,状态,网卡接口初始化回调函数,网卡数据接收回调函数

关于网卡接口也就是netif结构体,这里也需要展开来讲:

/** Generic data structure used for all lwIP network interfaces.

* The following fields should be filled in by the initialization

* function for the device driver: hwaddr_len, hwaddr[], mtu, flags */

struct netif {

#if !LWIP_SINGLE_NETIF

/** pointer to next in linked list */

struct netif *next;

#endif

#if LWIP_IPV4

/** IP address configuration in network byte order */

ip_addr_t ip_addr;

ip_addr_t netmask;

ip_addr_t gw;

#endif /* LWIP_IPV4 */

#if LWIP_IPV6

/** Array of IPv6 addresses for this netif. */

ip_addr_t ip6_addr[LWIP_IPV6_NUM_ADDRESSES];

/** The state of each IPv6 address (Tentative, Preferred, etc).

* @see ip6_addr.h */

u8_t ip6_addr_state[LWIP_IPV6_NUM_ADDRESSES];

#if LWIP_IPV6_ADDRESS_LIFETIMES

/** Remaining valid and preferred lifetime of each IPv6 address, in seconds.

* For valid lifetimes, the special value of IP6_ADDR_LIFE_STATIC (0)

* indicates the address is static and has no lifetimes. */

u32_t ip6_addr_valid_life[LWIP_IPV6_NUM_ADDRESSES];

u32_t ip6_addr_pref_life[LWIP_IPV6_NUM_ADDRESSES];

#endif /* LWIP_IPV6_ADDRESS_LIFETIMES */

#endif /* LWIP_IPV6 */

/** This function is called by the network device driver

* to pass a packet up the TCP/IP stack. */

netif_input_fn input;

#if LWIP_IPV4

/** This function is called by the IP module when it wants

* to send a packet on the interface. This function typically

* first resolves the hardware address, then sends the packet.

* For ethernet physical layer, this is usually etharp_output() */

netif_output_fn output;

#endif /* LWIP_IPV4 */

/** This function is called by ethernet_output() when it wants

* to send a packet on the interface. This function outputs

* the pbuf as-is on the link medium. */

netif_linkoutput_fn linkoutput;

#if LWIP_IPV6

/** This function is called by the IPv6 module when it wants

* to send a packet on the interface. This function typically

* first resolves the hardware address, then sends the packet.

* For ethernet physical layer, this is usually ethip6_output() */

netif_output_ip6_fn output_ip6;

#endif /* LWIP_IPV6 */

#if LWIP_NETIF_STATUS_CALLBACK

/** This function is called when the netif state is set to up or down

*/

netif_status_callback_fn status_callback; netif_status_callback_fn status_callback;#endif /* LWIP_NETIF_STATUS_CALLBACK */

#if LWIP_NETIF_LINK_CALLBACK /** This function is called when the netif link is set to up or down */ netif_status_callback_fn link_callback;#endif /* LWIP_NETIF_LINK_CALLBACK */#if LWIP_NETIF_REMOVE_CALLBACK /** This function is called when the netif has been removed */ netif_status_callback_fn remove_callback;#endif /* LWIP_NETIF_REMOVE_CALLBACK */ /** This field can be set by the device driver and could point * to state information for the device. */ void *state;#ifdef netif_get_client_data void* client_data[LWIP_NETIF_CLIENT_DATA_INDEX_MAX + LWIP_NUM_NETIF_CLIENT_DATA];#endif#if LWIP_NETIF_HOSTNAME /* the hostname for this netif, NULL is a valid value */ const char* hostname;#endif /* LWIP_NETIF_HOSTNAME */#if LWIP_CHECKSUM_CTRL_PER_NETIF u16_t chksum_flags;#endif /* LWIP_CHECKSUM_CTRL_PER_NETIF*/ /** maximum transfer unit (in bytes) */ u16_t mtu;#if LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES

/** maximum transfer unit (in bytes), updated by RA */ u16_t mtu6;#endif /* LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES */ /** link level hardware address of this interface */ u8_t hwaddr[NETIF_MAX_HWADDR_LEN]; /** number of bytes used in hwaddr */ u8_t hwaddr_len; /** flags (@see @ref netif_flags) */ u8_t flags; /** descriptive abbreviation */

char name[2]; /** number of this interface. Used for @ref if_api and @ref netifapi_netif,

* as well as for IPv6 zones */ u8_t num;#if LWIP_IPV6_AUTOCONFIG /** is this netif enabled for IPv6 autoconfiguration */ u8_t ip6_autoconfig_enabled;#endif /* LWIP_IPV6_AUTOCONFIG */#if LWIP_IPV6_SEND_ROUTER_SOLICIT /** Number of Router Solicitation messages that remain to be sent. */ u8_t rs_count;#endif /* LWIP_IPV6_SEND_ROUTER_SOLICIT */#if MIB2_STATS /** link type (from "snmp_ifType" enum from snmp_mib2.h) */ u8_t link_type; /** (estimate) link speed */ u32_t link_speed; /** timestamp at last change made (up/down) */ u32_t ts; /** counters */ struct stats_mib2_netif_ctrs mib2_counters;#endif /* MIB2_STATS */#if LWIP_IPV4 && LWIP_IGMP /** This function could be called to add or delete an entry in the multicast filter table of the ethernet MAC.*/ netif_igmp_mac_filter_fn igmp_mac_filter;#endif /* LWIP_IPV4 && LWIP_IGMP */#if LWIP_IPV6 && LWIP_IPV6_MLD /** This function could be called to add or delete an entry in the IPv6 multicast

/** This function could be called to add or delete an entry in the IPv6 multicast

filter table of the ethernet MAC. */

netif_mld_mac_filter_fn mld_mac_filter;

#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */

#if LWIP_NETIF_USE_HINTS

struct netif_hint *hints;

#endif /* LWIP_NETIF_USE_HINTS */

#if ENABLE_LOOPBACK

/* List of packets to be queued for ourselves. */

struct pbuf *loop_first;

struct pbuf *loop_last;

#if LWIP_LOOPBACK_MAX_PBUFS

u16_t loop_cnt_current;

#endif /* LWIP_LOOPBACK_MAX_PBUFS */

#endif /* ENABLE_LOOPBACK */

};

有没有被吓到,反正我是被吓到了.所以做一下减法:

条件编译和ipv6的先不看

struct netif {

struct netif *next; //

显然可以存在多网卡组成单向链表.

#if LWIP_IPV4

ip_addr_t ip_addr;

 ip_addr_t netmask;

 ip_addr_t gw;

#endif /* LWIP_IPV4 */

   netif_input_fn input; //

网卡数据接收回调函数,面向

ip

协议

   netif_output_fn output; //

网卡数据发送回调函数

   netif_linkoutput_fn linkoutput; //

网卡数据接收回调函数,(我理解为面向

arp

协议)

   netif_status_callback_fn status_callback; //

网卡启动

/

关闭回调函数

   u16_t mtu; //

最大数据传输单元

   const char* hostname;  //

主机名

   u8_t hwaddr[NETIF_MAX_HWADDR_LEN];  //mac

地址,

NETIF_MAX_HWADDR_LEN

猜到应该是

6

   u8_t hwaddr_len;  //mac

地址长度

   u8_t flags; 

   u8_t num;  //

网卡被用次数

...

}

这样一看就简单多了,结合数据传输理解也很容易想到这些成员变量.

6.3启动接口

netif_set_link_up(&loop_netif);

netif_set_up(&loop_netif);

netif_set_up是使能网卡,设置NETIF_FLAG_UP标志位,必须在网卡被使用前用户来调用

netif_set_link_up是当网卡链路层active时由网卡驱动来设置的,如,station关联上AP后就应该调用netif_set_link_up

_init()/etharp_init()/raw_init()

都是空函数

_init()/tcp_init()

这两个函数也简单,

udp_port = UDP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());

tcp_port = TCP_ENSURE_LOCAL_PORT_RANGE(LWIP_RAND());

就做了一件事,限制端口号的范围

_init()

/**

* Initialize the IGMP module

*/

void

igmp_init(void)

{

LWIP_DEBUGF(IGMP_DEBUG, ("igmp_init: initializingn"));

IP4_ADDR(&allsystems, 224, 0, 0, 1);

IP4_ADDR(&allrouters, 224, 0, 0, 2);

}

_init()

这个也需要详细讲解一下

/**

* Initialize the resolver: set up the UDP pcb and configure the default server

* (if DNS_SERVER_ADDRESS is set).

*/

void

dns_init(void)

{

#ifdef DNS_SERVER_ADDRESS

/* initialize default DNS server address */

ip_addr_t dnsserver;

DNS_SERVER_ADDRESS(&dnsserver);

dns_setserver(0, &dnsserver);

#endif /* DNS_SERVER_ADDRESS */

LWIP_ASSERT("sanity check SIZEOF_DNS_QUERY",

sizeof(struct dns_query) == SIZEOF_DNS_QUERY);

LWIP_ASSERT("sanity check SIZEOF_DNS_ANSWER",

sizeof(struct dns_answer) <= SIZEOF_DNS_ANSWER_ASSERT);

LWIP_DEBUGF(DNS_DEBUG, ("dns_init: initializingn"));

/* if dns client not */

#if ((LWIP_DNS_SECURE & LWIP_DNS_SECURE_RAND_SRC_PORT) == 0)

if (dns_pcbs[0] == NULL) {

dns_pcbs[0] = udp_new_ip_type(IPADDR_TYPE_ANY);

LWIP_ASSERT("dns_pcbs[0] != NULL", dns_pcbs[0] != NULL);

/* initialize DNS table not needed (initialized to zero since it is a

* global variable) */

LWIP_ASSERT("For implicit initialization to work, DNS_STATE_UNUSED needs to be 0",

DNS_STATE_UNUSED == 0);

/* initialize DNS client */

udp_bind(dns_pcbs[0], IP_ANY_TYPE, 0);

udp_recv(dns_pcbs[0], dns_recv, NULL);

}

#endif

#if DNS_LOCAL_HOSTLIST

dns_init_local();

#endif

}

10.1设置默认的dns服务器

关于这个,本人在调试过程中深有体会,移植好lwip后,直接ping ip地址一切正常,但是无论怎样就是解析不了域名,完整了跟了dns.c

的代码,硬是没有找到哪里有问题,后来还是用ntp时发现的,ntp默认选择了阿里的一个服务器,于是灵机一动,dns当初好像没有这是默

认服务器,中间也没有去指定.心累.

core/dns.c:92:#define DNS_SERVER_ADDRESS(ipaddr) (ip4_addr_set_u32(ipaddr, ipaddr_addr(“208.67.222.222”)))

/* */

这样就ok了.

10.2设置dns_pcb控制块

初始化dns table

_init()

点对点协议还没有去了解过,这里拿出来大家一起看看吧.

/************************************/

/*** PRIVATE FUNCTION DEFINITIONS ***/

/************************************/

/* Initialize the PPP subsystem. */

int ppp_init(void)

{

#if PPPOS_SUPPORT

LWIP_MEMPOOL_INIT(PPPOS_PCB);

#endif

#if PPPOE_SUPPORT

LWIP_MEMPOOL_INIT(PPPOE_IF);

#endif

#if PPPOL2TP_SUPPORT

LWIP_MEMPOOL_INIT(PPPOL2TP_PCB);

#endif

#if LWIP_PPP_API && LWIP_MPU_COMPATIBLE

LWIP_MEMPOOL_INIT(PPPAPI_MSG);

#endif

LWIP_MEMPOOL_INIT(PPP_PCB);

/*

* Initialize magic number generator now so that protocols may

* use magic numbers in initialization.

*/

magic_init();

return 0;

}

粗略看好像就是两个动作:初始化一些内存池子,然后初始化一个随机数.有什么用?鄙人也不清楚.emm

_timeouts_init()

/** Initialize this module */

void sys_timeouts_init(void)

{

size_t i;

/* tcp_tmr() at index 0 is started on demand */

for (i = (LWIP_TCP ? 1 : 0); i < LWIP_ARRAYSIZE(lwip_cyclic_timers); i++) {

/* we have to cast via size_t to get rid of const warning

(this is OK as cyclic_timer() casts back to const* */

sys_timeout(lwip_cyclic_timers[i].interval_ms, lwip_cyclic_timer, LWIP_CONST_CAST(void *, &lwip_cyclic_timers[i]));

}

}

最主要就是循环sys_timeout,而这个正是定时任务,网络应用里面很多定时应用.最关键肯定是lwip_cyclic_timer,第二个参数对应的是执行用

的fn,然后是参数.

我们看看需要定时的任务有很多.

const int lwip_num_cyclic_timers = LWIP_ARRAYSIZE(lwip_cyclic_timers);

/** This array contains all stack-internal cyclic timers. To get the number of

* timers, use LWIP_ARRAYSIZE() */

const struct lwip_cyclic_timer lwip_cyclic_timers[] = {

#if LWIP_TCP

/* The TCP timer is a special case: it does not have to run always and

is triggered to start from TCP using tcp_timer_needed() */

{TCP_TMR_INTERVAL, HANDLER(tcp_tmr)},

#endif /* LWIP_TCP */

#if LWIP_IPV4

#if IP_REASSEMBLY

{IP_TMR_INTERVAL, HANDLER(ip_reass_tmr)},

#endif /* IP_REASSEMBLY */

#if LWIP_ARP

{ARP_TMR_INTERVAL, HANDLER(etharp_tmr)},

#endif /* LWIP_ARP */

#if LWIP_DHCP

{DHCP_COARSE_TIMER_MSECS, HANDLER(dhcp_coarse_tmr)},

{DHCP_FINE_TIMER_MSECS, HANDLER(dhcp_fine_tmr)},

#endif /* LWIP_DHCP */

#if LWIP_AUTOIP

{AUTOIP_TMR_INTERVAL, HANDLER(autoip_tmr)},

#endif /* LWIP_AUTOIP */

#if LWIP_IGMP

{IGMP_TMR_INTERVAL, HANDLER(igmp_tmr)},

#endif /* LWIP_IGMP */

#endif /* LWIP_IPV4 */

#if LWIP_DNS

{DNS_TMR_INTERVAL, HANDLER(dns_tmr)},

#endif /* LWIP_DNS */

#if LWIP_IPV6

{ND6_TMR_INTERVAL, HANDLER(nd6_tmr)},

#if LWIP_IPV6_REASS

{IP6_REASS_TMR_INTERVAL, HANDLER(ip6_reass_tmr)},

#endif /* LWIP_IPV6_REASS */

#if LWIP_IPV6_MLD

{MLD6_TMR_INTERVAL, HANDLER(mld6_tmr)},

#endif /* LWIP_IPV6_MLD */

#if LWIP_IPV6_DHCP6

{DHCP6_TIMER_MSECS, HANDLER(dhcp6_tmr)},

#endif /* LWIP_IPV6_DHCP6 */

#endif /* LWIP_IPV6 */

};

都是xxx_tmr()函数,前面鼓励过大家在调试时大胆的把所有DEBUG宏打开,如果这样做了,你应该就会看到很多xxx_tmr的打印.

比如TCP断开连接等等,都是需要定时器参与的.就知道这一点,足够了.

好了,这一期讲的有点儿多了,不过总算是瞎扯完了.还有很多细节的东西大家自己去挖掘吧!

-------------------------------------------

这期就到这里了,LWIP想怎么玩就怎么玩,我们下期再见.