LwIP

Started by tha, August 29, 2021, 09:15:30 AM

Previous topic - Next topic

tha

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

Common pitfalls

Multiple Execution Contexts in lwIP code

The most common source of lwIP problems คือมี multiple execution contexts ภายใน the lwIP code.

lwIP สามารถถูกใช้ใน two basic modes: Mainloop mode ("NO_SYS") (ไม่มี OS/RTOS รันบน target system) หรือ OS mode (TCPIP thread) (มี an OS รันบน the target system).

ดูเพิ่มเติมที่: Multithreading (โดยเฉพาะส่วนที่เกี่ยวกับ LWIP_ASSERT_CORE_LOCKED()!)

Mainloop Mode

ใน mainloop mode, มีเพียง "raw" APIs สามารถถูกใช้. ผู้ใช้มีความเป็นไปได้สองทางเพื่อให้แน่ใจว่ามีเพียง one exection context ในแต่ละครั้งเท่านั้นใน lwIP:

1) ส่ง RX ethernet packets โดยตรงใน interrupt context ไปยัง lwIP โดยการเรียก netif->input โดยตรงใน interrupt. นี้หมายความว่าทุก lwIP callback functions ถูกเรียกใช้ใน IRQ context, ซึ่งอาจเป็นเหตุให้เกิดปัญหาเพิ่มเติมใน application code: IRQ ถูกบล็อกเป็นเวลานาน, multiple execution contexts ใน application code ฯลฯ. เมื่อ the application ต้องการเรียกใช้ lwIP, มันเพียงต้องการปิดการใช้งาน interrupts ในระหว่างการเรียกใช้. ถ้า timers ถูกเกี่ยวข้อง, มากไปกว่านั้น locking code ถูกจำเป็นเพื่อล็อค timer IRQ และ ethernet IRQ ออกจากกัน, ทึกทักเอาว่าสิ่งเหล่านี้อาจถูกวงซ้อนกัน.

2) รัน lwIP ใน a mainloop. มี example code ที่นี่: Mainloop mode ("NO_SYS"). lwIP เพียงถูกเรียกใช้จาก mainloop callstacks ที่นี่. The ethernet IRQ ต้องใส่ received telegrams ลงใน a queue ซึ่งถูกวนตรวจจับใน the mainloop. ทำให้แน่ใจว่า lwIP ไม่เคยถูกเรียกใช้จาก an interrupt, ตัวอย่างเช่นบาง SPI IRQ ต้องการส่ง data ไปยัง udp_send() หรือ tcp_write()!

tha

OS Mode

ใน OS mode, "raw" APIs และ Sequential-style APIs สามารถถูกใช้. Sequential-style APIs ถูกออกแบบเพื่อถูกเรียกใช้จาก threads นอกเหนือจาก the TCPIP thread, ดังนั้นจึงไม่มีอะไรต้องพิจารณาที่นี่. แต่ "raw" APIs functions ต้องถูกเรียกจาก TCPIP thread เท่านั้น. มันเป็น a common error ที่เรียกเหล่านี้จาก threads อื่นหรือจาก IRQ contexts. ​Ethernet RX จำเป็นต้องส่ง incoming packets ใน the correct way โดยการส่ง a message ไปยัง TCPIP thread, นี้จัดให้มีใช้งานใน tcpip_input().​​ อีกครั้ง, ทำให้แน่ใจว่า lwIP ไม่เคยถูกเรียกใช้จาก an interrupt, ตัวอย่างเช่นบาง SPI IRQ ต้องการส่ง data ไปยัง udp_send() หรือ tcp_write()!

1) tcpip_callback() สามารถถูกใช้ได้ called back จาก TCPIP thread, มันเป็นความปลอดภัยที่เรียกใช้ "raw" APIs ใดๆจากที่นี่.

2) ใช้ LWIP_TCPIP_CORE_LOCKING. ทุก "raw" APIs functions สามารถถูกเรียกใช้เมื่อ lwIP core lock ถูกได้รับ, ดู LOCK_TCPIP_CORE() และ UNLOCK_TCPIP_CORE(). macros เหล่านี้ไม่สามารถถูกใช้ใน an interrupt context! บันทึกไว้ว่า the OS ต้องจัดการอย่างถูกต้อง priority inversion สำหรับการนี้.

tha

Cache / DMA issues

DMA-capable ethernet hardware and zero-copy RX

