1. 程式人生 > >Linux核心系統定時器TIMER實現過程分析

Linux核心系統定時器TIMER實現過程分析

Linux系統定時器,在核心中扮演著重要角色。核心的許多重要實現如任務排程,工作佇列等均以系統定時器關係密切。系統定時器能以可程式設計的頻率中斷處理,這一中斷叫做軟中斷。此頻率即為每秒的定時器節拍數HZ。HZ的越大,說明定時器節拍越小,執行緒排程的準確性會越高。但HZ設得過大,對一個系統來說並不好,會導CPU開銷過大,反而造成任務排程效率降低。滴答jiffies 變數記錄系統啟動以來,系統定時器已經觸發的次數。也就是說每過一秒jiffies的增量為HZ,一般HZ=100,HZ是可以配置的,在S3C2440 arm linux中配置為200.

下面基於Linux2.6.30.4原始碼來探討其實現原理及其過程。

要理解系統定時器實現原理,先來看看關係到系統定時器的各種資料結構,其具體的描述引數。

結構體structtimer_list來描述timer的引數,其資料結構如下:

struct timer_list {
       structlist_head entry;              //timer雙向連結串列
       unsignedlong expires;             //timer超時變數
 
       void(*function)(unsigned long);   //timer超時回撥函式
       unsignedlong data;                  //傳遞給回撥函式的資料,也就是定時器資料
      struct tvec_base *base;            //timer base向量表用於timer_list的掛載和連結串列管理
                                              //timer的一些擴充套件引數
#ifdef CONFIG_TIMER_STATS       
       void*start_site;
       charstart_comm[16];
       intstart_pid;
#endif
#ifdef CONFIG_LOCKDEP
       structlockdep_map lockdep_map;
#endif
};

其中:

list_entry結構:
struct list_head {
       structlist_head *next, *prev;
};
tevc_base的結構:
struct tvec_base {
       spinlock_tlock;                         //自旋鎖lock
       structtimer_list *running_timer;   //指向已經掛載進來的timer_list
       unsignedlong timer_jiffies;          //timer jiffies用於記錄定時器當前jiffies
       structtvec_root tv1;                  //5組tvec_base,從tv1~tv5,成員數各不相同
       structtvec tv2;                        //其成員數TVR_SIZE,TVN_SIZE決定
       structtvec tv3;
       structtvec tv4;
       structtvec tv5;
} ____cacheline_aligned;

#define TVN_BITS (CONFIG_BASE_SMALL ? 4 :6)
#define TVR_BITS (CONFIG_BASE_SMALL ? 6 :8)
#define TVN_SIZE (1 << TVN_BITS)
#define TVR_SIZE (1 << TVR_BITS)
#define TVN_MASK (TVN_SIZE - 1)
#define TVR_MASK (TVR_SIZE - 1)
 
struct tvec {
       structlist_head vec[TVN_SIZE];   // tv2~t5個數為64的陣列
};                            
 
struct tvec_root {
       structlist_head vec[TVR_SIZE];  //tv1個數為256的陣列
};

可見涉及到系統定時器的資料結構並不多,那麼:對於一個linux系統中,定時器個數可能會很多,而且每個定時器的超時事件時間並不相同,所以如何管理和處理定時器超時事件,關係到核心效能的高低。它根據不同的定時事件,按時間間分組,將新增的timer定時器建成雙向連結串列,然後按照一定方式存放於5組tv1~tv5變數中稱為tec_base。對於對稱式多理器(SMP)系統還考慮到了TIMER從一個CPU遷移到另一個CPU的情況,相應的tev_base也跟著更改。那它在系統是怎樣實現的呢?現在先從一個簡單的系統定時器應用例子來看看它的實現過程:

#include <linux/init.h>
#include <linux/module.h>
#include <linux/timer.h>
 
struct  timer_list   my_timer;
 
static void my_function(unsigned   long  data)
{
          static int i = 0;
           printk( "timer’s callback function\n");
           printk( "timer’s data = %lu\n",data);
           return;
}
 
