移植Real Time Linux到英创工控主板

 2017-10-16     作者:廖光泽     [nemail]    
[lablebox]

  为了使英创的嵌入式主板产品满足工业实时控制的应用需求,我们对ESM335x系列主板的内核进行了调整,重新移植了Linux 4.1.7的系统,在Linux 4.1.7的基础上使用了kernel的RT PATCH(实时补丁),并且修改了相关接口的驱动代码,来保证操作系统的实时性。关于实时Linux(又称RT Linux)系统的相关技术知识可以浏览网站:https://wiki.linuxfoundation.org/realtime/documentation/start进行了解。我们将在下面介绍如何编写面向实时控制的应用程序,以及在实时Linux系统和标准Linux系统环境中,应用程序对硬件事件响应延时的测试结果。


实时应用程序编写


  尽管实时Linux系统对内核做了大量的改动来提供系统的实时性,但对于应用程序来说,只需要对有实时要求的线程设置实时优先级属性,以及设置响应的实时线程调度策略就可以了,并没有特殊的API函数。关于设置实时优先级以及调度策略请参考我们网站上的文章:《Linux系统调度简介》,本文中用到的函数以及结构体都在其中有更为详细的介绍。


  设置实时优先级以及调度策略有两种方式,一种是在创建线程时设置线程的属性,这样线程创建后就具有了实时优先级并且使用设置的调度策略,函数原型如下:


  设置实时优先级属性:

  int pthread_attr_setschedparam (pthread_attr_t *attr, const struct sched_param *param)


  上述函数所包含的2个参数说明如下:

  ● pthread_attr_t *attr 由pthread_attr_init函数初始化,是线程的属性

  ● struct sched_param结构体包含 int sched_priority,也即实施优先级,取值范围0~99。数值越大优先级越高,所有的实时线程优先级都高于普通线程。为了提高系统的实时性,RT Linux将大部分中断服务都改为了线程的形式,使得中断服务可以被实时要求更高的线程抢占,中断处理线程实时优先级为50,操作系统中最重要的线程(如看门狗线程)的优先级为99,当知道应用程序的处理需要等待某个特定的中断才能继续执行,而且该段代码实时性要求很高时,就可以将该段代码所在线程的实时优先级设置为大于50小于99的值,这样就能够不受其他中断处理线程的影响,最大限度的满足实时性要求,比如我们在下面的测试程序中设置我们的线程实施优先级为80。


  设置调度策略:

  int pthread_attr_setschedpolicy (pthread_attr_t *attr, int policy)

  ● policy 为调度策略,可选SCHED_FIFO或者SCHED_RR,两者都是实时调度策略,SCHED_FIFO为先进先出,只有高实时优先级线程能抢占当前运行的实时线程,SCHED_RR增加了时间片机制,使得同优先级的实时线程能够轮转运行。


  使用示例:

  // 设置调度策略为SCHED_FIFO

  res = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

  if(res)

  {

         printf("pthread setschedpolicy faild\n");

  }

 

  struct sched_param param;

  // 设置实时优先级为80

  param.sched_priority = 80;

  res = pthread_attr_setschedparam(&attr, &param);

  if(res)

  {

         printf("pthread setschedparam faild\n");

  }


  另一种方式是修改已经存在的线程,设置其实时优先级和使用的调度策略,可以在其他线程中设置,也可以在线程中自行设置,只需要知道线程的PID,线程自行设置时PID为0,函数原型如下:

  int sched_setscheduler (pid_t pid, int policy, const struct sched_param *param)


  使用示例:

  struct sched_param param;

  // 设置实时优先级为80并且使用调度策略SCHED_FIFO

  param.sched_priority = 80;

  if(sched_setscheduler(0, SCHED_FIFO, &param))

         printf("wrong sched_setscheduler\n");


  还有其他的函数可以设置这些相关属性,具体请参考我们网站的文章《Linux调度策略简介》,或者其他相关资料。一般的使用是将有实时要求的功能代码放入单独的线程中,主程序以普通线程启动后建立实时线程来完成相关功能。由于实时线程的不可抢占性,不恰当的使用有可能会造成系统资源被无限占用,而影响系统的正常运行,因此在开发阶段需要特别注意,规划好实时线程的任务,尤其是要检查循环语句,对于循环任务的设计可以参考网站资料:https://wiki.linuxfoundation.org/realtime/documentation/howto/applications/cyclic