lwIP เปลี่ยนสิ่งที่บรรจุอยู่ภายในของ RECEIVED pbufs ใน the TCP code path. นี้หมายความว่าหนึ่งหรือมากกว่า cacheline(s) ของ the RX pbuf กลายมาเป็นสกปรกและจำเป็นต้องถูกล้างก่อน the memory จะถูกส่งมอบไปยัง the DMA ethernet hardware สำหรับ the next telegram ที่จะถูกรับ. ดู http://lists.nongnu.org/archive/html/lwip-devel/2017-12/msg00070.html สำหรับการอธิบายรายละเอียดเพิ่มเติม. พึงระลึกไว้เสมอว่า the user application อาจเขียนลงใน pbufs อีกด้วย, ดังนั้นโดยทั่วไปมันเป็น a bug ไม่ได้ล้าง the data cache ก่อนส่งมอบ a buffer ไปยัง DMA hardware.

DMA-capable ethernet hardware and cacheline alignment

คำอธิบายที่ดีเกี่ยวกับ DMA capable hardware และ buffer handling: http://www.pebblebay.com/a-guide-to-using-direct-memory-access-in-embedded-systems-part-two/ อ่านโดยเฉพาะส่วน "Cache coherency" and "Buffer alignment".


tha

https://www.nongnu.org/lwip/2_1_x/bugs.html

Reporting bugs

โปรดรายงาน bugs ใน the lwIP bug tracker ที่ savannah.
ก่อนยื่นเสนอ, โปรดตรวจสอบถ้า the bug ถูกรายงานแล้ว!
https://savannah.nongnu.org/bugs/?group=lwip

tha

https://www.nongnu.org/lwip/2_1_x/zerocopyrx.html

Zero-copy RX

โค๊ดต่อไปนี้เป็นตัวอย่างสำหรับ zero-copy RX ethernet driver:
(ตัวอย่างโค๊ดดูที่ลิ้งค์เอานะครับ)

tha

https://www.nongnu.org/lwip/2_1_x/sys_init.html

System initalization

ความสมบูรณ์อย่างแท้จริงและลำดับโดยทั่วไปสำหรับการเริ่มต้น the lwIP stack ไม่สามารถถูกให้ได้เพราะว่ามันขึ้นอยู่กับการเริ่มต้นเพิ่มเติมสำหรับ runtime environment ของคุณ (เช่น timers).

เราสามารถให้คุณบางแนวคิดเกี่ยวกับวิธีการดำเนินการเมื่อใช้ the raw API. เราทึกทักเอาว่า a configuration โดยใช้ a single Ethernet netif และ the UDP and TCP transport layers, IPv4 and the DHCP client.

เรียกฟังก์ชันเหล่านี้ในลำดับของลักษณะที่ปรากฏ:

     •   lwip_init(): เริ่มต้น the lwIP stack และทั้งหมดของ subsystems ของมัน.
     •   netif_add(struct netif *netif, ...): เพิ่ม network interface ของคุณไปยัง the netif_list. จัดสรร a struct netif และส่ง
         ผ่าน a pointer ไปส่ง structure นี้เป็น the first argument. ให้ pointers เพื่อเคลียร์ ip_addr structures เมื่อใช้ DHCP,
         หรือเติมพวกมันด้วย sane numbers มิฉะนั้น. The state pointer อาจเป็น NULL.
     
        The init function pointer ต้องชี้ไปยัง a initialization function สำหรับ Ethernet netif interface ของคุณ. โค้ดต่อไปนี้
        แสดงการใช้งานของมัน.



สำหรับ Ethernet drivers, the input function pointer ต้องชี้ไปยัง the lwIP function ethernet_input() ที่ประกาศใน "netif/etharp.h". drivers อื่นๆต้องใช้ ip_input() ที่ประกาศใน "lwip/ip.h".

     •   netif_set_default(struct netif *netif) ลงทะเบียน the default network interface.
     •   netif_set_link_up(struct netif *netif) นี้คือ the hardware link state; เช่นว่า cable ถูกเสียบปลั๊กสำหรับ wired
         Ethernet interface. ฟังชั่นนี้ต้องถูกเรียกใช้ถึงแม้ว่าคุณไมรู้สถานะปัจจุบัน. มี link up และ link down events เป็นทางเลือกแต่
         DHCP and IPv6 ค้นพบว่ามีประโยชน์จาก events เหล่านี้.
     •   netif_set_up(struct netif *netif) นี้คือ the administrative (= software) state ของ the netif, เมื่อ the netif ถูก
         กำหนดค่าอย่างสมบูรณ์ฟังชั่นนี้ต้องถูกเรียกใช้.
     •   dhcp_start(struct netif *netif) สร้าง a new DHCP client สำหรับ interface นี้บน the first call. คุณสามารถดูใน the
          netif->dhcp struct สำหรับ the actual DHCP status.
     •   sys_check_timeouts() เมื่อ the system กำลังรันอยู่, คูณต้องเรียกใช้ sys_check_timeouts() เป็นระยะๆซึ่งจะจัดการ
          timers ทั้งหมดสำหรับ protocols ทั้งหมดใน the stack; เพิ่มสิ่งนี้ไปยัง main loop ของคุณหรือ equivalent.

