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

}

不再多加分析。

从这个例子可以看出,如果使用socket而不是RAW API,那么LWIP的功能将变的更加强大,且对于用户而言,编程变得更加简单,程序架构也更加清晰明确。
由于freertos支持multi-task,LWIP上的TCP,也随之可以进行并发连接。每一个连接都被创建了一个独立的通信线程。

你可能感兴趣的:(freertos)