freertos(第十二課,multi-task,LWIP)

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,也隨之可以進行併發連接。每一個連接都被創建了一個獨立的通信線程。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章