LINUX使用一個定時器實現設置任意數量定時器

来源:http://www.cnblogs.com/a-lai/archive/2017/08/07/7298463.html
-Advertisement-
Play Games

由於學習LINUX環境高級編程信號章節。課後第5小題要求實現這個功能。所以嘗試實現了一下,並做記錄 ...


本例子參考 Don Libes的Title: Implementing Software Timers例子改寫

 

 

為什麼需要這個功能,因為大多數電腦軟體時鐘系統通常只能有一個時鐘觸發一次中斷。當運行多個任務時,我們會想要多個定時器 的時鐘跟蹤併發這樣可以生成正確的時間重疊,操作系統這樣做。

 

本例子是為了實現使用Linux下的一個定時器,實現任一數量的定時器功能。

首先我們需要一些數據類型用來描述時鐘數據結構

#include <stdio.h>

#include<time.h>

#define TRUE 1
#define FALSE    0

#define MAX_TIMERS    ...    最大時鐘數量
typedef timerval TIME; 定義時間類型
#define VERY_LONG_TIME    ... 最大時間長度

struct timer {
int inuse;    時鐘是否可用
TIME time; 定時時間長度
char *event; 是否超時
} timers[MAX_TIMERS];    /* set of timers */

 

 

每個定時器都以這個數據結構來描述,第一個成員用來描述時鐘是否正在使用,第二個成員是這個定時器的定時時間,第三個成員是是一個指針,*event初始化應該為0,當他被置為1,我們知道這個定時器已經超時了,和他相關的任務可以執行。

 

接下來是定時器數組的初始化,這裡將每個時鐘inuse成員設置為FALSE,表示時鐘不可用。

void
timers_init() {
struct timer *t;

for (t=timers;t<&timers[MAX_TIMERS];t++)
t->inuse = FALSE;
}

 

現在開始是結構實現部分

首先寫到的timer_undeclare這個函數,這個函數與後面的timer_declare相對立。主要作用是清除一個定時器。

有很多方法可以用來保存定時器的定時記錄。沒有複雜時鐘硬體的機器通常在每一個時鐘周期處理一個中斷處理程式。然後軟體就在處理程式中獲取系統時間,然後判斷是否設置的定時器超時。

很多比較聰明的機器可以在硬體中設置定時時間,一旦時間超時,就觸發一個硬體中斷。這同樣適用與軟體中斷。

他們通過一個 定義一個time_now來記錄當前的系統時間,volatile告訴機器每次從寄存器取值,防止數據被系統優化。

volatile TIME time_now

 

接下來定義一系列數據來記錄 timer_next 指接下來要我們想要計時的定時器。time_timer_set保存最後一次獲取的系統時間。

struct timer *timer_next = NULL;/* timer we expect to run down next */
TIME time_timer_set;    /* time when physical timer was set */

//取消一個定時器
void timer_undeclare(struct timer *t)
{
  disable_interrupts();
  if(!t->inuse)
  {
    enable_interrupts();
    return ;
  }
  t->inuse=0;
  if(t==timer_next)
  {
    if(time(&time_now)<0)
    perror("time error");
    timers_update(time_now-time_timer_set);    
    if(timer_next)
    {
      start_physical_timer(&timer_next->time);
      time_timer_set=time_now;
    }
  }
  enable_interrupts();

}

 

timer_undeclare作用為取消一個定時器。首先讓中斷失效,這很重要,因為時鐘數據結構數據是在各進程中共用的,是可以在其他中斷中被修改的,為了防止不必要的錯我,這個取消操作應該為一個原子操作。開始我們先檢測是否這個時鐘已經無效了。如果有效,則設置inuse使其失效。如果我們要取消的定時器正好是下一個期望等待的定時器。那我們要重新指定下一個期望等待的定時器。在此之前所有定時器都要更新一下前一個定時器已經走過的時間。

 

接下來我們看到timers_update(time_t ti)函數

 

