<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(十一)- 定時(shí)器和時(shí)間管理

      系統(tǒng)中有很多與時(shí)間相關(guān)的程序(比如定期執(zhí)行的任務(wù),某一時(shí)間執(zhí)行的任務(wù),推遲一段時(shí)間執(zhí)行的任務(wù)),因此,時(shí)間的管理對(duì)于linux來(lái)說(shuō)非常重要。

       

      主要內(nèi)容:

      • 系統(tǒng)時(shí)間
      • 定時(shí)器
      • 定時(shí)器相關(guān)概念
      • 定時(shí)器執(zhí)行流程
      • 實(shí)現(xiàn)程序延遲的方法
      • 定時(shí)器和延遲的例子

       

      1. 系統(tǒng)時(shí)間

      系統(tǒng)中管理的時(shí)間有2種:實(shí)際時(shí)間和定時(shí)器。

      1.1  實(shí)際時(shí)間

      實(shí)際時(shí)間就是現(xiàn)實(shí)中鐘表上顯示的時(shí)間,其實(shí)內(nèi)核中并不常用這個(gè)時(shí)間,主要是用戶空間的程序有時(shí)需要獲取當(dāng)前時(shí)間,

      所以內(nèi)核中也管理著這個(gè)時(shí)間。

       

      實(shí)際時(shí)間的獲取是在開(kāi)機(jī)后,內(nèi)核初始化時(shí)從RTC讀取的。

      內(nèi)核讀取這個(gè)時(shí)間后就將其放入內(nèi)核中的 xtime 變量中,并且在系統(tǒng)的運(yùn)行中不斷更新這個(gè)值。

      注:RTC就是實(shí)時(shí)時(shí)鐘的縮寫,它是用來(lái)存放系統(tǒng)時(shí)間的設(shè)備。一般和BIOS一樣,由主板上的電池供電的,所以即使關(guān)機(jī)也可將時(shí)間保存。

       

      實(shí)際時(shí)間存放的變量 xtime 在文件 kernel/time/timekeeping.c中。

      /* 按照16位對(duì)齊,其實(shí)就是2個(gè)long型的數(shù)據(jù) */
      struct timespec xtime __attribute__ ((aligned (16)));
      
      /* timespec結(jié)構(gòu)體的定義如下, 參考 <linux/time.h>  */
      struct timespec {
          __kernel_time_t    tv_sec;            /* seconds */
          long        tv_nsec;        /* nanoseconds */
      };
      
      /* _kernel_time_t 定義如下 */
      typedef long        __kernel_time_t;

       

      系統(tǒng)讀寫 xtime 時(shí)用的就是順序鎖。

      /* 寫入 xtime 參考 do_sometimeofday 方法 */
      int do_settimeofday(struct timespec *tv)
      {
      /* 省略 。。。。 */
          write_seqlock_irqsave(&xtime_lock, flags); /* 獲取寫鎖 */
      /* 更新 xtime */
          write_sequnlock_irqrestore(&xtime_lock, flags); /* 釋放寫鎖 */
      /* 省略 。。。。 */
          return 0;
      }
      
      /* 讀取 xtime 參考 do_gettimeofday 方法 */
      void do_gettimeofday(struct timeval *tv)
      {
          struct timespec now;
      
          getnstimeofday(&now); /* 就是在這個(gè)方法中獲取讀鎖,并讀取 xtime */
          tv->tv_sec = now.tv_sec;
          tv->tv_usec = now.tv_nsec/1000;
      }
      
      void getnstimeofday(struct timespec *ts)
      {
      /* 省略 。。。。 */
      
      /* 順序鎖中讀鎖來(lái)循環(huán)獲取 xtime,直至讀取過(guò)程中 xtime 沒(méi)有被改變過(guò) */
          do {
              seq = read_seqbegin(&xtime_lock);
      
              *ts = xtime;
              nsecs = timekeeping_get_ns();
      
              /* If arch requires, add in gettimeoffset() */
              nsecs += arch_gettimeoffset();
      
          } while (read_seqretry(&xtime_lock, seq));
      /* 省略 。。。。 */
      }

      上述場(chǎng)景中,寫鎖必須要優(yōu)先于讀鎖(因?yàn)?xtime 必須及時(shí)更新),而且寫鎖的使用者很少(一般只有系統(tǒng)定期更新xtime的線程需要持有這個(gè)鎖)。

      這正是 順序鎖的應(yīng)用場(chǎng)景。

       

      1.2 定時(shí)器

      定時(shí)器是內(nèi)核中主要使用的時(shí)間管理方法,通過(guò)定時(shí)器,可以有效的調(diào)度程序的執(zhí)行。

      動(dòng)態(tài)定時(shí)器是內(nèi)核中使用比較多的定時(shí)器,下面重點(diǎn)討論的也是動(dòng)態(tài)定時(shí)器。

       

      2. 定時(shí)器

      內(nèi)核中的定時(shí)器有2種,靜態(tài)定時(shí)器和動(dòng)態(tài)定時(shí)器。

      靜態(tài)定時(shí)器一般執(zhí)行了一些周期性的固定工作:

      • 更新系統(tǒng)運(yùn)行時(shí)間
      • 更新實(shí)際時(shí)間
      • 在SMP系統(tǒng)上,平衡各個(gè)處理器上的運(yùn)行隊(duì)列
      • 檢查當(dāng)前進(jìn)程是否用盡了自己的時(shí)間片,如果用盡,需要重新調(diào)度。
      • 更新資源消耗和處理器時(shí)間統(tǒng)計(jì)值

       

      動(dòng)態(tài)定時(shí)器顧名思義,是在需要時(shí)(一般是推遲程序執(zhí)行)動(dòng)態(tài)創(chuàng)建的定時(shí)器,使用后銷毀(一般都是只用一次)。

      一般我們?cè)趦?nèi)核代碼中使用的定時(shí)器基本都是動(dòng)態(tài)定時(shí)器,下面重點(diǎn)討論動(dòng)態(tài)定時(shí)器相關(guān)的概念和使用方法。

       

      3. 定時(shí)器相關(guān)概念

      定時(shí)器的使用中,下面3個(gè)概念非常重要:

      1. HZ
      2. jiffies
      3. 時(shí)間中斷處理程序

       

      3.1 HZ

      節(jié)拍率(HZ)是時(shí)鐘中斷的頻率,表示的一秒內(nèi)時(shí)鐘中斷的次數(shù)。

      比如 HZ=100 表示一秒內(nèi)觸發(fā)100次時(shí)鐘中斷程序。

       

      HZ的值一般與體系結(jié)構(gòu)有關(guān),x86 體系結(jié)構(gòu)一般定義為 100,參考文件 include/asm-generic/param.h

      HZ值的大小的設(shè)置過(guò)程其實(shí)就是平衡 精度和性能 的過(guò)程,并不是HZ值越高越好。

      HZ值

      優(yōu)勢(shì)

      劣勢(shì)

      高HZ 時(shí)鐘中斷程序運(yùn)行的更加頻繁,依賴時(shí)間執(zhí)行的程序更加精確,
      對(duì)資源消耗和系統(tǒng)運(yùn)行時(shí)間的統(tǒng)計(jì)更加精確。
      時(shí)鐘中斷執(zhí)行的頻繁,增加系統(tǒng)負(fù)擔(dān)
      時(shí)鐘中斷占用的CPU時(shí)間過(guò)多

       

      此外,有一點(diǎn)需要注意,內(nèi)核中使用的HZ可能和用戶空間中定義的HZ值不一致,為了避免用戶空間取得錯(cuò)誤的時(shí)間,

      內(nèi)核中也定義了 USER_HZ,即用戶空間使用的HZ值。

      一般來(lái)說(shuō),USER_HZ 和 HZ 都是相差整數(shù)倍,內(nèi)核中通過(guò)函數(shù) jiffies_to_clock_t 來(lái)將內(nèi)核來(lái)將內(nèi)核中的 jiffies轉(zhuǎn)為 用戶空間 jiffies

      /* 參見(jiàn)文件: kernel/time.c  *
      //*
       * Convert jiffies/jiffies_64 to clock_t and back.
       */
      clock_t jiffies_to_clock_t(unsigned long x)
      {
      #if (TICK_NSEC % (NSEC_PER_SEC / USER_HZ)) == 0
      # if HZ < USER_HZ
          return x * (USER_HZ / HZ);
      # else
          return x / (HZ / USER_HZ);
      # endif
      #else
          return div_u64((u64)x * TICK_NSEC, NSEC_PER_SEC / USER_HZ);
      #endif
      }
      EXPORT_SYMBOL(jiffies_to_clock_t);

       

      3.2 jiffies

      jiffies用來(lái)記錄自系統(tǒng)啟動(dòng)以來(lái)產(chǎn)生的總節(jié)拍數(shù)。比如系統(tǒng)啟動(dòng)了 N 秒,那么 jiffies就為 N×HZ

      jiffies的相關(guān)定義參考頭文件 <linux/jiffies.h>  include/linux/jiffies.h

      /* 64bit和32bit的jiffies定義如下 */
      extern u64 __jiffy_data jiffies_64;
      extern unsigned long volatile __jiffy_data jiffies;

       

      使用定時(shí)器時(shí)一般都是以jiffies為單位來(lái)延遲程序執(zhí)行的,比如延遲5個(gè)節(jié)拍后執(zhí)行的話,執(zhí)行時(shí)間就是 jiffies+5

      32位的jiffies的最大值為 2^32-1,在使用時(shí)有可能會(huì)出現(xiàn)回繞的問(wèn)題。

      比如下面的代碼:

      unsigned long timeout = jiffies + HZ/2; /* 設(shè)置超時(shí)時(shí)間為 0.5秒 */
      
      while (timeout < jiffies)
      {
          /* 還沒(méi)有超時(shí),繼續(xù)執(zhí)行任務(wù) */
      }
      
      /* 執(zhí)行超時(shí)后的任務(wù) */

      正常情況下,上面的代碼沒(méi)有問(wèn)題。當(dāng)jiffies接近最大值的時(shí)候,就會(huì)出現(xiàn)回繞問(wèn)題。

      由于是unsinged long類型,所以jiffies達(dá)到最大值后會(huì)變成0然后再逐漸變大,如下圖所示:

      unsigned_jiffies

       

      所以在上述的循環(huán)代碼中,會(huì)出現(xiàn)如下情況:

      jiffies_rewind

      1. 循環(huán)中第一次比較時(shí),jiffies = J1,沒(méi)有超時(shí)
      2. 循環(huán)中第二次比較時(shí),jiffies = J2,實(shí)際已經(jīng)超時(shí)了,但是由于jiffies超過(guò)的最大值后又從0開(kāi)始,所以J2遠(yuǎn)遠(yuǎn)小于timeout
      3. while循環(huán)會(huì)執(zhí)行很長(zhǎng)時(shí)間(> 2^32-1 個(gè)節(jié)拍)不會(huì)結(jié)束,幾乎相當(dāng)于死循環(huán)了

       

      為了回避回?cái)_的問(wèn)題,可以使用<linux/jiffies.h>頭文件中提供的 time_aftertime_before等宏

      #define time_after(a,b)        \
          (typecheck(unsigned long, a) && \
           typecheck(unsigned long, b) && \
           ((long)(b) - (long)(a) < 0))
      #define time_before(a,b)    time_after(b,a)
      
      #define time_after_eq(a,b)    \
          (typecheck(unsigned long, a) && \
           typecheck(unsigned long, b) && \
           ((long)(a) - (long)(b) >= 0))
      #define time_before_eq(a,b)    time_after_eq(b,a)

      上述代碼的原理其實(shí)就是將 unsigned long 類型轉(zhuǎn)換為 long 類型來(lái)避免回?cái)_帶來(lái)的錯(cuò)誤,

      long 類型超過(guò)最大值時(shí)變化趨勢(shì)如下:

      signed_jiffies

       

      long 型的數(shù)據(jù)的回繞會(huì)出現(xiàn)在 2^31-1 變?yōu)?-2^32 的時(shí)候,如下圖所示:

      long_rewind

      1. 第一次比較時(shí),jiffies = J1,沒(méi)有超時(shí)
      2. 第二次比較時(shí),jiffies = J2,一般 J2 是負(fù)數(shù)
        理論上 (long)timeout - (long)J2 = 正數(shù) - 負(fù)數(shù) = 正數(shù)(result)
        但是,這個(gè)正數(shù)(result)一般會(huì)大于 2^31 - 1,所以long型的result又發(fā)生了一次回繞,變成了負(fù)數(shù)。
        除非timeout和J2之間的間隔 > 2^32 個(gè)節(jié)拍,result的值才會(huì)為正數(shù)(注1)。

      注1:result的值為正數(shù)時(shí),必須是在result的值 小于 2^31-1 的情況下,大于 2^31-1 會(huì)發(fā)生回繞。

      long_result

      上圖中 X + Y 表示timeout 和 J2之間經(jīng)過(guò)的節(jié)拍數(shù)。

      result 小于 2^31-1 ,也就是 timeout - J2 < 2^31 – 1

      timeout 和 -J2 表示的節(jié)拍數(shù)如上圖所示。(因?yàn)镴2是負(fù)數(shù),所有-J2表示上圖所示范圍的值)

      因?yàn)?timeout + X + Y - J2 = 2^31-1 + 2^32

      所以 timeout - J2 < 2^31 - 1 時(shí), X + Y > 2^32

      也就是說(shuō),當(dāng)timeout和J2之間經(jīng)過(guò)至少 2^32 個(gè)節(jié)拍后,result才可能變?yōu)檎龜?shù)。

      timeout和J2之間相差這么多節(jié)拍是不可能的(不信可以用HZ將這些節(jié)拍換算成秒就知道了。。。)

       

      利用time_after宏就可以巧妙的避免回繞帶來(lái)的超時(shí)判斷問(wèn)題,將之前的代碼改成如下代碼即可:

      unsigned long timeout = jiffies + HZ/2; /* 設(shè)置超時(shí)時(shí)間為 0.5秒 */
      
      while (time_after(jiffies, timeout))
      {
          /* 還沒(méi)有超時(shí),繼續(xù)執(zhí)行任務(wù) */
      }
      
      /* 執(zhí)行超時(shí)后的任務(wù) */

       

      3.3 時(shí)鐘中斷處理程序

      時(shí)鐘中斷處理程序作為系統(tǒng)定時(shí)器而注冊(cè)到內(nèi)核中,體系結(jié)構(gòu)的不同,可能時(shí)鐘中斷處理程序中處理的內(nèi)容不同。

      但是以下這些基本的工作都會(huì)執(zhí)行:

      • 獲得 xtime_lock 鎖,以便對(duì)訪問(wèn) jiffies_64 和墻上時(shí)間 xtime 進(jìn)行保護(hù)
      • 需要時(shí)應(yīng)答或重新設(shè)置系統(tǒng)時(shí)鐘
      • 周期性的使用墻上時(shí)間更新實(shí)時(shí)時(shí)鐘
      • 調(diào)用 tick_periodic()

       

      tick_periodic函數(shù)位于: kernel/time/tick-common.c

      static void tick_periodic(int cpu)
      {
          if (tick_do_timer_cpu == cpu) {
              write_seqlock(&xtime_lock);
      
              /* Keep track of the next tick event */
              tick_next_period = ktime_add(tick_next_period, tick_period);
      
              do_timer(1);
              write_sequnlock(&xtime_lock);
          }
      
          update_process_times(user_mode(get_irq_regs()));
          profile_tick(CPU_PROFILING);
      }

      其中最重要的是 do_timer 和 update_process_times 函數(shù)。

      我了解的步驟進(jìn)行了簡(jiǎn)單的注釋。

      void do_timer(unsigned long ticks)
      {
          /* jiffies_64 增加指定ticks */
          jiffies_64 += ticks;
          /* 更新實(shí)際時(shí)間 */
          update_wall_time();
          /* 更新系統(tǒng)的平均負(fù)載值 */
          calc_global_load();
      }
      
      void update_process_times(int user_tick)
      {
          struct task_struct *p = current;
          int cpu = smp_processor_id();
      
          /* 更新當(dāng)前進(jìn)程占用CPU的時(shí)間 */
          account_process_tick(p, user_tick);
          /* 同時(shí)觸發(fā)軟中斷,處理所有到期的定時(shí)器 */
          run_local_timers();
          rcu_check_callbacks(cpu, user_tick);
          printk_tick();
          /* 減少當(dāng)前進(jìn)程的時(shí)間片數(shù) */
          scheduler_tick();
          run_posix_cpu_timers(p);
      }

       

      4. 定時(shí)器執(zhí)行流程

      這里討論的定時(shí)器執(zhí)行流程是動(dòng)態(tài)定時(shí)器的執(zhí)行流程。

       

      4.1 定時(shí)器的定義

      定時(shí)器在內(nèi)核中用一個(gè)鏈表來(lái)保存的,鏈表的每個(gè)節(jié)點(diǎn)都是一個(gè)定時(shí)器。

      參見(jiàn)頭文件 <linux/timer.h>

      struct timer_list {
          struct list_head entry;
          unsigned long expires;
      
          void (*function)(unsigned long);
          unsigned long data;
      
          struct tvec_base *base;
      #ifdef CONFIG_TIMER_STATS
          void *start_site;
          char start_comm[16];
          int start_pid;
      #endif
      #ifdef CONFIG_LOCKDEP
          struct lockdep_map lockdep_map;
      #endif
      };

      通過(guò)加入條件編譯的參數(shù),可以追加一些調(diào)試信息。

       

      4.2 定時(shí)器的生命周期

      一個(gè)動(dòng)態(tài)定時(shí)器的生命周期中,一般會(huì)經(jīng)過(guò)下面的幾個(gè)步驟:

      timer_life

      1. 初始化定時(shí)器:

      struct timer_list my_timer; /* 定義定時(shí)器 */
      init_timer(&my_timer);      /* 初始化定時(shí)器 */

       

      2. 填充定時(shí)器:

      my_timer.expires = jiffies + delay; /* 定義超時(shí)的節(jié)拍數(shù) */
      my_timer.data = 0;                  /* 給定時(shí)器函數(shù)傳入的參數(shù) */
      my_timer.function = my_function;    /* 定時(shí)器超時(shí)時(shí),執(zhí)行的自定義函數(shù) */
      
      /* 從定時(shí)器結(jié)構(gòu)體中,我們可以看出這個(gè)函數(shù)的原型應(yīng)該如下所示: */
      void my_function(unsigned long data);

       

      3. 激活定時(shí)器和修改定時(shí)器:

      激活定時(shí)器之后才會(huì)被觸發(fā),否則定時(shí)器不會(huì)執(zhí)行。

      修改定時(shí)器主要是修改定時(shí)器的延遲時(shí)間,修改定時(shí)器后,不管原先定時(shí)器有沒(méi)有被激活,都會(huì)處于激活狀態(tài)。

       

      填充定時(shí)器結(jié)構(gòu)之后,可以只激活定時(shí)器,也可以只修改定時(shí)器,也可以激活定時(shí)器后再修改定時(shí)器。

      所以填充定時(shí)器結(jié)構(gòu)和觸發(fā)定時(shí)器之間的步驟,也就是虛線框中的步驟是不確定的。

      add_timer(&my_timer);  /* 激活定時(shí)器 */
      mod_timer(&my_timer, jiffies + new_delay);  /* 修改定時(shí)器,設(shè)置新的延遲時(shí)間 */

       

      4. 觸發(fā)定時(shí)器:

      每次時(shí)鐘中斷處理程序會(huì)檢查已經(jīng)激活的定時(shí)器是否超時(shí),如果超時(shí)就執(zhí)行定時(shí)器結(jié)構(gòu)中的自定義函數(shù)。

       

      5. 刪除定時(shí)器:

      激活和未被激活的定時(shí)器都可以被刪除,已經(jīng)超時(shí)的定時(shí)器會(huì)自動(dòng)刪除,不用特意去刪除。

      /*
       * 刪除激活的定時(shí)器時(shí),此函數(shù)返回1
       * 刪除未激活的定時(shí)器時(shí),此函數(shù)返回0
       */
      del_timer(&my_timer);

      在多核處理器上用 del_timer 函數(shù)刪除定時(shí)器時(shí),可能在刪除時(shí)正好另一個(gè)CPU核上的時(shí)鐘中斷處理程序正在執(zhí)行這個(gè)定時(shí)器,于是就形成了競(jìng)爭(zhēng)條件。

      為了避免競(jìng)爭(zhēng)條件,建議使用 del_timer_sync 函數(shù)來(lái)刪除定時(shí)器。

      del_timer_sync 函數(shù)會(huì)等待其他處理器上的定時(shí)器處理程序全部結(jié)束后,才刪除指定的定時(shí)器。

      /*
       * 和del_timer 不同,del_timer_sync 不能在中斷上下文中執(zhí)行
       */
      del_timer_sync(&my_timer);

       

      5. 實(shí)現(xiàn)程序延遲的方法

      內(nèi)核中有個(gè)利用定時(shí)器實(shí)現(xiàn)延遲的函數(shù) schedule_timeout

      這個(gè)函數(shù)會(huì)將當(dāng)前的任務(wù)睡眠到指定時(shí)間后喚醒,所以等待時(shí)不會(huì)占用CPU時(shí)間。

      /* 將任務(wù)設(shè)置為可中斷睡眠狀態(tài) */
      set_current_state(TASK_INTERRUPTIBLE);
      
      /* 小睡一會(huì)兒,“s“秒后喚醒 */
      schedule_timeout(s*HZ);

       

      查看 schedule_timeout 函數(shù)的實(shí)現(xiàn)方法,可以看出是如何使用定時(shí)器的。

      signed long __sched schedule_timeout(signed long timeout)
      {
          /* 定義一個(gè)定時(shí)器 */
          struct timer_list timer;
          unsigned long expire;
      
          switch (timeout)
          {
          case MAX_SCHEDULE_TIMEOUT:
              /*
               * These two special cases are useful to be comfortable
               * in the caller. Nothing more. We could take
               * MAX_SCHEDULE_TIMEOUT from one of the negative value
               * but I' d like to return a valid offset (>=0) to allow
               * the caller to do everything it want with the retval.
               */
              schedule();
              goto out;
          default:
              /*
               * Another bit of PARANOID. Note that the retval will be
               * 0 since no piece of kernel is supposed to do a check
               * for a negative retval of schedule_timeout() (since it
               * should never happens anyway). You just have the printk()
               * that will tell you if something is gone wrong and where.
               */
              if (timeout < 0) {
                  printk(KERN_ERR "schedule_timeout: wrong timeout "
                      "value %lx\n", timeout);
                  dump_stack();
                  current->state = TASK_RUNNING;
                  goto out;
              }
          }
      
          /* 設(shè)置超時(shí)時(shí)間 */
          expire = timeout + jiffies;
      
          /* 初始化定時(shí)器,超時(shí)處理函數(shù)是 process_timeout,后面再補(bǔ)充說(shuō)明一下這個(gè)函數(shù) */
          setup_timer_on_stack(&timer, process_timeout, (unsigned long)current);
          /* 修改定時(shí)器,同時(shí)會(huì)激活定時(shí)器 */
          __mod_timer(&timer, expire, false, TIMER_NOT_PINNED);
          /* 將本任務(wù)睡眠,調(diào)度其他任務(wù) */
          schedule();
          /* 刪除定時(shí)器,其實(shí)就是 del_timer_sync 的宏
          del_singleshot_timer_sync(&timer);
      
          /* Remove the timer from the object tracker */
          destroy_timer_on_stack(&timer);
      
          timeout = expire - jiffies;
      
       out:
          return timeout < 0 ? 0 : timeout;
      }
      EXPORT_SYMBOL(schedule_timeout);
      
      /* 
       * 超時(shí)處理函數(shù) process_timeout 里面只有一步操作,喚醒當(dāng)前任務(wù)。
       * process_timeout 的參數(shù)其實(shí)就是 當(dāng)前任務(wù)的地址
       */
      static void process_timeout(unsigned long __data)
      {
          wake_up_process((struct task_struct *)__data);
      }

      schedule_timeout 一般用于延遲時(shí)間較長(zhǎng)的程序。

      這里的延遲時(shí)間較長(zhǎng)是對(duì)于計(jì)算機(jī)而言的,其實(shí)也就是延遲大于 1 個(gè)節(jié)拍(jiffies)。

       

      對(duì)于某些極其短暫的延遲,比如只有1ms,甚至1us,1ns的延遲,必須使用特殊的延遲方法。

      1s = 1000ms = 1000000us = 1000000000ns (1秒=1000毫秒=1000000微秒=1000000000納秒)

      假設(shè) HZ=100,那么 1個(gè)節(jié)拍的時(shí)間間隔是 1/100秒,大概10ms左右。

      所以對(duì)于那些極其短暫的延遲,schedule_timeout 函數(shù)是無(wú)法使用的。

      好在內(nèi)核對(duì)于這些短暫,精確的延遲要求也提供了相應(yīng)的宏。

      /* 具體實(shí)現(xiàn)參見(jiàn) include/linux/delay.h
       * 以及 arch/x86/include/asm/delay.h
       */
      #define mdelay(n) ...
      #define udelay(n) ...
      #define ndelay(n) ...

      通過(guò)這些宏,可以簡(jiǎn)單的實(shí)現(xiàn)延遲,比如延遲 5ns,只需 ndelay(5); 即可。

       

      這些短延遲的實(shí)現(xiàn)原理并不復(fù)雜,

      首先,內(nèi)核在啟動(dòng)時(shí)就計(jì)算出了當(dāng)前處理器1秒能執(zhí)行多少次循環(huán),即 loops_per_jiffy

      (loops_per_jiffy 的計(jì)算方法參見(jiàn) init/main.c 文件中的 calibrate_delay 方法)。

      然后算出延遲 5ns 需要循環(huán)多少次,執(zhí)行那么多次空循環(huán)即可達(dá)到延遲的效果。

       

      loops_per_jiffy 的值可以在啟動(dòng)信息中看到:

      [root@vbox ~]# dmesg | grep delay
      Calibrating delay loop (skipped), value calculated using timer frequency.. 6387.58 BogoMIPS (lpj=3193792)

      我的虛擬機(jī)中看到 (lpj=3193792)

       

      6. 定時(shí)器和延遲的例子

      下面的例子測(cè)試了短延遲,自定義定時(shí)器以及 schedule_timeout 的使用:

      #include <linux/sched.h>
      #include <linux/timer.h>
      #include <linux/jiffies.h>
      #include <asm/param.h>
      #include <linux/delay.h>
      #include "kn_common.h"
      
      MODULE_LICENSE("Dual BSD/GPL");
      
      static void test_short_delay(void);
      static void test_delay(void);
      static void test_schedule_timeout(void);
      static void my_delay_function(unsigned long);
      
      static int testdelay_init(void)
      {
          printk(KERN_ALERT "HZ in current system: %dHz\n", HZ);
      
          /* test short delay */
          test_short_delay();
      
          /* test delay */
          test_delay();
      
          /* test schedule timeout */
          test_schedule_timeout();
      
          return 0;
      }
      
      static void testdelay_exit(void)
      {
          printk(KERN_ALERT "*************************\n");
          print_current_time(0);
          printk(KERN_ALERT "testdelay is exited!\n");
          printk(KERN_ALERT "*************************\n");
      }
      
      static void test_short_delay()
      {
          printk(KERN_ALERT "jiffies [b e f o r e] short delay: %lu", jiffies);
          ndelay(5);
          printk(KERN_ALERT "jiffies [a f t e r] short delay: %lu", jiffies);
      }
      
      static void test_delay()
      {
          /* 初始化定時(shí)器 */
          struct timer_list my_timer;
          init_timer(&my_timer);
      
          /* 填充定時(shí)器 */
          my_timer.expires = jiffies + 1*HZ; /* 2秒后超時(shí)函數(shù)執(zhí)行 */
          my_timer.data = jiffies;
          my_timer.function = my_delay_function;
      
          /* 激活定時(shí)器 */
          add_timer(&my_timer);
      }
      
      static void my_delay_function(unsigned long data)
      {
          printk(KERN_ALERT "This is my delay function start......\n");
          printk(KERN_ALERT "The jiffies when init timer: %lu\n", data);
          printk(KERN_ALERT "The jiffies when timer is running: %lu\n", jiffies);
          printk(KERN_ALERT "This is my delay function end........\n");
      }
      
      static void test_schedule_timeout()
      {
          printk(KERN_ALERT "This sample start at : %lu", jiffies);
      
          /* 睡眠2秒 */
          set_current_state(TASK_INTERRUPTIBLE);
          printk(KERN_ALERT "sleep 2s ....\n");
          schedule_timeout(2*HZ);
      
          printk(KERN_ALERT "This sample end at : %lu", jiffies);
      }
      
      module_init(testdelay_init);
      module_exit(testdelay_exit);

      其中用到的 kn_common.h 和 kn_common.c 參見(jiàn)之前的博客 《Linux內(nèi)核設(shè)計(jì)與實(shí)現(xiàn)》讀書筆記(六)- 內(nèi)核數(shù)據(jù)結(jié)構(gòu)

      Makefile如下:

      # must complile on customize kernel
      obj-m += mydelay.o
      mydelay-objs := testdelay.o kn_common.o
      
      #generate the path
      CURRENT_PATH:=$(shell pwd)
      #the current kernel version number
      LINUX_KERNEL:=$(shell uname -r)
      #the absolute path
      LINUX_KERNEL_PATH:=/usr/src/kernels/$(LINUX_KERNEL)
      #complie object
      all:
          make -C $(LINUX_KERNEL_PATH) M=$(CURRENT_PATH) modules
          rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c .tmp_versions *.unsigned
      #clean
      clean:
          rm -rf modules.order Module.symvers .*.cmd *.o *.mod.c *.ko .tmp_versions *.unsigned

       

      執(zhí)行測(cè)試命令及查看結(jié)果的方法如下:(我的測(cè)試系統(tǒng)是 CentOS 6.3 x64)

      [root@vbox chap11]# make
      [root@vbox chap11]# insmod mydelay.ko 
      [root@vbox chap11]# rmmod mydelay.ko 
      [root@vbox chap11]# dmesg | tail -14
      HZ in current system: 1000Hz
      jiffies [b e f o r e] short delay: 4296079617
      jiffies [a f t e r] short delay: 4296079617
      This sample start at : 4296079619
      sleep 2s ....
      This is my delay function start......
      The jiffies when init timer: 4296079619
      The jiffies when timer is running: 4296080621
      This is my delay function end........
      This sample end at : 4296081622
      *************************
      2013-5-9 23:7:20
      testdelay is exited!
      *************************

       

      結(jié)果說(shuō)明:

      1. 短延遲只延遲了 5ns,所以執(zhí)行前后的jiffies是一樣的。

      jiffies [b e f o r e] short delay: 4296079617
      jiffies [a f t e r] short delay: 4296079617

       

      2. 自定義定時(shí)器延遲了1秒后執(zhí)行自定義函數(shù),由于我的系統(tǒng) HZ=1000,所以jiffies應(yīng)該相差1000

      The jiffies when init timer: 4296079619
      The jiffies when timer is running: 4296080621

      實(shí)際上jiffies相差了 1002,多了2個(gè)節(jié)拍

       

      3. schedule_timeout 延遲了2秒,jiffies應(yīng)該相差 2000

      This sample start at : 4296079619
      This sample end at : 4296081622

      實(shí)際上jiffies相差了 2003,多了3個(gè)節(jié)拍

       

      以上結(jié)果也說(shuō)明了定時(shí)器的延遲并不是那么精確,差了2,3個(gè)節(jié)拍其實(shí)就是誤差2,3毫秒(因?yàn)镠Z=1000)

      如果HZ=100的話,一個(gè)節(jié)拍是10毫秒,那么定時(shí)器的誤差可能就發(fā)現(xiàn)不了了(誤差只有2,3毫秒,沒(méi)有超多1個(gè)節(jié)拍)。

      posted @ 2013-05-10 07:56  wang_yb  閱讀(11962)  評(píng)論(1)    收藏  舉報(bào)
      主站蜘蛛池模板: 人妻饥渴偷公乱中文字幕| 亚洲精品日本一区二区| 久久国产成人高清精品亚洲| 欧美大胆老熟妇乱子伦视频| 松潘县| 国产精品永久久久久久久久久 | 久久精品夜色噜噜亚洲aa| 青青草原网站在线观看| 日韩av影院在线观看| 日韩区中文字幕在线观看| 亚洲国产超清无码专区| 免费看成人aa片无码视频吃奶| 一区二区三区午夜福利院| 国产成人人综合亚洲欧美丁香花 | 日本一区二区三区小视频| 久久日韩精品一区二区五区 | 福利一区二区不卡国产| 热久久美女精品天天吊色| 久久99九九精品久久久久蜜桃 | 亚洲精品欧美综合二区| 亚洲人妻系列中文字幕| 色婷婷综合久久久久中文一区二区| 国产福利视频区一区二区| 99久久精品费精品国产一区二| 扒开女人内裤猛进猛出免费视频| 91亚洲国产成人精品性色| 国产一区二区不卡91| 国产香蕉尹人综合在线观看| 亚洲精品韩国一区二区| 日韩精品久久久肉伦网站| 72种姿势欧美久久久久大黄蕉| 兴国县| 偷拍专区一区二区三区| 精品91在线| 少妇极品熟妇人妻| 国产欧美精品一区aⅴ影院| 五月婷婷激情第四季| 黑森林福利视频导航| 久久精产国品一二三产品| 999久久久免费精品播放| 久久精品一区二区三区av|