linux-2.6.38poll机制简析(以tiny6410按键中断程序为基础)

一、应用程序

/*
struct pollfd {
    int fd;                       //文件描述符
    short events;                 //表示请求检测的事件
    short revents;                //表示检测之后返回的事件
};
*/
int fd;
struct pollfd fds[1];           // 只用poll函数来检测一个描述符
fd = open("/dev/tiny6410_button", 0);
fds[0].fd = fd;                //存放文件描述符
fds[0].events = POLLIN;        //有数据可以读
while (1)
{
  ret = poll(fds, 1, 5000);  // int poll(struct pollfd fds[], nfds_t nfds, int timeout)
  if (ret == 0)              // fds[]:存放需要被检测状态的描述符   nfds:fds[]数组的个数  timeout:poll函数阻塞调用的时间
   {
    printf("timeout   fds[0].revents = %d \n",fds[0].revents);
   }
  else if (ret>0)
   {
    printf("fds[0].revents = %d\n",fds[0].revents);
    read(fd, press_cnt, sizeof(press_cnt));
    for (i = 0; i < sizeof(press_cnt)/sizeof(press_cnt[0]); i++)
    {
      if (press_cnt[i])
      printf("K%d has been pressed %d times!\n", i+1, press_cnt[i]);
    }
   }
   else
   {
    return 0;
  }
}
close(fd);
return 0;

poll(...)函数的返回值:>0 表示 fds[]中存放的某些文件描述符的状态发生了变化

          =0 表示 fds[]中存放的文件描述符的状态没有变化,并且调用超时了

          <0 表示有错误发生

看到实验结果:当5秒没有按键按下时,timeout   fds[0].revents = 0

        当有按键立即按下时,   fds[0].revents = 1

        因此可以根据revents的值来判断哪个文件描述符的状态发生了变化

二、 从内核看poll 函数调用

  2.1 找sys_poll(...)函数

  在应用程序调用poll(...)函数时,内核会调用sys_poll(...)函数,因此在内核中寻找sys_poll(...)函数,在linux-2.6.38中,系统

调用函数名称都是用宏定义实现的。所有先找一找sys_poll(...)在哪里,在select.c中有如下函数:

  SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

这是一个宏定义,需要把这个宏定义展开成如下的形式:

  asmlink long sys_poll( struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs)

宏定义的展开过程分析:

在syscalls.h中有一大堆关于系统调用的宏定义

  #define SYSCALL_DEFINE3(name, ...) SYSCALL_DEFINEx(3, _##name, __VA_ARGS__)

宏替换后变成了:

  SYSCALL_DEFINEx(3, _poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

  #define SYSCALL_DEFINEx(x, sname, ...)   __SYSCALL_DEFINEx(x, sname, __VA_ARGS__)

宏替换后变成了:

  __SYSCALL_DEFINEx(3,_poll, struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)

  #define __SYSCALL_DEFINEx(x, name, ...)    asmlinkage long sys##name(__SC_DECL##x(__VA_ARGS__))

宏替换后变成了:

  asmlinkage long sys_poll(__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs))

对__SC_DECL3(struct pollfd __user *, ufds, unsigned int, nfds, long,  timeout_msecs)进行展开

    __SC_DECL##x(__VA_ARGS__) 这个也是一个宏定义,仍然需要进行展开:

    #define __SC_DECL1(t1, a1) t1 a1

    #define __SC_DECL2(t2, a2, ...) t2 a2, __SC_DECL1(__VA_ARGS__)

    #define __SC_DECL3(t3, a3, ...) t3 a3, __SC_DECL2(__VA_ARGS__)

    #define __SC_DECL4(t4, a4, ...) t4 a4, __SC_DECL3(__VA_ARGS__)

    #define __SC_DECL5(t5, a5, ...) t5 a5, __SC_DECL4(__VA_ARGS__)

    #define __SC_DECL6(t6, a6, ...) t6 a6, __SC_DECL5(__VA_ARGS__)

就得到了  struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs

故得到了最终的展开函数:

asmlink long sys_poll( struct pollfd __user * ufds, unsigned int  nfds, long  timeout_msecs)

  2.2 函数调用过程分析

