freertos能夠多任務處理,這對於LWIP而言,是最好不過的了。這樣,LWIP可以創建多線程,來並行處理髮送和接收。
SDK已經移植好了基於freertos的lwip。
先來看看lwip的選項。
1)api_mode。設置爲socket。
2)其他選項,保持默認。
在socket模式下,lwip是以thread的方式進行進程管理。
由於是架設在freertos上,所以thread是用task來實現的。後面統一用線程來稱呼LWIP中創建的任務。
我們從lwip141_v2_0/src/contrib/ports/xilinx/sys_arch.h文件中,也可以看出。
typedef xSemaphoreHandle sys_sem_t;
typedef xSemaphoreHandle sys_mutex_t;
typedef xQueueHandle sys_mbox_t;
typedef xTaskHandle sys_thread_t;
lwip141_v2_0/src/contrib/ports/xilinx/sys_arch.c文件,就是移植lwip所需要的底層文件了。例如:
err_t sys_mbox_new( sys_mbox_t *pxMailBox, int iSize );
void sys_mbox_free( sys_mbox_t *pxMailBox );
void sys_mbox_post( sys_mbox_t *pxMailBox, void *pxMessageToPost );
u32_t sys_arch_mbox_fetch( sys_mbox_t *pxMailBox, void **ppvBuffer, u32_t ulTimeOut );
err_t sys_sem_new( sys_sem_t *pxSemaphore, u8_t ucCount );
void sys_sem_free( sys_sem_t *pxSemaphore );
void sys_sem_signal( sys_sem_t *pxSemaphore );
u32_t sys_arch_sem_wait( sys_sem_t *pxSemaphore, u32_t ulTimeout );
err_t sys_mutex_new( sys_mutex_t *pxMutex );
void sys_mutex_free( sys_mutex_t *pxMutex );
void sys_mutex_lock( sys_mutex_t *pxMutex );
void sys_mutex_unlock(sys_mutex_t *pxMutex );
sys_thread_t
sys_thread_new(
const char *pcName,
void( *pxThread )( void *pvParameters ),
void *pvArg,
int iStackSize,
int iPriority
);
當LWIP調用這些arch相關的函數時,是基於freertos來實現的這些函數。
來看看一個具體的例子。tcpecho。
如前所述,freertos的啓動,是從main開始的。
#define THREAD_STACKSIZE 1024
後面需要用到的常數,這裏用宏進行符號化處理,使其具有特定的現實含義。
int main_thread();
void print_echo_app_header();
void echo_application_thread(void *);
void lwip_init();
void print_ip(char *msg, struct ip_addr *ip);
void print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw);
後面所需的函數,在文件前部先聲明。
static struct netif server_netif;
struct netif *echo_netif;
文件內需要用到的全局變量,在文件頭部先聲明。
1)main。
int main()
{
sys_init();
sys_thread_new("main_thrd", (void(*)(void*))main_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
vTaskStartScheduler();
while(1);
return 0;
}
啓動時,初始化系統後,創建了主線程,然後啓動調度器,然後main進入無限循環。
void main_thread(void* parameter)
{
int mscnt = 0;
lwip_init();
/* any thread using lwIP should be created using sys_thread_new */
sys_thread_new("NW_THRD",
network_thread, NULL,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
while (1) {
vTaskDelay(DHCP_FINE_TIMER_MSECS / portTICK_RATE_MS);
if (server_netif.ip_addr.addr) {
xil_printf("DHCP request success\r\n");
print_ip_settings(
&(server_netif.ip_addr),
&(server_netif.netmask),
&(server_netif.gw));
print_echo_app_header();
xil_printf("\r\n");
sys_thread_new("echod", echo_application_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
break;
}
mscnt += DHCP_FINE_TIMER_MSECS;
if (mscnt >= 10000) {
xil_printf("ERROR: DHCP request timed out\r\n");
xil_printf("Configuring default IP of 192.168.1.10\r\n");
IP4_ADDR(&(server_netif.ip_addr), 192, 168, 1, 10);
IP4_ADDR(&(server_netif.netmask), 255, 255, 255, 0);
IP4_ADDR(&(server_netif.gw), 192, 168, 1, 1);
print_ip_settings(&(server_netif.ip_addr), &(server_netif.netmask), &(server_netif.gw));
/* print all application headers */
xil_printf("\r\n");
xil_printf("%20s %6s %s\r\n", "Server", "Port", "Connect With..");
xil_printf("%20s %6s %s\r\n", "--------------------", "------", "--------------------");
print_echo_app_header();
xil_printf("\r\n");
sys_thread_new("echod", echo_application_thread, 0,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
break;
}
}
vTaskDelete(NULL);
return ;
}
在主線程中,創建了兩個線程Network和Echod.
在主線程的最後,刪除了自身,部署工作完成,生存週期結束。
首先來看看network。
1-1)network。
void network_thread(void *p)
{
struct netif *netif;
struct ip_addr ipaddr, netmask, gw;
int mscnt = 0;
/* the mac address of the board. this should be unique per board */
unsigned char mac_ethernet_address[] = { 0x00, 0x0a, 0x35, 0x00, 0x01, 0x02 };
netif = &server_netif;
/* print out IP settings of the board */
xil_printf("\r\n\r\n");
xil_printf("-----lwIP Socket Mode Echo server Demo Application ------\r\n");
ipaddr.addr = 0;
gw.addr = 0;
netmask.addr = 0;
/* Add network interface to the netif_list, and set it as default */
if (!xemac_add(netif, &ipaddr, &netmask, &gw, mac_ethernet_address, PLATFORM_EMAC_BASEADDR)) {
xil_printf("Error adding N/W interface\r\n");
return;
}
netif_set_default(netif);
/* specify that the network if is up */
netif_set_up(netif);
/* start packet receive thread - required for lwIP operation */
sys_thread_new("xemacif_input_thread", (void(*)(void*))xemacif_input_thread, netif,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
dhcp_start(netif);
while (1) {
vTaskDelay(DHCP_FINE_TIMER_MSECS / portTICK_RATE_MS);
dhcp_fine_tmr();
mscnt += DHCP_FINE_TIMER_MSECS;
if (mscnt >= DHCP_COARSE_TIMER_SECS*1000) {
dhcp_coarse_tmr();
mscnt = 0;
}
}
return;
}
在network線程中,首先,仍然是初始化。
初始化了netif之後,network進程創建了xemacif_input線程。
之後,network進程進入自己的無限循環體。它是一個TimingResponser,所以,循環體的首句,是TimingSync。主要工作是,定期維持DHCP。
來看看這個xemacif_input線程。
1-1-1)xemacif_input。
函數位於lwip141_v2_0/src/contrib/ports/xilinx/netif/xadaptor.c文件
void
xemacif_input_thread(struct netif *netif)
{
struct xemac_s *emac = (struct xemac_s *)netif->state;
while (1) {
sys_sem_wait(&emac->sem_rx_data_available);
/* move all received packets to lwIP */
xemacif_input(netif);
}
}
這個線程是一個IRQProcessor。所以線程循環體的首句,是一個EventSync。
在IRQ中,會產生這個event。
函數位於lwip141_v2_0/src/contrib/ports/xilinx/netif/xemacpsif_dma.c文件
void emacps_recv_handler(void *arg)
{
xemac = (struct xemac_s *)(arg);
...
sys_sem_signal(&xemac->sem_rx_data_available);
return;
}
來看看echod這個線程。
1-2)echod。
它位於lwip_test/src/echo.c文件。
void echo_application_thread()
{
int sock, new_sd;
struct sockaddr_in address, remote;
int size;
if ((sock = lwip_socket(AF_INET, SOCK_STREAM, 0)) < 0)
return;
address.sin_family = AF_INET;
address.sin_port = htons(echo_port);
address.sin_addr.s_addr = INADDR_ANY;
if (lwip_bind(sock, (struct sockaddr *)&address, sizeof (address)) < 0)
return;
lwip_listen(sock, 0);
size = sizeof(remote);
while (1) {
if ((new_sd = lwip_accept(sock, (struct sockaddr *)&remote, (socklen_t *)&size)) > 0) {
sys_thread_new(
"echos",
process_echo_request,
(void*)new_sd,
THREAD_STACKSIZE,
DEFAULT_THREAD_PRIO);
}
}
}
線程開始,仍然是初始化。
echod創建並初始化了一個socket。然後bind,然後listen。
之後,進入自己的循環主體。
echod是一個MSGProcessor,所以循環體首句,是一個MessageSync。
當線程收到msg而被喚醒後,繼續執行,它根據收到的msg,這裏是new_sd,來創建一個新的線程。
來看看被創建的process_echo_request線程。
1-2-1)process_echo_request
它位於lwip_test/src/echo.c文件。
/* thread spawned for each connection */
void process_echo_request(void *p)
{
int sd = (int)p;
int RECV_BUF_SIZE = 2048;
char recv_buf[RECV_BUF_SIZE];
int n, nwrote;
while (1) {
/* read a max of RECV_BUF_SIZE bytes from socket */
if ((n = read(sd, recv_buf, RECV_BUF_SIZE)) < 0) {
xil_printf("%s: error reading from socket %d, closing socket\r\n", __FUNCTION__, sd);
break;
}
/* break if the recved message = "quit" */
if (!strncmp(recv_buf, "quit", 4))
break;
/* break if client closed connection */
if (n <= 0)
break;
/* handle request */
if ((nwrote = write(sd, recv_buf, n)) < 0) {
xil_printf("%s: ERROR responding to client echo request. received = %d, written = %d\r\n",
__FUNCTION__, n, nwrote);
xil_printf("Closing socket %d\r\n", sd);
break;
}
}
/* close connection */
close(sd);
vTaskDelete(NULL);
}
線程的循環主體中,進行TCP通信。由於可能發生error,所以,循環體中,設置了多個break點,跳出無限循環,稱爲error escape或者error break。
escape發生後,需要進行資源回收,然後刪除自身。
線程是一個MSGProcessor,所以循環體首句,是一個MessageSync。
當線程收到msg而被喚醒後,繼續執行,它根據收到的msg,這裏是read status,來判斷下一步的操作。如果有error,則escape。如果沒有error,則繼續執行。
執行完write操作後,一次循環體全執行完成,之後,回到首句,繼續MessageSync。
注意,這裏使用的read,write,close等,並不是FILE的讀寫函數。
而是LWIP的讀寫函數。
它們位於lwip141_v2_0/src/lwip-141/src/api/socket.c文件
#define read(a,b,c) lwip_read(a,b,c)
#define write(a,b,c) lwip_write(a,b,c)
#define close(s) lwip_close(s)
#define fcntl(a,b,c) lwip_fcntl(a,b,c)
int
lwip_read(int s, void *mem, size_t len)
{
return lwip_recvfrom(s, mem, len, 0, NULL, NULL);
}
int
lwip_write(int s, const void *data, size_t size)
{
return lwip_send(s, data, size, 0);
}
至此,整個TCPECHOSERVER的架構分析完畢。
這裏,還有一些輔助函數。
void
print_ip(char *msg, struct ip_addr *ip)
{
xil_printf(msg);
xil_printf("%d.%d.%d.%d\n\r", ip4_addr1(ip), ip4_addr2(ip),
ip4_addr3(ip), ip4_addr4(ip));
}
void
print_ip_settings(struct ip_addr *ip, struct ip_addr *mask, struct ip_addr *gw)
{
print_ip("Board IP: ", ip);
print_ip("Netmask : ", mask);
print_ip("Gateway : ", gw);
}
void print_echo_app_header()
{
xil_printf("%20s %6d %s\r\n", "echo server",
echo_port,
"$ telnet <board_ip> 7");
}
不再多加分析。
從這個例子可以看出,如果使用socket而不是RAW API,那麼LWIP的功能將變的更加強大,且對於用戶而言,編程變得更加簡單,程序架構也更加清晰明確。
由於freertos支持multi-task,LWIP上的TCP,也隨之可以進行併發連接。每一個連接都被創建了一個獨立的通信線程。