主要内容:Socket的异步通知机制。

内核版本:3.15.2

我的博客:http://blog.csdn.net/zhangskd

概述

socket上定义了几个IO事件:状态改变事件、有数据可读事件、有发送缓存可写事件、有IO错误事件。

对于这些事件,socket中分别定义了相应的事件处理函数,也称回调函数。

Socket I/O事件的处理过程中,要使用到sock上的两个队列:等待队列和异步通知队列,这两个队列中

都保存着等待该Socket I/O事件的进程。

Q:为什么要使用两个队列,等待队列和异步通知队列有什么区别呢?

A:等待队列上的进程会睡眠,直到Socket I/O事件的发生,然后在事件处理函数中被唤醒。

异步通知队列上的进程则不需要睡眠,Socket I/O事件发时,事件处理函数会给它们发送到信号,

这些进程事先注册的信号处理函数就能够被执行。

异步通知队列

Socket层使用异步通知队列来实现异步等待,此时等待Socket I/O事件的进程不用睡眠。

struct sock {
...
struct socket_wq __rcu *sk_wq; /* socket的等待队列和异步通知队列 */
...
} struct socket_wq {
/* Note: wait MUST be first field of socket_wq */
wait_queue_head_t wait; /* 等待队列头 */
struct fasync_struct *fasync_list; /* 异步通知队列 */
struct rcu_head *rcu;
};
struct fasync_struct {
spinlock_t fa_lock;
int magic;
int fa_fd; /* 文件描述符 */
struct fasync_struct *fa_next; /* 用于链入单向链表 */
struct file *fa_file; /* fa_file->f_owner记录接收信号的进程 */
struct rcu_head fa_rcu;
};

通过之前的blog《linux的异步通知机制》,我们知道为了能处理协议栈发出的SIGIO信号,

用户程序需要做的事情有:

1. 通过signal()指定SIGIO的处理函数。

2. 设置sockfd的拥有者为本进程,如此一来本进程才能收到协议栈发出的SIGIO信号。

3. 设置sockfd支持异步通知,即设置O_ASYNC标志。

对应的用户程序函数调用大概如下:

signal(SIGIO, my_handler); /* set new SIGIO handler */

fcntl(sockfd, F_SETOWN, getpid()); /* set sockfd's owner process */

oflags = fcntl(sockfd, F_GETFL); /* get old sockfd flags */

fcntl(sockfd, F_SETFL, oflags | O_ASYNC); /* set new sockfd flags */

下文关注的是内核层面的一些工作:

1. 如何把进程加入Socket的异步通知队列,或者把进程从Socket的异步通知队列中删除。

2. 协议栈何时发送信号给Socket异步通知队列上的进程。

插入和删除

首先来看下fcntl()的系统调用。

SYSCALL_DEFINE3(fcntl, unsigned int, fd, unsigned int, cmd, unsigned long, arg)
{
struct fd f = fdget_raw(fd);
long err = -EBADF; /* Bad file number */ if (! f.file)
goto out; /* File is opened with O_PATH, almost nothing can be done with it */
if (unlikely(f.file->f_mode & FMODE_PATH)) {
if (! check_fcntl_cmd(cmd))
goto out1;
} err = security_file_fcntl(f.file, cmd, arg);
if (! err)
err = do_fcntl(fd, cmd, arg, f.file); /* 实际的处理函数 */ out1:
fdput(f);
out:
return err;
}
static long do_fcntl(int fd, unsigned int cmd, unsigned long arg, struct fil *filp)
{
long err = -EINVAL; switch(cmd) {
...
case F_SETFL: /* 在这里设置O_ASYNC标志 */
err = setfl(fd, filp, arg);
break;
...
case F_SETOWN: /* 在这里设置所有者进程 */
err = f_setown(filp, arg, 1);
break;
....
} return err;
}
static int setfl(int fd, struct file *filp, unsigned long arg)
{
...
/* ->fasync() is responsible for setting the FASYNC bit. */
if (((arg ^ filp->f_flags) & FASYNC) && filp->f_op->fasync) {
error = filp->f_op->fasync(fd, filp, (arg & FASYNC) != 0); if (error < 0)
goto out;
if (error > 0)
error = 0;
}
...
}

Socket文件的操作函数集为socket_file_ops。

