TCP/IP协议栈在Linux内核中的运行时序分析
1、Linux系统概述
1.1linux内核
Linux 系统一般有 4 个主要部分:内核、shell、文件系统和应用程序。内核、shell和文件系统一起形成了基本的操作系统结构,它们使得用户可以运行程序、管理文件并使用系统。
Linux内核对下,它管理系统的所有硬件设备;对上,它通过系统调用,向Library Routine(例如C库)或者其它应用程序提供接口。其核心功能就是:管理硬件设备,供应用程序使用。
从功能上,我们将linux内核划分为五个不同的部分,分别是
(1)进程管理:主要负载CPU的访问控制,对CPU进行调度管理;
(2)内存管理:主要提供对内存资源的访问控制;
(3)文件系统:将硬盘的扇区组织成文件系统,实现文件读写等操作;
(4)设备管理:用于控制所有的外部设备及控制器;
(5)网络管理:主要负责管理各种网络设备,并实现各种网络协议栈,最终
实现通过网络连接其它系统的功能;
每个部分分别处理一项明确的功能,又向其它各个部分提供自己所完成的功能,相互协调,共同完成操作系统的任务。
Linux内核架构如下图所示:

1.2 协议栈简介

TCP/IP 协议栈是一系列网络协议的总和,是构成网络通信的核心骨架,它定义了电子设备如何连入因特网,以及数据如何在它们之间进行传输。TCP/IP 协议采用4层结构,分别是应用层:支持各种网络应用。传输层:负责主机中两个进程的通信,即端到端的通信。网络层:把分组从源端传到目的端,为分组交换网上不同主机提供通信服务。负责接收IP数据报,并负责把这些数据报发送到指定网络上。
1.3 Linux网络协议栈的架构