SYSCALL_DEFINE3(poll, struct pollfd __user *, ufds, unsigned int, nfds,
        long, timeout_msecs)
{
        ....
        //1.设置timeout时间
    if (timeout_msecs >= 0) {
        to = &end_time;
        poll_select_set_timeout(to, timeout_msecs / MSEC_PER_SEC,
            NSEC_PER_MSEC * (timeout_msecs % MSEC_PER_SEC));
    }
        // 2.完成poll调用的主要任务
    ret = do_sys_poll(ufds, nfds, to);
        ....
}        

  2.2.1这里的核心函数do_sys_poll(...)

int do_sys_poll(struct pollfd __user *ufds, unsigned int nfds,
        struct timespec *end_time)
{
    struct poll_wqueues table;
     int err = -EFAULT, fdcount, len, size;
        // 从这里开始都是分配内存空间,并将用户空间的fds[]拷贝到内核空间
    long stack_pps[POLL_STACK_ALLOC/sizeof(long)];// 在栈上分配一个固定空间的
    struct poll_list *const head = (struct poll_list *)stack_pps;//强制将上边分配的空间转换为poll_list
     struct poll_list *walk = head;
     unsigned long todo = nfds;//用户空间的fds[]数组的个数

    if (nfds > rlimit(RLIMIT_NOFILE))
        return -EINVAL;

    len = min_t(unsigned int, nfds, N_STACK_PPS);
    for (;;) {
        walk->next = NULL; //将指针先置为NULL
        walk->len = len;      //长度=len
        if (!len)
            break;

        if (copy_from_user(walk->entries, ufds + nfds-todo,
                    sizeof(struct pollfd) * walk->len))//拷贝的前提应该是不会超过分配内存的大小
            goto out_fds; //当用户空间的fds[]超过所分配内存大小时,跳转

        todo -= walk->len;  //nfds-walk->len 求剩下多少个fds结构体组没有拷
        if (!todo)     //如果不剩即全部拷完了, 则break
            break;

        len = min(todo, POLLFD_PER_PAGE); //若还剩todo个没考, 求下一次需要拷的个数
        size = sizeof(struct poll_list) + sizeof(struct pollfd) * len;//求需要分配内存的大小
        walk = walk->next = kmalloc(size, GFP_KERNEL);// 在分配size大小的内存,并将这块内存的首地址挂载walk->next上, 这样如果有很多fds[]的话,就可以够成一个一个的poll_list 链表, 链表之间通过poll_list->next 连接
        if (!walk) {
            err = -ENOMEM;
            goto out_fds;
        }
    }
        // 到这里已经分配完内存空间,并将所有的fds[]从用户空间拷贝到内核空间
    poll_initwait(&table); // 这个函数就是初始化poll_wqueues类型的变量 table
初始化了什么,在下边分析
    fdcount = do_poll(nfds, head, &table, end_time);// 核心函数
    poll_freewait(&table);   

    for (walk = head; walk; walk = walk->next) {
        struct pollfd *fds = walk->entries;
        int j;

        for (j = 0; j < walk->len; j++, ufds++)    // 分析到这里应该就很明朗了,这里是把revents从内核空间拷贝到用户空间,方便用户空间来查询哪些文件描述符的状态发生了变化
            if (__put_user(fds[j].revents, &ufds->revents))
                goto out_fds;
      }
    err = fdcount;
out_fds:
    walk = head->next;
    while (walk) {
        struct poll_list *pos = walk;
        walk = walk->next;
        kfree(pos);
    }

    return err;
}

在do_sys_poll(...)函数中,首先就是分配内存空间,将用户空间的fds[]拷贝到内核空间,具体的拷贝过程已经在注释中大概分析了

            其次初始化table变量 ,在初始化table变量之前有必要看一下table是什么

struct poll_wqueues {
    poll_table pt;
    struct poll_table_page *table;
    struct task_struct *polling_task;
    int triggered;
    int error;
    int inline_index;
    struct poll_table_entry inline_entries[N_INLINE_POLL_ENTRIES];
};     //这是table是什么    重点是poll_table成员

