1. 程式人生 > >Linux時間子系統(十四) tick broadcast framework

Linux時間子系統(十四) tick broadcast framework

sub 之間 中斷處理 blog oca cast sso truct first

一、前言

在內核中,有cpuidle framework可以控制cpu的節電:當沒有進程調度到該cpu上執行的時候,swapper進程粉墨登場,將該cpu會被推入到idle狀態。當然CPU的idle狀態有深有淺,當CPU睡的比較深入的時候,有可能會關閉本地的timer硬件。這樣就會引入一個很有意思的問題:local timer將無法喚醒CPU,該cpu上的所有的software timer將無法喚醒cpu。tick broadcast framework就是用來解決這個問題的。

本文中的代碼來自linux kernel 4.0。

二、工作原理

在ARM的multi core的架構下,每個cpu core都有可能調度到idle進程(id=0的那個進程,又叫做swapper進程)從而使得cpu core進入idle狀態,如果這時候該cpu core的local timer由於cpu core進入idle而被停掉,那麽該cpu core上的所有的軟件timer將不能正常運作,為了維持software timer的功能,我們可以考慮下面的方法:

(1)使用一個系統級別的HW timer(和cpuidle事件獨立,不受其影響,always-on的,不屬於任何一個CPU,如果使用GIC的話,那麽該SOC timer的中斷會使用SPI類型的中斷,local timer都是使用PPI中斷)來通知cpu醒來,處理software timer。

(2)如果沒有系統級別的HW timer,那麽可以考慮選擇某個CPU CORE的local timer作為喚醒源(通過IPI喚醒其他處於idle狀態的CPU core)。當然,這樣選擇會導致該cpu無法進入idle mode,從而影響了power saving。

1、在硬件提供系統級別的HW timer的情況下,tick broadcast如何運作?

我們假設系統的HW timer包括:ARM generic timer和一個SOC的HW timer。系統初始化的時候,這些driver都會初始化並註冊clockevent device,毫無疑問,各個cpu core的local timer會建立一個clockevent device並成為該cpu core的per cpu tick device。SOC的HW timer對應的clockevent device當然被選擇作為broadcast tick設備。在系統初始化的過程中,per cpu tick device會正常運作,而broadcast tick設備則不會,是否開始正常運作是看來自cpuidle framework的消息(其他cpu進入idle狀態才會申請broadcast tick的服務)。

cpuidle core文檔中說到:cpuidle state中有一個叫做“CPUIDLE_FLAG_TIMER_STOP”的flag。當cpuidle driver的idle state中(可能會支持多個state)有一個state設置了這個flag時,就啟動setup broadcast tick過程。 CPUIDLE_FLAG_TIMER_STOP說明CPU core在進入該idle state時,會停掉該CPU的local timer,這時候local timer停止運作,無法驅動software timer,需要broadcast tick device的協助。

因此,在cpuidle framework初始化的時候,會根據情況,在每個cpu core上執行發送CLOCK_EVT_NOTIFY_BROADCAST_ON message到時間子系統的clock event模塊,通過這樣的消息,告知broadcast tick模塊,該cpu需要broadcast tick模塊的服務。當然,這時候並不需要立刻啟動向該cpu 發送tick的過程,畢竟這只是通知broadcast tick模塊有客戶需要它而已。在某CPU Core真正要進入一個標記有“CPUIDLE_FLAG_TIMER_STOP”的flag的cpuidle state的時候,會發送CLOCK_EVT_NOTIFY_BROADCAST_ENTER消息,告知broadcast tick模塊:我的local timer要掛掉了,請求broadcast tick支援。

OK,上面只是描述了來自cpuidle framework的一些請求,但是tick broadcast framework如何處理呢?我們用下面的block diagram來描述:

技術分享圖片

我們先看看來自各個cpu的請求。實際上來自cpu的請求可以這樣表述:我馬上就要idle了,local timer要掛掉,請在A時間後喚醒我,當然,這裏的A時間就是該cpu的timer list中最近的那個。同樣的,其他的cpu也會進入idle狀態,也會申請B時間,C時間…。broadcast tick就一個,只能是設定一個trigger的next event時間點,它考量的因素包括:

(1)broadcast tick當前設定的next event的觸發時間點

(2)各個cpu申請的時間點

我們假設當前時間點是X秒,broadcast tick當前設定的next event的觸發時間點是X+5秒,如果cpu申請的時間晚於當前設定的時間,例如X+7秒,那麽什麽也不需要做,保持當前的設定,如果cpu申請的時間早於當前設定的時間,例如X+3秒,那麽broadcast tick會立刻修改next event的觸發時間點為X+3秒,以便滿足3秒後喚醒該cpu的需求(當然,這時候一定要設定irq affinity,將該全局timer的中斷定向到該CPU CORE)。根據上面的描述,似乎兩個場景中總有一個被忽略,要麽是cpu的申請(cpu申請的時間晚於當前設定的時間),要麽是上一次某個cpu的申請(cpu申請的時間早於當前設定的時間)。實際上,tick broadcast framework不會忽略每個客戶的請求的,那些會在broadcast tick event處理函數中遍歷申請服務cpu的next event,找到最近要觸發的那個時間點進行設定。

一旦broadcast tick 到來了,系統如何處理呢?主要進行兩件事:

(1)scan所有的申請中的那些cpu的timer list,找最近要觸發的那個timer的超期時間並設定到broadcast tick 對應的那個全局HW timer中。

(2)針對每一個申請服務的cpu,將tick事件廣播到該cpu去。需要註意的是:並不是僅僅將event送達一個cpu(該cpu的next event被設定到broadcast tick中),而是遍歷所有申請服務的cpu,比對其next event和當前時間,以便確定是否要廣播tick事件到該cpu。因為如果兩個cpu的next event很近,一次性處理多個cpu的tick廣播是更合理的選擇。

2、hrtimer based tick broadcast如何運作?

理想的狀態當然是上節中描述的那樣,然而現實中總有不如意的地方,如果沒有系統級別的HW timer腫麽辦?你可能會覺得:那就不用吧,有什麽了不起,不就是功耗有點損失嘛(cpu無法進入深層次的idle),我忍。不過實際上沒有那麽簡單,我們一起來看看這種情況下per cpu tick的運作。我們都知道,在系統初始化過程中,基於local timer的per cpu tick總是從periodic mode開始工作,然後在softirq中周期性的檢測是否切換到one shot mode(以便可以真正實現高精度timer),這個檢查是tick_check_oneshot_change函數實現的,其中會調用tick_is_oneshot_available函數來檢查per cpu的tick device是否適合進入one shot mode:

