内核中的HZ 及延迟等
時鐘中斷由系統定時硬件以周期性的間隔產生,這個間隔由內核根據 HZ 值來設定,HZ 是一個體系依賴的值,在 <linux/param.h>中定義或該文件包含的某個子平臺相關文件中。作為通用的規則,即便如果知道 HZ 的值,在編程時應當不依賴這個特定值,而始終使用HZ。對于當前版本,我們應完全信任內核開發者,他們已經選擇了最適合的HZ值,最好保持 HZ 的默認值。
對用戶空間,內核HZ幾乎完全隱藏,用戶 HZ 始終擴展為 100。當用戶空間程序包含 param.h,且每個報告給用戶空間的計數器都做了相應轉換。對用戶來說確切的 HZ 值只能通過 /proc/interrupts 獲得:/proc/interrupts 的計數值除以 /proc/uptime 中報告的系統運行時間。
對于ARM體系結構:在<linux/param.h>文件中的定義如下:
#ifdef __KERNEL__
# define HZ???????? CONFIG_HZ??? /* Internal kernel timer frequency */
# define USER_HZ???? 100??????? /* 用戶空間使用的HZ,User interfaces are in "ticks" */
# define CLOCKS_PER_SEC (USER_HZ) /* like times() */
#else
# define HZ???????? 100
#endif
也就是說:HZ 由__KERNEL__和CONFIG_HZ決定。若未定義__KERNEL__,HZ為100;否則為CONFIG_HZ。而CONFIG_HZ是在內核的根目錄的.config文件中定義,并沒有在make menuconfig的配置選項中出現。Linux的\arch\arm\configs\s3c2410_defconfig文件中的定義為:
#
# Kernel Features
#
# CONFIG_PREEMPT is not set
# CONFIG_NO_IDLE_HZ is not set
CONFIG_HZ=200
# CONFIG_AEABI is not set
# CONFIG_ARCH_DISCONTIGMEM_ENABLE is not set
所以正常情況下s3c24x0的HZ為200。這一數值在后面的實驗中可以證實。
每次發生一個時鐘中斷,內核內部計數器的值就加一。這個計數器在系統啟動時初始化為 0, 因此它代表本次系統啟動以來的時鐘嘀噠數。這個計數器是一個 64-位 變量( 即便在 32-位的體系上)并且稱為 “jiffies_64”。但是驅動通常訪問 jiffies 變量(unsigned long)(根據體系結構的不同:可能是 jiffies_64 ,可能是jiffies_64 的低32位)。使用 jiffies 是首選,因為它訪問更快,且無需在所有的體系上實現原子地訪問 64-位的 jiffies_64 值。
使用 jiffies 計數器
這個計數器和用來讀取它的工具函數包含在 <linux/jiffies.h>, 通常只需包含 <linux/sched.h>,它會自動放入 jiffies.h 。 jiffies 和 jiffies_64 必須被當作只讀變量。當需要記錄當前 jiffies 值(被聲明為 volatile 避免編譯器優化內存讀)時,可以簡單地訪問這個 unsigned long 變量,如:
#include <linux/jiffies.h>
unsigned long j, stamp_1, stamp_half, stamp_n;
j = jiffies; /* read the current value */
stamp_1 = j + HZ; /* 1 second in the future */
stamp_half = j + HZ/2; /* half a second */
stamp_n = j + n * HZ / 1000; /* n milliseconds */
以下是一些簡單的工具宏及其定義:
#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)
用戶空間的時間表述法(struct timeval 和 struct timespec )與內核表述法的轉換函數:
#include <linux/time.h> /* #i nclude <linux/jiffies.h> --> \kernel\time.c*/
struct timespec {
time_t???? tv_sec; /* seconds */
long???? tv_nsec; /* nanoseconds */
};
#endif
struct timeval {
time_t???????? tv_sec; /* seconds */
???? suseconds_t???? tv_usec; /* microseconds */
};
unsigned long timespec_to_jiffies(struct timespec *value);
void jiffies_to_timespec(unsigned long jiffies, struct timespec *value);
unsigned long timeval_to_jiffies(struct timeval *value);
void jiffies_to_timeval(unsigned long jiffies, struct timeval *value);
訪問jiffies_64 對于 32-位 處理器不是原子的,這意味著如果這個變量在你正在讀取它們時被更新你可能讀到錯誤的值。若需要訪問jiffies_64,內核有一個特別的輔助函數,為你完成適當的鎖定:
#include <linux/jiffies.h>
u64 get_jiffies_64(void);
處理器特定的寄存器
若需測量非常短時間間隔或需非常高的精度,可以借助平臺依賴的資源。許多現代處理器包含一個隨時鐘周期不斷遞增的計數寄存器,他是進行高精度的時間管理任務唯一可靠的方法。最有名的計數器寄存器是 TSC ( timestamp counter), 在 x86 的 Pentium 處理器開始引入并在之后所有的 CPU 中出現(包括 x86_64 平臺)。它是一個 64-位 寄存器,計數 CPU 的時鐘周期,可從內核和用戶空間讀取。在包含了 <asm/msr.h> (一個 x86-特定的頭文件, 它的名子代表"machine-specific registers")的代碼中可使用這些宏:
rdtsc(low32,high32);/*原子地讀取 64-位TSC 值到 2 個 32-位 變量*/
rdtscl(low32);/*讀取TSC的低32位到一個 32-位 變量*/
rdtscll(var64);/*讀 64-位TSC 值到一個 long long 變量*/
/*下面的代碼行測量了指令自身的執行時間:*/
unsigned long ini, end;
rdtscl(ini); rdtscl(end);
printk("time lapse: %li\n", end - ini);
一些其他的平臺提供相似的功能, 并且內核頭文件提供一個體系無關的功能用來代替 rdtsc,稱 get_cycles(定義在 <asm/timex.h>( 由 <linux/timex.h> 包含)),原型如下:
#include <linux/timex.h>
cycles_t get_cycles(void);
/*這個函數在每個平臺都有定義, 但在沒有時鐘周期計數器的平臺上返回 0 */
/*由于s3c2410系列處理器上沒有時鐘周期計數器所以get_cycles定義如下:*/
typedef unsigned long cycles_t;
static inline cycles_t get_cycles (void)
{
return 0;
}
獲取當前時間
驅動一般無需知道時鐘時間(用年月日、小時、分鐘、秒來表達的時間),只對用戶程序才需要,如 cron 和 syslogd。 內核提供了一個將時鐘時間轉變為秒數值的函數:
unsigned long
mktime(const unsigned int year0, const unsigned int mon0,
const unsigned int day, const unsigned int hour,
const unsigned int min, const unsigned int sec)
{
unsigned int mon = mon0, year = year0;
/* 1..12 -> 11,12,1..10 */
if (0 >= (int) (mon -= 2)) {
???????? mon += 12; /* Puts Feb last since it has leap day */
???????? year -= 1;
}
return ((((unsigned long)
(year/4 - year/100 + year/400 + 367*mon/12 + day) +
???????? year*365 - 719499
)*24 + hour /* now have hours */
)*60 + min /* now have minutes */
)*60 + sec; /* finally seconds */
}
/*這個函數將時間轉換成從1970年1月1日0小時0分0秒到你輸入的時間所經過的秒數,溢出時間為2106-02-07 06:28:16。本人認為這個函數的使用應這樣:若你要計算2000-02-07 06:28:16 到2000-02-09 06:28:16 所經過的秒數:unsigned long time1 = mktime(2000,2,7,6,28,16)-mktime(2000,2,9,6,28,16); 若還要轉成jiffies,就再加上:unsigned long time2 = time1*HZ. 注意溢出的情況!*/
為了處理絕對時間, <linux/time.h> 導出了 do_gettimeofday 函數,它填充一個指向 struct timeval 的指針變量。絕對時間也可來自 xtime 變量,一個 struct timespec 值,為了原子地訪問它,內核提供了函數 current_kernel_time。它們的精確度由硬件決定,原型是:
#include <linux/time.h>
void do_gettimeofday(struct timeval *tv);
struct timespec current_kernel_time(void);
/*得到的數據都表示當前時間距UNIX時間基準1970-01-01 00:00:00的相對時間*/
以上兩個函數在ARM平臺都是通過 xtime 變量得到數據的。
全局變量xtime:它是一個timeval結構類型的變量,用來表示當前時間距UNIX時間基準1970-01-01 00:00:00的相對秒數值。
結構timeval是Linux內核表示時間的一種格式(Linux內核對時間的表示有多種格式,每種格式都有不同的時間精度),其時間精度是微秒。該結構是內核表示時間時最常用的一種格式,它定義在頭文件include/linux/time.h中,如下所示:
struct timeval {
time_t tv_sec; /* seconds */
suseconds_t tv_usec; /* microseconds */
};
其中,成員tv_sec表示當前時間距UNIX時間基準的秒數值,而成員tv_usec則表示一秒之內的微秒值,且1000000>tv_usec>=0。
Linux內核通過timeval結構類型的全局變量xtime來維持當前時間,該變量定義在kernel/timer.c文件中,如下所示:
/* The current time */
volatile struct timeval xtime __attribute__ ((aligned (16)));
但是,全局變量xtime所維持的當前時間通常是供用戶來檢索和設置的,而其他內核模塊通常很少使用它(其他內核模塊用得最多的是jiffies),因此對xtime的更新并不是一項緊迫的任務,所以這一工作通常被延遲到時鐘中斷的底半部(bottom half)中來進行。由于bottom half的執行時間帶有不確定性,因此為了記住內核上一次更新xtime是什么時候,Linux內核定義了一個類似于jiffies的全局變量wall_jiffies,來保存內核上一次更新xtime時的jiffies值。時鐘中斷的底半部分每一次更新xtime的時侯都會將wall_jiffies更新為當時的jiffies值。全局變量wall_jiffies定義在kernel/timer.c文件中:
/* jiffies at the most recent update of wall time */
unsigned long wall_jiffies;
總結
以上是生活随笔為你收集整理的内核中的HZ 及延迟等的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: 解决mysql“Access denie
- 下一篇: Windows7体验8G内存 用上真正的