typedef struct poll_table_struct {
    poll_queue_proc qproc;
    unsigned long key;
} poll_table;    // 这是poll_table 是什么
static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    struct poll_wqueues *pwq = container_of(p, struct poll_wqueues, pt);
    struct poll_table_entry *entry = poll_get_entry(pwq);
    if (!entry)
        return;
    get_file(filp);
    entry->filp = filp;
    entry->wait_address = wait_address;
    entry->key = p->key;
    init_waitqueue_func_entry(&entry->wait, pollwake);
    entry->wait.private = pwq;
    add_wait_queue(wait_address, &entry->wait);
}
void poll_initwait(struct poll_wqueues *pwq)
{
    init_poll_funcptr(&pwq->pt, __pollwait);  //见下边,这table->pt ->qproc = qproc = _pollwait;
    pwq->polling_task = current;
    pwq->triggered = 0;
    pwq->error = 0;
    pwq->table = NULL;
    pwq->inline_index = 0;
}
static inline void init_poll_funcptr(poll_table *pt, poll_queue_proc qproc)
{
    pt->qproc = qproc;
    pt->key   = ~0UL; /* all events enabled */
}

上边的函数和结构体说明了初始化table变量的全过程

在初始化完成table之后: table->polling_task = current;

             table->triggered = 0;

             table->error = 0;

             table->table = NULL;

             table->inline_index = 0;

            重点 table->pt->qproc = __pollwait; 这是给table->pt->qproc挂上了一个函数__pollwait,注意之后会用到,这个函数的具体类容已经在上边分析过了

2.2.2在do_sys_poll中核心函数: do_poll(nfds, head, &table, end_time);

    for (;;) {  //这里三层循环嵌套,虽然复杂,但是就是遍历从用户空间拷贝的fds[]数组,然后执行do_pollfd(pfd,pt) ,这里的pt就是上边分析的wait->pt
        struct poll_list *walk;

        for (walk = list; walk != NULL; walk = walk->next) {
            struct pollfd * pfd, * pfd_end;

            pfd = walk->entries;
            pfd_end = pfd + walk->len;
            for (; pfd != pfd_end; pfd++) {
                if (do_pollfd(pfd, pt)) {
                    count++;
                    pt = NULL;
                }
            }
        }
        pt = NULL;
        if (!count) {
            count = wait->error;
            if (signal_pending(current))
                count = -EINTR;
        }
        if (count || timed_out)
            break;
        if (end_time && !to) {
            expire = timespec_to_ktime(*end_time);
            to = &expire;
        }

        if (!poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack))
            timed_out = 1;
    }

(1)发循环退出条件的分析:

      第一个条件:fds[]文件中某些文件描述符的状态发生了变化,即fd[x]->revents != 0

      第二个条件:timeout != 0 也就是超时了

  这里先分析第二个条件即timeout  !=  0的情况

  当timeout != 0时大循环退出   注意在这里边timeout看起来像是一个bool量,程序刚进入do_poll(...)时,timeout为0, 假设所有的文件描述符的状态都没有发生变化, 然后执行休眠函数poll_schedule_timeout(wait, TASK_INTERRUPTIBLE, to, slack)后,定时休眠成功后timeout变成1,然后再次循环,此时不管fds[]中文件描述符的状态怎么样,timeout都为1,都会退出大循环。

(2) 第一个退出条件分析:count != 0时

