Anda di halaman 1dari 17

O  scheduling  da  CPU  é  a  base  dos  sistemas  operacionais  multiprogramados.

  Alternando  a  CPU  entre  os


processos,  o  sistema  operacional  pode  tornar  o  computador  mais  produtivo.  Neste  capítulo,  introduzimos
conceitos  básicos  sobre  o  scheduling  da  CPU  e  vários  algoritmos  de  scheduling  da  CPU.  Também
consideramos o problema da seleção de um algoritmo para um sistema específico.
No  Capítulo  4,  introduzimos  os  threads  no  modelo  de  processo.  Em  sistemas  operacionais  que  os
suportam, são os threads de nível de kernel — e não os processos — que são realmente incluídos no schedule
pelo  sistema  operacional.  No  entanto,  geralmente  os  termos  “scheduling  de  processos”  e  “scheduling  de
threads”  são  usados  de  forma  intercambiável.  Neste  capítulo,  usamos  scheduling  de  processos,  quando
discutimos  conceitos  gerais  de  scheduling,  e  scheduling  de  threads  para  nos  referir  a  ideias  relacionadas
especificamente com os threads.

OBJETIVOS DO CAPÍTULO
• Introduzir o scheduling da CPU que é a base dos sistemas operacionais multiprogramados.
• Descrever vários algoritmos de scheduling da CPU.
• Discutir  critérios  de  avaliação  para  a  seleção  de  um  algoritmo  de  scheduling  da  CPU  para  um
sistema específico.
• Examinar os algoritmos de scheduling de vários sistemas operacionais.

6.1 Conceitos Básicos
Em  um  sistema  com  um  único  processador,  só  um  processo  pode  ser  executado  de  cada  vez.  Os  outros
devem  esperar  até  que  a  CPU  esteja  livre  e  possa  ser  realocada.  O  objetivo  da  multiprogramação  é  haver
sempre algum processo em execução para maximizar a utilização da CPU. A ideia é relativamente simples.
Um processo é executado até ter que esperar, em geral, pela conclusão de alguma solicitação de I/O. Em um
sistema  de  computação  simples,  a  CPU  permanece  ociosa.  Todo  esse  tempo  de  espera  é  desperdiçado;
nenhum  trabalho  útil  é  realizado.  Com  a  multiprogramação,  tentamos  usar  esse  tempo  produtivamente.
Vários  processos  são  mantidos  na  memória  ao  mesmo  tempo.  Quando  um  processo  precisa  esperar,  o
sistema operacional desvincula  a  CPU  desse  processo  e  a  designa  a  outro  processo.  Esse  padrão  continua.
Sempre que um processo tem de esperar, outro processo pode assumir o uso da CPU.
Um  scheduling  desse  tipo  é  uma  função  básica  do  sistema  operacional.  Quase  todos  os  recursos  do
computador  são  alocados  antes  de  serem  usados.  É  claro  que  a  CPU  é  um  dos  principais  recursos  do
computador. Portanto, seu scheduling é essencial no projeto do sistema operacional.
6.3 Algoritmos de Scheduling
O scheduling da CPU lida com o problema de decidir para qual dos processos da fila de prontos a CPU deve
ser alocada. Há muitos algoritmos de scheduling da CPU diferentes. Nesta seção, descrevemos vários deles.

6.3.1 Scheduling “Primeiro­a­Chegar, Primeiro­a­Ser­Atendido”
Sem dúvida, o algoritmo mais simples de scheduling da CPU é o algoritmo “primeiro­a­chegar, primeiro­
a­ser­atendido” (FCFS –first­come, first­served). Nesse esquema, o processo que solicita a CPU primeiro é
o primeiro a usá­la. A implementação da política FCFS é facilmente gerenciada com uma fila FIFO. Quando
um processo entra na fila de prontos, seu PCB é conectado na cauda da fila. Quando a CPU está livre, ela é
alocada  ao  processo  na  cabeça  da  fila.  O  processo  em  execução  é  então  removido  da  fila.  O  código  do
scheduling FCFS é simples de escrever e entender.
O lado negativo é que o tempo médio de espera na política FCFS geralmente é bem longo. Considere o
conjunto  de  processos  a  seguir  que  chegam  no  tempo  0,  com  o  intervalo  do  pico  de  CPU  dado  em
milissegundos:

Processo Duração do Pico

P1 24

P2 3

P3 3

Se  os  processos  chegam  na  ordem  P1,  P2,  P3  e  são  atendidos  na  ordem  FCFS,  obtemos  o  resultado
mostrado  no  gráfico  de  Gantt,  a  seguir,  que  é  um  gráfico  de  barras  que  ilustra  um  schedule  específico,
incluindo os momentos de início e fim de cada um dos processos participantes:

O  tempo  de  espera  é  de  0  milissegundo  para  o  processo  P1,  24  milissegundos  para  o  processo  P2,  e  27
milissegundos para o processo P3. Logo, o tempo médio de espera é de (0 + 24 + 27)/3 = 17 milissegundos.
Se os processos chegarem na ordem P2, P3, P1, no entanto, os resultados serão os mostrados no gráfico de
Gantt a seguir:

O tempo médio de espera agora é de (6 + 0 + 3)/3 = 3 milissegundos. Essa redução é substancial. Portanto,
geralmente o tempo médio de espera na política FCFS não é mínimo e pode variar significativamente se os
intervalos de pico de CPU dos processos variarem muito.
Além  disso,  considere  o  desempenho  do  scheduling  FCFS  em  uma  situação  dinâmica.  Suponha  que
tenhamos um processo limitado por CPU e muitos processos limitados por I/O. Enquanto os processos fluem
pelo  sistema,  podemos  ter  como  resultado  o  cenário  a  seguir.  O  processo  limitado  por  CPU  ocupará  e
manterá a CPU. Durante esse período, todos  os  outros  processos  terminarão  seus  I/O  e  entrarão  na  fila  de
prontos,  esperando  pela  CPU.  Enquanto  os  processos  esperam  na  fila  de  prontos,  os  dispositivos  de  I/O
ficam  ociosos.  Eventualmente,  o  processo  limitado  por  CPU  termina  seu  pico  de  CPU  e  passa  para  um
dispositivo  de  I/O.  Todos  os  processos  limitados  por  I/O  que  têm  picos  de  CPU  curtos  são  executados
rapidamente e voltam para as filas de I/O. Nesse momento, a CPU permanece ociosa. O processo limitado
por  CPU  volta  então  para  a  fila  de  prontos  e  a  CPU  é  alocada  para  ele.  Novamente,  todos  os  processos
limitados  por  I/O  têm  de  esperar  na  fila  de  prontos  até  que  o  processo  limitado  por  CPU  termine.  Há  um
efeito comboio, já que todos os outros processos esperam que o grande processo saia da CPU. Esse efeito
resulta em uma utilização da CPU e dos dispositivos menor do que seria possível se os processos mais curtos
pudessem ser atendidos antes.
Observe também que o algoritmo de scheduling FCFS não tem preempção. Uma vez que a CPU tenha
sido  alocada  para  um  processo,  esse  processo  a  ocupa  até  liberá­la,  seja  ao  encerrar  sua  execução  ou  ao
solicitar  I/O.  O  algoritmo  FCFS  é,  portanto,  particularmente  problemático  para  sistemas  de  tempo
compartilhado,  em  que  é  importante  que  cada  usuário  tenha  sua  vez  na  CPU  a  intervalos  regulares.  Seria
desastroso permitir que um processo se apropriasse da CPU por um período extenso.