static int my_timer_init(void)
{
              printk(“timerinit…\n”);
           my_timer.data = 0xff;
           my_timer.function = my_function;
           my_timer.expires = jiffies + 3*HZ;
           init_timer(&my_timer);
           add_timer(&my_timer);
           return  0;
}
 
static void my_timer_exit(void)
{
              printk( "timer exit…\n");
}
 
module_init(my_timer_init);
module_exit(my_timer_exit);
MODULE_AUTHOR( "itspy");
MODULE_LICENSE( "GPL");
MODULE_DESCRIPTION("linux kernel timerprogramming");

上面例子,實現了一個定時器事件,將在3 HZ(秒)發生。my_imer_init函式中呼叫到的定時器API只有:

init_timer(&my_timer);   //用於定時器初始化

add_timer(&my_timer);    //增加一個新的定時器到tev_base向量表中

其中init_timer中呼叫了__init_timer(),這個函式才是真正初始化定時器的:

static void __init_timer(struct timer_list*timer,
                      const char *name,
                      struct lock_class_key *key)
{
       timer->entry.next= NULL;                           //對於新增的timer例項,其下一各總是指向NULL。
       timer->base= __raw_get_cpu_var(tvec_bases); //SMP中,獲得當前處理器的tev_base
                                                                   //這個tev_bases是根據一定規律變化的,稍後會將到
    …
}

新增的定時器初始化,就是完成了一個timer_list結構初始化過程。

add_timer()  --> mod_timer()  -->  __mod_timer()

其中:
static inline int
__mod_timer(struct timer_list *timer,unsigned long expires, bool pending_only)
{
       structtvec_base *base, *new_base;
       unsignedlong flags;
       intret;
 
       ret= 0;
       BUG_ON(!timer->function);                 // BUG檢測,確保回撥函式為非空NULL
       base= lock_timer_base(timer, &flags); //獲取本地cpu的tev_base,這是一個臨
                                                          //界資源,裡邊是一個for(;;)迴圈,如果找不到說明已經遷移到了別的CPU
       if (timer_pending(timer)) {                  //當已掛載的timer 定時超時發生後,會被解除安裝摘除
              detach_timer(timer,0);           
              ret= 1;
       }else {
              if(pending_only)                        //新增一個定時器時,pending_only 為 false
                     gotoout_unlock;
       }
 
…
       new_base= __get_cpu_var(tvec_bases);   //獲取本地cpu中的tevc_bases
       if(base != new_base) {     //由於之前base 可能已被遷移到其他CPU的 tev_base向量表,會造成 base != new_base
              if(likely(base->running_timer != timer)) { //由於在timer正在執行時,我們不能直接更改base,位與一個叫做DEFERRABLE(可延後標誌)後處理
                     /*See the comment in lock_timer_base() */    
                     timer_set_base(timer,NULL);
                     spin_unlock(&base->lock);
                     base= new_base;
                     spin_lock(&base->lock);
                     timer_set_base(timer,base);
              }
       }
 
       timer->expires= expires;                
       internal_add_timer(base,timer);       //分析timer expires及建表過程
 
out_unlock:
       spin_unlock_irqrestore(&base->lock,flags);
 
       return ret;
}

對於新增的timer最後呼叫internal_add_timer(base, timer)加入到相應的timer_list中以完成初始化過程。,這是一個建表的過程,表的建立情況,關係到表的管理效率。之前我們說到它共有tv1~tv5 組,tv1是一個很特別的組。每個tv中有各個組員,每個timer是如何新增的呢,看看internal_add_timer()的實現過程:

static void internal_add_timer(structtvec_base *base, struct timer_list *timer)
{
       unsignedlong expires = timer->expires;
       unsignedlong idx = expires - base->timer_jiffies;
       structlist_head *vec;
 
       if(idx < TVR_SIZE) {
              inti = expires & TVR_MASK;
              vec= base->tv1.vec + i;
       }else if (idx < 1 << (TVR_BITS + TVN_BITS)) {
              inti = (expires >> TVR_BITS) & TVN_MASK;
              vec= base->tv2.vec + i;
       }else if (idx < 1 << (TVR_BITS + 2 * TVN_BITS)) {
              inti = (expires >> (TVR_BITS + TVN_BITS)) & TVN_MASK;
              vec= base->tv3.vec + i;
       }else if (idx < 1 << (TVR_BITS + 3 * TVN_BITS)) {
              inti = (expires >> (TVR_BITS + 2 * TVN_BITS)) & TVN_MASK;
              vec= base->tv4.vec + i;
       }else if ((signed long) idx < 0) {
              /*
               * Can happen if you add a timer with expires== jiffies,
               * or you set a timer to go off in the past
               */
              vec= base->tv1.vec + (base->timer_jiffies & TVR_MASK);
       }else {
              inti;
              /*If the timeout is larger than 0xffffffff on 64-bit
               * architectures then we use the maximumtimeout:
               */
              if(idx > 0xffffffffUL) {
                     idx= 0xffffffffUL;
                     expires= idx + base->timer_jiffies;
              }
              i= (expires >> (TVR_BITS + 3 * TVN_BITS)) & TVN_MASK;
              vec= base->tv5.vec + i;
       }
       /*
        * Timers are FIFO:
        */
       list_add_tail(&timer->entry,vec);
}

通過程式碼我們知道它的過程是這樣的:首先它根據每個timer中的超時差值idx來決定timer所處的tev_base組別tv1~tv5.所以超時事件越後發生,那麼它所處的組位置越靠後。對於tv1組,超時插值idx為0~255之間。差值idx即為所屬組tv1中的陣列下標。從中可知tv1組中相鄰定時器的超時事件間隔1 jiffies發生。對於tv2組,超時差值idx為 256~2^14(16386)  之間,組中相鄰定時時器超事件時間隔256^1 = 256 jiffies發生。以此類推,tv3 組超時差值idx為(16387~2^20)之間,組中相鄰定時器超時時間間隔256^2 = 65536 jiffies發生 … 最後,新增的timer加入到當前節點(超時差值相等)的尾部list_add_tail()形成一個雙向連結串列。這樣分組timer雙向連結串列方便了後面對定時器的遷移更新管理過程,以及最終提高了CPU的處理效率,因為在__run_timers()時,我們只需掃描tv1組中即將到來的定時器事件就行了。

我們知道啟動過程時start_kernel()對定時器的初始化是這樣的 :

init_timers() -> run_timer_softirq()  -> __run_timers()…

timer_interrupt() -> update_process_times() ->run_local_timers() -> raise_softirq(TIMER_SOFTIRQ);

之前我寫的一篇《核心窺祕之一:start_kernel()執行過程記錄》也有提到過.

       __run_timers()是系統定時器超時事件的服務程式。這是run_timer_softirq()中一部分,是通過軟中斷的實現的,它是在軟中斷下半部處理的。

static inline void __run_timers(structtvec_base *base)
{
       …
       while(time_after_eq(jiffies, base->timer_jiffies)) {      //確定當前tvec_base->timer_jiffies是否有效
              structlist_head work_list;
              structlist_head *head = &work_list;
              intindex = base->timer_jiffies & TVR_MASK;               //只需掃描tv1組,看當前jiffies時刻是否有超時發生
              if(!index &&                                                         //cascade()定時器佇列級聯函式實現了
                     (!cascade(base,&base->tv2, INDEX(0))) &&         //tv5~tv2組遷移過程
                            (!cascade(base,&base->tv3, INDEX(1))) &&  
                                   !cascade(base,&base->tv4, INDEX(2)))
                     cascade(base,&base->tv5, INDEX(3));
              ++base->timer_jiffies;                                            //更新當前tvec_base->timer_jiffies
              list_replace_init(base->tv1.vec+ index, &work_list);        //連結串列更新新、舊取代
              while(!list_empty(head)) { //判定是否有定時器超時事件發生,非空為有,知道處理完連結串列中所有相同的定時器事件為止
                     void(*fn)(unsigned long);
                     unsignedlong data;
 
                     timer= list_first_entry(head, struct timer_list,entry);  // 這是一個巨集,獲取第一個實體(對應的是入口的下一個)的地址
                     fn= timer->function;                                    
                     data= timer->data;            
           …
                     fn(data);                                                        //timer超時時回撥函式入口
           …
        }
       }
     …
}

