1. 程式人生 > >linux定時中斷的三種實現

linux定時中斷的三種實現

前言

本文主要講述linux應用層三種定時中斷實現的方法。我們可以利用定時中斷在linux應用層實現一些對時間頻率要求不是很高的驅動,雖然有些不規範,但是也是有其適用的場合的。因為應用層不涉及到硬體,不同平臺可移植性更高。
本文涉及到的內容有:

  1. 多執行緒間訊號的處理
  2. 三種定時中斷的實現
  3. 測試結果與討論

一、多執行緒間訊號的處理

我們先下兩個結論,並在後面的測試程式中給出證明。

  1. 結論一:預設情況下,訊號將由主程序接收處理,就算訊號處理函式是由子執行緒註冊的。
  2. 結論二:對訊號的處理是程序中所有的執行緒共享的。

為什麼要先討論執行緒間的訊號的處理呢?這和定時中斷的實現有何關係?
放在前面討論是因為之前踩了一個坑,先把坑分享出來,以免有人掉到坑裡。
在專案開發中,對步進電機的控制,一開始我使用的是linux setitimer函式,目的是為了定時給出脈衝到步進電機,實現在應用層控制馬達的轉動。事實證明,是能夠在應用層實現的。但是,後來的測試中,其他功能報了一個錯誤,檢視log發現是sleep函式不生效。檢視相關資料才發現,他們之前都使用了同種訊號,導致了sleep函式接收了setitimer函式發出的訊號。是有想過用select函式實現自己的sleep函式,這樣可以解決問題,而且select函式比sleep函式更加準確。但是,專案搜尋出來的sleep函式實在太多了,每個都修改實在煩。所以,只能另尋他路。果然,linux還能用POSIX中內建的定時器,使用函式timer_create 和函式timer_settime可以解決上面問題。

二、定時中斷的三種實現

三種定時中斷實現:

  1. setitimer 函式產生SIGALRM訊號,同時使用signal函式註冊
  2. 使用timer_create 和 timer_settime函式可以指定任意產生任意訊號,同時使用signal函式註冊
  3. 使用timer_create 和 timer_settime函式 通知執行緒

程式實現的程式碼如下:

//timer_test.c

