為了使英創的嵌入式主板產品滿足工業實時控制的應用需求,我們對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, ¶m);
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, ¶m))
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, ¶m))
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, ¶m);
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次中斷延時的統計結果如下:
主板型號 | ESM3354 | ESM3352 | ||
CPU信息 | Cortex-A8 主頻1GHz | Cortex-A8 主頻600MHz | ||
操作系統 | Linux-4.1.6 | RT Linux-4.1.7 | Linux-4.1.6 | RT Linux-4.1.7 |
空載中斷延時最小值(us) | 16 | 15 | 26 | 20 |
空載中斷延時最大值(us) | 24 | 20 | 39 | 27 |
空載中斷延時平均值(us) | 19.64 | 16.16 | 30.92 | 25.04 |
空載中斷延時標準值(us) | 1.22 | 0.69 | 2.41 | 0.65 |
滿載中斷延時最小值(us) | 88 | 58 | 100 | 80 |
滿載中斷延時最大值(us) | 1050 | 185 | 2200 | 220 |
滿載中斷延時平均值(us) | 140.73 | 94.36 | 183.06 | 108.94 |
滿載中斷延時標準值(us) | 115.17 | 17.33 | 246.15 | 18.81 |
從上表可以看到:
● 盡管在空載情況下普通Linux系統與RT Linux系統的中斷延時參數差異不大,但在滿負荷下差異就非常明顯了,特別是中斷延時最大值。均超過1ms,這意味著普通Linux只能用于對實時性要求很低的場合。相對的,RT Linux在這項指標上有了很大的提高,已可滿足最小硬件中斷間隔為1ms的應用。
● RT Linux無論在空載還是滿載的情況下,中斷延時的方差,相對其平均值都比較小,這表示RT Linux系統對硬件中斷的響應延時很一致,響應時間更穩定,這也是良好實時性的重要指標。
● ESM3354相比于ESM3352由于CPU主頻更高,響應時間更短,但是在RT Linux上的響應時間均方差差別不大。由于ESM3352成本更低,而對于實時應用來說響應的實時性與ESM3354基本相同,所以在選購時客戶可以優先考慮性價比更高的ESM3352。
進一步,把外部中斷頻率提高一個數量級,即把中斷間隔縮短至1ms,再來比較中斷響應延時的變化。測試數據如下:
主板型號 | ESM3354 | ESM3352 | ||
CPU信息 | Cortex-A8 主頻1GHz | Cortex-A8 主頻600MHz | ||
操作系統 | RT Linux-4.1.7 | |||
硬件中斷間隔 | 10000us | 1000us | 10000us | 1000us |
空載中斷延時最小值(us) | 15 | 14 | 20 | 22 |
空載中斷延時最大值(us) | 20 | 18 | 27 | 30 |
空載中斷延時平均值(us) | 16.16 | 14.80 | 25.04 | 23.25 |
空載中斷延時標準值(us) | 0.69 | 0.57 | 0.65 | 0.92 |
滿載中斷延時最小值(us) | 58 | 40 | 80 | 54 |
滿載中斷延時最大值(us) | 185 | 125 | 220 | 160 |
滿載中斷延時平均值(us) | 94.36 | 57.30 | 108.94 | 70.58 |
滿載中斷延時標準值(us) | 17.33 | 9.69 | 18.81 | 11.78 |
可以看到,外部中斷間隔變小之后,響應延時并沒有變得更大,反而由于代碼執行頻率增加而更有可能常駐高速cache從而更快的得到運行,減小響應延時。考慮到我們測試程序的中斷處理線程在收到中斷后處理比較簡單,而實際應用中往往需要執行更為復雜的操作,需要更多的執行時間,一般在50us的水平,這樣從硬件中斷開始,到處理線程完成響應動作,最長需要大約270us時間。
根據以上的測試分析,可以認為ESM335x + RT Linux系統是完全能滿足最小硬件中斷間隔不低于1ms的實時控制應用,這意味著可滿足大部分的實時控制的應用需求。
我們也將會在近期推出ESM6800的RT Linux版本,ESM6800采用iMX6UL Cortex-A7 528MHz CPU,以低功耗、低成本為特色,特別適用于對成本敏感的批量工業智能設備。請關注我們官網以及官方微信公眾號的最新信息。
有興趣的客戶可以直接和我們的工程師進行溝通獲取相關文件進行評估測試。
成都英創信息技術有限公司 028-8618 0660