狗拿耗子
lwip
———狗拿耗子第四篇 1、lwip 的背景 lwip 是 Swedish Institute of Computer Science 开发的用于嵌入式系统的 TCP\IP 协议栈,从 网上的评论看似乎用的人不少。它的实现是应该参考了 BSD 的实现,在介绍 TCP 的时候, 大家就会发现,其拥塞控制的实现算法机会与 BSD 的一模一样。lwip 的整个代码写的比 YASS2 差一截,难以入手介绍,我打算按照 TCP 的 server 与 client 的角度分别走一遍代码。 lwip 的内核运行在同一个任务中,lwip 提供的系统调用通过 mailbox 与内核进行通信,然 后用户阻塞在一个专门的 mailbox 上,内核完成用户的请求后 post 该 mailbox,用户得以继 续执行。有的协议栈的实现却是,每层跑在一个任务中,这样层与层间的相互调用将会引起 上下文的切换。更重要的是 lwip 可以运行在裸机的环境中,即不需要操作系统的支持。这 对一些低成本的设备还是很具有吸引力的。 lwip 的为 /projects/lwip/,目前最新的版本是 1.3.0,而 本文参考的是 1.2.0。 2、netconn_new 系统调用 2.1 相关的数据结构
enum netconn_type { NETCONN_TCP, NETCONN_UDP, NETCONN_UDPLITE, NETCONN_UDPNOCHKSUM, NETCONN_RAW };
struct netconn { enum netconn_type type; enum netconn_state state; union { struct tcp_pcb *tcp; struct
udp_pcb *udp; struct raw_pcb *raw; } pcb; err_t err; sys_mbox_t mbox; sys_mbox_t recvmbox; sys_mbox_t acceptmbox; sys_sem_t sem; int socket; u16_t recv_avail; void (* callback)(struct netconn *, enum netconn_evt, u16_t len); };recv函数
1
狗拿耗子
struct netconn 用一个 union 将 udp、tcp、raw 的 pcb 包含起来,实现由 netconn 到不同协 议的分发,这是 c 语言编程的一个常用技巧。
sys_mbox_t mbox,用户阻塞在该 mailbox 上,内核处理完用户的请求后,post 该 mailbox,用户继续 执行。 sys_mbox_t recvmbox,如其名,用户用该 mailbox 接收来自内核的数据。 sys_mbox_t acceptmbox,用户调用 accept 阻塞在该 mailbox 上,内核接收到来自网络的连接请求并完 成三次握手后,post 该 mailbox。 sys_sem_t sem,系统调用 netconn_write 发现内核没有足够空间时 wait 该 semaphore,内核在适当的时 候会 post 该 semaphore,则操作系统唤醒运行在用户任务的系统调用,再次尝试发送数据。 2.2 流程 struct netconn *netconn_new_with_proto_and_callback(enum netconn_type t, u16_t proto, void (*callback)(struct netconn *, enum netconn_evt, u16_t len)) { struct netconn *conn; struct api_msg *msg;
conn = memp_malloc(MEMP_NETCONN); ...... conn->err = ERR_OK; conn->type = t; conn-&p = NULL; /* 表示没有关联 pcb */
if ((conn->mbox = sys_mbox_new()) == SYS_MBOX_NULL) { memp_free(MEMP_NETCONN, conn); return NULL; } conn->recvmbox = SYS_MBOX_NULL; conn->acceptmbox = SYS_MBOX_NULL; conn->sem = sys_sem_new(0); ...... conn->state = NETCONN_NONE; conn->socket = 0; conn->callback = callback; conn->recv_avail = 0;
if((msg = memp_malloc(MEMP_API_MSG)) == NULL) { memp_free(MEMP_NETCONN, conn); return NULL; }
msg->type = API_MSG_NEWCONN; msg->msg.msg.bc.port = proto; /* misusing the port field */ msg-& = conn;
2
狗拿耗子 api_msg_post(msg); /* 请求内核完成操作,内核需要根据链接类型分配并初始化 pcb */ sys_mbox_fetch(conn->mbox, NULL); /* 等待内核完成操作 */ memp_free(MEMP_API_MSG, msg);
......
return conn; }
3、内核函数 do_newconn ...... msg->conn->err = ERR_OK; /* Allocate a PCB for this connection */ switch(msg->conn->type) { ...... case NETCONN_TCP: msg->conn-&p = tcp_new(); if(msg->conn-&p == NULL) { msg->conn->err = ERR_MEM; break; } setup_tcp(msg->conn); break; } sys_mbox_post(msg->conn->mbox, NULL); /* 为 TCP connection 分配并初始化 pcb,post 系统调用准备 的 mailbox,系统调用得以继续执行 */ } 3.1 内核函数 tcp_alloc struct tcp_pcb * tcp_alloc(u8_t prio) { struct tcp_pcb *pcb; u32_t iss;
pcb = memp_malloc(MEMP_TCP_PCB); if (pcb == NULL) { /* Try killing oldest connection in TIME-WAIT. */ LWIP_DEBUGF(TCP_DEBUG, ("tcp_alloc: killing off oldest TIME-WAIT connection\n")); tcp_kill_timewait(); pcb = memp_malloc(MEMP_TCP_PCB); if (pcb == NULL) { tcp_kill_prio(prio); /* 释放优先级低的 pcb,这招够狠的 */
pcb = memp_malloc(MEMP_TCP_PCB);
3
狗拿耗子
} } if (pcb != NULL) { memset(pcb, 0, sizeof(struct tcp_pcb)); pcb->prio = TCP_PRIO_NORMAL; pcb->snd_buf = TCP_SND_BUF; /* self available buffer space for sending (in bytes). */
pcb->snd_queuelen = 0; /* self available buffer space for sending (in tcp_segs). */ pcb->rcv_wnd = TCP_WND; /* self receiver window (in bytes)*/ pcb->tos = 0; pcb->ttl = TCP_TTL; pcb->mss = TCP_MSS; pcb->rto = 3000 / TCP_SLOW_INTERVAL; pcb->sa = 0; pcb->sv = 3000 / TCP_SLOW_INTERVAL; pcb->rtime = 0; pcb->cwnd = 1; iss = tcp_next_iss(); pcb->snd_wl2 = iss; /* acknowledgement numbers of last window update from peer. */ pcb->snd_nxt = iss; /* next seqno to be sent by peer*/ pcb->snd_max = iss; /* Highest seqno sent by peer. */ pcb->lastack = iss; /* Highest acknowledged seqno from peer. */ pcb->snd_lbb = iss; pcb->tmr = tcp_ticks; pcb->polltmr = 0;
#if LWIP_CALLBACK_API pcb->recv = tcp_recv_null; #endif /* LWIP_CALLBACK_API */
/* Init KEEPALIVE timer */ pcb->keepalive = TCP_KEEPDEFAULT; pcb->keep_cnt = 0; } return pcb; } 从 pcb 分配函数分配一个 pcb,并初始化发送、接收、定时器参数。 3.2 内核函数 setup_tcp static void setup_tcp(struct netconn *conn) { struct tcp_pcb *pcb;
pcb = conn-&p; tcp_arg(pcb, conn);
4
狗拿耗子
tcp_recv(pcb, recv_tcp); tcp_sent(pcb, sent_tcp); tcp_poll(pcb, poll_tcp, 4); tcp_err(pcb, err_tcp); } 在 pcb 上绑定各个事件的处理函数,相应功能在随后介绍。
4、系统调用 netconn_bind err_t netconn_bind(struct netconn *conn, struct ip_addr *addr, u16_t port) { struct api_msg *msg;
if (conn == NULL) { return ERR_VAL; }
if (conn->type != NETCONN_TCP && conn->recvmbox == SYS_MBOX_NULL) { if ((conn->recvmbox = sys_mbox_new()) == SYS_MBOX_NULL) { return ERR_MEM; } }
if ((msg = memp_malloc(MEMP_API_MSG)) == NULL) { return (conn->err = ERR_MEM); } msg->type = API_MSG_BIND; msg-& = conn; msg->msg.msg.bc.ipaddr = addr; msg->msg.msg.bc.port = port; api_msg_post(msg); sys_mbox_fetch(conn->mbox, NULL); memp_free(MEMP_API_MSG, msg); return conn->err; } 为 tcp connnection 分配 receive mailbox,然后发消息 API_MSG_BIND 给内核。
5、内核函数 do_bind static void do_bind(struct api_msg_msg *msg) { ...... switch (msg->conn->type) { .
.....
5
版权声明:本站内容均来自互联网,仅供演示用,请勿用于商业和其他非法用途。如果侵犯了您的权益请与我们联系QQ:729038198,我们将在24小时内删除。
发表评论