精华内容
下载资源
问答
  • MySQL select实现原理

    千次阅读 2017-10-29 22:46:29
    工作中需要借鉴MySQL对于select的具体实现,在网上搜了很久,几乎都是介绍原理的,对于实现细节都没有介绍,无奈之下只得自己对着源码gdb。结合以前对于sql解析的了解,对mysql select的具体实现有了大致的了解,...
     

    工作中需要借鉴MySQL对于select的具体实现,在网上搜了很久,几乎都是介绍原理的,对于实现细节都没有介绍,无奈之下只得自己对着源码gdb。结合以前对于sql解析的了解,对mysql select的具体实现有了大致的了解,总结一下。

    如果要gdb单步调试,需要在编译MySQl时加上debug选项,参见这篇博客.编译好以后就可以用gdb启动了。如果希望mysql运行时有日志输出,可以指定输出文件的路径和日志类型:--debug=d,info,error,query,enter,general,where:O,/tmp/mysqld.trace日志对MySQl内部逻辑的了解还是挺有用的。

    MySQl在设计时,采用了这样的思路:针对主要应用场景选择一个或几个性能优异的核心算法作为引擎,然后努力将一些非主要应用场景作为该算法的特例或变种植入到引擎当中。具体而言,MySQL的select查询中,核心功能就是JOIN查询,因此在设计时,核心实现JOIN功能,对于其它功能,都通过转换为JOIN来实现。

    比如select id, name from student;,MySQL在执行时,也会转换为JOIN来操作。

    用gdb单步跟踪后可以看出MySQL的执行过程大致如下:

    1. 收到请求后分配线程处理;
    2. sql解析,MySQL解析完sql以后,会生成很多item类。item类是sql解析和执行中最重要的类之一,对于它的介绍可以参见这里
    3. 执行sql,可以看到JOIN::exec,MySQL是将任何select都转换为JOIN来处理的。

    以sql:select A.id, B.score from student A left join subject B on A.id=B.id where A.age > 10 and B.score > 60;为例来说明上面的步骤3的具体过程。

    首先,MySQL在执行sql之前,会对sql进行优化处理,具体是在JOIN::optimise函数中完成。MySQL针对JOIN的优化做的非常好,因此才会将其他操作都转换为性能实现的非常好的JOIN操作。对于上面的sql,MySQL在执行时,会将join的key也转换为一个where条件:A.id=B.id来执行,那么经过处理后,上面的sql就有了3个where条件:

    1. A.age > 10
    2. A.id = B.id
    3. B.score > 60

    预处理完以后开始执行,即JOIN::exec函数,首先会调用send_fields函数,将最终结果的信息返回,然后调用do_select。MySQL的join是采用nested loop join,可以参见这篇博客。在do_select函数中,通过调用sub_select函数来具体实现join功能。

    在上面的例子中,需要完成2个join:先join表A,再join表B(这里请注意,不是涉及几个表,就需要join几个表,MySQL的join优化还是挺强大的,具体解释见后)。在MySQL进行sql解析时,会生成一个需要join的表的list,后面会挨个对该list的表进行join操作。

    继续gdb,在sub_select函数中,可以看到这样一行代码:(*join_tab->read_first_record)(join_tab)这个就是读取表A的第一行结果,可以看join_tab里面的信息有表A的名字。接下来就是很关键的一个函数:evaluate_join_record,这个函数主要做2件事:

    1. 将当前已经拿到的信息进行where条件计算,判断是否需要继续往下走;
    2. 递归JOIN;

    还是以上面的sql为例,首先执行第一个join,此时会遍历表A的每一行结果,每遍历一个结果,会进行where条件的判断。这里需要注意:当前的where条件判断只会判断已经读出来的列,由于此时只读出来表A的数据,因此现在只能对第一个where条件,即A.age > 10进行判断,如果满足,则递归调用join:sql_select.cc: 11037 rc=(*join_tab->next_select)(join, join_tab+1, 0);,这里的next_select函数就是sub_select,MySQL就是这样来实现递归操作的。如果不满足,则不会递归join,而是继续到下一行数据,从而达到剪枝的目的。

    继续跟下去,此时通过上面的next_select递归的又调用到sub_select上,同样会走上面的逻辑,即先read_first_record,然后evaluate_join_record,这里由于表A和表B的数据都有了,于是可以对上面后面2个where条件:A.id = B.idB.score > 60进行判断了。到此,所有的where条件都已经判断完毕,如果当前行对3个where条件都满足,就可以将结果输出。

    以上就是select实现的大体过程,主要有2点,一个是join是采用递归实现的,另一个是每读一个表的数据,会将当前的where条件进行计算,剪枝。还有一个细节没有提到:MySQL是如何进行where条件判断的?或者说,MySQL是如何进行表达式计算的?

    答案就是前面提到的item类。当MySQL在解析时,会将sql解析为很多item,同时也会建立各个item之间的关系。对于表达式,会生成一棵语法树。比如表达式:B.score > 60,此时会生成3个item:B.score>60,其中B.score60分别是>的左右孩子,这样,求表达式的值时,就是求>val_int(),然后就会递归的调用左右子树的val_int(),再做比较判断即可。

    还有一个问题:如何求B.scoreval_int()?对于此问题的答案我没有具体看过,根据之前一个同事的sql实现方式,我是这样推测的:B.score是数据表中的真实值,因此它的值肯定是通过去表中获取。在item类中,有一个函数:fix_field,它是用于告诉外界,去哪里获取此item的值,往往在sql执行的预处理阶段调用。于是在预处理时,告诉该item去某个固定buffer读取结果,同时,每当从表中读出一行数据时,将该数据保存在该buffer中,这样就可以将两者关联起来。这个部分纯属个人推测,感兴趣的同学可以自己根据源码看看。

    再回到之前提到的一点,如果我们将sql稍微改一下:select A.id, B.score from student A left join subject B on A.id=B.id where B.score > 60;,即去掉第一个where条件,此时会发生什么?

    答案是,MySQL会做一个优化,将sql转换为select B.id, B.score from subject B where B.score > 60,这样就不需要A同B join的逻辑了。实际上最开始我在gdb时就用的这条sql,结果死活看不到递归调用sub_select的场景,还以为原理不对,后来才发现是MySQL优化捣的乱。

    作者:deepskywalker 链接:http://www.jianshu.com/p/NsWbRv 來源:简书 著作权归作者所有。商业转载请联系作者获得授权,非商业转载请注明出处。
    展开全文
  • Select函数实现原理分析

    千次阅读 2016-08-03 15:33:24
    Select函数实现原理分析  转载至:http://blog.chinaunix.net/uid-20643761-id-1594860.html select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断...

    Select函数实现原理分析

     转载至:http://blog.chinaunix.net/uid-20643761-id-1594860.html

    select需要驱动程序的支持,驱动程序实现fops内的poll函数。select通过每个设备文件对应的poll函数提供的信息判断当前是否有资源可用(如可读或写),如果有的话则返回可用资源的文件描述符个数,没有的话则睡眠,等待有资源变为可用时再被唤醒继续执行。
     
    下面我们分两个过程来分析select:
     

    1. select的睡眠过程

     
    支持阻塞操作的设备驱动通常会实现一组自身的等待队列如读/写等待队列用于支持上层(用户层)所需的BLOCK或NONBLOCK操作。当应用程序通过设备驱动访问该设备时(默认为BLOCK操作),若该设备当前没有数据可读或写,则将该用户进程插入到该设备驱动对应的读/写等待队列让其睡眠一段时间,等到有数据可读/写时再将该进程唤醒。
     
    select就是巧妙的利用等待队列机制让用户进程适当在没有资源可读/写时睡眠,有资源可读/写时唤醒。下面我们看看select睡眠的详细过程。
     
    select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。当select循环遍历完所有fd_set内指定的文件描述符对应的poll函数后,如果没有一个资源可用(即没有一个文件可供操作),则select让该进程睡眠,一直等到有资源可用为止,进程被唤醒(或者timeout)继续往下执行。
     
    下面分析一下代码是如何实现的。
    select的调用path如下:sys_select -> core_sys_select -> do_select

    其中最重要的函数是do_select, 最主要的工作是在这里, 前面两个函数主要做一些准备工作。do_select定义如下:

    int do_select(int n, fd_set_bits *fds, s64 *timeout)
    {
             struct poll_wqueues table;
             poll_table *wait;
             int retval, i;
     
             rcu_read_lock();
             retval = max_select_fd(n, fds);
             rcu_read_unlock();
     
             if (retval < 0)
                       return retval;
             n = retval;
     
             poll_initwait(&table);
             wait = &table.pt;
             if (!*timeout)
                       wait = NULL;
             retval = 0;        //retval用于保存已经准备好的描述符数,初始为0
             for (;;) {
                       unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
                       long __timeout;
     
                       set_current_state(TASK_INTERRUPTIBLE);    //将当前进程状态改为TASK_INTERRUPTIBLE
     
                       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) { //遍历每个描述符
                                unsigned long in, out, ex, all_bits, bit = 1, mask, j;
                                unsigned long res_in = 0, res_out = 0, res_ex = 0;
                                const struct file_operations *f_op = NULL;
                                struct file *file = NULL;
     
                                in = *inp++; out = *outp++; ex = *exp++;
                                all_bits = in | out | ex;
                                if (all_bits == 0) {
                                         i += __NFDBITS;       // //如果这个字没有待查找的描述符, 跳过这个长字(32位)
                                         continue;
                                }
     
                                for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {     //遍历每个长字里的每个位
                                         int fput_needed;
                                         if (i >= n)
                                                   break;
                                         if (!(bit & all_bits))
                                                   continue;
                                         file = fget_light(i, &fput_needed);
                                         if (file) {
                                                   f_op = file->f_op;
                                                   MARK(fs_select, "%d %lld",
                                                                     i, (long long)*timeout);
                                                   mask = DEFAULT_POLLMASK;
                                                   if (f_op && f_op->poll)
    /* 在这里循环调用所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数 */
                                                            mask = (*f_op->poll)(file, retval ? NULL : wait);
                                                   fput_light(file, fput_needed);
                                                   if ((mask & POLLIN_SET) && (in & bit)) {
                                                            res_in |= bit; //如果是这个描述符可读, 将这个位置位
                                                            retval++;  //返回描述符个数加1
                                                   }
                                                   if ((mask & POLLOUT_SET) && (out & bit)) {
                                                            res_out |= bit;
                                                            retval++;
                                                   }
                                                   if ((mask & POLLEX_SET) && (ex & bit)) {
                                                            res_ex |= bit;
                                                            retval++;
                                                   }
                                         }
                                         cond_resched();
                                }
    //返回结果
                                if (res_in)
                                         *rinp = res_in;
                                if (res_out)
                                         *routp = res_out;
                                if (res_ex)
                                         *rexp = res_ex;
                       }
                       wait = NULL;
    /* 到这里遍历结束。retval保存了检测到的可操作的文件描述符的个数。如果有文件可操作,则跳出for(;;)循环,直接返回。若没有文件可操作且timeout时间未到同时没有收到signal,则执行schedule_timeout睡眠。睡眠时间长短由__timeout决定,一直等到该进程被唤醒。
    那该进程是如何被唤醒的?被谁唤醒的呢?
    我们看下面的select唤醒过程*/
                       if (retval || !*timeout || signal_pending(current))
                                break;
                      if(table.error) {
                                retval = table.error;
                                break;
                       }
     
                       if (*timeout < 0) {
                                /* Wait indefinitely */
                                __timeout = MAX_SCHEDULE_TIMEOUT;
                       } else if (unlikely(*timeout >= (s64)MAX_SCHEDULE_TIMEOUT - 1)) {
                                /* Wait for longer than MAX_SCHEDULE_TIMEOUT. Do it in a loop */
                                __timeout = MAX_SCHEDULE_TIMEOUT - 1;
                                *timeout -= __timeout;
                       } else {
                                __timeout = *timeout;
                                *timeout = 0;
                       }
                       __timeout = schedule_timeout(__timeout);
                       if (*timeout >= 0)
                                *timeout += __timeout;
             }
             __set_current_state(TASK_RUNNING);
     
             poll_freewait(&table);
     
             return retval;
    }

    2.  select的唤醒过程

    前面介绍了select会循环遍历它所监测的fd_set内的所有文件描述符对应的驱动程序的poll函数。驱动程序提供的poll函数首先会将调用select的用户进程插入到该设备驱动对应资源的等待队列(如读/写等待队列),然后返回一个bitmask告诉select当前资源哪些可用。
    一个典型的驱动程序poll函数实现如下:
    (摘自《Linux Device Drivers – ThirdEdition》Page 165)

    static unsigned int scull_p_poll(struct file *filp, poll_table *wait)
    {
        struct scull_pipe *dev = filp->private_data;
        unsigned int mask = 0;
        /*
         * The buffer is circular; it is considered full
         * if "wp" is right behind "rp" and empty if the
         * two are equal.
         */
        down(&dev->sem);
        poll_wait(filp, &dev->inq,  wait);
        poll_wait(filp, &dev->outq, wait);
        if (dev->rp != dev->wp)
            mask |= POLLIN | POLLRDNORM;    /* readable */
        if (spacefree(dev))
            mask |= POLLOUT | POLLWRNORM;   /* writable */
        up(&dev->sem);
        return mask;
    }

    将用户进程插入驱动的等待队列是通过poll_wait做的。
    Poll_wait定义如下:

    static inline void poll_wait(struct file * filp, wait_queue_head_t * wait_address, poll_table *p)
    {
             if (p && wait_address)
                       p->qproc(filp, wait_address, p);
    }
    这里的p->qproc在do_select内poll_initwait(&table)被初始化为__pollwait,如下:

    void poll_initwait(struct poll_wqueues *pwq)
    {
             init_poll_funcptr(&pwq->pt, __pollwait);
             pwq->error = 0;
             pwq->table = NULL;
             pwq->inline_index = 0;
    }
    __pollwait定义如下:
    static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                                         poll_table *p)
    {
             struct poll_table_entry *entry = poll_get_entry(p);
             if (!entry)
                       return;
             get_file(filp);
             entry->filp = filp;
             entry->wait_address = wait_address;
             init_waitqueue_entry(&entry->wait, current);
             add_wait_queue(wait_address,&entry->wait);
    }
    通过init_waitqueue_entry初始化一个等待队列项,这个等待队列项关联的进程即当前调用select的进程。然后将这个等待队列项插入等待队列wait_address。Wait_address即在驱动poll函数内调用poll_wait(filp, &dev->inq,  wait);时传入的该驱动的&dev->inq或者&dev->outq等待队列。
     
    注: 关于等待队列的工作原理可以参考下面这篇文档:
    http://blog.chinaunix.net/u2/60011/showart_1334657.html
     
    到这里我们明白了select如何当前进程插入所有所监测的fd_set关联的驱动内的等待队列,那进程究竟是何时让出CPU进入睡眠状态的呢?
    进入睡眠状态是在do_select内调用schedule_timeout(__timeout)实现的。当select遍历完fd_set内的所有设备文件,发现没有文件可操作时(即retval=0),则调用schedule_timeout(__timeout)进入睡眠状态。
     
    唤醒该进程的过程通常是在所监测文件的设备驱动内实现的,驱动程序维护了针对自身资源读写的等待队列。当设备驱动发现自身资源变为可读写并且有进程睡眠在该资源的等待队列上时,就会唤醒这个资源等待队列上的进程。
    举个例子,比如内核的8250 uart driver:
    Uart是使用的Tty层维护的两个等待队列, 分别对应于读和写: (uart是tty设备的一种)
    struct tty_struct {
             //……
             wait_queue_head_t write_wait;
             wait_queue_head_t read_wait;
             //……
    }
    当uart设备接收到数据,会调用tty_flip_buffer_push(tty);将收到的数据push到tty层的buffer。
    然后查看是否有进程睡眠的读等待队列上,如果有则唤醒该等待会列。
    过程如下:
    serial8250_interrupt -> serial8250_handle_port -> receive_chars -> tty_flip_buffer_push ->
    flush_to_ldisc -> disc->receive_buf
    在disc->receive_buf函数内:
    if (waitqueue_active(&tty->read_wait)) //若有进程阻塞在read_wait上则唤醒
    wake_up_interruptible(&tty->read_wait);
     
    到这里明白了select进程被唤醒的过程。由于该进程是阻塞在所有监测的文件对应的设备等待队列上的,因此在timeout时间内,只要任意个设备变为可操作,都会立即唤醒该进程,从而继续往下执行。这就实现了select的当有一个文件描述符可操作时就立即唤醒执行的基本原理。
     
    Referece:
    1.       Linux Device Drivers – ThirdEdition
    2.       内核等待队列机制原理分析
    http://blog.chinaunix.net/u2/60011/showart_1334657.html
    3.       Kernel code : Linux 2.6.18_pro500 - Montavista


    展开全文
  • select实现connect超时连接

    千次阅读 2017-05-22 10:57:05
    2. 源自Berkeley的实现(和Posix.1g)有两条与select 和非阻塞IO相关的规则:   A. 当连接建立成功时,套接口描述符变成可写;  B. 当连接出错时,套接口描述符变成既可读又可写。   处理非阻塞...

    参考地址:http://blog.csdn.net/g_brightboy/article/details/8849437

                        http://blog.163.com/li_xiang1102/blog/static/60714076201110298170975/

    ###################################################################################################################################

       connect 函数的调用涉及到TCP连接的三次握手过程,通常阻塞的connect 函数会等待三次握手成功或失败后返回,0成功,-1失败。如果对方未响应,要隔6s,重发尝试,可能要等待75s的尝试并最终返回超时,才得知连接失败。即使是一次尝试成功,也会等待几毫秒到几秒的时间,如果此期间有其他事务要处理,则会白白浪费时间,而用非阻塞的connect 则可以做到并行,提高效率。

             而通常,非阻塞的connect 函数与 select 函数配合使用:在一个TCP套接口被设置为非阻塞之后调用connect,connect (函数本身返回-1)会立即返回EINPROGRESS或EWOULDBLOCK错误,表示连接操作正在进行中,但是仍未完成;同时TCP的三路握手操作继续进行;在这之后,我们可以调用select来检查这个链接是否建立成功。

           若建立连接成功,select的结果中该描述符可写;若失败,则可写可读,此时可以使用getsockopt获取错误信息。

    正常的三次握手时序:

     

     

     

    (以下内容转自http://blog.163.com/li_xiang1102/blog/static/60714076201110298170975/


    在一个TCP套接口被设置为非阻塞之后调用connect,connect 会立即返回EINPROGRESS错误,表示连接操作正在进行中,但是仍未完成;同时TCP的三路握手操作继续进行;在这之后,我们可以调用select来检查这个链接是否建立成功。

    非阻塞connect有三种用途:
    1. 我们可以在三路握手的同时做一些其它的处理。connect 操作要花一个往返时间完成,而且可以是在任何地方,从几个毫秒的局域网到几百毫秒或几秒的广域网,在这段时间内我们可能有一些其他的处理想要执行;
    2. 可以用这种技术同时建立多个连接.在Web浏览器中很普遍;
    3. 由于我们使用select 来等待连接的完,因此我们可以给select设置一个时间限制,从而缩短connect 的超时时间。在大多数实现中,connect 的超时时间在75秒到几分钟之间。有时候应用程序想要一个更短的超时时间,使用非阻塞connect 就是一种方法。

    非阻塞connect 听起来虽然简单,但是仍然有一些细节问题要处理:
    1. 即使套接口是非阻塞的。如果连接的服务器在同一台主机上,那么在调用connect 建立连接时,连接通常会立即建立成功,我们必须处理这种情况。
    2. 源自Berkeley的实现(和Posix.1g)有两条与select 和非阻塞IO相关的规则:
      A. 当连接建立成功时,套接口描述符变成可写;
      B. 当连接出错时,套接口描述符变成既可读又可写。

     

    处理非阻塞connect的步骤(重点):
    1. 创建socket,返回套接口描述符;
    2. 调用fcntl 把套接口描述符设置成非阻塞;
    3. 调用connect 开始建立连接;
    4. 判断连接是否成功建立。

    判断连接是否成功建立:
    A. 如果connect 返回0,表示连接成功(服务器和客户端在同一台机器上时就有可能发生这种情况);
    B. 调用select 来等待连接建立成功完成;
         如果select 返回0,则表示建立连接超时。我们返回超时错误给用户,同时关闭连接,以防止三路握手操作继续进行下去。
         如果select 返回大于0的值,则需要检查套接口描述符是否可写,如果套接口描述符可写,则我们可以通过调用getsockopt来得到套接口上待处理的错误(SO_ERROR)。如果连接建立成功,这个错误值将是0;如果建立连接时遇到错误,则这个值是连接错误所对应的errno值(比如:ECONNREFUSED,ETIMEDOUT等)。


    移植性问题总结(个人觉得下面移植问题可以忽略)

    非阻塞connect 有这么多好处,但是处理非阻塞connect 时会遇到很多可移植性问题。

    1. 对于出错的套接口描述符,getsockopt的返回值源自Berkeley的实现是返回0,待处理的错误值存储在errno中;而源自Solaris的实现是返回-1,待处理的错误存储在errno中。(套接口描述符出错时调用getsockopt的返回值不可移植
    2.有可能在调用select之前,连接就已经建立成功,而且对方的数据已经到来,在这种情况下,套接口描述符是既可读又可写。这与套接口描述符出错时是一样的。(怎样判断连接是否建立成功的条件不可移植

    在我们判断连接是否建立成功的条件不唯一时,我们可以有以下的方法来解决这个问题:
    1. 调用getpeername代替getsockopt。如果调用getpeername失败,getpeername返回ENOTCONN,表示连接建立失败,我们必须以SO_ERROR调用getsockopt得到套接口描述符上的待处理错误;
    2. 调用read,读取长度为0字节的数据。如果read调用失败,则表示连接建立失败,而且read返回的errno指明了连接失败的原因。如果连接建立成功,read应该返回0;
    3. 再调用一次connect,它应该失败。如果错误errno是EISCONN,就表示套接口已经建立,而且第一次连接是成功的;否则,连接就是失败的。

     

    被中断的connect

    如果在一个阻塞式套接口上调用connect,在TCP的三路握手操作完成之前被中断了,比如说,被捕获的信号中断,将会发生什么呢?假定connect不会自动重启,它将返回EINTR。那么,这个时候我们就不能再调用connect等待连接建立完成了,如果再次调用connect来等待连接建立完成的话,connect将会返回错误值EADDRINUSE。在这种情况下,应该做的是调用select,就像在非阻塞式connect中所做的一样。然后,select在连接建立成功(使套接口描述符可写)或连接建立失败(使套接口描述符既可读又可写)时返回。



    展开全文
  • 数据库的select底层实现

    千次阅读 2017-10-01 20:35:36
    话题:数据库中的select底层? 一、从数据库查询数据的角度,大概架构(前提:客户端需要将查询语句发送到服务器端) 1.接到语句查找sql计划缓存 如果没有—_—|| 2.检查语句合法性(对sql语句语法的检查,)3....

    话题:数据库中的select底层?
    一、从数据库查询数据的角度,大概架构(前提:客户端需要将查询语句发送到服务器端)
    1.接到语句查找sql计划缓存
    如果没有—_—||
    2.检查语句合法性(对sql语句语法的检查,)

    3.检查语言含义(对sql语句的所包含的表名,字段名)

    4.获得对象解析锁

    5.核对用户权限是否有访问数据的权限

    探究到着,我不禁想到,平时写sql语句的时候,就是这样的啊!
    6.确定最佳执行计划(自我优化,很有限),同时将当前sql语
    句与最佳执行计划保存到高速缓存

    二、从select的原型考虑
    select()原型主要是建立在fd_set类型的基础上的。fd_set是一组文件描述字(fd)的集合,它用一位来表示一个fd,对于fd_set类型通过下面四个宏定义来操作:
    fd_set set;
    FD_ZERO(&set); /将set的所有位置0,如set在内存中占8位则将set置为00000000/
    FD_SET(0, &set); /* 将set的第0位置1,如set原来是00000000,则现在变为10000000,这样fd==1的文件描述字就被加进set中了 */
    FD_CLR(4, &set); /*将set的第4位置0,如set原来是10001000,则现在变为10000000,这样fd==4的文件描述字就被从set中清除了
    FD_ISSET(5, &set); /* 测试set的第5位是否为1,如果set原来是10000100,则返回非零,表明fd==5的文件描述字在set中;否则返回0*/

    select函数的的接口:
    int select(int nfds, fd_set readset, fd_set *writeset,fd_set exceptset, struct timeval *timeout);
    功能:
    测试指定的fd可读?可写?有异常条件待处理?
    参数:
    1.nfds:需要检查的文件描述字个数(即检查到fd_set的第几位),数值应该比三组fd_set中所含的最大fd值更大,一般设为三组fd_set中所含的最大fd值加1(如在readset,writeset,exceptset中所含最大的fd为5,则nfds=6,因为fd是从0开始的)。设这个值是为提高效率,使函数不必检查fd_set的所有1024位。
    2.readset:用来检查可读性的一组文件描述字。
    3.writeset:用来检查可写性的一组文件描述字。
    4.exceptset:用来检查是否有异常条件出现的文件描述字。
    5.timeout:有三种可能:
    1. timeout=NULL(阻塞:直到有一个fd位被置为1函数才返回)
    2.timeout所指向的结构设为非零时间(等待固定时间:有一个fd位被置
    为1或者时间耗尽,函数均返回)
    3. timeout所指向的结构,时间设为0(非阻塞:函数检查完每个fd后立即返回)
    返回值:
    返回对应位仍然为1的fd的总数。
    Remarks:
    三组fd_set均将某些fd位置0,只有那些可读,可写以及有异常条件待处理的fd位 仍然为1。

    使用select函数的一般过程:调用宏FD_ZERO将指定的fd_set清零—–>宏FD_SET将fd加入fd_set——>接着调用函数select测试fd_set中的所有fd,最后用宏FD_ISSET检查某个fd在函数select调用后,相应位是否仍然为1。

    //单个文件描述字可读性
    int isready(int fd)
    {
    int rc;
    fd_set fds;
    struct timeval tv;
    FD_ZERO(&fds);
    FD_SET(fd,&fds);
    tv.tv_sec = tv.tv_usec = 0;
    rc = select(fd+1, &fds, NULL, NULL, &tv);
    if (rc < 0) //error
    return -1;
    return FD_ISSET(fd,&fds) ? 1 : 0;
    }

    应用程序调用select() 函数,系统调用陷入内核,进入到:
    SYSCALL_DEFINE5 (sys_select)—-> core_sys_select —–> do_select()
    SYSCALL_DEFINE5(select, int, n, fd_set __user , inp, fd_set __user , outp,
    fd_set __user , exp, struct timeval __user , tvp)//n为文件描述符
    {
    struct timespec end_time, *to = NULL;
    struct timeval tv;
    int ret;
    if (tvp) {
    if (copy_from_user(&tv, tvp, sizeof(tv)))
    return -EFAULT;
    to = &end_time;
    if (poll_select_set_timeout(to,
    tv.tv_sec + (tv.tv_usec / USEC_PER_SEC),

                            (tv.tv_usec % USEC_PER_SEC) * NSEC_PER_USEC))
                     return -EINVAL;
       }
       ret = core_sys_select(n, inp, outp, exp, to);
       ret = poll_select_copy_remaining(&end_time, tvp, 1, ret);
       return ret;
    

    }

    在core_sys_select() 函数中调用了do_select:
    int do_select(int n, fd_set_bits *fds, struct timespec *end_time)
    {
    ktime_t expire, *to = NULL;
    struct poll_wqueues table;
    poll_table *wait;
    int retval, i, timed_out = 0;
    unsigned long slack = 0;

         rcu_read_lock();
         retval = max_select_fd(n, fds);
         rcu_read_unlock();
    
         if (retval < 0)
                   return retval;
         n = retval;
    
         poll_initwait(&table);//初始化结构体,主要是初始化poll_wait的回调函数为__pollwait
         wait = &table.pt;
         if (end_time && !end_time->tv_sec && !end_time->tv_nsec) {
                   wait = NULL;
                   timed_out = 1;
         }
    
         if (end_time && !timed_out)
                   slack = estimate_accuracy(end_time);
    
         retval = 0;
         for (;;) {
                   unsigned long *rinp, *routp, *rexp, *inp, *outp, *exp;
    
                   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) {
                            unsigned long in, out, ex, all_bits, bit = 1, mask, j;
                            unsigned long res_in = 0, res_out = 0, res_ex = 0;
                            const struct file_operations *f_op = NULL;
                            struct file *file = NULL;
                            in = *inp++; out = *outp++; ex = *exp++;
                            all_bits = in | out | ex;
                            if (all_bits == 0) {
                                     i += __NFDBITS;
                                     continue;
                            }
    
                            for (j = 0; j < __NFDBITS; ++j, ++i, bit <<= 1) {
                                     int fput_needed;
                                     if (i >= n)
                                               break;
                                     if (!(bit & all_bits))
                                               continue;
                                     file = fget_light(i, &fput_needed);
                                      if (file) {
                                               f_op = file->f_op;
                                               mask = DEFAULT_POLLMASK;
                                               if (f_op && f_op->poll) { 
                                                        wait_key_set(wait, in, out, bit);
                                                        mask = (*f_op->poll)(file, wait););//调用poll_wait处理过程,
                                                        //即把驱动中等待队列头增加到poll_wqueues中的entry中,并把指向
                                                        //当前里程的等待队列项增加到等待队列头中。每一个等待队列头占用一个entry
                                               }
                                               fput_light(file, fput_needed);
                                               if ((mask & POLLIN_SET) && (in & bit)) {//如果有信号进行设置,记录,写回到对应项,设置跳出循环的retval
                                                        res_in |= bit;
                                                        retval++;
                                                        wait = NULL;
                                               }
                                               if ((mask & POLLOUT_SET) && (out & bit)) {
                                                        res_out |= bit;
                                                        retval++;
                                                        wait = NULL;
                                               }
                                               if ((mask & POLLEX_SET) && (ex & bit)) {
                                                        res_ex |= bit;
                                                        retval++;
                                                        wait = NULL;
                                               }
                                     }
                            }
                            if (res_in)
                                     *rinp = res_in;
                            if (res_out)
                                     *routp = res_out;
                            if (res_ex)
                                     *rexp = res_ex;
                            cond_resched();//增加抢占点,调度其它进程,当前里程进入睡眠
                   }
                   wait = NULL;
                   if (retval || timed_out || signal_pending(current))//这里就跳出循环,需要讲一下signal_pending
                            break;
                   if (table.error) {
                            retval = table.error;
                            break;
                   }
                   /*
                    * If this is the first loop and we have a timeout
                    * given, then we convert to ktime_t and set the to
                    * pointer to the expiry value.
                    */
                    //读取需要等待的时间,等待超时
                   if (end_time && !to) { 
                            expire = timespec_to_ktime(*end_time);
                            to = &expire;
                   }
                   if (!poll_schedule_timeout(&table, TASK_INTERRUPTIBLE,to, slack))
                           timed_out = 1;
         }
         poll_freewait(&table);//从等待队列头中删除poll_wait中添加的等待队列,并释放资源
         return retval;//调用成功与否就看这个返回值
    

    }

    do_select大概的思想就是:当应用程序调用select() 函数, 内核就会相应调用 poll_wait(), 把当前进程添加到相应设备的等待队列上,然后将该应用程序进程设置为睡眠状态。直到该设备上的数据可以获取,然后调用wake up 唤醒该应用程序进程来获取数据。

    这里写图片描述

    展开全文
  • 存储过程insert into select

    千次阅读 2016-03-26 19:40:04
    insert into select
  • SQL中select语句的计算过程

    千次阅读 2017-02-06 21:37:31
    对于一个select语句,其基本计算过程如下: 1. 取from字句中列出的各个关系的元组的所有可能的组合。 2. 将不符合where字句中给出的条件的元组去掉。 3. 如果有group by子句,将剩下的元组按group by子句中给出的...
  • Select和Epoll底层实现的区别

    千次阅读 2018-10-05 17:23:40
    Select,Poll和Epoll在OS底层实现上的区别,造成他们性能差距的根本原因
  • 在表单的开发过程中,有时会需要实现select的二级联动,如果涉及到大型的系统项目,select的选项会存储在后台数据库当中,这就需要前后端交互来获取持久化数据并展示,本文详细介绍使用jquery和java来实现select标签...
  • 【Angular2】angular2 select change 事件实现下拉联动

    千次阅读 热门讨论 2017-09-25 19:24:58
    一、实现要求 还用Angular2实现select下拉框联动的效果:二、实现过程2.1 项目结构 由于项目是使用了Angular2,前端工程化,组件化,在项目中,分成了下面的五个部分:css、html、routes、spec、component.ts。...
  • 利用select函数实现非阻塞式的socket accept这个实现是我在阅读Superuser源码的过程中看到的。当时就在想,虽然我们知道select函数的使用方法,为什么我们在适当的地方想不起来使用它呢。看来这些好的习惯需要有意识...
  • 如何通过jQuery实现select下拉框的联动效果

    万次阅读 多人点赞 2017-12-10 21:39:51
    由于项目需要,小编需要实现三个HTML的select下拉框联动的效果,如果考虑到实际用户体验的话,直接用异步刷新的方式操作DOM是最好的选择,这里小编直接使用了jQuery作为DOM操作的工具,直接操作,完成了对于下拉框的...
  • mybatis源码解析(三)-SqlSession.selectOne类似方法调用过程 mybatis源码解析(四)-Mapper方法调用过程 mybatis源码解析(五)-mybatis如何实现的事务控制 mybatis源码解析(六)-配合spring-tx实现事务的原理 ...
  • Linux : select()详解 和 实现原理

    千次阅读 2014-09-15 10:19:47
    linux—select详解   select系统调用时用来让我们的程序监视多个文件句柄的状态变化的。程序会停在select这里等待,直到被监视的文件句柄有一个或多个发生了状态改变。 关于文件句柄,其实就是一个整数,通过...
  • 原生js实现select下拉列表的内容过滤 场景描述: 笔者在工作的过程中,经常碰到这样的业务场景。客户要求一个下拉列表框旁边要有一个输入过滤的功能:如下图所示 由于在一个项目中出现了好多这样的需求,笔者...
  • 在笔者之前的项目中获取流水号的实现如下:序列号定义在一张表的某个字段中,通过select查询该字段,并且update +1,从而实现获取自增流水号。 但在高并发的环境中,会出现流水号重复的情况。后来通过 select for ...
  • 本文是mysql 数据库问题一 将表一的数据导入表二:将表一的数据导入表二:将表二中的数据 插入到 表一,表一的列对应表二 select 出来的列INSERT INTO 表一 (column1, column2, column3, column4)select column1, ...
  • 快速选择算法与快速排序算法在partition划分过程上是类似的),参考Mark的数据结构与算法分析-c语言描述一书,而后逐步深入分析快速选择SELECT算法,最后,给出SELECT算法的程序实现。  同时,本文有部分内容...
  • ibatis用@select注解开发实现in查询

    千次阅读 2018-06-15 10:22:42
    当传入参数的时候,我们习惯用**#**,从而实现防止注入攻击,但是当设计到in查询的时候,会发现# 不太好用,这是应为强调内容
  • 现在我们来回顾下各种模型(转载请指明出于breaksoftware的csdn博客)模型编程步骤对比 《朴素、Select、Poll和Epoll网络编程模型实现和分析——朴素模型》中介绍的是最基本的网络编程模型,我们使用单线程去实现,...
  • 就想着用listbox来实现,但后来针对这个项目中没有想到合适的办法(ps:大家有了可以推荐哟),从网上看了一些demo,就想着用select实现下拉框左右选择的效果,再结合input实现select中option项的模糊查询。...
  • 选择排序(Selectsort)之Java实现

    千次阅读 2013-12-04 21:18:37
    选择排序与冒泡排序非常的相似,都是一层层筑顶的过程,不同点在于冒泡排序会频繁的互换位置,而选择排序只是记录最大元素的位置,并与顶互换,只需交换一次。所以选择排序与冒泡排序相比时间常数会更小,更有效率,...
  • JDBC的详细实现过程

    千次阅读 多人点赞 2018-07-26 22:03:42
    JDBC的实现过程 一.导入MYsql jar包 1.新建 Java Project 名为 JDBCTest,在项目下右键–build path–add librarys 2.在弹出框选user librays name中输入jdbc点击OK 3.重新右键–build path–add librarys—user...
  • 附一篇oracle实现 top x 在psql中使用 \h select 命令,可以得到以下帮助 jincheng=# \h select 命令: SELECT 描述: 从资料表或视观表读取资料 语法: [ WITH [ RECURSIVE ] with查询语句(with_query) [, .....
  • 动态拼接select标签 本教程主要解决的问题是利用数据库中的数据动态的显示到下拉选框中,通过这个下拉选框的值动态的...第一个下拉框具体实现 这是第一个下拉选框的select标签 &lt;select style="w...
  • linux c实现超时、非阻塞socket的函数select Select在Socket编程中还是比较重要的,可是对于初学Socket的人来 说都不太爱用Select写程序,他们只是习惯写诸如 connect、accept、recv或recvfrom这样的阻塞程序...
  • select,poll,epoll都是...但select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据
  • 十四、亦第三章再续:快速选择SELECT算法的深入分析与实现作者:July。出处:http://blog.csdn.net/v_JULY_v 。 前言 经典算法研究系列已经写了十三个算法,共计22篇文章(详情,见这:十三个经典算法研究与总结...
  • 网站信息统计的简单实现过程

    千次阅读 2004-10-24 23:46:00
    作者:pcskySQL语句如下: SELECT DD.SumHits, AA.CountArt, CC.WeekUpdate, BB.RegUserNumFROM(SELECT COUNT(newsid) AS CountArt FROM article) AA,(SELECT COUNT(id) AS RegUserNum FROM Admin) BB,(SELECT COUNT...
  • 二、前面实现的能够并发服务的服务器端程序是使用fork出多个子进程来实现的,现在学习了select函数,可以用它来改进服务器端程序,实现单进程并发服务。 首先介绍一下select函数: /* According to POSIX.1...
  • select,poll,epoll本质上都是同步I/O,因为他们都需要在读写事件就绪后自己负责进行读写,也就是说这个读写过程是阻塞的,而异步I/O则无需自己负责进行读写,异步I/O的实现会负责把数据从内核拷贝到用

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 478,371
精华内容 191,348
关键字:

select实现过程