POLL原理分析(RT-Thread源码)

1.相关函数


poll测试值

常量说明备注
POLLIN数据可读
POLLRDNORM普通数据可读
POLLRDBAND带数据可读
POLLPRI高优先级数据可读
POLLOUT数据可写
POLLWRNORM普通数据可写
POLLWRBAND带数据可写
错误常量
POLLERR发生错误
POLLHUP发生挂起
POLLNVAL描述字不是一个打开的文件
rtos
POLLMASK_DEFAULTPOLLIN+POLLOUT+POLLRDNORM+POLLWRNORM

1.poll

int poll(struct pollfd *fds, nfds_t nfds, int timeout)
{
    int num;
    struct rt_poll_table table;

    poll_table_init(&table);

    num = poll_do(fds, nfds, &table, timeout);

    poll_teardown(&table);

    return num; //返回满足条件的设备数量
}

2.poll_table_init

static void poll_table_init(struct rt_poll_table *pt)
{
    pt->req._proc = _poll_add; //挂接处理函数
    pt->triggered = 0;
    pt->nodes = RT_NULL; //记录满足条件的设备
    pt->polling_thread = rt_thread_self(); //记录执行poll查询的线程(一般为本身)
}

3.poll_do

退出条件:

  1. 监听的事件发生
  2. 超时
static int poll_do(struct pollfd *fds, nfds_t nfds, struct rt_poll_table *pt, int msec)
{
    int num;
    int istimeout = 0; //超时标志,也是退出查询的条件之一
    int n;
    struct pollfd *pf;

    if (msec == 0)
    {
        pt->req._proc = RT_NULL;
        istimeout = 1;
    }

    while (1)
    {
        pf = fds;
        num = 0;

        for (n = 0; n < nfds; n ++)
        {
            if (do_pollfd(pf, &pt->req))
            {
                num ++;
                pt->req._proc = RT_NULL; 如果有一个设备符合监听条件,则清除之后设备的_poll_add的操作,因为poll的目的已达到
            }
            pf ++;
        }

        pt->req._proc = RT_NULL;

        if (num || istimeout) //只要有一个满足条件的设备,即退出
            break;

        if (poll_wait_timeout(pt, msec)) //挂起
            istimeout = 1;
    }

    return num;
}

.do_pollfd

每次查询一个设备fd的监听结果
函数内会根据fd查找相应设备,并暂时获取设备使用权
如果设备的驱动未实现poll函数,则会返回默认的POLLMASK_DEFAULT,保证使用poll的线程不会永久挂起

static int do_pollfd(struct pollfd *pollfd, rt_pollreq_t *req)
{
    int mask = 0;
    int fd;

    fd = pollfd->fd;

    if (fd >= 0)
    {
        struct dfs_fd *f = fd_get(fd); //根据fd查找对应设备,获取使用权
        mask = POLLNVAL;

        if (f)
        {
            mask = POLLMASK_DEFAULT; //如果没有定义poll函数,则由这里保证使用poll的线程不会永久挂起
            if (f->fops->poll)
            {
                req->_key = pollfd->events | POLLERR | POLLHUP;

                mask = f->fops->poll(f, req); //调用底层驱动实现的fops.poll()函数 //尽可能在底层使用清除而不是重新赋值
            }
            /* Mask out unneeded events. */
            mask &= pollfd->events | POLLERR | POLLHUP;
            fd_put(f); //交回设备使用权
        }
    }
    pollfd->revents = mask;

    return mask;
}

5.poll_wait_timeout

本函数一定会挂起poll线程
注意,poll的超时时间不能是-1,即不能永久阻塞!

static int poll_wait_timeout(struct rt_poll_table *pt, int msec)
{
    rt_int32_t timeout;
    int ret = 0;
    struct rt_thread *thread;
    rt_base_t level;

    thread = pt->polling_thread;

    timeout = rt_tick_from_millisecond(msec);

    level = rt_hw_interrupt_disable();

    if (timeout != 0 && !pt->triggered)
    {
        rt_thread_suspend(thread); //主动挂起poll的线程
        if (timeout > 0)
        {
            rt_timer_control(&(thread->thread_timer),
                             RT_TIMER_CTRL_SET_TIME,
                             &timeout);
            rt_timer_start(&(thread->thread_timer));
        }

        rt_hw_interrupt_enable(level);

        rt_schedule();

        level = rt_hw_interrupt_disable();
    }

    ret = !pt->triggered;
    rt_hw_interrupt_enable(level);

    return ret; //0,监听到事件发生/1,未监听到指定事件发生
}

总结:

通过以上函数分析可以发现:

  • 应用线程在调用poll()时,如果do_pollfd()监听到事件发生,则立即清除req->_proc
  • 如果没有监听到,无论是否设置了超时时间,都会在驱动中添加到等待队列中,等待异步唤醒(需要在驱动中调用req->_proc());
  • 如果没有监听到,同时设置了超时时间,则会在所有设备轮询完成后再poll_do()中挂起poll线程;
@rt_timer_start()
RT_ASSERT(timer->init_tick < RT_TICK_MAX / 2);
  • poll的超时时间不能设置成-1,即永久阻塞(因为poll中调用timer,timer不允许设置过大的超时时间,);

6._poll_add

此函数需要在驱动fops->poll()中调用,调用方式req->_proc(wq, req);
此函数的主要是挂接唤醒函数,用于异步唤醒。