static const struct file_operations socket_file_ops = {
...
.fasync = sock_fasync,
...
};
/* Update the socket async list. */
static int sock_fasync(int fd, struct file *filp, int on)
{
struct socket *sock = filp->private_data;
struct sock *sk = sock->sk;
struct socket_wq *wq; /* Socket的等待队列和异步通知队列 */ if (sk == NULL)
return -EINVAL; lock_sock(sk);
wq = rcu_dereference_protected(sock->wq, sock_owned_by_user(sk)); fasync_helper(fd, filp, on, &wq->fasync_list); /* 使用此函数来插入或删除 */ /* 设置或取消SOCK_FASYNC标志 */
if (! wq->fasync_list)
sock_reset_flag(sk, SOCK_FASYNC);
else
sock_set_flag(sk, SOCK_FASYNC); release_sock(sk); return 0;
}

和设备驱动一样,最终调用fasync_helper()来把进程插入异步通知队列,或者把进程从异步通知队列中删除。

/*
* fasync_helper() is used by almost all character device drivers to set up the fasync
* queue, and for regular files by the file lease code. It returns negative on error, 0 if
* it did no changes and positive if it added / deleted the entry.
*/ int fasync_helper(int fd, struct file *filp, int on, struct fasync_struct **fapp)
{
if (! on)
return fasync_remove_entry(filp, fapp); /* 插入 */ return fasync_add_entry(fd, filp, fapp); /* 删除 */
}

发送信号

当Socket I/O事件触发时,协议栈会调用sk_wake_async()来进行异步通知。

函数的处理方式:

enum {
SOCK_WAKE_IO, /* 直接发送SIGIO信号 */
SOCK_WAKE_WAITD, /* 检测应用程序是否通过recv()类调用来等待接收数据,如果没有才发送SIGIO信号 */
SOCK_WAKE_SPACE, /* 检测sock的发送队列是否曾经到达上限,如果有的话发送SIGIO信号 */
SOCK_WAKE_URG, /* 直接发送SIGURG信号 */
};

通告的IO类型,常用的有:

#define __SI_POLL 0
#define POLL_IN (__SI_POLL | 1) /* data input available, 有接收数据可读 */
#define POLL_OUT (__SI_POLL | 2) /* output buffers available, 有输出缓存可写 */
#define POLL_MSG (__SI_POLL | 3) /* input message available, 有输入消息可读 */
#define POLL_ERR (__SI_POLL | 4) /* i/0 error, I/O错误 */
#define POLL_PRI (__SI_POLL | 5) /* high priority input available, 有紧急数据可读 */
#define POLL_HUP (__SI_POLL | 6) /* device disconnected, 设备关闭或文件关闭,无法继续读写 */

how为函数的处理方式,band为通告的IO类型。

static inline void sk_wake_async(struct sock *sk, int how, int band)
{
if (sock_flag(sk, SOCK_FASYNC)) /* sock需要支持异步通知 */
sock_wake_async(sk->sk_socket, how, band);
}
int sock_wake_async(struct socket *sock, int how, int band)
{
struct socket_wq *wq; if (! sock)
return -1; rcu_read_lock();
wq = rcu_dereference(sock->wq); /* socket的等待队列和异步通知队列 */ if (! wq || !wq->fasync_list) { /* 如果有队列没有实例 */
rcu_read_unlock();
return -1;
} switch(how) {
/* 检测应用程序是否通过recv()类调用来等待接收数据,如果没有才发送SIGIO信号 */
case SOCK_WAKE_WAITD:
if (test_bit(SOCK_ASYNC_WAITDATA, &sock->flags))
break;
goto call_kill; /* 检测sock的发送队列是否曾经到达上限,如果有的话发送SIGIO信号 */
case SOCK_WAKE_SPACE:
if (! test_and_clear_bit(SOCK_ASYNC_NOSPACE, &sock->flags))
break;
/* fall_through */ case SOCK_WAKE_IO: /* 直接发送SIGIO信号 */
call_kill:
/* 发送SIGIO信号给异步通知队列上的进程,告知IO消息 */
kill_fasync(&wq->fasync_list, SIGIO, band);
break; case SOCK_WAKE_URG:
/* 发送SIGURG信号给异步通知队列上的进程 */
kill_fasync(&wq->fasync_list, SIGURG, band);
} rcu_read_unlock();
return 0;
}

和设备驱动一样,最终调用kill_fasync()来发送信号给用户进程。

