轉自:https://blog.csdn.net/Roland_Sun/article/details/105564672
幾乎所有的計算機系統中都會存在一個所謂的定時設備,經過設置后,在某個固定的時間或某個相對的時間間隔后,達到觸發條件,發送中斷給處理器。
系統中的每一種實際的定時事件設備都由一個叫做clock_event_device的結構體變量表示(代碼位於include/linux/clockchips.h):
struct clock_event_device {
void (*event_handler)(struct clock_event_device *);
int (*set_next_event)(unsigned long evt, struct clock_event_device *);
int (*set_next_ktime)(ktime_t expires, struct clock_event_device *);
ktime_t next_event;
u64 max_delta_ns;
u64 min_delta_ns;
u32 mult;
u32 shift;
enum clock_event_state state_use_accessors;
unsigned int features;
unsigned long retries;
int (*set_state_periodic)(struct clock_event_device *);
int (*set_state_oneshot)(struct clock_event_device *);
int (*set_state_oneshot_stopped)(struct clock_event_device *);
int (*set_state_shutdown)(struct clock_event_device *);
int (*tick_resume)(struct clock_event_device *);
void (*broadcast)(const struct cpumask *mask);
void (*suspend)(struct clock_event_device *);
void (*resume)(struct clock_event_device *);
unsigned long min_delta_ticks;
unsigned long max_delta_ticks;
const char *name;
int rating;
int irq;
int bound_on;
const struct cpumask *cpumask;
struct list_head list;
struct module *owner;
} ____cacheline_aligned;
這個結構體是“____cacheline_aligned”的,表明其是緩存行對齊的,頻繁訪問的話可以加快速度。
熟悉面向對象編程的一定覺得對這個結構體的定義非常熟悉,除了變量外還定義了一堆函數指針,並且每個函數的第一個參數是一個指向自己的指針。所以,每一個所謂的定時事件設備都是一個對象實例。
下面來說結構體中這些字段的具體意思:
event_handler:這是一個回調函數,當定時器觸發條件滿足后,會發送中斷給處理器,對應的中斷處理程序在執行的時候會調用這個函數。
set_next_event:設置下一次觸發的時間,用經過了多少個時鍾源的周期數作為參數。
set_next_ktime:同樣是設置下一次觸發的時間,直接使用ktime時間作為參數。
next_event:該定時事件設備的下一次到期絕對時間,用ktime表示。
max_delta_ns:表示當前定時事件設備能分辨的最大定時時間間隔,以納秒數表示。系統中的時鍾源計數器一般都有一個最大計數值,超過這個值后就會回滾了,這也就是單次定時能設定的最大時間間隔。假如系統時鍾源計數超過10分鍾就會越界回滾,如果定時在10分鍾內,那沒關系,即使會越界系統回滾后也可以正確定時。而如果定時超過10分鍾,那系統就無法區分到底是越界之前的值是對了還是越界之后的值是對的。所以,超過這個定時間隔系統一定會出錯。這個值可以和max_delta_ticks通過mult和shift互相轉換。
min_delta_ns:表示當前定時事件設備能分辨的最小定時時間間隔,以納秒數表示。系統中的時鍾源一般都有一個最小分辨率,如果時鍾源以10MHz運行,那么其最小的定時時間間隔肯定要大於100納秒,小於這個定時間隔在這個系統上是無法實現的。這個值可以和min_delta_ticks通過mult和shift互相轉換。
mult和shift:系統中都會有一個時鍾源(Clock Source),有的系統會將其稱作計數器,它會按照一個固定的頻率周期工作,不停的累加。注意區分時鍾源和本文說的定時器,時鍾源只是自顧自的累加,頻率很高,讓系統“感知”時間的流逝,它不會觸發中斷,計數器的值是CPU自己主動讀取的;而定時器是會觸發中斷的,而且其定時間隔肯定比時鍾源的周期間隔要大。內核可以通過不同渠道知道時鍾源的頻率(Frequency),也可以通過比較現在的時鍾計數器數值和上一次時鍾計數器數值獲得已經過去了多少個周期(Cycle),有了這兩個參數就可以知道過去了多少秒了(Cycle / Frequency)。但是,內核是沒有浮點運算單元的,因此,只能通過整數運算進行模擬。mult表示乘數,shift表示位移多少位。這樣,拿到了計數器的值后,先用shift左移位,然后再整數除以mult之后,就可以算出過了多少納秒((Cycle << shift) / mult)。這兩個值是需要精心計算了,如果太大了會造成溢出,如果太小了,會造成精度不夠。關於如何計算這兩個值,以及如何將周期數轉換成納秒數在后面章節中有介紹。
state_use_accessors:表示當前定時事件設備所處的狀態,是一個枚舉變量,一共有五種。CLOCK_EVT_STATE_DETACHED,表示這個設備目前沒有被內核事件子系統使用,也是設備的初始狀態;CLOCK_EVT_STATE_SHUTDOWN,表示該設備已經被關閉了;CLOCK_EVT_STATE_PERIODIC,表示這個設備一旦設置完成后就可以產生周期性事件,一般都是低精度的設備;CLOCK_EVT_STATE_ONESHOT,表示該設備只能產生單次觸發的時鍾事件,一般都是高精度設備;CLOCK_EVT_STATE_ONESHOT_STOPPED,表示該設備是單次觸發設備,但是已經被停止了(代碼位於include/linux/clockchips.h)。
enum clock_event_state {
CLOCK_EVT_STATE_DETACHED,
CLOCK_EVT_STATE_SHUTDOWN,
CLOCK_EVT_STATE_PERIODIC,
CLOCK_EVT_STATE_ONESHOT,
CLOCK_EVT_STATE_ONESHOT_STOPPED,
};
features:表示這個定時事件設備支持的功能特性。例如,如果是周期設備那就要包含CLOCK_EVT_FEAT_PERIODIC。不像前面說的clock_event_state是排它的,這個字段是按位與的,可以包含多個。但有些也是互斥的,比如CLOCK_EVT_FEAT_PERIODIC(表示該設備可以支持周期觸發)和CLOCK_EVT_FEAT_ONESHOT(表示該設備只支持單次觸發)一般不會在一起出現。CLOCK_EVT_FEAT_KTIME表示該設備只支持以ktime絕對時間定時,只能調用set_next_ktime函數。CLOCK_EVT_FEAT_C3STOP表示該定時事件設備支持C3_STOP工作模式,在對應的CPU進入空閑狀態后,有可能會被關閉。CLOCK_EVT_FEAT_PERCPU表示該定時事件設備是某個CPU私有的。CLOCK_EVT_FEAT_DYNIRQ表示該定時事件設備可以設定CPU親緣性,也就是可以指定到期后觸發某個特定CPU的中斷。CLOCK_EVT_FEAT_DUMMY表示這個定時事件設備是一個“假”的占位設備。CLOCK_EVT_FEAT_HRTIMER表示該定時事件設備實際上是有高分辨率定時器模擬出來的。
# define CLOCK_EVT_FEAT_PERIODIC 0x000001
# define CLOCK_EVT_FEAT_ONESHOT 0x000002
# define CLOCK_EVT_FEAT_KTIME 0x000004
# define CLOCK_EVT_FEAT_C3STOP 0x000008
# define CLOCK_EVT_FEAT_DUMMY 0x000010
# define CLOCK_EVT_FEAT_DYNIRQ 0x000020
# define CLOCK_EVT_FEAT_PERCPU 0x000040
# define CLOCK_EVT_FEAT_HRTIMER 0x000080
retries:重試次數(在clockevents_program_min_delta函數中使用)。
set_state_periodic:當定時事件設備將要被切換到周期觸發狀態(也就是CLOCK_EVT_STATE_PERIODIC)時,時間子系統會調用這個函數。
set_state_oneshot:當定時事件設備將要被切換到單次觸發狀態(也就是CLOCK_EVT_STATE_ONESHOT)時,時間子系統會調用這個函數。
set_state_oneshot_stopped:當定時事件設備將要被切換到單次觸發停止狀態(也就是CLOCK_EVT_STATE_ONESHOT_STOPPED)時,時間子系統會調用這個函數。
set_state_shutdown:當定時事件設備將要被切換到關閉狀態(也就是CLOCK_EVT_STATE_SHUTDOWN)時,時間子系統會調用這個函數。
tick_resume:當一個tick設備恢復的時候,會調用對應的定時事件設備的該函數。
broadcast:發送廣播事件的函數。
suspend:當要暫停定時事件設備時,會調用對應設備的該函數。
resume:當要恢復定時事件設備時,會調用對應設備的該函數。
min_delta_ticks:表示當前定時事件設備能分辨的最小定時時間間隔,以時鍾源設備的周期數表示,肯定是一個大於1的值。
max_delta_ticks:表示當前定時事件設備能分辨的最大定時時間間隔,以時鍾源設備的周期數表示,肯定不能大於時鍾源設備的最大計數器值。
name:是給這個定時事件設備起的一個名字,一般比較直觀,在/proc/timer_list中或者dmesg中都會出現。
rating:代表這個定時事件設備的精度值,其取值范圍從1到499,數字越大代表設備的精度越高。當系統中同時有多個定時事件設備存在的時候,內核可以根據這個值選一個最佳的設備。
irq:指定了該定時事件設備使用的中斷號。
bound_on:綁定的CPU,主要在Tick廣播層使用。
cpumask:指定了這個定時事件設備所服務的CPU號,系統中高精度定時事件設備一般都是每個CPU核私有的。
list:系統中所有的定時事件設備實例都會保存在全局鏈表clockevent_devices中,這個變量作為鏈表的元素(代碼位於kernel/time/clockevents.c)。
static LIST_HEAD(clockevent_devices);
owner:擁有這個定時事件設備的模塊。
有了前面的知識准備了之后,下面我們分幾個方面來解釋一下時鍾事件(Clock Events)層的工作過程。
1)mult、shift的計算和周期數到納秒的轉換
mult和shift值的計算主要是在函數clocks_calc_mult_shift中,和時鍾源(Clock Source)中對應的值計算方式是一樣的(代碼位於kernel/time/clocksource.c中):
void
clocks_calc_mult_shift(u32 *mult, u32 *shift, u32 from, u32 to, u32 maxsec)
{
u64 tmp;
u32 sft, sftacc= 32;
/* 計算最大納秒數前面有多少個0 */
tmp = ((u64)maxsec * from) >> 32;
while (tmp) {
tmp >>=1;
sftacc--;
}
/* 試探計算mult和shift的最大值 */
for (sft = 32; sft > 0; sft--) {
/* 左移sft位 */
tmp = (u64) to << sft;
/* 四舍五入 */
tmp += from / 2;
do_div(tmp, from);
/* 判斷是否會越界 */
if ((tmp >> sftacc) == 0)
break;
}
*mult = tmp;
*shift = sft;
}
EXPORT_SYMBOL_GPL(clocks_calc_mult_shift);
對於定時時間設備來說,其主要需要計算時鍾周期數到納秒數的轉換,所以雖然和時鍾源計算mult和shift調用的函數是一樣的,但參數並不同。這里,from設置成了NSEC_PER_SEC(1000000000L),即每秒多少納秒數,而to設置成了時鍾源的頻率,maxsec表示最大能轉換的秒數。轉換后要滿足等式:
也就是時鍾源周期數除以to(頻率)要等於納秒數除以from(1000000000L)。而時鍾源周期數用shift和mult轉換成納秒數的公式基本為:
兩個公式結合一下就可以得到:
從公式中可以看出來,當然mult和shift越大越好,計算的精度損失越小。但是,整數運算位數是有限制的,對於64位系統來說只有64位的長度。所以,shift和mult就不能太大,否者計算的過程中就可能越界。這時候,maxsec就有用處了,它用來限制最大能轉換的秒數,那么maxsec * from(NSEC_PER_SEC)就表示能轉換的最大納秒數,而通過前面的公式變換得到:
那么mult的位數一定要比這個最大數前面0的位數要多,否則就會越界。有點拗口,舉個例子,假如最大表示的納秒數有40位,那么如果mult超過24位的話,那以上等式兩邊的數值就會超過64位,也就意味着轉換最大時鍾源周期數到最大納秒數時,肯定會越界。
do_div是一個宏定義(代碼位於include/asm-generic/div64.h):
# define do_div(n,base) ({ \
uint32_t __base = (base); \
uint32_t __rem; \
__rem = ((uint64_t)(n)) % __base; \
(n) = ((uint64_t)(n)) / __base; \
__rem; \
})
可以看出其主要的功能是將第一個參數整數除第二個參數后再賦值給第一個參數。
將周期數轉換成納秒數主要在函數cev_delta2ns函數中實現(代碼位於kernel/time/clockevents.c):
static u64 cev_delta2ns(unsigned long latch, struct clock_event_device *evt,
bool ismax)
{
u64 clc = (u64) latch << evt->shift;
u64 rnd;
if (WARN_ON(!evt->mult))
evt->mult = 1;
rnd = (u64) evt->mult - 1;
/* 判斷是否越界,如果越界則將clc設置成最大值。 */
if ((clc >> evt->shift) != (u64)latch)
clc = ~0ULL;
if ((~0ULL - clc > rnd) &&
(!ismax || evt->mult <= (1ULL << evt->shift)))
clc += rnd;
do_div(clc, evt->mult);
return clc > 1000 ? clc : 1000;
}
latch表示經過的周期數,ismax表示latch傳入的周期數是不是能表示的最大的那個值。可以看出來,基本是按照上面的計算公式轉換的,中間加了一些越界檢查。do_dive(clc, evt->mult)實際等價於clc = clc / evt->mult。最后,如果如果除出來的數小於等於1000的話,也就是等於1000納秒或1毫秒,可以認為是噪聲,強制返回1000。
2)更換當前定時事件設備
當有新的定時事件設備加入內核后,有可能會切換當前tick設備使用的定時事件設備,這是在函數clockevents_exchange_device中實現的:
void clockevents_exchange_device(struct clock_event_device *old,
struct clock_event_device *new)
{
if (old) {
module_put(old->owner);
/* 將被替換的老設備設置到CLOCK_EVT_STATE_DETACHED狀態 */
clockevents_switch_state(old, CLOCK_EVT_STATE_DETACHED);
/* 將被替換的老設備從clockevent_devices全局鏈表中刪除 */
list_del(&old->list);
/* 將被替換的老設備加入到clockevents_released全局鏈表中 */
list_add(&old->list, &clockevents_released);
}
if (new) {
/* 替換的新設備必須處於CLOCK_EVT_STATE_DETACHED狀態 */
BUG_ON(!clockevent_state_detached(new));
/* 將替換的新設備關閉 */
clockevents_shutdown(new);
}
}
值得注意的是,這個函數是在本地中斷關閉並且獲得自旋鎖的情況下調用的。功能其實很簡單,主要就是把被替換的老設備從原有的clockevent_devices全局鏈表中刪除,並加入clockevents_released全局鏈表中,於此同時,把新替換的設備加入clockevent_devices全局鏈表中,當然還要更新設備的狀態。新加入的設備的初始狀態必須是CLOCK_EVT_STATE_DETACHED。
3)定時事件設備的注冊
如果驅動程序發現了系統中的一個新的定時事件設備,它將會構造一個clock_event_device結構體數據,相應的填寫好結構體內的各個字段,然后向時間子系統注冊。注冊的函數是clockevents_config_and_register或clockevents_register_device。
CLOCK_EVT_STATE_DETACHEDclockevents_config_and_register根據參數,對clock_event_device進行設置后,還是直接調用clockevents_register_device函數(代碼位於kernel/time/clockevents.c):
void clockevents_config_and_register(struct clock_event_device *dev,
u32 freq, unsigned long min_delta,
unsigned long max_delta)
{
dev->min_delta_ticks = min_delta;
dev->max_delta_ticks = max_delta;
clockevents_config(dev, freq);
clockevents_register_device(dev);
}
函數clockevents_config主要用來設置對應的mult和shift的值:
static void clockevents_config(struct clock_event_device *dev, u32 freq)
{
u64 sec;
/* 如果不是單觸發的定時時間設備則直接返回 */
if (!(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return;
/* 根據max_delta_ticks計算定時事件設備支持的最大秒數 */
sec = dev->max_delta_ticks;
do_div(sec, freq);
if (!sec)
sec = 1;
else if (sec > 600 && dev->max_delta_ticks > UINT_MAX)
sec = 600;
/* 根據頻率和最大秒數計算並更新mult和shift的值 */
clockevents_calc_mult_shift(dev, freq, sec);
/* 根據min_delta_ticks計算min_delta_ns */
dev->min_delta_ns = cev_delta2ns(dev->min_delta_ticks, dev, false);
/* 根據max_delta_ticks計算max_delta_ns */
dev->max_delta_ns = cev_delta2ns(dev->max_delta_ticks, dev, true);
}
在調用了clockevents_config后就馬上調用clockevents_register_device了:
void clockevents_register_device(struct clock_event_device *dev)
{
unsigned long flags;
/* 將待注冊設備的狀態設置成CLOCK_EVT_STATE_DETACHED */
clockevent_set_state(dev, CLOCK_EVT_STATE_DETACHED);
/* 檢查並修正該設備的cpumask */
if (!dev->cpumask) {
WARN_ON(num_possible_cpus() > 1);
dev->cpumask = cpumask_of(smp_processor_id());
}
if (dev->cpumask == cpu_all_mask) {
WARN(1, "%s cpumask == cpu_all_mask, using cpu_possible_mask instead\n",
dev->name);
dev->cpumask = cpu_possible_mask;
}
/* 持有自旋鎖並關本地中斷 */
raw_spin_lock_irqsave(&clockevents_lock, flags);
/* 將本定時事件設備加入全局鏈表 */
list_add(&dev->list, &clockevent_devices);
/* 檢查該定時事件設備是否可以替換原設備成為新的tick設備 */
tick_check_new_device(dev);
clockevents_notify_released();
/* 釋放自旋鎖並打開本地中斷 */
raw_spin_unlock_irqrestore(&clockevents_lock, flags);
}
EXPORT_SYMBOL_GPL(clockevents_register_device);
函數會對傳入設備的cpumask變量進行修正。如果cpumask沒有設置,這會將其設置成當前正在運行程序的這個CPU,即將這個設備占為己有了,並且系統中不止一個CPU的話還會報警告。
tick_check_new_device是一個tick設備層提供的函數,如果有新的定時事件設備加入內核,則可以將新加的這個設備和原有的設備進行比較,看哪個更適合作為tick設備層的驅動設備。如果新設備更時候的話,tick設備層會調用前面分析的clockevents_exchange_device函數。接着,在從tick設備層返回后,會調用clockevents_notify_released函數:
static void clockevents_notify_released(void)
{
struct clock_event_device *dev;
/* 循環遍歷全局變量clockevents_released中的所有鏈表元素 */
while (!list_empty(&clockevents_released)) {
dev = list_entry(clockevents_released.next,
struct clock_event_device, list);
/* 將該設備從clockevents_released鏈表中刪除 */
list_del(&dev->list);
/* 將該設備重新加入clockevent_devices全局鏈表中 */
list_add(&dev->list, &clockevent_devices);
/* 檢查該設備是否可以替換當前的設備成為tick設備 */
tick_check_new_device(dev);
}
}
這個函數會遍歷前一步添加到clockevents_released全局鏈表中的所有設備(在注冊的過程中實際只會添加一個,也就是被替換的設備),將其從clockevents_released中刪除並重新添加回clockevent_devices全局鏈表中,再檢查一下這個設備是否是更好的tick設備(在這個場景中,被替換的設備肯定不如替換的新設備,所以其實這個調用應該不起作用)。
4)對定時事件設備重編程
當定時事件設備的狀態有變化時,比如頻率變動了,或者當定時到期且,需要設置下一次定時事件的時候,都有可能會對定時事件設備重新進行編程。如果頻率變化了,那同樣的納秒數轉換成的周期數就肯定會改變,當然需要重新計算編程。而定時事件到期后,且定時事件設備是單觸發模式的,如果不對其再編程,那這個設備將不會再產生任何定時中斷。
對定時事件設備重編程是在函數clockevents_program_event中完成的:
int clockevents_program_event(struct clock_event_device *dev, ktime_t expires,
bool force)
{
unsigned long long clc;
int64_t delta;
int rc;
if (WARN_ON_ONCE(expires < 0))
return -ETIME;
/* 儲存下一次定時到期時間 */
dev->next_event = expires;
/* 先關閉該設備 */
if (clockevent_state_shutdown(dev))
return 0;
/* 定時事件設備必須處於CLOCK_EVT_STATE_ONESHOT狀態 */
WARN_ONCE(!clockevent_state_oneshot(dev), "Current state: %d\n",
clockevent_get_state(dev));
/* 如果這個設備要用絕對到期時間設置 */
if (dev->features & CLOCK_EVT_FEAT_KTIME)
return dev->set_next_ktime(expires, dev);
/* 計算到期時間和當前時間之間差多少納秒 */
delta = ktime_to_ns(ktime_sub(expires, ktime_get()));
/* 如果當前時間已經超過到期時間了 */
if (delta <= 0)
return force ? clockevents_program_min_delta(dev) : -ETIME;
/* 設置的時間間隔必須大於min_delta_ns且小於max_delta_ns */
delta = min(delta, (int64_t) dev->max_delta_ns);
delta = max(delta, (int64_t) dev->min_delta_ns);
/* 將納秒值轉成時鍾源周期數 */
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
rc = dev->set_next_event((unsigned long) clc, dev);
return (rc && force) ? clockevents_program_min_delta(dev) : rc;
}
這個函數有三個參數,dev表示要重新編程的定時事件設備;expires表示要設定的下一次到期時間,以ktime表示;force表示如果這個定時事件設置出了問題,是不是需要嘗試用最小的時間間隔設定該設備。可以看到,如果當前時間已經超過了要設定的到期時間,或者在調用set_next_event出錯時,且force是真的情況下,還會嘗試調用clockevents_program_min_delta設置一個最小的到期事件,否則直接返回錯誤。
static int clockevents_program_min_delta(struct clock_event_device *dev)
{
unsigned long long clc;
int64_t delta = 0;
int i;
/* 共嘗試10次 */
for (i = 0; i < 10; i++) {
/* 每次加上定時事件設備允許的最小事件間隔 */
delta += dev->min_delta_ns;
dev->next_event = ktime_add_ns(ktime_get(), delta);
if (clockevent_state_shutdown(dev))
return 0;
dev->retries++;
/* 將納秒數轉換為時鍾周期數 */
clc = ((unsigned long long) delta * dev->mult) >> dev->shift;
if (dev->set_next_event((unsigned long) clc, dev) == 0)
return 0;
}
return -ETIME;
}
這個函數非常簡單,共嘗試10次設置下一次到期事件,每次將間隔遞增min_delta_ns,直到成功為止。如果10次都不成功,則返回錯誤碼退出。設備結構體中的retries變量在這里記錄嘗試了多少次。
5)注冊sysfs
定時事件設備會在sysfs中注冊對應的文件,可以通過訪問這些文件的內容知道當前系統中關於定時事件設備的基本信息。
注冊sysfs是在clockevents_init_sysfs函數中完成的:
static int __init clockevents_init_sysfs(void)
{
/* 注冊子系統 */
int err = subsys_system_register(&clockevents_subsys, NULL);
if (!err)
err = tick_init_sysfs();
return err;
}
device_initcall(clockevents_init_sysfs);
subsys_system_register函數會根據參數將一個子系統注冊在/sys/devices/system/目錄下。
注冊信息保存在clockevents_subsys靜態全局變量中:
static struct bus_type clockevents_subsys = {
.name = "clockevents",
.dev_name = "clockevent",
};
所以總線名字叫做“clockevents”,而設備名字叫做“clockevent”。
注冊完子系統后,如果沒問題,會接着調用tick_init_sysfs函數:
static int __init tick_init_sysfs(void)
{
int cpu;
/* 遍歷系統中的每個CPU */
for_each_possible_cpu(cpu) {
/* 讀取每CPU變量tick_percpu_dev */
struct device *dev = &per_cpu(tick_percpu_dev, cpu);
int err;
/* 填寫要注冊的設備信息 */
dev->id = cpu;
dev->bus = &clockevents_subsys;
/* 注冊設備 */
err = device_register(dev);
if (!err)
/* 在設備目錄下創建current_device文件 */
err = device_create_file(dev, &dev_attr_current_device);
if (!err)
/* 在設備目錄下創建unbind_device文件 */
err = device_create_file(dev, &dev_attr_unbind_device);
if (err)
return err;
}
return tick_broadcast_init_sysfs();
}
經過這些函數的注冊后,將會在/sys/devices/system/clockevents/目錄下創建多個目錄,系統中有幾個CPU(包含超線程)就會創建幾個目錄,例如筆者的筆記本是4核8線程的,就會創建clockevent0到clockevent7,共8個目錄。每個目錄下會創建兩個文件,分別是current_device和unbind_device。以current_device為例,其文件屬性定義為:
static ssize_t sysfs_show_current_tick_dev(struct device *dev,
struct device_attribute *attr,
char *buf)
{
struct tick_device *td;
ssize_t count = 0;
/* 獲得自旋鎖並關閉本地中斷 */
raw_spin_lock_irq(&clockevents_lock);
/* 獲得當前tick設備所使用的定時事件設備 */
td = tick_get_tick_dev(dev);
if (td && td->evtdev)
/* 輸出定時事件設備的名字 */
count = snprintf(buf, PAGE_SIZE, "%s\n", td->evtdev->name);
/* 釋放自旋鎖並打開本地中斷 */
raw_spin_unlock_irq(&clockevents_lock);
return count;
}
/* 申明了dev_attr_current_device全局變量 */
static DEVICE_ATTR(current_device, 0444, sysfs_show_current_tick_dev, NULL);
所以,訪問了對應目錄下的current_device文件,其內容將是對應CPU所使用的定時事件設備的名字。
例如,在64位樹莓派4系統下,訪問/sys/devices/system/clockevents/clockevent3/current_device將會返回arch_sys_timer,表明其當前使用的是Arm通用計時器。
————————————————
版權聲明:本文為CSDN博主「Roland_Sun」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/Roland_Sun/article/details/105564672