6.3.2 Scheduling Menor­Job­Primeiro
Uma abordagem diferente para o scheduling da CPU é o algoritmo de scheduling menor­job­primeiro (SJF
– shortest­job­first). Esse algoritmo associa a cada processo a duração do próximo pico de CPU do processo.
Quando a CPU está disponível, ela é atribuída ao processo que tem o próximo pico de CPU mais curto. Se os
próximos  picos  de  CPU  de  dois  processos  forem  iguais,  o  scheduling  FCFS  será  usado  para  resolver  o
impasse.  Observe  que  um  termo  mais  apropriado  para  esse  método  de  scheduling  seria  o  algoritmo  do
próximo pico de CPU mais curto porque o scheduling depende da duração do próximo pico de CPU de um
processo,  e  não  de  sua  duração  total.  Usamos  o  termo  SJF  porque  a  maioria  das  pessoas  e  livros  usa  esse
termo para se referir a esse tipo de scheduling.
Como exemplo de scheduling SJF, considere o conjunto de processos a seguir, com a duração de pico de
CPU dada em milissegundos:

Processo Duração do Pico

P1 6

P2 8

P3 7

P4 3

Usando  o  sheduling  SJF,  esses  processos  seriam  organizados  para  execução  de  acordo  com  o  seguinte
gráfico de Gantt:

O  tempo  de  espera  é  de  3  milissegundos  para  o  processo  P1,  16  milissegundos  para  o  processo  P2,  9
milissegundos para o processo P3, e 0 milissegundo para o processo P4. Portanto, o tempo médio de espera é
de (3 + 16 + 9 + 0)/4 = 7 milissegundos. Por comparação, se estivéssemos usando o esquema de scheduling
FCFS, o tempo médio de espera seria de 10,25 milissegundos.
O algoritmo de scheduling SJF é comprovadamente ótimo, pelo fato de fornecer o tempo médio de espera
mínimo para determinado conjunto de processos. Executar um processo curto antes de um longo reduz mais
o  tempo  de  espera  do  processo  curto  do  que  aumenta  o  tempo  de  espera  do  processo  longo.
Consequentemente, o tempo médio de espera diminui.
A  grande  dificuldade  do  algoritmo  SJF  é  como  saber  a  duração  da  próxima  solicitação  de  CPU.  No
scheduling de longo prazo (scheduling  de  jobs)  em  um  sistema  batch,  podemos  usar  o  limite  de  tempo  do
processo, que é especificado pelo usuário quando submete o job. Nessa situação, os usuários são motivados a
estimar o limite de tempo do processo de maneira precisa, já que um valor mais baixo pode significar uma
resposta mais rápida, mas um valor baixo demais causará um erro de limite de tempo excedido e demandará
uma nova submissão do job. O scheduling SJF costuma ser usado no scheduling de longo prazo.
Embora o algoritmo SJF seja ótimo, ele não pode ser implementado no nível do scheduling da CPU de
curto prazo. No scheduling de curto prazo, não há maneira de saber a duração do próximo pico de CPU. Uma
abordagem para esse problema é tentar encontrar um valor aproximado ao do scheduling SJF. Podemos não
saber a duração do próximo pico de CPU, mas talvez possamos prever seu valor. Esperamos que o próximo
pico  de  CPU  tenha  duração  semelhante  à  dos  picos  de  CPU  anteriores.  Calculando  um  valor  aproximado
para a duração do próximo pico de CPU, podemos selecionar o processo com o menor pico de CPU previsto.
O próximo pico de CPU é, em geral, previsto como uma média exponencial dos intervalos medidos dos
picos de CPU anteriores. Podemos definir a média exponencial com a fórmula a seguir. Seja tn a duração do
enésimo  pico  de  CPU  e  seja  τn+1  o  valor  previsto  para  o  próximo  pico  de  CPU.  Então,  para  α,  0  ≤  a  ≤  1,
temos

τn+1 = α tn + (1 − α)τn.

O valor de tn contém nossa informação mais recente, enquanto τn armazena a história passada. O parâmetro α
controla o peso relativo da história  recente  e  da  passada  em  nossa  previsão.  Se  α  =  0,  então  τn+1  =  τn,  e  a
história recente não tem efeito (as condições correntes são consideradas transientes). Se α = 1, então τn+1 = τn,
e  somente  o  pico  de  CPU  mais  recente  importa  (a  história  é  considerada  passada  e  irrelevante).  O  mais
comum é α = 1/2; assim, as histórias recente e passada têm peso igual. O τ0inicial pode ser definido como
uma constante ou como uma média geral do sistema. A Figura 6.3 mostra uma média exponencial com α =
1/2 e τ0 = 10.
Para  entender  o  comportamento  da  média  exponencial,  podemos  expandir  a  fórmula  para  τn+1,
substituindo τn, para encontrar

τn+1 = αtn + (1 − α)αtn−1 +… + (1 − α)j αtn−j +… + (1 − α)n+1τ0.

Normalmente,  a  é  menor  do  que  1.  Como  resultado,  (1  –  a)  também  é  menor  do  que  1,  e  cada  termo
sucessivo tem menos peso do que seu predecessor.
O algoritmo SJF pode ter ou não ter preempção. A escolha é feita quando um novo processo chega à fila
de prontos enquanto um processo anterior ainda está sendo executado. O próximo pico de CPU do processo
recém­chegado  pode  ser  mais  curto  do  que  o  tempo  remanescente  deixado  pelo  processo  em  execução
corrente. Um algoritmo SJF  com  preempção  interromperá  o  processo  em  execução  corrente,  enquanto  um
algoritmo SJF sem preempção permitirá que o processo em execução corrente termine seu pico de CPU. O
algoritmo  SJF  com  preempção  também  é  chamado  de  scheduling  do  tempo­remanescente­mais­curto­
primeiro.
Como  exemplo,  considere  os  quatro  processos  a  seguir,  com  a  duração  do  pico  de  CPU  dada  em
milissegundos:

Processo Tempo de Chegada Duração do Pico

P1 0 8

P2 1 4

P3 2 9

P4 3 5

Se  os  processos  chegarem  à  fila  de  prontos  nos  momentos  mostrados  e  necessitarem  dos  tempos  de  pico
indicados,  então  o  scheduling  SJF  com  preempção  resultante  será  como  o  mostrado  no  gráfico  de  Gantt
abaixo:
Figura 6.3 Previsão da duração do próximo pico de CPU.

