博客:blog.focus-linux.net linuxfocus.blog.chinaunix.net
Linux版本:2.6.36
好久没有看Linux源代码了,今天先回顾了一下以前的笔记,基本上把发送和接收数据包的流程学习了一遍。如果需要看以前的笔记,请看linuxfocus.blog.chinaunix.net上面的笔记。
不过以前的接收流程,只学习到kernel如何将数据包分发到对应的socket上。接收流程的其它部分没有太多值得关注的。我想可以这样去看源码,将一些有意思的部分单拿出来学习。
下面看一下UDP读取数据包的关键函数:
- struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
- int *peeked, int *err)
- {
- struct sk_buff *skb;
- long timeo;
- /*
- * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
- */
- int error = sock_error(sk);
- if (error)
- goto no_packet;
/* 当socket为阻塞时,获取timeout的值 */
- timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);
- do {
- /* Again only user level code calls this function, so nothing
- * interrupt level will suddenly eat the receive_queue.
- *
- * Look at current nfs client by the way...
- * However, this function was corrent in any case. 8)
- */
- unsigned long cpu_flags;
/*
当查看socket是否有数据包时,需要上锁,因为需要保证其它线程不会将数据包取走。
*/
- spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
- /* 查看在socket的buffer中是否有数据包 */
- skb = skb_peek(&sk->sk_receive_queue);
- if (skb) {
- *peeked = skb->peeked;
- if (flags & MSG_PEEK) {
- /*
- 设置MSG_PEEK,表示用户不是真的要读取数据,只是一个peek调用。
- 那么并不真正读取数据
- */
- skb->peeked = 1;
- atomic_inc(&skb->users);
- } else
- __skb_unlink(skb, &sk->sk_receive_queue); //从队列中取出数据,即可看作读出数据
- }
- spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);
// 有数据包,返回skb
- if (skb)
- return skb;
- /* User doesn't want to wait */
- error = -EAGAIN;
- /*
- timeo为0,有2中情况:1种是socket为非阻塞的,第2种,即socket阻塞的时间已经超过了timeo的值,
- 那么就跳到no_packet处理
- */
- if (!timeo)
- goto no_packet;
- } while (!wait_for_packet(sk, err, &timeo)); //阻塞进程,等待数据包
- return NULL;
- no_packet:
- *err = error;
- return NULL;
- }
下面看wait_for_packet:
- static int wait_for_packet(struct sock *sk, int *err, long *timeo_p)
- {
- int error;
/*
前面的操作都是初始化wait,为将socket加入wait队列作准备,这部分代码牵涉到进程调度。关于进程调度,我 只是知道一些皮毛,留在以后学习。这里只需要将其看作是一些加入wait队列的准备工作即可,并不影响理解代码 。
*/
- DEFINE_WAIT_FUNC(wait, receiver_wake_function);
- prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
- /* Socket errors? */
- error = sock_error(sk);
- if (error)
- goto out_err;
/* 一个完备检测。在决定wait和调用wait之间,有数据包到了,那么就不需要wait,所以这里再次检查socket 的队列是否为空 */
- if (!skb_queue_empty(&sk->sk_receive_queue))
- goto out;
/* 完备检测。也许socket无数据包读取,因为socket已经被另外的线程关闭了。这样可以保证关闭socket的时 候,不会导致其他的socket的读写操作被阻塞。*/
- /* Socket shut down? */
- if (sk->sk_shutdown & RCV_SHUTDOWN)
- goto out_noerr;
/* 对于面向连接的socket进行检查。如果是面向连接的socket,如果不是已经建立连接或者正在监听状态的so cket是不可能有数据包的。不然即出错*/
- /* Sequenced packets can come disconnected.
- * If so we report the problem
- */
- error = -ENOTCONN;
- if (connection_based(sk) &&
- !(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN))
- goto out_err;
/* 检查是否有pending的signal,保证阻塞时,进程可以被signal唤醒 */
- /* handle signals */
- if (signal_pending(current))
- goto interrupted;
- error = 0;
- /* sleep本进程,直至满足唤醒条件或者被信号唤醒——因为前面设置了TASK_INTERRUPTIBLE*/
- *timeo_p = schedule_timeout(*timeo_p);
- out:
/* wait队列的清理工作 */
- finish_wait(sk_sleep(sk), &wait);
- return error;
- interrupted:
- error = sock_intr_errno(*timeo_p);
- out_err:
- *err = error;
- goto out;
- out_noerr:
- *err = 0;
- error = 1;
- goto out;
- }
看完了这两个函数,个人感觉这种针对一些有意义的函数进行学习,比流水账似的从系统调用开始学习的效果要好。因为后者会浪费很多精力在不太重要的代码或者流程上,而前者直接聚焦于比较关键的地方。