#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <signal.h>
#include <time.h> #include <sys/time.h> #include <string.h> // return: 自CPU上電以來的時間,單位:毫秒 unsigned long long GetCpuRunTimeMs(void) { unsigned long long uptime = 0; struct timespec on; if(clock_gettime(CLOCK_MONOTONIC, &on) == 0) { uptime = on.tv_sec * 1000ul; uptime += on.tv_nsec / 1000000u
l; } return uptime; } //訊號處理函式 void signal_func(int signo) { static unsigned long long start_time = 0; switch(signo) { case SIGALRM: printf("%s:1s-----> take %llu ms,signo=SIGALRM(%d)\n",__FUNCTION__,GetCpuRunTimeMs()-start_time,signo); break; case SIGUSR1: printf("%s:1s-----> take %llu ms,signo=SIGUSR1(%d)\n",__FUNCTION__,GetCpuRunTimeMs()-start_time,signo); break; default: break; } start_time = GetCpuRunTimeMs(); } //setitimer函式相關初始化 void vTimerInit(void) { struct itimerval value, old_value; signal(SIGALRM, signal_func); value.it_value.tv_sec = 1; //設定距離首次中斷的時間 value.it_value.tv_usec = 0; value.it_interval.tv_sec = 1; //時間間隔 value.it_interval.tv_usec = 0; setitimer(ITIMER_REAL, &value, &old_value); } //timer_create初始化相關,註冊訊號SIGUSR1 int vTimerCreateInit(void) { struct sigevent evp; struct itimerspec ts; timer_t timer; int ret; memset(&evp, 0, sizeof(struct sigevent)); //清零初始化 evp.sigev_value.sival_ptr = &timer; evp.sigev_notify = SIGEV_SIGNAL; evp.sigev_signo = SIGUSR1; signal(SIGUSR1, signal_func); ret = timer_create(CLOCK_REALTIME, &evp, &timer); if( ret ) perror("timer_create"); ts.it_interval.tv_sec = 1; ts.it_interval.tv_nsec = 0; ts.it_value.tv_sec = 1; ts.it_value.tv_nsec = 0; ret = timer_settime(timer, 0, &ts, NULL); if( ret ) perror("timer_settime"); return ret; } void timer_thread(union sigval v) { static unsigned long long start_time = 0; printf("%s:1s-----> take %llu ms, v.sival_int = %d\n",__FUNCTION__,GetCpuRunTimeMs()-start_time,v.sival_int); start_time = GetCpuRunTimeMs(); } //不採用訊號通知的方式,直接通知執行緒 int vTimerPthreadInit(void) { timer_t timerid; struct sigevent evp; int ret; memset(&evp, 0, sizeof(struct sigevent)); //清零初始化 evp.sigev_value.sival_int = 1111; evp.sigev_notify = SIGEV_THREAD; //執行緒通知的方式,派駐新執行緒 evp.sigev_notify_function = timer_thread; //執行緒函式地址 ret = timer_create(CLOCK_REALTIME, &evp, &timerid); if( ret ) perror("timer_create"); struct itimerspec it; it.it_interval.tv_sec = 1; //間隔1s it.it_interval.tv_nsec = 0; it.it_value.tv_sec = 1; it.it_value.tv_nsec = 0; ret = timer_settime(timerid, 0, &it, NULL); if( ret ) perror("timer_settime"); return ret; } //執行緒,用於測試訊號接收 void *vTimerProc(void *arg) { printf("pthread func:%s\n",__FUNCTION__); unsigned long long start_time = 0; while(1) { start_time = GetCpuRunTimeMs(); sleep(5); printf("%s:sleep 5s take %llu ms\n",__FUNCTION__,GetCpuRunTimeMs()-start_time); } return NULL; } //執行緒,用於測試訊號接收 void *vSleepProc(void *arg) { printf("pthread func:%s\n",__FUNCTION__); unsigned long long start_time = 0; while(1) { start_time = GetCpuRunTimeMs(); sleep(5); printf("%s:sleep 5s take %llu ms\n",__FUNCTION__,GetCpuRunTimeMs()-start_time); } return NULL; } //執行緒初始化函式 void vPthreadInit(void) { printf("func:%s\n",__FUNCTION__); pthread_t timer_thread_id = 0; pthread_create(&timer_thread_id, NULL, vTimerProc, NULL); pthread_t sleep_thread_id = 0; pthread_create(&sleep_thread_id, NULL, vSleepProc, NULL); } int main(int argc, char *argv[]) { if (argc != 2) { printf("please input eg: %s 1|2|3\n",argv[0]); printf("1:user SIGALRM\n"); printf("2:user SIGUSR1\n"); printf("3:user thread\n"); return -1; } printf("func:%s ,process id is %d\n",__FUNCTION__,getpid()); struct itimerval value, old_value; unsigned long long start_time = 0; unsigned long long end_time = 0; int ctrl = atoi(argv[1]); switch(ctrl) { case 1: vTimerInit(); break; case 2: vTimerCreateInit(); break; case 3: vTimerPthreadInit(); break; default: return -1; break; } vPthreadInit(); while(1) { start_time = GetCpuRunTimeMs(); sleep(5); printf("%s:sleep 5s take %llu ms\n",__FUNCTION__,GetCpuRunTimeMs()-start_time); } }

以上程式 ,並沒有所有函式都有做判斷,如果是寫到專案中,是需要加多錯誤判斷和處理的。
以下三個函式包含了定時中斷三種方式。