O processo P1 é iniciado no tempo 0, já que é o único processo na fila. O processo P2 chega no tempo 1. O
tempo  restante  do  processo  P1  (7  milissegundos)  é  maior  do  que  o  tempo  requerido  pelo  processo  P2  (4
milissegundos);  portanto,  o  processo  P1  é  interceptado,  e  o  processo  P2  é  incluído  no  schedule.  O  tempo
médio de espera nesse exemplo é de [(10 – 1) + (1 – 1) + (17 – 2) + (5 – 3)]/4 = 26/4 = 6,5 milissegundos. O
scheduling SJF sem preempção resultaria em um tempo médio de espera de 7,75 milissegundos.

6.3.3 Scheduling por Prioridades
O algoritmo SJF é um caso especial do algoritmo geral de scheduling por prioridades. Uma  prioridade  é
associada  a  cada  processo,  e  a  CPU  é  alocada  ao  processo  com  a  prioridade  mais  alta.  Processos  com
prioridades  iguais  são  organizados  no  schedule  em  ordem  FCFS.  O  algoritmo  SJF  é  simplesmente  um
algoritmo por prioridades em que a prioridade (p) é o inverso do próximo pico de CPU (previsto). Quanto
maior o pico de CPU, menor a prioridade, e vice­versa.
Observe  que  discutimos  o  scheduling  em  termos  de  alta  e  baixa  prioridade.  As  prioridades  são,
geralmente, indicadas por algum intervalo de números fixo, como 0 a 7 ou 0 a 4.095. No entanto, não há um
consenso geral sobre se 0 é a prioridade mais alta ou mais baixa. Alguns sistemas usam números baixos para
representar baixa prioridade; outros usam números baixos para prioridades altas. Essa diferença pode levar à
confusão. Neste texto, assumimos que números baixos representam alta prioridade.
Como exemplo, considere o conjunto de processos a seguir, que assumimos tenham chegado no tempo 0,
na ordem P1,P2, …, P5, com a duração do pico de CPU dada em milissegundos:

Processo Duração do Pico Prioridade

P1 10 3

P2 1 1
P3 2 4

P4 1 5

P5 5 2

Usando  o  scheduling  por  prioridades,  esses  processos  seriam  organizados  no  schedule  de  acordo  com  o
gráfico de Gantt a seguir:

O tempo médio de espera é de 8,2 milissegundos.
As  prioridades  podem  ser  definidas  interna  ou  externamente.  Prioridades  definidas  internamente  usam
uma ou mais quantidades mensuráveis para calcular a prioridade de um processo. Por exemplo,  limites  de
tempo,  requisitos  de  memória,  número  de  arquivos  abertos  e  a  razão  entre  o  pico  médio  de  I/O  e  o  pico
médio de CPU têm sido usados no cômputo das prioridades. Prioridades externas são definidas por critérios
externos ao sistema operacional, como a importância do processo, o tipo e o montante dos fundos pagos pelo
uso do computador, o departamento que patrocina o trabalho, e outros fatores, geralmente políticos.
O scheduling por prioridades pode ou não ter preempção. Quando um processo chega à fila de prontos,
sua  prioridade  é  comparada  com  a  prioridade  do  processo  em  execução  corrente.  Um  algoritmo  de
scheduling por prioridades com preempção se apropriará da CPU se a prioridade do processo recém­chegado
for  mais  alta  do  que  a  prioridade  do  processo  em  execução  corrente.  Um  algoritmo  de  scheduling  de
prioridades sem preempção simplesmente inserirá o novo processo na cabeça da fila de prontos.
Um grande problema dos algoritmos de scheduling por prioridades é o bloqueio indefinido ou inanição.
Um  processo  que  esteja  pronto  para  ser  executado,  mas  em  espera  pela  CPU,  pode  ser  considerado
bloqueado.  Um  algoritmo  de  scheduling  por  prioridades  pode  deixar  alguns  processos  de  baixa  prioridade
esperando  indefinidamente.  Em  um  sistema  de  computação  muito  carregado,  um  fluxo  constante  de
processos  de  prioridade  mais  alta  pode  impedir  que  um processo de prioridade baixa consiga usar a CPU.
Geralmente,  acontece  uma  entre  duas  coisas.  O  processo  acaba  sendo  executado  (às  2  da  madrugada  de
domingo, quando finalmente o sistema está pouco carregado) ou o sistema de computação cai e perde todos
os processos de baixa prioridade não concluídos. (Dizem que quando o IBM 7094 foi desligado no MIT em
1973, foi achado um processo de baixa prioridade que tinha sido submetido em 1967 e ainda não tinha sido
executado.)
Uma  solução  para  o  problema  do  bloqueio  indefinido  de  processos  de  baixa  prioridade  é  o
envelhecimento. O envelhecimento envolve o aumento gradual da prioridade dos processos que esperam no
sistema por muito tempo. Por exemplo, se as prioridades variam de 127 (baixa) a 0 (alta), podemos aumentar
a prioridade de um processo em espera de uma unidade a cada 15 minutos. Eventualmente, até mesmo  um
processo  com  prioridade  inicial  igual  a  127  teria  a  prioridade  mais  alta  no  sistema  e  seria  executado.  Na
verdade, não demoraria mais do que 32 horas para que um processo de prioridade 127 envelhecesse até se
tornar um processo de prioridade 0.

6.3.4 Scheduling Round­Robin
O  algoritmo  de  scheduling  round­robin  (RR)  foi  projetado  especialmente  para  sistemas  de  tempo
compartilhado. Ele é semelhante ao scheduling FCFS, mas a preempção é adicionada para habilitar o sistema
a se alternar entre os processos. Uma pequena unidade de tempo, chamada quantum de tempo  ou  porção
de tempo, é definida. Geralmente um quantum de tempo tem duração de 10 a 100 milissegundos. A fila de
prontos é tratada como uma fila circular. O scheduler da CPU percorre a fila de prontos, alocando a CPU a
cada processo por um intervalo de até um quantum de tempo.
Para implementar o scheduling RR, devemos tratar novamente a fila de prontos como uma fila FIFO de
processos.  Novos  processos  são  adicionados  à  cauda  da  fila  de  prontos.  O  scheduler  da  CPU  seleciona  o
primeiro processo da fila de prontos, define um timer com interrupção após 1 quantum de tempo e despacha
o processo.
Portanto, uma entre duas coisas ocorrerá. O processo pode ter um pico de CPU menor do que 1 quantum
de tempo. Nesse caso, o próprio processo liberará a CPU voluntariamente. O scheduler passará então para o
próximo processo na fila de prontos. Se o pico de CPU do processo em execução corrente for maior do que 1
quantum  de  tempo,  o  timer  será  desligado  e  causará  uma  interrupção  para  o  sistema  operacional.  Uma
mudança de contexto será executada e o processo será inserido na cauda da fila de prontos. O scheduler da
CPU selecionará então o próximo processo na fila de prontos.
O tempo médio de espera na política RR é frequentemente longo. Considere o conjunto de processos, a
seguir, que chegam no tempo 0, com a duração do pico de CPU dada em milissegundos:

Processo Duração do Pico

P1 24

P2 3

P3 3