int tick_is_oneshot_available(void)
{
struct clock_event_device *dev = __this_cpu_read(tick_cpu_device.evtdev);

if (!dev || !(dev->features & CLOCK_EVT_FEAT_ONESHOT))
return 0;
if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))
return 1;
return tick_broadcast_oneshot_available();
}

在我們這個場景下,local timer對應的clock event device的flag應該是有one shot的能力,但是有C3STOP的特點,這時候,是否切入one shot模式完全依賴broadcast tick device是否有one shot的能力。如果你因為沒有全局的HW timer而放棄構建broadcast tick設備,那麽tick_is_oneshot_available函數無情的返回了false,從而阻止了各個cpu上的tick device進入one shot mode。

因此,有必要構建一個基於軟件timer的broadcast tick device,這也就是傳說中的hrtimer based tick broadcast,在linux/kernel/time/tick-broadcast-hrtimer.c文件中實現。本質上,這種方法是讓其中一個cpu不進入idle,讓該cpu上的一個hrtimer(基於local timer的)來模擬了全局的HW timer的硬件。當然這種方法也是有壞處的:

(1)local timer無法直接將中斷送達指定CPU CORE,只能通過IPI的中斷

(2)在支持cpu hotplug的情況下,broadcast tick device有可能需要切換cpu,引入軟件復雜性

Anyway,有總比沒有強,犧牲了一個cpu(不能idle),幸福了其他cpu(可以進入idle)。具體hrtimer based tick broadcast的代碼邏輯(linux/kernel/time/tick-broadcast-hrtimer.c)不會在本文中描述,大家自行修煉吧。

三、數據結構

1、抽象broadcast tick device

本質上broadcast tick device也是tick device,只不過有些特殊的功能和要求,在kernel中,下面的全局變量表示了系統中的broadcast tick device:

static struct tick_device tick_broadcast_device;

隨著系統的初始化(或者支持hotplug HW timer插入系統),會有各種clock event device註冊到系統中,tick broadcast framework會選擇合適的clock event device作為向各個cpu core廣播tick的那個tick device。

2、保存各個CPU CORE信息的數據

當系統處於周期性tick工作模式下(每個per cpu tick device都處於周期性tick工作模式下),各個cpu的請求狀態數據會保存在若幹個cpumask_var_t類型的變量中,定義如下:

static cpumask_var_t tick_broadcast_mask;
static cpumask_var_t tick_broadcast_on;
static cpumask_var_t tmpmask;-----中間變量,後面的代碼解析中就自然明確了

tick_broadcast_mask中的每個bit標識該cpu core是否需要broadcast tick device的tick廣播服務,如果需要,對應的bit被置位。tick_broadcast_on這個變量和per cpu tick device中的具體的HW timer特性無關,是收集來自cpuidle framework的信息,也就是說來自cpu core的信息。如果cpu core發現自己在進入某些cpuidle state的時候會關閉local timer,那麽就會設定相應的bit。

其實,在舊的kernel中,只有一個tick_broadcast_mask的變量,然而引入dummy timer的時候,導致了tick broadcast framework在某個特定的場景下會有issue,因此tick broadcast framework增加了控制邏輯(多了tick_broadcast_on這個變量),以便讓系統在任何情況下都可以正常工作。了解更詳細的信息,請參考https://lkml.org/lkml/2013/7/2/272

如果配置了CONFIG_TICK_ONESHOT的話,還有一波cpu mask類型的變量會被定義:

static cpumask_var_t tick_broadcast_oneshot_mask;
static cpumask_var_t tick_broadcast_pending_mask;
static cpumask_var_t tick_broadcast_force_mask;

tick_broadcast_oneshot_mask和tick_broadcast_mask類似,不同的是tick_broadcast_mask用在周期性tick的場景下,而tick_broadcast_oneshot_mask用於one shot mode的場景。tick_broadcast_pending_mask和tick_broadcast_force_mask在後面的代碼分析中會具體描述。

3、強制進入broadcast mode

static int tick_broadcast_force;

這是一個強制使用broadcast tick device(而不使用per cpu的tick device)的一個控制變量,主要用於x86平臺,這裏就不詳細描述了,有興趣的讀者可以訪問http://lwn.net/Articles/286432/了解更多的信息。

四、初始化過程

本章,我們將分析一個典型的ARM平臺上的案例。我們假設系統使用了ARM generic timer和一個SOC級別的HW timer(block diagram在上文中已經給出)。為了簡單,我們假定所有的timer都是支持one shot和periodic模式(設有CLOCK_EVT_FEAT_ONESHOT和CLOCK_EVT_FEAT_PERIODIC這兩個flag),並且有C3STOP的問題。

clock event設備的註冊順序為:

(1)Bootstrap CPU上的local timer進行clock event設備的註冊

(2)SOC級別的HW timer進行clock event設備的註冊

(3)各個secondary CPU上的local timer進行clock event設備的註冊

(一)、我們首先看看Bootstrap CPU上的local timer的過程

1、註冊clock event device的時候,和broadcast tick device相關的操作

tick_check_new_device這個函數在periodic tick中已經描述,不過那份文檔忽略掉了broadcast device的內容,我們這裏主要broadcast device角度來分析tick device的初始化。

void tick_check_new_device(struct clock_event_device *newdev)
{
struct clock_event_device *curdev;
struct tick_device *td;
int cpu;

cpu = smp_processor_id();
if (!cpumask_test_cpu(cpu, newdev->cpumask))-----------------(1)
goto out_bc;

td = &per_cpu(tick_cpu_device, cpu);----------------------(2)
curdev = td->evtdev;

if (!tick_check_percpu(curdev, newdev, cpu))-------------------(3)
goto out_bc;

if (!tick_check_preferred(curdev, newdev))--------------------(4)
goto out_bc;

if (!try_module_get(newdev->owner))
return;

if (tick_is_broadcast_device(curdev)) {----------------------(5)
clockevents_shutdown(curdev);---這裏shutdown了broad cast對應的那個clock event device,那麽問題來了,什麽時候打開呢?
curdev = NULL;
}
clockevents_exchange_device(curdev, newdev);
tick_setup_device(td, newdev, cpu, cpumask_of(cpu));
if (newdev->features & CLOCK_EVT_FEAT_ONESHOT)
tick_oneshot_notify(); ---異步通知clock event layer,確保可以切換到one shot mode
return;

out_bc:
tick_install_broadcast_device(newdev);---------------------(6)
}