static inline unsigned int do_pollfd(struct pollfd *pollfd, poll_table *pwait)
{
       // 先看传入参数:pollfd  :fds[]数组中的某一个注意是拷贝到内核空间的fds[]
       //                   pwait : 这个pwait就是之前初始化的table->pt
       //                             pt下边挂着一个回调函数,在这里派上用处了
    unsigned int mask;
    int fd;

    mask = 0;
    fd = pollfd->fd;       // 取出文件描述符
    if (fd >= 0) {
        int fput_needed;
        struct file * file;

        file = fget_light(fd, &fput_needed);
        mask = POLLNVAL;
        if (file != NULL) {
            mask = DEFAULT_POLLMASK;
            if (file->f_op && file->f_op->poll) {  // 这里就和文件操作对应的结构体中中的内容就比较相似了,
//当我们驱动程序中定义了文件操作结构体,并且定义了poll函数,那么久执行驱动程序的poll函数
                if (pwait)
                    pwait->key = pollfd->events |
                            POLLERR | POLLHUP;
                mask = file->f_op->poll(file, pwait); // 这里执行驱动程序的poll函数, 传入参数有一个回调函数
     //这个回调函数就是之前初始化table提到的
     // 注意驱动程序poll函数执行完会返回一个值, 正是根据驱动程序中的poll函数的返回值来确定,我们的文件描述符的状态是否发生了变化
            }
            /* Mask out unneeded events. */
            mask &= pollfd->events | POLLERR | POLLHUP;
            fput_light(file, fput_needed);
        }
    }
    pollfd->revents = mask;

    return mask;
}     

  这里的do_pollfd(...)函数实际上就是执行驱动函数poll,通过驱动函数来判断文件描述符的状态。

 这里我们结合按键驱动的poll函数来分析这个是怎么判断状态的

unsigned int tiny6410_button_poll (struct file *file, struct poll_table_struct *wait)
{
    unsigned int mask = 0;
    poll_wait(file, &button_waitq, wait);
    if (ev_press)
        mask |= POLLIN;
    return mask;
}

(1)驱动函数中的poll_wait(file, &button_waitq, wait) , 注意wait是table->pt

    poll_wait(file, &button_waitq, wait) ====》table->pt->qproc(file, &button_waitq, table->pt) ===》__pollwait(file,&button_waitq,table->pt)

这个_pollwait(.....)函数之前提过,现在在重复一遍:

static void __pollwait(struct file *filp, wait_queue_head_t *wait_address,
                poll_table *p)
{
    ...
    add_wait_queue(wait_address, &entry->wait);  // add_wait_queue(&button_waitq, &entry->wait)
}

  这个函数就是将等待对列挂到我们在驱动程序中定义的等待对列:button_waitq中,在这里并不会进入到休眠。

(2)if (ev_press)

    mask |= POLLIN;

   return mask;

  当有按键按下时,产生中断,在中断服务函数中会将ev_press至为1;

  当do_pollfd(...) 中调用驱动程序的poll函数时,会检测ev_press,当ev_press==1 时,说明有中断发生,然后mask |= POLLIN 并返回mask

                                当ev_press == 0, 没有中断发生, mask=0,并返回

  然后在do_pollfd(...)将根据mask设置revents;

  最后 do_poll(nfds, head, &table, end_time)中,根据返回do_pollfd()返回的mask值,来判断count是否++ ,即当有中断产生的时候,count++,

从而退出do_poll(...)的死循环。

原文地址:https://www.cnblogs.com/zf1-2/p/10835726.html

时间: 2024-11-08 22:43:21

linux-2.6.38poll机制简析(以tiny6410按键中断程序为基础)的相关文章

Linux网络性能优化方法简析

Linux网络性能优化方法简析 2010-12-20 10:56 赵军 IBMDW 字号:T | T 性能问题永远是永恒的主题之一,而Linux在网络性能方面的优势则显而易见,这篇文章是对于Linux内核中提升网络性能的一些优化方法的简析,以让我们去后台看看魔术师表演用的盒子,同时也看看内核极客们是怎样灵活的,渐进的去解决这些实际的问题. AD:2014WOT全球软件技术峰会北京站 课程视频发布 对于网络的行为,可以简单划分为 3 条路径:1) 发送路径,2) 转发路径,3) 接收路径,而网络性

linux网络流控-htb算法简析

项目中用tc,htb做流控期间,研究了htb(分层令牌桶)算法的实现.觉得这种思想在类似与有消费优先级的生产者消费者场景中也很适用.该算法过于复杂,碍于嘴拙遂在标题中加了简析,只介绍核心思想和关键代码的实现. 一个栗子: tc qdisc add dev eth0 root handle 1: htb tc class add dev eth0 parent 1: classid 1:1 htb rate 100mibps tc class add dev eth0 parent 1:1 cla