Linux的网络架构从上往下可以分为三层,分别是:
用户空间的应用层。
内核空间的网络协议栈层。
物理硬件层。
其中最重要最核心的当然是内核空间的协议栈层了。LLinux的整个网络协议栈都构建与Linux Kernel中,整个栈也是严格按照分层的思想来设计的,整个栈共分为五层,分别是 :
(1)系统调用接口层,实质是一个面向用户空间应用程序的接口调用库,向用户空间应用程序提供使用网络服务的接口。
(2)协议无关接口层,它提供了一种通用方法来使用网络协议,屏蔽底层的不同协议(主要是TCP与UDP,还包括RAW IP, SCTP等),以便与系统调用层之间的接口可以简单,统一。
(3)网络协议实现层,这一层主要实现各种网络协议,最主要的当然是IP,ICMP,ARP,RARP,TCP,UDP等。
(4)设备无关接口层,提供了与各个设备驱动通信的通用接口,这一层的目的主要是为了统一不同的接口卡的驱动程序与网络协议层的接口,屏蔽底层不同的驱动程序。
(5)设备驱动程序层,这一层的目的是建立与硬件的接口层
Linux网络子系统通过这五层结构的相互交互,共同完成TCP/IP协议栈的运行。
1.4 Socket
Socket是独立于具体协议的网络编程接口,在OSI模型中,主要位于会话层和传输层之间。屏蔽了不同网络协议之间的差异,它提供了大量的系统调用,构成了网络程序的主体。在Linux系统中socket 属于文件系统的一部分,网络通信可以被看作是对文件的读取,使得我们对网络的控制和对文件的控制一样方便。
Linux中使用stuct socket描述Socket,代表一条通信链路的一端,用来存储与该链路有关的所有信息,这些信息中包括:
- 所使用的协议
- 协议的状态信息(包括源地址和目标地址)
- 到达的连接队列
- 数据缓存和可选标志等等
struct socket { socket_state state; //描述套接字的状态 unsigned long flags; //套接字的一组标志位 const struct proto_ops *ops; //将套接字层调用于传输层协议对应 struct fasync_struct *fasync_list; //存储异步通知队列 struct file *file; //套接字相关联的文件指针 struct sock *sk; //套接字相关联的传输控制块 wait_queue_head_t wait; //等待套接字的进程列表 short type; //套接字的类型 };
socket最关键的成员是sk和ops,指向相关的传输控制块,ops指向特定的传输协议的操作集。
网络程序调用Socket 进行通信(TCP)的主要流程,如图所示:

2.send和recv过程中TCP/IP协议栈相关的运行分析
应用层
发送端
,然后发起系统调用。sendto函数的作用是将数据报发送至指定的目的地址。而sys_sendto是通过调用sys_sendmsg函数进行发送数据的。sys_sendmsg函数的作用是将用户空间的信息复制到内核空间中,然后再逐级调用发包接口发送数据。 sys_sendmsg最终调用了sock_sendmsg()。
send的定义如下所示:
ssize_t send(int sockfd, const void *buf, size_t len, int flags)
sendto函数部分源代码如下:
int __sys_sendto(int fd, void __user *buff, size_t len, unsigned int flags, struct sockaddr __user *addr, int addr_len) { struct msghdr msg; //...预处理... //在接受数据之前,检查用户空间的地址是否可读 err = import_single_range(WRITE, buff, len, &iov, &msg.msg_iter); //...... //通过文件描述符fd来找到需要的结构体 sock = sockfd_lookup_light(fd, &err, &fput_needed); //...... if (addr) { //将用户空间的数据传递至内核空间 err = move_addr_to_kernel(addr, addr_len, &address); //...... } //...... //发送数据 err = sock_sendmsg(sock, &msg); out_put: //更新文件的引用计数 fput_light(sock->file, fput_needed); out: return err; }
sys_sendmsg函数部分源代码如下:
asmlinkage long sys_sendmsg(int fd, struct msghdr __user *msg, unsigned flags) {.......//控制缓存信息进行复制 if ((MSG_CMSG_COMPAT & flags) && ctl_len) { err = cmsghdr_from_user_compat_to_kern(&msg_sys, ctl, sizeof(ctl)); if (err) goto out_freeiov; ctl_buf = msg_sys.msg_control; } else if (ctl_len) { //复制控制缓存信息 if (ctl_len > sizeof(ctl)) { ctl_buf = sock_kmalloc(sock->sk, ctl_len, GFP_KERNEL); if (ctl_buf == NULL) goto out_freeiov; } err = -EFAULT; /* * Careful! Before this, msg_sys.msg_control contains a user pointer. * Afterwards, it will be a kernel pointer. Thus the compiler-assisted * checking falls down on this. */ if (copy_from_user(ctl_buf, (void __user *) msg_sys.msg_control, ctl_len)) goto out_freectl; msg_sys.msg_control = ctl_buf; } msg_sys.msg_flags = flags; //设置数据发送标识 if (sock->file->f_flags & O_NONBLOCK) msg_sys.msg_flags |= MSG_DONTWAIT; //调用sock_sendmsg函数发送报文 err = sock_sendmsg(sock, &msg_sys, total_len); out_freectl: //释放临时申请的缓冲区 if (ctl_buf != ctl) sock_kfree_s(sock->sk, ctl_buf, ctl_len); out_freeiov: if (iov != iovstack) sock_kfree_s(sock->sk, iov, iov_size); out_put: sockfd_put(sock); out: return err; }......
sys_sendmsg调用sock_sendmsg函数继续执行发送流程。sock_sendmsg调用sock_sendmsg_nosec(),sock_sendmsg_nosec()最后调用struct socket->ops->sendmsg。
static inline int __sock_sendmsg(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size) { int err = security_socket_sendmsg(sock, msg, size); return err ?: __sock_sendmsg_nosec(iocb, sock, msg, size); } static inline int __sock_sendmsg_nosec(struct kiocb *iocb, struct socket *sock, struct msghdr *msg, size_t size) { struct sock_iocb *si = kiocb_to_siocb(iocb); si->sock = sock; si->scm = NULL; si->msg = msg; si->size = size; /* 调用Socket层的操作函数,如果是SOCK_STREAM,则proto_ops为inet_stream_ops, * 函数指针指向inet_sendmsg()。 */ return sock->ops->sendmsg(iocb, sock, msg, size); }
sock-ops->sendmsg会根据 socket 的协议类型,调用相应协议的发送函数,对于TCP ,调用 tcp_sendmsg 函数。
下面进行gdb调试追踪验证:

应用层接收端
接收端
对于recv函数与send函数类似,先调用 __sys_recv 系统调用,并被转化为 __sys_recvfrom调用,然后调用 sock_recvmsg 函数。对TCP 来说,调用 tcp_recvmsg。该函数从 socket buffer 中拷贝数据到 user buffer。
int __sys_recvfrom(int fd, void __user *ubuf, size_t size, unsigned int flags, struct sockaddr __user *addr, int __user *addr_len) { ...... err = import_single_range(READ, ubuf, size, &iov, &msg.msg_iter); if (unlikely(err)) return err; sock = sockfd_lookup_light(fd, &err, &fput_needed); ..... msg.msg_control = NULL; msg.msg_controllen = 0; /* Save some cycles and don't copy the address if not needed */ msg.msg_name = addr ? (struct sockaddr *)&address : NULL; /* We assume all kernel code knows the size of sockaddr_storage */ msg.msg_namelen = 0; msg.msg_iocb = NULL; msg.msg_flags = 0; if (sock->file->f_flags & O_NONBLOCK) flags |= MSG_DONTWAIT; err = sock_recvmsg(sock, &msg, flags); if (err >= 0 && addr != NULL) { err2 = move_addr_to_user(&address, msg.msg_namelen, addr, addr_len); ..... }
int sock_recvmsg(struct socket *sock, struct msghdr *msg, int flags) { int err = security_socket_recvmsg(sock, msg, msg_data_left(msg), flags); return err ?: sock_recvmsg_nosec(sock, msg, flags); } EXPORT_SYMBOL(sock_recvmsg); static inline int sock_recvmsg_nosec(struct socket *sock, struct msghdr *msg, int flags) { return INDIRECT_CALL_INET(sock->ops->recvmsg, inet6_recvmsg, inet_recvmsg, sock, msg, msg_data_left(msg), flags); }
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len) { ...... if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) && (sk->sk_state == TCP_ESTABLISHED)) sk_busy_loop(sk, nonblock); //如果接收队列为空,则会在该函数内循环等待 lock_sock(sk); ..... if (unlikely(tp->repair)) { err = -EPERM; if (!(flags & MSG_PEEK)) goto out; if (tp->repair_queue == TCP_SEND_QUEUE) goto recv_sndq; err = -EINVAL; if (tp->repair_queue == TCP_NO_QUEUE) goto out; ...... last = skb_peek_tail(&sk->sk_receive_queue); skb_queue_walk(&sk->sk_receive_queue, skb) { last = skb; ...... if (!(flags & MSG_TRUNC)) { err = skb_copy_datagram_msg(skb, offset, msg, used); //将接收到的数据拷贝到用户态 if (err) { /* Exception. Bailout! */ if (!copied) copied = -EFAULT; break; } } *seq += used; copied += used; len -= used; tcp_rcv_space_adjust(sk); 在连接建立后,若没有数据到来,接收队列为空,进程会在sk_busy_loop函数内循环等待,知道接收队列不为空,并调用函数数skb_copy_datagram_msg将接收到的数据拷贝到用户态,该函数内部实际调用的是__skb_datagram_iter,其代码如下所示: int __skb_datagram_iter(const struct sk_buff *skb, int offset, struct iov_iter *to, int len, bool fault_short, size_t (*cb)(const void *, size_t, void *, struct iov_iter *), void *data) { int start = skb_headlen(skb); int i, copy = start - offset, start_off = offset, n; struct sk_buff *frag_iter; /* 拷贝tcp头部 */ if (copy > 0) { if (copy > len) copy = len; n = cb(skb->data + offset, copy, data, to); offset += n; if (n != copy) goto short_copy; if ((len -= copy) == 0) return 0; } /* 拷贝数据部分 */ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; WARN_ON(start > offset + len); end = start + skb_frag_size(frag); if ((copy = end - offset) > 0) { struct page *page = skb_frag_page(frag); u8 *vaddr = kmap(page); if (copy > len) copy = len; n = cb(vaddr + frag->page_offset + offset - start, copy, data, to); kunmap(page); offset += n; if (n != copy) goto short_copy; if (!(len -= copy)) return 0; } start = end; }
下面进行gdb调试追踪验证:

传输层
发送端
tcp_sendmsg()函数要完成的工作就是将应用程序要发送的数据组织成skb,然后调用tcp_push函数。
...
//查看是否立即发送数据包
if (forced_push(tp)) {
//设置立即发送数据包标志PSH
tcp_mark_push(tp, skb);
//发送数据包,实际调用的是ip_queue_xmit
__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);
} else if (skb == tcp_send_head(sk))
tcp_push_one(sk, mss_now);
continue;
wait_for_sndbuf:
//缓冲数据段,等到一定数量再发送
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
wait_for_memory:
if (copied)
tcp_push(sk, flags & ~MSG_MORE, mss_now, TCP_NAGLE_PUSH);
if ((err = sk_stream_wait_memory(sk, &timeo)) != 0)
goto do_error;
mss_now = tcp_send_mss(sk, &size_goal, flags);
}
}
...
}
EXPORT_SYMBOL(tcp_sendmsg);
tcp_push()在判断了是否需要设置PUSH标记位之后,会调用__tcp_push_pending_frames()
static inline void tcp_push(struct sock *sk, int flags, int mss_now, int nonagle) { struct tcp_sock *tp = tcp_sk(sk); if (tcp_send_head(sk)) { //判断是否需要设置PUSH标记 struct sk_buff *skb = tcp_write_queue_tail(sk); if (!(flags & MSG_MORE) || forced_push(tp)) tcp_mark_push(tp, skb); //MSG_OOB相关,忽略 tcp_mark_urg(tp, flags, skb); //调用__tcp_push_pending_frames()尝试发送 __tcp_push_pending_frames(sk, mss_now, (flags & MSG_MORE) ? TCP_NAGLE_CORK : nonagle); } } __tcp_push_pending_frames()调用调用tcp_write_xmit()完成发送。 /* Push out any pending frames which were held back due to * TCP_CORK or attempt at coalescing tiny packets. * The socket must be locked by the caller. */ void __tcp_push_pending_frames(struct sock *sk, unsigned int cur_mss, int nonagle) { struct sk_buff *skb = tcp_send_head(sk); //如果有新数据可供发送,调用tcp_write_xmit()发送 if (skb) { if (tcp_write_xmit(sk, cur_mss, nonagle)) //和PMTU相关 tcp_check_probe_timer(sk); } }
tcp_write_xmit()该函数是TCP发送新数据的核心函数,包括发送窗口判断、拥塞控制判断等核心操作都是在该函数中完成。它又将调用发送函数tcp_transmit_skb函数,对TCP数据段的头部进行了处理;最后调用网络层提供的发送回调函数发送skb,ip层的回调函数为ip_queue_xmit;
static bool tcp_write_xmit(struct sock *sk, unsigned int mss_now, int nonagle,
int push_one, gfp_t gfp)
{
struct tcp_sock *tp = tcp_sk(sk);
struct sk_buff *skb;
unsigned int tso_segs, sent_pkts;
int cwnd_quota;
int result;
bool is_cwnd_limited = false, is_rwnd_limited = false;
u32 max_segs;
/*统计已发送的报文总数*/
sent_pkts = 0;
......
/*若发送队列未满,则准备发送报文*/
while ((skb = tcp_send_head(sk))) {
unsigned int limit;
if (unlikely(tp->repair) && tp->repair_queue == TCP_SEND_QUEUE) {
/* "skb_mstamp_ns" is used as a start point for the retransmit timer */
skb->skb_mstamp_ns = tp->tcp_wstamp_ns = tp->tcp_clock_cache;
list_move_tail(&skb->tcp_tsorted_anchor, &tp->tsorted_sent_queue);
tcp_init_tso_segs(skb, mss_now);
goto repair; /* Skip network transmission */
}
if (tcp_pacing_check(sk))
break;
tso_segs = tcp_init_tso_segs(skb, mss_now);
BUG_ON(!tso_segs);
/*检查发送窗口的大小*/
cwnd_quota = tcp_cwnd_test(tp, skb);
if (!cwnd_quota) {
if (push_one == 2)
/* Force out a loss probe pkt. */
cwnd_quota = 1;
else
break;
}
if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now))) {
is_rwnd_limited = true;
break;
......
limit = mss_now;
if (tso_segs > 1 && !tcp_urg_mode(tp))
limit = tcp_mss_split_point(sk, skb, mss_now,
min_t(unsigned int,
cwnd_quota,
max_segs),
nonagle);
if (skb->len > limit &&
unlikely(tso_fragment(sk, TCP_FRAG_IN_WRITE_QUEUE,
skb, limit, mss_now, gfp)))
break;
if (tcp_small_queue_check(sk, skb, 0))
break;
if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))
break;
......
}
static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it, gfp_t gfp_mask, u32 rcv_nxt) { skb_push(skb, tcp_header_size); skb_reset_transport_header(skb); ...... /* 构建TCP头部和校验和 */ th = (struct tcphdr *)skb->data; th->source = inet->inet_sport; th->dest = inet->inet_dport; th->seq = htonl(tcb->seq); th->ack_seq = htonl(rcv_nxt); tcp_options_write((__be32 *)(th + 1), tp, &opts); skb_shinfo(skb)->gso_type = sk->sk_gso_type; if (likely(!(tcb->tcp_flags & TCPHDR_SYN))) { th->window = htons(tcp_select_window(sk)); tcp_ecn_send(sk, skb, th, tcp_header_size); } else { /* RFC1323: The window in SYN & SYN/ACK segments * is never scaled. */ th->window = htons(min(tp->rcv_wnd, 65535U)); } ...... icsk->icsk_af_ops->send_check(sk, skb); if (likely(tcb->tcp_flags & TCPHDR_ACK)) tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt); if (skb->len != tcp_header_size) { tcp_event_data_sent(tp, sk); tp->data_segs_out += tcp_skb_pcount(skb); tp->bytes_sent += skb->len - tcp_header_size; } if (after(tcb->end_seq, tp->snd_nxt) || tcb->seq == tcb->end_seq) TCP_ADD_STATS(sock_net(sk), TCP_MIB_OUTSEGS, tcp_skb_pcount(skb)); tp->segs_out += tcp_skb_pcount(skb); /* OK, its time to fill skb_shinfo(skb)->gso_{segs|size} */ skb_shinfo(skb)->gso_segs = tcp_skb_pcount(skb); skb_shinfo(skb)->gso_size = tcp_skb_mss(skb); /* Leave earliest departure time in skb->tstamp (skb->skb_mstamp_ns) */ /* Cleanup our debris for IP stacks */ memset(skb->cb, 0, max(sizeof(struct inet_skb_parm), sizeof(struct inet6_skb_parm))); err = icsk->icsk_af_ops->queue_xmit(sk, skb, &inet->cork.fl); ......
gdb追踪验证:

接收端
传输层
tcp_v4_rcv函数为TCP的总入口,数据包从IP层传递上来,进入该函数。tcp_v4_rcv函数主要设置TCP_CB查找控制块,根据控制块状态做不同处理,包括TCP_TIME_WAIT状态处理,TCP_NEW_SYN_RECV状态处理,TCP_LISTEN状态处理,接收TCP段;当状态为TCP_LISTEN调用tcp_v4_do_rcv
/* LISTEN状态处理 */ if (sk->sk_state == TCP_LISTEN) { ret = tcp_v4_do_rcv(sk, skb); goto put_and_return; }
查找入口地址函数tcp_v4_rcv,如果状态为ESTABLISHED,即已连接状态,就会调用tcp_rcv_established()函数
if (sk->sk_state == TCP_ESTABLISHED) { /* Fast path */ struct dst_entry *dst = sk->sk_rx_dst; sock_rps_save_rxhash(sk, skb); sk_mark_napi_id(sk, skb); if (dst) { if (inet_sk(sk)->rx_dst_ifindex != skb->skb_iif || !dst->ops->check(dst, 0)) { dst_release(dst); sk->sk_rx_dst = NULL; } } tcp_rcv_established(sk, skb, tcp_hdr(skb), skb->len); return 0; }
在tcp_rcv_established这个函数中,调用了tcp_queue_rcv函数
1 /* Bulk data transfer: receiver */ 2 3 __skb_pull(skb, tcp_header_len); 4 5 eaten = tcp_queue_rcv(sk, skb, &fragstolen); 6 7 tcp_event_data_recv(sk, skb); 8 9 if (TCP_SKB_CB(skb)->ack_seq != tp->snd_una) { 10 11 /* Well, only one small jumplet in fast path... */ 12 13 tcp_ack(sk, skb, FLAG_DATA); 14 15 tcp_data_snd_check(sk); 16 17 if (!inet_csk_ack_scheduled(sk)) 18 19 goto no_ack; 20 21 } 22 23 __tcp_ack_snd_check(sk, 0);
static int __must_check tcp_queue_rcv(struct sock *sk, struct sk_buff *skb, 2 3 bool *fragstolen) 4 5 { 6 7 int eaten; 8 9 struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue); 10 11 eaten = (tail && 12 13 tcp_try_coalesce(sk, tail, 14 15 skb, fragstolen)) ? 1 : 0; 16 17 tcp_rcv_nxt_update(tcp_sk(sk), TCP_SKB_CB(skb)->end_seq); 18 19 if (!eaten) { 20 21 __skb_queue_tail(&sk->sk_receive_queue, skb); 22 23 skb_set_owner_r(skb, sk); 24 25 } 26 27 return eaten; 28 29 }
struct sk_buff *tail = skb_peek_tail(&sk->sk_receive_queue);表明将发送的消息添加到队列的最尾端,即相当于发送之后进行系统调用唤醒socket,然后再利用我们刚刚在应用层提到的tcp_recvmsg函数去进行消息的处理.
int tcp_recvmsg(struct sock *sk, struct msghdr *msg, size_t len, int nonblock, int flags, int *addr_len) { ...... if (sk_can_busy_loop(sk) && skb_queue_empty(&sk->sk_receive_queue) && (sk->sk_state == TCP_ESTABLISHED)) sk_busy_loop(sk, nonblock); lock_sock(sk); ..... if (unlikely(tp->repair)) { err = -EPERM; if (!(flags & MSG_PEEK)) goto out; if (tp->repair_queue == TCP_SEND_QUEUE) goto recv_sndq; err = -EINVAL; if (tp->repair_queue == TCP_NO_QUEUE) goto out; ...... last = skb_peek_tail(&sk->sk_receive_queue); skb_queue_walk(&sk->sk_receive_queue, skb) { last = skb; ...... if (!(flags & MSG_TRUNC)) { err = skb_copy_datagram_msg(skb, offset, msg, used); if (err) { /* Exception. Bailout! */ if (!copied) copied = -EFAULT; break; } } *seq += used; copied += used; len -= used; tcp_rcv_space_adjust(sk); ...... }
这里共维护了三个队列:prequeue、backlog、receive_queue、分别为预处理队列,后备队列和接收队列,在连接建立后,若没有数据到来,接收队列为空,进程会在sk_busy_loop函数内循环等待,知道接收队列不为空,并调用函数skb_copy_datagram_msg将接收到的数据拷贝到用户态,实际调用的是__skb_datagram_iter,这里同样用了struct msghdr *msg来实现。
int __skb_datagram_iter(const struct sk_buff *skb, int offset, struct iov_iter *to, int len, bool fault_short, size_t (*cb)(const void *, size_t, void *, struct iov_iter *), void *data) { int start = skb_headlen(skb); int i, copy = start - offset, start_off = offset, n; struct sk_buff *frag_iter; /* 拷贝tcp头部 */ if (copy > 0) { if (copy > len) copy = len; n = cb(skb->data + offset, copy, data, to); offset += n; if (n != copy) goto short_copy; if ((len -= copy) == 0) return 0; } /* 拷贝数据部分 */ for (i = 0; i < skb_shinfo(skb)->nr_frags; i++) { int end; const skb_frag_t *frag = &skb_shinfo(skb)->frags[i]; WARN_ON(start > offset + len); end = start + skb_frag_size(frag); if ((copy = end - offset) > 0) { struct page *page = skb_frag_page(frag); u8 *vaddr = kmap(page); if (copy > len) copy = len; n = cb(vaddr + frag->page_offset + offset - start, copy, data, to); kunmap(page); offset += n; if (n != copy) goto short_copy; if (!(len -= copy)) return 0; } start = end; } }
拷贝完成后,函数返回,整个接收的过程也就完成了。
gdb追踪验证:


浙公网安备 33010602011771号