linux系統調度之時間【轉】

轉自:https://blog.csdn.net/eleven_xiy/article/details/71175347

【摘要】

linux系統的調度過程是基於時間子系統實現的。無論判斷一個系統的性能還是計算一個進程的cpu佔用率等,其本質都是進程或中斷等佔有cpu的時間。瞭解linux中時間的概念,尤爲重要。本文將爲你剖析一下與進程和調度有關的幾個關鍵時間。若想了解整個linux的時間子系統,請參考博文.

【背景】

本文主要是爲介紹調度子系統做鋪墊。

【正文】

在linux時間子系統一文中我們知道,linux每個時鐘中斷(又稱tick中斷)處理中都會更新進程時間,即update_process_times。所以本文把update_process_times函數作爲入口點進行分析,直接上代碼。

1 時鐘中斷中更新進程相關的時間:

普通定時器 :arch_timer_handler_phys->tick_handle_periodic->update_process_times;

高精度定時器 : tick_sched_handle->update_process_times;

void update_process_times(int user_tick)
{
struct task_struct *p=current;
/* 找到多核中的cpu id */
int cpu = smp_processor_id();
/* user_tick根據cpu模式判斷是用戶態還是內核態。linux統計時間的方式:
1 基於整個cpu的統計,user_tick表示cpu在用戶態,內核態還是中斷狀態。此處把一個tick的時間累加到kstat_cpu(i).cpustat.
/proc/stat中的統計值是在此處統計的,表示cpu在用戶態,內核態,中斷中各佔用多少時間,對應 stat.c(fs/proc):static int __init proc_stat_init(void);
2 基於進程的統計。linux還有一種統計時間的方法更細化,統計的是調度實體上的時間sum_exec_runtime,它在sched_clock_cpu函數中基於timer計算
/proc/pid/stat,/proc/pid/task/tid/stat中的時間是在此處統計的,它統計了一個進程/線程佔用cpu的時間,對應do_task_stat實現。
*/
account_process_tick(p,user_tick);
/*
此處負責系統中的定時器到期操作。
並未真正處理,只是實現raise_softirq(TIMER_SOFTIRQ),當這個tick中斷退出irq_exit時,會處理TIMER_SOFTIRQ軟中斷;
TIMER_SOFTIRQ軟中斷處理含稅run_timer_softirq()中負責處理到期的定時器。
*/
run_locl_timers();
/* 與進程和調度用過的時間參數 */
scheduler_tick();
}
2 每個tick時鐘中斷中都更新調度相關的時間。

update_process_times->scheduler_tick()

/*
* This function gets called by the timer code, with HZ frequency.
* We call it with interrupts disabled.
定時器中斷時改變時間戳update_process_times->
*/
void scheduler_tick(void)
{
/* 處理當前中斷的cpu id */
int cpu = smp_processor_id();
/* 每個cpu上都有一個運行隊列,運行隊列rq上包含實時進程運行隊列rt_rq和普通進程運行隊列cfs_rq */
struct rq *rq = cpu_rq(cpu);
struct task_struct *curr = rq->curr;
/* 該函數爲空實現:CONFIG_HAVE_UNSTABLE_SCHED_CLOCK
每個tick時鐘中斷中處理如下:
sched_clock_tick()中更新sched_clock_data結構體(調用關係scheduler_tick()->sched_clock_tick())。
sched_clock_cpu()中通過sched_clock_data結構計算時間差(調用關係:scheduler_tick()->update_rq_clock()),
update_rq_clock中通過sched_cock_cpu函數計算就緒調度隊列上的時間:rq->clock;
*/
sched_clock_tick();
raw_spin_lock(&rq->lock);
/* 更新當前調度隊列rq的clock */
update_rq_clock(rq);
update_cpu_load_active(rq);
/*
普通進程task_tick_fair
實時進程task_tick_rt
實時進程在task_tick_rt中檢測時間片是否用完。
*/
curr->sched_class->task_tick(rq, curr, 0);
raw_spin_unlock(&rq->lock);
perf_event_task_tick();
#ifdef CONFIG_SMP
rq->idle_balance = idle_cpu(cpu);
trigger_load_balance(rq, cpu);
#endif
rq_last_tick_reset(rq);
}
void sched_clock_tick(void)