tha

https://www.nongnu.org/lwip/2_1_x/multithreading.html

Multithreading

lwIP สตาร์ท targeting single-threaded environments. เมื่อเพิ่ม multi- threading support, แทนที่จะทำ the core thread-safe, วิธีเข้าใกล้หนึ่งเคยถูกเลือก: มี one main thread รัน the lwIP core (เรียกอีกอย่างว่า the "tcpip_thread"). เมื่อรันใน a multithreaded environment, raw API functions ต้องถูกเรียกเฉพาะจาก the core thread เท่านั้นเนื่องจาก raw API functions ไม่ได้ถูกป้องกันจากการเข้าถึงพร้อมกัน (นอกเหนือจาก pbuf- และ memory management functions). Application threads ใช้ the sequential- หรือ socket API สื่อสารกับ main thread นี้ผ่านทาง message passing.

ด้วยเหตุนี้, รายการของฟังชั่นที่อาจถูกเรียกจาก threads อื่นหรือ an ISR ถูกจำกัดมาก! เฉพาะฟังชั่นจาก API header files เหล่านี้เป็น thread-safe:

     •   api.h
     •   netbuf.h
     •   netdb.h
     •   netifapi.h
     •   pppapi.h
     •   sockets.h
     •   sys.h

นอกจากนี้, memory (de-)allocation functions อาจถูกเรียกจากหลาย threads (ไม่ใช่ ISR!) ด้วย NO_SYS=0 เนื่องจากพวกมันถูกป้องกันโดย SYS_LIGHTWEIGHT_PROT และ/หรือ semaphores.

Netconn หรือ Socket API functions เป็น thread safe ขัดต่อ the core thread แต่พวกมันไม่กลับเข้ามาใหม่ที่ the control block granularity level. นั่นคือ, a UDP or TCP control block ต้องไม่ถูกแชร์ในหมู่ threads ทั้งหลายโดยไม่มีการล็อคที่เหมาะสม.

ถ้า SYS_LIGHTWEIGHT_PROT ถูกเซ็ตเป็น 1 และ LWIP_ALLOW_MEM_FREE_FROM_OTHER_CONTEXT ถูกเซ็ตเป็น 1, pbuf_free() อาจถูกเรียกใช้จาก thread อื่นหรือ an ISR อีกด้วย (ตั้งแต่นั้นมา, mem_free - สำหรับ PBUF_RAM - อาจถูกเรียกใช้จาก an ISR: มิฉะนั้น, the HEAP ถูกป้องกันโดย semaphores เท่านั้น).

tha

How to get threading done right

ขอแนะนำอย่างยิ่งให้ใช้ the LWIP_ASSERT_CORE_LOCKED() macro ใน an application ที่ใช้ multithreading. lwIP code มีหลายตำแหน่งที่ตรวจสอบสำหรับ a correct thread context ถูกจัดให้มีใช้ซึ่งช่วยอย่างมากให้ผู้ใช้ได้รับการ threading อย่างถูกต้อง. ดูตัวอย่าง sys_arch.c files ใน unix และ Win32 พอร์ตใน the contrib repository.

กล่าวโดยย่อ: คัดลอก the functions sys_mark_tcpip_thread() และ sys_check_core_locking() ไปยังพอร์ตของคุณและแก้ไขพวกมันให้ทำงานกับ OS ของคุณ. จากนั้นให้ LWIP_ASSERT_CORE_LOCKED() and LWIP_MARK_TCPIP_THREAD() ชี้ไปยังฟังชั่นเหล่านี้.

ถ้าคุณใช้ LWIP_TCPIP_CORE_LOCKING, คุณจำเป็นต้องคัดลอกและปรับเปลี่ยน the functions sys_lock_tcpip_core() and sys_unlock_tcpip_core(). ให้ LOCK_TCPIP_CORE() and UNLOCK_TCPIP_CORE() ชี้ไปยังฟังชั่นเหล่านี้.