Linux系统启动过程及其修复过程简析

Linux组成 Linux: kernel+rootfs kernel: 进程管理.内存管理.网络管理.驱动程序.文件系统.安全功能 rootfs:程序和glibc 库:函数集合, function, 调用接口(头文件负责描述) 过程调用:procedure,无返回值 函数调用:function 程序:二进制执行文件 内核设计流派: 单内核(monolithic kernel):Linux 把所有功能集成于同一个程序 微内核(micro kernel):Windows, Solaris 每种功能

安卓Binder机制简析

转自:http://www.linuxidc.com/Linux/2011-07/39271.htm 摘要 Binder是Android系统进程间通信(IPC)方式之一.Linux已经拥有管道,system V IPC,socket等IPC手段,却还要倚赖Binder来实现进程间通信,说明Binder具有无可比拟的优势.深入了解Binder并将之与传统 IPC做对比有助于我们深入领会进程间通信的实现和性能优化.本文将对Binder的设计细节做一个全面的阐述,首先通过介绍Binder通信模型和 B

Linux Hugetlbfs内核源码简析-----(一)Hugetlbfs初始化

一.引言 为了实现虚拟内存管理机制,操作系统对内存实行分页管理.自内存“分页机制”提出之始,内存页面的默认大小便被设置为 4096 字节(4KB),虽然原则上内存页面大小是可配置的,但绝大多数的操作系统实现中仍然采用默认的 4KB 页面.当某些应用的需要使用的内存达到几G.甚至几十G的时候,4KB的内存页面将严重制约程序的性能. CPU缓存中有一组缓存专门用于缓存TLB,但其大小是有限的.当采用的默认页面大小为 4KB,其产生的TLB较大,因而将会产生较多 TLB Miss 和缺页中断,从而大大

Linux常用命令与权限简析

1.文件权限处理( ll 命令可查看当前文件的具有的权限) drwxr-xr-x. 2 root root 12288 Dec 21 11:49 bin 所属组 所属用户 文件大小 文件最后更改时间 drwxr-xr-x:(d表示文件夹,后面三个一组表示权限): d:表示 bin为 文件夹 rwx:表示 具有读写执行即 111(二进制数)--->7(十进制数) :第一部分 代表 文件所属者 具有的权限 r-x:表示 具有读 不能写 执行权限 101---->6 :第二部分 代表 用户组的权限

Unity - 存读档机制简析

本文旨在于简要分析Unity中的两种存档机制,即:PlayerPrefs数据持久化方法及Serialization数据序列化方法 较比于源项目,我另加了JSON方法.XML方法等及一些Unity设置,更便于读者在使用中理解Unity的存档机制.核心脚本为Game.cs 源项目地址:How to Save and Load a Game in Unity - raywenderlich 个人项目地址:BattleSave - SouthBegonia 一.PlayerPrefs 数据持久化方法 存

DPDK多核多线程机制简析

DPDK通过在多核设备上,创建多个线程,每个线程绑定到单独的核上,减少线程调度的开销,以提高性能. DPDK的线程分为控制线程和数据线程,控制线程一般绑定到MASTER核上,主要是接受用户配置,并传递配置参数给数据线程等:数据线程主要是处理数据包. 一.初始化 1.rte_eal_cpu_init()函数中,通过读取/sys/devices/system/cpu/cpuX/下的相关信息,确定当前系统有哪些CPU核,已经每个核属于哪个CPU Socket. 2.eal_parse_args()函数

异步消息处理机制 简析

1.什么是异步消息处理? 答:对于普通的线程来说,执行完run()方法内的代码后线程就结束了.而异步消息处理线程是指:线程启动后会进入一个无限循环体之中,每执行一次,从线程内部的消息队列中取出一个消息,并回调相应的消息处理函数,执行完一个消息后则继续循环.如果消息队列为空,线程会暂停(一般也就是我们调用休眠方法),直到消息队列中又新的消息. 2.什么时候使用异步消息处理? 答:当我们在处理下载或是其他需要长时间执行的任务时,如果直接把处理函数放Activity的OnCreate或是OnStart