本文主要分析accept()的阻塞等待和唤醒。

内核版本:3.6

Author:zhangskd @ csdn blog

等待队列

(1)socket的等待队列

  1. /*
  2. * @sk_wq: sock wait queue head and async head
  3. */
  4. struct sock {
  5. ...
  6. struct socket_wq __rcu *sk_wq; /* 套接字的等待队列 */
  7. ...
  8. };
  1. struct socket_wq {
  2. /* Note: wait MUST be first field of socket_wq */
  3. wait_queue_head_t wait; /* 等待队列 */
  4.  
  5. struct fasync_struct *fasync_list; /* 异步文件操作 */
  6.  
  7. struct rcu_head rcu; /* 更新时的回调函数 */
  8. } __cacheline_aligned_in_smp;
  9.  
  10. struct __wait_queue_head {
  11. spinlock_t lock;
  12. struct list_head task_list;
  13. };
  14. typedef struct __wait_queue_head wait_queue_head_t; /* 等待队列头 */

(2)进程的等待任务

  1. struct __wait_queue {
  2. unsigned int flags;
  3. #define WQ_FLAG_EXCLUSIVE 0x01
  4. void *private; /* 指向当前的进程控制块 */
  5. wait_queue_func_t func; /* 唤醒函数 */
  6. struct list_head task_list; /* 用于链接入等待队列 */
  7. };
  8. typedef struct __wait_queue wait_queue_t;
  9. typedef int (*wait_queue_func_t) (wait_queue_t *wait, unsigned mode, int flags, void *key);
  10. int default_wake_function(wait_queue_t *wait, unsigned mode, int flags, void *key);

初始化等待任务。

  1. #define DEFINE_WAIT(name) DEFINE_WAIT_FUNC(name, autoremove_wake_function)
  2.  
  3. #define DEFINE_WAIT_FUNC(name, function) \
  4. wait_queue_t name = { \
  5. .private = current, \
  6. .func = function, \
  7. .task_list = LIST_HEAD_INIT((name).task_list), \
  8. }
  9.  
  10. int autoremove_wake_function(wait_queue_t *wait, unsigned mode, int sync, void *key)
  11. {
  12. int ret = default_wake_function(wait, mode, sync, key); /* 默认的唤醒函数 */
  13.  
  14. if (ret)
  15. list_del_init(&wait->task_list); /* 从等待队列中删除,初始化此等待任务 */
  16.  
  17. return ret;
  18. }

获取sock的等待队列。

  1. static inline wait_queue_head_t *sk_sleep(struct sock *sk)
  2. {
  3. BUILD_BUG_ON(offsetof(struct socket_wq, wait) != 0);
  4. return &rcu_dereference_raw(sk->sk_wq)->wait;
  5. }

把等待任务加入到等待队列中,设置当前进程的状态。

  1. void prepare_to_wait_exclusive(wait_queue_head_t *q, wait_queue_t *wait, int state)
  2. {
  3. unsigned long flags;
  4.  
  5. /* 这个标志表示一次只唤醒一个等待任务,避免惊群现象 */
  6. wait->flags |= WQ_FLAG_EXCLUSIVE;
  7.  
  8. spin_lock_irqsave(&q->lock, flags);
  9.  
  10. if (list_empty(&wait->task_list))
  11. __add_wait_queue_tail(q, wait); /* 把此等待任务加入到等待队列中 */
  12.  
  13. set_current_state(state); /* 设置当前进程的状态 */
  14.  
  15. spin_unlock_irqrestore(&q->lock, flags);
  16. }
  17.  
  18. static inline void __add_wait_queue_tail(wait_queue_head_t *head, wait_queue_t *new)
  19. {
  20. list_add_tail(&new->task_list, &head->task_list);
  21. }
  22.  
  23. #define set_current_state(state_value) \
  24. set_mb(current->state, (state_value))

(3)accept()的阻塞等待

