狗拿耗子
1
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 的官方网站为 http://savannah.nongnu.org/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);
};
yjwl
文本下划线工具
yjwl
文本下划线工具
狗拿耗子
2
z struct netconn用一个 union将 udp、tcp、raw的 pcb包含起来,实现由 netconn到不同协
议的分发,这是 c语言编程的一个常用技巧。
z sys_mbox_t mbox,用户阻塞在该 mailbox上,内核处理完用户的请求后,post该 mailbox,用户继续
执行。
z sys_mbox_t recvmbox,如其名,用户用该 mailbox接收来自内核的数据。
z sys_mbox_t acceptmbox,用户调用 accept阻塞在该 mailbox上,内核接收到来自网络的连接请求并完
成三次握手后,post该 mailbox。
z 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->pcb.tcp = 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->msg.conn = conn;
狗拿耗子
3
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->pcb.tcp = tcp_new();
if(msg->conn->pcb.tcp == 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);
狗拿耗子
4
}
}
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->pcb.tcp;
tcp_arg(pcb, conn);
狗拿耗子
5
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->msg.conn = 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) {
......
狗拿耗子
6
case NETCONN_TCP:
msg->conn->err = tcp_bind(msg->conn->pcb.tcp,
msg->msg.bc.ipaddr, msg->msg.bc.port);
default:
break;
}
sys_mbox_post(msg->conn->mbox, NULL);
}
内核函数 tcp_bind将本地 ip、port绑定到指定的 connection上,源代码中的函数
说明
关于失联党员情况说明岗位说明总经理岗位说明书会计岗位说明书行政主管岗位说明书
如下。
/**
* Binds the connection to a local portnumber and IP address. If the
* IP address is not given (i.e., ipaddr == NULL), the IP address of
* the outgoing network interface is used instead.
* */
6、系统调用 netconn_listen
err_t netconn_listen(struct netconn *conn)
{
struct api_msg *msg;
if (conn == NULL) {
return ERR_VAL;
}
if (conn->acceptmbox == SYS_MBOX_NULL) {
conn->acceptmbox = sys_mbox_new();
if (conn->acceptmbox == SYS_MBOX_NULL) {
return ERR_MEM;
}
}
if ((msg = memp_malloc(MEMP_API_MSG)) == NULL) {
return (conn->err = ERR_MEM);
}
msg->type = API_MSG_LISTEN;
msg->msg.conn = conn;
api_msg_post(msg);
sys_mbox_fetch(conn->mbox, NULL);
memp_free(MEMP_API_MSG, msg);
return conn->err;
}
为 tcp connnection分配 accept mailbox,然后发消息 API_MSG_LISTEN给内核。
狗拿耗子
7
7、内核函数 do_listen
static void do_listen(struct api_msg_msg *msg)
{
if (msg->conn->pcb.tcp != NULL) {
switch (msg->conn->type) {
......
case NETCONN_TCP:
msg->conn->pcb.tcp = tcp_listen(msg->conn->pcb.tcp);
if (msg->conn->pcb.tcp == NULL) {
msg->conn->err = ERR_MEM;
} else {
......
tcp_arg(msg->conn->pcb.tcp, msg->conn);
tcp_accept(msg->conn->pcb.tcp, accept_function);
}
default:
break;
}
}
sys_mbox_post(msg->conn->mbox, NULL);
}
7.1 内核函数 tcp_listen
struct tcp_pcb *tcp_listen(struct tcp_pcb *pcb)
{
struct tcp_pcb_listen *lpcb;
......
lpcb = memp_malloc(MEMP_TCP_PCB_LISTEN);
if (lpcb == NULL) {
return NULL;
}
lpcb->callback_arg = pcb->callback_arg;
lpcb->local_port = pcb->local_port;
lpcb->state = LISTEN;
lpcb->so_options = pcb->so_options;
lpcb->so_options |= SOF_ACCEPTCONN;
lpcb->ttl = pcb->ttl;
lpcb->tos = pcb->tos;
ip_addr_set(&lpcb->local_ip, &pcb->local_ip);
memp_free(MEMP_TCP_PCB, pcb);
#if LWIP_CALLBACK_API
lpcb->accept = tcp_accept_null;
#endif /* LWIP_CALLBACK_API */
TCP_REG(&tcp_listen_pcbs.listen_pcbs, lpcb);
狗拿耗子
8
return (struct tcp_pcb *)lpcb;
}
分配一个类型为 tcp_pcb_listen的 pcb,并将 tcp_pcb的相应的参数拷贝过来,包括 self ip、self port等,设
置新分配 pcb的状态为 LISTEN,最后将它放入队列 tcp_listen_pcbs。队列 tcp_listen_pcbs存放着所有处于
LISTEN状态的 pcb,相应的 connection的指针可从 pcb->callback_arg获得。
7.2 注册 accept_function,当接收了一个 connect链接请求并三次握手完成后,调用该函数通知用户任务。
8、RTT的估计
8.1 估计 RTT的算法
static u8_t tcp_receive(struct tcp_pcb *pcb)
{
......
/* RTT estimation calculations. This is done by checking if the
incoming segment acknowledges the segment we use to take a
round-trip time measurement. */
if (pcb->rttest && TCP_SEQ_LT(pcb->rtseq, ackno)) {
m = tcp_ticks - pcb->rttest;
......
/* This is taken directly from VJs original code in his paper */
m = m - (pcb->sa >> 3);
pcb->sa += m;
if (m < 0) {
m = -m;
}
m = m - (pcb->sv >> 2);
pcb->sv += m;
pcb->rto = (pcb->sa >> 3) + pcb->sv;
......
pcb->rttest = 0;
}
......
}
z tcp_ticks,内核的 tick,500ms递增一次。
z pcb->rttest,发起 rtt估计时的 tick,当该值为 0时表示不需要做 rtt估计。
z pcb->rtseq,用于 rtt估计的 segment,当该 segment的 ack到达时,进行 rtt估计。
z pcb->sa,A = sa >> 3。
z pcb->sv,D = sv >> 2。
z pcb->rto,rtt的估计值。
算法的详细叙述见《TCP/IP详解,卷 1:协议》第 21章,上述代码可转换为:
m = m - (pcb->sa >> 3); /* Err = M – A */
pcb->sa += m; /* 8A = 8A + Err,即 A = A + Err / 8 */
if (m < 0) { /* e = |Err| */
m = -m;
狗拿耗子
9
}
m = m - (pcb->sv >> 2); /* t = |Err| - D */
pcb->sv += m; /* 4D = 4D + (|Err| - D),即 D = D + (|Err| - D) / 4 */
pcb->rto = (pcb->sa >> 3) + pcb->sv; /* RTT = A + 4D */
可见,与《卷 1:协议》中描述的一模一样。
8.2 启动 RTT估计的时机
《卷 1:协议》说在内核没有进行 rtt估计时,且不处于超时重传状态(避免重传多义性)(此处指的不是快
速重传状态)时可以进行 rtt估计。
1) 在窗口允许的前提下,即MIN(self cwnd, peer rcv_wnd)大于待发送 segment的长度,内核函数 tcp_output
无条件调用内核函数 tcp_output_segment。
2) 内核函数 tcp_output_segment发现当前没有进行 rtt估计,则无条件立刻启动 rtt估计。
static void tcp_output_segment(struct tcp_seg *seg, struct tcp_pcb *pcb)
{
......
pcb->rtime = 0;
if (pcb->rttest == 0) {
pcb->rttest = tcp_ticks;
pcb->rtseq = ntohl(seg->tcphdr->seqno);
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_output_segment: rtseq %"U32_F"\n", pcb->rtseq));
}
......
ip_output(seg->p, &(pcb->local_ip), &(pcb->remote_ip), pcb->ttl, pcb->tos,
IP_PROTO_TCP);
}
与《卷 1:协议》不一致的是:快速重传内核函数 tcp_rexmit会调用 tcp_output,这里没问题,因为不存在
重传二义性;但超时重传内核函数 tcp_rexmit_rto 也会调用 tcp_output,这是与《卷 1:协议》矛盾的。如
果我的理解没出错的话,lwip在这个地方在估计 rtt时会存在一定的风险。
8.3 rtt估计的关闭
将 pcb->rttest置为 0即关闭 rtt估计,共有三处会关闭 rtt估计:
z tcp_receive,完成了 rtt估计的运算,正常关闭。
z tcp_rexmit,快速重传,关闭 rtt估计。
z tcp_rexmit_rto,超时重传,关闭 rtt估计。
9、segment的超时重传与慢启动
void tcp_slowtmr(void)
{
......
pcb_remove = 0;
if (pcb->state == SYN_SENT && pcb->nrtx == TCP_SYNMAXRTX) {
狗拿耗子
10
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max SYN retries reached\n"));
}
else if (pcb->nrtx == TCP_MAXRTX) {
++pcb_remove;
LWIP_DEBUGF(TCP_DEBUG, ("tcp_slowtmr: max DATA retries reached\n"));
} else {
++pcb->rtime;
if (pcb->unacked != NULL && pcb->rtime >= pcb->rto) {
/* Time for a retransmission. */
LWIP_DEBUGF(TCP_RTO_DEBUG, ("tcp_slowtmr: rtime %"U16_F" pcb->rto %"U16_F"\n",
pcb->rtime, pcb->rto));
/* Double retransmission time-out unless we are trying to
* connect to somebody (i.e., we are in SYN_SENT). */
if (pcb->state != SYN_SENT) {
pcb->rto = ((pcb->sa >> 3) + pcb->sv) << tcp_backoff[pcb->nrtx];
}
/* Reduce congestion window and ssthresh. */
eff_wnd = LWIP_MIN(pcb->cwnd, pcb->snd_wnd);
pcb->ssthresh = eff_wnd >> 1;
if (pcb->ssthresh < pcb->mss) {
pcb->ssthresh = pcb->mss * 2;
}
pcb->cwnd = pcb->mss;
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_slowtmr: cwnd %"U16_F" ssthresh %"U16_F"\n",
pcb->cwnd, pcb->ssthresh));
/* The following needs to be called AFTER cwnd is set to one mss - STJ */
tcp_rexmit_rto(pcb);
}
}
......
}
9.1 相应数据结构
z pcb->rto,重传定时器时长,rtt估计时给出初始值,当超时重传时该值按指数增大。
z pcb->rtime,segment从被发出到此时(没有收到 ack),所经历的 tick数。在 tcp_output_segment 中,
该值被置 0。在 tcp_slowtmr中,该值被递增。
z pcb->nrtx,segment 重传的次数。在 tcp_receive 中,收到新的 ack 时,该值被置 0。在 tcp_rexmit、
tcp_rexmit_rto中,该值被递增。
9.2 主要流程
狗拿耗子
11
当重传次数大于 TCP_MAXRTX时,segment被扔掉。当 rtime大于 rto时,启动慢启动流程,慢启动门限
被置为当前窗口的一半,但要不小于 2个 mss;拥塞窗口为一个 mss,rto按指数增大;最后重传该 segment。
详细介绍见《卷 1:协议》21.6。此后如果有新的 ack到达时,拥塞窗口每次递增一个 mss。
10、快速重传与快速恢复
tcp_receive(struct tcp_pcb *pcb)
{
......
if (pcb->lastack == ackno) { /* 到达的 ack为重复 ack */
pcb->acked = 0;
if (pcb->snd_wl1 + pcb->snd_wnd == right_wnd_edge){ /* peer rcv_wnd没有张开 */
++pcb->dupacks;
if (pcb->dupacks >= 3 && pcb->unacked != NULL) { /* 收到了三个以上的重复 ack */
if (!(pcb->flags & TF_INFR)) { /* 不处于快速恢复状态 */
/* This is fast retransmit. Retransmit the first unacked segment. */
......
tcp_rexmit(pcb);
......
/* Set ssthresh to half of the minimum of the currenct cwnd and the advertised window */
if (pcb->cwnd > pcb->snd_wnd) /* 慢启动门限被置为当前窗口的一半 */
pcb->ssthresh = pcb->snd_wnd / 2;
else
pcb->ssthresh = pcb->cwnd / 2;
pcb->cwnd = pcb->ssthresh + 3 * pcb->mss; /* 拥塞窗口为慢启动门限加上 3个 mss */
pcb->flags |= TF_INFR; /* 标记当前状态为快速恢复 */
} else { /* 已处于快速恢复状态 */
/* Inflate the congestion window, but not if it means that
the value overflows. */
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss; /* 当前处于快速恢复状态,拥塞窗口递增一个 mss */
}
}
}
} else {
LWIP_DEBUGF(TCP_FR_DEBUG, ("tcp_receive: dupack averted %"U32_F" %"U32_F"\n",
pcb->snd_wl1 + pcb->snd_wnd, right_wnd_edge));
}
} else
if (TCP_SEQ_BETWEEN(ackno, pcb->lastack+1, pcb->snd_max)){
/* We come here when the ACK acknowledges new data. */
/* Reset the "IN Fast Retransmit" flag, since we are no longer
狗拿耗子
12
in fast retransmit. Also reset the congestion window to the
slow start threshold. */
if (pcb->flags & TF_INFR) { /* 如果处于快速恢复,新的 ack到达时,应将拥塞控制窗口置为慢启动
门限的值(拥塞发生时的窗口大小的一半);并清除快速恢复标识;
此时传入拥塞避免状态 */
pcb->flags &= ~TF_INFR;
pcb->cwnd = pcb->ssthresh;
}
/* Reset the number of retransmissions. */
pcb->nrtx = 0;
/* Reset the retransmission time-out. */
pcb->rto = (pcb->sa >> 3) + pcb->sv;
/* Update the send buffer space. */
pcb->acked = ackno - pcb->lastack;
pcb->snd_buf += pcb->acked;
/* Reset the fast retransmit variables. */
pcb->dupacks = 0;
pcb->lastack = ackno;
/* Update the congestion control variables (cwnd and ssthresh). */
if (pcb->state >= ESTABLISHED) {
if (pcb->cwnd < pcb->ssthresh) { /* 当前处于慢启动状态,拥塞窗口递增一个 mss */
if ((u16_t)(pcb->cwnd + pcb->mss) > pcb->cwnd) {
pcb->cwnd += pcb->mss;
}
LWIP_DEBUGF(TCP_CWND_DEBUG, ("tcp_receive: slow start cwnd %"U16_F"\n", pcb->cwnd));
} else { /* 当前处于拥塞避免状态,拥塞窗口递增 mss/N,其中 cwnd = N*mss */
u16_t new_cwnd = (pcb->cwnd + pcb->mss * pcb->mss / pcb->cwnd);
if (new_cwnd > pcb->cwnd) {
pcb->cwnd = new_cwnd;
}
......
}
}
......
}
}
11、内核函数 tcp_enqueue
err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len, u8_t flags, u8_t copy, u8_t *optdata, u8_t optlen)
狗拿耗子
13
z arg,待发送数据指针
z len,待发送数据长度
z flags,待发送 segment的 flag,如 FIN、SYN
z copy,表示 tcp_enqueue是否需要拷贝待发送数据,1表示将持有数据的拷贝的 segment放入待发送队
列,0表示将持有数据的引用的 segment放入待发送队列。
z optdata, 待发送选项数据指针
z optlen,待发送选项数据长度
11.1 对 tcp_enqueue的引用
z tcp_connect,发送 SYN和 mss。
z tcp_listen_input,发送 SYN、ACK和 mss。
z tcp_send_ctrl,发送给定的 flags
z tcp_write,发送给定的数据
可见只有 tcp_connect、tcp_listen_input发送了 optdata,可以总结出:
1)当有 optdata发送时,flags一定被掩上了 SYN
2)optdata与数据不同时发送
11.2 主要流程
err_t tcp_enqueue(struct tcp_pcb *pcb, void *arg, u16_t len,
u8_t flags, u8_t copy,
u8_t *optdata, u8_t optlen)
{
struct pbuf *p;
struct tcp_seg *seg, *useg, *queue;
u32_t left, seqno;
u16_t seglen;
void *ptr;
u8_t queuelen;
......
/* fail on too much data */
if (len > pcb->snd_buf) {
......
return ERR_MEM;
}
left = len;
ptr = arg;
/* seqno will be the sequence number of the first segment enqueued
* by the call to this function. */
seqno = pcb->snd_lbb; /* snd_lbb,下一个 segment的 sequence number */
......
/* If total number of pbufs on the unsent/unacked queues exceeds the
* configured maximum, return an error */
queuelen = pcb->snd_queuelen;
狗拿耗子
14
if (queuelen >= TCP_SND_QUEUELEN) { /* buffer最多值放 TCP_SND_QUEUELEN个 segment */
......
return ERR_MEM;
}
......
/* First, break up the data into segments and tuck them together in
* the local "queue" variable. */
useg = queue = seg = NULL;
seglen = 0;
while (queue == NULL || left > 0) {
/* The segment length should be the MSS if the data to be enqueued
* is larger than the MSS. */
seglen = left > pcb->mss? pcb->mss: left; /* 待发送数据被分割成 mss大小的 segment */
/* Allocate memory for tcp_seg, and fill in fields. */
seg = memp_malloc(MEMP_TCP_SEG); /* 分配一个 segment */
if (seg == NULL) {
LWIP_DEBUGF(TCP_OUTPUT_DEBUG | 2, ("tcp_enqueue: could not allocate memory for tcp_seg\n"));
goto memerr;
}
seg->next = NULL;
seg->p = NULL;
/* first segment of to-be-queued data? */
if (queue == NULL) {
queue = seg;
}
/* subsequent segments of to-be-queued data */
else {
/* Attach the segment to the end of the queued segments */
LWIP_ASSERT("useg != NULL", useg != NULL);
useg->next = seg;
}
/* remember last segment of to-be-queued data for next iteration */
useg = seg;
/* If copy is set, memory should be allocated
* and data copied into pbuf, otherwise data comes from
* ROM or other static memory, and need not be copied. If
* optdata is != NULL, we have options instead of data. */
/* options? */
if (optdata != NULL) { /* 分配一个 RAM类型的 pbuf,optdata的拷贝在后面 */
狗拿耗子
15
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, optlen, PBUF_RAM)) == NULL) {
goto memerr;
}
++queuelen;
seg->dataptr = seg->p->payload;
}
/* copy from volatile memory? */
else if (copy) { /* 分配一个 RAM类型的 pbuf,将待发送数据拷贝进去 */
if ((seg->p = pbuf_alloc(PBUF_TRANSPORT, seglen, PBUF_RAM)) == NULL) {
......
goto memerr;
}
++queuelen;
if (arg != NULL) {
memcpy(seg->p->payload, ptr, seglen);
}
seg->dataptr = seg->p->payload;
}
/* do not copy data */
else {
/* First, allocate