Process Scheduling Renato de Castro Dutra COPPE/UFRJ Jul/2006 Baseado no capítulo 4 do livro Linux Kernel Development 2 nd Edition de Robert Love Agenda 1. 2. 3. 4. 5. 6. Linux kernel Escalonador Runqueue Vetores de Prioridade Sleeping e Wakeup Balanceamento de Carga 7. Chaveamento de Contexto 8. System Calls 9. Discussão 10. Futuro do Escalonador 11. Referências 1.1. Linux Kernel • Linux Kernel 2.6.17.4 (estável) – Kernel tornou-se preemptivo no Kernel 2.5 – Robert Love foi o principal articulador • Linux Kernel 2.4 (estável) – Kernel não-preemptivo • O que é kernel preemptivo ? 1.2. Kernel Preemptivo • Os processos são preemptivos (podem ser interrompidos) – se um processo A entra no estado TASK_RUNNING (processo runnable), o kernel checa se a prioridade dinâmica do processo A é maior que a prioridade do processo que está rodando – Se é, a execução do processo corrente é interrompida e o escalonador é chamado para selecionar um outro processo para rodar (normalmente, o processo A) – Um processo também pode entrar em preempção se o timeslice expira (este valor é pré-determinado, calculado dinamicamente) » Se isto ocorre, o campo need_resched do processo corrente fica ativo, e o escalonador é chamado quando o tempo do interrupt handler termina ( ou seja, kernel libera o interrupt) • Processo preemptivo não é suspenso, desde que ele fica no estado TASK_RUNNING, apenas não usa CPU. 1.3. Escalonador – Função – componente do Kernel que seleciona o processo que vai rodar primeiro – Pode ser visto como o elemento que divide os recursos finitos do tempo do processador entre os processos com estado TASK_RUNNING – É a base da multi-tarefa, dando a impressão de que vários processos estão rodando simultaneamente – Três formas de escalonamento: – FIFO – RoundRobin – Prioridade (calculada dinamicamente) 1.4. Escalonamento Baseado em Prioridade Dinâmica • Inicia com uma prioridade default e permite ao escalonador aumentar ou diminuir, dinamicamente, a prioridade de acordo com seus objetivos. – Duas faixas de Prioridade: • Nice Range (default = 0) -20 0 +19 Mínimo Timeslice Máximo Timeslice Prioridade baixa • Real-Time Range 0 +99 Prioridade baixa • Um processo Real-Time tem sempre maior prioridade do que um processo normal! 1.5. Timeslice* • Valor numérico que representa quanto tempo uma tarefa pode rodar até ser interrompida na preempção. • Este valor de Timeslice é difícil de estimar, nem deve ser curto demais, nem longo demais… – Curto – muito do tempo será perdido na troca de processos – Longo – processos interativos vão se tornar tediosos Mínimo 5 ms baixa prioridade alta prioridade menos interativo mais interativo Default 100 ms Máximo 800 ms • Processos não precisam gastar todo seu Timeslice em uma rodada somente. Pode rodar em pedaços separados. • Ao fim do Timeslice o processo é considerado expirado e só poderá rodar após todos os Timeslices dos outros processos terem terminado. Então o escalonador fará uma nova atribuição de Timeslices. *Em alguns sistemas operacionais também chamado de quantum ou processor slice 1.6. Equação de Timeslice 164 #define SCALE_PRIO(x, prio) \ 165 max(x * (MAX_PRIO - prio) / (MAX_USER_PRIO/2), MIN_TIMESLICE) • Por pior que seja a prioridade do processo, o menor valor de Timeslice será MIN_TIMESLICE = 5ms • O processo pode quebrar seu Timeslice utilizando a função TIMESLICE_GRANULARITY: 135 #define TIMESLICE_GRANULARITY(p) (GRANULARITY * \ 136 (1 << (((MAX_BONUS - CURRENT_BONUS(p)) ? : 1) - 1))) 1.7. Objetivos para o Kernel 2.5 • • • • • • • O(1) scheduler preemptive kernel latency improvements redesigned block layer improved VM subsystem improved threading support new sound layer 1.8. Objetivos para o Escalonador O(1) • Prover complexidade O(1) ! • Escalabilidade SMP perfeita – Quando aumenta o número de processadores o escalonamento é escalável. • Afinidade SMP melhorada – a troca de processos entre processadores apenas para fim de balanceamento da runqueue, evitando efeito “ping-pong”. 2. Escalonador O(1) • Escalonador O(1) – Decidir qual processo vai rodar em tempo constante, independente do número de processos. – Antigamente O(N): – Lista única de processos N: Para cada processo N runnable{ Acha a dignidade deste processo; Se (este é o processo mais digno ainda) { Lembra isto; } } Roda o processo mais digno; – Mesma prioridade -> Round Robin Escala linearmente com o número de processos ! 2.1. Escalonador O(1) (cont.) • O(1) – utiliza Listas de Prioridade • • • • • Pegue a Lista de Prioridade Mais Alta que tenha processos Pegue o primeiro processo desta Lista de Prioridade Mais Alta Rode este processo Como fazer isto ? Agradecimentos ao Ingo Molnar! 12 * 2002-01-04 New ultra-scalable O(1) scheduler by Ingo Molnar: 13 * hybrid priority-list and round-robin design with 14 * an array-switch method of distributing timeslices 15 * and per-CPU runqueues. Cleanups and useful suggestions 16 * by Davide Libenzi, preemptible kernel bits by Robert Love. Fonte: Linux-kernel-sched_c.htm 2.2. Gráfico O(N) x O(1) Fonte: Timo Hönig – Linux Magazin http://www.linuxmagazin.de/Artikel/ausgabe/2004/02/038_ scheduler/scheduler.html 1 CPU: Pentium III 1 GHz, 512 MByte RAM 2 CPU: Pentium III 850 MHz, 1 GByte RAM 4 CPU: Pentium III 700 MHz, 4 GByte RAM 8 CPU: Pentium III 700 MHz, 8 GByte RAM v.2.4 v.2.6 3. Runqueue • Estrutura básica do Escalonador é a runqueue • É uma lista de processos runnable em um dado processador • Adicionalmente contém informação de escalonamento por processador 3.1. Struct Runqueue 198 struct runqueue { 199 spinlock_t lock; 200 201 /* 202 * nr_running and cpu_load should be in the same cacheline because 203 * remote CPUs use both these fields when doing load calculation. 204 */ 205 unsigned long nr_running; 206 #ifdef CONFIG_SMP 207 unsigned long cpu_load; 208 #endif 209 unsigned long long nr_switches; 210 211 /* 212 * This is part of a global counter where only the total sum 213 * over all CPUs matters. A task can increase this counter on 214 * one CPU and if it got migrated afterwards it may decrease 215 * it on another CPU. Always updated under the runqueue lock: 216 */ 217 unsigned long nr_uninterruptible; 218 219 unsigned long expired_timestamp; 220 unsigned long long timestamp_last_tick; 221 task_t *curr, *idle; 222 struct mm_struct *prev_mm; 223 prio_array_t *active, *expired, arrays[2]; 224 int best_expired_prio; 225 atomic_t nr_iowait; 226 227 #ifdef CONFIG_SMP 228 struct sched_domain *sd; 229 230 /* For active balancing */ 231 int active_balance; 232 int push_cpu; 233 234 task_t *migration_thread; 270 unsigned long wunt_cnt; 235 struct list_head migration_queue; 271 unsigned long wunt_moved; 236 #endif 272 237 273 /* sched_migrate_task() stats */ 238 #ifdef CONFIG_SCHEDSTATS 274 unsigned long smt_cnt; 239 /* latency stats */ 275 240 struct sched_info rq_sched_info; 276 /* sched_balance_exec() stats */ 241 277 unsigned long sbe_cnt; 242 /* sys_sched_yield() stats */ 278 #endif 243 unsigned long yld_exp_empty; 279 }; 244 unsigned long yld_act_empty; 245 unsigned long yld_both_empty; 246 unsigned long yld_cnt; 247 248 /* schedule() stats */ 249 unsigned long sched_noswitch; 250 unsigned long sched_switch; 251 unsigned long sched_cnt; 252 unsigned long sched_goidle; 253 254 /* pull_task() stats */ 255 unsigned long pt_gained[MAX_IDLE_TYPES]; 256 unsigned long pt_lost[MAX_IDLE_TYPES]; 257 258 /* active_load_balance() stats */ 259 unsigned long alb_cnt; 260 unsigned long alb_lost; 261 unsigned long alb_gained; 262 unsigned long alb_failed; 263 264 /* try_to_wake_up() stats */ 265 unsigned long ttwu_cnt; 266 unsigned long ttwu_attempts; 267 unsigned long ttwu_moved; 268 269 /* wake_up_new_task() stats */ 3.2. Struct Runqueue Clean struct runqueue{ spinlock_t unsigned long unsigned long unsigned long unsigned long unsigned long long struct task_struct struct tasl_struct struct mm_struct struct prio_array struct prio_array struct prio_array struct tasl_strucut struct list_head atomic_t }; lock; nr_running; nr_switches; expired_timestamp; nr_uninterruptible;; timestamp_last_tick; *curr; *idel; *prev_mm; *active; *expired; arrays[2]; *migration_thread; migration_queue; nr_iowait; /* spin lock that protects this runqueue */ /* number of runnable tasks */ /* context switch count */ /* time of last array swap */ /* uninterruptible tasks */ /* last scheduler tick */ /* currently running task */ /* this processor´s idle task */ /* mm_struct of last ran task */ /* active priority array */ /* the expired priority array */ /* the actual priority arrays */ /* migration thread */ /* migration queue */ /* number of tasks waiting in I/O */ • Antes de um runqueue ser manipulado deve ser protegido por um lock 3.3. Parênteses Escalabilidade SMP • Em máquinas uniprocessadas o escalonador O(1) de Ingor Molnat é muito bom, porém como resolver os problemas inerentes a escalonamento em SMP/NUMA ? – Escalonamento Multiqueue – SpinLock – Balanceamento de Carga 3.4. Parênteses SMP, MPP, NUMA Symmetric Multi-Processor Non-Uniform Memory Access Massively Parallel Processor 3.5. Parênteses Problemas • Deadlock • Condições necessárias e suficientes para ocorrer: – – – – Exclusão Mútua – somente um processo pode usar um recurso por vez Hold-and-wait – processo pode bloquear um recurso enquanto aguarda Não preemptivo – recurso não pode ser removido de um processo rodando Ciclo no grafo – A -> B -> C -> A • Formas de evitar: – Prevention - Prevenir – Avoidance - Evitar – Detection - Detetar • Starvation 3.6. Solução • Multiqueue Scheduler 3.7. Afinidade entre SMP • Versão 2.4 tinha um problema indesejável: • Um processo podia ficar pingando de um processador para outro (efeito “Ping-Pong”) time 1 time 2 time 3 time 4 time 5 Process A CPU 0 CPU 1 CPU 0 CPU 1 CPU 0 Process B CPU 1 CPU 0 CPU 1 CPU 0 CPU 1 CPU • Este problema foi solucionado também pelas runqueues por processador e garante a afinidade entre multi-processadores 3.8. Lock • Para garantir que somente um processador possa manipular concorrentemente a runqueue, esta é protegida por um lock. Assim, somente um processador pode executar o escalonador concorrentemente. • É raro um processador diferente da origem da runqueue manipulá-lo, porém é possível. • Funções de lock e unlock: task_rq_lock() task_rq_unlock() • Para evitar deadlock o código deve sempre obter o lock na mesma ordem: pelo endereço de runqueues ascendente (cap. 8) v.2.4 v.2.6 4. Vetores de Prioridade • Cada runqueue possui dois vetores de prioridade: • Vetor de Prioridade Ativo • Vetor de Prioridade Expirado • São estes vetores que permitem que a complexidade do algoritmo seja O(1). 4.1. Vetores de Prioridade • Cada vetor de prioridade contém uma fila de processos runnable por nível de prioridade. • As filas contém listas de processos runnable em cada nível de prioridade • O vetor de prioridade também contém um bitmap de prioridade usado para eficientemente descobrir a tarefa de mais alta prioridade no sistema. 185 struct prio_array { 186 unsigned int nr_active; 187 unsigned long bitmap[BITMAP_SIZE]; 188 struct list_head queue[MAX_PRIO]; 189 }; 4.2. Vetores de Prioridade • Cada vetor de prioridade contém um campo bitmap que tem ao menos um bit para toda prioridade no sistema. Inicialmente todos os bits são 0. • Quando uma tarefa torna-se runnable, o bit correspondente à prioridade da tarefa no bitmap torna-se 1. O problema, então é achar, na tabela, o processo mais prioritário dos que tiverem bit 1. Como a tabela é estática porque o número de prioridades é fixo (140), esta busca tem complexidade constante e não depende do número de processos N. • Cada vetor de prioridade também contém um vetor queue da struct list_head, uma fila para cada prioridade e contém todos os processos que têm o mesmo valor de prioridade (representado pela variável nr_active). 4.3. Recálculo de Timeslice • Sistemas antigos faziam esta tarefa em O(N). • Devido aos vetores Ativo e Expirado, este recálculo não precisa ser feito em O(N). • Vetor de Prioridade Ativo – contém todas as tarefas na runqueue associada em que o Timeslice não expirou. • Vetor de Prioridade Expirado – contém todas as tarefas na runqueue associada em que o Timeslice expirou • Quando o Timeslice da tarefa expira, este é movido para o vetor Expirado, porém, antes, seu Timeslice é recalculado. 4.4. Troca de Vetores Ativo / Expirado 2765 array = rq->active; 2766 if (unlikely(!array->nr_active)) { 2767 /* 2768 * Switch the active and expired arrays. 2769 */ 2770 schedstat_inc(rq, sched_switch); 2771 rq->active = rq->expired; 2772 rq->expired = array; 2773 array = rq->active; 2774 rq->expired_timestamp = 0; 2775 rq->best_expired_prio = MAX_PRIO; 4.5. Cálculo de Prioridade e Timeslice • Prioridade Dinâmica – leva em conta uma heurística porque não sabe se o processo é interativo ou não. Se o processo tem muito tempo inativo, é interativo. Se não, não é. Implementação -> guarda em sleep_avg (uma tabela) a razão entre o tempo dormindo e o tempo acordado para cada processo. • Sleep_avg é incrementado quando o processo se torna runnable (até MAX_SLEEP_AVG) e decrementado a cada tick de tempo que o processo roda (até 0) MAX_SLEEP_AVG 0 Bonus/penalty -5 0 Default = 10 ms +5 4.6. Cálculo de Prioridade e Timeslice 105 * Here are a few examples of different nice levels: 106 * 107 * TASK_INTERACTIVE(-20): [1, 1, 1, 1, 1, 1,1,1,1,0,0] 108 * TASK_INTERACTIVE(-10): [1,1,1,1,1,1,1,0,0,0,0] 109 * TASK_INTERACTIVE( 0): [1,1,1,1,0,0,0,0,0,0,0] 110 * TASK_INTERACTIVE( 10): [1,1,0,0,0,0,0,0,0,0,0] 111 * TASK_INTERACTIVE( 19): [0,0,0,0,0,0,0,0,0,0,0] 112 * 113 * (the X axis represents the possible -5 ... 0 ... +5 dynamic 114 * priority range a task can explore, a value of '1' means the 115 * task is rated interactive.) 4.7. Cálculo de Prioridade e Timeslice • Timeslice é recalculado baseado na prioridade estática. • Quando um novo filho é criado o pai e o filho dividem o Timeslice remanescente (como?) • Quando o Timeslice expira, o escalonador inclui a tarefa no vetor Expirado, recalculando antes o Timeslice. 5. Sleeping e Wakeup 6. Balanceamento de Carga • load_balance() no Escalonador. Dois métodos de chamada: • Quando a runqueue está vazia • Pelo timer • A cada 1ms quando o sistema está parado • A cada 200ms quando não • Em sistema uniprocessados não é compilado. 7. Chaveamento de Contexto – Manipulado pelo context_switch. Ocorre sempre que um novo processo é chamado para run. – Executa dois trabalhos: » Chama switch_mm() – troca o mapeamento de memória virtual do processo antigo para o novo » Chama switch_to() – troca o estado do processador do processo novo para o antigo, envolvendo atualização de pilhas e registradores – Utiliza uma flag need_resched para que o kernel chame o escalonador pis existe um processo novo para substituir o antigo – Duas formas para ativar a flag » Quando o timeslice de um processo expira o scheduler_tick() ativa a flag » Quando um processo de maior prioridade acorda o try_to_wake_up() ativa a flag 8. SystemCalls Relacionadas com Escalonamento • Splice () e Tee() system calls (fonte: Abril/2006 – Linus Torvalds) • Splice() é um read/write para um buffer do kernel diretamente. • Tee() é um memcpy() de um buffer do Kernel para outro, sem copiar realmente, apenas incrementando ponteiros de referência. • Existe um buffer do kernel aleatório no espaço do usuário e que o usuário tem controle (como é buffer do kernel, é mais eficiente). • Pode fazer movimento de dado zero-copy. – Exemplo: Direcionar dado de um socket para outro sem copiar isto no espaço do usuário. Assim, é possível direcionar dado que venha de um Encoder MPEG-4 e fazer um Tee() para duplicar o stream e escrever um dos streams para disco e outro para socket para um broadcast em tempo real, sem copiar fisicamente na memória. • Aumentam a performance, porque agiliza os processos que precisam de leitura/escrita. 9. Discussão – A versao 2.6.17 é mais rápida em SMP (Symmetric MultiProcessor), porém em máquinas uniprocessadas rápidas (2GHz) esta diferença não é perceptível. 9.1. Discussão Comparação entre Linux 2.6.4 e 2.4.25 Test System and Benchmarks CPU Motherboard Chipset Memory Storage OS Dual Xeon Test Box 2x 3.2GHz Xeons w/ 1MB L3 Cache MSI Master LS2 Intel E7505 1GB Crucial Registered/ECC PC2700 Western Digital 400JB Gentoo Linux 1.4 What benchmarks will we run? picColor Image software compiling (MySQL source code compiled with make -j 4) mp3 encoding (BladeEnc) Apache Static HTML Apache PHP/MySQL MySQL Super Smack(SELECT and UPDATE) dbench v. 2.0 (file server performance) 9.2. Discussão 9.3. Discussão File Server 10. Futuro do Escalonador • Scheduler Modes – classificar workloads em categorias e permitir que usuários root ajustem o comportamento do escalonador dinamicamente. • Swappable Scheduler (http://www.gnu.org/software/hurd) usuário especifica qual escalonador deve ser usado dependendo da aplicação. 11. Referências • • • • • • • Love, R., Linux Kernel Development – 2nd edition, Novell Press, June 2005. Bovet, D. P., Cesati, M. Understanding The Linux Kernel – 1st edition, O´Reilly, October 2000. Aas, J., Understanding the Linux kernel 2.6.8.1 CPU Scheduler – Silicon Graphics Inc., February 2005. Cross Reference Linux – Linux/kernel/sched.c http://lxr.linux.no/source/kernel/sched.c Love, R., Introducing the 2.6 kernel – Linux Journal, may 2003 Heursch, A.C., Grambow, D., Roedel, D., Rzehak, H., Time-critical tasks in Linux 2.6 Concepts to increase the preemptability of the Linux kernel – Linux Automation Konferenz 2004 Ge, l. – Kernel Comparison: Web Serving on 2.4 and 2.6 – Linux Technology Center IBM – February 2004 12. Questão _______ • Suponha que 5 processos, de P1 a P5, sejam sucessivamente submetidos para execução em intervalos de 1ut (unidade de tempo). O tempo estimado de serviço para os processos é respectivamente de: 5, 3, 4, 5 e 2ut, uma política de time slice de 2ut é igualmente usada para todos os processos, o critério de escalonamento adotado é o Round Robin e a estratégia para deadlock é a da detecção. • a) Defina e compare as estratégias para: – – – prevenir (prevention) evitar (avoidance) e detectar (detecion) deadlocks. • b) Monte o gráfico e determine o turnaround dos processos. • c) Determine o fator de eficiência do método (Ts/Tq).