Se  usarmos  um  quantum  de  tempo  de  4  milissegundos,  o  processo  P1  ficará  com  os  4  primeiros
milissegundos. Já que ele requer outros 20 milissegundos, é interceptado após o primeiro quantum de tempo,
e a CPU é alocada ao próximo processo na fila, o processo P2. O processo P2 não precisa de 4 milissegundos
e,  portanto,  é  encerrado  antes  que  seu  quantum  de  tempo  expire.  A  CPU  é  então  liberada  para  o  próximo
processo, o processo P3. Uma vez que cada processo tenha recebido 1 quantum de tempo, a CPU é retornada
para o processo P1 por um quantum de tempo adicional. O schedule RR resultante é o seguinte:

Vamos calcular o tempo médio de espera desse schedule. P1 espera por 6 milissegundos (10 – 4), P2 espera
por  4  milissegundos  e  P3  espera  por  7  milissegundos.  Assim,  o  tempo  médio  de  espera  é  de  17/3  =  5,66
milissegundos.
No  algoritmo  de  scheduling  RR,  nenhum  processo  é  alocado  à  CPU  por  mais  de  1  quantum  de  tempo
sucessivamente (a menos que seja o único processo executável). Se o pico de CPU de um processo exceder 1
quantum  de  tempo,  esse  processo  é  interceptado  e  devolvido  à  fila  de  prontos.  Portanto,  o  algoritmo  de
scheduling RR tem preempção.
Se existem n processos na fila de prontos e o quantum de tempo é q, cada processo recebe 1/n do tempo
da CPU em porções de no máximo q unidades de tempo. Cada processo não deve esperar por mais de (n – 1)
× q unidades de tempo até seu próximo quantum de tempo. Por exemplo, no caso de cinco processos e um
quantum  de  tempo  de  20  milissegundos,  cada  processo  receberá  até  20  milissegundos  a  cada  100
milissegundos.
O  desempenho  do  algoritmo  RR  depende  substancialmente  do  tamanho  do  quantum  de  tempo.  Por  um
lado, se o quantum de tempo é extremamente longo, a política RR é igual à política FCFS. Por outro lado,
quando  o  quantum  de  tempo  é  extremamente  curto  (digamos,  1  milissegundo),  a  abordagem  RR  pode
resultar em um grande número de mudanças de contexto. Suponha, por exemplo, que tenhamos apenas um
processo de 10 unidades de tempo. Se o quantum é de 12 unidades de tempo, o processo termina em menos
de  1  quantum  de  tempo,  sem  overhead.  Se  o  quantum  é  de  6  unidades  de  tempo,  no  entanto,  o  processo
precisa  de  2  quanta,  resultando  em  uma  mudança  de  contexto.  Se  o  quantum  de  tempo  é  de  1  unidade  de
tempo, nove mudanças de contexto ocorrem, tornando proporcionalmente mais lenta a execução do processo
(Figura 6.4).
Assim, queremos que o quantum de tempo seja longo em relação ao tempo de mudança de contexto. Se o
tempo de mudança de contexto for de aproximadamente 10% do quantum de tempo, então cerca de 10% do
tempo  da  CPU  serão  gastos  com  mudança  de  contexto.  Na  prática,  a  maioria  dos  sistemas  modernos  tem
quanta de tempo que variam de 10 a 100 milissegundos. O tempo requerido por uma mudança de contexto é,
tipicamente, menor do que 10 microssegundos; portanto, o tempo de mudança de contexto é uma pequena
fração do quantum de tempo.
O  tempo  de  turnaround  também  depende  do  tamanho  do  quantum  de  tempo.  Como  podemos  ver  na
Figura  6.5,  o  tempo  médio  de  turnaround  de  um  conjunto  de  processos  não  melhora  necessariamente  na
medida em que o tamanho do quantum de tempo aumenta. Geralmente, o tempo médio de turnaround pode
ser melhorado quando a maioria dos processos termina seu próximo pico de CPU em um único quantum de
tempo.  Por  exemplo,  dados  três  processos  de  10  unidades  de  tempo  cada  e  um  quantum  de  1  unidade  de
tempo,  o  tempo  médio  de  turnaround  é  de  29.  Se  o  quantum  de  tempo  é  igual  a  10,  no  entanto,  o  tempo
médio  de  turnaround  cai  para  20.  Se  o  tempo  de  mudança  de  contexto  for  incluído,  o  tempo  médio  de
turnaround aumenta ainda mais para um quantum de tempo menor, já que mais mudanças de contexto são
necessárias.
Embora o quantum de tempo deva ser longo, comparado ao tempo de mudança de contexto, ele não deve
ser longo demais. Como apontamos anteriormente, se o quantum de tempo for longo demais, o scheduling
RR  degenerará  para  uma  política  FCFS.  Uma  regra  prática  é  a  de  que  80%  dos  picos  de  CPU  devem  ser
menores do que o quantum de tempo.

6.3.5 Scheduling de Filas Multiníveis
Outra  classe  de  algoritmos  de  scheduling  foi  criada  para  situações  em  que  os  processos  são  facilmente
classificados em diferentes grupos. Por exemplo, uma divisão comum é feita entre processos de foreground
(interativos) e processos de background (batch). Esses dois tipos de processos têm requisitos de tempo de
resposta diferentes e, portanto, podem ter diferentes necessidades de scheduling. Além disso, os processos de
foreground podem ter prioridade (definida externamente) sobre os processos de background.

Figura 6.4 Como um quantum de tempo menor aumenta as mudanças de contexto.
Figura 6.5 Como o tempo de turnaround varia com o quantum de tempo.

Um  algoritmo  de  scheduling  de  filas multiníveis  particiona  a  fila  de  prontos  em  várias  filas  separadas
(Figura  6.6).  Os  processos  são  atribuídos  permanentemente  a  uma  fila,  geralmente  com  base  em  alguma
propriedade do processo, como o tamanho da memória, a prioridade do processo ou o tipo do processo. Cada
fila tem seu próprio algoritmo de scheduling. Por exemplo, filas separadas podem ser usadas para processos
de foreground e de background. A fila de foreground pode ser organizada no schedule por um algoritmo RR,
enquanto a fila de background, por um algoritmo FCFS.
Além  disso,  deve  haver  um  scheduling  entre  as  filas,  que  é  normalmente  implementado  como  um
scheduling de prioridade fixa com preempção. Por exemplo, a fila de foreground pode ter prioridade absoluta
sobre a fila de background.
Examinemos  um  exemplo  de  um  algoritmo  de  scheduling  de  filas  multiníveis  com  cinco  filas,  listadas
abaixo em ordem de prioridade:

1. Processos do sistema
2. Processos interativos
3. Processos de edição interativa
4. Processos batch
5. Processos de estudantes

Cada  fila  tem  prioridade  absoluta  sobre  as  filas  de  menor  prioridade.  Nenhum  processo  na  fila  batch,  por
exemplo,  pode  ser  executado,  a  não  ser  que  as  filas  de  processos  do  sistema,  processos  interativos  e
processos  de  edição  interativa  estejam  todas  vazias.  Se  um  processo  de  edição  interativa  entrar  na  fila  de
prontos enquanto um processo batch estiver em execução, o processo batch sofrerá preempção.
Figura 6.6 Scheduling de filas multiníveis.

