【lwip】06-網路介面層分析

2022-08-31 15:01:45


前言

主要分析網路介面概念、網路卡資料結構、網路介面、環回介面實現等等。

參考:

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_liststruct 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 簡要步驟

  1. 定義一個 netif 作為網路卡裝置結構體。

  2. 掛載到 netif_list 連結串列中:netif_add();

    1. 需要提供網路卡初始化函數和網路卡協定棧入口函數作為netif_add()的引數傳入。
  3. 使能網路卡底層(鏈路層):netif_set_link_up()

  4. 使能網路卡上層(協定棧):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 */
  }
}

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()

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);
}