NIO底层的实现

poll函数的定义如下:

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

 其中的参数类型pollfd的定义如下:

struct pollfd {
    int   fd;
    short events; 
    short revents;
};

poll系统调用对应的内核中的代码为sys_poll,整体的流程是先把用户想知道的事件从用户内存弄到内核空间中来。然后调用文件的poll函数来监听对应的事件。如果在指定的时间内发生了,那么就要通知用户态的进程了。为了更清楚地阅读代码,先来看涉及到的数据结构,poll_wqueues用来保存发生的事件:

poll_list用来保存从用户空间得到的用户感兴趣的事件:

asmlinkage long sys_poll(struct pollfd __user * ufds, unsigned int nfds, long timeout)
{
    struct poll_wqueues table;
    int fdcount, err;
    unsigned int i;
    struct poll_list *head;
    struct poll_list *walk;

    if (nfds > current->files->max_fdset && nfds > OPEN_MAX)
        return -EINVAL;
    if (timeout) {
        if ((unsigned long) timeout < MAX_SCHEDULE_TIMEOUT / HZ)
            timeout = (unsigned long)(timeout*HZ+999)/1000+1;
        else
            timeout = MAX_SCHEDULE_TIMEOUT;
    }
    // 初始化poll_wqueues,设置了回调函数为__pollwait
    poll_initwait(&table);
    
    head = NULL;
    walk = NULL;
    i = nfds;
    err = -ENOMEM;
    // 每次都分配一个页面来保存传入的pollfd数组,下面的循环用来分割
    while(i!=0) {
        struct poll_list *pp;
        pp = kmalloc(sizeof(struct poll_list)+ sizeof(struct pollfd)*(i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i), GFP_KERNEL);
        if(pp==NULL)
            goto out_fds;
        pp->next=NULL;
        pp->len = (i>POLLFD_PER_PAGE?POLLFD_PER_PAGE:i);
        if (head == NULL)
            head = pp;
        else
            walk->next = pp;
            
        walk = pp;
        if (copy_from_user(pp->entries, ufds + nfds-i, sizeof(struct pollfd)*pp->len)) {
            err = -EFAULT;
            goto out_fds;
        }
        i -= pp->len;
    }
    // 真正的执行
    fdcount = do_poll(nfds, head, &table, timeout);
    walk = head;
    err = -EFAULT;
    // 如果发现事件发生了,这些仍然在内核空间,需要通知用户进程
    while(walk != NULL) {
        struct pollfd *fds = walk->entries;
        int j;
        for (j=0; j < walk->len; j++, ufds++) {
            if(__put_user(fds[j].revents, &ufds->revents))
                goto out_fds;
        }
        walk = walk->next;
    }
    err = fdcount;
    if (!fdcount && signal_pending(current))
        err = -EINTR;
out_fds:
    // 在离开之前释放掉分配的内存
    walk = head;
    while(walk!=NULL) {
        struct poll_list *pp = walk->next;
        kfree(walk);
        walk = pp;
    }
    poll_freewait(&table);
    return err;
}

在poll_wqueues中我们看到了一个回调函数poll_queue_proc,在初始化poll_wqueues的时候(也就是poll_initwait函数中)会被初始化为__pollwait。

void __pollwait(struct file *filp, wait_queue_head_t *wait_address, poll_table *_p)
{
    // 根据_p的位置就能找到所在的poll_wqueues所在的位置
    struct poll_wqueues *p = container_of(_p, struct poll_wqueues, pt);
    struct poll_table_page *table = p->table;
    // 如果为空或者没有足够的空间
    if (!table || POLL_TABLE_FULL(table)) {
        struct poll_table_page *new_table;
        new_table = (struct poll_table_page *) __get_free_page(GFP_KERNEL);
        if (!new_table) {
            p->error = -ENOMEM;
            __set_current_state(TASK_RUNNING);
            return;
        }
        new_table->entry = new_table->entries;
        new_table->next = table;
        p->table = new_table;
        table = new_table;
    }
    {
        struct poll_table_entry * entry = table->entry;
        table->entry = entry+1;
        get_file(filp);
        entry->filp = filp;
        entry->wait_address = wait_address;
        // 初始化等待队列项,挂载当前线程
        init_waitqueue_entry(&entry->wait, current);
        add_wait_queue(wait_address,&entry->wait);
    }
}

