TREE RCU實現之二 —— 主幹函數

         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中涉及到的主幹函數介紹完了,但是還需要與進程切換等過程交互。將在下節分析它們。

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章