static void _poll_add(rt_wqueue_t *wq, rt_pollreq_t *req)
{
    struct rt_poll_table *pt;
    struct rt_poll_node *node;

    node = (struct rt_poll_node *)rt_malloc(sizeof(struct rt_poll_node));
    if (node == RT_NULL)
        return;

    pt = rt_container_of(req, struct rt_poll_table, req);

    node->wqn.key = req->_key; //poll的events,监听的事件,do_pollfd中首次监听时被设置
    rt_list_init(&(node->wqn.list));
    node->wqn.polling_thread = pt->polling_thread;
    node->wqn.wakeup = __wqueue_pollwake; //挂接唤醒函数
    node->next = pt->nodes; //链接到poll_table中,单向带头节点的链表
    node->pt = pt;
    pt->nodes = node;
    rt_wqueue_add(wq, &node->wqn); //添加到队列中,用于线程间共享
}

7._wqueue_pollwake

此函数需要在驱动中调用,调用方式rt_wqueue_wakeup( &(dfs->wait_queue), (void *)POLLIN );
rt_wqueue_wakeup()中,在函数中会调用wakeup函数
注意,rt_wqueue_wakeup()会执行这个等待队列多有节点的wakeup函数!

//poll.c
static int __wqueue_pollwake(struct rt_wqueue_node *wait, void *key)
{
    struct rt_poll_node *pn;

    # if (key && !((rt_ubase_t)key & wait->key)) //key继承自rt_wqueue_wakeup()中的key参数
        return -1;

    pn = rt_container_of(wait, struct rt_poll_node, wqn);
    pn->pt->triggered = 1; //置触发标志,用于唤醒后poll设置监听结果

    return __wqueue_default_wake(wait, key); //rt源码中__wqueue_default_wake返回0
}

@waitqueue.c
int __wqueue_default_wake(struct rt_wqueue_node *wait, void *key)
{
    return 0;
}

8.poll_teardown

poll会为每一个没有监听到的设备创建一个rt_wqueue_node类型的等待节点
同一个个poll下的不同设备只有key不同

struct rt_wqueue_node
{
    rt_thread_t polling_thread; //未使用!
    rt_list_t   list; //用于链接成链表

    rt_wqueue_func_t wakeup; //唤醒函数,同一类型的设备一样!
    rt_uint32_t key; //监听的事件
};
static void poll_teardown(struct rt_poll_table *pt)
{
    struct rt_poll_node *node, *next;

    next = pt->nodes;
    while (next)
    {
        node = next;
        rt_wqueue_remove(&node->wqn);
        next = node->next;
        rt_free(node);
    }
}

2.使用注意


1 waitqueue继承自list,初始化只是初始化链表(self.next = self);
2 poll的超时不能设置成-1,不能永久阻塞。同时超时和timer的范围一样;
3 poll的驱动中尽量对传递下来的pollreq进行清除操作,不要重新创建,因为poll的上层会与pollreq做掩码处理;
4 poll的驱动中不需要再创建waitqueue(poll会创建);

  • 同时wq由以下的保护机制:
  • wq未被创建时,wq_wakeup会直接退出;
  • wq未被创建时,wq_wait会调用wq_init;
  • 并且,设备描述rt_device.rt_wqueue是分配了空间的,不是单纯的指针;
  • 当设备close时,需要对wq初始化,即清除;

5 尽量使用以下的流程:

  • 驱动中read中要有wq_wait操作,尽量使用WAITING_FOREVER,即当无数据时,挂起直到有数据;

  • 接收到数据时进行wq_wakeup操作(此举不需要一定和poll搭配,只是保证无数据时挂起,有数据时立即唤醒);

  • 尽量read前先poll,这样效率最高;

  • write前尽量也要用poll,如果上层每次write都等待操作结果(需要底层配合,底层知道了结果),则不必;

  • 如果底层不保证上层write时,底层返回前已经获知了操作结果,那么必须通过poll来监听POLLOUT

  • POLLOUT只有在底层完成上一次的write操作后才能置位;

  • POLLIN表示的是有数据可读,poll不是必须的;

  • POLLOUT表示的是写空闲,即上一次发送完成,可以进行下一次发送,poll不是必须的;

3.理想的使用场景


  • write前先poll,检测发送空闲;

  • write返回前不必得知执行结果;

  • read前先poll,保证有数据可读;

  • read设置成堵塞读取不会影响到上层人物;

通过以上方式:

  • 避免read的堵塞;
  • 实现write和上层应用的异步(一般需要DMA加持);
  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
ModbusPoll是一种常用的Modbus通信协议工具,在工业控制系统中广泛应用。ModbusPoll源码指的是该软件的程序代码。 ModbusPoll源码通常由C或C++编写,采用面向对象的编程风格。源码主要包含通信协议的实现、界面设计、数据解析和处理等功能。 在通信协议实现方面,源码通过使用串行通信和Modbus协议规范,实现了与Modbus从设备之间的数据交换。它支持Modbus协议的主要功能,如读取/写入寄存器、读取/写入线圈等。源码会对Modbus报文进行解析和封装,实现对Modbus通信指令的处理。 在界面设计方面,源码采用图形用户界面(GUI),通过可视化操作界面,方便用户进行参数设置和数据监控。源码提供了各种界面元素,如窗口、按钮、文本框等,以实现用户与软件的交互。 在数据解析和处理方面,源码会对收到的Modbus数据进行解析,提取出有用的信息。它将数据进行处理,并根据用户设置的规则进行分析。然后,源码将解析后的数据显示在界面上,以便用户进行监控和分析。 ModbusPoll源码可以为用户提供一个基于Modbus通信协议的开发平台,用户可以根据自己的需求进行二次开发和定制。通过查看源码,用户可以理解Modbus通信协议的实现细节,从而更好地掌握和应用该通信协议。 总的来说,ModbusPoll源码是ModbusPoll软件的程序代码,它实现了Modbus通信协议的相关功能,包括通信协议实现、界面设计、数据解析和处理。用户可以通过查看源码,进行二次开发和定制,以满足自己的需求。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值