接下来会调动do_poll函数,在这个函数里并没有做什么实际性的工作,循环处理poll_list下面的每个页面(每个页面上有用户进程感兴趣的事件),然后再do_pollfd函数中循环处理一个页面上所有的pollfd。这个函数中实际上还是循环调用file_operation结构中的poll函数。在返回的结果中判断我们感兴趣的事件有没有发生。具体的代码如下:

static void do_pollfd(unsigned int num, struct pollfd * fdpage, poll_table ** pwait, int *count)
{
    int i;
    // 循环遍历一个页面上保存的所有的pollfd
    for (i = 0; i < num; i++) {
        int fd;
        unsigned int mask;
        struct pollfd *fdp;
        mask = 0;
        fdp = fdpage+i;
        fd = fdp->fd;
        if (fd >= 0) {
            struct file * file = fget(fd);
            mask = POLLNVAL;
            if (file != NULL) {
                mask = DEFAULT_POLLMASK;
                if (file->f_op && file->f_op->poll)
                    mask = file->f_op->poll(file, *pwait);
                // 用来判断是不是自己感兴趣的事件发生了
                mask &= fdp->events | POLLERR | POLLHUP;
                fput(file);
            }
            // 如果发生了,就将pwait清空,后面就不用再把当前线程挂入等待队列
            if (mask) {
                *pwait = NULL;
                (*count)++;
            }
        }
        // 把发生的事件记录下来,用来返回
        fdp->revents = mask;
    }
}

到此为止,如果发现我们感兴趣的事件发生了,那就把回调函数设置为null,这样就算以后再发现有事件发生也不会做任何实质性的操作,而是记录一下数目而已。相应的在do_poll函数中发现对应的事件发生了就会中断循环返回。再底层的比如file_operation中的poll是怎么实现的话以后有事件再看。

转载于:https://www.cnblogs.com/ggzwtj/archive/2012/06/11/2544487.html

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
在 C++ 中,可以使用 Boost.Asio 库来实现 NIO(New Input/Output)模型。Boost.Asio 提供了高效的异步网络编程和底层 I/O 操作支持,可以实现 TCP、UDP、SSL 等协议的数据传输。 下面是一个简单的 Boost.Asio TCP 客户端的实现示例: ```cpp #include <iostream> #include <boost/asio.hpp> using boost::asio::ip::tcp; int main() { try { boost::asio::io_context io_context; // 创建 TCP socket tcp::socket socket(io_context); // 连接服务器 tcp::resolver resolver(io_context); boost::asio::connect(socket, resolver.resolve("localhost", "daytime")); // 发送请求 std::string request = "Hello, server!"; boost::asio::write(socket, boost::asio::buffer(request)); // 接收响应 char response[1024]; boost::asio::read(socket, boost::asio::buffer(response, request.size())); // 输出响应 std::cout << "Response: " << response << std::endl; } catch (std::exception& e) { std::cerr << "Exception: " << e.what() << std::endl; } return 0; } ``` 在这个示例中,首先创建了一个 io_context 对象,它是 Boost.Asio 库的核心。然后创建了一个 TCP socket 对象,并使用 resolver 对象解析服务器地址。接着使用 connect 函数连接服务器,并使用 write 函数发送请求,使用 read 函数接收响应。最后输出响应字符串。 需要注意的是,上述示例中的所有 I/O 操作都是异步的,要使用回调函数来进行处理。例如,可以使用 async_read 函数来进行异步读取操作,然后在回调函数中处理读取到的数据。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值