//更新定時器表時間
void timers_update(time_t time)
{
  static struct timer timer_last={
  0,
  {},
  NULL
  };
  timer_last.time.tv_sec=10;

 

  struct timer *t;

 

  timer_next=&timer_last;

 

  for(t=timers;t<&timers[MAX_TIMERS];t++)
  {
    if(t->inuse)
    {
      if(time<t->time.tv_sec){
      t->time.tv_sec-=time;
      if(t->time.tv_sec<\
      timer_next->time.tv_sec)
        timer_next=t;
      }else
      {
        *(t->event)=1;
        t->inuse=0;
      }
    }
  }
  if(!timer_next->inuse)timer_next=0;
}

 

 

此函數作用是更新所有有效定時器的時間長,同時將timer_next指向當前延時時間最短的一個定時器。如沒有定時器,則將timer_next設置為空。

timer_declare 加入一個定時器 

struct timer * timer_declare(TIME *ti,char *event)
{
  struct timer *t;
  disable_interrupts();

  for(t=timers;t<&timers[MAX_TIMERS];t++)
  {
    if(!t->inuse)break;
  }

  if(t==&timers[MAX_TIMERS])
  {
    enable_interrupts();
    return 0;
  }

  t->event=event;
  t->time.tv_sec=ti->tv_sec;
  t->time.tv_usec=ti->tv_usec;
  if(!timer_next)
  {

    if(time(&time_now)<0)
    perror("time() error");
    time_timer_set=time_now;
    start_physical_timer(&((timer_next=t)->time));    
  }else if((ti->tv_sec+time_now)<(\
  timer_next->time.tv_sec+time_timer_set))
  {
    if(time(&time_now)<0)
    perror("time error");
    timers_update(time_now-time_timer_set);
    time_timer_set=time_now;
    start_physical_timer(&((timer_next=t)->time));
  }else
  {

  }
  t->inuse=1;
  enable_interrupts();
  return t;
}

 

首先找到一個可用的定時器表項,設置相關參數。

接下來判斷如果timer_next為空,那麼說明定時器表項沒有定時器需要定時,那我們直接將timer_next指向新加入定時器,開始計時。

如果新加入定時器需要延時時間比當前正在延時的定時器的剩餘時間還要短,則更新定時器表,並計時當前加入的定時器。

處理完當前定時器事件後,將新加入的定時器的inuse置1.

 

接下來是定時器中斷處理函數

//定時器中斷處理函數
void timer_interrupt_hander(int signo)
{
  printf("interrupt_hander\n");
  if(time(&time_now)<0)
    perror("time() error");
  timers_update(time_now-time_timer_set);

  if(timer_next)
  {
    time_timer_set=time_now;
    start_physical_timer(&timer_next->time);    
  }
}

 

這裡我們列印一串字元來證明定時器時間的觸發,首先要做的先更新定時器表,然後將time_timer_set設置成當前系統時間,繼續進行下一個定時器事件,直到所有定時器都處理完畢。

 

接下來幾個是LINUX系統相關函數

//失效定時器中斷
void disable_interrupts()
{
sigset_t new_mask;

sigemptyset(&new_mask);
sigaddset(&new_mask,SIGALRM);

if(sigprocmask(SIG_BLOCK,&new_mask,NULL)<0)
perror("SIG_BLOCK error");
}

//使能定時器中斷
void enable_interrupts()
{
sigset_t new_mask;

sigemptyset(&new_mask);
sigaddset(&new_mask,SIGALRM);
if(sigprocmask(SIG_UNBLOCK,&new_mask,NULL)<0)
perror("SIG_UNBLOCK error");
}

//開啟一個定時器工作
void start_physical_timer(TIME* time)
{
if(signal(SIGALRM,timer_interrupt_hander)==SIG_ERR)
perror("signal error");    
struct itimerval new_value;
sigset_t zero_mask;
sigemptyset(&zero_mask);

new_value.it_value.tv_sec=time->tv_sec;
new_value.it_value.tv_usec=time->tv_usec;
new_value.it_interval.tv_sec=0;
new_value.it_interval.tv_usec=0;
setitimer(ITIMER_REAL,&new_value,NULL);
sigsuspend(&zero_mask);
}

 

 

