【lwip】06-網路介面層分析
前言
主要分析網路介面概念、網卡數據結構、網路介面、環回介面實現等等。
參考:
6.1 概念引入
網路介面(乙太網介面)是硬體介面(網路介面又可以稱之為網卡)。
LWIP 是軟體那麼而怎樣讓硬體和軟體無縫連接起來呢?
而且網卡又多種多樣,怎樣才能讓 LWIP 使用同樣的軟體兼容不同的硬體平台?
LWIP 中使用了一個netif
結構體來描述網卡但是網卡是直接和硬體平台打交道的:
-
用戶提供最底層介面函數。
-
LWIP 提供統一的 API。
-
舉例:
- 收:如網卡的初始化和網卡的收發數據,當 LWIP 底層得到數據之後,才會傳入到內核中去處理。
- 發:LWIP 內核需要發送數據包的時候,也需要調用網卡的發送函數。
-
LWIP 中的 etherneif.c 文件的函數通常為硬體打交道的底層函數,當有數據需要通過網卡接收或者發送數據的時候就會被調用,通過 LWIP 的協議棧的內部進行處理後,從應用層就能得到數據或者可以發送數據。
小總結:
簡單來說,netif 是 LWIP 抽象出來的網卡,LWIP 協議棧可以使用多種不同介面,而 etherneif.c 文件則提供了乙太網網卡 netif 的抽象,每個網卡有不同的實現方式,每戶只需要修改 ethernetif.c 文件即可。
小筆記:
-
網卡就是一個水管介面,把上層tcpip協議棧和底層數據鏈路對接起來,使其數據流通。
- 收:底層數據鏈路的數據,經過網卡,網卡把這些數據解析好,然後格式化為上層協議棧需要的數據格式,再把這些數據交給上層協議棧。
- 發:上層協議棧的數據,經過網卡,網卡把這些數據解析好,然後格式化為底層數據鏈路需要的數據格式,再把這些數據交給底層讓其發出去。
-
網卡底層自由:
- 由於網路介面需要對接的上層包含了鏈路層的乙太網幀處理
ethnet_input()
和ethnet_output()
。 - 所以網路介面底層對接的可以直接是自由數據,包括UART、SPI、乙太網設備、GPRS、其它執行緒接過來的數據等等任何數據流。
- 由於網路介面需要對接的上層包含了鏈路層的乙太網幀處理
6.2 網路介面層數據概念流圖
圖源自李柱明
6.3 網卡收包程式流圖
圖源自李柱明
6.4 網卡數據結構
6.4.1 struct 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;
#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
filter table of the ethernet MAC. */
netif_mld_mac_filter_fn mld_mac_filter;
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
#if LWIP_ACD
struct acd *acd_list;
#endif /* LWIP_ACD */
#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; /* 環回數據包快取的首個pbuf地址 */
struct pbuf *loop_last; /* 環回數據包快取的最後一個pbuf地址,用於後面繼續有pbuf入隊時,從尾部插入。(因為pbuf鏈表是單向非循環鏈表) */
#if LWIP_LOOPBACK_MAX_PBUFS
u16_t loop_cnt_current; /* 當前有多少個環回數據包未讀 */
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
/* Used if the original scheduling failed. */
/* 是否需要把netif_poll()API重新轉發到lwip內核執行緒去跑。在轉發失敗時,標記下次要重新轉發 */
u8_t reschedule_poll;
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
#endif /* ENABLE_LOOPBACK */
};
6.4.2 欄位分析
6.4.2.1 網卡鏈表
/** pointer to next in linked list */
struct netif *next;
LWIP 使用鏈表來統一管理同一設備的多個網卡。
netif.c 文件中定義兩個全局指針 struct netif *netif_list
和 struct netif *netif_default
netif_list
就是網卡鏈表指針,指向網卡鏈表的首節點(第一個網卡)。netif_default
默認網卡。
6.4.2.2 網路 IP
#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 */
ip_addr
:網路中的 IP 地址。
netmask
:子網掩碼。
gw
:網關地址。
6.4.2.3 接收數據函數input()
/** This function is called by the network device driver
* to pass a packet up the TCP/IP stack. */
netif_input_fn input;
該函數為網卡北向出口函數,是把底層數據包往協議棧送的,一般是ethernet_input()
,送入arp協議處理,再往上送。
6.4.2.4 網路IP層發送數據函數output()
#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 */
函數由 IP 層調用,在介面上發送數據包。用戶需要編寫該函數並使 output 指向它。
通這個函數的處理步驟是首先解析硬體地址,然後發送數據包。
對於乙太網物理層,該函數通常是 etharp_output()
,參數是 pbuf
,netif
,和 ip_addr
類型。
注意:其中 ipaddr
代表要將數據包發送到的地址,但不一定數據包最終到達的 ip 地址。比如要發送 ip 數據報到一個並不在本網路的主機上,該數據包要被發送到一個路由器上,這裡的 ipaddr 就是路由器的 ip 地址。
6.4.2.5 鏈路層發送函數linkoutput()
/** 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;
該函數和 output
類似,也需要用戶自己實現一個函數,但是只有兩個參數,一般是自定義函數 low_level_output()
。
當需要在網卡上發送一個數據包時,該函數會被 ethernet_output()
函數調用,在底層發送數據。
6.4.2.6 出口回調函數
#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 */
當 netif 被刪除時調用此函數。
6.4.2.7 用戶私有數據
/** 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_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 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*/
由設備驅動程式設置並指向設備的狀態資訊,主要將網卡的某些私有數據傳遞給上層。
6.4.2.8 最大傳輸單位
/** maximum transfer unit (in bytes) */
u16_t mtu;
6.4.2.9 鏈路硬體地址長度&地址
/** number of bytes used in hwaddr */
u8_t hwaddr_len;
/** link level hardware address of this interface */
u8_t hwaddr[NETIF_MAX_HWADDR_LEN];
6.4.2.10 網卡資訊狀態標誌
/** flags (@see @ref netif_flags) */
u8_t flags;
網卡狀態資訊標誌位,是很重要的控制欄位,它包括網卡的功能使能,廣播使能,ARP 使能等重要控制位。
6.4.2.11 網卡名字
/** descriptive abbreviation */
char name[2];
用於保存每一個網卡的名字,用兩個字元的名字來標識。
網卡使用的設備驅動的種類,名字由設備驅動來設置並且應該反映通過網卡表示的硬體的種類。
如果兩個網卡具有相同的網路名字,我們就用 num 欄位來區分相同類別的不同網卡。
6.4.2.12 網卡標識
/** number of this interface */
u8_t num;
標識使用同種驅動類型的不同網卡。
6.4.2.13 網卡提示
#if LWIP_NETIF_USE_HINTS
struct netif_hint *hints;
#endif /* LWIP_NETIF_USE_HINTS */
網卡提示,有些網卡相關的數據保存空間。
其數據結構:
struct netif_hint {
#if LWIP_NETIF_HWADDRHINT
u8_t addr_hint;
#endif
#if LWIP_VLAN_PCP
/** VLAN hader is set if this is >= 0 (but must be <= 0xFFFF) */
s32_t tci;
#endif
};
比如addr_hint
在ARP協議中的用途就是,噹噹前網卡發送了一包數據時用到了ARP快取表某條arp entry時,就會保存這個arp entry的索引到addr_hint
中,下次這個網卡發送數據時,通過addr_hint
獲取索引後,直接從這條索引開始遍歷ARP快取表,加速組包時間。
tci
是用於VLAN組包中的欄位值。
6.5 netif 快速使用
6.5.1 簡要步驟
-
定義一個
netif
作為網卡設備結構體。 -
掛載到
netif_list
鏈表中:netif_add();
。- 需要提供網卡初始化函數和網卡協議棧入口函數作為
netif_add()
的參數傳入。
- 需要提供網卡初始化函數和網卡協議棧入口函數作為
-
使能網卡底層(鏈路層):
netif_set_link_up()
。 -
使能網卡上層(協議棧):
netif_set_up()
。
6.5.2 與 netif 相關的底層函數
low_level_output()
和low_level_input()
函數是網卡的南向直接操作函數,是對網卡設備的寫、讀處理。
相當於網卡設備的驅動範疇的函數。
主要 API:
/* 網卡初始化函數 */
static void low_level_init(struct netif *netif);
/* 網卡的發送函數,
* 將內核的數據包發送出去,數據包採用pbuf數據結構進行描述 */
static err_t low_level_output(struct netif *netif, struct pbuf *p);
/* 網卡的數據接收函數,
* 該函數必須將接收的數據封裝成pbuf的形式 */
static struct pbuf * low_level_input(struct netif *netif);
相關 API:(如乙太網)
/* 上層管理網卡netif的到時候會被調用的函數,
* 最終還是調用 low_level_init() 函數 */
err_t ethernetif_init(struct netif *netif);
/* 主要作用就是調用low_level_input()函數從網卡中讀取一個數據包,
* 然後解析該數據包的類型是屬於ARP數據包還是IP數據包,
* 再將包遞交給上層。
* 無作業系統中使用:可以直接使用的函數,因為內核會周期性去處理該接收函數。
* 有作業系統中系統:一般會將其改寫成一個執行緒的形式,可以周期性去調用low_level_input()網卡接收函數。*/
void ethernetif_input(void *pParams);
6.6 網卡資訊狀態標誌
在網卡netif->flags;
成員中體現。
-
NETIF_FLAG_UP
:- 網路介面上層是否被使能。
- 屬於一個軟體(協議棧)就緒標誌,表示lwip協議棧已經合法獲取到該IP,lwip協議棧已經準備好接收處理這個網卡的數據了。
- 相當於一個lwip協議棧內部與外部南向通訊的開關閥。
-
NETIF_FLAG_BROADCAST
:網路介面是否支援廣播。 -
NETIF_FLAG_LINK_UP
:- 網路介面的底層鏈路是否被使能。
- 屬於一個硬體(鏈路層)就緒標誌,表示當前網卡南向介面使用的數據鏈路硬體就緒。
-
NETIF_FLAG_ETHARP
:網路介面是否支援ARP功能。 -
NETIF_FLAG_ETHERNET
:網路介面是否是乙太網設備。 -
NETIF_FLAG_IGMP
:網路介面是否支援IGMP協議功能。 -
NETIF_FLAG_MLD6
:網路介面是否支援MLD6功能。
/**
* @defgroup netif_flags Flags
* @ingroup netif
* @{
*/
/** Whether the network interface is 'up'. This is
* a software flag used to control whether this network
* interface is enabled and processes traffic.
* It must be set by the startup code before this netif can be used
* (also for dhcp/autoip).
*/
#define NETIF_FLAG_UP 0x01U
/** If set, the netif has broadcast capability.
* Set by the netif driver in its init function. */
#define NETIF_FLAG_BROADCAST 0x02U
/** If set, the interface has an active link
* (set by the network interface driver).
* Either set by the netif driver in its init function (if the link
* is up at that time) or at a later point once the link comes up
* (if link detection is supported by the hardware). */
#define NETIF_FLAG_LINK_UP 0x04U
/** If set, the netif is an ethernet device using ARP.
* Set by the netif driver in its init function.
* Used to check input packet types and use of DHCP. */
#define NETIF_FLAG_ETHARP 0x08U
/** If set, the netif is an ethernet device. It might not use
* ARP or TCP/IP if it is used for PPPoE only.
*/
#define NETIF_FLAG_ETHERNET 0x10U
/** If set, the netif has IGMP capability.
* Set by the netif driver in its init function. */
#define NETIF_FLAG_IGMP 0x20U
/** If set, the netif has MLD6 capability.
* Set by the netif driver in its init function. */
#define NETIF_FLAG_MLD6 0x40U
6.7 添加網卡netif_add()
主要內容是:
- 網卡數據結構內容配置;
- 把這個網卡插入到網卡鏈表
netif_list
。
參數:
netif
:網卡數據結構資源。ipaddr
:網卡IP地址。netmask
:網卡子網掩碼。gw
:網卡網關。state
:用戶自定義的一些數據。init
:網卡初始化函數。各種網卡的初始化不完全一樣,所以又用戶自己實現。input
:網卡北方北向介面。是把數據傳入TCPIP協議棧的介面。
/**
* @ingroup netif
* Add a network interface to the list of lwIP netifs.
*
* @param netif a pre-allocated netif structure
* @param ipaddr IP address for the new netif
* @param netmask network mask for the new netif
* @param gw default gateway IP address for the new netif
* @param state opaque data passed to the new netif
* @param init callback function that initializes the interface
* @param input callback function that is called to pass
* ingress packets up in the protocol layer stack.<br>
* It is recommended to use a function that passes the input directly
* to the stack (netif_input(), NO_SYS=1 mode) or via sending a
* message to TCPIP thread (tcpip_input(), NO_SYS=0 mode).<br>
* These functions use netif flags NETIF_FLAG_ETHARP and NETIF_FLAG_ETHERNET
* to decide whether to forward to ethernet_input() or ip_input().
* In other words, the functions only work when the netif
* driver is implemented correctly!<br>
* Most members of struct netif should be be initialized by the
* netif init function = netif driver (init parameter of this function).<br>
* IPv6: Don't forget to call netif_create_ip6_linklocal_address() after
* setting the MAC address in struct netif.hwaddr
* (IPv6 requires a link-local address).
*
* @return netif, or NULL if failed.
*/
struct netif *
netif_add(struct netif *netif,
#if LWIP_IPV4
const ip4_addr_t *ipaddr, const ip4_addr_t *netmask, const ip4_addr_t *gw,
#endif /* LWIP_IPV4 */
void *state, netif_init_fn init, netif_input_fn input)
{
#if LWIP_IPV6
s8_t i;
#endif
LWIP_ASSERT_CORE_LOCKED();
#if LWIP_SINGLE_NETIF
if (netif_default != NULL) { /* 單網卡功能監測 */
LWIP_ASSERT("single netif already set", 0);
return NULL;
}
#endif
/* 網卡數據結構記憶體資源檢驗 */
LWIP_ERROR("netif_add: invalid netif", netif != NULL, return NULL);
/* 網卡初始化鉤子函數校驗 */
LWIP_ERROR("netif_add: No init function given", init != NULL, return NULL);
#if LWIP_IPV4
if (ipaddr == NULL) {
ipaddr = ip_2_ip4(IP4_ADDR_ANY); /* 獲取IPV4地址 */
}
if (netmask == NULL) {
netmask = ip_2_ip4(IP4_ADDR_ANY); /* 獲取子網掩碼 */
}
if (gw == NULL) {
gw = ip_2_ip4(IP4_ADDR_ANY); /* 獲取網關 */
}
/* 重置網卡配置狀態 */
ip_addr_set_zero_ip4(&netif->ip_addr);
ip_addr_set_zero_ip4(&netif->netmask);
ip_addr_set_zero_ip4(&netif->gw);
netif->output = netif_null_output_ip4;
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
for (i = 0; i < LWIP_IPV6_NUM_ADDRESSES; i++) { /* 遍歷當前網卡的各個IPV6地址空間 */
ip_addr_set_zero_ip6(&netif->ip6_addr[i]); /* 重置這個IPV6 */
netif->ip6_addr_state[i] = IP6_ADDR_INVALID; /* 標記為無效態 */
#if LWIP_IPV6_ADDRESS_LIFETIMES /* ipv6生存期欄位 */
netif->ip6_addr_valid_life[i] = IP6_ADDR_LIFE_STATIC; /* 靜態地址,沒有生存期 */
netif->ip6_addr_pref_life[i] = IP6_ADDR_LIFE_STATIC; /* 靜態地址,沒有生存期 */
#endif /* LWIP_IPV6_ADDRESS_LIFETIMES */
}
netif->output_ip6 = netif_null_output_ip6; /* 初始化北方南向介面為一個虛擬介面 */
#endif /* LWIP_IPV6 */
NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_ENABLE_ALL);
netif->mtu = 0; /* 重置MTU */
netif->flags = 0; /* 重置網卡狀態 */
#ifdef netif_get_client_data
memset(netif->client_data, 0, sizeof(netif->client_data)); /* 清空網卡數據結構用戶數據空間 */
#endif /* LWIP_NUM_NETIF_CLIENT_DATA */
#if LWIP_IPV6
#if LWIP_IPV6_AUTOCONFIG
/* 預設情況下,IPv6地址自動配置功能處於開啟狀態 */
netif->ip6_autoconfig_enabled = 1;
#endif /* LWIP_IPV6_AUTOCONFIG */
nd6_restart_netif(netif); /* 使能ipv6網卡,配置路由請求RS次數 */
#endif /* LWIP_IPV6 */
#if LWIP_NETIF_STATUS_CALLBACK
netif->status_callback = NULL; /* 重置網卡上層(協議棧)狀態回調 */
#endif /* LWIP_NETIF_STATUS_CALLBACK */
#if LWIP_NETIF_LINK_CALLBACK
netif->link_callback = NULL; /* 重置底層(數據鏈路)狀態回調 */
#endif /* LWIP_NETIF_LINK_CALLBACK */
#if LWIP_IGMP
netif->igmp_mac_filter = NULL; /* 重置(ipv4)IGMP協議乙太網MAC過濾表函數 */
#endif /* LWIP_IGMP */
#if LWIP_IPV6 && LWIP_IPV6_MLD
netif->mld_mac_filter = NULL; /* 重置(ipv6)MLD協議乙太網MAC過濾表函數 */
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
netif->state = state; /* 配置網卡狀態 */
netif->num = netif_num; /* 配置網卡標識 */
netif->input = input; /* 配置網卡數據到TCPIP協議棧入口函數 */
#if LWIP_ACD
netif->acd_list = NULL; /* 重置ACD模組鏈表 */
#endif /* LWIP_ACD */
NETIF_RESET_HINTS(netif); /* 重置網卡hints欄位 */
#if ENABLE_LOOPBACK
netif->loop_first = NULL; /* 重置發送給自己的第一個數據包指針 */
netif->loop_last = NULL; /* 重置發送給自己的最後一個數據包指針 */
#if LWIP_LOOPBACK_MAX_PBUFS
netif->loop_cnt_current = 0; /* 重置發送給自己的pbuf最大快取包數限制 */
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
netif->reschedule_poll = 0; /* 是否在多執行緒環境下 */
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
#endif /* ENABLE_LOOPBACK */
#if LWIP_IPV4
netif_set_addr(netif, ipaddr, netmask, gw); /* 設置網卡IP、子網掩碼、網關 */
#endif /* LWIP_IPV4 */
/* 為netif調用用戶指定的初始化函數 */
if (init(netif) != ERR_OK) { /* 不同的網卡有不同的初始化,所以又用戶提供 */
return NULL;
}
#if LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES
/* 初始化IPv6的MTU為netif驅動設置的MTU。這可以稍後由RA進行更新。 */
netif->mtu6 = netif->mtu;
#endif /* LWIP_IPV6 && LWIP_ND6_ALLOW_RA_UPDATES */
#if !LWIP_SINGLE_NETIF
/* 網卡標識唯一性校驗。其演算法先在歷史累加獲得,溢出再複位。 */
{
struct netif *netif2;
int num_netifs;
do {
if (netif->num == 255) {
netif->num = 0;
}
num_netifs = 0;
for (netif2 = netif_list; netif2 != NULL; netif2 = netif2->next) {
LWIP_ASSERT("netif already added", netif2 != netif);
num_netifs++;
LWIP_ASSERT("too many netifs, max. supported number is 255", num_netifs <= 255);
if (netif2->num == netif->num) { /* 已經溢出過了,標識+1直接用 */
netif->num++;
break;
}
}
} while (netif2 != NULL);
}
if (netif->num == 254) { /* 網卡標識溢出,歸零 */
netif_num = 0;
} else { /* */
netif_num = (u8_t)(netif->num + 1); /* 更新網卡標識全局記錄值 */
}
/* 添加網卡到網卡鏈表頭部 */
netif->next = netif_list;
netif_list = netif;
#endif /* "LWIP_SINGLE_NETIF */
mib2_netif_added(netif);
#if LWIP_IGMP
if (netif->flags & NETIF_FLAG_IGMP) { /* 如果網卡支援IGMP功能 */
igmp_start(netif); /* 開始IGMP處理 */
}
#endif /* LWIP_IGMP */
LWIP_DEBUGF(NETIF_DEBUG, ("netif: added interface %c%c IP",
netif->name[0], netif->name[1]));
#if LWIP_IPV4
LWIP_DEBUGF(NETIF_DEBUG, (" addr "));
ip4_addr_debug_print(NETIF_DEBUG, ipaddr);
LWIP_DEBUGF(NETIF_DEBUG, (" netmask "));
ip4_addr_debug_print(NETIF_DEBUG, netmask);
LWIP_DEBUGF(NETIF_DEBUG, (" gw "));
ip4_addr_debug_print(NETIF_DEBUG, gw);
#endif /* LWIP_IPV4 */
LWIP_DEBUGF(NETIF_DEBUG, ("\n"));
/* 網卡添加完畢,把這個網卡數據結構地址及狀態通過回調鏈表ext_callback,通知到各個地方 */
netif_invoke_ext_callback(netif, LWIP_NSC_NETIF_ADDED, NULL);
return netif;
}
6.8 網卡上層協議棧使能netif_set_up()
netif_set_up()
函數用於使能網卡上層協議棧。
相當於打開網卡北向介面的開關閥,讓網卡和協議棧的數據能夠流通。
對應標誌位:NETIF_FLAG_UP
。表示上層協議棧準備好了,也打通了網卡與協議棧的通道。
/**
* @ingroup netif
* Bring an interface up, available for processing
* traffic.
*/
void
netif_set_up(struct netif *netif)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif_set_up: invalid netif", netif != NULL, return);
if (!(netif->flags & NETIF_FLAG_UP)) { /* 協議棧還沒使能 */
netif_set_flags(netif, NETIF_FLAG_UP); /* 標記使能協議棧 */
/* 記錄網卡協議棧使能時間 */
MIB2_COPY_SYSUPTIME_TO(&netif->ts);
/* 網卡狀態回調 */
NETIF_STATUS_CALLBACK(netif);
#if LWIP_NETIF_EXT_STATUS_CALLBACK
{
netif_ext_callback_args_t args;
args.status_changed.state = 1;
/* 網卡狀態靜態回調 */
netif_invoke_ext_callback(netif, LWIP_NSC_STATUS_CHANGED, &args);
}
#endif
/* 發送ARP/IGMP/MLD/RS事件,例如:linup / netifup或addr-change */
/* 協議棧和數據鏈路都使能了才生效 */
netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4 | NETIF_REPORT_TYPE_IPV6);
#if LWIP_IPV6
/* 重置ipv6網卡,設置路由請求RS次數(see RFC 4861, ch. 6.3.7) */
nd6_restart_netif(netif);
#endif /* LWIP_IPV6 */
}
}
6.9 網卡底層數據鏈路使能netif_set_link_up()
netif_set_link_up()
函數用於使能網卡底層數據鏈路。
相當於打開網卡南向介面的開關閥,讓網卡和底層數據鏈路的數據能夠流通。
對應標誌位:NETIF_FLAG_LINK_UP
。表示底層數據鏈路準備好了,也打通了網卡與底層數據鏈路的通道。
因為在數據鏈路層新增了一個設備節點,所以需要在鏈路層做一些處理,通告下。
比如發起ARP請求,宣告當前IP被我使用,給為同僚ARP快取表有空間就存一下吧。
鏈路層使能後,還需要通過回調通知其它業務,表明當前網卡狀態更新了。
/**
* @ingroup netif
* Called by a driver when its link goes up
*/
void
netif_set_link_up(struct netif *netif)
{
LWIP_ASSERT_CORE_LOCKED();
LWIP_ERROR("netif_set_link_up: invalid netif", netif != NULL, return);
if (!(netif->flags & NETIF_FLAG_LINK_UP)) { /* 數據鏈路還沒使能 */
netif_set_flags(netif, NETIF_FLAG_LINK_UP); /* 標記使能數據鏈路 */
/* 鏈路層新增了一個設備節點,需要在鏈路層通告處理下 */
#if LWIP_DHCP /* dhcp協議相關 */
dhcp_network_changed_link_up(netif);
#endif /* LWIP_DHCP */
#if LWIP_AUTOIP /* AUTOIP相關 */
autoip_network_changed_link_up(netif);
#endif /* LWIP_AUTOIP */
/* 發送ARP/IGMP/MLD/RS事件,例如:linup / netifup或addr-change */
/* 協議棧和數據鏈路都使能了才生效 */
netif_issue_reports(netif, NETIF_REPORT_TYPE_IPV4 | NETIF_REPORT_TYPE_IPV6);
#if LWIP_IPV6
/* 重置ipv6網卡,設置路由請求RS次數(see RFC 4861, ch. 6.3.7) */
nd6_restart_netif(netif);
#endif /* LWIP_IPV6 */
/* 數據鏈路狀態回調 */
NETIF_LINK_CALLBACK(netif);
#if LWIP_NETIF_EXT_STATUS_CALLBACK
{
netif_ext_callback_args_t args;
args.link_changed.state = 1;
/* 網卡狀態靜態回調 */
netif_invoke_ext_callback(netif, LWIP_NSC_LINK_CHANGED, &args);
}
#endif
}
}
6.10 乙太網網卡偽程式碼分析
參考lwip官方提供的ethernetif.c
文件。
6.10.1 相關API說明
用戶需要封裝單個網卡底層數據鏈路相關的函數:
-
low_level_init()
:- 該函數用於初始化底層數據鏈路硬體設備,使其具備底層通訊能力。
-
low_level_output()
:- 該函數用於底層數據鏈路發送數據,網卡的南向出口。
- 收到的上層數據包時pbuf類型的。
-
low_level_input()
:- 該函數用於底層數據鏈路接收數據,網卡的南向入口。
- 需要組裝為pbuf類型再轉交給上層。
ethernetif_init()
:網卡初始化函數。封裝這個介面是給netif_add()
添加網卡時初始化當前網卡的。
ethernetif_input()
:數據鏈路接收數據並傳入TCPIP協議棧。
發送的數據是由上層一層一層傳下來的,相關介面也是層層調用即可。
但是接收數據不一樣,我們程式碼不知道數據什麼時候來,所以需要創建一條執行緒,專門用於檢索接收到的數據包,然後傳入網卡,讓網卡一層一層上傳處理。
lwip提供的參考,這個執行緒就是ethernetif_input()
。
6.10.2 ethernetif_init()
偽程式碼參考:
/**
* Should be called at the beginning of the program to set up the
* network interface. It calls the function low_level_init() to do the
* actual setup of the hardware.
*
* This function should be passed as a parameter to netif_add().
*
* @param netif the lwip network interface structure for this ethernetif
* @return ERR_OK if the loopif is initialized
* ERR_MEM if private data couldn't be allocated
* any other err_t on error
*/
err_t
ethernetif_init(struct netif *netif)
{
struct ethernetif *ethernetif;
LWIP_ASSERT("netif != NULL", (netif != NULL));
ethernetif = mem_malloc(sizeof(struct ethernetif));
if (ethernetif == NULL) {
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_init: out of memory\n"));
return ERR_MEM;
}
#if LWIP_NETIF_HOSTNAME
/* Initialize interface hostname */
netif->hostname = "lwip";
#endif /* LWIP_NETIF_HOSTNAME */
/* 在結構netif中初始化snmp變數和計數器。最後一個參數應該替換為鏈接速度,單位為比特/秒 */
MIB2_INIT_NETIF(netif, snmp_ifType_ethernet_csmacd, LINK_SPEED_OF_YOUR_NETIF_IN_BPS);
/* 網卡資訊配置 */
netif->state = ethernetif;
netif->name[0] = IFNAME0;
netif->name[1] = IFNAME1;
/* 我們在這裡直接使用etharp_output()來保存函數調用。
如果你必須在發送之前做一些檢查(例如,如果鏈接可用…),你可以聲明自己的函數為調用etharp_output()。 */
#if LWIP_IPV4
netif->output = etharp_output; /* ARP協議的北向入口,用戶可以二次封裝這個介面。收到的數據時網路層的IP數據 */
#endif /* LWIP_IPV4 */
#if LWIP_IPV6
netif->output_ip6 = ethip6_output;
#endif /* LWIP_IPV6 */
netif->linkoutput = low_level_output; /* 網卡南向出口。把數據發往底層數據鏈路。 */
ethernetif->ethaddr = (struct eth_addr *) & (netif->hwaddr[0]); /* 配置硬體地址,MAC */
/* 初始化網卡硬體設備 */
low_level_init(netif);
return ERR_OK;
}
6.10.3 ethernetif_input()
該函數為應該獨立執行緒,用於檢測和接收底層數據鏈路數據,打包好發往TCPIP協議棧。
/**
* This function should be called when a packet is ready to be read
* from the interface. It uses the function low_level_input() that
* should handle the actual reception of bytes from the network
* interface. Then the type of the received packet is determined and
* the appropriate input function is called.
*
* @param netif the lwip network interface structure for this ethernetif
*/
static void
ethernetif_input(struct netif *netif)
{
struct ethernetif *ethernetif;
struct eth_hdr *ethhdr;
struct pbuf *p;
ethernetif = netif->state; /* 用戶私人資訊拿出來 */
/* 接收一個數據包,並封裝成pbuf類型 */
p = low_level_input(netif);
/* 如果無法讀取數據包,則靜默地忽略它 */
if (p != NULL) {
/* 將所有數據包傳遞給ethernet_input,由ethernet_input決定支援哪些數據包 */
if (netif->input(p, netif) != ERR_OK) {
LWIP_DEBUGF(NETIF_DEBUG, ("ethernetif_input: IP input error\n"));
pbuf_free(p); /* 上傳失敗,丟棄該數據包 */
p = NULL;
}
}
}
6.10.4 low_level_init()
/**
* In this function, the hardware should be initialized.
* Called from ethernetif_init().
*
* @param netif the already initialized lwip network interface structure
* for this ethernetif
*/
static void
low_level_init(struct netif *netif)
{
struct ethernetif *ethernetif = netif->state; /* 獲取用戶私人資訊 */
/* 設置MAC硬體地址長度 */
netif->hwaddr_len = ETHARP_HWADDR_LEN;
/* 設置MAC地址 */
netif->hwaddr[0] = ;
/* ... */
netif->hwaddr[5] = ;
/* 設置MTU */
netif->mtu = 1500;
/* 設備性能標記 */
/* 根據實際設備性能而配置 */
netif->flags = NETIF_FLAG_BROADCAST | NETIF_FLAG_ETHARP | NETIF_FLAG_LINK_UP;
#if LWIP_IPV6 && LWIP_IPV6_MLD
/*
* 用於實現MAC過濾的硬體/網路。
* 默認情況下,所有節點的鏈路本地是被處理的,因此我們必須讓硬體知道允許多播包進入。
* 應該在前面設置mld_mac_filter。 */
if (netif->mld_mac_filter != NULL) {
ip6_addr_t ip6_allnodes_ll;
ip6_addr_set_allnodes_linklocal(&ip6_allnodes_ll);
netif->mld_mac_filter(netif, &ip6_allnodes_ll, NETIF_ADD_MAC_FILTER);
}
#endif /* LWIP_IPV6 && LWIP_IPV6_MLD */
/* 執行初始化介面所需的其他操作。 */
}
6.11環回介面
6.11.1 環回地址
ipv4的127 網段的所有地址都稱為環回地址,主要用來測試網路協議是否工作正常的作用。
常用:
IPv4:127.0.0.1
IPv6:::1
默認記錄IPv4。
環回地址表示「我自己」的意思。
6.11.2 環回數據包流圖
圖源自李柱明
6.11.3 相關宏及API
ENABLE_LOOPBACK
:開啟環回功能。
LWIP_NETIF_LOOPBACK
:每個網卡都有環回功能。
LWIP_HAVE_LOOPIF
:可創建獨立的環回網卡。
netif_init()
:lwip網卡模組初始化。如果開啟了LWIP_NETIF_LOOPBACK
,則會在該函數裡面添加一個環回網卡loop_netif
。
netif_loopif_init()
:環回網卡初始化。
- 相當於乙太網網卡的
low_level_init()
。
netif_loop_output()
:環回輸出介面。
-
如果數據包的目標IP和當前網卡的IP一致,則調用內部會調用該函數,而不是
netif->output()
。 -
如果是環回網卡,
netif->output()
也是該函數。- 所以如果目標IP和環回網卡一致,則直接調用
netif_loop_output()
。 - 如果不一致,則調用
netif->output()
,其實也是netif_loop_output()
。
- 所以如果目標IP和環回網卡一致,則直接調用
netif_poll()
:環回處理介面。
- 單執行緒環境下:在應用程式中循環調用。
- 多執行緒環境下:內部會通過消息,把
netif_poll()
轉交給wlip內核執行緒去跑。 - 目的是把環回數據包環回發到對應網卡的IP層處理。
netif_loop_output()
輸入的數據傳到netif->input()
。
6.11.4 環回網卡初始化
lwip網卡模組初始化調用netif_init()
函數,只是在lwip協議棧初始化是調用,如果開啟了環回LWIP_HAVE_LOOPIF
功能,才會有操作,就是創建一個環回IP的網卡,僅此而已。
對應到每一個實際網卡的初始化,在netif_add()
會講解。
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); /* 設置迴環網卡的IP地址 */
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_netif.ip6_addr, 0, 0, 0, 0x00000001UL); /* ipv6的迴環地址為::1 */
loop_netif.ip6_addr_state[0] = IP6_ADDR_VALID; /* 標記地址有效 */
#endif /* LWIP_IPV6 */
netif_set_link_up(&loop_netif); /* 添加新網卡,告知鏈路層,如ARP宣告使用次IP,都讓迴環的沒有。 */
netif_set_up(&loop_netif); /* 協議棧啟用此網卡 */
#endif /* LWIP_HAVE_LOOPIF */
}
迴環網卡初始化:netif_loopif_init()
/**
* Initialize a lwip network interface structure for a loopback interface
*
* @param netif the lwip network interface structure for this loopif
* @return ERR_OK if the loopif is initialized
* ERR_MEM if private data couldn't be allocated
*/
static err_t
netif_loopif_init(struct netif *netif)
{
LWIP_ASSERT("netif_loopif_init: invalid netif", netif != NULL);
/* 初始化結構netif中的SNMP變數和計數器
* ifSpeed:不能做任何假設!
*/
MIB2_INIT_NETIF(netif, snmp_ifType_softwareLoopback, 0);
/* 網卡名字 */
netif->name[0] = 'l';
netif->name[1] = 'o';
#if LWIP_IPV4
netif->output = netif_loop_output_ipv4; /* 迴環輸出介面 */
#endif
#if LWIP_IPV6
netif->output_ip6 = netif_loop_output_ipv6; /* 迴環輸出介面 */
#endif
#if LWIP_LOOPIF_MULTICAST
netif_set_flags(netif, NETIF_FLAG_IGMP); /* 支援IGMP協議 */
#endif
NETIF_SET_CHECKSUM_CTRL(netif, NETIF_CHECKSUM_DISABLE_ALL); /* checksum校驗功能相關 */
return ERR_OK;
}
#endif /* LWIP_HAVE_LOOPIF */
6.11.5 環回輸出介面netif_loop_output()
傳入的pbuf是被以複製的顯示快取到對應網卡的loop_first
鏈表中:
- 先申請新的pbuf r,用於存儲拷貝傳入的pbuf p的數據。
- 預測檢查pbuf節點數是否超出限制
LWIP_LOOPBACK_MAX_PBUFS
。 - 拷貝pbuf數據。
- 拼接到對應網卡的環回pbuf鏈表
netif->loop_first
中。 - 如果網卡的環回pbuf鏈表還有數據,說明上次已經觸發過
netif_poll()
,正常按理是不用再觸發的。但是如果上次觸發失敗,這次還是要觸發。 - 如果網卡大的環回鏈表沒有數據,說明當前
netif_poll()
沒有在跑。需要觸發netif_poll()
。 - 如果
netif_poll()
觸發失敗,需要標記下,下次再發環回數據時補回觸發。
/**
* @ingroup netif
* Send an IP packet to be received on the same netif (loopif-like).
* The pbuf is copied and added to an internal queue which is fed to
* netif->input by netif_poll().
* In multithreaded mode, the call to netif_poll() is queued to be done on the
* TCP/IP thread.
* In callback mode, the user has the responsibility to call netif_poll() in
* the main loop of their application.
*
* @param netif the lwip network interface structure
* @param p the (IP) packet to 'send'
* @return ERR_OK if the packet has been sent
* ERR_MEM if the pbuf used to copy the packet couldn't be allocated
*/
err_t
netif_loop_output(struct netif *netif, struct pbuf *p)
{
struct pbuf *r;
err_t err;
struct pbuf *last;
#if LWIP_LOOPBACK_MAX_PBUFS
u16_t clen = 0;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
/* 如果我們有一個loop, SNMP計數器會為此進行調整,
* 如果不是,則調整為'netif'。 */
#if MIB2_STATS
#if LWIP_HAVE_LOOPIF
struct netif *stats_if = &loop_netif; /* 相關狀態資訊記錄到loop_netif(不管數據包的目標是否是該網卡) */
#else /* LWIP_HAVE_LOOPIF */
struct netif *stats_if = netif; /* 相關狀態資訊記錄到對應網卡 */
#endif /* LWIP_HAVE_LOOPIF */
#endif /* MIB2_STATS */
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
u8_t schedule_poll = 0; /* 是否需要轉發netif_poll()到lwip內核執行緒執行 */
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
SYS_ARCH_DECL_PROTECT(lev);
LWIP_ASSERT("netif_loop_output: invalid netif", netif != NULL);
LWIP_ASSERT("netif_loop_output: invalid pbuf", p != NULL);
/* 申請新的pbuf資源 */
r = pbuf_alloc(PBUF_LINK, p->tot_len, PBUF_RAM);
if (r == NULL) {
/* 申請失敗。狀態資訊記錄。 */
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
return ERR_MEM;
}
#if LWIP_LOOPBACK_MAX_PBUFS
clen = pbuf_clen(r); /* 查看申請到的pbuf有多長(查看多少個pbuf節點組成,如果MEM基於POOL時才有意義) */
/* 檢查隊列上是否有溢出或太多的pbuf */
if (((netif->loop_cnt_current + clen) < netif->loop_cnt_current) ||
((netif->loop_cnt_current + clen) > LWIP_MIN(LWIP_LOOPBACK_MAX_PBUFS, 0xFFFF))) {
pbuf_free(r); /* 過多,全部丟棄 */
/* 輸出失敗。記錄狀態資訊。 */
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
return ERR_MEM;
}
/* 資源申請成功,需要記錄pbuf鏈表有是由多少個節點組成的,用於出隊檢查和包數限制。 */
/* 筆者認為,在這裡就更新,未免太早了。如果pbuf拷貝失敗,會導致這個值虛大。 */
netif->loop_cnt_current = (u16_t)(netif->loop_cnt_current + clen);
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
/* 將整個pbuf隊列p複製到單個pbuf r中 */
if ((err = pbuf_copy(r, p)) != ERR_OK) {
pbuf_free(r);
/* 拷貝失敗。記錄狀態資訊。 */
LINK_STATS_INC(link.memerr);
LINK_STATS_INC(link.drop);
MIB2_STATS_NETIF_INC(stats_if, ifoutdiscards);
/* 筆者添加:netif->loop_cnt_current = (u16_t)(netif->loop_cnt_current - clen); */
return err;
}
/* 把包放在一個鏈表上,通過調用netif_poll()進行出隊. */
/* last指向鏈r中的最後一個pbuf */
for (last = r; last->next != NULL; last = last->next) {
/* nothing to do here, just get to the last pbuf */
}
SYS_ARCH_PROTECT(lev); /* 進入臨界 */
if (netif->loop_first != NULL) { /* 環回鏈表中已有快取數據 */
LWIP_ASSERT("if first != NULL, last must also be != NULL", netif->loop_last != NULL);
netif->loop_last->next = r; /* 拼接pbuf鏈表 */
netif->loop_last = last; /* 保存最後一個pbuf的指針。用於還沒其它pbuf插入時,直接從尾部插入。 */
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
if (netif->reschedule_poll) { /* 需要重新把netif_pool()轉發到lwip內核執行緒執行(因為上次轉發失敗) */
schedule_poll = 1; /* 標記需要外包 */
netif->reschedule_poll = 0; /* 恢復標誌位 */
}
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
} else { /* 環回鏈表中沒有快取數據 */
netif->loop_first = r; /* 首個pbuf */
netif->loop_last = last; /* 保存最後一個pbuf的指針 */
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
/* 環回隊列中沒有數據,說明netif_poll()已經處理完畢,需要重新外包netif_poll() */
schedule_poll = 1;
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
}
SYS_ARCH_UNPROTECT(lev); /* 退出臨界 */
/* 記錄狀態資訊。 */
LINK_STATS_INC(link.xmit);
MIB2_STATS_NETIF_ADD(stats_if, ifoutoctets, p->tot_len);
MIB2_STATS_NETIF_INC(stats_if, ifoutucastpkts);
#if LWIP_NETIF_LOOPBACK_MULTITHREADING
/* 多執行緒環境下 */
if (schedule_poll) {
/* 打包消息,轉發netif_poll()函數到lwip內核執行緒去跑 */
if (tcpip_try_callback((tcpip_callback_fn)netif_poll, netif) != ERR_OK) {
SYS_ARCH_PROTECT(lev);
netif->reschedule_poll = 1;
SYS_ARCH_UNPROTECT(lev);
}
}
#endif /* LWIP_NETIF_LOOPBACK_MULTITHREADING */
return ERR_OK;
}
6.11.6 環回處理介面netif_poll()
非可重入函數。
- pbuf數據從網卡環回pbuf鏈表
netif->loop_first
中出隊,把數據轉交給IP模組入口ip_input()
處理。
/**
* Call netif_poll() in the main loop of your application. This is to prevent
* reentering non-reentrant functions like tcp_input(). Packets passed to
* netif_loop_output() are put on a list that is passed to netif->input() by
* netif_poll().
*/
void
netif_poll(struct netif *netif)
{
/* 如果我們有一個loop, SNMP計數器會為此進行調整,
* 如果不是,則調整為'netif'。 */
#if MIB2_STATS
#if LWIP_HAVE_LOOPIF
struct netif *stats_if = &loop_netif; /* 相關狀態資訊記錄到loop_netif(不管數據包的目標是否是該網卡) */
#else /* LWIP_HAVE_LOOPIF */
struct netif *stats_if = netif; /* 相關狀態資訊記錄到對應網卡 */
#endif /* LWIP_HAVE_LOOPIF */
#endif /* MIB2_STATS */
SYS_ARCH_DECL_PROTECT(lev);
LWIP_ASSERT("netif_poll: invalid netif", netif != NULL);
SYS_ARCH_PROTECT(lev); /* 進入臨界 */
while (netif->loop_first != NULL) { /* 直到環回鏈表都處理完畢 */
struct pbuf *in, *in_end;
#if LWIP_LOOPBACK_MAX_PBUFS
u8_t clen = 1;
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
in = in_end = netif->loop_first;
while (in_end->len != in_end->tot_len) { /* 遍歷當前pbuf鏈表數據包最後一個有效的pbuf */
LWIP_ASSERT("bogus pbuf: len != tot_len but next == NULL!", in_end->next != NULL);
in_end = in_end->next; /* 遍歷下一個 */
#if LWIP_LOOPBACK_MAX_PBUFS
clen++; /* 記錄當前pbuf鏈表的pbuf成員個數 */
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
}
#if LWIP_LOOPBACK_MAX_PBUFS
/* 調整隊列上的pbuf數量 */
LWIP_ASSERT("netif->loop_cnt_current underflow",
((netif->loop_cnt_current - clen) < netif->loop_cnt_current));
netif->loop_cnt_current = (u16_t)(netif->loop_cnt_current - clen);
#endif /* LWIP_LOOPBACK_MAX_PBUFS */
/* 'in_end'現在指向'in'的最後一個pbuf */
if (in_end == netif->loop_last) {
/* 隊列上的pbuf已經遍歷完畢了,後續沒有其它pbuf。更新相關欄位 */
netif->loop_first = netif->loop_last = NULL;
} else { /* 環回隊列還要其它pbuf */
/* 從列表中彈出pbuf */
netif->loop_first = in_end->next;
LWIP_ASSERT("should not be null since first != last!", netif->loop_first != NULL);
}
/* 彈出隊列 */
in_end->next = NULL;
SYS_ARCH_UNPROTECT(lev);
/* 獲取這個環回pbuf數據包的源網卡,也是目標網卡 */
in->if_idx = netif_get_index(netif);
/* 狀態資訊記錄。 */
LINK_STATS_INC(link.recv);
MIB2_STATS_NETIF_ADD(stats_if, ifinoctets, in->tot_len);
MIB2_STATS_NETIF_INC(stats_if, ifinucastpkts);
/* loopback報文始終是IP報文! */
if (ip_input(in, netif) != ERR_OK) { /* 轉入對應網卡的IP模組 */
pbuf_free(in); /* 轉入失敗後需要釋放這個pbuf數據包記憶體資源 */
}
SYS_ARCH_PROTECT(lev);
}
SYS_ARCH_UNPROTECT(lev);
}