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,也随之可以进行并发连接。每一个连接都被创建了一个独立的通信线程。