关于send和sendto阻塞和非阻塞模式的底层细节

本文详细解析了Linux中sendto函数的工作流程,特别是非阻塞模式下的行为。通过跟踪sendto到udp_sendmsg的调用过程,揭示了如何根据socket标志决定是否等待以及如何处理发送缓冲区满的情况。
摘要由CSDN通过智能技术生成


对于sendto,Linux 内核最后会调用udp_sendmsg,大概的调用堆栈是

udp_sendmsg

security_socket_sendmsg

__sock_sendmsg

sock_sendmsg

sendto

在sento里面,会根据socket的模式把一个标志传递给内核:

if (sock->file->f_flags & O_NONBLOCK)
flags |= MSG_DONTWAIT;

这个flags的值在后面会有用处

udp_sendmsg的第三个参数msg保存着等待发送的数据,即msg.msg_iov.iov_base,这个指针目前仍然是最上面用户层传递进来的函数临时变量,


udp_sendmsg里面有大量的细节暂且不管,涉及数据拷贝和发动的是ip_append_data,而它调用的是__ip_append_data,ip_append_data会传递一个getfrag函数指针作为回调,getfrag的目的就是执行发送数据的拷贝。getfrag在哪里被执行呢,继续往下看


在__ip_append_data里,代码分成2个执行分支,1

1调用ip_ufo_append_data,

2调用sock_alloc_send_skb


如果走分支2,直接调用sock_alloc_send_skb,sock_alloc_send_skb只有一个调用sock_alloc_send_pskb,sock_alloc_send_pskb是阻塞与非阻塞的关键所在,此函数首先调用timeo = sock_sndtimeo(sk, noblock); 

noblock由最初的flags决定,意思是阻塞模式的话会一直等到到超时为止,如果是非阻塞则不等待,那么在哪里等待呢,下面一段代码展示了基本的逻辑

if (atomic_read(&sk->sk_wmem_alloc) >= sk->sk_sndbuf) {
set_bit(SOCK_ASYNC_NOSPACE, &sk->sk_socket->flags);
set_bit(SOCK_NOSPACE, &sk->sk_socket->flags);
err = -EAGAIN;
if (!timeo)
goto failure;
if (signal_pending(current))
goto interrupted;
timeo = sock_wait_for_wmem(sk, timeo);
continue;

}

意思是需要发送的数据长度是大于发送缓存情况下,并且timeo为0,返回EAGAIN给用户层,如果timeo非0,则调用sock_wait_for_wmem一直检查发送缓存的情况,直到发送缓存足够长或者超时。

在非阻塞情况下,sock_alloc_send_skb返回EAGAIN,会跳转到error。而在阻塞情况下如果返回成功,后面才会调用getfrag执行真正的数据拷贝。


补充一下,不管阻塞还是非阻塞模式,在申请skb之前都会调用lock_sock,从而可能进入锁竞争状态,其实也不绝对的非阻塞。

lock_sock这个函数也值得研究,他既调用了自旋锁,又调用了互斥锁。


除了申请kbuffer存在阻塞,发现还需要研究一下真正send数据的时候存在阻塞,发送数据会从udp->ip分层的往下逐层进行调用,最后到达dev,即网络驱动。里面细节太多,主要会调用下面几个函数:


ip_send_skb 》 ip_local_out 》ip_output 》ip_finish_output2 》__dev_queue_xmit


最后调用的是__dev_queue_xmit,这个函数比较复杂,如果想看详细的分析可以看这里http://shaojiashuai123456.iteye.com/blog/842236,

__dev_queue_xmit 获取逻辑设备对象,然后判断其是否有发送队列,如果有发送队列则调用__dev_xmit_skb,并依次调用__qdisc_run-》qdisc_restart->sch_direct_xmit->dev_hard_start_xmit


dev_hard_start_xmit 直接调用的是 rc = ops->ndo_start_xmit(skb, dev);

ndo_start_xmit则属于驱动层的调用,典型的是ethernet,对应的函数是cvm_oct_xmit,驱动层会把这个skb放入等待发送队列,然后直接返回NETDEV_TX_OK。


从这里可以看到,不管阻塞还是非阻塞send,都会经历漫长的调用堆栈,知道驱动层把skb放入发送队列,才会返回,其中会有多次对rcu锁的申请。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值