int vTimerPthreadInit(void)//不採用訊號通知的方式,直接通知執行緒
int vTimerCreateInit(void)//timer_create初始化相關,註冊訊號SIGUSR1
int vTimerPthreadInit(void)//不採用訊號通知的方式,直接通知執行緒

在測試程式中使用引數1,2,3區分呼叫。在main函式中處理如下:

int ctrl = atoi(argv[1]);
switch(ctrl)
{
    case 1:
        vTimerInit();
        break;
    case 2:
        vTimerCreateInit();
        break;
    case 3:
        vTimerPthreadInit();
        break;
    default:
        return -1;
        break;
}

三、測試結果與討論

接下來對程式程序測試。
編譯,需要加多兩個庫:-lpthread -lrt

ubuntu@ubuntu:~/test/timer_create$ gcc timer_test.c -o timer_test -lpthread -lrt

先執行測試一下,發現測試程式已經有提示了,按照提示來測試。

ubuntu@ubuntu:~/test/timer_create$ ./timer_test
please input eg: ./timer_test 1|2|3
1:user SIGALRM
2:user SIGUSR1
3:user thread

首先,測試setitimer函式,它在程式中會產生SIGALRM訊號

[email protected]:~/test/timer_create$ ./timer_test 1
func:main ,process id is 7186
func:vPthreadInit
pthread func:vSleepProc
pthread func:vTimerProc
signal_func:1s-----> take 535951046 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
vSleepProc:sleep 5s take 5001 ms
vTimerProc:sleep 5s take 5000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGALRM(14)
main:sleep 5s take 1000 ms
^C

從以上的測試結果看出,在main函式使用sleep 5秒,但是列印main:sleep 5s take 1000 ms,也就是睡眠5秒,結果1秒就被打斷。這是因為執行緒發出的SIGALRM訊號被sleep函式接收了。如果想知道具體是什麼終止了sleep函式,可以去看看它的實現。在測試程式中,我是把註冊訊號的函式放在了主執行緒。其實在測試的時候,我有把初始化訊號函式vTimerInit放線上程vTimerProc中測試,結果也是一樣的。這就可以證明結論二。而子執行緒vSleepProc和子執行緒vTimerProc中呼叫sleep函式並沒有被終止就可以證明結論一。

  1. 結論一:預設情況下,訊號將由主程序接收處理,就算訊號處理函式是由子執行緒註冊的。
  2. 結論二:對訊號的處理是程序中所有的執行緒共享的。

接著再測試timer_create函式,註冊訊號是SIGUSR1。

[email protected]:~/test/timer_create$ ./timer_test 2
func:main ,process id is 7189
func:vPthreadInit
pthread func:vSleepProc
pthread func:vTimerProc
signal_func:1s-----> take 535963773 ms,signo=SIGUSR1(10)
main:sleep 5s take 1001 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 999 ms,signo=SIGUSR1(10)
main:sleep 5s take 999 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
vSleepProc:sleep 5s take 5000 ms
vTimerProc:sleep 5s take 5000 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
signal_func:1s-----> take 1000 ms,signo=SIGUSR1(10)
main:sleep 5s take 1000 ms
^C

結果竟然和上一個結果是一樣的。說明使用訊號SIGUSR1還是會中斷main函式的sleep函式。目前來說,我並不清楚為什麼會影響到。
最後,測試不採用訊號通知的方式,直接通知執行緒的方式:

[email protected]:~/test/timer_create$ ./timer_test 3
func:main ,process id is 7192
func:vPthreadInit
pthread func:vSleepProc
pthread func:vTimerProc
timer_thread:1s-----> take 535974740 ms, v.sival_int = 1111
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
main:sleep 5s take 5001 ms
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
vSleepProc:sleep 5s take 5001 ms
vTimerProc:sleep 5s take 5001 ms
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
timer_thread:1s-----> take 1000 ms, v.sival_int = 1111
timer_thread:1s-----> take 999 ms, v.sival_int = 1111
main:sleep 5s take 5000 ms
timer_thread:1s-----> take 1001 ms, v.sival_int = 1111
vSleepProc:sleep 5s take 5000 ms
vTimerProc:sleep 5s take 5000 ms
timer_thread:1s-----> take 999 ms, v.sival_int = 1111
timer_thread:1s-----> take 1001 ms, v.sival_int = 1111
^C