Outra  possibilidade  é  a  divisão  do  tempo  entre  as  filas.  Aqui,  cada  fila  recebe  determinada  parcela  do
tempo de CPU que ela pode então distribuir entre seus diversos processos. Por exemplo, no caso das filas de
foreground e de background, a fila de foreground pode receber 80% do tempo da CPU para o scheduling RR
entre seus processos, enquanto a fila de background recebe 20% da CPU para distribuir entre seus processos
usando o scheduling FCFS.

6.3.6 Scheduling de Filas Multiníveis com Retroalimentação
Normalmente,  quando  o  algoritmo  de  scheduling  de  filas  multiníveis  é  usado,  os  processos  são  atribuídos
permanentemente  a  uma  fila  quando  entram  no  sistema.  Se  houver  filas  separadas  para  processos  de
foreground e de background, por exemplo, os processos não passam de uma fila para a outra, já que eles não
mudam sua natureza de foreground ou background. Essa definição tem a vantagem de gerar baixo overhead
de scheduling, mas é inflexível.
Por  outro  lado,  um  algoritmo  de  scheduling  de  filas  multiníveis  com  retroalimentação  permite  a
alternância de um processo entre as filas. A ideia é separar os processos de acordo com as características de
seus picos de CPU. Se um processo usar muito tempo da CPU, ele será passado para uma fila de prioridade
mais baixa. Esse esquema deixa os processos interativos e limitados por I/O nas filas de prioridade mais alta.
Além disso, um processo  que  esperar  demais  em  uma  fila  de  prioridade  mais  baixa  pode  ser  movido  para
uma fila de maior prioridade. Esse tipo de envelhecimento evita a inanição.
Por exemplo, considere um scheduler de filas multiníveis com retroalimentação manipulando três filas,
numeradas de 0 a 2 (Figura 6.7). Primeiro, o scheduler executa todos os processos na fila 0. Somente quando
a fila 0 estiver vazia é que ele executará os processos na fila 1. Da mesma forma, os processos na fila 2 serão
executados  apenas  se  as  filas  0  e  1  estiverem  vazias.  Um  processo  que  chegue  à  fila  1  interceptará  um
processo na fila 2. Por sua vez, um processo na fila 1 será interceptado por um processo que chegue à fila 0.
Um processo que entre na fila de prontos é inserido na fila 0. Um processo na fila 0 recebe um quantum
de tempo de 8 milissegundos. Se ele não for concluído dentro desse período, será passado para a cauda da
fila  1.  Se  a  fila  0  estiver  vazia,  o  processo  na  cabeça  da  fila  1  receberá  um  quantum  de  tempo  de  16
milissegundos. Se ele não for concluído, sofrerá preempção e será inserido na fila 2. Os processos da fila 2
serão executados segundo o scheduling FCFS, mas só entrarão em execução quando as filas 0 e 1 estiverem
vazias.
Figura 6.7 Filas multiníveis com retroalimentação.

Esse  algoritmo  de  scheduling  dá  prioridade  mais  alta  a  qualquer  processo  com  pico  de  CPU  de  8
milissegundos ou menos. Tal processo obterá rapidamente a CPU, terminará seu pico de CPU e passará para
seu próximo pico de I/O. Processos que precisam de mais de 8 e menos de 24 milissegundos também são
atendidos rapidamente, embora com prioridade mais baixa do que processos mais curtos. Processos longos
são  automaticamente  relegados  à  fila  2,  sendo  atendidos  em  ordem  FCFS  com  quaisquer  ciclos  de  CPU
deixados pelas filas 0 e 1.
Em geral, um scheduler de filas multiníveis com retroalimentação é definido pelos parâmetros a seguir:

• O número de filas
• O algoritmo de scheduling de cada fila
• O método usado para determinar quando um processo deve ser elevado a uma fila de prioridade mais alta
• O método usado para determinar quando um processo deve ser rebaixado a uma fila de prioridade mais
baixa
• O método usado para determinar a fila em que um processo entrará quando precisar de serviço

A definição de um scheduler de filas multiníveis com retroalimentação o torna o algoritmo de scheduling de
CPU mais geral. Ele pode ser configurado para se ajustar a um sistema específico que esteja sendo projetado.
Infelizmente, também é o algoritmo mais complexo, já que a definição do melhor scheduler requer alguma
forma de seleção de valores para todos os parâmetros.

6.4 Scheduling de Threads
No Capítulo 4, introduzimos os threads ao modelo de processo, fazendo a distinção entre threads de nível de
usuário e de nível de kernel. Em sistemas operacionais que os suportam, são os threads de nível de kernel —
e não os processos — que são incluídos no schedule pelo sistema operacional. Os threads de nível de usuário
são  gerenciados  por  uma  biblioteca  de  threads,  e  o  kernel  não  tem  conhecimento  deles.  Para  serem
executados  em  uma  CPU,  os  threads  de  nível  de  usuário  devem  ser  mapeados  para  um  thread  de  nível  de
kernel associado, embora esse mapeamento possa ser indireto e usar um processo peso leve (LWP). Nesta
seção,  exploramos  questões  de  scheduling  envolvendo  threads  de  nível  de  usuário  e  de  nível  de  kernel  e
outros exemplos específicos de scheduling para o Pthreads.

6.4.1 Escopo de Disputa
Uma  diferença  entre  os  threads  de  nível  de  usuário  e  de  nível  de  kernel  diz  respeito  a  como  eles  são
organizados no schedule. Em sistemas que implementam os modelos muitos­para­um (Seção 4.3.1) e muitos­
para­muitos (Seção 4.3.3), a biblioteca de threads organiza os threads de nível de usuário para execução em
um LWP disponível. Esse esquema é conhecido como escopo de disputa de processos (PCS  —  process­
contention scope), já que a disputa pela CPU ocorre entre threads pertencentes ao mesmo processo. (Quando
dizemos que a biblioteca de threads organiza threads de usuário para execução em LWPs disponíveis, não
queremos  dizer  que  os  threads  estão  sendo  realmente  executados  em  uma  CPU.  Isso  demandaria  que  o
sistema  operacional  designasse  o  thread  de  kernel  a  uma  CPU  física.)  Para  decidir  que  thread  de  nível  de
kernel  deve  ser  designado  a  uma  CPU,  o  kernel  usa  o  escopo  de  disputa  do  sistema  (SCS  —  system­
contention  scope).  A  disputa  pela  CPU  com  o  scheduling  SCS  ocorre  entre  todos  os  threads  no  sistema.
Sistemas que usam o modelo um­para­um (Seção 4.3.2), como o Windows, o Linux e o Solaris, organizam
threads para execução usando somente o SCS.
Normalmente, o PCS é estabelecido de acordo com prioridades — o scheduler seleciona para execução o
thread executável com a prioridade mais alta. As prioridades dos threads de nível de usuário são definidas
pelo  programador  e  não  são  ajustadas  pela  biblioteca  de  threads,  embora  algumas  bibliotecas  de  threads
possam  permitir  que  o  programador  altere  a  prioridade  de  um  thread.  É  importante  observar  que  o  PCS,
normalmente, intercepta o thread em execução corrente em favor de um thread de prioridade mais alta; no
entanto, não há garantia de divisão do tempo (Seção 6.3.4) entre threads de prioridade igual.