(1)tick device有兩種,一種是per cpu的tick device,另外一種是broadcast device,並不附著在某個CPU上。到底這個新的clock event device要成為哪種tick device是由它自己的特性決定的(當然,也有可能不和任何的tick device形成關聯)。如果新註冊的clock event device的所服務的CPU就不包含當前的CPU(cpumask說明了一個clock event device的所服務CPU的bitmap),那麽當然就不要驚動per cpu tick device的探測過程了,直接調用tick_install_broadcast_device函數看看是否broadcast tick device是否會收留它。

(2)想要入主per cpu的tick device也不是那麽簡單,還要和current進行鬥爭,td指向了該CPU core當前的per cpu tick device,該tick device對應的clock event device會和註冊的這個新的clock event device進行能力比拼。

(3)per cpu的tick device偏愛“專情”的clock event device。我們可以假設這樣的一個場景:系統中有一個全局的timer,其中斷可以送達任何一個CPU,因此可以服務任何一個cpu(cpu mask設定為全1),雖然這個全局timer通過的step (1)的考驗(cpumask_test_cpu),但是,當前cpu的那個per cpu的tick device已經和local timer結合了,而且那個local timer對應的clockevent device只服務於本cpu(cpu mask只有一個被設定為1),那麽毫無疑問,當前註冊的這個全局timer對應的clock event device會被無情的goto out_bc。當然,也不是說這種全局HW timer一點機會都沒有,如果該cpu沒有current per cpu的tick device,那麽也就勉為其難了。

(4)per cpu的tick device偏愛“優秀”的clock event device。所謂“優秀”其實就是是否有one shot的能力?是否rating高?如果新註冊的clock event device在和current的競爭中失敗,那麽請去broadcast tick device那裏試一試。

(5)OK,我們已經準備使用新註冊的clock event device來代替當前CPU上per cpu的tick device使用的clock event device,但是,也有可能舊的那個clock event device一人分飾兩角,即是當前CPU上per cpu的tick device,又是boradcast tick device,那麽保持其和boradcast tick device關系。這樣也就是意味著該clock event device仍然掛在active的clocke event device隊列中。後續的代碼和本文無關,因此就不再描述了。

(6)step(1)~(4)就是為了區分後續的針對該tick device的動作,要麽試圖使用這個clock event device來更新broad cast tick device(step (6)),要麽更新per cpu的tick device(step (5)),對於BSP,我們當然走step (5)了。

2、BSP的per cpu的tick device的初始化

這個過程在在periodic tick中已經描述,我們這裏只關註一個函數tick_device_uses_broadcast:

static void tick_setup_device(struct tick_device *td,
struct clock_event_device *newdev, int cpu,
const struct cpumask *cpumask)
{……
if (tick_device_uses_broadcast(newdev, cpu))
return;

……
}

tick_device_uses_broadcast主要是檢查當前per cpu tick device的HW timer的特性並確定是否要啟用broadcast tick設備,具體的代碼如下:

int tick_device_uses_broadcast(struct clock_event_device *dev, int cpu)
{
struct clock_event_device *bc = tick_broadcast_device.evtdev;
unsigned long flags;
int ret;

raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
if (!tick_device_is_functional(dev)) {-------------------------(1)
……
} else {----ARM平臺上都是進入這個分支
if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))----------------(2)
cpumask_clear_cpu(cpu, tick_broadcast_mask);
else
tick_device_setup_broadcast_func(dev);

if (!cpumask_test_cpu(cpu, tick_broadcast_on))------------------(3)
cpumask_clear_cpu(cpu, tick_broadcast_mask);

switch (tick_broadcast_device.mode) {
case TICKDEV_MODE_ONESHOT:
tick_broadcast_clear_oneshot(cpu);----------------------(4)
ret = 0;
break;

case TICKDEV_MODE_PERIODIC:
if (cpumask_empty(tick_broadcast_mask) && bc) ---------------(5)
clockevents_shutdown(bc);
ret = cpumask_test_cpu(cpu, tick_broadcast_mask); --------------(6)
break;
default:
ret = 0;
break;
}
}
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
return ret;
}

(1)一個clock event device是否可以正常的提供產生tick功能是可以通過CLOCK_EVT_FEAT_DUMMY 這個feature flag來判斷的,如果註冊的clock event device有DUMMY的flag,則說明這個clock event device只是個擺設,不能拿來產生tick。目前linux kernel中的x86平臺的apic local timer設定了這個flag,也就意味著該timer對應的clock event device不能做tick device,雖然per cpu的tick device中的clock event device指針指向了自己,但是我只是dummy設備,是個占位符而已,還是請broadcast tick設備來幫我產生tick吧。這時候,整個系統是工作在周期性tick的模式下,broadcast tick設備會周期性的產生tick,廣播到各個CPU上。

由於ARM平臺中沒有使用CLOCK_EVT_FEAT_DUMMY這個flag,我們這裏就一帶而過吧。

(2)如果該clock event device沒有標註C3STOP的flag,說明在cpuidle的時候自己能搞定一切,不需麻煩broadcast tick設備,因此清除tick_broadcast_mask中的標記。否則,調用tick_device_setup_broadcast_func函數設定broadcast call back函數(設定為tick_broadcast)。對於BSP的local timer,我們當然走這一個分支:

static void tick_device_setup_broadcast_func(struct clock_event_device *dev)
{
if (!dev->broadcast)
dev->broadcast = tick_broadcast;
if (!dev->broadcast) {
dev->broadcast = err_broadcast;
}
}

在這個函數中per cpu tick device對應的那個clock event device的broadcast call back函數被設定成tick_broadcast。clock event device的broadcast call back函數是用來廣播clock event的,broadcast tick的clock event需要廣播到其他一個或者多個cpu的時候,需要調用該函數進行具體的廣播動作(具體通過IPI實現)。

(3)tick_broadcast_on是cpuidle framework設定的標誌,local timer初始化的時候,cpuidle driver應該還沒有初始化,因此這裏這裏會clear tick_broadcast_mask這個標識。

(4)如果broadcast tick device已經處於one shot mode,那麽reset broadcast tick模塊和per cpu tick之間的狀態信息(tick_broadcast_oneshot_mask和tick_broadcast_pending_mask)。為什麽可以這麽做呢?因為這是在per cpu tick device的初始化過程中,系統正在運行過程中。在這種情況下,tick_device_uses_broadcast函數返回0,表示讓調用者繼續初始化該clock event device。

