RCU的實現集中在以下幾個步驟:
1, 調用call_rcu,將回調函數增加到列表。
2, 開始一個寬限期。
3, 每個CPU報告自己的狀態,直到最後一個CPU,結束一個寬限期。
4, 寬限期結束,每個CPU處理自己的回調函數。
call_rcu的實現
static void
__call_rcu(struct rcu_head *head, void (*func)(struct rcu_head *rcu),
struct rcu_state *rsp, bool lazy)
{
unsigned long flags;
struct rcu_data *rdp;
WARN_ON_ONCE((unsigned long)head & 0x3); /* 檢測head在內存中是否對齊! */
debug_rcu_head_queue(head);
head->func = func;
head->next = NULL;
smp_mb(); /* Ensure RCU update seen before callback registry. */
/*
* 這是一個檢測寬限期開始或者結束的機會。
* 當我們看到一個結束的時候,可能還會看到一個開始。
* 反過來,看到一個開始的時候,不一定能看到一個結束,
* 因爲寬限期結束需要一定時間。
*/
local_irq_save(flags);
rdp = this_cpu_ptr(rsp->rda);
/* 將要增加callback到nxtlist. */
ACCESS_ONCE(rdp->qlen)++;
if (lazy)
rdp->qlen_lazy++;
else
rcu_idle_count_callbacks_posted();
smp_mb(); /* Count before adding callback for rcu_barrier(). */
*rdp->nxttail[RCU_NEXT_TAIL] = head;
rdp->nxttail[RCU_NEXT_TAIL] = &head->next;
if (__is_kfree_rcu_offset((unsigned long)func))
trace_rcu_kfree_callback(rsp->name, head, (unsigned long)func,
rdp->qlen_lazy, rdp->qlen);
else
trace_rcu_callback(rsp->name, head, rdp->qlen_lazy, rdp->qlen);
/* 去處理rcu_core。 */
__call_rcu_core(rsp, rdp, head, flags);
local_irq_restore(flags);
}
call_rcu中最主要的工作,就是將回調函數加入到CPU的nxtlist列表。這裏用到了指針處理的小技巧,我們來看看。首先看看nxttail的初始化:
static void init_callback_list(struct rcu_data *rdp)
{
int i;
rdp->nxtlist = NULL;
for (i = 0; i < RCU_NEXT_SIZE; i++)
rdp->nxttail[i] = &rdp->nxtlist;
}
我們看到nxttail的全部成員都指向了nxtlist的地址。當nxtlist爲空的時候,也是這個情形。
*rdp->nxttail[RCU_NEXT_TAIL] = head;
當nxtlist爲空的時候, *rdp->nxttail[RCU_NEXT_TAIL] 得到的其實就是nxtlist,將head的值賦予它。
rdp->nxttail[RCU_NEXT_TAIL] = &head->next;
之後 RCU_NEXT_TAIL指向 head的next指針。這樣當再有一個節點加入的時候,*rdp->nxttail[RCU_NEXT_TAIL]得到的其實就是前一次加入的head的next指針,它將指向新加入的值。如此,nxtlist就成爲了一個鏈表。或者這樣理解,rdp->nxttail[RCU_NEXT_TAIL] 指向的就是nxtlist中最後一個節點的 next指針。
除了將回調函數插入,該函數其它代碼多爲檢查代碼。而最後要調用__call_rcu_core,該函數的功用主要是在回調函數太多或者等待時間過長的狀態下,強制執行RCU狀態更新。我們暫時不關注。
開始一個寬限期
在一個寬限期結束,或者當一個CPU檢測到自身有需要一個寬限期的時候會開始一個新的寬限期,開始寬限期的代碼如下:
static void
rcu_start_gp(struct rcu_state *rsp, unsigned long flags)
__releases(rcu_get_root(rsp)->lock)
{
struct rcu_data *rdp = this_cpu_ptr(rsp->rda);
struct rcu_node *rnp = rcu_get_root(rsp);
if (!rcu_scheduler_fully_active ||
!cpu_needs_another_gp(rsp, rdp)) {
/*
* 如果scheduler 還沒有啓動non-idle任務
* 或者不需要啓動一個新的寬限期則退出。
* 需要再次判斷cpu_needs_another_gp,
* 是因爲可能有多個CPU執行這個過程。
*/
raw_spin_unlock_irqrestore(&rnp->lock, flags);
return;
}
if (rsp->fqs_active) {
/*
* 這個CPU需要一個寬限期,而force_quiescent_state()
* 正在運行,告訴它開始一個。
*/
rsp->fqs_need_gp = 1;
raw_spin_unlock_irqrestore(&rnp->lock, flags);
return;
}
/* 開始一個新的寬限期並且初始化。 */
rsp->gpnum++;
trace_rcu_grace_period(rsp->name, rsp->gpnum, "start");
WARN_ON_ONCE(rsp->fqs_state == RCU_GP_INIT);
rsp->fqs_state = RCU_GP_INIT; /* 阻止 force_quiescent_state。 */
rsp->jiffies_force_qs = jiffies + RCU_JIFFIES_TILL_FORCE_QS;
record_gp_stall_check_time(rsp);
raw_spin_unlock(&rnp->lock); /* leave irqs disabled. */
/* 排除CPU的熱插拔。*/
raw_spin_lock(&rsp->onofflock); /* irqs already disabled. */
/*
* 從父節點開始以廣度優先的方式,遍歷所有的節點,設置qsmask的值,
* 所有在線CPU所在bit都將被設置成1。
* 通過遍歷rsp->node[]數組就可以達到這個目的。
* 其它CPU在自己所屬的節點還沒有被設置前,只有可能訪問這個節點,
* 因爲它所作的判斷是寬限期還沒有開始。
* 此外,我們排除了CPU熱插拔。
*
* 直到初始化過程完成之前,這個寬限期不可能完成,因爲至少當前的
* CPU所屬的bit將不會被設置。這個是因爲我們啓動了禁止中斷,所以
* 這個CPU不會調用到寬限期檢測代碼。
*/
rcu_for_each_node_breadth_first(rsp, rnp) {
raw_spin_lock(&rnp->lock); /* irqs already disabled. */
rcu_preempt_check_blocked_tasks(rnp);
rnp->qsmask = rnp->qsmaskinit;
rnp->gpnum = rsp->gpnum;
rnp->completed = rsp->completed;
if (rnp == rdp->mynode)
rcu_start_gp_per_cpu(rsp, rnp, rdp);
rcu_preempt_boost_start_gp(rnp);
trace_rcu_grace_period_init(rsp->name, rnp->gpnum,
rnp->level, rnp->grplo,
rnp->grphi, rnp->qsmask);
raw_spin_unlock(&rnp->lock); /* irqs remain disabled. */
}
rnp = rcu_get_root(rsp);
raw_spin_lock(&rnp->lock); /* irqs already disabled. */
rsp->fqs_state = RCU_SIGNAL_INIT; /* force_quiescent_state now OK. */
raw_spin_unlock(&rnp->lock); /* irqs remain disabled. */
raw_spin_unlock_irqrestore(&rsp->onofflock, flags);
}
標記一個新的寬限期開始,rcu_state要做的就是將gp_num加1。然後再設置所有node,qsmask被設置成qsmasinit,qsmask每個bit代表一個CPU,所有在線的CPU都將被設置成1;gpnum將被設置成新值。嗯,一個新寬限期的開始只需要設置這些標記位。
CPU的寬限期檢測
當一個寬限期開始後,每個CPU都需要檢測自己的狀態,如果已經通過靜止狀態,那麼就向上一級node進行報告。
這個處理過程,可以分爲兩個步驟:
1, 檢測新的處理過程開始,設置rcu_data中的gpnum和passed_quiesce,另外用qs_pending標記一個待處理的新寬限期的開始。
2, 一個靜止狀態結束,向上一級node報告這個過程。
這兩個過程通過rcu_check_quiescent_state()來實現,需要注意的是這個函數隔一段時間調用一次,並不只調用一次。
/*
* 檢測這個CPU是否還不知道一個新寬限期開始,如果是設置它的變量。
* 否則檢查它是不是第一次通過靜止狀態,如果是,向上報告。
*/
static void
rcu_check_quiescent_state(struct rcu_state *rsp, struct rcu_data *rdp)
{
/* 如果有新的寬限期開始,記錄它並返回。*/
if (check_for_new_grace_period(rsp, rdp))
return;
/*
* 這個CPU是否已經處理過它的寬限期?如果是返回。
*/
if (!rdp->qs_pending)
return;
/*
* 是否通過了靜止狀態?如果沒有,返回。
*/
if (!rdp->passed_quiesce)
return;
/*
* 向所屬的node報告。(但rcu_report_qs_rdp() 仍然會去判斷它)。
*/
rcu_report_qs_rdp(rdp->cpu, rsp, rdp, rdp->passed_quiesce_gpnum);
}
A, CPU檢測新寬限期的開始
/*
* 爲當前CPU,更新rcu_data的狀態,去標記一個新寬限期的開始
* 如果當前CPU啓動了一個寬限期或者檢測到一個新的寬限期開始,
* 都需要調用這個函數。這個過程必須鎖定父節點的lock,另外需
* 要禁止中斷
*/
static void __note_new_gpnum(struct rcu_state *rsp, struct rcu_node *rnp, struct rcu_data *rdp)
{
if (rdp->gpnum != rnp->gpnum) {
/*
* 如果當前的寬限期需要處理這個CPU的狀態,設置並
* 去檢測它的靜止狀態。否則不要去管它。
*/
rdp->gpnum = rnp->gpnum;
trace_rcu_grace_period(rsp->name, rdp->gpnum, "cpustart");
if (rnp->qsmask & rdp->grpmask) {
rdp->qs_pending = 1;
rdp->passed_quiesce = 0;
} else {
rdp->qs_pending = 0;
}
zero_cpu_stall_ticks(rdp);
}
}
static void note_new_gpnum(struct rcu_state *rsp, struct rcu_data *rdp)
{
unsigned long flags;
struct rcu_node *rnp;
local_irq_save(flags);
rnp = rdp->mynode;
if (rdp->gpnum == ACCESS_ONCE(rnp->gpnum) || /* outside lock. */
!raw_spin_trylock(&rnp->lock)) { /* irqs already off, so later. */
local_irq_restore(flags);
return;
}
__note_new_gpnum(rsp, rnp, rdp);
raw_spin_unlock_irqrestore(&rnp->lock, flags);
}
/*
* 在我們的上次檢測之後,其它CPU啓動了一個新的寬限期?
* 如果是更新相應的rcu_data的狀態。
* 必須是在rdp對應的CPU上執行。
*/
static int
check_for_new_grace_period(struct rcu_state *rsp, struct rcu_data *rdp)
{
unsigned long flags;
int ret = 0;
local_irq_save(flags);
if (rdp->gpnum != rsp->gpnum) {
note_new_gpnum(rsp, rdp);
ret = 1;
}
local_irq_restore(flags);
return ret;
}
check_for_new_grace_period 和 note_new_gpnum分別用來檢測rdp的gpnum與rsp已經對應的rnp的值是否相同,來確定是否有一個新的寬限期開始。之所以需要檢測兩次,是因爲在rsp設置以後,rnp可能並沒有設置完成。
__note_new_gpnum 將設置gpnum的值。另外設置 qs_pending爲1,該標記位代表該節點還沒有向父節點報告自己的狀態;passed_quiesce爲0,表示需要一個靜止狀態,設置該位是因爲下次調用rcu_check_quiescent_state()可能是在一個讀過程還沒有結束的時候。
qs_pending的狀態有可能爲0,這隻在以下情形下出現:當前CPU在寬限期開始的時候實在離線狀態,而現在變成了在線。
我們注意到在 check_for_new_grace_period檢測到有新的寬限期開始後,rcu_check_quiescent_state將直接返回,因爲這個寬限期可能是在該CPU的上一個靜止狀態之前已經開始,所以需要等待下一個靜止狀態。
B,CPU報告靜止狀態
當再一次調用到rcu_check_quiescent_state()的時候,check_for_new_grace_period()將返回FALSE,接着運行後面的函數來判斷 qs_pending 和 passed_quiesce 的值來決定是否調用rcu_report_qs_rdp。需要判斷qs_peding是因爲當這次rcu_report_qs_rdp調用成功的時候,下次再運行rcu_check_quiescent_state()則不需要繼續運行後續函數。
static void
rcu_report_qs_rdp(int cpu, struct rcu_state *rsp, struct rcu_data *rdp, long lastgp)
{
unsigned long flags;
unsigned long mask;
struct rcu_node *rnp;
rnp = rdp->mynode;
raw_spin_lock_irqsave(&rnp->lock, flags);
if (lastgp != rnp->gpnum || rnp->completed == rnp->gpnum) {
/*
* 如果寬限期的處理已經完成,那麼返回。
*/
rdp->passed_quiesce = 0; /* need qs for new gp. */
raw_spin_unlock_irqrestore(&rnp->lock, flags);
return;
}
mask = rdp->grpmask;
if ((rnp->qsmask & mask) == 0) {
raw_spin_unlock_irqrestore(&rnp->lock, flags);
} else {
rdp->qs_pending = 0;
/*
* 可以確定這個寬限期還沒有結束,所以可以確定當前CPU上的
* 所有回調函數可以在下次寬限期結束後處理。
*/
rdp->nxttail[RCU_NEXT_READY_TAIL] = rdp->nxttail[RCU_NEXT_TAIL];
rcu_report_qs_rnp(mask, rsp, rnp, flags); /* rlses rnp->lock */
}
}
從我看來,這個函數只會調用到最後一個else分支,而之前的連個if分支都不會調用到。因爲在調用該函數前,代碼已經做了必要的檢測。
以此來看,這個函數的功用就是設置qs_pending的值,阻止這次寬限期沒有完成之前再次調用掉該函數;設置nxttail,決定下次寬限期後可以執行的回調函數;然後向父節點報告靜止狀態完成。
C,向上報告
static void
rcu_report_qs_rnp(unsigned long mask, struct rcu_state *rsp,
struct rcu_node *rnp, unsigned long flags)
__releases(rnp->lock)
{
struct rcu_node *rnp_c;
/* 向上遍歷所有層級 */
for (;;) {
if (!(rnp->qsmask & mask)) {
/* 這個CPU的標記已經被清除,證明已經處理過了,返回 */
raw_spin_unlock_irqrestore(&rnp->lock, flags);
return;
}
rnp->qsmask &= ~mask;
trace_rcu_quiescent_state_report(rsp->name, rnp->gpnum,
mask, rnp->qsmask, rnp->level,
rnp->grplo, rnp->grphi,
!!rnp->gp_tasks);
if (rnp->qsmask != 0 || rcu_preempt_blocked_readers_cgp(rnp)) {
/* 這個節點中還有其它CPU沒有處理完成,那麼返回 */
raw_spin_unlock_irqrestore(&rnp->lock, flags);
return;
}
mask = rnp->grpmask;
if (rnp->parent == NULL) {
/* 到這兒,已經到了根節點 */
break;
}
raw_spin_unlock_irqrestore(&rnp->lock, flags);
rnp_c = rnp;
rnp = rnp->parent;
raw_spin_lock_irqsave(&rnp->lock, flags);
WARN_ON_ONCE(rnp_c->qsmask);
}
/*
* 程序運行到這兒,說明所有的CPU都通過了寬限期,
* 那麼調用rcu_report_qs_rsp()來結束這個寬限期。
*/
rcu_report_qs_rsp(rsp, flags); /* releases rnp->lock. */
}
這個過程並不複雜,清理rnp中qsmask對應該CPU的bit。然後判斷該節點是否處理完成,如果是則繼續向上調用,否則就退出函數。最後一個CPU調用後,可以調用到rcu_report_qs_rsp()。
static void rcu_report_qs_rsp(struct rcu_state *rsp, unsigned long flags)
__releases(rcu_get_root(rsp)->lock)
{
unsigned long gp_duration;
struct rcu_node *rnp = rcu_get_root(rsp);
struct rcu_data *rdp = this_cpu_ptr(rsp->rda);
WARN_ON_ONCE(!rcu_gp_in_progress(rsp));
/*
* Ensure that all grace-period and pre-grace-period activity
* is seen before the assignment to rsp->completed.
*/
smp_mb(); /* See above block comment. */
gp_duration = jiffies - rsp->gp_start;
if (gp_duration > rsp->gp_max)
rsp->gp_max = gp_duration;
/*
* 當前CPU知道寬限期已經結束,不過其它CPU都認爲它還在運行。
* 由於completed還沒有設置,其它CPU都不會對父node進行處理。
* 所以這時候將各個node標記爲完成是安全的。
*
* 不過當前CPU有等待下一次寬限期的回調函數的時候,我們會
* 先去處理下一個寬限期。
* 這兒使用RCU_WAIT_TAIL代替了RCU_DONE_TAIL,這是因爲當前
* CPU還沒有進一步處理完成狀態,當前RCU_WAIT_TAIL狀態的元
* 素其實在這次寬限期結束後,已經可以執行了。
*
*/
if (*rdp->nxttail[RCU_WAIT_TAIL] == NULL) {
raw_spin_unlock(&rnp->lock); /* irqs remain disabled. */
/*
* 設置 rnp->completed的值,避免這個過程要等到下一次寬限期開始。
*/
rcu_for_each_node_breadth_first(rsp, rnp) {
raw_spin_lock(&rnp->lock); /* irqs already disabled. */
rnp->completed = rsp->gpnum;
raw_spin_unlock(&rnp->lock); /* irqs remain disabled. */
}
rnp = rcu_get_root(rsp);
raw_spin_lock(&rnp->lock); /* irqs already disabled. */
}
rsp->completed = rsp->gpnum; /* Declare the grace period complete. */
trace_rcu_grace_period(rsp->name, rsp->completed, "end");
rsp->fqs_state = RCU_GP_IDLE;
rcu_start_gp(rsp, flags); /* releases root node's rnp->lock. */
}
這個過程最主要的內容就是設置rsp->completed的值,中間多了對node的處理。因爲在rcu_start_gp中也會對node進行處理,當前CPU無法判斷其它CPU是否需要一個寬限期,但它自身還有等待寬限期的回調函數的時候,它確定會有一個新的寬限期馬上開始,所以忽略這個過程。
CPU的寬限期結束處理
這個過程也可以分爲兩個步驟,第一步是檢查寬限期是否結束,第二步是調用已完成的回調函數。
A, CPU檢測寬限期的結束
每個CPU都會定期檢查當前的寬限期是否結束,如果結束將處理自身狀態已經nxtlist表。rcu_process_gp_end就是用來做這個事情:
static void
rcu_process_gp_end(struct rcu_state *rsp, struct rcu_data *rdp)
{
unsigned long flags;
struct rcu_node *rnp;
local_irq_save(flags);
rnp = rdp->mynode;
if (rdp->completed == ACCESS_ONCE(rnp->completed) || /* outside lock. */
!raw_spin_trylock(&rnp->lock)) { /* irqs already off, so later. */
local_irq_restore(flags);
return;
}
__rcu_process_gp_end(rsp, rnp, rdp);
raw_spin_unlock_irqrestore(&rnp->lock, flags);
}
當 rdp->completed與rnp->completed的值不同的時候,會調用__rcu_process_gp_end來完成具體的工作。
static void
__rcu_process_gp_end(struct rcu_state *rsp, struct rcu_node *rnp, struct rcu_data *rdp)
{
/* 之前的寬限期是否完成? */
if (rdp->completed != rnp->completed) {
/* 推進回調函數,即使是NULL指針也沒關係。 */
rdp->nxttail[RCU_DONE_TAIL] = rdp->nxttail[RCU_WAIT_TAIL];
rdp->nxttail[RCU_WAIT_TAIL] = rdp->nxttail[RCU_NEXT_READY_TAIL];
rdp->nxttail[RCU_NEXT_READY_TAIL] = rdp->nxttail[RCU_NEXT_TAIL];
/* 更新completed。 */
rdp->completed = rnp->completed;
trace_rcu_grace_period(rsp->name, rdp->gpnum, "cpuend");
/*
* 如果當前的CPU在外部的靜止的狀態(如離線狀態),
* 可能已經錯過了其它CPU發起的寬限期。所以需要更
* 新gpnum的值,同時要注意不要錯過當前正在運行的
* 寬限期,所以它的值被設置成與rnp->completed相同,
* 此時rnp->gpnum 可以已經加1,那麼後續的調用
* rcu_check_quiescent_state()會去檢測新的寬限期。
*/
if (ULONG_CMP_LT(rdp->gpnum, rdp->completed))
rdp->gpnum = rdp->completed;
/*
* 如果下次的寬限期不需要當前CPU報告靜止狀態,
* 設置qs_pending爲0。
*/
if ((rnp->qsmask & rdp->grpmask) == 0)
rdp->qs_pending = 0;
}
}
這個過程的重點是設置nxttail的值,將根據它來進行下一步的處理。
B,回調函數的調用
static void rcu_do_batch(struct rcu_state *rsp, struct rcu_data *rdp)
{
unsigned long flags;
struct rcu_head *next, *list, **tail;
int bl, count, count_lazy, i;
/* 沒有回調函數,那麼返回。*/
if (!cpu_has_callbacks_ready_to_invoke(rdp)) {
trace_rcu_batch_start(rsp->name, rdp->qlen_lazy, rdp->qlen, 0);
trace_rcu_batch_end(rsp->name, 0, !!ACCESS_ONCE(rdp->nxtlist),
need_resched(), is_idle_task(current),
rcu_is_callbacks_kthread());
return;
}
/*
* 提取回調函數的list,需要禁用中斷,以防止調用call_rcu()。
*/
local_irq_save(flags);
WARN_ON_ONCE(cpu_is_offline(smp_processor_id()));
bl = rdp->blimit;
trace_rcu_batch_start(rsp->name, rdp->qlen_lazy, rdp->qlen, bl);
list = rdp->nxtlist;
/*
* 已經將list指向了nxtlist,此時將nxtlist指向 *rdp->nxttail[RCU_DONE_TAIL]。
* 由於nxttail指向的是 rcu_head中的next指針的地址,所以此處得到的就是next所
* 指向的rcu_head對象。
*/
rdp->nxtlist = *rdp->nxttail[RCU_DONE_TAIL];
/*將*rdp->nxttail[RCU_DONE_TAIL]指向NULL,也就是將list中的最後一個元素的next設置成NULL*/
*rdp->nxttail[RCU_DONE_TAIL] = NULL;
/*tail指向list最後一個元素的next指針的地址*/
tail = rdp->nxttail[RCU_DONE_TAIL];
/*此時rdp->nxttail[RCU_DONE_TAIL]指向的內容已經移出,所以讓它重新指向nxtlist的地址*/
for (i = RCU_NEXT_SIZE - 1; i >= 0; i--)
if (rdp->nxttail[i] == rdp->nxttail[RCU_DONE_TAIL])
rdp->nxttail[i] = &rdp->nxtlist;
local_irq_restore(flags);
/* 調用回調函數 */
count = count_lazy = 0;
while (list) {
next = list->next;
prefetch(next);
debug_rcu_head_unqueue(list);
if (__rcu_reclaim(rsp->name, list))
count_lazy++;
list = next;
/* 當已經全部運行完畢或者CPU有更重要的事情的時候,退出循環。 */
if (++count >= bl &&
(need_resched() ||
(!is_idle_task(current) && !rcu_is_callbacks_kthread())))
break;
}
local_irq_save(flags);
trace_rcu_batch_end(rsp->name, count, !!list, need_resched(),
is_idle_task(current),
rcu_is_callbacks_kthread());
/* 更新數量。並將沒有執行完的回調函數重新放進列表。 */
if (list != NULL) {
*tail = rdp->nxtlist;
rdp->nxtlist = list;
for (i = 0; i < RCU_NEXT_SIZE; i++)
if (&rdp->nxtlist == rdp->nxttail[i])
rdp->nxttail[i] = tail;
else
break;
}
smp_mb(); /* 爲了 rcu_barrier()統計運行過的回調函數 */
rdp->qlen_lazy -= count_lazy;
ACCESS_ONCE(rdp->qlen) -= count;
rdp->n_cbs_invoked += count;
/* Reinstate batch limit if we have worked down the excess. */
if (rdp->blimit == LONG_MAX && rdp->qlen <= qlowmark)
rdp->blimit = blimit;
/* Reset ->qlen_last_fqs_check trigger if enough CBs have drained. */
if (rdp->qlen == 0 && rdp->qlen_last_fqs_check != 0) {
rdp->qlen_last_fqs_check = 0;
rdp->n_force_qs_snap = rsp->n_force_qs;
} else if (rdp->qlen < rdp->qlen_last_fqs_check - qhimark)
rdp->qlen_last_fqs_check = rdp->qlen;
WARN_ON_ONCE((rdp->nxtlist == NULL) != (rdp->qlen == 0));
local_irq_restore(flags);
/* 如果還有回調函數沒有執行,通知再次調用軟中斷 */
if (cpu_has_callbacks_ready_to_invoke(rdp))
invoke_rcu_core();
}
rcu_do_batch主要作用是取出nxtlist中,nxttail[RCU_DONE_TAIL]之前的元素,遍歷執行它們。這時候銷燬過程真正的執行了。這段函數需要仔細想想nxttail的處理。
到此RCU中涉及到的主幹函數介紹完了,但是還需要與進程切換等過程交互。將在下節分析它們。