{

struct sched_clock_data *scd;

u64 now,now_gtod;

now_gtod = ktime_to_ns(ktime_get());

now = sched_clock();

scd->tick_raw = now;

scd->tick_gtod=now_gtod;

sched_clock_local(scd);//scd->clock初始化

}

2.1 更新運行隊列上時間:

1) 時鐘中斷中更新:update_process_times->scheduler_tick()->update_rq_clock()

void update_rq_clock(struct rq *rq)
{
s64 delta;
if (rq->skip_clock_update > 0)
return;
/*
兩次相鄰兩次週期性調度器運行的時間差 */
delta = sched_clock_cpu(cpu_of(rq)) - rq->clock;
/*
更新運行隊列上的時鐘:更新rq->clock_task 與rq->clock
*/
rq->clock += delta;
/*
此處更新rq->clock_task+=delta,默認配置不開情況rq->clock_task=rq->clock;如果CONFIG_IRQ_TIME_ACCOUNTING配置打開,則rq->clock_task需要減去中斷上的時間。
所以可以認爲rq->clock隊列時間每個tick中斷都統計在內,甚至包括中斷處理的時間;
而rq->clock_task是進程真正佔用的時間,只不過很多情況下rq->clock_task=rq->clock,調度過程中使用rq->clock_task .
*/
update_rq_clock_task(rq, delta);
}
2)進程出入調度隊列時更新activate_task/deactivate_task->enqueue_task/dequeue_task->update_rq_clock:

static void enqueue_task(struct rq *rq, struct task_struct *p, int flags)
{
update_rq_clock(rq);
sched_info_queued(p);
/*
__setscheduler->
SCHED_NORMAL: enqueue_task_fair
SCHED_RR: enqueue_task_rt
*/
p->sched_class->enqueue_task(rq, p, flags);
}
static void dequeue_task(struct rq *rq, struct task_struct *p, int flags)
{
update_rq_clock(rq);
sched_info_dequeued(p);
p->sched_class->dequeue_task(rq, p, flags);
}
/*wake_up_new_task->*/
void activate_task(struct rq *rq, struct task_struct *p, int flags)
{
if (task_contributes_to_load(p))
rq->nr_uninterruptible--;
/*
__setscheduler-> enqueue_task_fair /enqueue_task_rt
*/
enqueue_task(rq, p, flags);
}
void deactivate_task(struct rq *rq, struct task_struct *p, int flags)
{
if (task_contributes_to_load(p))
rq->nr_uninterruptible++;
dequeue_task(rq, p, flags);
}
2.2 更新實時進程運行隊列上調度相關的時間:

1)與進程調度有關的時間存放在如下結構體,一般task_struct結構中se表示sched_entity(如:current->se.exex_start)

struct sched_entity {
u64 exec_start;
u64 sum_exec_runtime;
u64 vruntime;
u64 prev_sum_exec_runtime;
}
其中 實時進程一般只使用exec_start和sum_exec_runtime;普通進程中這幾個成員變量都會被使用.sched_entity中時間的初始化函數:

staticvoid __sched_fork(struct task_struct *p)
{
/* se是普通進程的調度實體struct sched_entity */
/*
見下面解釋;
*/
p->se.exec_start = 0;
/*進程執行的總時間大小*/
p->se.sum_exec_runtime = 0;
/*
進程上一次運行總時間,即醒來時間。調度過程選擇下一個進程是初始爲
se.sum_exec_runtime,
進程最近一次已獲取cpu的時間:
sum_exec_runtime-prev_sum_exec_runtime
*/
p->se.prev_sum_exec_runtime = 0;
p->se.nr_migrations = 0;
/*進程在一個調度週期內的虛擬調度時間 */
p->se.vruntime = 0;
}
2)update_process_times->scheduler_tick()->task_tick_rt()