(5)缺省狀態下,broadcast tick device處於periodic mode。對應本場景,這時候broadcast tick device還沒有初始化呢,因此不會執行clockevents_shutdown。

(6)對於BSP的per cpu的tick device,這裏會返回0值,表示暫時不考慮使用broadcast tick device。返回tick_setup_device的現場,如果返回0值,則會繼續進行周期性tick設備的初始化(調用tick_setup_periodic)

OK,自此,BSP上的local timer已經初始化並啟動,工作在周期性tick狀態下,即是BSP上的local tick,又是系統的global tick設備。當然,local timer有one shot能力,是否這時候會切換到one shot mode呢?在timer的softirq中會周期性的檢測是否可以切換到oneshot mode,代碼如下:

int tick_check_oneshot_change(int allow_nohz)
{
struct tick_sched *ts = this_cpu_ptr(&tick_cpu_sched);

if (!test_and_clear_bit(0, &ts->check_clocks))---tick_check_new_device已經異步通知了,通過
return 0;

if (ts->nohz_mode != NOHZ_MODE_INACTIVE)-----初始化的狀態就是INACTIVE,通過
return 0;

if (!timekeeping_valid_for_hres() || !tick_is_oneshot_available())---不通過
return 0;

if (!allow_nohz)
return 1;

tick_nohz_switch_to_nohz();
return 0;
}

我們在第二章第二節描述了tick_is_oneshot_available這個函數,雖然BSP 的local timer有one shot能力,但是由於是C3STOP的,因此還需要看broadcast tick是否有one shot的能力(tick_broadcast_oneshot_available),這時候,broadcast tick還沒有初始化呢。因此BSP處於周期性tick的模式下,直到啟動SOC timer的初始化。

(二)、SOC timer的初始化過程

在進行SOC timer的初始化的過程中會調用註冊clock event設備的接口,從而觸發tick_check_new_device的調用,這裏,由於BSP已經有了per cpu的tick device,而且比SOC timer專情,因此執行step (6)

1、如何安裝/替換一個broadcast tick device?

在系統註冊一個新的clockevent device的時候,會根據情況來更新tick device(包括per cpu和broadcast tick device)的clock event device。調用tick_install_broadcast_device函數可以安裝/替換系統中broadcast tick device中的clock event device。

void tick_install_broadcast_device(struct clock_event_device *dev)
{
struct clock_event_device *cur = tick_broadcast_device.evtdev; --獲取當前的broadcast tick device

if (!tick_check_broadcast_device(cur, dev))--------------(1)
return;

if (!try_module_get(dev->owner))---增加reference count
return;

clockevents_exchange_device(cur, dev);---------------(2)
if (cur)
cur->event_handler = clockevents_handle_noop;
tick_broadcast_device.evtdev = dev;----------已經更新到新的clock event device了
if (!cpumask_empty(tick_broadcast_mask))--------------(3)
tick_broadcast_start_periodic(dev);
if (dev->features & CLOCK_EVT_FEAT_ONESHOT)-----------(4)
tick_clock_notify();
}

(1)不是什麽阿貓阿狗都可以稱為broadcast tick device的,首先不能是CLOCK_EVT_FEAT_C3STOP,本來broadcast tick就是為了解決cpuidle的時候timer會停掉的問題,如果broad cast tick device所依賴的clock event device也是C3STOP的,那還搞什麽搞的。其次,不能是CLOCK_EVT_FEAT_PERCPU,broadcast tick device必須有能力將clock event事件傳遞到系統中的任何一個CPU上,如果是percpu的,那麽其能力範圍僅僅限於本CPU,沒有broadcast的能力。滿足了這些條件就OK了嗎?也不是,那些都是一些基本條件,如果當前系統中沒有設定broadcast tick device(cur ==NULL),那麽只要滿足基本條件就OK了,但是,如果當前已經設定好了broadcast tick device,那麽又要比較了。不只是per cpu的tick device偏愛“優秀”的clock event device,broadcast tick device也是嫌貧愛富的主,還是要看看one shot能力以及rating,大家有興趣可以看看tick_check_broadcast_device代碼。

對於SOC timer場景,這裏當然可以通過檢查,SOC timer不是C3STOP的,而且有能力把中斷送達每一個cpu(需要中斷控制器支持,如果使用GIC的話,當然是OK的)。

(2)OK,需要用新的clock event device來替換舊的,因此需要將舊的clock event device(如果存在的話)從active 隊列中摘除,並設定CLOCK_EVT_MODE_UNUSED狀態,掛入released clockevent隊列。對於新的clockevent device,需要設定為CLOCK_EVT_MODE_SHUTDOWN狀態。

(3)tick_broadcast_mask中記錄了哪些cpu要使用broadcast tick device的周期性tick服務,只要有一個需要,那麽我們就要啟動broadcast tick device的運作,使之進入周期性tick的mode。當然,最開始的時候,tick_broadcast_mask等於0,因此這裏不會調用tick_broadcast_start_periodic。

(4)如果作為broadcast tick設備的clock event device具備one shot能力,還要異步通知到各個CPU,因為per cpu tick device有可能工作在periodic mode,現在系統有了支持one shot的broadcast tick device,看看是否有機會讓per cpu tick device也切換到one shot的狀態。

2、SOC timer的初始化後的系統狀態

SOC timer的初始化是在BSP上執行的,這時候BSP的per cpu tick device已經OK,工作在周期性tick下,由於cpudile framework還沒有初始化(實際BSP一直在執行,因此沒有機會調度到idle進程),也就是沒有機會使用broadcast tick device的服務,這時候,雖然已經安裝了broadcast tick device,但其功能沒有啟動,不會有tick事件發生。

(三)、Secondary CPU上的local timer的初始化過程

當Secondary CPU的狀態變成online之後,就會啟動該cpu上的local timer的初始化過程。當然,仍然是在註冊clockevent device的過程中調用 tick_check_new_device來檢查和哪個tick device結合。結果很明顯,當然是走per cpu tick device初始化的那條路徑。這個過程類似BSP,這裏就不詳述了。

五、處於周期性tick模式下的broadcast tick device如何工作?

1、系統何時處於周期性tick模式下呢?

一個簡單的場景就是per cpu tick device的clock event device是dummy device。我們來看看這種場景下,系統是如何運作的。我們首先看看在初始化per cpu tick device中的處理,在tick_device_uses_broadcast處理函數中有一段代碼:

if (!tick_device_is_functional(dev)) {
dev->event_handler = tick_handle_periodic;
tick_device_setup_broadcast_func(dev);
cpumask_set_cpu(cpu, tick_broadcast_mask);
if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
tick_broadcast_start_periodic(bc);
else
tick_broadcast_setup_oneshot(bc);
ret = 1;------如果是dummy device,不初始化per cpu tick
}

在per cpu tick device的clock event device是dummy device的情況下,percpu tick device工作在周期性tick模式下,其clock event device的event handler被設定為tick_handle_periodic,其broadcast callback函數被設定為tick_broadcast。為了告知broadcast tick device本cpu需要它的服務,因此需要設定tick_broadcast_mask中對應該cpu的那個bit。由於需要broadcast tick的服務,因此也會根據當前的broadcast tick device的模式設定其event handler。

由於per cpu的local timer是dummy device,因此實際上代碼不初始化per cpu tick,這時候就需要啟動broadcast tick device,來為各個cpu提供tick,具體參考tick_broadcast_start_periodic函數。

另外一個場景是各個cpu的local timer不是dummy device,但是有C3STOP的問題,這時候,在系統初始化的初期會運作在周期性tick的模式(percpu tick device處於周期性tick mode下,broadcast tick沒有啟動)。

2、啟動broadcast tick

在上面描述的兩個場景中,場景二的broadcast tick device並沒有啟動,那麽什麽時候會啟動其運作呢?這和cpuidle framework相關了。在cpuidle framework沒有初始化之前,各個cpu是不會進入idle狀態的,因此,不需要考慮C3STOP的問題。我們來看看cpuidle framework初始化過程中引發的操作。

cpuidle framework初始化過程中,如果cpuidle state有CPUIDLE_FLAG_TIMER_STOP”的flag,那麽就會向時間子系統發送CLOCK_EVT_NOTIFY_BROADCAST_ON的message,這時候會調用tick_broadcast_on_off函數:

static void tick_do_broadcast_on_off(unsigned long *reason)
{
struct clock_event_device *bc, *dev;
struct tick_device *td;
unsigned long flags;
int cpu, bc_stopped;

raw_spin_lock_irqsave(&tick_broadcast_lock, flags);

cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
dev = td->evtdev;
bc = tick_broadcast_device.evtdev;


if (!dev || !(dev->features & CLOCK_EVT_FEAT_C3STOP))-----------(1)
goto out;

if (!tick_device_is_functional(dev))----------------------(2)
goto out;

bc_stopped = cpumask_empty(tick_broadcast_mask);

switch (*reason) {
case CLOCK_EVT_NOTIFY_BROADCAST_ON:
case CLOCK_EVT_NOTIFY_BROADCAST_FORCE:
cpumask_set_cpu(cpu, tick_broadcast_on);
if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_mask)) {---------(3)
if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
clockevents_shutdown(dev);
}
if (*reason == CLOCK_EVT_NOTIFY_BROADCAST_FORCE)
tick_broadcast_force = 1;
break;
case CLOCK_EVT_NOTIFY_BROADCAST_OFF:
……略過off的代碼
}

if (cpumask_empty(tick_broadcast_mask)) {
if (!bc_stopped)
clockevents_shutdown(bc);---在OFF的時候看看是否有機會shutdown bc
} else if (bc_stopped) {----------------------------(4)
if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)
tick_broadcast_start_periodic(bc);--------------------(5)
else
tick_broadcast_setup_oneshot(bc);
}
out:
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}

(1)沒有本cpu的local timer沒有C3STOP的問題,那麽直接返回

(2)如果是dummy device,那麽也直接返回。也就是說,在dummy device的情況下,雖然各個cpu在cpuidle framework初始化/註銷的時候會發送BROADCAST_ON/BROADCAST_OFF的消息,不過實際上並不參與實際的控制,因為在per cpu tick device初始化的時候就已經啟動了broadcast tick device工作在周期性tick的模式下,這裏就不用多此一舉了。

(3)設定tick_broadcast_on,同時也設定tick_broadcast_mask,由於後續會啟動broadcast tick device產生周期性tick服務每一個cpu,因此會shutdown per cpu的clock event device。

(4)如果是首次初始化cpuidle framework(每個cpu都會執行一次),那麽需要初始化broadcast tick

(5)根據broadcast tick device的mode進行相應的初始化。

如果系統處於周期性tick模式下,則各個percpu 的tick device對應的clock event device要麽是dummy的,要麽被停掉,這時候,broadcast tick device為各個cpu提供周期性tick。這時候,雖然各個cpu在進入/退出idle狀態的時候會發送BROADCAST_ENTER/BROADCAST_EXIT或者BROADCAST_ON/BROADCAST_OFF的消息,不過實際上並不參與實際的控制(tick_broadcast_oneshot_control函數一開始檢查就退出了)。

3、設定broadcast tick device工作在周期性tick模式

tick_setup_periodic這個函數在periodic tick中已經描述,不過那份文檔忽略掉了broadcast device的內容,我們這裏主要broadcast device角度來分析如何setup一個tick device工作在周期性tick的模式。

void tick_setup_periodic(struct clock_event_device *dev, int broadcast)
{
tick_set_periodic_handler(dev, broadcast); -------------------(1)

if (!tick_device_is_functional(dev))
return;

if ((dev->features & CLOCK_EVT_FEAT_PERIODIC) &&
!tick_broadcast_oneshot_active()) {
clockevents_set_mode(dev, CLOCK_EVT_MODE_PERIODIC);---------(2)
} else {
…… 用one shot模擬周期性的tick
}
}

(1)對於broadcast tick device,event handler被設定為tick_handle_periodic_broadcast

(2)如果broadcast tick device對於的clock event支持CLOCK_EVT_FEAT_PERIODIC(硬件支持periodic的設定),並且沒有工作在one shot模式下,那麽啟動周期性tick模式。

4、broadcast tick device如何分發tick event到各個cpu core

全局HW timer如果觸發了中斷,會被調度到某個cpu上執行(這個HW timer硬件中斷應該是SPI類型的,可以送達任何一個cpu,在周期性mode下,軟件沒有控制irq affinity,因該是按照缺省的策略投遞irq),在timer的中斷處理函數中會調用該clock event device的event handler,具體的函數是tick_handle_periodic_broadcast:

static void tick_handle_periodic_broadcast(struct clock_event_device *dev)
{
ktime_t next;

raw_spin_lock(&tick_broadcast_lock);

tick_do_periodic_broadcast();

if (dev->mode == CLOCK_EVT_MODE_PERIODIC)
goto unlock;

…… 用one shot模擬周期性的tick,設定下一次觸發的時間
unlock:
raw_spin_unlock(&tick_broadcast_lock);
}

如果clock event device工作在periodic模式,那麽處理很簡單,否則(one shot模式)需要設定下一次觸發的時間值,因此,最核心的處理是tick_do_periodic_broadcast,代碼如下:

static void tick_do_periodic_broadcast(void)
{
cpumask_and(tmpmask, cpu_online_mask, tick_broadcast_mask);
tick_do_broadcast(tmpmask);
}

cpu_online_mask記錄了on line的cpu,tick_broadcast_mask記錄了申請broad cast服務的cpu,因此只需要處理那些CPU處於online狀態並且申請了broad cast服務的cpu。

static void tick_do_broadcast(struct cpumask *mask)
{
int cpu = smp_processor_id();
struct tick_device *td;

if (cpumask_test_cpu(cpu, mask)) {------------(1)
cpumask_clear_cpu(cpu, mask);
td = &per_cpu(tick_cpu_device, cpu);
td->evtdev->event_handler(td->evtdev);
}

if (!cpumask_empty(mask)) {---------------(2)
td = &per_cpu(tick_cpu_device, cpumask_first(mask));
td->evtdev->broadcast(mask);
}
}

(1)是否本cpu也需要broadcast tick 設備的服務,如果需要,那麽直接調用本cpu對應的per cpu tick device的event handler。調用event handler也就意味著將tick事件廣播到了本cpu。

(2)這是處理其他cpu的代碼。有可能多個cpu(除了本cpu)需要broadcast tick 設備的服務,這時候,就需要調用clock event device對應的broadcast call back函數了(隨便選擇一個即可)。broadcast call back函數可以將tick廣播到多個cpu上,具體如何做呢?當然是通過IPI機制的進行的(IPI_TIMER message),具體可以參考tick_broadcast的代碼。

六、系統切換切換到one shot mode

1、per cpu tick device切換到one shot mode

我們又回到hrtimer_run_pending函數,每次timer的軟中斷中都會調用它:

void hrtimer_run_pending(void)
{
if (tick_check_oneshot_change(!hrtimer_is_hres_enabled()))
hrtimer_switch_to_hres();
}

由於broadcast tick已經ready, tick_check_oneshot_change返回TRUE,因此會調用hrtimer_switch_to_hres函數,將系統的hrtimer切換到高精度狀態。原來的系統的clock event設備工作在periodic mode下,無法實現真正的高精度timer,因此hrtimer模塊委曲求全,工作在低精度模塊下,現在系統可以one shot了,因此可以改變到高精度狀態。

hrtimer_switch_to_hres函數會調用tick_init_highres,代碼如下:

int tick_init_highres(void)
{
return tick_switch_to_oneshot(hrtimer_interrupt);
}

int tick_switch_to_oneshot(void (*handler)(struct clock_event_device *))
{
切換per cpu tick device進入one shot mode
tick_broadcast_switch_to_oneshot();
return 0;
}

因此,一旦broadcast tick已經ready,所有的per cpu tick device都會依次切換到one shot mode。

2、broadcast tick device切換到one shot mode

在per cpu tick device切換進入one shot mode之後,broadcast tick device也尾隨而入:

void tick_broadcast_switch_to_oneshot(void)
{
struct clock_event_device *bc;
unsigned long flags;

raw_spin_lock_irqsave(&tick_broadcast_lock, flags);

tick_broadcast_device.mode = TICKDEV_MODE_ONESHOT;
bc = tick_broadcast_device.evtdev;
if (bc)
tick_broadcast_setup_oneshot(bc); ---設定broadcast tick進入one shot狀態

raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
}

上面的代碼簡單而且清晰,主要的邏輯在tick_broadcast_setup_oneshot中實現:

void tick_broadcast_setup_oneshot(struct clock_event_device *bc)
{
int cpu = smp_processor_id();

if (bc->event_handler != tick_handle_oneshot_broadcast) { -------------(1)
int was_periodic = bc->mode == CLOCK_EVT_MODE_PERIODIC;

bc->event_handler = tick_handle_oneshot_broadcast; ---設定one shot handler

cpumask_copy(tmpmask, tick_broadcast_mask); ---------------(2)
cpumask_clear_cpu(cpu, tmpmask); ---------------------(3)
cpumask_or(tick_broadcast_oneshot_mask,
tick_broadcast_oneshot_mask, tmpmask); ----------------(4)

if (was_periodic && !cpumask_empty(tmpmask)) { ---------------(5)
clockevents_set_mode(bc, CLOCK_EVT_MODE_ONESHOT);
tick_broadcast_init_next_event(tmpmask, tick_next_period); ---------(6)
tick_broadcast_set_event(bc, cpu, tick_next_period, 1); -----------(7)
} else
bc->next_event.tv64 = KTIME_MAX;
} else {
tick_broadcast_clear_oneshot(cpu); ---------------------(8)
}
}

(1)每次cpu在進行模式切換的時候都會調用該函數,當然,我們限制調用tick_broadcast_setup_oneshot函數進行一次切換就OK了。

(2)我們要非常小心的處理broadcast tick的模式切換,因為有可能這時候broadcast tick設備工作在周期性tick下,並且還在服務其他cpu呢。因此要保留那些周期性tick的需求。

(3)本cpu的周期性tick可以清掉,本cpu正在執行模式切換,不需broadcast tick的服務,當本cpu進入idle的時候會按照正常的流程來申請服務的。

(4)切換到one shot mode,代碼邏輯所依賴的狀態信息也就會切換到tick_broadcast_oneshot_mask,歸並過去的需求到該變量上來。

(5)如果有殘留的周期性tick服務需求,那麽需要立刻啟動該broadcast tick進入oneshot模式的工作狀態,為那些還需要周期性tick的cpu提供可以正常運作的周期性tick。當這些tick送達到那些CPU後,在timer軟中斷上下文中會切換周期性mode到one shot mode。這時候,整個系統(各個cpu的tick以及broadcast tick)都是在one shot模式下工作。

(6)將需要周期性服務的那些cpu的next event設定為tick_next_period

(7)設定broadcast tick device在tick_next_period時間之後觸發,站好周期性tick的最後一班崗。