accept()超时时间为sk->sk_rcvtimeo,在sock_init_data()中初始化为MAX_SCHEDULE_TIMEOUT,表示无限等待。

  1. /* Wait for an incoming connection, avoid race conditions.
  2. * This must be called with the socket locked.
  3. */
  4. static int inet_csk_wait_for_connect(struct sock *sk, long timeo)
  5. {
  6. struct inet_connection_sock *icsk = inet_csk(sk);
  7. DEFINE_WAIT(wait); /* 初始化等待任务 */
  8. int err;
  9.  
  10. for (; ;) {
  11. /* 把等待任务加入到socket的等待队列中,把进程状态设置为TASK_INTERRUPTIBLE */
  12. prepare_to_wait_exclusive(sk_sleep(sk), &wait, TASK_INTERRUPTIBLE);
  13.  
  14. release_sock(sk); /* 等下可能要睡觉了,先释放 */
  15.  
  16. if (reqsk_queue_empty(&icsk->icsk_accept_queue)) /* 如果全连接队列为空 */
  17. timeo = schedule_timeout(timeo); /* 进入睡眠,直到超时或收到信号 */
  18.  
  19. lock_sock(sk); /* 醒来后重新上锁 */
  20. err = 0;
  21. if (! reqsk_queue_empty(&icsk->icsk_accept_queue)) /* 全连接队列不为空时,退出 */
  22. break;
  23.  
  24. err = -EINVAL;
  25. if (sk->sk_state != TCP_LISTEN) /* 如果sock不处于监听状态了,退出 */
  26. break;
  27.  
  28. err = sock_intr_errno(timeo);
  29.  
  30. /* 如果进程有待处理的信号,退出。
  31. * 因为timeo默认为MAX_SCHEDULE_TIMEOUT,所以err默认为-ERESTARTSYS。
  32. * 接下来会重新调用此函数,所以accept()依然阻塞。
  33. */
  34. if (signal_pending(current))
  35. break;
  36.  
  37. err = -EAGAIN;
  38. if (! timeo) /* 如果等待超时,即超过用户设置的sk->sk_rcvtimeo,退出 */
  39. break;
  40. }
  41.  
  42. finish_wait(sk_sleep(sk), &wait);
  43. return err;
  44. }
  1. /**
  2. * schedule_timeout - sleep until timeout
  3. * @timeout: timeout value in jiffies
  4. *
  5. * Make the current task sleep until @timeout jiffies have elapsed. The routine
  6. * will return immediately unless the current task state has been set (see set_current_state()).
  7. *
  8. * You can set the task state as follows -
  9. * %TASK_UNINTERRUPTIBLE - at least @timeout jiffies are guaranteed to pass before
  10. * the routine returns. The routine will return 0.
  11. *
  12. * %TASK_INTERRUPTIBLE - the routine may return early if a signal is delivered to the
  13. * current task. In this case the remaining time in jiffies will be returned, or 0 if the timer
  14. * expired in time.
  15. *
  16. * The current task state is guaranteed to be TASK_RUNNING when this routine returns.
  17. *
  18. * Specifying a @timeout value of %MAX_SCHEDULE_TIMEOUT will schedule the CPU
  19. * away without a bound on the timeout. In this case the return value will be
  20. * %MAX_SCHEDULE_TIMEOUT.
  21. *
  22. * In all cases the return value is guaranteed to be non-negative.
  23. */
  24.  
  25. signed long __sched schedule_timeout(signed long timeout) {}

因为sk->sk_rcvtimeo默认值为MAX_SCHEDULE_TIMEOUT,所以返回-ERESTARTSYS,即告诉系统

重新执行accept()的系统调用。

  1. static inline int sock_intr_errno(long timeo)
  2. {
  3. return timeo == MAX_SCHEDULE_TIMEOUT? -ERESTARTSYS : -EINTR; /* Interrupted system call */
  4. }

从等待队列中删除等待任务,把当前进程的状态置为可运行。

  1. /**
  2. * finish_wait - clean up after waiting in a queue
  3. * @q: waitqueue waited on,等待队列头
  4. * @wait: wait descriptor,等待任务
  5. *
  6. * Sets current thread back to running state and removes the wait
  7. * descriptor from the given waitqueue if still queued.
  8. */
  9. void finish_wait(wait_queue_head_t *q, wait_queue_t *wait)
  10. {
  11. unsigned long flags;
  12. __set_current_state(TASK_RUNNING);
  13.  
  14. if (! list_empty_careful(&wait->task_list)) {
  15. spin_lock_irqsave(&q->lock, flags);
  16.  
  17. list_del_init(&wait->task_list); /* 从等待队列中删除,初始化此等待任务 */
  18.  
  19. spin_unlock_irqrestore(&q->lock, flags);
  20. }
  21. }

(4)accept()的唤醒

当收到客户端的ACK后,经过如下调用:

tcp_v4_rcv

tcp_v4_do_rcv

tcp_child_process

sock_def_readable

wake_up_interruptible_sync_poll

__wake_up_sync_key

__wake_up_common

最终调用我们给等待任务注册的唤醒函数。

我们来看下accept()是如何避免惊群现象的。

  1. static void __wake_up_common(wait_queue_head_t *q, unsigned int mode, int nr_exclusive,
  2. int wake_flags, void *key)
  3. {
  4. wait_queue_t *curr, *next;
  5.  
  6. list_for_each_entry_safe(curr, next, &q->task_list, task_list) {
  7. unsigned flags = curr->flags;
  8.  
  9. if (curr->func(curr, mode, wake_flags, key) && (flags & WQ_FLAG_EXCLUSIVE)
  10. !--nr_exclusive)
  11. break;
  12. }
  13. }

