linux runqueue定义,Linux中多CPU的runqueue及抢占
一、引出
在在嵌入式操作系統(tǒng)中,很多線程都可以為實(shí)時(shí)任務(wù),因?yàn)楫吘惯@些線程很少和人接觸,而是面向任務(wù)的。所有就有一個(gè)搶占的時(shí)機(jī)問題。特別是2.6內(nèi)核中引入了新的內(nèi)核態(tài)搶占任務(wù),所以就可以說一下這個(gè)內(nèi)核態(tài)搶占的實(shí)現(xiàn)。
內(nèi)核態(tài)搶占主要發(fā)生在兩個(gè)時(shí)機(jī),一個(gè)是主動(dòng)的檢測(cè)是否需要搶占,另一個(gè)就是在異常處理完之后的異常判斷。
#define preempt_enable() \
do { \
preempt_enable_no_resched(); \
barrier(); \
preempt_check_resched(); \
} while (0)
這一點(diǎn)和用戶態(tài)的pthread_setcanceltype中也有使用,也就是如果禁止線程的異步取消,在使能之后的第一時(shí)間判斷線程是不是已經(jīng)被取消,包括內(nèi)核態(tài)對(duì)信號(hào)的處理也是如此,例如,當(dāng)sigprocmask開啟一個(gè)信號(hào)屏蔽之后,也需要在第一時(shí)間來判斷系統(tǒng)中是否有未處理的信號(hào),如果有則需要及時(shí)處理,這個(gè)操作是在sys_sigprocmask--->>>recalc_sigpending--->>>set_tsk_thread_flag(t, TIF_SIGPENDING)中完成。
另一個(gè)就是內(nèi)核線程無法預(yù)測(cè)的中斷或者異常處理結(jié)束之后的判斷。
linux-2.6.21\arch\i386\kernel\entry.S:? ret_from_exception(ret_from_intr)--->>>>
#ifdef CONFIG_PREEMPT
ENTRY(resume_kernel)
DISABLE_INTERRUPTS(CLBR_ANY)
cmpl $0,TI_preempt_count(%ebp)?# non-zero preempt_count ?首先判斷線程是否禁止了搶占,如果禁止搶占,則不檢測(cè)是否重新調(diào)度標(biāo)志。
jnz restore_nocheck
need_resched:
movl TI_flags(%ebp), %ecx?# need_resched set ?
testb $_TIF_NEED_RESCHED, %cl檢測(cè)是否需要重新調(diào)度。
jz restore_all
testl $IF_MASK,PT_EFLAGS(%esp)?# interrupts off (exception path) ?
jz restore_all
call preempt_schedule_irq?這里就是第二個(gè)搶占發(fā)生的時(shí)機(jī),就是內(nèi)核線程不可預(yù)測(cè)的時(shí)候發(fā)生的。
jmp need_resched
END(resume_kernel)
在preempt_schedule_irq中引入了一個(gè)比較常見的概念,就是這個(gè)PREEMPT_ACTIVE,
add_preempt_count(PREEMPT_ACTIVE);
/*
* We use bit 30 of the preempt_count toindicate that kernel
*?preemption is occurring.? See include/asm-arm/hardirq.h.
*/
#define PREEMPT_ACTIVE?0x40000000
這個(gè)標(biāo)志位在內(nèi)核中的線程搶占統(tǒng)計(jì)中將會(huì)用到,在schedule函數(shù)中
switch_count = &prev->nivcsw;
if (prev->state && !(preempt_count() & PREEMPT_ACTIVE)) {這里判斷的是搶占標(biāo)志是否被置位,如果沒有置位,也就是如果不是被搶占,則認(rèn)為是自愿放棄CPU,也就是Voluntary釋放CPU,否則認(rèn)為是被搶占。
switch_count = &prev->nvcsw;
if (unlikely((prev->state & TASK_INTERRUPTIBLE) &&
unlikely(signal_pending(prev))))
prev->state = TASK_RUNNING;
else {
if (prev->state == TASK_UNINTERRUPTIBLE)
rq->nr_uninterruptible++;
deactivate_task(prev, rq);
}
}
通過/proc/$PID/status可以看到這個(gè)切換次數(shù)記錄。
static inline void task_context_switch_counts(struct seq_file *m,
struct task_struct *p)
{
seq_printf(m,?"voluntary_ctxt_switches:\t%lu\n"
"nonvoluntary_ctxt_switches:\t%lu\n",
p->nvcsw,
p->nivcsw);
}
從調(diào)度的代碼中可以看到,如果線程禁止了搶占,那么線程是不能執(zhí)行調(diào)度的,這樣可以推出線程在關(guān)掉搶占之后不能睡眠,如果需要等待,應(yīng)該應(yīng)該用spinlock,在asmlinkage void __sched schedule(void)的開始有這個(gè)判斷
if (unlikely(in_atomic() && !current->exit_state)) {
printk(KERN_ERR "BUG: scheduling while atomic: "
"%s/0x%08x/%d\n",
current->comm, preempt_count(), current->pid);
debug_show_held_locks(current);
if (irqs_disabled())
print_irqtrace_events(current);
dump_stack();
}
也就是,如果 線程是禁止搶占之后進(jìn)行調(diào)度,后果是很嚴(yán)重的,直接內(nèi)核就dump_stack了(但是沒有panic,至少對(duì)386是如此)。這一點(diǎn)也容易理解,因?yàn)樵谧栽负头亲栽傅膬蓚€(gè)搶占判斷中,都判斷了線程的preempt_count的值,如果非零就退出,所以應(yīng)該是不能走到這一步的。
二、多核中的運(yùn)行隊(duì)列
這個(gè)在大型服務(wù)器中是比較有用的一個(gè)概念,就是線程在CPU之間的均勻分配或者非均勻分配問題。目的就是讓各個(gè)CPU盡量負(fù)載平衡,不要忙的忙死,閑的閑死。按照計(jì)算機(jī)的原始概念,CPU可以作為一個(gè)資源,然后等待使用這個(gè)資源的線程就需要排隊(duì)。如果要排隊(duì),就需要有一個(gè)約定的地點(diǎn)讓大家在這里排隊(duì),這樣便于管理,比如說先來先服務(wù),然后優(yōu)先級(jí)的判斷等。
在內(nèi)核里,這個(gè)隊(duì)列就是每個(gè)CPU都定義的一個(gè)為struct rq 結(jié)構(gòu)的runqueue變量,這個(gè)是每個(gè)CPU的一個(gè)排隊(duì)區(qū),可以認(rèn)為是CPU的一個(gè)私有資源,并且是靜態(tài)分配,每個(gè)CPU有天生擁有這么一個(gè)隊(duì)列,拿人權(quán)的角度看,這個(gè)也就是CPU的一個(gè)基本權(quán)利,并且是一個(gè)內(nèi)置權(quán)利。當(dāng)CPU存在之后,它的runqueue就存在了。注意:這是一個(gè)容器,它是用來存放它的客戶線程的,所以的線程在這里進(jìn)行匯集和等待;對(duì)每個(gè)CPU來說,它的這個(gè)結(jié)構(gòu)本身是不會(huì)變化的,變化的只是這個(gè)隊(duì)列中的線程,一個(gè)線程可以在這個(gè)CPU隊(duì)列里等待并運(yùn)行,也可以在另一個(gè)CPU中運(yùn)行,當(dāng)然不能同時(shí)運(yùn)行。這個(gè)變量的定義為
static DEFINE_PER_CPU(struct rq, runqueues);
現(xiàn)在,一個(gè)CPU需要服務(wù)的所有的線程都在這個(gè)結(jié)構(gòu)里,所以也就包含了實(shí)時(shí)線程組和非實(shí)時(shí)線程組,它們?cè)趓q的體現(xiàn)為兩個(gè)成員。
struct cfs_rq cfs;
struct rt_rq rt;
同一個(gè)CPU上的兩個(gè)運(yùn)行隊(duì)列采用不同的調(diào)度策略,實(shí)時(shí)策略也就是內(nèi)核中希望實(shí)現(xiàn)的O(1)調(diào)度器,所以它的內(nèi)容中包含了100個(gè)實(shí)時(shí)隊(duì)列結(jié)構(gòu)。這個(gè)結(jié)構(gòu)也和信號(hào)相同,首先有一個(gè)位圖,表示這個(gè)優(yōu)先級(jí)是否有可運(yùn)行線程,然后有一個(gè)指針數(shù)組,指向各個(gè)優(yōu)先級(jí)的就緒線程,前者用于快速判斷最高優(yōu)先級(jí)隊(duì)列下表,后者用于真正取出該優(yōu)先級(jí)的線程。
對(duì)于cfs調(diào)度,它一般是為了保證系統(tǒng)中線程對(duì)用戶的及時(shí)響應(yīng),也就是說這個(gè)線程和用戶交互,不能讓用戶感覺到某個(gè)任務(wù)有“卡”的感覺。保證這個(gè)流暢的方法就是快速切換,從而在某個(gè)時(shí)間段內(nèi)所有的cfs任務(wù)都可以被運(yùn)行一次。也就是不會(huì)出現(xiàn)某個(gè)任務(wù)跑的很歡樂,另外某個(gè)跑的很苦逼。
這個(gè)的實(shí)現(xiàn)就是大家經(jīng)常說的內(nèi)核紅黑樹結(jié)構(gòu),很多地方都有說明。這里注意紅黑樹是一個(gè)有序樹,有序就需要有鍵值,并且有鍵值的比較方法。在內(nèi)核中這個(gè)鍵值就是每個(gè)線程的一個(gè)調(diào)度實(shí)體的vruntime成員,在linux-2.6.37.1\kernel\sched_fair.c中我們看到的鍵值比較為put_prev_task_fair--->>>put_prev_entity--->>>__enqueue_entity--->>>entity_key
static inline s64 entity_key(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
return se->vruntime?- cfs_rq->min_vruntime;
}
而這個(gè)調(diào)度實(shí)體是一個(gè)抽象的概念,它可能考慮到了任務(wù)組的調(diào)度吧。實(shí)時(shí)任務(wù)和cfs任務(wù)的調(diào)度實(shí)體結(jié)構(gòu)并不相同,并且這個(gè)兩個(gè)在task_struct結(jié)構(gòu)中兩個(gè)并不是一個(gè)union,而是實(shí)實(shí)在在兩個(gè)獨(dú)立的實(shí)體,在task_struct結(jié)構(gòu)中可以看到:
struct sched_entity se;
struct sched_rt_entity rt;
這個(gè)可能是為了保證線程的優(yōu)先級(jí)可以在運(yùn)行時(shí)通過sys_sched_setscheduler來動(dòng)態(tài)修改而設(shè)置的吧。
對(duì)于一個(gè)runqueue,它對(duì)應(yīng)一個(gè)CPU,由于一個(gè)CPU上只能同時(shí)運(yùn)行一個(gè)線程,所以一個(gè)runqueue只有一個(gè)curr,因?yàn)槲覀兛梢钥吹揭粋€(gè)rq有一個(gè)curr結(jié)構(gòu)
struct task_struct *curr, *idle, *stop;
注意的是,同一時(shí)間真正使用CPU的線程只有一個(gè),但是一個(gè)CPU上可以有多個(gè)線程都是處于就緒狀態(tài),也就是running狀態(tài),我們可以看到這個(gè)running在rq、rt_rq、cfs_rq中都有相應(yīng)的成員(nr_running)。這里說的running并不是他們?cè)谶\(yùn)行,而是可運(yùn)行,他們是用來進(jìn)行CPU之間負(fù)載均衡的,和是否正在CPU上運(yùn)行沒有直接關(guān)系。反過來,一個(gè)線程是否處于可運(yùn)行狀態(tài),是通過p->se.on_rq 來判斷的。
我們看一下系統(tǒng)喚醒一個(gè)線程時(shí)的操作:
wake_up_new_task--->>>activate_task--->>enqueue_task
p->se.on_rq = 1; 這里可以看到,實(shí)時(shí)任務(wù)也是用了task_struct中的struct sched_entity se;成員,所以可以認(rèn)為這是一個(gè)線程固有的成員,而struct sched_rt_entity rt;是為rt線程專門另外設(shè)置的一個(gè)附加成員,它們不是互斥或者說可替代的,而是基礎(chǔ)和附加屬性的關(guān)系。
而對(duì)于某個(gè)CPU上正在運(yùn)行的線程的判斷則使用的是
static inline int task_current(struct rq *rq, struct task_struct *p)
{
return rq->curr == p;
}
而對(duì)于nr_running的設(shè)置為
wake_up_new_task--->>>activate_task--->>inc_nr_running(rq);
static void inc_nr_running(struct rq *rq)
{
rq->nr_running++;
}
標(biāo)簽:task,搶占,rq,runqueue,線程,中多,CPU,struct
來源: https://www.cnblogs.com/tsecer/p/10485824.html
總結(jié)
以上是生活随笔為你收集整理的linux runqueue定义,Linux中多CPU的runqueue及抢占的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: c语言编程题2^0+2^1+……+2e6
- 下一篇: linux 其他常用命令