6.4.2 Scheduling no Pthreads
Fornecemos  um  exemplo  de  programa  Pthreads  do  POSIX  na  Seção  4.4.1,  junto  com  uma  introdução  à
criação de threads com o Pthreads. Agora, destacamos a API POSIX Pthreads que permite a especificação do
PCS ou do SCS durante a criação de threads. O Pthreads identifica os valores de escopo de disputa a seguir:

• PTHREAD_SCOPE_PROCESS organiza threads para execução usando o scheduling PCS.
• PTHREAD_SCOPE_SYSTEM organiza threads para execução usando o scheduling SCS.

Em sistemas que implementam o modelo muitos­para­muitos, a política PTHREAD_SCOPE_PROCESS
designa threads de nível de usuário para execução em LWPs disponíveis. O número de LWPs é mantido pela
biblioteca  de  threads,  casualmente  usando  ativações  do  scheduler  (Seção  4.6.5).  A  política  de  scheduling
PTHREAD_SCOPE_SYSTEM  cria  e  vincula  um  LWP  a  cada  thread  de  nível  de  usuário  em  sistemas
muitos­para­muitos, mapeando efetivamente os threads com o uso da política um­para­um.
A IPC do Pthreads fornece duas funções para a obtenção — e o estabelecimento — da política de escopo
de disputa:

• pthread_attr_setscope(pthread_attr_t *attr, int scope)


• pthread_attr_getscope(pthread_attr_t *attr, int *scope)

O  primeiro  parâmetro  das  duas  funções  contém  um  ponteiro  para  o  conjunto  de  atributos  do  thread.  O
segundo  parâmetro  da  função  pthread_attr_setscope ( )  recebe  o  valor
PTHREAD_SCOPE_SYSTEM  ou  PTHREAD_SCOPE_PROCESS,  indicando  como  o  escopo  de  disputa
deve ser estabelecido. No caso de pthread_attr_getscope ( ), esse segundo parâmetro contém um
ponteiro  para  um  valor  int,  que  é  posicionado  com  o  valor  corrente  do  escopo  de  disputa.  Se  um  erro
ocorre, cada uma dessas duas funções retorna um valor diferente de zero.
Na Figura 6.8, ilustramos uma API de scheduling do Pthreads. Primeiro o programa determina o escopo
de  disputa  existente  e  o  define  como  PTHREADS_SCOPE_SYSTEM.  Em  seguida,  cria  cinco  threads
separados que serão executados com o uso da política de scheduling SCS. Observe que, em alguns sistemas,
apenas  certos  valores  de  escopo  de  disputa  são  permitidos.  Por  exemplo,  os  sistemas  Linux  e  Mac  OS  X
permitem somente PTHREAD_SCOPE_SYSTEM.

#include <pthread.h>
#include <stdio.h>
#define NUM_THREADS 5

int main(int argc, char *argv[])


{
  int i, scope;
  pthread_t tid[NUM_THREADS];
  pthread_attr_t attr;

  /* obtém os atributos default */


  pthread_attr_init(&attr);

  /* primeiro descobre o escopo corrente */


  if (pthread_attr_getscope(&attr, &scope) != 0)
    fprintf(stderr, “Unable to get scheduling
    scope\n”);
  else {
    if (scope == PTHREAD_SCOPE_PROCESS)
      printf(“PTHREAD_SCOPE_PROCESS”);
    else if (scope == PTHREAD_SCOPE_SYSTEM)
      printf(“PTHREAD_SCOPE_SYSTEM”);
    else
      fprintf(stderr, “Illegal scope value.\n”);
}

/* define o algoritmo de scheduling como PCS ou SCS */


pthread_attr_setscope(&attr, PTHREAD_SCOPE_SYSTEM);

/* cria os threads */
for (i = 0; i < NUM_THREADS; i++)
       pthread_create(&tid[i],&attr,runner,NULL);

/* agora vincula cada um dos threads */


for (i = 0; i < NUM_THREADS; i++)
       pthread_join(tid[i], NULL);
}

/* Cada thread assumirá o controle nessa função */


void *runner(void *param)
{
      /* faz algum trabalho ... */

     pthread_exit(0);
}

Figura 6.8 API de scheduling do Pthreads.

6.5 Scheduling para Múltiplos Processadores
Até  agora  nossa  discussão  enfocou  os  problemas  de  scheduling  da  CPU  em  um  sistema  com  um  único
processador. Se múltiplas CPUs estão disponíveis, o compartilhamento de carga torna­se possível; mas os
problemas do scheduling passam a ser igualmente mais complexos. Muitas possibilidades têm sido tentadas,
e como vimos no scheduling da CPU com um único processador, não há uma solução melhor.
Aqui,  discutimos  várias  questões  referentes  ao  scheduling  com  múltiplos  processadores.  Enfocamos
sistemas em que os processadores são idênticos — homogêneos — quanto à sua  funcionalidade.  Podemos
assim  usar  qualquer  processador  disponível  para  executar  qualquer  processo  na  fila.  Observe,  no  entanto,
que,  mesmo  com  multiprocessadores  homogêneos,  podemos  ter  limitações  no  scheduling.  Considere  um
sistema com um dispositivo de I/O conectado a um bus privado de um processador. Processos que queiram
usar esse dispositivo devem ser designados para execução nesse processador.
6.5.1 Abordagens para o Scheduling com Múltiplos Processadores
Em uma das abordagens para o scheduling da CPU em um sistema multiprocessador, todas as decisões de
scheduling,  o  processamento  de  I/O  e  outras  atividades  do  sistema  são  manipulados  por  um  único
processador  —  o  servidor  mestre.  Os  outros  processadores  executam  apenas  código  de  usuário.  Esse
multiprocessamento assimétrico é simples porque somente um processador acessa as estruturas de dados
do sistema, reduzindo a necessidade de compartilhamento de dados.
Uma  segunda  abordagem  usa  o  multiprocessamento simétrico (SMP  —  symmetric  multiprocessing),
em que cada processador faz o seu próprio scheduling. Todos os processos podem estar numa fila de prontos
comum  ou  cada  processador  pode  ter  sua  própria  fila  privada  de  processos  prontos.  De  uma  forma  ou  de
outra,  o  scheduling  é  executado  tendo  o  scheduler  de  cada  processador  que  examinar  a  fila  de  prontos  e
selecionar  um  processo  para  execução.  Como  vimos  no  Capítulo  5,  se  existem  múltiplos  processadores
tentando acessar e atualizar a mesma estrutura de dados, o scheduler deve ser programado cuidadosamente.
Devemos  assegurar  que  dois  processadores  não  selecionem  o  mesmo  processo  para  execução  e  que
processos não sejam perdidos da fila. Praticamente todos os sistemas operacionais modernos dão suporte ao
SMP, incluindo o Windows, o Linux e o Mac OS X. No resto desta seção, discutimos questões relacionadas
com os sistemas SMP.

