对于一次IO访问,例如read操作,数据会先被拷贝到操作系统内核缓存区,然后才从操作系统内核缓存区拷贝到应用程序的地址空间。它会经历两个阶段:

1) 等待数据准备

2) 将数据拷贝到用户进程中

正是因为如此,Linux下面有5种IO模式

◆阻塞型IO

◆非阻塞型IO

◆IO多路复用

◆信号驱动

◆异步IO


使用场景

IO复用是为了解决大量客户端访问问题而提出来的,它与多进程/多线程技术相比,系统开销小,系统不需要创建和维护这么多的进程/线程。

IO复用就是适用的场合:

1) 客户处理多个fd

2) 一个客户处理多个socket

3) 一个tcp服务器既要处理监听socket,又要处理已连接socket

4) 一个服务器要处理多个服务或协议

 

select、poll和epoll都是使用IO复用的机制,下面分析select函数。


ios中解决cell的复用 io复用select_内核


说明:

参数

说明

nfds

待测试的fd个数,它是最大fd加1,这样从0~nfds-1均被测试

readfds

指定要让内核测试的读、写和异常fd。

Fd_set是文件描述符fd的集合。

FD_CLR宏:将指定的fd从集合中删除

FD_ISSET宏:检查集合中指定的fd是否可以读写

FD_SET宏:将指定的描述符加入集合中

FD_ZERO宏:清空集合

writefds

exceptfds

timeout

等待所指定fd中任何一个就绪等待的时间。

永远等待:仅在至少有一个fd准备好的情况下返回,参数设置为NULL

等待一段时间:在至少一个fd准备好后返回,但是不能超过该参数指定的秒数和微秒数。

不等待:检查描述符立即返回,也即是轮询。该参数中的秒数和微秒数都指定为0。


内核代码分析

下面看下Linux内核中select实现的相关代码,核心处理函数为core_sys_select