主函數測試部分

#include<stdio.h>
#include<signal.h>
#include"multtime.h"
#include<stdlib.h>
#include<unistd.h>


int main()
{
    pid_t pid;
    TIME time1,time2,time3;
    time1.tv_sec=6;
    time1.tv_usec=0;
    
    time2.tv_sec=4;
    time2.tv_usec=0;

    time3.tv_sec=2;
    time3.tv_usec=0;
    timer_init();
    if((pid=fork())<0)
    {
        perror("fork() error");
    }
    else if(pid==0)
    {
        printf("child 1\n");
        timer_undeclare(timer_declare(&time1,0));
    }
    else 
    {    
        if((pid=fork())<0)
        {
            perror("fork error");
        }
        else if(pid==0)
        {
            printf("child 2\n");
            timer_undeclare(timer_declare(&time3,0));
        }
        else
        {
            printf("parent\n");
            timer_undeclare(timer_declare(&time2,0));
        }
    }
    

    exit(0); 
}

實驗結果:

parent
child 2
child 1
interrupt_hander
interrupt_hander
interrupt_hander

 

本人初學LINUX是個菜鳥,以自己的理解寫的,待深入學習後再完善改進。

 

 

 


您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • rpc.statd程式主要實現NFS鎖相關內容,如普通的文件鎖(NLM、NSM)、文件委托、租約等,但註意,它和sm-notify組合起來才能實現整個NFS鎖機制,具體見下文翻譯文檔中的說明。 以下是NFS相關翻譯篇: 回到系列文章大綱:http://www.cnblogs.com/f-ck-nee ...
  • 以下是NFS相關翻譯篇: 回到系列文章大綱:http://www.cnblogs.com/f-ck-need-u/p/7048359.html 轉載請註明出處:http://www.cnblogs.com/f-ck-need-u/p/7302577.html 註:若您覺得這篇文章還不錯請點擊下右下角 ...
  • LNMP編譯安裝之nginx關聯php--圖文詳解 1、前言 之前已經介紹了nginx,php,mysql的編譯安裝過程,但nginx和php的關聯沒有涉及,導致網頁不能正常使用php功能,所有本編介紹如何將nginx和php進行關聯,使*.php文件可以正常在瀏覽器訪問。 2、準備步驟 2.1、修 ...
  • HAproxy部署配置 拓撲圖 說明: 一 HAProxy主機配置 1 global部分 用來設定全局配置參數,屬於進程級的配置,通常和操作系統配置有關。 2 default部分 預設參數的配置部分。在次部分配置的參數值,預設會自動引用到下麵frontend、backend、listen部分中,因此 ...
  • 文件系統 格式化 使用fdisk命令創建分區後,並不能直接使用,必須先格式化。 在Linux中有兩種方式格式化,分別是命令mke2fs和mkfs 實踐:對磁碟sdb新創建的三個分區執行格式化操作。 查看分區信息 實踐: 日誌 總結:一般都會使用帶日誌的文件系統,比如windows中的NTFS,Lin ...
  • 需要註意的是,掛載點必須是一個已經存在的目錄,這個目錄可以不為空,但掛載後這個目錄下以前的內容將不可用,umount以後會恢復正常。使用多個-o參數的時候,-o 只用一次,參數之間用半形逗號隔開: ...
  • 1.1 What is Kerberos 1.1.1 簡單介紹 Kerberos是一個用於鑒定身份(authentication)的協議, 它採取對稱密鑰加密(symmetric-key cryptography),這意味著密鑰不會在網路上傳輸。在Kerberos中,未加密的密碼(unencrypt ...
  • 認識fdisk 如下圖。 使用fdisk工具對磁碟進行分區 如下圖: 實踐:使用fdisk對新添加的一塊磁碟進行分區,一個主分區,一個擴展分區和兩個邏輯分區。 1,切換到root用戶。因為只有root用戶才有許可權進行分區。 2,使用fdisk -l命令查看目前所有的磁碟及分區情況。 3,使用fdis ...
一周排行
    -Advertisement-
    Play Games
  • 移動開發(一):使用.NET MAUI開發第一個安卓APP 對於工作多年的C#程式員來說,近來想嘗試開發一款安卓APP,考慮了很久最終選擇使用.NET MAUI這個微軟官方的框架來嘗試體驗開發安卓APP,畢竟是使用Visual Studio開發工具,使用起來也比較的順手,結合微軟官方的教程進行了安卓 ...
  • 前言 QuestPDF 是一個開源 .NET 庫,用於生成 PDF 文檔。使用了C# Fluent API方式可簡化開發、減少錯誤並提高工作效率。利用它可以輕鬆生成 PDF 報告、發票、導出文件等。 項目介紹 QuestPDF 是一個革命性的開源 .NET 庫,它徹底改變了我們生成 PDF 文檔的方 ...
  • 項目地址 項目後端地址: https://github.com/ZyPLJ/ZYTteeHole 項目前端頁面地址: ZyPLJ/TreeHoleVue (github.com) https://github.com/ZyPLJ/TreeHoleVue 目前項目測試訪問地址: http://tree ...
  • 話不多說,直接開乾 一.下載 1.官方鏈接下載: https://www.microsoft.com/zh-cn/sql-server/sql-server-downloads 2.在下載目錄中找到下麵這個小的安裝包 SQL2022-SSEI-Dev.exe,運行開始下載SQL server; 二. ...
  • 前言 隨著物聯網(IoT)技術的迅猛發展,MQTT(消息隊列遙測傳輸)協議憑藉其輕量級和高效性,已成為眾多物聯網應用的首選通信標準。 MQTTnet 作為一個高性能的 .NET 開源庫,為 .NET 平臺上的 MQTT 客戶端與伺服器開發提供了強大的支持。 本文將全面介紹 MQTTnet 的核心功能 ...
  • Serilog支持多種接收器用於日誌存儲,增強器用於添加屬性,LogContext管理動態屬性,支持多種輸出格式包括純文本、JSON及ExpressionTemplate。還提供了自定義格式化選項,適用於不同需求。 ...
  • 目錄簡介獲取 HTML 文檔解析 HTML 文檔測試參考文章 簡介 動態內容網站使用 JavaScript 腳本動態檢索和渲染數據,爬取信息時需要模擬瀏覽器行為,否則獲取到的源碼基本是空的。 本文使用的爬取步驟如下: 使用 Selenium 獲取渲染後的 HTML 文檔 使用 HtmlAgility ...
  • 1.前言 什麼是熱更新 游戲或者軟體更新時,無需重新下載客戶端進行安裝,而是在應用程式啟動的情況下,在內部進行資源或者代碼更新 Unity目前常用熱更新解決方案 HybridCLR,Xlua,ILRuntime等 Unity目前常用資源管理解決方案 AssetBundles,Addressable, ...
  • 本文章主要是在C# ASP.NET Core Web API框架實現向手機發送驗證碼簡訊功能。這裡我選擇是一個互億無線簡訊驗證碼平臺,其實像阿裡雲,騰訊雲上面也可以。 首先我們先去 互億無線 https://www.ihuyi.com/api/sms.html 去註冊一個賬號 註冊完成賬號後,它會送 ...
  • 通過以下方式可以高效,並保證數據同步的可靠性 1.API設計 使用RESTful設計,確保API端點明確,並使用適當的HTTP方法(如POST用於創建,PUT用於更新)。 設計清晰的請求和響應模型,以確保客戶端能夠理解預期格式。 2.數據驗證 在伺服器端進行嚴格的數據驗證,確保接收到的數據符合預期格 ...