OpENer实时性的研究

本文档记录OpENer库实时性的研究.

前言

首先介绍一下OpENer库

OpENer库的源码: https://github.com/EIPStackGroup/OpENer

OpENer是Ethernet/IP协议从站功能实现锁需要的开源代码库.

在这个开源库的readme文件中详细介绍了在 Linux/POSIX 中任何开启 实时功能.
在这里插入图片描述

本文档以OpENer库的实时性设计为基础,致力于 搞清楚 OpENer库实时性的设计逻辑 及 将它移植到普通程序的方法.

解释实时性

从OpENer库的readme文档来看,要实现OpENer的实时功能需要进行一下操作.

首先在CMake中添加 OpENer_RT 的支持

对编译出来的可执行文件添加权限 sudo setcap cap_ipc_lock,cap_sys_nice+ep ./src/ports/POSIX/OpENer .

从CMake文件看起, 定义了 -DOpENer_RT:BOOL=ON 之后会怎么样.

在CMake文件中, 仅有 source/src/ports/POSIX 中有使用到这个参数.

set( OpENer_RT OFF CACHE BOOL "Activate OpENer RT" )
if(OpENer_RT)
  set( OpENer_RT_Additional_Stacksize "10240" CACHE STRING "Additional stack size above the defined minimum")
  add_definitions( -DOPENER_RT )
  add_definitions(-DOPENER_RT_THREAD_SIZE=${OpENer_RT_Additional_Stacksize})
endif(OpENer_RT)

如果CMake时配置了 -DOpENer_RT:BOOL=ON , 里面主要定义了两个宏 OPENER_RTOPENER_RT_THREAD_SIZE(值为 10240)

再看一下代码中的影响:

有关这两个宏的代码都在 source/src/ports/POSIX/main.cpp 中 :

#ifdef OPENER_RT
    int ret;

    /* Memory lock all*/
    if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
      OPENER_TRACE_ERR("mlockall failed: %m\n");
      exit(-2);
    }

    struct sched_param param;
    pthread_attr_t attr;
    pthread_t thread;
    ret = pthread_attr_init(&attr);
    if (ret) {
      OPENER_TRACE_ERR("init pthread attributes failed\n");
      exit(-2);
    }

    /* Set stack size  */
    ret = pthread_attr_setstacksize(&attr,
                                    PTHREAD_STACK_MIN + OPENER_RT_THREAD_SIZE);
    if (ret) {
      OPENER_TRACE_ERR("setstacksize failed\n");
      exit(-2);
    }

    /* Set policy and priority of the thread */
    ret = pthread_attr_setschedpolicy(&attr, SCHED_RR);
    if (ret) {
      OPENER_TRACE_ERR("setschedpolicy failed\n");
      exit(-2);
    }
    param.sched_priority = 25;
    ret = pthread_attr_setschedparam(&attr, &param);
    if (ret) {
      OPENER_TRACE_ERR("pthread setschedparam failed\n");
      exit(-2);
    }
    /* scheduling parameters */
    ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    if (ret) {
      OPENER_TRACE_ERR("setinheritsched failed\n");
      exit(-2);
    }

    /* Create a thread with the specified attributes */
    ret = pthread_create(&thread, &attr, executeEventLoop, NULL);
    if (ret) {
      OPENER_TRACE_ERR("create pthread failed\n");
      exit(-2);
    }

    /* Join the thread */
    ret = pthread_join(thread, NULL);
    if (ret) {
      OPENER_TRACE_ERR("join pthread failed: %m\n");
    }
    /* Unlock memory */
    munlockall();
#else
    (void) executeEventLoop(NULL);
#endif

以上代码截取自main函数中,从代码来看,如果定义了 OPENER_RT 就会 通过线程的方式执行 executeEventLoop 函数. 如果没定义 OPENER_RT 会直接调用 executeEventLoop 函数.

一步步看一下定义了 OPENER_RT 后具体执行了什么?

  if (mlockall(MCL_CURRENT | MCL_FUTURE) == -1) {
      OPENER_TRACE_ERR("mlockall failed: %m\n");
      exit(-2);
    }