6.5.2 Afinidade com o Processador
Considere o que ocorre na memória cache quando um processo é executado em um processador específico.
Os dados acessados mais recentemente pelo processo preenchem o cache do processador. Como resultado,
acessos  sucessivos  à  memória  executados  pelo  processo  são  atendidos  com  frequência  na  memória  cache.
Agora considere o que acontece quando o processo migra para outro processador. O conteúdo da memória
cache  deve  ser  invalidado  para  o  primeiro  processador,  e  o  cache  do  segundo  processador  deve  ser
preenchido  novamente.  Por  causa  do  alto  custo  da  invalidação  e  repovoamento  dos  caches,  a  maioria  dos
sistemas  SMP  tenta  evitar  a  migração  de  processos  de  um  processador  para  outro  e,  em  vez  disso,  tenta
manter  o  processo  em  execução  no  mesmo  processador.  Isso  é  conhecido  como  afinidade  com  o
processador — isto é, um processo tem afinidade com o processador em que está em execução corrente.
A  afinidade  com  o  processador  assume  diversas  formas.  Quando  um  sistema  operacional  tem  uma
política de tentar manter um processo em execução no mesmo processador — mas não garantindo que ele
fará  isso  —  temos  uma  situação  conhecida  como  afinidade leve. Nesse  caso,  o  sistema  operacional  tenta
manter o processo em um único processador, mas é possível que um processo migre entre processadores. Por
outro lado, alguns sistemas fornecem chamadas de sistema que dão suporte à afinidade rígida, permitindo
que  um  processo  especifique  um  subconjunto  de  processadores  em  que  ele  pode  ser  executado.  Muitos
sistemas fornecem tanto a afinidade leve quanto a rígida. Por exemplo, o Linux implementa a afinidade leve,
mas também fornece a chamada de sistema sched_setaffinity ( ), que suporta a afinidade rígida.
A arquitetura da memória principal de um sistema pode afetar questões relacionadas com a afinidade com
o  processador.  A  Figura  6.9  ilustra  uma  arquitetura  representando  o  acesso  não  uniforme  à  memória
(NUMA), em que uma CPU tem acesso mais rápido a algumas partes da memória principal do que a outras
partes. Normalmente, isso ocorre em sistemas que contêm placas de CPU e memória combinadas. As CPUs
em uma placa podem acessar a memória nessa placa com mais rapidez do que conseguem acessar a memória
em outras placas do sistema. Se os algoritmos de scheduler da CPU e de alocação da memória do sistema
operacional  funcionam  em  conjunto,  um  processo  ao  qual  é  atribuída  afinidade  com  uma  CPU  específica
pode ter memória alocada na placa em que essa CPU reside. Esse exemplo também mostra que os sistemas
operacionais  frequentemente  não  são  definidos  e  implementados  de  maneira  tão  clara  como  descrito  nos
livros de sistemas operacionais. Em vez disso, as “linhas sólidas” entre as seções de um sistema operacional
são com frequência apenas “linhas pontilhadas” com algoritmos criando conexões de maneiras destinadas a
otimizar o desempenho e a confiabilidade.
Figura 6.9 O NUMA e o scheduling da CPU.

6.5.3 Balanceamento de Carga
Em sistemas SMP, é importante manter a carga de trabalho balanceada entre todos os processadores para que
os benefícios do uso de mais de um processador sejam auferidos plenamente.
Caso contrário, um ou mais processadores podem ficar ociosos enquanto outros terão cargas de trabalho
altas, juntamente com listas de processos esperando pela CPU. O balanceamento de carga tenta  manter  a
carga de trabalho uniformemente distribuída entre todos os processadores em um sistema SMP. É importante
observar  que  normalmente  o  balanceamento  de  carga  é  necessário  somente  em  sistemas  em  que  cada
processador tem sua própria fila privada de processos elegíveis para execução. Em sistemas com uma fila de
execução  comum,  o  balanceamento  de  carga  não  costuma  ser  necessário,  porque,  uma  vez  que  um
processador se torne ocioso, ele extrai imediatamente um processo executável da fila de execução comum.
Também é importante observar, no entanto, que, na maioria dos sistemas operacionais contemporâneos que
suportam o SMP, cada processador tem uma fila privada de processos elegíveis.
Existem  duas  abordagens  gerais  para  o  balanceamento  de  carga:  migração  por  impulsão  e  migração
por extração. Na  migração  por  impulsão,  uma  tarefa  específica  verifica  periodicamente  a  carga  em  cada
processador  e  —  quando  encontra  um  desequilíbrio  —  distribui  uniformemente  a  carga,  movendo  (ou
impulsionando) processos de processadores sobrecarregados para processadores ociosos ou menos ocupados.
A migração por extração ocorre quando um processador ocioso extrai uma tarefa que está esperando em um
processador  ocupado.  As  migrações  por  impulsão  e  extração  não  precisam  ser  mutuamente  exclusivas; na
verdade,  são  frequentemente  implementadas  em  paralelo  em  sistemas  de  balanceamento  de  carga.  Por
exemplo,  o  scheduler  do  Linux  (descrito  na  Seção  6.7.1)  e  o  scheduler  ULE  disponível  para  sistemas
FreeBSD implementam as duas técnicas.
O  interessante  é  que  geralmente  o  balanceamento  de  carga  neutraliza  os  benefícios  da  afinidade  com  o
processador, discutida na Seção 6.5.2. Isto é, a vantagem de mantermos um processo em execução no mesmo
processador é que o processo pode se beneficiar de seus dados estarem na memória cache desse processador.
A  extração  ou  a  impulsão  de  um  processo  de  um  processador  para  outro  invalida  esse  benefício.  Como
costuma ocorrer na engenharia de sistemas, não há uma regra absoluta com relação a que política é melhor.
Portanto,  em  alguns  sistemas,  um  processador  ocioso  sempre  extrai  um  processo  de  um  processador  não
ocioso.  Em  outros  sistemas,  os  processos  são  movidos  apenas  quando  o  desequilíbrio  excede  determinado
limite.