(8)第一個切換到one shot mode的cpu處理比較復雜(上面的1~7步),對於其他的cpu而言,tick_broadcast_setup_oneshot比較簡單,只要清掉one shot相關的mask就OK了。

七、處於one shot模式下的broadcast tick device如何工作?

1、各個CPU進入/退出idle時候的處理

當系統中的某個CPU Core要進入/退出一個標記有“CPUIDLE_FLAG_TIMER_STOP”的flag的cpuidle state的時候,會發送CLOCK_EVT_NOTIFY_BROADCAST_ENTER/EXIT消息,告知broadcast tick模塊:我的local timer要掛掉了,請求broadcast tick支援。ENTER/EXIT消息是發送到了clock event layer,在clockevents_notify中處理:

int clockevents_notify(unsigned long reason, void *arg)
{……

case CLOCK_EVT_NOTIFY_BROADCAST_ENTER:
case CLOCK_EVT_NOTIFY_BROADCAST_EXIT:
ret = tick_broadcast_oneshot_control(reason);
break;

……
}

tick_broadcast_oneshot_control函數的代碼如下:

int tick_broadcast_oneshot_control(unsigned long reason)
{
struct clock_event_device *bc, *dev;
struct tick_device *td;
unsigned long flags;
ktime_t now;
int cpu, ret = 0;

if (tick_broadcast_device.mode == TICKDEV_MODE_PERIODIC)---------(1)
return 0;
cpu = smp_processor_id();
td = &per_cpu(tick_cpu_device, cpu);
dev = td->evtdev;

if (!(dev->features & CLOCK_EVT_FEAT_C3STOP))---------------(2)
return 0;

bc = tick_broadcast_device.evtdev;

raw_spin_lock_irqsave(&tick_broadcast_lock, flags);
if (reason == CLOCK_EVT_NOTIFY_BROADCAST_ENTER) {
if (!cpumask_test_and_set_cpu(cpu, tick_broadcast_oneshot_mask)) {------(3)
WARN_ON_ONCE(cpumask_test_cpu(cpu, tick_broadcast_pending_mask));
broadcast_shutdown_local(bc, dev);---shutdown local timer

if (!cpumask_test_cpu(cpu, tick_broadcast_force_mask) &&
dev->next_event.tv64 < bc->next_event.tv64)
tick_broadcast_set_event(bc, cpu, dev->next_event, 1);-----------(4)
}

ret = broadcast_needs_cpu(bc, cpu); ---和hrtimer broadcast相關
if (ret)
cpumask_clear_cpu(cpu, tick_broadcast_oneshot_mask);-----------(5)
} else {
if (cpumask_test_and_clear_cpu(cpu, tick_broadcast_oneshot_mask)) { ------(6)
clockevents_set_mode(dev, CLOCK_EVT_MODE_ONESHOT); ---------(7)

if (cpumask_test_and_clear_cpu(cpu, tick_broadcast_pending_mask))------(8)
goto out;


if (dev->next_event.tv64 == KTIME_MAX)
goto out;


now = ktime_get(); ---獲取當前時間
if (dev->next_event.tv64 <= now.tv64) { --------------------(9)
cpumask_set_cpu(cpu, tick_broadcast_force_mask);
goto out;
}
tick_program_event(dev->next_event, 1); -------------------(10)
}
}
out:
raw_spin_unlock_irqrestore(&tick_broadcast_lock, flags);
return ret;
}

(1)broadcast tick device一般是不會工作在periodic mode下的,畢竟在這種狀態下broadcast tick device是無法按照進入idle狀態的cpu(local timer停掉)的需求提供tick。不過,如果per cpu tick的clock event設備是dummy的,也就是無法提供功能,那麽這時候可以讓broadcast tick device處於周期性模式,不斷的為各個cpu提供周期性的tick。這時候,是否cpu進入idle狀態是沒有關系的(由於其per cpu的tick設備對應的clock event設備都是dummy的,broadcast tick device總是會周期性的傳送tick事件),可以直接返回。此外,由於系統一開始總是從周期性模式開始,隨著系統的運行會切換到one shot模式,不過這種場景下,周期性模式是一個暫態過程,系統最終還是會停留在one shot mode。

(2)如果percpu tickdevice對應的clock event device不是C3STOP的,那麽也不需要額外的處理,直接返回。

(3)首先需要設定tick_broadcast_oneshot_mask這個標識位,以此告知broadcast tick framework該cpu需要broadcast tick模塊的服務。此外,還檢查之前的設定(這裏使用的是cpumask_test_and_set_cpu)。為何要這麽做呢?正常來說,tick_broadcast_oneshot_mask不都是隨著cpu進入idle置位,退出idle清零嗎?實際上這裏主要是考慮從周期性模式到one shot模式切換中,這時候,broadcast tick device處於one shot mode,因此可以通過步驟1,而在第一個切換到one shot mode的cpu的代碼執行中,會將tick_broadcast_mask復制到tick_broadcast_oneshot_mask中,如果tick_broadcast_oneshot_mask對應該cpu的bit等於1,說明該cpu的clockevent device已經shutdown(周期性tick mode下,per cpu的tick device都是shutdown的,都靠broadcast tick),不能提供tick,該cpu使用了broadcast tick device來提供周期性tick服務。因此,在這種情況下,不必shutdown per cpu的clockevent device了,也不必reprogram next event。

broadcast_shutdown_local函數用於shutdown local timer,當然,是否可以真正shutdown還要看看broadcast tick device是否有CLOCK_EVT_FEAT_HRTIMER特性,此外,如果在hrtimer based broadcast tick的情況下,如果本cpu就是用來提供broadcast tick服務的那個cpu,這時候也不能shutdown local timer。

(4)如果該cpu的next event比broadcast tick的next event還要小,那麽用該cpu上的next event重新設定next trigger event。當然,這裏還有一個特例就是tick_broadcast_force_mask,如果該cpu設置了force mask,則說明

(5)在hrtimer based tick broadcast的場景下,如果tick broadcast設備所依賴的那個hrtimer就是運行在當前的cpu上,那麽該cpu是不會進入idle狀態的,因此clear tick_broadcast_oneshot_mask(本cpu不會進入idle,其local timer就不會停掉,也就不需要tick broadcast的服務)。

(6)清除tick_broadcast_oneshot_mask這個標識位,告知broadcast tick framework,我已經不用你提供的tick服務了。

