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

主要内容: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); /* 指向下一个异步通知结构体 */
    }
}
时间: 2024-08-05 19:37:05

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

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

主要内容:Socket的同步等待机制,connect和accept等待的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 概述 socket上定义了几个IO事件:状态改变事件.有数据可读事件.有发送缓存可写事件.有IO错误事件. 对于这些事件,socket中分别定义了相应的事件处理函数,也称回调函数. Socket I/O事件的处理过程中,要使用到sock上的两个队列:等待队列和异步通知队列,这两个队列中 都保存着等待该Socket I/O事件

信号驱动和异步驱动的区别

5种I/O模型: 1.阻塞I/O 2.非阻塞I/O 3.异步I/O 4.信号驱动I/O 5.I/O复用 信号驱动和异步驱动的区别 信号驱动IO是指:进程预先告知内核,使得 当某个socketfd有events(事件)发生时,内核使用信号通知相关进程. 异步IO(Asynchronous IO)是指:进程执行IO系统调用(read / write)告知内核启动某个IO操作,内核启动IO操作后立即返回到进程.IO操作即内核当中的服务例程. 异步I/O和信号驱动I/O的区别很容易被混淆.前者与后者的区

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

主要内容:connect()的Socket层实现.期间进程的睡眠和唤醒. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 应用层 int connect(int sockfd, const struct sockaddr *serv_addr, socklen_t addrlen); Connects the socket referred to by the file descriptor sockfd to the address specifi

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

主要内容:Socket I/O事件的定义.I/O处理函数的实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd I/O事件定义 sock中定义了几个I/O事件,当协议栈遇到这些事件时,会调用它们的处理函数. struct sock { ... struct socket_wq __rcu *sk_wq; /* socket的等待队列和异步通知队列 */ ... /* callback to indicate change in the state

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

主要内容:socket发送函数的系统调用.Socket层实现. 内核版本:3.15.2 我的博客:http://blog.csdn.net/zhangskd 发送流程图 以下是send().sendto().sendmsg()和sendmmsg()的发送流程图,这四个函数除了在系统调用层面 上有些差别,在Socket层和TCP层的实现都是相同的. 应用层 应用层可以使用以下Socket函数来发送数据: ssize_t write(int fd, const void *buf, size_t c

五种网络IO模型-阻塞I/O、非阻塞I/O、I/O多路复用、信号驱动I/O、异步I/O

1.阻塞I/O模型 我去面馆吃面,点了碗面条,我也不知道做面条需要多久,也不敢出去,只能在那里坐着等.等值师傅做完面,我吃完面条后,再去逛街.这里的吃面就是I/O操作. 2.非阻塞I/O模型 我不甘心在这里等着无聊,我想这段时间继续逛商场,但是又怕面条做好了,所以,我逛一会就回来吧台看一眼!为了吃碗面,来来回回跑,差点没跑断腿! 3.I/O多路复用 我同时在不同的地方点了菜,要跑来跑去的轮询几十遍,累死了.后来,善良的管理员安装了电子屏幕:上面写着每个菜的状态,我需要去看屏幕,就知道数据准备好了

深入浅出~Linux设备驱动之异步通知和异步I/O

在设备驱动中使用异步通知可以使得对设备的访问可进行时,由驱动主动通知应用程序进行访问.因此,使用无阻塞I/O的应用程序无需轮询设备是否可访问,而阻塞访问也可以被类似“中断”的异步通知所取代.异步通知类似于硬件上的“中断”概念,比较准确的称谓是“信号驱动的异步I/O". 1.异步通知的概念和作用 影响:阻塞--应用程序无需轮询设备是否可以访问 非阻塞--中断进行通知 即:由驱动发起,主动通知应用程序 2.linux异步通知编程 2.1 linux信号 作用:linux系统中,异步通知使用信号来实现

udp套接字使用信号驱动式I/O

信号驱动式I/O的本质就是:进程预先告知内核当某个描写叙述符发生事件时,内核会向该进程发送SIGIO信号通知进程,进程可在信号处理函数中进行处理 进程能够通过fcntl打开O_ASYNC标志或ioctl打开FIOASYNC标志来通知内核,二者的差别是一些系统不支持fcntl,所以应尽量使用ioctl 对于TCP套接字产生SIGIO信号的条件: 1.监听套接字上有新连接请求完毕 2.某个断连请求发起 3.某个断连请求完毕 4.数据到达套接字 5.数据已从套接字发送走(输出缓冲区有空暇空间) 6.发

IO的多路复用和信号驱动

Linux为多路复用IO提供了较多的接口,有select(),pselect(),poll()的方式,继承自BSD和System V 两大派系. select模型比较简单,“轮询”检测fd_set的状态,然后再采取相应的措施. 信号驱动模型有必要仔细研究一下,一般有如下步骤: 设置安装函数,信号是驱动信号是SIGIO(最好使用sigaction的方式,方便设置flag为SA_RESTART,因为client中读取终端的syscall可能会被中断,有必要重启.当然,使用signal()的方式然后再