每个实时任务都有死线限制。如果在硬实时系统,丢失死线后的计算结果是没有任何意义的;在软实时系统,丢失死线后的计算结果也遭到不同程度的贬值。因此在执行实时任务时,使用最优算法减少执行指令外,还要避免任务被阻塞。
1.
限制与动态内存子系统的换交
在开发一个实时应用程序之前,必须知道系统内存的限制。例如,检验系统是否有足够的空闲RAM。任何使用动态内存分配的系统,至少需要1024KB的空闲内存。
另外,限制与动态内存子系统的相互作用也是非常重要的。当实时进程或者线程和动态内存子系统打交道时,它们的性能会下降。
2.
避免缺页异常
Linux进程堆栈的大小动态变化的。当进程的函数嵌套过大或使用大数组自动变量时,堆栈空间可能不够。这时系统会产生一个缺页异常,执行异常处理程序,向系统申请一些页面,加入到当前进程的堆栈空间。这时,进程才得以继续运行。也是说,当产生缺页异常时,是会延迟当进程的执行。
如果实时程序是在线程中执行,可以使用pthread_attr_setstacksize函数设置线程的堆栈的大小,如下程序清单所示:
- #include <stdio.h>
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sched.h>
-
#include <time.h>
-
#include <pthread.h>
-
#include <sys/mman.h>
-
#include <sys/time.h>
-
#define MILLION 1000000L
-
-
static int doService(int *p)
-
{
-
int i = 0, j;
-
-
struct sched_param sched_param;
-
struct timeval start_tv, end_tv;
-
struct timezone tz;
-
gettimeofday(&start_tv, &tz);
-
for( i = 0; i < 30000; i++) (1)
-
for (j = 0; j < 10000; j++)
-
j++; (2)
-
gettimeofday(&end_tv, &tz)
-
-
printf("thread time: %f \n", ((double)(MILLION*(end_tv.tv_sec - start_tv.tv_sec) +
-
(end_tv.tv_usec - start_tv.tv_usec)) )/1000000L);
-
-
return 0;
-
}
-
-
int main()
-
{
-
int iRet = 0;
-
int i = 0, j;
-
-
struct sched_param sched_param;
-
struct timeval start_tv, end_tv;
-
-
pthread_attr_t thread_attr;
-
struct timezone tz;
-
pthread_t threadNew;
-
-
iRet = pthread_attr_init(&thread_attr);
-
if (iRet != 0) {
-
perror("Attribute creation failed \n");
-
exit("EXIT_FAILURE");
-
}
-
iRet = pthread_attr_setstacksize(&thread_attr, 65536);
-
if (iRet != 0) {
-
perror("Settin Stack Size failed \n");
-
exit("EXIT_FAILURE");
-
}
-
sched_param.sched_priority = 49;
-
iRet = pthread_create(&threadNew, &thread_attr, (void *) doService, NULL);
-
if (iRet == 0) {
-
iRet = pthread_setschedparam(threadNew, SCHED_FIFO, &sched_param);
-
}
-
gettimeofday(&start_tv, &tz);
-
-
for( i = 0; i < 30000; i++) (3)
-
for (j = 0; j < 10000; j++)
-
j++; (4)
- gettimeofday(&end_tv, &tz);
-
printf("use time: %f \n", ((double)(MILLION*(end_tv.tv_sec - start_tv.tv_sec) +
-
-
end_tv.tv_usec - start_tv.tv_usec ))/MILLION);
-
sleep(1);
-
}
同时如果要在实时代码中使用大数组或占大内存的数据结构时,建议尽量使用静态变量,减少引起缺页异常的可能。
3.
避免页交换
实时程序在运行时应该避免进程交换到磁盘(禁止页面调度)。如果进行SWAP操作,任何进程都会产生很长的延时。必须将对时间要求很严格的应用程序锁定在RAM中。为了将实时应用的页面锁定在RAM中,可以使用mlockall()函数,并设置下面两个标志:
MCL_CURRENT——锁定当前所有映射到进程空间的页面;
MCL_FUTRUE——锁定所有将会被映射到进程空间的页面。
不过,在MontaVista可以用的宏只有MCL_CURRENT。
实时进程中,使用mlockall来禁止进程调度时候的页面调度。用法:
#include
int mlockall(int flags);
由于在程序中,所有线程是共享内存的,所以我们只需要在主线程中调用mlockall函数即可,如下 REF _Ref210103870 \h 程序清单所示。
- #include <stdio.h>
-
#include <stdlib.h>
-
#include <stdio.h>
-
#include <stdlib.h>
-
#include <sched.h>
-
#include <time.h>
-
#include <pthread.h>
-
#include <sys/mman.h>
-
#include <sys/time.h>
-
-
#define MILLION 1000000L
-
-
static int doService(int *p)
-
{
-
int i = 0, j;
-
-
struct sched_param sched_param;
-
struct timeval start_tv, end_tv;
-
struct timezone tz;
-
gettimeofday(&start_tv, &tz);
-
-
for( i = 0; i < 30000; i++) (1)
- for (j = 0; j < 10000; j++)
-
j++; (2)
-
-
gettimeofday(&end_tv, &tz);
-
printf("thread time: %f \n", ((double)(MILLION*(end_tv.tv_sec - start_tv.tv_sec) +
-
(end_tv.tv_usec - start_tv.tv_usec)) )/1000000L);
-
-
return 0;
-
}
-
int main()
-
{
-
int i = 0, j;
-
struct sched_param sched_param;
-
struct timeval start_tv, end_tv;
-
pthread_attr_t thread_attr;
-
struct timezone tz;
-
pthread_t threadNew;
-
-
int iRet = 0;
-
- mlockall(MCL_CURRENT);
-
-
iRet = pthread_attr_init(&thread_attr);
-
-
if (iRet != 0) {
-
perror("Attribute creation failed \n");
-
exit("EXIT_FAILURE");
-
}
-
iRet = pthread_attr_setstacksize(&thread_attr, 65536);
-
if (iRet != 0) {
-
perror("Settin Stack Size failed \n");
- exit("EXIT_FAILURE");
-
}
-
sched_param.sched_priority = 49;
-
iRet = pthread_create(&threadNew, &thread_attr, (void *) doService, NULL);
-
if (iRet == 0) {
-
iRet = pthread_setschedparam(threadNew, SCHED_FIFO, &sched_param);
-
}
-
gettimeofday(&start_tv, &tz);
-
-
for( i = 0; i < 30000; i++) (3)
-
for (j = 0; j < 10000; j++)
-
j++; (4)
-
-
gettimeofday(&end_tv, &tz);
-
printf("use time: %f \n", ((double)(MILLION*(end_tv.tv_sec - start_tv.tv_sec) +
-
-
end_tv.tv_usec - start_tv.tv_usec ))/MILLION);
-
sleep(1);
-
}
mlockall禁止所有被映射到被调用进程空间的页面调度,包含代码页面、数据和堆栈段,还有共享库、用户空间的内核数据、共享内存和内存映射文件。当mlockall调用成功后,所有被映射的页面都在RAM中,直到页面被unlock解锁或者进程终止或者程序中通过exec开始另外一个程序。通过fork产生的子进程不会继承页面锁。
内存锁定有两个主要应用:实时算法和高安全数据处理。实时应用程序要求非常严格的时序,像调度、页面调度是无法预计的延迟的主要因素。实时应用程序通常通过sched_setscheduler切换到实时调度器。带加密的高安全的软件通常处理像密钥、安全密码等关键数据,而通常的页面调度,会将这些数据转移到一个现有的交换媒介,即时软件已经清空了RAM或者已经终止,但还可以访问到这些数据。当然,对于安全性应用,仅有非常少量的页面需要被锁定。
flags参数可设置为MCL_CURRENT。实时进程必须保证在进入临界态之前有足够的锁定的堆页面,这样就不会导致页面错误了。调用一个有足够大的自动变量的函数,向占用的内存空间进行写操作,可以做到这一点。这样,就有足够的堆页面被锁定在RAM中。Dummy写操作能够保证在临界段连COW(Copy-On-Write)页面错误都不会发生。
返回值,锁定成功返回0,返回-1表示错误,错误号:
ENOMEM——超出所允许的最大页面;
EPERM——没有权限,仅root进程有锁定页面的权限。
EINVAL——Flags错误。
页面解锁函数是munlockall,用法:
int munlockall(void);
1.1.2
避免死锁和优先级倒置
任何基于优先级调度的系统,在运行多任务的时候,都有可能出现死锁或者优先级反转的问题。当占有资源R1任务A为了完成执行,需要等待被任务B所占用的资源R2;如果任务B必须需要获得已经被任务A占有的资源R1才能释放R2,这样就发生了死锁。避免死锁的一个基本方法是保证临界资源总是以某种顺序获取,并以相反的顺序释放。
当一个较低优先级任务获得了一个较高优先级所需要的资源的时候会发生优先级反转情况。高优先级任务被阻塞,等待资源,但是较低优先级任务因为中等优先级而不能运行。这将导致高优先级任务立即阻塞的状况。有很多方法可以罚避免优先级反转,一个简单方法就是将占有资源的任务处理资源期间,将它的优先级提高到比任何一个将要获取资源的高优先级任务的优先级还高。
阅读(3294) | 评论(0) | 转发(0) |