int core_sys_select(int n, fd_set __user *inp, fd_set __user *outp,                                                             

        fd_set __user *exp, struct timespec *end_time)  

 {  

   /* Allocate small arguments on the stack to save memory and be faster */   

   long stack_fds[SELECT_STACK_ALLOC/sizeof(long)];  

    

   /* max_fds can increase, so grab it once to avoid race */   

   rcu_read_lock();  

   fdt = files_fdtable(current->files );  /*获取当前进程的文件描述符表*/

   max_fds = fdt->max_fds ;  

   rcu_read_unlock();  

   if (n > max_fds)  

             n = max_fds;  


数组stack_fds空间大小为256字节。如果应用层传入的参数n大于当前进程文件描述符表的max_fds,则修正n值等于max_fds。

/*  

* We need 6 bitmaps (in/out/ex for both incoming and outgoing),  

* since we used fdset we need to allocate memory in units of  

* long-words.  

*/  

size = FDS_BYTES(n);  

bits = stack_fds;  

if (size > sizeof(stack_fds) / 6) {  

    /* Not enough space in on-stack array; must use kmalloc */                                                                           

    bits = kmalloc(6 * size, GFP_KERNEL);  

}  

fds.in      = bits;  

fds.out     = bits +   size;  

fds.ex      = bits + 2*size;  

fds.res_in  = bits + 3*size;  

fds.res_out = bits + 4*size;  

fds.res_ex  = bits + 5*size;  

size值为n个fd需要的字节空间,每个fd占1bit。每个fd总共需要6bit空间,分别存储读in、写out、异常ex以及结果res_in、res_out和res_ex。

当size*6空间大于stack_fds数组空间大小时,调用kmalloc重新分配空间。

fds的内存布局如下图所示:


ios中解决cell的复用 io复用select_ios中解决cell的复用_02

if ((ret = get_fd_set(n, inp, fds.in)) ||  

(ret = get_fd_set(n, outp, fds.out)) ||                                                                                                             

(ret = get_fd_set(n, exp, fds.ex)))     /*从用户空间拷贝fd set*/

    goto  out;  

zero_fd_set(n, fds.res_in);  

zero_fd_set(n, fds.res_out);  

zero_fd_set(n, fds.res_ex);       /*初始化fd set的结果集*/

ret = do_select(n, &fds, end_time);  


if (set_fd_set(n, inp, fds.res_in) ||  

    set_fd_set(n, outp, fds.res_out) ||  

    set_fd_set(n, exp, fds.res_ex))    /*将结果集拷贝回用户空间*/

    ret = - EFAULT;  


get_fd_set函数就是调用copy_from_user从用户态空间拷贝fds的读fd in、写fd out和异常fd ex。接着初始化fds的结果集的读fd res_in、写fd res_out和异常fd res_ex。

do_select是select实现的核心函数,将在下面进行分析。

set_fd_set调用__copy_to_user将结果集的读fd res_in、写fd res_out和异常fd res_ex拷贝到用户态空间。

 

下面接着分析do_select函数,代码片段如下:

rcu_read_lock();   

retval = max_select_fd(n, fds);  /*得到要监测的最大的描述符值*/                                                                                     

rcu_read_unlock();  

n = retval;  

poll_initwait(&table);

wait = &table.pt;  

if (end_time && ! end_time->tv_sec && !end_time->tv_nsec ) { /*定时器值为0表示不等待*/

    wait->_qproc =  NULL;  

    timed_out = 1;  

}  


max_select_fd函数根据已打开fd位图检查用户打开的fd,要求对应的fd必须打开,并返回最大的fd值。

poll_initwait函数将当前进程加入等待队列table,并将table添加到poll_table中。

for (;;) {  
    inp = fds->in ; outp = fds->out ; exp = fds->ex ;  
    rinp = fds->res_in ; routp = fds->res_out ; rexp = fds->res_ex ;                                                                          
    for (i = 0; i < n; ++rinp, ++routp, ++rexp) {     
        in = *inp++; out = *outp++; ex = *exp++;  
        all_bits = in | out | ex;
        if (all_bits == 0) {     /*此位图区间没有我们关心的fd*/    
            i += BITS_PER_LONG;  /*以BITS_PER_LONG个bit步长移动*/
            continue;  
        }  
        for (j = 0; j < BITS_PER_LONG; ++j, ++i, bit <<= 1) {
            if (! (bit & all_bits))    /*遍历BITS_PER_LONG个bit中的每一bit*/
                continue;  
            f = fdget(i);  
            if (f.file) {  
                f_op = f.file->f_op ;  
                if (f_op->poll ) {  
                    wait_key_set(wait, in, out, bit, busy_flag);  
                    mask = (*f_op->poll )(f.file, wait); /*对于socket描述符,poll函数应该是sock_poll*/   
                }  
                fdput(f);        
            if ((mask & POLLIN_SET) && (in & bit)) {  /*关心的fd的读就绪*/
                res_in |= bit;  /*更新读结果fd*/
                retval++;     /*增加就绪个数*/
                wait->_qproc =  NULL;  
            }  
            if ((mask & POLLOUT_SET) && (out & bit)) {  /*关心的fd的写就绪*/
                res_out |= bit;  /*更新写结果fd*/
                retval++;       /*增加就绪个数*/
                wait->_qproc =  NULL;  
            }  
            if ((mask & POLLEX_SET) && (ex & bit)) {  /*关心的fd的异常就绪*/
                res_ex |= bit;   /*更新异常结果fd*/
                retval++;  
                wait->_qproc =  NULL;  
            }  
        } 
    }  
    if (res_in)  
        *rinp = res_in;  /*遍历完此fd区间后更新读结果集*/
    if (res_out)  
        *routp = res_out;  /*遍历完此fd区间后更新写结果集*/
    if (res_ex)  
        *rexp = res_ex;  /*遍历完此fd区间后更新异常结果集*/
    cond_resched();  /*进行一次调度让其他进程运行,后面由等待队列唤醒*/     
}

上述do_select代码片段就是遍历位图中我们所关心的fd,并更新读、写、异常结果集。