初始化等待任务时,flags |= WQ_FLAG_EXCLUSIVE。传入的nr_exclusive为1,表示只允许唤醒一个等待任务。

所以这里只会唤醒一个等待的进程,不会导致惊群现象。

Socket层实现系列 — accept()的实现(二)的更多相关文章

  1. Socket层实现系列 — accept()的实现(一)

    本文主要介绍了accept()的系统调用.Socket层实现,以及TCP层实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int accept(int soc ...

  2. Socket层实现系列 — 睡眠驱动的同步等待

    主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了 ...

  3. Socket层实现系列 — listen()的实现

    本文主要分析listen()的内核实现,包括它的系统调用.Socket层实现.半连接队列,以及监听哈希表. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int l ...

  4. Socket层实现系列 — bind()的实现(一)

    bind()函数的使用方法很简单,但是它是怎么实现的呢? 笔者从应用层出发,沿着网络协议栈,分析了bind()的系统调用.Socket层实现,以及它的TCP层实现. 本文主要内容:bind()的系统调 ...

  5. Socket层实现系列 — send()类发送函数的实现

    主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendt ...

  6. Socket层实现系列 — connect()的实现

    主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect( ...

  7. Socket层实现系列 — 信号驱动的异步等待

    主要内容:Socket的异步通知机制. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事 ...

  8. Socket层实现系列 — getsockname()和getpeername()的实现

    本文主要介绍了getsockname()和getpeername()的内核实现. 内核版本:3.6 Author:zhangskd @ csdn blog 应用层 int getsockname(in ...

  9. Socket层实现系列 — bind()的实现(二)

    本文主要内容:bind()的TCP层实现.端口的冲突处理,以及不同内核版本的实现差异. 内核版本:3.6 Author:zhangskd @ csdn blog TCP层实现 SOCK_STREAM套 ...

随机推荐

  1. OpenMP实现生产者消费者模型

    生产者消费者模型已经很古老了吧,最近写了个OpenMP版的此模型之实现,来分享下. 先说一下模型的大致做法是: 1.生产者需要取任务,生产产品. 2.消费者需要取产品,消费产品. 生产者在生产某个产品 ...

  2. XMPP系列(六)---创建群组

    最近公司项目需要,要做一个自己的IMSDK,顺便先把之前没有记录的群聊功能记录一下. 先上资料,查看XMPP群聊相关的资料,可以去这里看协议:XEP-0045 . 创建群组 XMPP 框架里有一个类X ...

  3. 自定义控件辅助神器ViewDragHelper

    ViewDragHelper作为官方推出的手势滑动辅助工具,极大的简化了我们对手势滑动的处理逻辑,v4包中的SlidingPaneLayout和DrawerLayout内部都有ViewDragHelp ...

  4. android连接打印机

    android连接  网络打印,主要使用socket连接设备,发送指令给设备. 首先要有设备的IP,端口号一般默认的是9100 //打印设备网络IP etIp.setText("192.16 ...

  5. Android之自定义AlertDialog和PopupWindow实现(仿微信Dialog)

    我们知道,在很多时候,我们都不用Android内置的一些控件,而是自己自定义一些自己想要的控件,这样显得界面更美观. 今天主要是讲自定义AlertDialog和popupWindow的使用,在很多需求 ...

  6. shell的数值计算,小数计算

    shell脚本中,可以进行数值计算, 如加减乘除,通过expr.let.(())等完成,文章介绍:http://blog.csdn.net/longshenlmj/article/details/14 ...

  7. mysql 字符集查看 设定

    (1) 最简单的修改方法,就是修改mysql的my.ini文件中的字符集键值, 如 default-character-set = utf8 character_set_server = utf8 修 ...

  8. DVB-C系统中QAM调制与解调仿真

    本文简单记录一下自己学习<通信原理>的时候调试的一个仿真DVB-C(Cable,数字有线电视)系统中QAM调制和解调的程序.自己一直是研究"信源"方面的东西,所以对&q ...

  9. antlr v4 使用指南连载1——简介

    antlr v4简介        antlr是一个强大语言解析工具,可以用于处理结构化文本.二进制文件.说白了,其实可以这么认为,antlr是一个更强大的正则表达式工具.它可以完成更多正则表达式无法 ...

  10. (NO.00005)iOS实现炸弹人游戏(六):游戏数据的初始化(三)

    大熊猫猪·侯佩原创或翻译作品.欢迎转载,转载请注明出处. 如果觉得写的不好请告诉我,如果觉得不错请多多支持点赞.谢谢! hopy ;) 现在我们来看看实际初始化地图的randomCreateMap方法 ...