6.5.4 Processadores Multicore
Tradicionalmente, os sistemas SMP têm permitido que vários threads sejam executados concorrentemente,
fornecendo múltiplos processadores físicos. No entanto, uma prática recente no hardware dos computadores
tem  sido  a  inserção  de  múltiplos  núcleos  processadores  no  mesmo  chip  físico,  resultando  em  um
processador  multicore.  Cada  núcleo  mantém  o  estado  de  sua  arquitetura  e,  portanto,  parece  ser  um
processador  físico  separado  para  o  sistema  operacional.  Sistemas  SMP  que  usam  processadores  multicore
são mais rápidos e consomem menos energia do que sistemas em que cada processador tem seu próprio chip
físico.
Os  processadores  multicore  podem  complicar  questões  relacionadas  com  o  scheduling.  Vejamos  como
isso pode ocorrer. Pesquisadores descobriram que, quando um processador acessa a memória, ele  gasta  um
montante de tempo significativo esperando que os dados fiquem disponíveis. Essa situação, conhecida como
obstrução  da  memória,  pode  ocorrer  por  várias  razões,  como  um  erro  de  cache  (acesso  a  dados  que  não
estão  na  memória  cache).  A  Figura 6.10 ilustra  uma  obstrução  da  memória.  Nesse  cenário,  o  processador
pode gastar até 50% de seu tempo esperando que os dados da memória se tornem disponíveis. Para remediar
essa situação, muitos projetos de hardware recentes têm implementado núcleos processadores multithreaded
em que dois  (ou  mais)  threads  de  hardware  são  atribuídos  a  cada  núcleo.  Dessa  forma,  se  um  thread  ficar
obstruído enquanto espera pela memória, o núcleo pode permutar para outro thread. A Figura 6.11 ilustra um
núcleo  processador  com  thread  dual  em  que  a  execução  do  thread  0  e  a  execução  do  thread  1  são
intercaladas. Para o sistema operacional, cada thread de hardware aparece como um processador lógico que
está  disponível  para  executar  um  thread  de  software.  Portanto,  em  um  sistema  dual­core  e  dual­threaded,
quatro  processadores  lógicos  são  apresentados  ao  sistema  operacional.  A  CPU  UltraSPARC  T3  tem
dezesseis núcleos por chip e oito threads de hardware por núcleo. Para o sistema operacional, parece haver
128 processadores lógicos.
Em  geral,  há  duas  maneiras  de  tornar  um  núcleo  processador  multithreaded:  criação  de  ambiente
multithreads  de  baixa  granularidade  e  de  alta  granularidade.  No  ambiente  multithread  de  baixa
granularidade, um thread é executado em um processador até que ocorra um evento de latência longa como
uma obstrução da memória. Em razão do atraso causado pelo evento de latência longa, o processador deve
permutar para outro thread e começar sua execução. No entanto, o custo da alternância entre threads é alto, já
que o pipeline de instruções deve ser esvaziado antes que o outro thread possa começar a ser executado no
núcleo processador. Uma vez que esse novo thread comece a ser executado, ele inicia o preenchimento do
pipeline com suas instruções. O ambiente multithread de alta granularidade (ou intercalado) alterna­se entre
os  threads  com  um  nível  de  granularidade  muito  mais  fina  —  normalmente  no  limite  de  um  ciclo  de
instrução.  No  entanto,  o  projeto  da  arquitetura  de  sistemas  de  alta  granularidade  inclui  a  lógica  para  a
alternância entre threads. Como resultado, o custo da alternância entre threads é baixo.

Figura 6.10 Obstrução da memória.

Figura 6.11 Sistema multithreaded e multicore.
Observe  que  um  processador  multicore  e  multithreaded  requer  na  verdade  dois  níveis  diferentes  de
scheduling. Em um nível estão as decisões de scheduling que devem ser tomadas pelo sistema operacional ao
selecionar qual thread de software executar em cada thread de hardware (processador lógico). Para esse nível
de scheduling, o sistema operacional pode selecionar qualquer algoritmo de scheduling, como os descritos na
Seção 6.3. Um  segundo  nível  de  scheduling  especifica  como  cada  núcleo  decide  qual  thread  de  hardware
executar.  Há  várias  estratégias  que  podem  ser  adotadas  nessa  situação.  O  UltraSPARC  T3,  mencionado
anteriormente,  usa  um  algoritmo  round­robin  simples  para  organizar  a  execução  dos  oito  threads  de
hardware  para  cada  núcleo.  Outro  exemplo,  o  Intel  Itanium,  é  um processador dual­core com dois threads
gerenciados  pelo  hardware  por  núcleo.  A  cada  thread  de  hardware  é  atribuído  um  valor  de  urgência
dinâmico  que  varia  de  0  a  7,  com  0  representando  a  urgência  mais  baixa,  e  7,  a  mais  alta.  O  Itanium
identifica cinco eventos diferentes que podem disparar uma permuta de threads. Quando um desses eventos
ocorre, a lógica de alternância de threads compara a urgência dos dois threads e seleciona o thread com valor
de urgência mais alto para executar no núcleo processador.

6.6 Scheduling da CPU de Tempo Real
O  scheduling  da  CPU  para  sistemas  de  tempo  real envolve questões especiais. Em geral, podemos fazer a
distinção entre sistemas  de  tempo  real  não  crítico  e  sistemas  de  tempo  real  crítico.  Os  sistemas  de  tempo
real  não  crítico  não  fornecem  garantia  de  quando  um  processo  de  tempo  real  crítico  será  alocado  no
schedule.  Eles  garantem  apenas  que  o  processo  terá  preferência  sobre  processos  não  críticos.  Sistemas de
tempo real crítico têm requisitos mais rigorosos. Uma tarefa deve ser atendida de acordo com seu limite de
tempo;  o  atendimento  após  o  limite  de  tempo  ter  expirado  é  o  mesmo  que  não  haver  atendimento.  Nesta
seção, exploramos várias questões relacionadas com o scheduling de processos em sistemas operacionais de
tempo real tanto crítico quanto não crítico.

6.6.1 Minimizando a Latência
Considere a natureza dirigida por eventos de um sistema de tempo real. O sistema espera normalmente pela
ocorrência  de  um  evento  de  tempo  real.  Eventos  podem  ocorrer  em  software  —  como  quando  um  timer
expira — ou em hardware — como quando um veículo de controle remoto detecta que está se aproximando
de  um  obstáculo.  Quando  um  evento  ocorre,  o  sistema  deve  responder  a  ele  e  atendê­lo  o  mais  rápido
possível. Denominamos latência do evento o período de tempo decorrido desde a ocorrência de um evento
até o seu atendimento (Figura 6.12).
Usualmente, eventos diferentes têm diferentes requisitos de latência. Por exemplo, o requisito de latência
para um sistema de freios antitravamento pode ser de três a cinco milissegundos. Isto é, a partir do momento
em que uma roda detecta que está derrapando, o sistema que controla os freios antitravamento terá de três a
cinco milissegundos para responder à situação e controlá­la. Qualquer resposta mais demorada pode resultar
na perda do controle da direção do automóvel. Por outro lado, um sistema embutido de controle de radar em
uma aeronave pode tolerar um período de latência de vários segundos.
Dois tipos de latências afetam o desempenho de sistemas de tempo real:

1. Latência de interrupção
2. Latência de despacho

A latência de interrupção é o período de tempo que vai da chegada de uma interrupção na CPU até o
início  da  rotina  que  atende  à  interrupção.  Quando  ocorre  uma  interrupção,  o  sistema  operacional  deve
primeiro concluir a instrução que está executando e determinar o tipo de interrupção que ocorreu. Então, ele
deve  salvar  o  estado  do  processo  corrente  antes  de  atender  à  interrupção  usando  a  rotina  de  serviço  de
interrupção  (ISR  —  interrupt  service  routine)  específica.  O  tempo  total  requerido  para  a  execução  dessas
tarefas  é  a  latência  de  interrupção  (Figura  6.13).  Obviamente,  é  crucial  que  os  sistemas  operacionais  de
tempo real minimizem a latência de interrupção para assegurar que as tarefas de tempo real recebam atenção
imediata.  Na  verdade,  para  sistemas  de  tempo  real  crítico,  a  latência  de  interrupção  não  deve  apenas  ser

Anda mungkin juga menyukai