對於cascade()函式它是確保之前定時器建立時internal_add_timer()定時器佇列以及佇列租得遷移更新工作,為什麼要遷移,因為,系統在處理定時器時,比較的只是tv1組而已,也就是說,原來的tv1執行完之後,那麼剩下的tv2,tv3,tv4,tv5將會先後遷移到tv1組:tv5 -> tv4 -> tv3 -> tv2-> tv1,這樣定時器超時事件服務程式並不需要對每組的tv的超時事件進行檢測,相比而言,也就提高了CPU的處理效率。那麼這樣一來timer 連結串列將發生變化,所以需要重新計算,重新實現internal_add_timer(),所以cascade ()函式程式碼如下:

tatic int cascade(struct tvec_base *base,struct tvec *tv, int index)
{
       /*cascade all the timers from tv up one level */
       structtimer_list *timer, *tmp;
       structlist_head tv_list;
 
       list_replace_init(tv->vec+ index, &tv_list);                   //
       list_for_each_entry_safe(timer,tmp, &tv_list, entry) {
              BUG_ON(tbase_get_base(timer->base)!= base);    //確保本地cpu 的tvec_base沒
              internal_add_timer(base,timer);                         //有發生改變
       }
 
       return index;
}

通過以上分析,我們對Linux中系統定時器TIMER實現過程有所瞭解了。

相關推薦

Linux核心系統定時TIMER實現過程分析

Linux系統定時器,在核心中扮演著重要角色。核心的許多重要實現如任務排程,工作佇列等均以系統定時器關係密切。系統定時器能以可程式設計的頻率中斷處理,這一中斷叫做軟中斷。此頻率即為每秒的定時器節拍數HZ。HZ的越大,說明定時器節拍越小,執行緒排程的準確性會越高。但HZ設得

Linux C/C++定時實現原理和使用方法

定時器的實現原理 定時器的實現依賴的是CPU時鐘中斷,時鐘中斷的精度就決定定時器精度的極限。一個時鐘中斷源如何實現多個定時器呢?對於核心,簡單來說就是用特定的資料結構管理眾多的定時器,在時鐘中斷處理中判斷哪些定時器超時,然後執行超時處理動作。而使用者空間程式不

連結串列的c語言實現以及根據linux核心中連結串列的實現過程

轉自 : http://blog.csdn.net/lickylin/article/details/8010618 連結串列,就是用一組任意的儲存單元儲存線性表元素的一種資料結構。連結串列又分為單鏈表、雙向連結串列和迴圈連結串列等。 下面程式碼是連結串列的兩種實現方式

Linux核心定時詳解

