定制开发网站的公司,怎样注册企业邮箱帐号,网站首页导航栏,温州网站建设的公司文章目录 1、write源码剖析2、vfs层进行数据传输3、socket层进行数据传输4、tcp层进行数据传输5、ip层进行数据传输6、网络设备层进行数据传输7、网卡驱动层进行数据传输8、数据传输的整个流程 1、write源码剖析
系统调用原型
ssize_t write(int fildes, const void *buf, si… 文章目录 1、write源码剖析2、vfs层进行数据传输3、socket层进行数据传输4、tcp层进行数据传输5、ip层进行数据传输6、网络设备层进行数据传输7、网卡驱动层进行数据传输8、数据传输的整个流程 1、write源码剖析
系统调用原型
ssize_t write(int fildes, const void *buf, size_t nbyte);fildes文件描述符 buf用户缓冲区用于存放要写入的数据 nbyte用户缓冲区的大小 返回值表示成功写入了多少字节的数据因为write并不保证一定将数据全部写完
write系统调用实现位于/fs/read_write.c
SYSCALL_DEFINE3(write, unsigned int, fd, const char __user *, buf,size_t, count)
{/*struct fd {struct file *file;unsigned int flags;};*///得到要操作的文件struct fd f fdget_pos(fd);ssize_t ret -EBADF;//如果文件fd对应的文件不存在直接返回if (f.file) {//需要写文件的位置loff_t pos file_pos_read(f.file);//调用vfs(虚拟文件系统) 提供的写函数ret表示成功写入的数据字节大小ret vfs_write(f.file, buf, count, pos);//如果写入成功就需要更改文件的操作(写入)位置if (ret 0)file_pos_write(f.file, pos);fdput_pos(f);}return ret;
}2、vfs层进行数据传输
接着会调用vfs提供的写入函数
ssize_t vfs_write(struct file *file, const char __user *buf, size_t count, loff_t *pos)
{ssize_t ret;//判断当前的文件是否有写权限 f_mode存放了文件的读写权限类似的可以用if (file-f_mode FMODE_READ)判断文件是否有读权限if (!(file-f_mode FMODE_WRITE))return -EBADF;if (!(file-f_mode FMODE_CAN_WRITE))return -EINVAL;//检查用户空间缓冲区是否可访问if (unlikely(!access_ok(VERIFY_READ, buf, count)))return -EFAULT;//验证要写入的区域是否有效。如果验证失败ret 将不为零ret rw_verify_area(WRITE, file, pos, count);if (!ret) {//用户写入的数据最大为MAX_RW_COUNT因此write不保证一次性都能将用户数据写入完成if (count MAX_RW_COUNT)count MAX_RW_COUNT;file_start_write(file);ret __vfs_write(file, buf, count, pos);if (ret 0) {fsnotify_modify(file);add_wchar(current, ret);}inc_syscw(current);file_end_write(file);}return ret;
}ssize_t __vfs_write(struct file *file, const char __user *p, size_t count,loff_t *pos)
{if (file-f_op-write)return file-f_op-write(file, p, count, pos);else if (file-f_op-write_iter)return new_sync_write(file, p, count, pos);elsereturn -EINVAL;
}如果文件中的f_op存在write就会调用write否则如果存在write_iter就会调用new_sync_write。 那么f_op是什么呢 其实每个文件都对应着自己的file_operations只有实现了里面的这些函数文件才能进行相应的操作。举个例子比如epoll是所有的文件都能加入到epoll让内核帮我们等待吗当然不是只有文件的file_operations实现了poll函数才能放到epoll中等待。
换句话讲就是如果file_operations没有实现write或者write_iter那么文件就无法写入即使这个文件有读写权限也不行。
file_operations也体现了Linux下一切皆文件的含义。
对于socket文件file_operations在net/socket.c中初始化的
static const struct file_operations socket_file_ops {.owner THIS_MODULE,.llseek no_llseek,.read_iter sock_read_iter,.write_iter sock_write_iter,.poll sock_poll,.unlocked_ioctl sock_ioctl,
#ifdef CONFIG_COMPAT.compat_ioctl compat_sock_ioctl,
#endif.mmap sock_mmap,.release sock_close,.fasync sock_fasync,.sendpage sock_sendpage,.splice_write generic_splice_sendpage,.splice_read sock_splice_read,
};socket_file_ops 中没有write却有write_iter(sock_write_iter)但后面却调用new_sync_write函数 但是不用担心最终还是会调用到sock_write_iter函数 想
static ssize_t new_sync_write(struct file *filp, const char __user *buf, size_t len, loff_t *ppos)
{/*struct iovec{void __user *iov_base; //缓存区__kernel_size_t iov_len; //缓冲区的大小};*///将用户缓冲区赋值给struct iovec这个结构体可以用于多缓冲区的写入(writev)和读取(readv)以减少系统调用提高效率。 用户层可以传入多个缓冲区struct iovec iov { .iov_base (void __user *)buf, .iov_len len };//struct kiocb通常用于 Linux 的异步 I/O 操作当IO操作完成时ki_complete函数将被调用/*struct kiocb {struct file *ki_filp; //文件loff_t ki_pos; //偏移量void (*ki_complete)(struct kiocb *iocb, long ret, long ret2); //回调函数void *private; //用于存储与特定异步 I/O 操作相关的私有数据int ki_flags; //用于存储与异步 I/O 操作相关的各种标志};*/struct kiocb kiocb;//用于处理 I/O 向量I/O vectors的结构体。I/O 向量是一种用于表示不连续内存区域的数据结构//struct iov_iter 提供了迭代和遍历这些向量的方法。struct iov_iter iter;ssize_t ret;//初始化上述的结构体init_sync_kiocb(kiocb, filp);kiocb.ki_pos *ppos;iov_iter_init(iter, WRITE, iov, 1, len);//调用write_iter(sock_write_iter)写入数据ret filp-f_op-write_iter(kiocb, iter);BUG_ON(ret -EIOCBQUEUED);if (ret 0)*ppos kiocb.ki_pos;return ret;
}3、socket层进行数据传输
位于net/socket.c
static ssize_t sock_write_iter(struct kiocb *iocb, struct iov_iter *from)
{//获取文件struct file *file iocb-ki_filp;//获取文件对应的socket。在创建socket时会创建对应的file并将socket的指针放在file的private_datastruct socket *sock file-private_data;//这个结构体用于封装要发送的消息。这里将 iov_iter 和 kiocb 传递给 msgstruct msghdr msg {.msg_iter *from,.msg_iocb iocb};ssize_t res;if (iocb-ki_pos ! 0)return -ESPIPE;if (file-f_flags O_NONBLOCK)msg.msg_flags MSG_DONTWAIT;if (sock-type SOCK_SEQPACKET)msg.msg_flags | MSG_EOR;//进一步调用sock_sendmsgres sock_sendmsg(sock, msg);*from msg.msg_iter;return res;
}int sock_sendmsg(struct socket *sock, struct msghdr *msg)
{int err security_socket_sendmsg(sock, msg,msg_data_left(msg));//进一步调用sock_sendmsg_nosecreturn err ?: sock_sendmsg_nosec(sock, msg);
}static inline int sock_sendmsg_nosec(struct socket *sock, struct msghdr *msg)
{//调用sock-ops-sendmsgint ret sock-ops-sendmsg(sock, msg, msg_data_left(msg));BUG_ON(ret -EIOCBQUEUED);return ret;
}这里的sock-ops在协议栈初始化时就已经确定了指向了net/ipv4/af_inet.c中的inet_stream_ops
static struct inet_protosw inetsw_array[]
{{.type SOCK_STREAM,.protocol IPPROTO_TCP,.prot tcp_prot,.ops inet_stream_ops,.flags INET_PROTOSW_PERMANENT |INET_PROTOSW_ICSK,},...
}const struct proto_ops inet_stream_ops {....sendmsg inet_sendmsg,.recvmsg inet_recvmsg,...
};
EXPORT_SYMBOL(inet_stream_ops);其实调用的就是af_inet.c中的inet_sendmsg
int inet_sendmsg(struct socket *sock, struct msghdr *msg, size_t size)
{struct sock *sk sock-sk;sock_rps_record_flow(sk);/* We may need to bind the socket. */if (!inet_sk(sk)-inet_num !sk-sk_prot-no_autobind inet_autobind(sk))return -EAGAIN;return sk-sk_prot-sendmsg(sk, msg, size);
}接着调用sock中sk_prot的sendmsg sk_prot也是在协议栈初始化时就已经确定了指向了net/ipv4/af_inet.c中tcp_prot
struct proto tcp_prot {....recvmsg tcp_recvmsg,.sendmsg tcp_sendmsg,...
};最终调用了tcp_sendmsg函数
4、tcp层进行数据传输
int tcp_sendmsg(struct sock *sk, struct msghdr *msg, size_t size)
{struct tcp_sock *tp tcp_sk(sk);struct sk_buff *skb;struct sockcm_cookie sockc;int flags, err, copied 0;int mss_now 0, size_goal, copied_syn 0;bool process_backlog false;bool sg;long timeo;//对这个socket加锁lock_sock(sk);flags msg-msg_flags;if ((flags MSG_FASTOPEN) !tp-repair) {//使用tcp fastopen来发送数据允许客户端在SYN包中携带应用数据err tcp_sendmsg_fastopen(sk, msg, copied_syn, size);if (err -EINPROGRESS copied_syn 0)goto out;else if (err)goto out_err;}//计算超时时间如果设置了MSG_DONTWAIT标记则超时时间为0timeo sock_sndtimeo(sk, flags MSG_DONTWAIT);//检测TCP连接是否受到应用层限制的tcp_rate_check_app_limited(sk); /* is sending application-limited? *///只有ESTABLISHED和CLOSE_WAIT两个状态可以发送数据//CLOSE_WAIT是收到对端FIN但是本端还没有发送FIN时所处状态所以也可以发送数据//TCP快速打开(被动端)它允许在连接完全建立之前发送数据//除了上述的其他状态都需要等待连接完成才能传输数据if (((1 sk-sk_state) ~(TCPF_ESTABLISHED | TCPF_CLOSE_WAIT)) !tcp_passive_fastopen(sk)) {err sk_stream_wait_connect(sk, timeo);if (err ! 0)goto do_error;}if (unlikely(tp-repair)) {if (tp-repair_queue TCP_RECV_QUEUE) {copied tcp_send_rcvq(sk, msg, size);goto out_nopush;}err -EINVAL;if (tp-repair_queue TCP_NO_QUEUE)goto out_err;}sockc.tsflags sk-sk_tsflags;if (msg-msg_controllen) {err sock_cmsg_send(sk, msg, sockc);if (unlikely(err)) {err -EINVAL;goto out_err;}}/* This should be in poll */sk_clear_bit(SOCKWQ_ASYNC_NOSPACE, sk);//copied将记录本次能够写入TCP的字节数如果成功最终会返回给应用初始化为0copied 0;restart://每次发送都操作都会重新获取MSS值保存到mss_now中mss_now tcp_send_mss(sk, size_goal, flags);err -EPIPE;//检查之前TCP连接是否发生过异常if (sk-sk_err || (sk-sk_shutdown SEND_SHUTDOWN))goto do_error;sg !!(sk-sk_route_caps NETIF_F_SG);//msg里保存着用户传入一个或者多个缓冲区而msg_data_left(msg)返回的就是缓冲区数据量的大小while (msg_data_left(msg)) {int copy 0;int max size_goal;//获取发送队列中最后一个数据块因为该数据块当前已保存数据可能还没有超过//size_goal所以可以继续往该数据块中填充数据skb tcp_write_queue_tail(sk);//tcp_send_head()返回sk_send_head指向发送队列中下一个要发送的数据包//sk_send_head如果为NULL表示待发送的数据为空(可能有待确认数据)//如果不为NULLcopy则表示还能往这个skb放入多少数据if (tcp_send_head(sk)) {if (skb-ip_summed CHECKSUM_NONE)max mss_now;copy max - skb-len;}//copy 0说明发送队列最后一个skb数据量也达到了size_goal不能继续填充数据了if (copy 0 || !tcp_skb_can_collapse_to(skb)) {bool first_skb;new_segment://分配新的skb//即将分配内存首先检查内存使用是否会超限如果会要先等待有内存可用if (!sk_stream_memory_free(sk))goto wait_for_sndbuf;if (process_backlog sk_flush_backlog(sk)) {process_backlog false;goto restart;}//判断即将申请的skb是否是发送队列的第一个skbfirst_skb skb_queue_empty(sk-sk_write_queue);//申请skb//分配skb,select_size()的返回值决定了skb的线性区域大小skb sk_stream_alloc_skb(sk,select_size(sk, sg, first_skb),sk-sk_allocation,first_skb);//分配失败需要等待有剩余内存可用后才能继续发送if (!skb)goto wait_for_memory;process_backlog true;//根据硬件能力确定TCP是否需要执行校验工作if (sk_check_csum_caps(sk))skb-ip_summed CHECKSUM_PARTIAL;//将新分配的skb加入到TCB的发送队列中并且更新相关内存记账信息skb_entail(sk, skb);//设置本轮要拷贝的数据量为size_goal因为该skb是新分配的所以//一定可以容纳这么多但是具体能不能拷贝这么多还需要看有没有这么//多的数据要发送copy size_goal;max size_goal;if (tp-repair)TCP_SKB_CB(skb)-sacked | TCPCB_REPAIRED;}/* Try to append data to the end of skb. *///如果skb可拷贝的数据量(copy)大于用户传入的数据量那么就可以一次性全部拷贝完if (copy msg_data_left(msg))copy msg_data_left(msg);//如果skb的线性部分还有空间先填充这部分if (skb_availroom(skb) 0) {/* We have some space in skb head. Superb! *///如果线性空间部分小于当前要拷贝的数据量则调整本轮要拷贝的数据量copy min_t(int, copy, skb_availroom(skb));//拷贝数据如果出错则结束发送过程err skb_add_data_nocache(sk, skb, msg-msg_iter, copy);if (err)goto do_fault;} else {//merge用于指示是否可以将新拷贝的数据和当前skb的最后一个片段合并。如果//它们在页面内刚好是连续的那么就可以合并为一个片段bool merge true;//i为当前skb中已经存在的分片个数int i skb_shinfo(skb)-nr_frags;//page指向上一次分配的页面off指向该页面中的偏移量struct page_frag *pfrag sk_page_frag(sk);if (!sk_page_frag_refill(sk, pfrag))goto wait_for_memory;//该函数用于判断该skb最后一个片段是否就是当前页面的最后一部分如果是那么新拷贝的//数据和该片段就可以合并所以设置merge为1这样可以节省一个frag_list[]位置if (!skb_can_coalesce(skb, i, pfrag-page,pfrag-offset)) {if (i sysctl_max_skb_frags || !sg) {tcp_mark_push(tp, skb);goto new_segment;}merge false;}copy min_t(int, copy, pfrag-size - pfrag-offset);if (!sk_wmem_schedule(sk, copy))goto wait_for_memory;//拷贝copy字节数据到页面中err skb_copy_to_page_nocache(sk, msg-msg_iter, skb,pfrag-page,pfrag-offset,copy);if (err)goto do_error;//更新skb中相关指针、计数信息if (merge) {skb_frag_size_add(skb_shinfo(skb)-frags[i - 1], copy);} else {skb_fill_page_desc(skb, i, pfrag-page,pfrag-offset, copy);get_page(pfrag-page);}pfrag-offset copy;}//如果本轮是第一次拷贝清除PUSH标记if (!copied)TCP_SKB_CB(skb)-tcp_flags ~TCPHDR_PSH;//write_seq记录的是发送队列中下一个要分配的序号所以这里需要更新它tp-write_seq copy;//更新该数据包的最后一个字节的序号TCP_SKB_CB(skb)-end_seq copy;tcp_skb_pcount_set(skb, 0);//累加已经拷贝字节数copied copy;//如果所有要发送的数据都拷贝完了并且设置了MSG_EOR结束发送过程if (!msg_data_left(msg)) {if (unlikely(flags MSG_EOR))TCP_SKB_CB(skb)-eor 1;goto out;}//如果该skb没有填满继续下一轮拷贝if (skb-len max || (flags MSG_OOB) || unlikely(tp-repair))continue;//如果需要设置PUSH标志位那么设置PUSH然后发送数据包可将PUSH可以让TCP尽快的发送数据if (forced_push(tp)) {tcp_mark_push(tp, skb);//尽可能的将发送队列中的skb发送出去禁用nalge__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH);} else if (skb tcp_send_head(sk))//当前只有这一个skb也发送出去。因为只有一个所以肯定也不存在拥塞可以发送tcp_push_one(sk, mss_now);continue;wait_for_sndbuf://设置套接字结构中发送缓存不足的标志set_bit(SOCK_NOSPACE, sk-sk_socket-flags);
wait_for_memory://如果已经有数据拷贝到了发送缓存中那么调用tcp_push()立即发送这样可能可以//让发送缓存快速的有剩余空间可用if (copied)tcp_push(sk, flags ~MSG_MORE, mss_now,TCP_NAGLE_PUSH, size_goal);//等待有空余内存可以使用如果timeo不为0那么这一步会休眠err sk_stream_wait_memory(sk, timeo);if (err ! 0)goto do_error;//睡眠后MSS可能发生了变化所以重新计算mss_now tcp_send_mss(sk, size_goal, flags);}out://如果拷贝了数据到发送缓存区尝试进行一次发送if (copied) {tcp_tx_timestamp(sk, sockc.tsflags, tcp_write_queue_tail(sk));tcp_push(sk, flags, mss_now, tp-nonagle, size_goal);}
out_nopush:release_sock(sk);//返回本次写入的数据量return copied copied_syn;do_fault://发生了错误并且当前skb尚未包含任何数据那么需要释放该skbif (!skb-len) {tcp_unlink_write_queue(skb, sk);tcp_check_send_head(sk, skb);sk_wmem_free_skb(sk, skb);}do_error:if (copied copied_syn)goto out;
out_err:err sk_stream_error(sk, flags, err);if (unlikely(skb_queue_len(sk-sk_write_queue) 0 err -EAGAIN))sk-sk_write_space(sk);release_sock(sk);return err;
}tcp_sendmsg主要做了以下几件事 1.判断套接字状态 2.将用户数据拷贝到skb中优先考虑报文的线性区然后是分页区必要时需要使用新skb或者新分页来存放用户数据 3.根据具体的情况调用__tcp_push_pending_frames(sk, mss_now, TCP_NAGLE_PUSH)(禁用了nagle算法可以发送多个skb)或者tcp_push_one(sk, mss_now)(使用nagle算法只能发送一个skb)发送数据
无论是调用上面的哪个函数发送数据但最终都会调用到tcp_write_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;u32 max_segs;//sent_pkts用来统计函数中已发送报文总数sent_pkts 0;//检查是不是只发送一个skb buffer即push oneif (!push_one) {//执行MTU探测result tcp_mtu_probe(sk);if (!result) {return false;} else if (result 0) {sent_pkts 1;}}max_segs tcp_tso_segs(sk, mss_now);// 计算最大可发送的段数while ((skb tcp_send_head(sk))) {// 遍历发送队列unsigned int limit;/* 设置有关TSO的信息包括GSO类型GSO分段的大小等等。* 这些信息是准备给软件TSO分段使用的。* 如果网络设备不支持TSO但又使用了TSO功能* 则报文在提交给网络设备之前需进行软分段即由代码实现TSO分段。*/tso_segs tcp_init_tso_segs(skb, mss_now);BUG_ON(!tso_segs);if (unlikely(tp-repair) tp-repair_queue TCP_SEND_QUEUE) {skb_mstamp_get(skb-skb_mstamp);goto repair; }/* 检查congestion windows 可以发送几个segment *//* 检测拥塞窗口的大小如果为0则说明拥塞窗口已满目前不能发送。* 拿拥塞窗口和正在网络上传输的包数目相比如果拥塞窗口还大* 则返回拥塞窗口减掉正在网络上传输的包数目剩下的大小。* 该函数目的是判断正在网络上传输的包数目是否超过拥塞窗口* 如果超过了则不发送。*/cwnd_quota tcp_cwnd_test(tp, skb);if (!cwnd_quota) { //push_one为2表示需要强制发送此时就设置窗口大小为1表示可以发送一个数据包if (push_one 2)cwnd_quota 1;elsebreak;}//检测当前报文是否完全处于发送窗口内如果是则可以发送否则不能发送if (unlikely(!tcp_snd_wnd_test(tp, skb, mss_now)))break;//tso_segs1表示无需tso分段 if (tso_segs 1) {//根据nagle算法计算是否需要发送数据if (unlikely(!tcp_nagle_test(tp, skb, mss_now,(tcp_skb_is_last(sk, skb) ?nonagle : TCP_NAGLE_PUSH))))break;} else {/* 当不止一个skb时通过TSO计算是否需要延时发送 *//* 如果需要TSO分段则检测该报文是否应该延时发送。* tcp_tso_should_defer()用来检测GSO段是否需要延时发送。* 在段中有FIN标志或者不处于open拥塞状态或者TSO段延时超过2个时钟滴答* 或者拥塞窗口和发送窗口的最小值大于64K或三倍的当前有效MSS在这些情况下会立即发送* 而其他情况下会延时发送这样主要是为了减少软GSO分段的次数以提高性能。*/if (!push_one tcp_tso_should_defer(sk, skb, is_cwnd_limited,max_segs))break;}limit mss_now;/* 在TSO分片大于1的情况下且TCP不是URG模式。通过MSS计算发送数据的limit* 以发送窗口和拥塞窗口的最小值作为分段段长*/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);/* 当skb的长度大于限制时需要调用tso_fragment分片,如果分段失败则暂不发送 */if (skb-len limit unlikely(tso_fragment(sk, skb, limit, mss_now, gfp)))break;//检查当前TCP发送队列的状态//它可能会考虑队列的长度、当前的网络条件、拥塞窗口的大小以及其他相关因素//以确定是否应该继续发送数据或采取其他行动如延迟发送。这个函数的主要目的是避免队列过度拥塞从而保持网络传输的稳定性和效率。if (tcp_small_queue_check(sk, skb, 0))break;//调用tcp_transmit_skb()发送TCP段其中第三个参数1表示是否需要克隆被发送的报文if (unlikely(tcp_transmit_skb(sk, skb, 1, gfp)))break;repair:/* 更新统计并启动重传计时器 *//* 调用tcp_event_new_data_sent()--tcp_advance_send_head()更新sk_send_head* 即取发送队列中的下一个SKB。同时更新snd_nxt即等待发送的下一个TCP段的序号* 然后统计发出但未得到确认的数据报个数。最后如果发送该报文前没有需要确认的报文* 则复位重传定时器对本次发送的报文做重传超时计时。*/tcp_event_new_data_sent(sk, skb);/* 更新struct tcp_sock中的snd_sml字段。snd_sml表示最近发送的小包(小于MSS的段)的最后一个字节序号* 在发送成功后如果报文小于MSS即更新该字段主要用来判断是否启动nagle算法*/tcp_minshall_update(tp, mss_now, skb);sent_pkts tcp_skb_pcount(skb);if (push_one)break;}/* 如果本次有数据发送则对TCP拥塞窗口进行检查确认。*/if (likely(sent_pkts)) {if (tcp_in_cwnd_reduction(sk))tp-prr_out sent_pkts;/* Send one loss probe per tail loss episode. */if (push_one ! 2)//丢包检测tcp_schedule_loss_probe(sk);//更新拥塞控制状态is_cwnd_limited | (tcp_packets_in_flight(tp) tp-snd_cwnd);//验证拥塞窗口tcp_cwnd_validate(sk, is_cwnd_limited);return false;}/* * 如果本次没有数据发送则根据已发送但未确认的报文数packets_out和sk_send_head返回* packets_out不为零或sk_send_head为空都视为有数据发出因此返回成功。*/return !tp-packets_out tcp_send_head(sk);
}接着会调用tcp_transmit_skb函数填充tcp头部
static int tcp_transmit_skb(struct sock *sk, struct sk_buff *skb, int clone_it,gfp_t gfp_mask)
{return __tcp_transmit_skb(sk, skb, clone_it, gfp_mask,tcp_sk(sk)-rcv_nxt);
}static int __tcp_transmit_skb(struct sock *sk, struct sk_buff *skb,int clone_it, gfp_t gfp_mask, u32 rcv_nxt)
{const struct inet_connection_sock *icsk inet_csk(sk);struct inet_sock *inet;struct tcp_sock *tp;struct tcp_skb_cb *tcb;struct tcp_out_options opts;unsigned int tcp_options_size, tcp_header_size;struct sk_buff *oskb NULL;struct tcp_md5sig_key *md5;struct tcphdr *th;int err;BUG_ON(!skb || !tcp_skb_pcount(skb));tp tcp_sk(sk);//根据传递进来的clone_it参数来确定是否需要克隆待发送的报文if (clone_it) {TCP_SKB_CB(skb)-tx.in_flight TCP_SKB_CB(skb)-end_seq- tp-snd_una;oskb skb;//如果skb已经被clone则只能复制该skb的数据到新分配的skb中if (unlikely(skb_cloned(skb)))skb pskb_copy(skb, gfp_mask);else//clone新的skbskb skb_clone(skb, gfp_mask);if (unlikely(!skb))return -ENOBUFS;}skb_mstamp_get(skb-skb_mstamp);//获取INET层和TCP层的传输控制块、skb中的TCP私有数据块inet inet_sk(sk);tcb TCP_SKB_CB(skb);memset(opts, 0, sizeof(opts));/*根据TCP选项重新调整TCP首部的长度。*//*判断当前TCP报文是否是SYN段因为有些选项只能出现在SYN报文中需做特别处理。*/if (unlikely(tcb-tcp_flags TCPHDR_SYN))tcp_options_size tcp_syn_options(sk, skb, opts, md5);elsetcp_options_size tcp_established_options(sk, skb, opts,md5);/*tcp首部的总长度等于可选长度加上TCP头部。*/tcp_header_size tcp_options_size sizeof(struct tcphdr);skb-ooo_okay sk_wmem_alloc_get(sk) SKB_TRUESIZE(1);/*调用skb_push()在数据部分的头部添加TCP首部长度即为之前计算得到的那个tcp_header_size实际上是把data指针往上移。*/skb_push(skb, tcp_header_size);skb_reset_transport_header(skb);skb_orphan(skb);// 将skb和sock关联起来并设置skb的析构函数skb-sk sk;skb-destructor skb_is_tcp_pure_ack(skb) ? __sock_wfree : tcp_wfree;// 从sock中设定skb的哈希值skb_set_hash_from_sk(skb, sk);// 增加skb占用的内存大小计数atomic_add(skb-truesize, sk-sk_wmem_alloc);//填充TCP首部中的源端口source、目的端口dest、TCP报文的序号seq、确认序号ack_seq以及各个标志位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);*(((__be16 *)th) 6) htons(((tcp_header_size 2) 12) |tcb-tcp_flags);th-check 0;th-urg_ptr 0;// 如果当前包含紧急指针的包在snd_una探测窗口之下// 则需要设置紧急指针。if (unlikely(tcp_urg_mode(tp) before(tcb-seq, tp-snd_up))) {if (before(tp-snd_up, tcb-seq 0x10000)) {th-urg_ptr htons(tp-snd_up - tcb-seq);th-urg 1;} else if (after(tcb-seq 0xFFFF, tp-snd_nxt)) {th-urg_ptr htons(0xFFFF);th-urg 1;}}//构建TCP选项例如窗口大小时间戳等选项tcp_options_write((__be32 *)(th 1), tp, opts);// 设置skb的GSO类型skb_shinfo(skb)-gso_type sk-sk_gso_type;//分两种情况设置TCP首部的接收窗口的大小if (likely(!(tcb-tcp_flags TCPHDR_SYN))) {//如果不是SYN报文则调用tcp_select_window()计算当前接收窗口的大小th-window htons(tcp_select_window(sk));tcp_ecn_send(sk, skb, th, tcp_header_size);} else {//如果是SYN段则设置接收窗口初始值为rcv_wndth-window htons(min(tp-rcv_wnd, 65535U));}
#ifdef CONFIG_TCP_MD5SIG// 如果启用了MD5签名计算MD5哈希因为我们现在有了所需的全部数据if (md5) {sk_nocaps_add(sk, NETIF_F_GSO_MASK);tp-af_specific-calc_md5_hash(opts.hash_location,md5, sk, skb);}
#endif// 由底层网络函数完成skb的校验和icsk-icsk_af_ops-send_check(sk, skb);//如果skb中设置了TCPHDR_ACK标志则记录ACK已发送的事件if (likely(tcb-tcp_flags TCPHDR_ACK))tcp_event_ack_sent(sk, tcp_skb_pcount(skb), rcv_nxt);// 如果skb的长度不只是TCP头部的长度表示有数据被发送// 更新统计信息。if (skb-len ! tcp_header_size) {tcp_event_data_sent(tp, sk);tp-data_segs_out tcp_skb_pcount(skb);}// 如果当前数据段的结束序列号在snd_nxt之后或与之相等// 或者如果是一个单独的序列号更新发送的数据包统计。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);// 设置skb中GSO分段卸载相关的字段skb_shinfo(skb)-gso_segs tcp_skb_pcount(skb);skb_shinfo(skb)-gso_size tcp_skb_mss(skb);// 我们使用的时间戳应保持私有。skb-tstamp.tv64 0;// 清理我们对IP栈的痕迹重置skb的控制块。memset(skb-cb, 0, max(sizeof(struct inet_skb_parm),sizeof(struct inet6_skb_parm)));//调用发送接口queue_xmit发送报文进入到ip层如果失败返回错误码。在TCP中该接口实现函数为ip_queue_xmit()err icsk-icsk_af_ops-queue_xmit(sk, skb, inet-cork.fl);// 如果出现错误调用tcp_enter_cwr函数进入拥塞窗口减少状态并评估错误。if (unlikely(err 0)) {tcp_enter_cwr(sk);err net_xmit_eval(err);}// 如果发送成功并且有原始的skb更新相关统计数据。if (!err oskb) {skb_mstamp_get(oskb-skb_mstamp);tcp_rate_skb_sent(sk, oskb);}return err;
}5、ip层进行数据传输
icsk-icsk_af_ops-queue_xmit(sk, skb, inet-cork.fl)函数就会调用到ip_output.c中的ip_queue_xmit函数主要就是获取路由填充ip头部信息
int ip_queue_xmit(struct sock *sk, struct sk_buff *skb, struct flowi *fl)
{struct inet_sock *inet inet_sk(sk);struct net *net sock_net(sk);struct ip_options_rcu *inet_opt;struct flowi4 *fl4; //这个结构体帮助内核处理IPv4网络数据包包括路由选择、策略路由、流量控制等struct rtable *rt; //这个结构体包含了关于特定路由条目的各种信息例如目标地址、下一跳网关、网络接口等struct iphdr *iph;int res;rcu_read_lock();inet_opt rcu_dereference(inet-inet_opt);fl4 fl-u.ip4;//如果还没有查询过路由那么就先查询路由。对于TCP大多数情况下都已经查询过了//先从skb中查找路由信息rt skb_rtable(skb);if (rt)goto packet_routed;//路由和套接字是关联的一般来讲一旦查询后目的地址不发生变化路由查询结果//不会有变化所以往往会将路由查询结果缓存到sk中上面发现skb-dst中没有设置//再检查sk中缓存的路由信息是否依然有效如果也无效那么向路由子系统发起查询rt (struct rtable *)__sk_dst_check(sk, 0);if (!rt) {//如果路由缓存项过期则重新通过输出网络设备dev目的地址源地址等信息查找输出路由缓存项。//如果查找到对应的路由缓存项则将其缓存到输出控制块中否则丢弃该数据包__be32 daddr;//如果有源路由选项在查路由之前替换下目的地址daddr inet-inet_daddr;if (inet_opt inet_opt-opt.srr)daddr inet_opt-opt.faddr;//根据这些参数查找路由信息rt ip_route_output_ports(net, fl4, sk,daddr, inet-inet_saddr,inet-inet_dport,inet-inet_sport,sk-sk_protocol,RT_CONN_FLAGS(sk),sk-sk_bound_dev_if);if (IS_ERR(rt))goto no_route;sk_setup_caps(sk, rt-dst);}skb_dst_set_noref(skb, rt-dst);//如果没有过期则使用缓存再传输控制块中的路由缓存项packet_routed://查找到输出路由以后先进行严格源路由选项的处理如果存在严格源路由选项并且路由使用网关地址则丢弃if (inet_opt inet_opt-opt.is_strictroute rt-rt_uses_gateway)goto no_route;skb_push(skb, sizeof(struct iphdr) (inet_opt ? inet_opt-opt.optlen : 0));//现在只要要往哪里发送了申请并创建IP头部skb_reset_network_header(skb); //重新设置网络层头部指针skb-network_headeriph ip_hdr(skb);*((__be16 *)iph) htons((4 12) | (5 8) | (inet-tos 0xff));if (ip_dont_fragment(sk, rt-dst) !skb-ignore_df)//如果有设置不需要分片设置DF标记位,否则初始化frag_off为0iph-frag_off htons(IP_DF);elseiph-frag_off 0;iph-ttl ip_select_ttl(inet, rt-dst);//设置IP头部的TTLiph-protocol sk-sk_protocol;//设置IP头部的协议//设置源IP和目的IPip_copy_addrs(iph, fl4);/* Transport layer set skb-h.foo itself. *///如果有选项则需要给IP头部添加选项部分if (inet_opt inet_opt-opt.optlen) {iph-ihl inet_opt-opt.optlen 2;//重新调整了IP首部长度加上了选项部分的长度ip_options_build(skb, inet_opt-opt, inet-inet_daddr, rt, 0);}ip_select_ident_segs(net, skb, sk,skb_shinfo(skb)-gso_segs ?: 1);skb-priority sk-sk_priority;skb-mark sk-sk_mark;//对于单播包使用的是ip_output,多播使用的是ip_mc_outputres ip_local_out(net, sk, skb);rcu_read_unlock();return res;no_route:rcu_read_unlock();IP_INC_STATS(net, IPSTATS_MIB_OUTNOROUTES);kfree_skb(skb);return -EHOSTUNREACH;
}接着会调用ip_local_out函数
int ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{int err;err __ip_local_out(net, sk, skb);if (likely(err 1))err dst_output(net, sk, skb);return err;
}在ip_local_out内又会调用__ip_local_out或者dst_output如果调用了__ip_local_out在它内部还是会调用到dst_output函数
int __ip_local_out(struct net *net, struct sock *sk, struct sk_buff *skb)
{// 获取指向sk_buff中IP报头的指针struct iphdr *iph ip_hdr(skb);// 设置IP报头中的总长度字段为skb的长度htons用于将主机字节顺序转换为网络字节顺序iph-tot_len htons(skb-len);// 计算和填充IP报头的校验和ip_send_check(iph);/* 如果出口设备属于一个L3主设备就将skb传递给它的处理函数* l3mdev_ip_out负责处理skb可能进行一些特定于该设备的处理*/skb l3mdev_ip_out(sk, skb);// 如果skb为空说明处理不成功返回0if (unlikely(!skb))return 0;// 设置skb的协议字段为IP协议htons用于将主机字节顺序转换为网络字节顺序skb-protocol htons(ETH_P_IP);// 调用netfilter钩子以便进行进一步的处理例如过滤NAT等// nf_hook会根据配置决定是否处理skb或将其传递给下一个处理阶段return nf_hook(NFPROTO_IPV4, NF_INET_LOCAL_OUT,net, sk, skb, NULL, skb_dst(skb)-dev,dst_output);
}static inline int dst_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{return skb_dst(skb)-output(net, sk, skb);
}这里会调用到output函数这里的output就是ip_output函数
int ip_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{//获取网络设备struct net_device *dev skb_dst(skb)-dev;//更新输出的统计信息IP_UPD_PO_STATS(net, IPSTATS_MIB_OUT, skb-len);//设置网络设备和协议skb-dev dev;skb-protocol htons(ETH_P_IP);//如果 Netfilter 钩子函数没有返回 NF_DROP表示丢弃数据包//那么 ip_finish_output 函数最终会被调用以完成数据包的发送。return NF_HOOK_COND(NFPROTO_IPV4, NF_INET_POST_ROUTING,net, sk, skb, NULL, dev,ip_finish_output,!(IPCB(skb)-flags IPSKB_REROUTED));
}紧接着会调用ip_finish_output
static int ip_finish_output(struct net *net, struct sock *sk, struct sk_buff *skb)
{unsigned int mtu;#if defined(CONFIG_NETFILTER) defined(CONFIG_XFRM)/* Policy lookup after SNAT yielded a new policy */if (skb_dst(skb)-xfrm) {IPCB(skb)-flags | IPSKB_REROUTED;return dst_output(net, sk, skb);}
#endif//获取数据包的目的地 MTUmtu ip_skb_dst_mtu(sk, skb);//如果数据包是巨型帧就使用ip_finish_output_gso来处理if (skb_is_gso(skb))return ip_finish_output_gso(net, sk, skb, mtu);//skb的长度大于对端的mtu或者设置了IPSKB_FRAG_PMTU就会调用ip_fragment进行分片//分完片后再调用ip_finish_output2if (skb-len mtu || (IPCB(skb)-flags IPSKB_FRAG_PMTU))return ip_fragment(net, sk, skb, mtu, ip_finish_output2);return ip_finish_output2(net, sk, skb);
} 然后会调用ip_finish_output2函数
static int ip_finish_output2(struct net *net, struct sock *sk, struct sk_buff *skb)
{struct dst_entry *dst skb_dst(skb);struct rtable *rt (struct rtable *)dst;struct net_device *dev dst-dev;unsigned int hh_len LL_RESERVED_SPACE(dev);struct neighbour *neigh;u32 nexthop;//如果与此数据包关联的路由是多播类型则使用 IP_UPD_PO_STATS 宏来增加 OutMcastPkts 和 OutMcastOctets 计数if (rt-rt_type RTN_MULTICAST) {IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTMCAST, skb-len);} else if (rt-rt_type RTN_BROADCAST)//如果广播路由则会增加 OutBcastPkts 和 OutBcastOctets 计数。IP_UPD_PO_STATS(net, IPSTATS_MIB_OUTBCAST, skb-len);//确保 skb 结构有足够的空间容纳需要添加的任何链路层头if (unlikely(skb_headroom(skb) hh_len dev-header_ops)) {struct sk_buff *skb2;//确保 skb 结构有足够的空间容纳需要添加的任何链路层头skb2 skb_realloc_headroom(skb, LL_RESERVED_SPACE(dev));if (!skb2) {kfree_skb(skb);return -ENOMEM;}if (skb-sk)skb_set_owner_w(skb2, skb-sk);consume_skb(skb);skb skb2;}if (lwtunnel_xmit_redirect(dst-lwtstate)) {int res lwtunnel_xmit(skb);if (res 0 || res LWTUNNEL_XMIT_DONE)return res;}rcu_read_lock_bh();//查询路由层找到下一跳的ip地址nexthop (__force u32) rt_nexthop(rt, ip_hdr(skb)-daddr);//再根据下一跳的ip地址查找邻居缓存neigh __ipv4_neigh_lookup_noref(dev, nexthop);if (unlikely(!neigh))//如果未找到则调用__neigh_create 创建一个邻居内部就是ARP相关的操作neigh __neigh_create(arp_tbl, nexthop, dev, false);if (!IS_ERR(neigh)) {//调用 dst_neigh_output 继续传递 skbint res dst_neigh_output(dst, neigh, skb);rcu_read_unlock_bh();return res;}rcu_read_unlock_bh();net_dbg_ratelimited(%s: No header cache and no neighbour!\n,__func__);kfree_skb(skb);return -EINVAL;
}紧接着会调用到dst_neigh_output函数
static inline int dst_neigh_output(struct dst_entry *dst, struct neighbour *n,struct sk_buff *skb)
{const struct hh_cache *hh;//如果路由条目 dst 有一个待确认的标志则将其清除并更新邻居 n 的确认时间戳if (dst-pending_confirm) {unsigned long now jiffies;dst-pending_confirm 0;if (n-confirmed ! now)n-confirmed now;}//获取下一跳ip地址所对应的硬件头信息(内部包含mac地址)hh n-hh;//如果邻居处于已连接状态NUD_CONNECTED并且硬件头缓存中有有效的数据则调用neigh_hh_output发送数据包if ((n-nud_state NUD_CONNECTED) hh-hh_len)return neigh_hh_output(hh, skb);else //否则调用邻居条目 n 中的 output 方法来发送数据包return n-output(n, skb);
}以上两种情况最后都会到 dev_queue_xmit它将 skb 发送给 Linux 网络设备子系统在它进入设备驱动程序层之前将对其进行更多处理。让我们沿着 neigh_hh_output 和 n-output 代码继续向下直到达到 dev_queue_xmit
neigh_hh_output函数
static inline int neigh_hh_output(const struct hh_cache *hh, struct sk_buff *skb)
{unsigned int seq;int hh_len;//填充mac地址do {seq read_seqbegin(hh-hh_lock);hh_len hh-hh_len;if (likely(hh_len HH_DATA_MOD)) {memcpy(skb-data - HH_DATA_MOD, hh-hh_data, HH_DATA_MOD);} else {int hh_alen HH_DATA_ALIGN(hh_len);memcpy(skb-data - hh_alen, hh-hh_data, hh_alen);}} while (read_seqretry(hh-hh_lock, seq));//更新 skb 内指向数据缓冲区的指针和数据长度skb_push(skb, hh_len);//调用 dev_queue_xmit 将 skb 传递给 Linux 网络设备子系统return dev_queue_xmit(skb);
}如果邻居已经关闭则会调用n-output也就是struct neighbour中的output这里的output就是neigh_ops中的output而neigh_ops的初始化又是在net/ipv4/arp.c中
static const struct neigh_ops arp_generic_ops {.family AF_INET,.solicit arp_solicit,.error_report arp_error_report,.output neigh_resolve_output,.connected_output neigh_connected_output,
};最终调用的是neigh_resolve_output
int neigh_resolve_output(struct neighbour *neigh, struct sk_buff *skb)
{int rc 0;//neigh_event_send() 函数发送一个查询邻居节点 MAC 地址的 ARP 请求//在这个函数内部会把skb添加到邻居节点信息对象的 arp_queue 队列中等待获取到邻居节点 MAC 地址后重新发送这个数据包if (!neigh_event_send(neigh, skb)) {int err;struct net_device *dev neigh-dev;unsigned int seq;// 网络设备可以使用L2帧头缓存(dev-header_ops-cache)但是还没有建立缓存(dst-hh)if (dev-header_ops-cache !neigh-hh.hh_len)neigh_hh_init(neigh);do {__skb_pull(skb, skb_network_offset(skb));seq read_seqbegin(neigh-ha_lock);//设置数据包的目标 MAC 地址err dev_hard_header(skb, dev, ntohs(skb-protocol),neigh-ha, NULL, skb-len);} while (read_seqretry(neigh-ha_lock, seq));// 首部构造成功输出数据包if (err 0)rc dev_queue_xmit(skb);elsegoto out_kfree_skb;}
out:return rc;
out_kfree_skb:rc -EINVAL;kfree_skb(skb);goto out;
}6、网络设备层进行数据传输
如果上述过程一次顺利那么就会调用到网络设备层的dev_queue_xmit将数据做进一步处理
int dev_queue_xmit(struct sk_buff *skb)
{return __dev_queue_xmit(skb, NULL);
}static int __dev_queue_xmit(struct sk_buff *skb, void *accel_priv)
{struct net_device *dev skb-dev;//struct netdev_queue用于表示一个网络设备的发送队列struct netdev_queue *txq;//struct Qdisc负责决定如何排队和发送数据包struct Qdisc *q;int rc -ENOMEM;//设置mac的头部偏移skb_reset_mac_header(skb);if (unlikely(skb_shinfo(skb)-tx_flags SKBTX_SCHED_TSTAMP))__skb_tstamp_tx(skb, NULL, skb-sk, SCM_TSTAMP_SCHED);rcu_read_lock_bh();skb_update_prio(skb);qdisc_pkt_len_init(skb);
#ifdef CONFIG_NET_CLS_ACTskb-tc_verd SET_TC_AT(skb-tc_verd, AT_EGRESS);
# ifdef CONFIG_NET_EGRESSif (static_key_false(egress_needed)) {skb sch_handle_egress(skb, rc, dev);if (!skb)goto out;}
# endif
#endif//现在已经处于mac层不需要对应的路由信息因此可以将路由信息删除if (dev-priv_flags IFF_XMIT_DST_RELEASE)skb_dst_drop(skb);elseskb_dst_force(skb);//获取网络设备的队列txq netdev_pick_tx(dev, skb, accel_priv);//从netdev_queue结构上取下设备的qdisc q rcu_dereference_bh(txq-qdisc);trace_net_dev_queue(skb);/*如果Qdisc有对应的enqueue规则就会调用__dev_xmit_skb进入带有拥塞的控制的Flow注意这个地方虽然是走拥塞控制的*Flow但是并不一定非得进行enqueue操作只有Busy的状况下才会走Qdisc的enqueue/dequeue操作进行*/if (q-enqueue) {rc __dev_xmit_skb(skb, q, dev, txq);goto out;}//此处是设备没有Qdisc的实际上没有enqueue/dequeue的规则无法进行拥塞控制的操作则直接发送if (dev-flags IFF_UP) {// 当前CPU编号int cpu smp_processor_id(); /* ok because BHs are off */if (txq-xmit_lock_owner ! cpu) {if (unlikely(__this_cpu_read(xmit_recursion) XMIT_RECURSION_LIMIT))goto recursion_alert;skb validate_xmit_skb(skb, dev);if (!skb)goto out;HARD_TX_LOCK(dev, txq, cpu);//这个地方判断一下txq不是stop状态那么就直接调用dev_hard_start_xmit函数来发送数据if (!netif_xmit_stopped(txq)) {__this_cpu_inc(xmit_recursion);skb dev_hard_start_xmit(skb, dev, txq, rc);__this_cpu_dec(xmit_recursion);// 如果发送完成就解锁if (dev_xmit_complete(rc)) {HARD_TX_UNLOCK(dev, txq);goto out;}}HARD_TX_UNLOCK(dev, txq);net_crit_ratelimited(Virtual device %s asks to queue packet!\n,dev-name);} else {
recursion_alert:net_crit_ratelimited(Dead loop on virtual device %s, fix it urgently!\n,dev-name);}}rc -ENETDOWN;rcu_read_unlock_bh();atomic_long_inc(dev-tx_dropped);kfree_skb_list(skb);return rc;
out:rcu_read_unlock_bh();return rc;
}先检查是否有enqueue的规则如果有即调用__dev_xmit_skb进入拥塞控制的flow如果没有且txq处于On的状态那么就调用dev_hard_start_xmit直接发送到driver好 那先分析带Qdisc策略的flow 进入__dev_xmit_skb
static inline int __dev_xmit_skb(struct sk_buff *skb, struct Qdisc *q,struct net_device *dev,struct netdev_queue *txq)
{spinlock_t *root_lock qdisc_lock(q);struct sk_buff *to_free NULL;bool contended;int rc;qdisc_calculate_pkt_len(skb, q);/** Heuristic to force contended enqueues to serialize on a* separate lock before trying to get qdisc main lock.* This permits qdisc-running owner to get the lock more* often and dequeue packets faster.*/contended qdisc_is_running(q);if (unlikely(contended))spin_lock(q-busylock);spin_lock(root_lock);//主要是判定Qdisc的state: __QDISC_STATE_DEACTIVATED,如果处于非活动的状态就DROP这个包返回NET_XMIT_DROPif (unlikely(test_bit(__QDISC_STATE_DEACTIVATED, q-state))) {__qdisc_drop(skb, to_free);rc NET_XMIT_DROP;//(q-flags TCQ_F_CAN_BYPASS)表示qdisc允许数据包绕过排队系统//!qdisc_qlen(q)表示qdisc的队列中没有等待发送的数据//qdisc_run_begin(q)判断队列是否是运行状态如果是返回true否则将状态设置为运行状态然后返回false//网络没有拥塞} else if ((q-flags TCQ_F_CAN_BYPASS) !qdisc_qlen(q) qdisc_run_begin(q)) {/** This is a work-conserving queue; there are no old skbs* waiting to be sent out; and the qdisc is not running -* xmit the skb directly.*///增加qdisc发送的字节数和数据包数qdisc_bstats_update(q, skb);//发送数据包if (sch_direct_xmit(skb, q, dev, txq, root_lock, true)) {if (unlikely(contended)) {spin_unlock(q-busylock);contended false;}//如果发送数据包成功继续发生__qdisc_run(q);//如果发送数据包失败那么会调用qdisc_run_end将队列的状态设置为停止状态} elseqdisc_run_end(q);rc NET_XMIT_SUCCESS;//网络拥塞} else {//将数据包加入队列rc q-enqueue(skb, q, to_free) NET_XMIT_MASK;//如果Qdisc q不是运行状态就设置成运行状态if (qdisc_run_begin(q)) {if (unlikely(contended)) {spin_unlock(q-busylock);contended false;}__qdisc_run(q);}}spin_unlock(root_lock);if (unlikely(to_free))kfree_skb_list(to_free);if (unlikely(contended))spin_unlock(q-busylock);return rc;
}如果发送了网络拥塞则会将数据放到设备的发送队列中如果没有发生网络拥塞那么就会调用sch_direct_xmit函数发生数据
下面来分析sch_direct_xmit这个函数可能传输几个数据包因为在不经过queue状况下和经过queue的状况下都会调通过这个函数发送如果是queue状况肯定是能够传输多个数据包了
int sch_direct_xmit(struct sk_buff *skb, struct Qdisc *q,struct net_device *dev, struct netdev_queue *txq,spinlock_t *root_lock, bool validate)
{int ret NETDEV_TX_BUSY;/* And release qdisc *///调用该函数时队列策略的队列锁已经被锁了现在解锁spin_unlock(root_lock);if (validate)skb validate_xmit_skb_list(skb, dev);//如果这个skb有效if (likely(skb)) {//取得发送队列的锁HARD_TX_LOCK(dev, txq, smp_processor_id());//如果发送队列已经开启if (!netif_xmit_frozen_or_stopped(txq))/*如果说txq被stop即置位QUEUE_STATE_ANY_XOFF_OR_FROZEN就直接ret NETDEV_TX_BUSY*如果说txq 正常运行那么直接调用dev_hard_start_xmit发送数据包*/skb dev_hard_start_xmit(skb, dev, txq, ret);HARD_TX_UNLOCK(dev, txq);} else {spin_lock(root_lock);return qdisc_qlen(q);}spin_lock(root_lock);//进行返回值处理 如果ret NET_XMIT_MASK 为true 否则 flaseif (dev_xmit_complete(ret)) {//这个地方需要注意可能有driver的负数的case也意味着这个skb被drop了ret qdisc_qlen(q);} else {if (unlikely(ret ! NETDEV_TX_BUSY))net_warn_ratelimited(BUG %s code %d qlen %d\n,dev-name, ret, q-q.qlen);//发生Tx Busy的时候重新进行requeuret dev_requeue_skb(skb, q);}//如果txq stop并且ret 0 说明已经无法发送数据包了ret 0if (ret netif_xmit_frozen_or_stopped(txq))ret 0;return ret;
}如果前面调用dev_hard_start_xmit发送数据失败则会调用dev_requeue_skb把skb放到发送队列中并设置对应的软中断当网卡不忙时就会触发然后再次发送数据
继续看dev_hard_start_xmit这个函数比较简单调用xmit_one来发送一个到多个数据包了
struct sk_buff *dev_hard_start_xmit(struct sk_buff *first, struct net_device *dev,struct netdev_queue *txq, int *ret)
{struct sk_buff *skb first;int rc NETDEV_TX_OK;while (skb) {struct sk_buff *next skb-next;skb-next NULL;//将此数据包送到driver Tx函数因为dequeue的数据也会从这里发送所以会有netxrc xmit_one(skb, dev, txq, next ! NULL);//如果发送不成功next还原到skb-next 退出if (unlikely(!dev_xmit_complete(rc))) {skb-next next;goto out;}/*如果发送成功把next置给skb一般的next为空 这样就返回如果不为空就继续发*/skb next;//如果txq被stop并且skb需要发送就产生TX Busy的问题if (netif_xmit_stopped(txq) skb) {rc NETDEV_TX_BUSY;break;}}out:*ret rc;return skb;
}对于xmit_one这个来讲比较简单了下面代码中列出了xmit_one, netdev_start_xmit,__netdev_start_xmit 这个三个函数其目的就是将封包送到driver的tx函数了
static int xmit_one(struct sk_buff *skb, struct net_device *dev,struct netdev_queue *txq, bool more)
{unsigned int len;int rc;/*如果有抓包的工具的话这个地方会进行抓包such as Tcpdump*/if (!list_empty(ptype_all) || !list_empty(dev-ptype_all))dev_queue_xmit_nit(skb, dev);len skb-len;trace_net_dev_start_xmit(skb, dev);/*调用netdev_start_xmit快到driver的tx函数了*/rc netdev_start_xmit(skb, dev, txq, more);trace_net_dev_xmit(skb, rc, dev, len);return rc;
}static inline netdev_tx_t netdev_start_xmit(struct sk_buff *skb, struct net_device *dev,struct netdev_queue *txq, bool more)
{//获取对应网卡驱动的操作函数集const struct net_device_ops *ops dev-netdev_ops;int rc;/*__netdev_start_xmit 里面就完全是使用driver 的ops去发包了其实到此为止一个skb已经从netdevice*这个层面送到driver层了接下来会等待driver的返回*/rc __netdev_start_xmit(ops, skb, dev, more);/*如果返回NETDEV_TX_OK那么会更新下Txq的trans时间戳哦txq-trans_start jiffies;*/if (rc NETDEV_TX_OK)txq_trans_update(txq);return rc;
}static inline netdev_tx_t __netdev_start_xmit(const struct net_device_ops *ops,struct sk_buff *skb, struct net_device *dev,bool more)
{skb-xmit_more more ? 1 : 0;//调用网卡驱动的发送函数真正的通过硬件发送数据return ops-ndo_start_xmit(skb, dev);
}7、网卡驱动层进行数据传输
通过调用ndo_start_xmit函数数据才能真正被网卡发送出去。对应不同的网卡驱动ndo_start_xmit都有各自的实现方式也是必须要实现的这样才能和上层的协议栈衔接起来
以DM9000驱动为例
/*分析DM9000发生数据函数**/
/** Hardware start transmission.* Send a packet to media from the upper layer.*/
static int
dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev)
{unsigned long flags;board_info_t *db netdev_priv(dev);dm9000_dbg(db, 3, %s:\n, __func__);if (db-tx_pkt_cnt 1)return NETDEV_TX_BUSY;spin_lock_irqsave(db-lock, flags);/* Move data to DM9000 TX RAM *///写数据到DM9000 Tx RAM中 写地址自动增加writeb(DM9000_MWCMD, db-io_addr);/*将skb中的数据写入寄存器然后发送字节改变*/(db-outblk)(db-io_data, skb-data, skb-len);dev-stats.tx_bytes skb-len;db-tx_pkt_cnt;/*第一个发送包立刻发送 第二个排列到发送队列中去*//* TX control: First packet immediately send, second packet queue */if (db-tx_pkt_cnt 1) {dm9000_send_packet(dev, skb-ip_summed, skb-len);} else {/* Second packet */db-queue_pkt_len skb-len;db-queue_ip_summed skb-ip_summed;/*告诉网络协议栈停止发送数据。*/netif_stop_queue(dev);}spin_unlock_irqrestore(db-lock, flags);/* free this SKB *//*释放skb*/dev_kfree_skb(skb);return NETDEV_TX_OK;
}/*当发送完成后会触发一次发送完成的中断。 当然要去中断处理函数中*/
static irqreturn_t dm9000_interrupt(int irq, void *dev_id)
{struct net_device *dev dev_id;board_info_t *db netdev_priv(dev);int int_status;unsigned long flags;u8 reg_save;dm9000_dbg(db, 3, entering %s\n, __func__);/* A real interrupt coming *//* holders of db-lock must always block IRQs */spin_lock_irqsave(db-lock, flags); /* Save previous register address */reg_save readb(db-io_addr); //存储以前的地址/* Disable all interrupts */ //屏蔽所有中断iow(db, DM9000_IMR, IMR_PAR);/* Got DM9000 interrupt status */int_status ior(db, DM9000_ISR); /* Got ISR */iow(db, DM9000_ISR, int_status); /* Clear ISR status */if (netif_msg_intr(db))dev_dbg(db-dev, interrupt status %02x\n, int_status);/* Received the coming packet */ //接受中断发生if (int_status ISR_PRS)dm9000_rx(dev);/* Trnasmit Interrupt check */if (int_status ISR_PTS) //检测是否发送完成dm9000_tx_done(dev, db);if (db-type ! TYPE_DM9000E) {if (int_status ISR_LNKCHNG) {/* fire a link-change request */schedule_delayed_work(db-phy_poll, 1);}}/* Re-enable interrupt mask */iow(db, DM9000_IMR, db-imr_all); //使能中断/* Restore previous register address */writeb(reg_save, db-io_addr); //恢复以前的地址spin_unlock_irqrestore(db-lock, flags);return IRQ_HANDLED;
}/*分析中断发送完成处理函数*/static void dm9000_tx_done(struct net_device *dev, board_info_t *db)
{int tx_status ior(db, DM9000_NSR); /* Got TX status */ //得到发送的状态if (tx_status (NSR_TX2END | NSR_TX1END)) {/* One packet sent complete */ //是一个包就发送完成db-tx_pkt_cnt--;dev-stats.tx_packets;if (netif_msg_tx_done(db))dev_dbg(db-dev, tx done, NSR %02x\n, tx_status);/* Queue packet check send */ //如果超过2个进入队列。发送if (db-tx_pkt_cnt 0)dm9000_send_packet(dev, db-queue_ip_summed,db-queue_pkt_len);netif_wake_queue(dev); //唤醒发送队列}
}/*
* 总结1. 通知网络协议栈停止发送队列2. 写skb数据到寄存器中去3. 释放skb资源4. 当发送完成后唤醒发送队列
*/8、数据传输的整个流程