/*
定時器中斷時更新實時進程時間update_process_times->scheduler_tick->:
*/
static void task_tick_rt(struct rq *rq, struct task_struct *p, int queued)
{
struct sched_rt_entity *rt_se = &p->rt;
/*
更新實時進程相關統計時間
*/
update_curr_rt(rq);
watchdog(rq, p);
/*
* RR tasks need a special form of timeslice management.
* FIFO tasks have no timeslices.
*/
/*
檢查SCHED_RR進程時間片是否用光;SCHED_FIFO只有主動放棄CPU使用權;
*/
if (p->policy != SCHED_RR)
return;
/*
時間片減一,若未運行完時間則直接返回,
否則再次分配時間片,加入隊列尾部,設置調度標記 TIF_NEED_RESCHED
*/
if (--p->rt.time_slice)
return;
/*
實時進程SCHED_RR
初始時間片爲100 ms,即time_slice=10個時鐘中斷
*/
p->rt.time_slice = sched_rr_timeslice;
/*
* Requeue to the end of queue if we (and all of our ancestors) are the
* only element on the queue
*/
for_each_sched_rt_entity(rt_se) {
if (rt_se->run_list.prev != rt_se->run_list.next) {
/*
如果時間片耗盡,則把實時進程SCHED_RR的運行實體從active優先級隊列上刪除,如此schedule調度過程無法
通過運行實體找到該進程p進行調度,從而實現系統調度器。
那麼進程p何時能重新加入調度隊列呢?例如wake_up_process過程可以重新入隊列,具體可參照調度子系統介紹
*/
requeue_task_rt(rq, p, 0);
/* 該標記表示需要重新調度 */
set_tsk_need_resched(p);
return;
}
}
}
3)實時進程統計時間更新:update_process_times->scheduler_tick()->task_tick_rt()->update_curr_rt()

此時更新實時進程佔有cpu的總時間se.sum_exec_runtime和一個tick間隔內進程開始執行的時間se.exec_start.

/
* Update the current task's runtime statistics. Skip current tasks that
* are not in our scheduling class.
*/
static void update_curr_rt(struct rq *rq)
{
struct task_struct *curr = rq->curr;
struct sched_rt_entity *rt_se = &curr->rt;
struct rt_rq *rt_rq = rt_rq_of_se(rt_se);
u64 delta_exec;
if (curr->sched_class != &rt_sched_class)
return;
/*
exec_start分析:
1)exec_start表示進程在一個時鐘中斷間隔內開始運行的時間,主要用於在時鐘中斷中計算時間差,
該時間差主要用於計算進程獲取cpu的時間(se.sum_exec_runtime累加該時間差:update_curr : rq->clock_task-exec_start),
2) 此時clock_task爲時鐘中斷開始時間,exec_start可以爲之前時鐘中斷中任一時間,且不必是時鐘中斷開始的時間點。
exec_start在每個tick中斷中都更新爲隊列時間rq->clock_task.
3) 時鐘中斷到來時clock_task先在update_rq_clock更新。然後再用clock_task減去exec_start即獲取當前進程在一個時鐘中斷中獲取到cpu的時間。
如果exec_start恰好等於上一個時鐘中斷時隊列上時間clock_task,則表示進程在這個時鐘中斷中一直佔有cpu,未發生調度。
delta_exec分析:
delta_exec表示進程在一個時鐘週期內,所獲得的cpu執行權的時間。 把delta_exec累加,就是進程獲取cpu的總時間
注意此處使用rq->clock_task而不是rq->clock,注意update_rq_clock中二者區別。
*/
delta_exec = rq->clock_task - curr->se.exec_start;
if (unlikely((s64)delta_exec <= 0))
return;
schedstat_set(curr->se.statistics.exec_max,
max(curr->se.statistics.exec_max, delta_exec));
/* sum_exec_runtime和exec_start是如何更新的?
更新當前進程總運行時間,進程的運行的時間等於進程在每個tick時鐘中斷中運行的時間的和,即rq運行隊列上的時間減去這個時鐘中斷間隔內進程開始執行的時間;
舉例說明:從第n到n+1時鐘中斷,此時cpu處理第n+1個tick中斷。(相差1HZ,假設10ms)。
1)如果進程p一直沒有被調度到,即current!=p,則顯然進程p的p->se.sum_exec_runtime不會累加。
2)如果進程p一直佔有cpu,即current=p,在第n個時鐘中斷時p->se.exec_start更新爲第n個時鐘中斷時的隊列時間rq->clock_task;
注意此時rq->clock_task 是第n+1個時鐘中斷時的隊列時間rq->clock_task。
通過上面知道delta_exec = rq->clock_task - curr->se.exec_start;,所以delta剛好是一個tick的時間(10ms)。
3)如果進程p在第n個時鐘中斷髮生時沒有執行,即current!=p,而在第n個時鐘中斷髮生後開始執行,即current=p,此處說明第n個tick中斷之後有調度發生,
而調度過程會在選擇下一個進程pick_next_task->pick_next_task_rt中將p->se.exec_start=rq->clock_start,(注意選擇下一進程之前,會更新隊列時間)
從而實現將進程p在一個時鐘中斷中的起始執行時間賦值給exec_start;注意此時rq->clock_task 是第n+1個時鐘中斷時的隊列時間rq->clock_task。
通過上面知道delta_exec = rq->clock_task - curr->se.exec_start;,所以delta小於一個tick的時間(10ms)。
*/
curr->se.sum_exec_runtime += delta_exec;
account_group_exec_runtime(curr, delta_exec);
/*
tick中斷處理中更新進程在一個時鐘中斷間隔內開始運行的時間;選擇下一個進程時(pick_next_task_rt)也有可能更新
*/
curr->se.exec_start = rq->clock_task;
cpuacct_charge(curr, delta_exec);
sched_rt_avg_update(rq, delta_exec);
/*開啓sysctl_sched_rt_runtime 表示實時進程的運行時間是0.95s */
if (!rt_bandwidth_enabled())
return;
for_each_sched_rt_entity(rt_se) {
rt_rq = rt_rq_of_se(rt_se);
if (sched_rt_runtime(rt_rq) != RUNTIME_INF) {
raw_spin_lock(&rt_rq->rt_runtime_lock);
rt_rq->rt_time += delta_exec;
if (sched_rt_runtime_exceeded(rt_rq))
resched_task(curr);
raw_spin_unlock(&rt_rq->rt_runtime_lock);
}
}
}
2.3 更新普通進程調度相關時間:

1) tick中斷處理中的更新: update_process_times->scheduler_tick()->task_tick_fair()->entity_tick->update_curr()

static void update_curr(struct cfs_rq *cfs_rq)
{
struct sched_entity *curr = cfs_rq->curr;
u64 now = rq_of(cfs_rq)->clock_task;
unsigned long delta_exec;
if (unlikely(!curr))
return;
/*
* Get the amount of time the current task was running
* since the last time we changed load (this cannot
* overflow on 32 bits):
*/
/*
此處參考update_curr_rt中對exec_start的解釋;
*/
delta_exec = (unsigned long)(now - curr->exec_start);
if (!delta_exec)
return;
__update_curr(cfs_rq, curr, delta_exec);
/*更新進程下次運行的起始時間
如果被搶佔,下次調度時將會更新
*/
curr->exec_start = now;
if (entity_is_task(curr)) {
struct task_struct *curtask = task_of(curr);
trace_sched_stat_runtime(curtask, delta_exec, curr->vruntime);
cpuacct_charge(curtask, delta_exec);
account_group_exec_runtime(curtask, delta_exec);
}
更新當前進程運行時間統計數據update_curr()->__update_curr()

/*
* Update the current task's runtime statistics. Skip current tasks that
* are not in our scheduling class.
*/
static inline void __update_curr(struct cfs_rq *cfs_rq, struct sched_entity *curr,
unsigned long delta_exec)
{
unsigned long delta_exec_weighted;
schedstat_set(curr->statistics.exec_max,
max((u64)delta_exec, curr->statistics.exec_max));
/* 更新該進程獲得cpu執行權的總時間 ,可參考update_curr_rt函數中sum_exec_runtime累計處的解釋。
此處更新進程下一次的運行時間:表示進程在一個時鐘中斷內開始運行的時間 時鐘中斷到來時clock_task先在update_rq_clock更新。
然後再用clock_task減去exec_start即獲取當前進程在一個時鐘中斷中獲取到cpu的時間。如果exec_start恰好等於上一個時鐘中斷時
隊列上時間clock_task,則表示進程在這個時鐘中斷中未發生調度。
*/
curr->sum_exec_runtime += delta_exec;
schedstat_add(cfs_rq, exec_clock, delta_exec);
/*
calc_delta_fair用來將真實時間轉化爲虛擬時間。進程的優先級不同,它在系統中的地位(權重)也不同,
進程的優先級越高,它的虛擬時間走的越慢。
*/
delta_exec_weighted = calc_delta_fair(delta_exec, curr);
/*更新該進程獲得cpu執行權的虛擬時間*/
curr->vruntime += delta_exec_weighted;
update_min_vruntime(cfs_rq);
}
2)無論普通進程還是實時進程,都會在選擇下一進程時更新一個進程在一個時鐘中斷中開始執行的時間exec_start,下面以