static struct pin_desc *irq_pd; /* 鍵值: 按下時, 0x01, 0x02, 0x03, 0x04 */ /* 鍵值: 鬆開時, 0x81, 0x82, 0x83, 0x84 */ static unsigned char key_val; struct pin_desc{u

Linux定時實現方式分析

級別: 初級 趙 軍 ([email protected]), 開發工程師, Pixelworks 2009 年 10 月 31 日 定時器屬於基本的基礎元件,不管是使用者空間的程式開發,還是核心空間的程式開發,很多時候都需要有定時器作為基礎元件的支援,

多線程中sleep和wait的區別,以及多線程的實現方式及原因,定時--Timer

守護 驗證 取消 技術 方法 代碼 安全 接口 art 1. Java中sleep和wait的區別 ① 這兩個方法來自不同的類分別是,sleep來自Thread類,和wait來自Object類。 sleep是Thread的靜態類方法,誰調用的誰去睡覺,即使在a線程裏調用b

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

ftw rup () int stdlib.h val span 時鐘 sof 本例子參考 Don Libes的Title: Implementing Software Timers例子改寫 為什麽需要這個功能,因為大多數計算機軟件時鐘系統通常只能有一個時鐘觸發一次

利用deadline_timer實現定時Timer

second adl span 停止 deadline timer style set hello 1 // 類似QTimer的定時器 2 class Timer 3 { 4 typedef void(* handler)(); 5 public: 6

linux定時實現方法

this 就是 沒有 讀取數據 entry arm sigalrm read time Linux提供定時器機制,可以指定在未來的某個時刻發生某個事件,定時器的結構如下: struct timer_list { struct list_head list;

Atitit 定時timer 總結 目錄 1. 定時 迴圈定時 和timeout超時定時 1 2. Spring定時 1 2.1. 大概流程 1 2.2. 核心原始碼springboot 1

Atitit 定時器timer 總結   目錄 1. 定時器 迴圈定時器 和timeout超時定時器 1 2. Spring定時器 1 2.1. 大概流程 1 2.2. 核心原始碼springboot 1 3. Js定時器 window.setInte

amlogic平臺android 系統linux核心中新增i2c裝置實現i2c的讀寫

上一篇,我介紹瞭如何在uboot中新增i2c裝置,以及移植i2c的讀寫介面。簡單來說uboot階段使用i2c裝置和平臺關聯性比較大,但不同平臺套路是差不多的。你可以將uboot階段看作是引導androi

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

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

移植好uboot和基於initramfs根檔案系統Linux核心的開發板的啟動過程

我們移植好uboot和Linux核心之後,當我們重啟之後,開發板首先做的事情就是將nandflash前4K的內容複製到SRAM中去,由於SRAM只有4K大小,所以我們必須將初始化CPU、記憶體、中斷、關閉開門狗以及nandflash中uboot拷貝到SDRAM中的的程式都放

Servlet監聽器與Timer定時配合實現JAVA WEB應用簡單自動作業

       在web應用中,有時候客戶需要一些定時程式,不需要客戶自己去操作,而是由應用程式自行觸發執行某些操作。這個時候監聽與定時器的配合使用就基本可以實現這個需求了。  1.建立一個監聽的SERVELET,這個類繼承javax.servlet.http.HttpSe

Atitit 定時timer 總結 目錄 1. 定時 迴圈定時 和timeout超時定時 1 2. Spring定時 1 2.1. 大概流程 1 2.2. 核心原始碼springboot 1

Atitit 定時器timer 總結 目錄 定時器 迴圈定時器 和timeout超時定時器 Spring定時器 大概流程 增加一個定時配置類,新增@Configuration和@EnableScheduling註解

一種嵌入式系統軟體定時實現:以STM32為例

1.什麼是軟體定時器 軟體定時器是用程式模擬出來的定時器,可以由一個硬體定時器模擬出成千上萬個軟體定時器,這樣程式在需要使用較多定時器的時候就不會受限於硬體資源的不足,這是軟體定時器的一個優點,即數量不受限制。但由於軟體定時器是通過程式實現的,其執行和維護

Java多執行緒核心技術(五):定時Timer

Timer類主要負責計劃任務的功能,也就是在指定的時間開始執行某一個任務。 1、schedule(TimerTask task,Date time) 方法schedule(TimerTask task,Date time)的作用是在指定的日期執行一次某一任務。 如下程式碼是

定時實現、java定時Timer和Quartz介紹與Spring中定時的配置

欄位 允許值 允許的特殊字元    秒 0-59 , - * /    分 0-59 , - * /    小時 0-23 , - * /    日期 1-31 , - * ? / L W C    月份 1-12 或者 JAN-DEC , - * /    星期 1-7 或者 SUN-SAT , - *

Linux核心隨機數產生的設計與實現

  EDN部落格精華文章  作者:bluehacker        這幾天抽了點時間看了看linux 2.6.10的程式碼,對裡面的那個核心隨機數產生器發生興趣,花了點工夫分析了下,貼在這裡灌水.   ---------------------------------------------------

Linux應用層的定時Timer

       通過第一個引數which來指定要使用哪一種Timer(ITIMER_REAL、ITIMER_VIRTUAL、ITIMER_PROF)。settimer函式是用來設定對應的Timer的觸發時間是多少,而gettimer函式是用來獲取上一次Timer設定的時間。設定的時間是一個結構體struct