從以上結果看,使用這種方式,並沒有使sleep函式終止,無論是主執行緒還是兩個子執行緒,說明在應用層使用這種方式是可行的。

最後的最後,由於專案中的嵌入式linux系統中的時鐘被設定為10ms,所以在應用層只能產生最高100hz的脈衝,這對步進電機來說還是有點慢,所以,沒辦法還是得用硬體定時中斷,實現linux裝置驅動。不過,對於I2C這種時鐘要求不高的情況,或許可以一試。還有,提醒一點:usleep函式在應用層中是相當不精準的,select函式實現延時效果較好,但還是誤差挺大的,所以才需要軟體定時器。

後續

linux一些系統呼叫是跟訊號緊密相關的,如system函式是需要捕獲SIGCHLD 訊號的,如果程式為了避免產生殭屍程序,而把SIGCHLD 訊號設定為忽略,那system呼叫就會受到影響。下次再討論system相關的技術。

相關推薦

linux定時中斷實現

前言 本文主要講述linux應用層三種定時中斷實現的方法。我們可以利用定時中斷在linux應用層實現一些對時間頻率要求不是很高的驅動,雖然有些不規範,但是也是有其適用的場合的。因為應用層不涉及到硬體,不同平臺可移植性更高。 本文涉及到的內容有: 多執行緒

LINUX環境並發服務器的實現模型

服務 sset 成了 nec 使用 ndt 系統調用 accept listen 服務器設計技術有很多,按使用的協議來分有TCP服務器和UDP服務器。按處理方式來分有循環服務器和並發服務器。 1 循環服務器與並發服務器模型 在網絡程序裏面,一般來說都是許多客戶對應一個服務

java的實現定時任務的方法