RT Linux的实时性测试


  在我们的测试中,由GPIO中断作为硬件事件,具体是使用外部的方波信号发生器作为中断源,驱动检测到GPIO中断后会唤醒用户层的中断处理线程(该线程已设置为实时线程),实时中断处理线程作为对中断事件的响应,是将另一位GPIO的输出电平反相(Toggle处理)。用示波器测量输入的中断信号和作为响应的GPIO信号之间的时间延迟。程序部分代码如下:


  struct paraset

  {

         int fd;                  // 中断设备对应的文件

         int gpio;                     // 线程使用的GPIO进行toggle处理

         int rt_prio;           // 线程的实时优先级,0则设置为普通线程

  };

 

  int IRQSelectThreadFunc(void* lparam)

  {

         struct paraset * p = (struct paraset*)lparam;

         int fd = p->fd ;  // 获取中断设备对应的文件识别号

         int i=0;

         printf ( "fd = %d \n", fd );

         struct sched_param param;

         param.sched_priority = p->rt_prio;      // 设置实时优先级,由主线程传入

         if(p->rt_prio)

                if(sched_setscheduler(0, SCHED_FIFO, &param))

                       printf("wrong sched_setscheduler\n");

         fd_set fdRead;

         struct timeval aTime;

         int ret;

         GPIO_OutEnable(gpio_fd, 1<<p->gpio);

         while(1)

         {

                FD_ZERO(&fdRead);

                FD_SET(fd,&fdRead);

                aTime.tv_sec = 2;

                aTime.tv_usec = 0;

                // 等待中断事件

                ret = select ( fd+1, &fdRead, NULL, NULL, &aTime );

                if ( ret<0 )

                       printf( "select, something wrong!\n " );

                if ( ret>0 )

                {

                       if ( FD_ISSET(fd, &fdRead) )

                       {

                              //toggel one gpio

                              nIrqCounter++;

                              if(nIrqCounter%2)

                                     GPIO_OutClear(gpio_fd, 1<<p->gpio);

                              else

                                     GPIO_OutSet(gpio_fd, 1<<p->gpio);

                       }

                }

         }

         pthread_exit( NULL );

         return 0;

  }

 

  int StartPulseThread( struct paraset *p  )

  {

         pthread_attr_t           attr;

         pthread_t                  m_thread;

         int                               res;

         struct sched_param param;


         // 创建gpio线程

         res = pthread_attr_init(&attr);

         if( res!=0 )

         {

                printf("Create attribute failed\n" );

         }

         // 主程序传入的参数,判断实时优先级是否为0,不为0则设置为实时线程

         if(p->rt_prio)

         {

                // 设置调度策略为SCHED_FIFO

                res = pthread_attr_setschedpolicy(&attr, SCHED_FIFO);

                if(res)

                {

                       printf("pthread setschedpolicy faild\n");

                }

                // 设置实时优先级

                param.sched_priority = p->rt_prio;

                res = pthread_attr_setschedparam(&attr, &param);

                if(res)

                {

                       printf("pthread setschedparam faild\n");

                }

                res = pthread_attr_setinheritsched(&attr, PTHREAD_EXPLICIT_SCHED);

                if(res)

                {

                       printf("pthread setinheritshed faild\n");

                }

                printf("set %d rt_priority: %d\n", p->fd, p->rt_prio);

         }

         // 创建线程

         res = pthread_create( &m_thread, &attr, (void *(*) (void *))&IRQSelectThreadFunc, p );

         if( res!=0 )

         {

                return -1;

         }

 

         pthread_attr_destroy( &attr );

 

    return 0;

  }


  我们用同一个测试程序分别在ESM3354和ESM3352的标准Linux版本和RT Linux上运行,当输入100Hz方波信号,即硬件中断间隔为10ms时,随机测试100次中断延时的统计结果如下:


主板型号ESM3354ESM3352
CPU信息Cortex-A8 主频1GHzCortex-A8 主频600MHz
操作系统Linux-4.1.6RT Linux-4.1.7Linux-4.1.6RT Linux-4.1.7
空载中断延时最小值(us)16152620
空载中断延时最大值(us)24203927
空载中断延时平均值(us)19.6416.1630.9225.04
空载中断延时标准值(us)1.220.692.410.65
满载中断延时最小值(us)885810080
满载中断延时最大值(us)10501852200220
满载中断延时平均值(us)140.7394.36183.06108.94
满载中断延时标准值(us)115.1717.33246.1518.81



  从上表可以看到:

  ● 尽管在空载情况下普通Linux系统与RT Linux系统的中断延时参数差异不大,但在满负荷下差异就非常明显了,特别是中断延时最大值。均超过1ms,这意味着普通Linux只能用于对实时性要求很低的场合。相对的,RT Linux在这项指标上有了很大的提高,已可满足最小硬件中断间隔为1ms的应用。

  ● RT Linux无论在空载还是满载的情况下,中断延时的方差,相对其平均值都比较小,这表示RT Linux系统对硬件中断的响应延时很一致,响应时间更稳定,这也是良好实时性的重要指标。

  ● ESM3354相比于ESM3352由于CPU主频更高,响应时间更短,但是在RT Linux上的响应时间均方差差别不大。由于ESM3352成本更低,而对于实时应用来说响应的实时性与ESM3354基本相同,所以在选购时客户可以优先考虑性价比更高的ESM3352。


  进一步,把外部中断频率提高一个数量级,即把中断间隔缩短至1ms,再来比较中断响应延时的变化。测试数据如下:


主板型号ESM3354ESM3352
CPU信息Cortex-A8 主频1GHzCortex-A8 主频600MHz
操作系统RT Linux-4.1.7
硬件中断间隔10000us1000us10000us1000us
空载中断延时最小值(us)15142022
空载中断延时最大值(us)20182730
空载中断延时平均值(us)16.1614.8025.0423.25
空载中断延时标准值(us)0.690.570.650.92
满载中断延时最小值(us)58408054
满载中断延时最大值(us)185125220160
满载中断延时平均值(us)94.3657.30108.9470.58
满载中断延时标准值(us)17.339.6918.8111.78



  可以看到,外部中断间隔变小之后,响应延时并没有变得更大,反而由于代码执行频率增加而更有可能常驻高速cache从而更快的得到运行,减小响应延时。考虑到我们测试程序的中断处理线程在收到中断后处理比较简单,而实际应用中往往需要执行更为复杂的操作,需要更多的执行时间,一般在50us的水平,这样从硬件中断开始,到处理线程完成响应动作,最长需要大约270us时间。


  根据以上的测试分析,可以认为ESM335x + RT Linux系统是完全能满足最小硬件中断间隔不低于1ms的实时控制应用,这意味着可满足大部分的实时控制的应用需求。


  我们也将会在近期推出ESM6800的RT Linux版本,ESM6800采用iMX6UL Cortex-A7 528MHz CPU,以低功耗、低成本为特色,特别适用于对成本敏感的批量工业智能设备。请关注我们官网以及官方微信公众号的最新信息。


  有兴趣的客户可以直接和我们的工程师进行沟通获取相关文件进行评估测试。

[lablebox]