Chinaunix首页 | 论坛 | 博客
  • 博客访问: 456905
  • 博文数量: 362
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-07-26 17:08
文章分类

全部博文(362)

文章存档

2015年(362)

我的朋友

分类: LINUX

2015-12-11 09:56:05

作者:gfree.wind@gmail.com
博客:blog.focus-linux.net    linuxfocus.blog.chinaunix.net
Linux版本:2.6.36

好久没有看Linux源代码了,今天先回顾了一下以前的笔记,基本上把发送和接收数据包的流程学习了一遍。如果需要看以前的笔记,请看linuxfocus.blog.chinaunix.net上面的笔记。

不过以前的接收流程,只学习到kernel如何将数据包分发到对应的socket上。接收流程的其它部分没有太多值得关注的。我想可以这样去看源码,将一些有意思的部分单拿出来学习。

下面看一下UDP读取数据包的关键函数:
  1. struct sk_buff *__skb_recv_datagram(struct sock *sk, unsigned flags,
  2.                  int *peeked, int *err)
  3. {
  4.     struct sk_buff *skb;
  5.     long timeo;
  6.     /*
  7.      * Caller is allowed not to check sk->sk_err before skb_recv_datagram()
  8.      */
  9.     int error = sock_error(sk);

  10.     if (error)
  11.         goto no_packet;
     /* 当socket为阻塞时,获取timeout的值 */
  1.     timeo = sock_rcvtimeo(sk, flags & MSG_DONTWAIT);

  2.     do {
  3.         /* Again only user level code calls this function, so nothing
  4.          * interrupt level will suddenly eat the receive_queue.
  5.          *
  6.          * Look at current nfs client by the way...
  7.          * However, this function was corrent in any case. 8)
  8.          */
  9.         unsigned long cpu_flags;
         /* 
         当查看socket是否有数据包时,需要上锁,因为需要保证其它线程不会将数据包取走。
         */
  1.         spin_lock_irqsave(&sk->sk_receive_queue.lock, cpu_flags);
  2.         /* 查看在socket的buffer中是否有数据包 */
  3.         skb = skb_peek(&sk->sk_receive_queue);
  4.         if (skb) {
  5.             *peeked = skb->peeked;
  6.             if (flags & MSG_PEEK) {
  7.                 /* 
  8.                 设置MSG_PEEK,表示用户不是真的要读取数据,只是一个peek调用。
  9.                 那么并不真正读取数据
  10.                 */
  11.                 skb->peeked = 1;
  12.                 atomic_inc(&skb->users);
  13.             } else 
  14.                 __skb_unlink(skb, &sk->sk_receive_queue); //从队列中取出数据,即可看作读出数据
  15.         }
  16.         spin_unlock_irqrestore(&sk->sk_receive_queue.lock, cpu_flags);

         // 有数据包,返回skb
  1.         if (skb)
  2.             return skb;

  3.         /* User doesn't want to wait */
  4.         error = -EAGAIN;
  5.         /*
  6.         timeo为0,有2中情况:1种是socket为非阻塞的,第2种,即socket阻塞的时间已经超过了timeo的值,
  7. 那么就跳到no_packet处理 
  8.         */
  9.         if (!timeo)
  10.             goto no_packet;

  11.     } while (!wait_for_packet(sk, err, &timeo)); //阻塞进程,等待数据包

  12.     return NULL;

  13. no_packet:
  14.     *err = error;
  15.     return NULL;
  16. }
下面看wait_for_packet:
  1. static int wait_for_packet(struct sock *sk, int *err, long *timeo_p)
  2. {
  3.     int error;
     /* 
     前面的操作都是初始化wait,为将socket加入wait队列作准备,这部分代码牵涉到进程调度。关于进程调度,我      只是知道一些皮毛,留在以后学习。这里只需要将其看作是一些加入wait队列的准备工作即可,并不影响理解代码      。
     */
  1.     DEFINE_WAIT_FUNC(wait, receiver_wake_function);

  2.     prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);

  1.     /* Socket errors? */
  2.     error = sock_error(sk);
  3.     if (error)
  4.         goto out_err;

     /* 一个完备检测。在决定wait和调用wait之间,有数据包到了,那么就不需要wait,所以这里再次检查socket      的队列是否为空 */
  1.     if (!skb_queue_empty(&sk->sk_receive_queue))
  2.         goto out;

     /* 完备检测。也许socket无数据包读取,因为socket已经被另外的线程关闭了。这样可以保证关闭socket的时      候,不会导致其他的socket的读写操作被阻塞。*/
  1.     /* Socket shut down? */
  2.     if (sk->sk_shutdown & RCV_SHUTDOWN)
  3.         goto out_noerr;

      /* 对于面向连接的socket进行检查。如果是面向连接的socket,如果不是已经建立连接或者正在监听状态的so       cket是不可能有数据包的。不然即出错*/
  1.     /* Sequenced packets can come disconnected.
  2.      * If so we report the problem
  3.      */
  4.     error = -ENOTCONN;
  5.     if (connection_based(sk) &&
  6.      !(sk->sk_state == TCP_ESTABLISHED || sk->sk_state == TCP_LISTEN))
  7.         goto out_err;

     /* 检查是否有pending的signal,保证阻塞时,进程可以被signal唤醒 */
  1.     /* handle signals */
  2.     if (signal_pending(current))
  3.         goto interrupted;

  4.     error = 0;
  5.     /* sleep本进程,直至满足唤醒条件或者被信号唤醒——因为前面设置了TASK_INTERRUPTIBLE*/
  6.     *timeo_p = schedule_timeout(*timeo_p);
  7. out:
     /* wait队列的清理工作 */
  1.     finish_wait(sk_sleep(sk), &wait);
  2.     return error;
  3. interrupted:
  4.     error = sock_intr_errno(*timeo_p);
  5. out_err:
  6.     *err = error;
  7.     goto out;
  8. out_noerr:
  9.     *err = 0;
  10.     error = 1;
  11.     goto out;
  12. }

看完了这两个函数,个人感觉这种针对一些有意义的函数进行学习,比流水账似的从系统调用开始学习的效果要好。因为后者会浪费很多精力在不太重要的代码或者流程上,而前者直接聚焦于比较关键的地方。




阅读(432) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~