/**   * 普通thread   * 這是最常見的,建立一個thread,然後讓它在while迴圈裡一直執行著,   * 通過sleep方法來達到定時任務的效果。這

Thread實現&多執行緒操作同一物件的互斥同步以及多物件的同步&定時器Timer

多執行緒 程序 程序:(Process)是計算機中的程式關於某資料集合上的一次執行活動,是系統進行資源分配和排程的基本單位,是作業系統結構的基礎。在程序是程式的基本執行實體,在當代面向執行緒設計的計算機結構中,程序是執行緒的容器,程是程式的實體。 多執行緒:是指從

Linux 雙線策略路由的實現方式總結+埠對映

網路環境 伺服器(閘道器):    eth0 為LAN口,IP為 LAN_IP = 192.168.0.1    eth1 為第一個WAN口,接電信線路,IP為 CTC_IP,閘道器為 CTC_GW    eth2 為第二個WAN口,接網通線路,IP為 CNC_IP,閘道器為 CNC_GW 內網網站   

Linux網路程式設計》: 併發伺服器的實現模型

迴圈伺服器與併發伺服器模型 伺服器設計技術有很多,按使用的協議來分有 TCP 伺服器和 UDP 伺服器,按處理方式來分有迴圈伺服器和併發伺服器。 在網路程式裡面,一般來說都是許多客戶對應一個伺服器(多對一),為了處理客戶的請求,對服務端的程式就提出了特殊的要求。 目前最

LINUX環境併發伺服器的實現模型

伺服器設計技術有很多,按使用的協議來分有TCP伺服器和UDP伺服器。按處理方式來分有迴圈伺服器和併發伺服器。 1  迴圈伺服器與併發伺服器模型 在網路程式裡面,一般來說都是許多客戶對應一個伺服器,為了處理客戶的請求,對服務端的程式就提出了特殊的要求。 目前最常用的伺服器模型

單例模式的實現 以及各自的優缺點

屏蔽 () 模式 避免 sync code 實例化 pan single 單例模式:單例模式的意思就是只有一個實例。單例模式確保某一個類只有一個實例,而且自行實例化並向整個系統提供這個實例。這個類稱為單例類。 單例模式有三種:懶漢式單例,餓漢式單例,登記式單例。 1.

Java 多線程 實現方式

() 例子 屬於 周期性 core www object 並且 check Java多線程實現方式主要有三種:繼承Thread類、實現Runnable接口、使用ExecutorService、Callable、Future實現有返回結果的多線程。其中前兩種方式線程執行完後都

mysql讀寫分離的實現方式

不能 span bsp 缺點 解決方案 使用 隨機 mas 均衡   1 程序修改mysql操作類可以參考PHP實現的Mysql讀寫分離,阿權開始的本項目,以php程序解決此需求。優點:直接和數據庫通信,簡單快捷的讀寫分離和隨機的方式實現的負載均衡,權限獨立分配缺點:自己維

生產者消費者問題Java實現

read 可執行 tran extend 模式 fit ner consumer 傳輸數據 生產者-消費者Java實現 2017-07-27 1 概述 生產者消費者問題是多線程的一個經典問題,它描述是有一塊緩沖區作為倉庫,生產者可以將產品放入倉庫,消費者則可

沈浸式狀態欄的實現方式

stemwin barh webp trac war nba schema 布局文件 adding 沈浸式算是目前Android行業比較流行的一種App設計風格,將菜單欄北京設置為導航欄的顏色,感覺頂部狀態欄像是被入侵了一樣,因此稱為沈浸式菜單欄。本文將介紹三種方式去實現沈

C#使用DataSet Datatable更新數據庫的實現方法

從數據 數據庫 設計 dddddd 操作註冊表 同時 包含 一個 自動 本文以實例形式講述了使用DataSet Datatable更新數據庫的三種實現方法,包括CommandBuilder 方法、DataAdapter 更新數據源以及使用sql語句更新。分享給大家供大家參

交換函數swap的實現方法

tools view art pbo class -m tails 鏈接 clip http://blog.csdn.net/GarfieldEr007/article/details/48314295 本文采用三種方式實現兩個數之間的交換,分別是①借助輔助變量temp的s

mybatis之接口方法多參數的實現方式

自動 spa commit col pri true keys use 數據 關鍵代碼舉例: DaoMapper.xml 1 <!-- 傳入多個參數時,自動轉換為map形式 --> 2 <insert id="insertByCol

生產者消費者模式的實現方式

ring product while ide bsp turn this trac exce synchronized版本public class Test { public static void main(String[] args) { Shared s =

C# 客戶端程序調用外部程序的實現

type HA RM num box system hwnd 支持 inter 簡介 我們用C#來開發客戶端程序的時候,總會不可避免的需要調用外部程序或者訪問網站,本篇博客介紹了三種調用外部應用的方法,供參考 實現 第一種是利用shel

學習 IOC 設計模式前必讀:依賴註入的實現

重要 來看 內部 mysql 再看 truct 告訴 並不是 決定 學無止境,精益求精 十年河東十年河西,莫欺少年窮 呵呵,此篇博客轉載自:http://www.cnblogs.com/liuhaorain/p/3747470.html 摘要 面向對象設計(OOD)有助於我

linux基礎學習-9.7-linux中的時間戳

練習題 href node節點 lena HA content name 解釋 locks 1、三種時間對應關系表 column column column 訪問時間 Access atime 修改時間 Modify mtime 狀態改動時間 Change ctime 2、

單例的實現方式

In imp SQ span wrap 定制 bsp () import 一:類方法實現單例 class Mysql: __instance = None def __init__(self,host,port): self.ho