主要内容: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. 58. Length of Last Word(easy, 字符串问题)

    Given a string s consists of upper/lower-case alphabets and empty space characters ' ', return the l ...

  2. Java不走弯路教程(1.环境搭建)

    1.环境搭建在开始写第一个Java程序之前,我们需要做一些简单的准备工作. 1.1 还记得DOS吗 我们可以通过图形界面来操作我们的电脑.但作为程序员,你首先需要学会用命令行的方式来操作电脑,因为不是 ...

  3. 使用Vitrualbox虚拟Windows Server 2016系统的一些常见问题

    所有的问题都是基于有路由器的网络环境下进行设置的,所有的vitrualbox都是安装在win7或者win2008系统上进行的. 1.无法创建x64系统? 解决方法:1)进入主板bios设置,开启cpu ...

  4. Oracle 11g 中SQL性能优化新特性之SQL性能分析器(SQLPA)

    Oracle11g中,真实应用测试选项(the Real Application Testing Option)提供了一个有用的特点,叫SQL性能分析器(SQL Performance Analyze ...

  5. Docker服务端防护

    运行一个容器或应用程序的核心是通过 Docker 服务端.Docker 服务的运行目前需要 root 权限,因此其安全性十分关键. 首先,确保只有可信的用户才可以访问 Docker 服务.Docker ...

  6. Docker 镜像

    Docker 镜像就是一个只读的模板. 例如:一个镜像可以包含一个完整的 ubuntu 操作系统环境,里面仅安装了 Apache 或用户需要的其它应用程序. 镜像可以用来创建 Docker 容器. D ...

  7. myeclipse中修改maven的默认仓库位置

     1.本地需要安装Maven.这里假设安装在D:\Program Files\apache-maven-2.2.1       2.修改本地maven库的路径:在D:\Program Files\ap ...

  8. SpringBatch的核心组件JobLauncher和JobRepository

    Spring Batch的框架包括启动批处理作业的组件和存储Job执行产生的元数据.因此只需掌握配置这个基础框架在批处理应用程序中即启动Jobs并存储Job元数据. 组件:Job Launcher和J ...

  9. iOS 中隐藏UITableView最后一条分隔线

    如何优雅的隐藏UITableView中最后一条分割线? 这个问题是很常见,却又不太容易解决的. 可能通常的做法都是隐藏UITableView的分割线,自定义一条. 最近在使用弹出菜单的时候,同样遇到了 ...

  10. Android N(7.0) 被美翻的新特性!

    Tamic 专注移动开发!更多文章请关注 Csdn: http://blog.csdn.net/sk719887916/article/details/52612444 $ http://www.ji ...