(7)CPU從idle狀態返回,local timer又活過來了,因此重新設定local timer工作在one shot mode

(8)我們假設一個場景:有三個cpu core分別是A、B、C,A的next event是最近的,因此SOC timer被設定為A的時間點並且SOC timer的中斷被定向到A處理器,但是,實際上B和C的時間僅僅比A小了一點點,在A處理器上執行broad cast tick device的event handler的時候,發現B和C也已經到期了,需要處理(發送了IPI給B和C處理器),為了標識這種狀態,B和C的tick_broadcast_pending_mask標識被置位了。

當然清掉tick_broadcast_pending_mask中的標誌是必須要的,畢竟本cpu已經離開了idle狀態,可以處理自己的clock event了,但是,在這裏,就不適合再reprogramming next event了,畢竟這個next event已經超期,而且會在IPI的handler中處理。因此,當清除tick_broadcast_pending_mask的時候,檢查發現該bit是pending的,那麽就直接跳出,不需要後續的處理了

(9)當代碼執行到這裏,有兩個場景:

---場景一:SOC timer將中斷送達本cpu,從而將該處理器喚醒,執行完成這裏的代碼,該處理器會調用broadcast tick device的event handler的。

---場景二:其他的中斷喚醒了該cpu

這時候,tick_broadcast_oneshot_mask已經被清除了,隨後的tick_handle_oneshot_broadcast可是根據tick_broadcast_oneshot_mask進行處理的,因此,如果是場景一,我們必須有辦法讓後續的tick_handle_oneshot_broadcast來處理本cpu超期的clock event。這時候需要設定tick_broadcast_force_mask的標記,標了這個標記的cpu,tick_handle_oneshot_broadcast也會進行處理的。

如果是場景二,並且本cpu的clockevent對應的next time已經需要trigger了,怎麽辦?反正tick_handle_oneshot_broadcast也很快要執行了,那麽就不再programming local timer了,還是設定force flag,讓broadcast tick來處理吧。

(10)對於場景二,並且本cpu的next event的時間點還沒有超期,這時候反正local timer以及ready(cpu 離開了idle狀態),那麽還是依靠自己的local timer吧。

2、broadcast tick device如何分發tick event到各個cpu core

當SOC timer(或者叫做全局timer)觸發中斷的時候,會將該中斷送達set next event的那個cpu(tick_broadcast_set_event中會設定irq affinity),從而引發broadcast tick device對於的那個clock event設備的event handler的執行,代碼如下:

static void tick_handle_oneshot_broadcast(struct clock_event_device *dev)
{
struct tick_device *td;
ktime_t now, next_event;
int cpu, next_cpu = 0;

raw_spin_lock(&tick_broadcast_lock);
again:
dev->next_event.tv64 = KTIME_MAX;
next_event.tv64 = KTIME_MAX;
cpumask_clear(tmpmask);
now = ktime_get();

for_each_cpu(cpu, tick_broadcast_oneshot_mask) {--------------(1)
td = &per_cpu(tick_cpu_device, cpu);
if (td->evtdev->next_event.tv64 <= now.tv64) {----------------(2)
cpumask_set_cpu(cpu, tmpmask);
cpumask_set_cpu(cpu, tick_broadcast_pending_mask); ----------(3)
} else if (td->evtdev->next_event.tv64 < next_event.tv64) { -----------(4)
next_event.tv64 = td->evtdev->next_event.tv64;
next_cpu = cpu;
}
}

cpumask_clear_cpu(smp_processor_id(), tick_broadcast_pending_mask); -----(5)

cpumask_or(tmpmask, tmpmask, tick_broadcast_force_mask); ----------(6)
cpumask_clear(tick_broadcast_force_mask);


if (WARN_ON_ONCE(!cpumask_subset(tmpmask, cpu_online_mask))) -------(7)
cpumask_and(tmpmask, tmpmask, cpu_online_mask);

tick_do_broadcast(tmpmask); -----喚醒那些需要服務而且時間到期的cpu們

if (next_event.tv64 != KTIME_MAX) {
if (tick_broadcast_set_event(dev, next_cpu, next_event, 0)) -----------(8)
goto again;
}
raw_spin_unlock(&tick_broadcast_lock);
}

(1)tick_broadcast_oneshot_mask變量中,每個set的bit說明該cpu已經進入idle狀態,需要broadcast tick device的服務。通過for_each_cpu來遍歷每一個需要服務的cpu。

(2)next_event就是該clock event device下次要觸發的時間點,如果該時間點早於當前的時間點,說明需要alter該cpu了,因此調用cpumask_set_cpu設定tmpmask,後續會根據tmpmask來發送tick廣播。從這裏的代碼可以看出,雖然broadcast tick設定的觸發時間是所有cpu中最近的那個trigger timer,並把irq送達該cpu,但是實際上,並不是一次只將該tick送到設定next event的那個cpu,實際上會檢查所有的cpu的next event,以便盡可能的一次多處理幾個CPU上的clock event事件。

(3)設定tick_broadcast_pending_mask,告知該cpu:你的next event我已經處理,並且發送了IPI message,請在IPI handler中處理就OK了。這主要是為了避免在tick_broadcast_oneshot_control中reprogram local timer。

(4)如果不需要廣播tick,喚醒該cpu,那麽需要為下一次的喚醒時間做準備。我們會遍歷所有的需要服務的cpu,比對其per cpu tick的next event,並把最近的那個觸發時間點保存在next_event同時記錄該cpu id為next_cpu。

(5)清除本cpu在tick_broadcast_pending_mask中的標誌。為何要清本cpu的pending標誌呢?因為隨後就會在tick_do_broadcast中直接處理本cpu的event(直接調用event handler而不是發送IPI消息),這些代碼都是在一個臨界區內(tick_broadcast_lock),不會有其他分支插入了。

(6)除了正常的那些申請了服務並且到期的cpu需要被喚醒之外,還需要加上那些enforced broadcast requests。

(7)沒有online的那些cpu就不考慮廣播tick到該cpu了

(8)本次的broadcast tick device的event handler處理一部分到期的cpu們,但是,tick_broadcast_oneshot_mask中還有一些沒有到期的cpu,我們需要為下一次做準備。因此這裏會調用tick_broadcast_set_event用來設定next event時間點到broadcast tick device,如果設置失敗,說明這些cpu中有些next event已經超期,因此需要重新執行本函數的邏輯,以便廣播tick,喚醒那些到期的cpu。

Linux時間子系統(十四) tick broadcast framework