普通進程爲例進行介紹pick_next_task_fair->set_next_entity():

ps:實時進程在pick_next_task_rt()->__pick_next_task_rt()中更新se.exec_start;

static void set_next_entity(struct cfs_rq *cfs_rq, struct sched_entity *se)
{
/* 'current' is not kept within the tree. */
if (se->on_rq) {
/*
* Any task has to be enqueued before it get to execute on
* a CPU. So account for the time it spent waiting on the
* runqueue.
*/
update_stats_wait_end(cfs_rq, se);
__dequeue_entity(cfs_rq, se);
}
/*
update_stats_curr_start()函數實現:se->exec_start =rq_of(cfs_rq)->clock_task
調度器在選擇下一個進程執行時,會更新exec_start爲隊列時間rq->clock_task
*/
update_stats_curr_start(cfs_rq, se);
cfs_rq->curr = se;
#ifdef CONFIG_SCHEDSTATS
/*
* Track our maximum slice length, if the CPU's load is at
* least twice that of our own weight (i.e. dont track it
* when there are only lesser-weight tasks around):
*/
if (rq_of(cfs_rq)->load.weight >= 2*se->load.weight) {
se->statistics.slice_max = max(se->statistics.slice_max,
se->sum_exec_runtime - se->prev_sum_exec_runtime);
#endif
/*
prev_sum_exec_runtime表示進程截止目前獲取cpu的時間(即執行時間);
可以使用sum_exec_runtime- prev_sum_exec_runtime計算進程最近一次調度內獲取cpu使用權的時間。
不過sum_exec_runtime是使用exec_start計算出的時間差的累加。
*/
se->prev_sum_exec_runtime = se->sum_exec_runtime;
}
2.4 以上討論了進程與調度相關時間的更新,可以關注三個方面:

1) tick中斷處理過程中的更新,主要通過update_rq_clock函數更新調度隊列上的時間rq->clock_task;

通過task_tick_rt/task_tick_fair更新進程在一個時鐘中斷內開始執行的時間exec_start和進程執行的總時間,sum_exec_runtime;

2) 出入調度隊列時,也會更新調度隊列上的時間rq->clock_task,schedule調度過程也可能更新隊列時間。

3) 無論普通進程還是實時進程,都會在選擇下一進程時更新一個進程在一個時鐘中斷中開始執行的時間exec_start。

【總結】

本文討論了每個tick時鐘中斷中,對 與進程和調度相關時間 的更新,爲後續介紹調度子系統做鋪墊。

注意:本文討論的時間不是牆上時間(如:年月日時分),而是運行時間,比如一個程序運行了1s,則時間戳就是1s,

而不是某年某月某時某分等等諸如此類.

【其他】

1 查看進程時間的方法:

void show_taskTime(struct task_struct *tsk)
{
unsigned long long fork_time;
unsigned long long new_start_time;
unsigned long long sum_exec_runtime;
unsigned long long prev_sum_exec_runtime;
unsigned long long curr_fsum_exec_runtime;
int policy;
/*以下時間:單位ns;從0開始累計*/
/* 進程創建的時間點; */
fork_time=(unsigned long long)tsk->real_start_time.tv_sec*1000000000+
+ tsk->real_start_time。tv_nsec;
/* 進程最近一次開始執行的時間點 */
new_start_time=tsk->se.exec_start;
/*進程一共執行(即獲取到cpu)的時間長度*/
sum_exec_runtime=tsk->se.sum_exec_runtime;
/*進程上一次一共執行的時間長度;注意在schedule選擇下一進程時,
下一進程tsk->se.prev_sum_exec_runtime初始化爲sum_exec_runtime;
而當前進程的prev_sum_exec_runtime是在之前切換到當前進程時初始化的;
*/
prev_sum_exec_runtime=tsk->se.prev_sum_exec_runtime;
/*當前進程最近一次獲取到cpu的時間長度;*/
curr_sum_exec_runtime=tsk->se.sum_exec_runtime-tsk->se.prev_sum_exec_runtime;
policy=tsk->policy;
printk("[%s_%d_%d:%llu_%llu_%llu_%llu_%llu]\n",tsk->comm,tsk->pid,policy,
fork_time,new_start_time,sum_exec_runtime,prev_sum_exec_runtime,
curr_sum_exec_runtime);
}
2 查看調試信息方法:

void show_debugInfo(void)
{
/*查看當前進程的執行時間*/
show_taskTime(current);
/*查看系統內存使用情況*/
show_mem(0);
/*查看當前進程的棧*/
show_stack(current,NULL);
}
3 爲計算進程最近一次獲取cpu的時間長度,需要使用prev_sum_exec_runtime參數,而 對實時進程來說,沒有使用task->se.prev_sum_exec_runtime,所以需要修改kernel代碼如下:(注意如果是當前進程,則sum_exec_runtime-prev_sum_exec_runtime計算的是當前進程獲取cpu以來的執行時間)

/*schedule->pick_next_task_rt->_pick_next_task_rt選擇下一進程時*/

static struct task_struct *_pick_next_task_rt(struct rq *rq)
{
p->se.exec_start=rq->clock_task;
/* 添加如下代碼 */
p->se.prev_sum_exec_runtime=p->se.sum_exec_runtime;
/*end*/
return p;
}
【c標準接口】

times()函數: 系統調用所在文件 kernel/sys.c;

Linux內核中的jiffies及其作用介紹及jiffies等相關函數詳解;

使用系統sched_clock計算時間:

static void update_time_stamp(void)
{
unsigned long long timeNow = 0;
unsigned long long timeInc = 0;
static unsigned long long preTimeStamp = 0;
static unsigned long long timePtsNs = 0;
static unsigned long long timePts = 0;//返回ns

timeNow = sched_clock(); //返回ns
//64bit溢出
if(preTimeStamp > timeNow)
{
timeInc = (0xFFFFFFFFFFFFFFFF-preTimeStamp)+timeNow;
}
else
{
timeInc = timeNow - preTimeStamp;
}
timeInc += timePtsNs;//timePtsNs爲上一次計算的精度損失.timePts爲毫秒級,因此有損失,需要在增量timeInc上加timePtsNs
/*timePtsNs爲除後餘數;do_div計算後timeInc爲除後結果:毫秒*/
timePtsNs = do_div(timeInc,1000000);
timePts = timePts + timeInc; //timepts爲要獲取的ms時間戳
preTimeStamp = timeNow;

return ;
}
sched_clock->sched_clock_func=sched_clock_32->read_sched_clock()

read_sched_clock初始化可以在setup_sched_clock中完成;

如1arch/arm/mach-xx/timer.c中通過setup_sched_clock設置(read_sched_clock=xx_read_sched_clock)

xx_read_sched_clock實現讀定時器.

2 sched_clock_register(arch_timer_read_counter);(read_sched_clock=arch_timer_read_counter)

中斷的負載均衡:
1) echo 4 > /proc/irq/49/smp_affinity
4=0100 表示bit3=1;即49號中斷在cpu2上處理;
可以通過 cat /proc/interrupts查看中斷號;
可以通過 cat /proc/softirqs 查看中斷數量;

進程的負載均衡:
cat /proc/loadavg 查看負載均衡參數;uptime命令也可以查看.
loadavg:表示系統的平均負荷;
參考文章:load average
top -d 1 查看進程cpu佔用率
敲入c :查看不同核;敲入H:查看線程cpu佔用率

在linux下,top -H查看線程情況,包括CPU,內存等佔用情況。

進入到top環境後,可以使用P來按照CPU排序,使用M按照內存排序。

cpu stall

進程、線程的時間: /proc/pid/sched ; /proc/pid/task/tid/sched
————————————————
版權聲明:本文爲CSDN博主「eleven_xiy」的原創文章,遵循CC 4.0 BY-SA版權協議,轉載請附上原文出處鏈接及本聲明。
原文鏈接:https://blog.csdn.net/eleven_xiy/article/details/71175347

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