mlockall 函数是 Linux 操作系统中用于内存管理的一个系统调用, 它的主要作用是将当前进程的所有内存页锁定到物理内存中, 防止它们被交换到交换空间(swap). 这对于一些对性能和实时性要求非常高的应用程序非常重要, 因为交换操作可能会导致不可预测的延迟.

两个参数的意义:

MCL_CURRENT: 将当前内存页锁定到物理内存中.

MCL_FUTURE: 将未来分配的内存页也锁定到物理内存中.

参考链接: https://blog.csdn.net/junjun5156/article/details/70598113

    ret = pthread_attr_init(&attr);
    if (ret) {
      OPENER_TRACE_ERR("init pthread attributes failed\n");
      exit(-2);
    }

线程初始化 : pthread_attr_init 函数初始化一个线程属性对象 pthread_attr_t,为创建线程设置默认属性。

    ret = pthread_attr_setstacksize(&attr,
                                    PTHREAD_STACK_MIN + OPENER_RT_THREAD_SIZE);
    if (ret) {
      OPENER_TRACE_ERR("setstacksize failed\n");
      exit(-2);
    }

设置线程 栈 的大小.

两个参数 :

PTHREAD_STACK_MIN : 这是系统定义的线程栈的最小值.

OPENER_RT_THREAD_SIZE : 这是刚刚在CMake文件中定义的宏,值为 10240 .

/* Set policy and priority of the thread */
ret = pthread_attr_setschedpolicy(&attr, SCHED_RR);
if (ret) {
  OPENER_TRACE_ERR("setschedpolicy failed\n");
  exit(-2);
}

设置线程调度策略.

关于线程调度策略的问题建议参考链接: https://blog.csdn.net/weixin_38184628/article/details/135589790

这里将线程的调度策略设置成 SCHED_RR(轮询). 个线程将时间片用完的时候会触发调度.

    param.sched_priority = 25;
    ret = pthread_attr_setschedparam(&attr, &param);
    if (ret) {
      OPENER_TRACE_ERR("pthread setschedparam failed\n");
      exit(-2);
    }

设置线程优先级.

pthread_attr_setschedparam 函数用于设置线程属性对象的调度参数. 具体来说, 它可以设置线程的优先级等调度相关属性.

这里将线程的优先级设置成25.

    ret = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);
    if (ret) {
      OPENER_TRACE_ERR("setinheritsched failed\n");
      exit(-2);
    }

这段代码的作用是设置线程属性对象 attr 的调度继承属性, 使得新创建的线程显式地使用由 pthread_attr_setschedpolicypthread_attr_setschedparam 函数设置的调度策略和优先级, 而不是继承其创建者线程的调度属性.

    ret = pthread_create(&thread, &attr, executeEventLoop, NULL);
    if (ret) {
      OPENER_TRACE_ERR("create pthread failed\n");
      exit(-2);
    }

创建一个新的线程, 启动 executeEventLoop 函数.

    ret = pthread_join(thread, NULL);
    if (ret) {
      OPENER_TRACE_ERR("join pthread failed: %m\n");
    }

阻塞当前线程,等待线程中止.

munlockall();

用于解除当前进程地址空间中所有内存页的锁定. 该函数会使进程的所有内存页变为可交换, 即允许它们被移出物理内存并写入交换空间(swap) .

这个地方是主要的工作线程因为特殊情况结束了,所以解除了 内存锁的限制.

然后就是 执行命令 sudo setcap cap_ipc_lock,cap_sys_nice+ep ./src/ports/POSIX/OpENer .
这里直接引入chatGPT对于这条指令的解释:
功能:
cap_ipc_lock:允许进程锁定其虚拟内存页,避免这些页面被换出,适用于需要实时性能的应用。
cap_sys_nice:允许进程改变自身或其他进程的调度优先级,提升实时调度能力。

参数:
+ep:表示将能力添加为有效(effective)和可继承(permitted),这样程序在执行时会拥有这些特权。

原理:
Linux 内核通过能力机制替代传统的超级用户权限,使得程序可以拥有仅所需的特权,减少安全风险。通过 setcap 设置能力,程序在运行时将拥有相应的特权,而不需要以超级用户身份运行,从而提升了系统的安全性。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值