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

本文详细介绍了RTOS中poll函数的工作原理和流程,包括poll的使用、poll_table的初始化、poll_do函数的循环检查、do_pollfd的设备事件检测以及poll_wait_timeout的超时处理。还特别强调了poll_wait_timeout的超时限制和_wqueue_pollwake的唤醒机制。此外,讨论了驱动程序如何配合poll机制以及在实际应用中的注意事项,如避免read堵塞和实现异步write。

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加持);
<think>我们正在将librws库移植到RT-Thread操作系统。librws是一个轻量级的WebSocket客户端库,通常用于嵌入式设备中实现WebSocket通信。RT-Thread是一个开源的嵌入式实时操作系统,支持多种架构和硬件平台。步骤概述: 1. **获取librws源码**:从官方仓库(如GitHub)获取librws的源代码。 2. **分析依赖**:librws依赖于特定的网络库(如libcurl或原生的socketAPI)以及可能需要的TLS库(如mbedTLS、OpenSSL等)。RT-Thread有自己的网络协议栈和SAL(套接字抽象层),我们需要将librws的网络操作适配到RT-Thread的SAL上。3.**创建RT-Thread软件包**:按照RT-Thread的包管理方式(env工具和pkgs)创建新的软件包,将librws集成进去。4.**编写适配层**:由于librws可能使用了POSIX标准的socketAPI,而RT-Thread的SAL提供了类似的接口,但可能不完全相同,因此可能需要编写适配代码。5.**配置TLS支持**:如果使用安全WebSocket(wss),则需要集成TLS库。RT-Thread通常使用mbedTLS,因此需要确保librws使用mbedTLS作为后端。 6. **修改构建脚本**:编写SConscript或Kconfig文件,以便在RT-Thread的构建系统中编译librws。7.**测试**:在RT-Thread上运行librws的示例程序,验证功能。 详细步骤:###1.获取librws源码从GitHub克隆librws的仓库:https://github.com/gsauthof/librws###2.分析依赖-librws主要依赖于socket和TLS库。- RT-Thread提供了SAL(套接字抽象层),可以支持多种网络协议栈(如lwIP、ATSocket等)。因此,我们需要将librws中直接使用socket的地方改为使用SAL的接口。-对于TLS,RT-Thread通常使用mbedTLS,因此需要配置librws使用mbedTLS。 ###3.创建RT-Thread软件包在RT-Thread的包管理器中创建一个新的软件包,例如`packages/librws`。目录结构建议如下: ```librws/├───inc///头文件├───src///librws的源码├───port///移植相关的适配代码├───SConscript//构建脚本└───Kconfig//配置选项```###4.编写适配层由于librws原本是为Linux等系统设计,使用了标准的BSDsocket API。我们需要将其替换为RT-Thread的SAL接口。SAL提供了与标准socket兼容的接口,因此通常只需要修改头文件和链接即可。但需要注意: -头文件:将`#include<sys/socket.h>`等改为`#include<sys/socket.h>`(RT-Thread的SAL提供了类似头文件,但实际在RT-Thread环境中,应使用`#include<sys/socket.h>`,因为RT-Thread的SAL头文件在`components/net/sal_socket/include/sal_socket.h`,但通常通过`#include<sys/socket.h>`即可使用,因为RT-Thread的SAL已经重定向了标准socket头文件)。 -如果librws使用了多线程,需要将线程操作替换为RT-Thread的线程API(如创建线程、互斥锁等)。但librws本身可能并不需要多线程,需要检查其源码。###5.配置TLS支持在RT-Thread中,通常使用mbedTLS作为TLS库。我们需要在librws中配置使用mbedTLS。具体步骤: -修改librws的TLS后端实现,使其调用mbedTLS的API。 -RT-Thread中开启mbedTLS支持,并在librws的Kconfig中增加依赖。###6.修改构建脚本编写SConscript文件,指导RT-Thread的构建系统如何编译librws。示例: ```pythonfrom buildingimport *cwd =GetCurrentDir()src =Glob('src/*.c')+ Glob('port/*.c')path= [cwd+ '/inc',cwd+ '/port']group =DefineGroup('librws', src, depend= ['PKG_USING_LIBRWS'],CPPPATH=path)Return('group')``` 在Kconfig中定义配置选项:```config PKG_USING_LIBRWSbool"librws: Lightweight WebSocket clientlibrary"defaultnselectRT_USING_SAL#依赖SALselectRT_USING_MBEDTLS#如果使用TLS,则依赖mbedTLSifPKG_USING_LIBRWSconfig PKG_LIBRWS_PATHstringdefault"/packages/iot/librws"config PKG_LIBRWS_VERstringdefault "v1.0.0" endif```###7.测试编写一个测试程序,使用librws连接到一个WebSocket服务器,并发送接收消息。注意在RT-Thread中,需要先初始化网络(如连接WiFi或使能有线网络)。 ###注意事项-由于嵌入式资源限制,可能需要配置librws为单线程模式(如果它支持的话)。-注意内存使用,可能需要调整librws的缓冲区大小。-如果librws使用了阻塞式socket,需要确保在RT-Thread中使用时不会阻塞整个系统(可以使用select或非阻塞模式,并在单独的线程中运行WebSocket操作)。 ###参考RT-Thread的官方文档和示例,特别是网络相关的软件包(如WebClient、PahoMQTT等)的移植方式。§§相关问题§§1.如何为RT-Thread编写一个新的软件包? 2. RT-Thread的SAL(套接字抽象层)是如何工作的? 3.如何在RT-Thread中使用mbedTLS实现安全通信? 4.在RT-Thread中如何管理多线程网络应用?5.如何调试RT-Thread中的网络问题?</think>### 如何将 librws 移植到 RT-Thread 操作系统 #### 移植步骤详解 1. **分析库依赖** - librws 的核心依赖: - POSIX socket API(基础网络通信) - TLS 库(如 mbedTLS/OpenSSL,用于加密连接) - 可选:线程支持(若需异步处理) - RT-Thread 对应能力: $$ \text{RT-Thread SAL} \rightarrow \text{socket API} $$ $$ \text{mbedTLS 软件包} \rightarrow \text{TLS 支持} $$ $$ \text{RT-Thread 线程管理} \rightarrow \text{多任务处理} $$ 2. **创建软件包目录** 按 RT-Thread 包规范创建结构: ``` librws/ ├── inc/ # librws 头文件 ├── src/ # librws 源码 ├── port/ # RT-Thread 适配层 │ ├── socket_adapt.c │ └── tls_adapt.c ├── Kconfig # 包配置选项 └── SConscript # 构建脚本 ``` 3. **实现关键适配层** - **Socket 适配**(`port/socket_adapt.c`): ```c #include <sys/socket.h> #include <sal_socket.h> // RT-Thread SAL 头文件 // 重定向 connect 到 SAL int rws_connect(int fd, const struct sockaddr *addr, socklen_t len) { return sal_connect(fd, addr, len); } // 类似实现 send/recv/close 等 ``` - **TLS 适配**(`port/tls_adapt.c`): ```c #include <mbedtls/ssl.h> // RT-Thread mbedTLS 头文件 void *rws_tls_init(void) { mbedtls_ssl_context *ctx = rt_malloc(sizeof(mbedtls_ssl_context)); mbedtls_ssl_init(ctx); return ctx; } ``` 4. **配置构建系统** - **Kconfig 示例**: ``` config PKG_USING_LIBRWS bool "librws: Lightweight WebSocket client" select RT_USING_SAL # 依赖 SAL 层 select PKG_USING_MBEDTLS # 依赖 mbedTLS default n ``` - **SConscript 构建脚本**: ```python from building import * src = Glob('src/*.c') + Glob('port/*.c') cflags = '-Iinc -Iport' group = DefineGroup('librws', src, depend=['PKG_USING_LIBRWS'], CFLAGS=cflags) Return('group') ``` 5. **解决关键问题** - **线程安全**: - 若 librws 使用多线程,替换为 RT-Thread 线程 API: ```c // 原 pthread_create 替换为 rt_thread_t thread; rt_thread_create(&thread, "ws_th", ws_thread_func, NULL, 2048, 20, 10); ``` - **内存管理**: - 将库中的 `malloc/free` 替换为 `rt_malloc/rt_free` - **事件循环**: - 使用 RT-Thread 的 `select()` 或事件标志组替代 `poll()` 6. **测试验证** ```c #include <rws.h> #include <netdev.h> // RT-Thread 网络设备 void websocket_test() { // 确保网络就绪 struct netdev *net = netdev_get_first(); while (!netdev_is_up(net)) rt_thread_mdelay(100); // 使用 librws 连接 rws_socket socket = rws_connect("wss://echo.websocket.org", 443); rws_send(socket, "Hello RT-Thread!"); } ``` #### 移植注意事项 1. **配置裁剪**: - 通过 `#define RWS_CONFIG_XXX` 关闭非必要功能(如调试输出) - 在 `rtconfig.h` 中设置配置宏 2. **资源优化**: - 减小收发缓冲区:修改 `RWS_CONFIG_BUF_SIZE` - 关闭冗余校验:`#define RWS_CONFIG_VERIFY_SSL 0` 3. **SAL 层兼容**: - 若遇到 `getsockopt` 等未实现函数,在 `port/` 添加桩函数: ```c int rws_getsockopt(int fd, int level, int optname, void *optval, socklen_t *optlen) { return sal_getsockopt(fd, level, optname, optval, optlen); } ``` 4. **调试技巧**: - 开启 SAL 调试:`#define SAL_DBG_ENABLE` - 使用 `rws_set_log_callback()` 捕获库日志 > **移植验证要点**: > 1. 基础 TCP 连接测试(先测试 ws:// 再 wss://) > 2. 大数据包压力测试(验证内存管理) > 3. 长时间稳定性测试(结合看门狗)[^1][^2] #### 参考资源 - [RT-Thread SAL 编程指南](https://www.rt-thread.org/document/site/#/rt-thread-version/rt-thread-standard/programming-manual/sal/sal) - [mbedTLS 在 RT-Thread 中的使用](https://github.com/RT-Thread-packages/mbedtls) - librws 官方仓库:[https://github.com/gsauthof/librws](https://github.com/gsauthof/librws) ---
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值