void kill_fasync(struct fasync_struct **fp, int sig, int band)
{
/* First a quick test without locking: usually the list is empty. */
if (*f) {
rcu_read_lock();
kill_fasync_rcu(rcu_dereference(*fp), sig, band);
rcu_read_unlock();
}
}
static void kill_fasync_rcu(struct fasync_struct *fa, int sig, int band)
{
while (fa) {
struct fown_struct *fown;
unsigned long flags; if (fa->magic != FASYNC_MAGIC) {
printk(KERN_ERR "kill_fasync: bad magic number in fasync_struct!\n");
return;
} spin_lock_irqsave(&fa->fa_lock, flags);
if (fa->fa_file) {
fown = &fa->file->f_owner; /* 持有文件的进程 */ /* Don't send SIGURG to processes which have not set a queued signum:
* SIGURG has its own default signalling mechanism. */ if (! (sig == SIGURG && fown->signum == 0))
send_sigio(fown, fa->fa_fd, band); /* 发送信号给持有文件的进程 */
}
spin_unlock_irqrestore(&fa->fa_lock, flags); fa = rcu_dereference(fa->fa_next); /* 指向下一个异步通知结构体 */
}
}

Socket层实现系列 — 信号驱动的异步等待的更多相关文章

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

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

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

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

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

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

  4. Socket层实现系列 — I/O事件及其处理函数

    主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/ ...

  5. UNIX网络编程读书笔记:I/O模型(阻塞、非阻塞、I/O复用、信号驱动、异步)

    I/O模型 UNIX下可用的5种I/O模型: (1)阻塞I/O (2)非阻塞I/O (3)I/O复用(select和poll) (4)信号驱动I/O(SIGIO) (5)异步I/O 对于一个套接口上的 ...

  6. IO模型浅析-阻塞、非阻塞、IO复用、信号驱动、异步IO、同步IO

    最近看到OVS用户态的代码,在接收内核态信息的时候,使用了Epoll多路复用机制,对其十分不解,于是从网上找了一些资料,学习了一下<UNIX网络变成卷1:套接字联网API>这本书对应的章节 ...

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

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

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

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

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

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

随机推荐

  1. electron-vue 初体验

    注意事项 首先确保node和npm是最新版本 避免使用镜像(我淘宝镜像安装有报错现象) 避免window的一些坑 若上一项检查完成,我们可以继续设置所需的构建工具.使用 windows-build-t ...

  2. 小白的Python之路_day2

    Python 的逻辑运算符具有短路原则,例如: or 运算符前面只要是 True,后面都不需要看了,结果就是 True.  Python 中表示为真必须用 True,如果用 true 则会当成是变量, ...

  3. Go 语言循环语句

    在不少实际问题中有许多具有规律性的重复操作,因此在程序中就需要重复执行某些语句. 以下为大多编程语言循环程序的流程图: Go 语言提供了以下几种类型循环处理语句: 循环类型 描述 for 循环 重复执 ...

  4. JavaSE基础问答

    1.JAVA的基本数据类型有哪些? JAVA的基本数据类型分为4类8种,就是整形 byte.short.int.long.浮点型 float 跟double,字符型 char,跟布尔型 true和 f ...

  5. 自定义view实现阻尼效果的加载动画

    效果: > 需要知识: 1. 二次贝塞尔曲线 2. 动画知识 3. 基础自定义view知识 先来解释下什么叫阻尼运动 阻尼振动是指,由于振动系统受到摩擦和介质阻力或其他能耗而使振幅随时间逐渐衰减 ...

  6. 安卓高级5 zXing

    ZXing作者的github地址: https://github.com/zxing/zxing 这里为大家也提供一个封装好的最新的ZXing Lib: https://github.com/xuyi ...

  7. Bootstrap3 排版-标题

    HTML 中的所有标题标签,<h1> 到 <h6> 均可使用.另外,还提供了 .h1 到 .h6 类,为的是给内联(inline)属性的文本赋予标题的样式. h1. Boots ...

  8. chrome浏览器不兼容jQuery Mobile问题解决

    最近在学习jQuery Mobile.第一次运行例子的时候发现chrome总是等待,查看后台报错.错误如下所示: 最后在stackoverflow上找到一个解决方案:将以下代码放在 jquery.mo ...

  9. MySQL连接及基本信息查看命令小结

    前言 学习PHP就不得不提MySQL,虽然有phpMyadmin这样的工具可以图形化操作数据库,但我还是想借学习PHP的机会使用下命令行方式操作数据库.以下就是我的学习小结,包括命令行连接数据库,查看 ...

  10. Android开发之手把手教你写ButterKnife框架(一)

    欢迎转载,转载请标明出处: http://blog.csdn.net/johnny901114/article/details/52662376 本文出自:[余志强的博客] 一.概述 JakeWhar ...