2013年7月30日 星期二

Linux BogoMIPS

在Linux開機的過程中,通常可以看到這個訊息:Calibrating Delay... xxxx BogoMIPS (lpj=xxxxxxx),表示Kernel在開機過程會計算CPU在一個jiffy時間內能執行多少次的延時迴圈,這個計算過程是依據CPU的處理速度進行校正,校正結果值會儲存在Kernel變數中,此變數稱為loops_per_jiffy,而loops_per_jiffy值經過處理後可得到BogoMIPS值。Bogo是Bogus(偽)意思,而MIPS(Million Instructions Per Secon)是每秒百萬指令,所以BogoMIPS就是假的MIPS。透過此網頁http://www.clifton.nl/bogo-list.html可知道不同處理器所對應的BogoMIPS。

那為什麼需要loops_per_jiffy這個值?因為Kernel Driver有時需要很短且精準的delay (millisecond, microsecond, and nanosecond delays),此時透過jiffies實現delay是不適用,由於單位不夠小,假設Timer Frequency是250HZ,則一個tick相當於4ms,表示最小delay時間只能到4ms。所以Kernel提供 mdelay(), udelay(), 和 ndelay() 這三個函式來達到極短且精準的delay,而這三個函式是透過loops_per_jiffy實現,去計算需要多少個loop operation來得到精確的delay。

loops_per_jiffy值是透過calibrate_delay()計算得到,定義在init/calibrate.c,分析如下:
#define LPS_PREC 8

void __cpuinit calibrate_delay(void)
{
 unsigned long ticks, loopbit;
 int lps_precision = LPS_PREC;
 static bool printed;

 if (preset_lpj) {
  loops_per_jiffy = preset_lpj;
  if (!printed)
   pr_info("Calibrating delay loop (skipped) "
    "preset value.. ");
 } else if ((!printed) && lpj_fine) {
  loops_per_jiffy = lpj_fine;
  pr_info("Calibrating delay loop (skipped), "
   "value calculated using timer frequency.. ");
 } else if ((loops_per_jiffy = calibrate_delay_direct()) != 0) {
  if (!printed)
   pr_info("Calibrating delay using timer "
    "specific routine.. ");
 } else {
  loops_per_jiffy = (1<<12); //一個jiffy時間內執行多少次delay迴圈,初始值4096。

  if (!printed)
   pr_info("Calibrating delay loop... ");
  /* 
   * 底下while迴圈,第一次粗略的計算loops_per_jiffy,
   * 為下面的計算打好基礎。一般loops_per_sec的初始值
   * 並不大(4096),所以循環會逐步加大loops_per_sec的
   * 值,直到延時超過一個jiffy。我們可以看出,前一次
   * loops_per_sec的值還因太小不合適時,經過一次增大
   * ,它提高了兩倍,滿足了循環條件,跳出循環,而這
   * 個值實在是誤差太大,所以我們還要經過第二次計算。
   */
  while ((loops_per_jiffy <<= 1) != 0) {
   /* wait for "start of" clock tick */
   ticks = jiffies; //紀錄當前jiffies
   while (ticks == jiffies) //等待下一個新的jiffy開始
    /* nothing */;
   /* Go .. */
   ticks = jiffies; //紀錄新的jiffy開始後當前jiffies
   __delay(loops_per_jiffy); //依據loops_per_jiffy值進行延時
   //下面三行用於判斷執行的延時是否超過一個jiffy時間。
   ticks = jiffies - ticks;
   if (ticks)
    break;
  }

  /*
   * Do a binary approximation to get loops_per_jiffy set to
   * equal one clock (up to lps_precision bits)
   *
   * 這裡開始就是第二次計算了。由於第一次階段得到的
   * loops_per_jiffy是粗略值,而且大於正確值很多,
   * 所以要向右shift一個bit,既由0b10000變成0b1000。
   */
  loops_per_jiffy >>= 1;

  /*
   * 然後第二次計算就是要找出0b1xyz中的xyz值,
   * 底下的while迴圈會先假設a為1,然後測試0b1100,
   * 如果0b110超過一個jiffy時間,則x=0,如果沒超過,
   * 則x=1,然後再繼續檢查y,以此類推。最後檢查到
   * LPS_PREC=8個位數。
   */
  loopbit = loops_per_jiffy;
  while (lps_precision-- && (loopbit >>= 1)) {
   loops_per_jiffy |= loopbit;
   ticks = jiffies;
   while (ticks == jiffies)
    /* nothing */;
   ticks = jiffies;
   __delay(loops_per_jiffy);
   if (jiffies != ticks) /* longer than 1 tick */
    loops_per_jiffy &= ~loopbit;
  }
 }
 /*
  * BogoMIPS = (loops_per_jiffy * HZ * 2) / (1 million)
  */
 if (!printed)
  pr_cont("%lu.%02lu BogoMIPS (lpj=%lu)\n",
   loops_per_jiffy/(500000/HZ),
   (loops_per_jiffy/(5000/HZ)) % 100, loops_per_jiffy);

 printed = true;
}
註:
  • HZ : 每隔固定週期Linux Kernel會發出timer interrupt,HZ定義每一秒有幾次timer interrupts。
  • Tick (Jiffy) : HZ的倒數,意即timer過多久發生一次interrupt。
  • Jiffies : 用來紀錄系統自開幾以來,已經過多少的tick,每發生一次timer interrupt,Jiffies變數會被加一。

沒有留言:

張貼留言