Transcript
Sumário
1. Introdução a História do Sistema Operacional UNIX 4
2. UNIX Uma Visão Geral 8
2.1 Modularidade 8
2.2 Multitarefa (Multitasking) 8
2.3 Multiusuário (Multiuser) 9
2.4 Portabilidade 10
2.5 Conectividade e Comunicações 10
3. Estrutura do UNIX 12
4. Shell (Interpretador de Comandos) 14
5. Noções de Linha de Comando no UNIX 16
6. Arquivos de Inicialização 18
7. Gerenciamento de Arquivos 22
7.1 Atributos e Nomes de Arquivos 22
8. Gerenciamento de Processos e Escalonamento no UNIX 22
8.1 Blocos de Controle de Processos (BCP) 22
8.2 Escalonamento de CPU 24
9. Memória e Gerenciamento 25
10. Deadlocks 27
10.1 Condições para Ocorrências de Deadlocks 27
10.2 Algoritmo do Avestruz 28
10.3 Algoritmo do Banqueiro 28
10.4 Tratamento de Deadlocks 29
10.5 Detectar e Recuperar 29
11. Threads no UNIX 30
12. Kernel 32
13. Dispositivos de E/S 34
14. Comunicação Serial e Paralela 35
15. Configuração do TCP/IP 36
16. Serviços TCP/IP
17. Procedimentos de boot e o Init
18. Referências Bibliográficas
1. Introdução a História do Sistema Operacional UNIX
Segundo Fernando Krahe[1], as raízes do UNIX datam dos meados dos anos
60, quando a AT&T, Honeywell, GE e o MIT embarcaram em um massivo projeto
para desenvolvimento de um utilitário de informação, chamado Multics
(Multiplexed Information and Computing Service). O sistema deveria ser
multiusuário, com um sistema de arquivos de alta confiança, suporte a
vários tipos de aplicações, e a vários ambientes de programação e
interfaces de usuário.
Multics era um sistema modular montado em uma bancada de processadores,
memórias e equipamentos de comunicação de alta velocidade. Pelo desenho,
partes do computador poderiam ser desligadas para manutenção sem que outras
partes ou usuários fossem afetados. O objetivo era prover serviço 24 horas
por dia 365 dias por ano - um computador que poderia ser tornado mais
rápido adicionando mais partes.
Em 1969, o projeto estava muito atrasado em relação ao seu cronograma e
a AT&T resolveu abandona-lo. O projeto continuou no MIT. Neste mesmo ano,
Ken Thompson e Dennis Ritchie decidiram desenvolver algumas idéias do
Multics, eles reescreveram todo o sistema operacional para um computador
bem menos potente, um DEC PDP-7, de 4 kbytes de memória. Thompson terminou
o trabalho no verão de 1969, foi utilizada a linguagem BCPL (conhecida
popularmente como Linguagem B "assembly"), que contava com funções básicas:
editor de texto, montador (ou assembler, que transforma linguagem assembly
em linguagem de máquina) e interpretador de comandos (um Shell) neste
período o sistema era chamado de Unics (UNiplexed Information and Computing
Service), numa alusão ao Multics, e foi logo rebatizado como Unix.
A primeira versão do sistema "BELL LABs PDP-11" foi lançada em 1º
Janeiro de 1970, data considerada como a de nascimento do sistema Unix, o
que explica porque todos os relógios dos sistemas de exploração Unix
começam a partir desta data.
O projeto cresceu e surgiu a necessidade de se usar um sistema
operacional compatível com diferentes plataformas de hardware, o que levou
ao desenvolvimento de uma nova linguagem de programação, que pudesse ser
portada com facilidade, sem ou com pouquíssimas modificações no
source(código-fonte).
Em 1972, Dennis Ritchie e Brian W.Kernighan, participaram ativamente do
desenvolvimento da Linguagem C, (motivo pelo qual, é considerado como um
dos seus inventores), a linguagem C superava as limitações da linguagem B,
assim, o conjunto do sistema UNIX foi inteiramente reescrito em linguagem C
em 1973 e batizado Unix Time-Sharing System (TSS).
Em 1973 o UNIX foi reescrito em C, talvez o fato mais importante da
história deste sistema operacional. Isto significava que o UNIX poderia ser
portado para novo hardware em meses, e que mudanças eram fáceis. A
linguagem C foi projetada para o sistema operacional UNIX, e portanto há
uma grande sinergia entre C e UNIX.
Como a lei impedia a empresa AT&T, e a Bell Labs, de comercializar
outra coisa que não equipamentos telefônicos ou telegráficos, tomo-se a
decisão de distribuir os fontes de UNIX para as universidades para fins
educativos em 1973.
Em 1975 foi lançada a V6, que foi a primeira versão de UNIX amplamente
disponível fora dos domínios do Bell Labs, especialmente em universidades.
Este foi o início da diversidade e popularidade do UNIX. Nesta época na
Universidade de Berkley os alunos começaram a fazer modificações ao
sistema.
Começaram a surgir novas versões, além da original da AT&T (rebatizada
como System V); a primeira foi desenvolvida na Universidade de Berkeley,
denominado BSD (Berkeley Software Distribution), liberada publicamente no
final de 1977, precursor dos atuais e bem-sucedidos BSD's, (alguns anos
depois com base no sistemas BSD surgiram novas "versões", como o freeBSD,
netBSD e openBSD, que tinham o kernel baseado no sistema BSD).
Assim dois ramos de desenvolvimento das fontes se formaram:
AT&T que ia tornar-se System V de UNIX System Labs (USL);
BSD (Berkeley Software Developpement) desenvolvido pela
Universidade de Califórnia.
Ainda em 1977 a AT&T deixou os fontes do UNIX à disposição das
outras empresas, de modo que um grande número de UNIX-like foram
desenvolvidos:
AIX, Unix comercial baseado no System V desenvolvido em
fevereiro de 1990 pela IBM;
Sun Solaris, Unix comerciais baseado no System V e BSD
desenvolvido pela Sun Microsystems ;
HP-UX, Unix comercial baseado em BSD desenvolvido a partir de
1986 pela Hewlett Packard ;
Ultrix, Unix comerciais desenvolvido pela Digital Equipment
Corporation;
IRIX, Unix comercial desenvolvido pela Silicon Graphics;
Unixware, Unix comerciais desenvolvidos pela Novell;
Unix SCO, Unix comercial baseado no System V desenvolvido a
partir de 1979 pela Santa Cruz Operações e Hewlett Packard;
Tru64 UNIX, Unix comercial desenvolvido pela Compaq.
Em 1978 Berkley Software Distribuition lança a série 2.xBSD para PDP -
11 (a versão 2.11 foi lançada em 1992). Nesta versão saiu o csh. Neste ano
também saiu a série 3BSD, que teve uma importante contribuição, virtual
memory.
Em 1979 saiu a V7 e o Unix foi portado para o novo VAX da Digital. Esta
versão incluia C K&R completo, uucp, Bourne Shell. O kernel tinha meramente
40 bytes! Esta foi a primeira versão vendida comercialmente do sistema, mas
usada principalmente por universidades. Em sua versão 7 de 1979, a evolução
foi acompanhada de numerosas modificações notáveis como:
a supressão do bridage ligado à dimensão dos arquivos,
uma melhor mobilidade do sistema (funcionamento sobre
numerosas plataformas materiais),
a adição de numerosos utilitários.
Em 1983 é lançado o System V da AT&T e o 4.2 BSD. O SV incluía o pacote
IPC (shm, msg, sem) para comunicação entre processos. Surgiram outras
versões do SV com a inclusão de novas características como sharedlibs no
SVR4. O 4.2BSD foi talvez uma das mais importantes versões do UNIX. O seu
software de conexão de redes tornava muito fácil a tarefa de conectar
computadores UNIX a redes locais. Nessa versão é que foram integrados os
softwares que implementam TCP/IP e sockets.
A AT&T parou de disponibilizá-lo livremente, passou a ser
disponibilizado por um preço muito alto, é depois de algum tempo uma serie
de universidades, empresas e grupos de programadores, começaram a
desenvolver uma serie de aplicativos para o Unix (desde jogos até
aplicações comerciais).
Tantas variedades de Unix surgindo a todo momento, com as mesmas
características, mas com tendências a se divergirem fez surgir então em
1985 o padrão POSIX (Portable Operating System Interface for UniX), um
conjunto de padrões definidos pelo IEEE(Institute of Electrical and
Electronics Engineers). POSIX assim é conhecido igualmente sob o nome IEEE
P1003.
O POSIX não permitiu compatibilidade de rodar programas binários entre
os vários Unix, mas sim facilidade de portar um programa de um Unix para
outros, através de compilação de códigos-fonte em C.
Em 1988 foi lançado o SVR4. Este sistema era um merge de releazes
anteriores do SV, BSD e SunOs, uma implementação descendente de BSD. O
4.4BSD foi lançado em 1992 para várias plataformas: HP 9000/300, Sparc,
386, DEC e outras, mas não em VAX. Entre as novas características estão:
Novo sistema de memória virtual baseado em Mach 2.5
Suporte ISO/OSI (baseado em ISODE)
A Sun Microsystem também lançou a sua versão do UNIX a partir do BSD.
Isto ocorreu até a versão SunOs 4.x. A nova versão, SunOs 5.x está baseada
no SVR4, embora tenha herdado algumas características do SunOs 4.x. O novo
sistema operacional da Sun, Solaris 2.x, é um SO que engloba SunOs 5.x,
Open Network Computing e Open Windows. É o solaris que provê o pacote de
compatibilidade entre os BSD/SunOs e o SVR4/SunOs 5.x.
A Microsoft também lançou uma versão do UNIX, chamada XENIX, que rodava
em PCs. Este sistema era inicialmente baseado na Versão 7, depois herdou
características dos SIII e depois do SV.
No início da década de 1990, a AT&T vendeu o código UNIX para a Novell.
Em 1995, a Novell vendeu parcialmente alguns dos direitos do código UNIX à
Santa Cruz Operation. Em 2000, a Santa Cruz Operation vendeu o código UNIX
para a Caldera Systems, que mudou seu nome para SCO.
Atualmente, Unix (ou *nix) é o nome dado a uma grande família de
Sistemas Operacionais que partilham muitos dos conceitos dos Sistemas Unix
originais, todos desenvolvidos sob a plataforma de padrões como o POSIX e
outros. Alguns dos Sistemas derivados do Unix são: BSD (FreeBSD, OpenBSD e
NetBSD), Solaris (anteriormente conhecido por SunOS), IRIX, AIX, HP-UX,
Tru64, Linux (nas suas centenas de distribuições), e até o Mac OS X
(baseado em um kernel Mach BSD chamado Darwin). Existem mais de quarenta
sistemas operacionais *nix, rodando desde celulares a supercomputadores, de
relógios de pulso a sistemas de grande porte.
2. O Que é o UNIX?
O Unix é um sistema operacional multi-usuário e multi-tarefas, o que
permite a um computador executar simultaneamente vários programas para um
ou vários usuários. Multitarefa significa executar varias tarefas ou
processos simultaneamente, porem, em um hardware monoprocessado, o sistema
multi-thread não funciona, mas os processos são executados seqüencialmente
de forma tão rápida que parecem estar sendo executados simultaneamente, O
Unix escalona sua execução e reserva-lhes recursos computacionais
(intervalo de tempo de processamento, espaço em memória RAM, espaço no
disco rígido, etc.).
Hoje em dia, os sistemas Unix estão presentes nos meios profissionais e
universitários graças à sua grande estabilidade, ao seu nível de segurança
elevado e ao respeito dos grandes padrões, nomeadamente em matéria de rede.
O Unix apresenta também como característica o seu sistema de arquivos.
Tudo neste sistema operacional é considerado um arquivo, até mesmo os
dispositivos de I/O são tratados como arquivo pelo sistema. Além do
conceito de arquivos, outros conceitos presentes no sistema são: processos,
shell, diretórios e path.
2.1 Modularidade
O UNIX é único em seu desenho modular, que permite usuários adicionar
ou remover partes para adaptá-lo às suas necessidades específicas. Os
módulos se encaixam com conexões-padrão. É possível tirar um módulo e
substituí-lo por um outro ou expandir o sistema acrescentando vários
módulos. De uma certa maneira, o sistema UNIX de cada pessoa é único.
Muitos usários acrescentam ou eliminam módulos sempre que precisam,
adaptando suas implementações às suas necessidades. Geralmente, é possível
remover um módulo, sem prejudicar a operação do resto do sistema. Esta
característica é muito útil nas implementações em microcomputadores, onde
as unidades de disco têm capacidade limitada. A remoção de arquivos
desnecessários abre espaço para mais arquivos de dados.
2.2 Multitarefa (Multitasking)
A capacidade de multitasking do UNIX permite que mais de uma tarefa
seja realizada simultaneamente. Esta é uma das principais características
do UNIX. As tarefas que estão em execução concorrem pelos recursos da
máquina que são controlados pelo sistema. É possível, portanto, que um
programa que atualize um banco de dados seja rodando ao mesmo tempo que a
impressão de um relatório esteja sendo realizada e uma tela de terminal
esteja sendo enviada.
O Unix é um sistema operacional de multitarefa preemptiva, ou seja,
quando termina o intervalo de tempo (chamado quantum), a execução do
processo e suspensa, salva o seu contexto (informações necessárias para a
execução do processo), para que ele possa ser retomado posteriormente, e
coloca em execução o próximo processo da fila de espera, também determina
quando cada processo será executado, a duração de sua execução e a sua
prioridade sobre os outros. A multitarefa, permite que o usuário e o
computador fiquem livres para realizarem outras tarefas economizando tempo.
Em uma interface gráfica com um ambiente de janelas pode-se disparar
cada tarefa em uma janela shell. Já em um ambiente não gráfico, as tarefas
que podem ser executadas sem a intervenção do usuário são colocadas em
background. Em foreground deixa-se as tarefas que necessitam da intervenção
do usuário, como a edição de um arquivo.
2.3 Multiusuário (Multiuser)
O Unix permite que vários usuários utilizem um mesmo computador
simultaneamente, geralmente por meio de terminais, semelhante ao Mainframe.
Cada terminal é composto de um monitor, um teclado e, eventualmente, um
mouse. Vários terminais podem ser conectados ao mesmo computador num
sistema Unix. Há alguns anos eram usadas conexões seriais, mas atualmente é
mais comum o uso de redes locais, principalmente para o uso de terminais
gráficos.
O Unix gerencia os pedidos que os usuários fazem, evitando a
interferência de uns nos outros. Cada usuário possui direitos de
propriedade e permissões exclusivas sobre arquivos. Quaisquer arquivos
modificados pelo usuário conservarão esses direitos. Programas executados
por um usuário comum estarão limitados em termos de quais arquivos poderão
acessar.
O sistema Unix possui dois tipos de usuários: o usuário root (também
conhecido como super-usuário), semelhante ao administrador do Windows, que
possui a missão de administrar o sistema, podendo manipular todos os
recursos do sistema operacional; e os usuários comuns, que possuem direitos
limitados.
Para que o sistema opere adequadamente em modo multiusuário, existem
alguns mecanismos:
Sistema de autenticação de usuário (o programa login, por
exemplo, autentica o usuário verificando uma base de dados,
normalmente armazenada no arquivo /etc/password);
Sistema de permissões e propriedades sobre arquivos (os
direitos anteriormente citados);
Proteção de memória, impede que um processo de usuário acesse
dados ou interfira com outro processo. Esse mecanismo é feito
com a ajuda do hardware, que divide o ambiente de processamento
e memória em modo supervisor (ou modo Unix 4 núcleo) e modo
usuário.
2.4 Portabilidade
A portabilidade é a possibilidade dos softwares que operam em uma
máquina operarem em uma outra diferente. Há dois tipos de portabilidade a
serem considerados:
Portabilidade do sistema operacional
Portabilidade dos aplicativos
Mais de 90% do programa Kernel está escrito em C e menos de 10% em
linguagem de máquina. Assim, na pior das hipóteses, apenas 10% do programa
Kernel terá de ser reescrito ao se deslocar o Kernel para uma máquina com
arquitetura diferente. Os programas aplicativos escritos em linguagem de
nível mais alto, como C, são facilmente portáveis para vários sistemas
UNIX. Basta que sejam recompilados, exigindo, as vezes, poucas alterações.
O Unix Possui vários interpretadores de comandos (Shell) bem como um
grande número de comandos e numerosos utilitários (compiladores para
numerosas linguagens, tratamentos de texto, serviço de mensagens
eletrônicas,etc). Também possui uma grande mobilidade, o que permite sua
instalação em quase todas as plataformas materiais.
2.5 Conectividade e comunicações
No UNIX temos dois tipos de comunicações, quais sejam, comunicação
entre programas e comunicação entre usuários. A comunicação entre programas
é realizada através de mensagens, semáforos ou memória compartilhada. Estes
mecanismos, também conhecidos por IPC, interprocess comunications, são
extensões do System V.
A comunicação entre usuários pode se realizar de diversas maneiras,
entre elas há o Mail, que é um programa para enviar e receber mensagens
eletrônicas, o Write, que escreve uma mensagem no terminal de outra pessoa
logada no sistema, o Wall, que escreve mensagens nos terminais de todas as
pessoas logadas no sistema, o Talk, que permite uma conversa em tempo real.
Há também a comunicação entre os sistemas, tais como UUCP (UNIX to UNIX
Copy Protocol).
3. Estrutura do UNIX
Um sistema Unix é composto basicamente de duas partes:
núcleo - o núcleo do sistema operacional, a parte que
relaciona-se diretamente com o hardware, e que executa num
espaço de memória privilegiado, agenda processos, gerencia a
memória, controla o acesso a arquivos e a dispositivos de
hardware (estes, por meio dos controladores de disposito -
drivers - e interrupções). O acesso ao núcleo é feito por
chamadas de sistema, que são funções fornecidas pelo núcleo;
essas funções são disponibilizadas para as aplicações por
bibliotecas de sistema C (libc).
programas de sistema - são aplicações, que executam em espaços
de memória não privilegiados, e que fazem a interface entre o
usuário e o núcleo. Consistem, principalmente, de:
Conjunto de bibliotecas C (libc)
Shell - um ambiente que permite que o usuário digite comandos.
Programas utilitários diversos - são programas usados para
manipular arquivos, controlar processos, etc.
Ambiente gráfico (GUI) Graphics User Interface - eventualmente
utiliza-se também um ambiente gráfico para facilitar a
interação do usuário com o sistema.
Em um sistema Unix, o espaço de memória utilizado pelo núcleo é
denominado espaço do núcleo ou supervisor (em inglês: Kernel Space); a área
de memória para os outros programas é denominada espaço do usuário (User
Space).
Essa separação é um mecanismo de proteção que impede que programas
comuns interfiram com o sistema operacional.
Para um melhor entendimento, o sistema operacional UNIX pode ser
representado pelas quatro partes básicas, como ilustrado na figura[2]
abaixo:
Kernel: é o núcleo do sistema operacional, controla o hardware
traduzindo comandos UNIX em instruções de hardware. O usuário não
trabalha diretamente com o Kernel.
Sistema de arquivos: é o modo do UNIX armazenar informações de
qualquer tipo, como por exemplo, gráficos, textos, etc.
Shell: é um programa que atua como interface entre o Kernel e o
usuário.
Aplicativos: são programas que podem ser invocados pelo Shell para
realizar diversas tarefas
4. Shell
O SHELL é um interpretador de comandos interativo e constitui a
principal interface entre o usuário e o sistema UNIX. Na SUN há dois tipos,
que podem ser chamados pelo usuário: o "sh", "csh", "tcsh", "bash", e
outros. É a partir do SHELL que os processos podem ser chamados.
SHELL possui variáveis que armazenam informações sobre o sistema.
Elas devem ser inicializadas logo no início da sessão. Algumas
variáveis são:
PATH: guarda o nome dos diretórios permitidos na busca de comandos;
HOME: nome do diretório de entrada do usuário;
O intérpretador de comandos (Shell) é a interface entre o usuário e o
sistema de exploração, daí o seu nome inglês "Shell", que significa
"casca".
Assim, encarregado de ser o intermediário entre o sistema de exploração
e o usuário graças às linhas de comandos escritas por este, a função do
Shell consiste em ler a linha de comando, interpretar o seu significado,
executar o comando e seguidamente dar o resultado nas saídas.
O Shell é assim um arquivo executável, encarregado de interpretar os
comandos, transmiti-los ao sistema e devolver o resultado. Existem vários
shells, sendo o mais corrente o sh (chamado "Bourne shell"), bash ("Bourne
again shell"), csh ("C Shell"), Tcsh ("Tenex C shell"), ksh ("Korn shell")
e zsh ("Zero shell"). O seu nome corresponde geralmente ao nome do
executável.
Cada utilizador possui um shell por padrão, que será lançado quando da
abertura de uma janela de comando. Por padrão, o Shell está no ficheiro de
configuração /etc/passwd no último campo da linha que corresponde ao
utilizador. É possível alterar o shell numa sessão, executando muito
simplesmente o ficheiro executável correspondente, por exemplo:
/bin/bash
Janela de comando (rápida)
O Shell inicia lendo a sua configuração global (num ficheiro do
directório /etc/), seguidamente lendo a configuração própria ao utilizador
(num ficheiro escondido, cujo nome começa por um ponto, situado no
directório básico do utilizador, ou seja
/home/nom_de_l_utilisateur/.fichier_de_configuration), por último mostra a
jenale de comando (em inglês prompt) do seguinte modo:
machine:/repertoire/courant$
Por padrão, na maior parte dos shells, o prompt é composto pelo nome da
máquina, seguido de dois pontos (:), do directório corrente, seguidamente
de um carácter que indica o tipo de utilizador conectado :
"$" indica que se trata de um utilizador normal;
"#" indica que se trata do administrador, chamado "root".
5. Noção de linha de comando no UNIX
Uma linha de comando é uma cadeia de caracteres constituída por uma
comando, correspondente a um arquivo executável do sistema ou um comando
Shell, bem como os argumentos (parâmetros) opcionais:
ls -al /home/jf/
No comando acima, ls é o nome do comando, - Al e /home/jf/ são
argumentos. Os argumentos que começam por "-" designam-se opções. Para cada
comando existem geralmente diversas opções que podem ser detalhadas
escrevendo um dos comandos seguintes:
commande --help
commande -?
man commande
Entrada/saída standard, quando da execução de um comando, um dossiê é
criado. Este vai então abrir três fluxos:
stdin, chamado entrada standard, no qual o processo vai ler os
dados de entrada. Por defeito, stdin corresponde ao teclado; STDIN é
identificado pelo número 0;
stdout, chamado saída standard, no qual o processo vai escrever os
dados de saída. Por defeito, stdout corresponde ao monitor; STDOUT é
identificado pelo número 1;
stderr, chamado erro standard, no qual o processo vai escrever as
mensagens de erro. Por defeito stderr corresponde ao monitor. STDERR
é identificado pelo número 2;
6. Arquivos de inicialização
No UNIX temos alguns arquivos que são executados todas as vezes que
abrimos uma Shell, ou quando inicializamos uma nova sessão. Estes arquivos
são chamados de arquivos de inicialização. Através deles, o usuário pode
personalizar alguns comandos que mais utiliza, colocando alguns 'apelidos'
referentes a estes comandos, e/ou alterar o ambiente de trabalho da sua
própria conta.
Alguns exemplos de arquivos de inicialização são:
C Shell: .login e .cshrc
Bash: .bash_profile e .bashrc
Os arquivos, .login e .bash_profile são executados pelo Shell somente
ao iniciar a sessão. Estes arquivos devem conter comandos que precisam ser
executados somente uma vez durante o login. Os arquivos .cshrc e .bashrc
são executados toda vez que uma nova cópia do Shell for chamada.
Para exemplificar abaixo temos um arquivo .cshrc que contém 'apelidos'
para alguns dos comandos do UNIX. O usuário 'apelidou' o comando history de
h. Assim, ao invés dele digitar history, basta digitar h. Como esta
alteração foi feita no arquivo .cshrc, este 'apelido' será válido todas as
vezes que ele abrir uma nova cópia do Shell.
# /etc/cshrc
# csh configuration for all shell invocations.
limit coredumpsize 0k
alias h history
alias l ls -lF
alias psa 'ps xua " more'
set history = 100
save history = 100
Após modificar os arquivos de inicialização, deve-se usar o comando
source, para que as modificações comecem a funcionar. Neste exemplo, temos:
% source .cshrc
Devido a importância dos arquivos de inicialização, é aconselhável que
antes de efetuar qualquer modificação nos mesmos, faça uma cópia de
segurança, para facilitar a reparação de algum erro.
7. Gerenciamento de arquivos
O UNIX armazena toda informação em arquivos no disco rígido. Os
arquivos no UNIX possuem diferentes tipos: arquivo texto, diretório,
executável, etc. Estes arquivos têm nome, nome do proprietário, respectivo
grupo a que pertence, permissões de manipulação, tamanho e data da última
modificação. Através do comando 'ls -laF' obtemos as informações sobre os
arquivos da pasta atual.
Para facilitar o gerenciamento de arquivos, o UNIX utiliza um sistema de
diretórios com nomes particulares, cada qual pode conter arquivos e
subdiretórios próprios. Os diretórios do UNIX apresentam uma hierarquia
representada na forma de uma árvore invertida.
O diretório base do UNIX é chamado de root, sendo representado pelo
símbolo '/'. Abaixo do diretório root, o UNIX cria diversos subdiretórios
com objetivos específicos, a seguir são listados os diretórios mais
importantes:
"/ "diretório raíz, root. "
"/etc "contém arquivos de configuração do sistema. "
"/Bin "contém os principais executáveis do sistema (binários). "
"/usr "contém os programas instalados, aplicativos, documentação do "
" "sistema e dos programas. "
"/dev "contém informações sobre todos os dispositivos (devices) do "
" "sistema. "
"/home "contém os diretórios de usuários. "
"/lib "contém as bibliotecas do sistema. "
"/tmp "contém todos os arquivos temporários. "
"/var "contém os arquivos de informação variável, que são alterados com "
" "freqüência. "
"/sbin "arquivos de sistema essenciais. "
"/boot "contém arquivos de boot ou inicialização. "
"/lost+fou"arquivos recuperados. "
"nd " "
"/mnt "diretório de acesso aos drives, ponto de montagem de partição "
" "temporária. "
7.1 Atributos e Nomes de Arquivos
Conforme texto do IME-USP3, no UNIX cada arquivo (inclusive
diretórios, que são casos particulares de arquivos), conta com um
conjunto de atributos de leitura, escrita e execução, que são setáveis
através do comando chmod, e podem ser exibidos pelo comando:
s-I:
$ Is –I /Bin/cat
-rwxr-xr-x 1root root 16584 Dec 16 20:09 /bin/cat
A string –rwxr-xr-x representa os atributos do arquivo/Bin/cat. O
primeiro caracter (-) significa que se trata de um arquivo regular, em
contraposição aos diretórios (d), device special files (c) e links
simbólicos (I). Os nove caracteres restantes informam as permissões de
leitura, escrita e execução desse arquivo relativas ao seu
proprietário, ao seu grupo, e a todos os demais. O proprietário e o
grupo são informados logo à direita, e no caso são o usuário root e o
grupo root.
3 Unix: Conceitos e Comandos Básicos
Note que no UNIX não existem os atributos de arquivos
ocultos("hidden") e do sistema ("system"), suportados no MS-DOS. Não
obstante, arquivos cujo primeiro caractere é ponto (".") normalmente
são omitidos pelo Is, a não ser que se utilize a opção –a (de "all"),
conforme comentamos no início.
Um atributo importantíssimo existente no UNIX é o chamado setuid,
através do qual um processo adquire ao ser executado os privilégios do
owner do arquivo. Isso é frequentemente utilizado por programas que são
disparados por usuários não privilegiados mas que por algum motivo
necessitam dos privilégios do super-usuário, por exemplo:
$ Is –I /usr/sbin/pppd
-rwsr-xr-x 1root Bin 104876 Apr 27 1998 /usr/sbin/pppd
Sem os privilégios de super-usuário, um usuário comum não
conseguiria configurar a interface ppp e nem alterar a tabela de rotas
do sistema, no momento em que se conecta ao provedor internet. Assim, o
pppd, ao ser executado, requisitará os privilégios do owner do arquivo,
que no caso é o superusuário, e dessa forma ele poderá realizar essas
operações.
No UNIX via de regra são suportados nomes "longos" (até 64 ou às
vezes até 256 caracteres), e o "." Não é um separador entre o nome e a
extensão do arquivo, mas um caractere como os outros. Não obstante, a
noção de "sufixo" é utilizada informalmente para facilitar a
identificação de alguns formatos de arquivos, por exemplo:
.tar Archive produzido com tar
.zip Archive comprimido produzido com zip
.Z Archive comprimido com compress
.gz Archive comprimido com gzip
Archives (o termo não tem correspondente em português) são
concatenações de vários arquivos ou de subárvores inteiras num único
arquivo. Em UNIX, tipicamente são produzidos com tar:
$ tar cvf /tmp/etc.tar / etc
$ tar tvf /tmp/etc.tar / etc
$ cd /tmp; tar xvf etc.tar
O primeiro comando irá criar o archive / tmp/etc.tar, composto por
toda a subárvore/etc. O segundo exibirá o conteúdo desse archive. O
terceiro irá extrair todo o seu conteúdo dentro do diretório /tmp, ou
seja, recriará aquela mesma subárvore como uma subárvore do diretório
/tmp.
8. Gerenciamento de Processos e Escalonamento no Unix
Na visão mais simples, processo é uma instância de um programa em
execução. Um programa, para ser executado, necessita ser carregado na
memória; a área de memória utilizada é dividida em três partes: código
(text), dados inicializados (data) e pilha (stack).
Por ser multitarefa, o Unix utiliza uma estrutura chamada tabela de
processos, que contém informações sobre cada processo, tais como:
identificação do processo (PID), dono, área de memória utilizada,
estado (status).
Apenas um processo pode ocupar o processador em cada instante - o
processo encontra-se no estado "executando" (running). Os outros
processos podem estar "prontos" (ready), aguardando na fila de
processos, ou então estão "dormindo" (asleep), esperando alguma
condição que permita sua execução. Um processo em execução pode ser
retirado do processador por duas razões:
necessita acessar algum recurso, fazendo uma chamada de sistema -
neste caso, após sua retirada do processador, seu estado será
alterado para "dormindo", até que o recurso seja liberado pelo
núcleo;
o núcleo pode interromper o processo (preempção) - neste caso, o
processo irá para a fila de processos (estado "pronto"), aguardando
nova oportunidade para executar - ou porque a fatia de tempo esgotou-
se, ou porque o núcleo necessita realizar alguma tarefa.
Existem quatro chamadas de sistema principais associadas a processos:
fork, exec, exit e wait;
Fork é usada para criar um novo processo, que irá executar o mesmo
código (programa) do programa chamador (processo-pai);
Exec irá determinar o código a ser executado pelo processo chamado
(processo-filho);
Exit termina o processo;
Wait faz a sincronização entre a finalização do processo-filho e o
processo-pai.
8.1 Blocos de Controle de Processos (BCP)
Os processos possuem um espaço de endereçamento que é divido em
segmentos de texto, de dados e de pilha. Os segmentos de dados e de
pilha estão sempre no mesmo espaço de endereçamento, podendo se
expandir separadamente e, geralmente, em direções opostas. Os de texto,
às vezes estão em um espaço de endereçamento diferente dos dados e
pilha, e geralmente é somente de leitura.
A estrutura de texto registra quantos processos estão usando o
segmento de texto, incluindo um ponteiro para uma lista de suas
estruturas de processo, e onde a tabela de página para o segmento de
texto pode ser encontrada no disco, em caso de swapping. A estrutura de
texto em si sempre está residente na memória principal. Os segmentos de
texto, dados e de pilha para os processos podem ser submetidos ao
swapping. Quando os segmentos são trazidos à memória, eles são
paginados.
As tabelas de página gravam informações do mapeamento da memória
virtual do processo para a memória física. A estrutura de processo
contém ponteiros para a tabela de página, para uso quando o processo
está residente na memória principal, ou o endereço do processo no
dispositivo de swap, quando o processo passa pelo swapping. De
relevância mais óbvia ao usuário comum, o diretório corrente e a tabela
de arquivos abertos são mantidos na estrutura de usuário.
Todo processo tem uma fase de usuário e uma fase de sistema. Boa
parte do trabalho é feito no modo usuário, mas, quando uma chamada ao
sistema é feita, ela é realizada no modo de sistema. As fases nunca
executam ao mesmo tempo.
Quando um processo está executando em modo sistema, uma pilha de
kernel para esse processo é usada, em vez da pilha de usuário que
pertence a esse processo. A pilha de kernel e a estrutura de usuário
juntas compõem o segmento de dados do sistema para o processo.
A chamada ao sistema fork aloca uma nova estrutura de processo (com
novo identificador de processo) para o processo filho, e copia a
estrutura de usuário. Não há a necessidade de uma nova estrutura de
texto, os processos compartilham seu texto. Contadores e listas
apropriados são simples segmentos de dados e de pilha do processo
filho. Já a chamada ao sistema vfork não copia os dados e a pilha para
o novo processo, em vez disso, o novo processo simplesmente compartilha
a tabela de página do processo anterior. Uma nova estrutura de usuário
e de processo são criadas.
Quando o processo pai é grande, vfork pode gerar economias
substanciais de tempo de CPU do sistema. No entanto, é uma chamada ao
sistema consideravelmente perigosa, já que qualquer mudança na memória
ocorre nos dois processos até que execve ocorra. A chamada ao sistema
execve não cria um novo processo ou uma nova estrutura de usuário. O
texto e os dados do processo são substituídos. Os arquivos abertos são
preservados, bem como a maioria das propriedades de tratamento de
sinais.
8.2 Escalonamento de CPU
O escalonamento de CPU no UNIX foi criado para beneficiar os
processos interativos. Todo processo tem uma prioridade de
escalonamento associada a ele. Os processos que fazem I/O de disco ou
outras tarefas importantes têm prioridades menores do que zero (número
maiores indicam prioridade menor) e não podem ser interrompidos por
sinais. Os processos de usuários comuns têm menos prioridades de
executar que os processos de sistema.
Quanto mais tempo de CPU um processo acumula, menor (ou mais
positiva) se torna a sua prioridade, e vice-versa. Assim, existe um
feedback negativo no escalonamento de CPU e é difícil para um único
processo usar todo o tempo da CPU. O envelhecimento de processos é
usado para evitar paralisação.
Não existe preempção de um processo por outro dentro do kernel. Um
processo pode abandonar a CPU porque está esperando por I/O ou porque
sua fatia de tempo expirou. Quando um processo escolhe abandonar a CPU,
ele é suspenso em um evento. A experiência sugere que o escalonador
UNIX tem melhor desempenho com jobs limitados por I/O, como pode ser
observado quando existem vários Jobs limitados por CPU em execução.
Os processos são representados por duas estruturas: a estrutura de
processo e a estrutura de usuário. O escalonamento de CPU é um
algoritmo com prioridades calculadas dinamicamente, que se reduz ao
Round-Robin, em casos extremos.
O escalonamento Round-Robin é realizado pelo mecanismo de timeout,
que informa ao driver de interrupção de clock para chamar uma sub-
rotina do kernel apor um intervalo específico. A sub-rotina a ser
chamada nesse caso causa o reescalonamento e, em seguida, submete
novamente um timeout para si mesma.
9. MEMÓRIA E GERENCIAMENTO
As primeiras versões do Unix utilizavam basicamente a técnica de
swapping para o gerenciamento de memória. Apenas a partir da versão
3BSD, o Unix passou a utilizar paginação por demanda. Atualmente, a
grande maioria das versões do Unix, tanto BSD como System V, implementa
gerenciamento de memória virtual por paginação com swapping.
O espaço de endereçamento dos processos no Unix é dividido em três
segmentos: texto, dados e pilha. O segmento de texto corresponde à área
onde está o código executável dos programas, sendo uma área protegida
contra gravação. O segmento de texto é estático e pode ser
compartilhado por vários processos, utilizando o esquema de memória
compartilhada. O segmento de dados corresponde às variáveis do
programa, como tipos numéricos, vetores e strings. A área de dados é
dinâmica, podendo aumentar ou diminuir durante a execução do programa.
A pilha armazena informações de controle do ambiente do processo, como
parâmetros passados a um procedimento ou system call. A área de pilha
cresce dinamicamente do endereço virtual mais alto para o mais baixo.
O Unix implementa o esquema de paginação por demanda como política
de busca de páginas. Nesse esquema, páginas do processo são trazidas do
disco para a memória principal apenas quando são referenciadas. O
sistema mantém uma lista de páginas livres com todos os frames
disponíveis na memória e gerencia os frames de todos os processos em
uma lista de páginas em uso. Quando um processo faz referência a uma
página que não se encontra na lista de páginas em uso, ocorre um page
fault. O sistema identifica se a página está na memória através do bit
de validade. Nesse caso, a gerência de memória retira uma página da
lista de páginas livres e transfere para a lista de páginas em uso.
Como pode ser observado, o Unix utiliza uma política global de
substituição de páginas.
O sistema implementa uma variação do algoritmo FIFO (First In First
Out) circular como política de substituição de páginas. Esse algoritmo,
conhecido como Two-Handed Clock, utiliza dois ponteiros, ao contrário
do FIFO circular, que implementa apenas um. O primeiro ponteiro fica à
frente do segundo um certo número de frames na lista de páginas em uso.
Enquanto o primeiro ponteiro desliga o bit de referência das páginas, o
segundo verifica o seu estado. Se o bit de referencia continuar
desligado, significa que a página não foi referenciada desde o momento
em que o primeiro ponteiro desligou o bit, fazendo com que essa página
seja selecionada.
Apesar de estarem liberadas para outros processos, as páginas
selecionadas permanecem um certo tempo intactas na lista de páginas
livres. Dessa forma, é possível que as páginas retornem aos processos
de onde foram retiradas, eliminando-se a necessidade de acesso a disco.
O deamon page também é responsável por implementar a política de
substituição de páginas.
Em casos onde o sistema não consegue manter um número suficiente de
páginas livres o mecanismo de swapping é ativado. Nesse caso, o daemon
swapper seleciona, inicialmente os processos que estão a mais tempo
inativos. Em seguida, o swapper seleciona dentre os quatro processos
quem mais consomem memória principal aquele que estiver a mais tempo
inativo. Esse mecanismo é repetido até que a lista de páginas livres
retorne ao seu tamanho normal.
Para controlar todas as páginas e listas na memória principal, a
gerência de memória mantêm uma estrutura de mapeamento dos frames na
memória (core map), com informações sobre todas as páginas livres e em
uso. O core map fica residente na parte não paginável da memória
principal, juntamente com o kernel do sistema.
10. Deadlocks
Deadlock é "Uma ocorrência em que cada processo do conjunto esta
esperando por um evento que somente outro processo pertencente ao
conjunto poderá fazer acontecer." Ressalta que a maior parte das
situações de deadlock envolve a espera por recursos e que, pela
definição, nenhum processo do conjunto poderá evitar que todos os
processos do conjunto fiquem eternamente bloqueados.
10.1 Condições para Ocorrência de Deadlocks
Exclusão Mútua, se nenhum recurso for atribuído exclusivamente a um
único processo, nunca haverá deadlock. O princípio é evitar alocar um
recurso quando ele não for absolutamente necessário e tentar
assegurar que o menor número possível de processos possa de fato
requisitar o recurso. Exemplo: O uso de spool permitindo que somente
um recurso da impressora seja usado por processo.
Condição de posse e espera, basta requerer que todos os processos
solicitem os seus recursos antes de iniciar a execução. No entanto,
um processo nunca tem que esperar por aquilo que precisa. Um dos
problemas é não poder não saber quantos e quais recursos vão precisar
no início da execução e também reter recursos que outros processos
poderiam estar usando.
Nenhuma preempção, seria tomar o recurso a força, contudo é uma
solução pouco promissora, de certa forma inviável.
Espera circular, pode ser eliminado de várias maneiras, uma delas é
ter uma regra que diz que um processo é intitulado somente para um
único recurso em qualquer momento, ou atribuir uma ordem para chamada
para aquele recurso. 1.2.6. Impedimento do Impasse. Anteriormente o
deadlock não foi evitado pela imposição de regras arbitrárias aos
processos, mas pela análise cuidadosa de cada solicitação de recurso
para ver se ele poderia ser concedido seguramente.
Para evitar dinamicamente o deadlock podem-se adotar algumas soluções:
Alocação individual de recursos - à medida que processo
necessita;
Escalonamento cuidadoso – impões um alto custo e conhecimento
prévio dos recursos que serão utilizados;
Algoritmos:
10.2 Algoritmo do Avestruz
Este "Algoritmo do Avestruz" é uma forma humorística do autor
Tanenbaum para comentar a abordagem dos sistemas UNIX, que
simplesmente ignoram o problema, em razão da baixíssima
probabilidade de ocorrer tal situação[3].
10.3 Algoritmo do Banqueiro
O algoritmo do banqueiro (algoritmo de Dijkstra), considera
cada requisição no momento em que ela ocorre, verificando se essa
requisição leva a um estado seguro; Se sim, a requisição é
atendida, se não o atendimento é adiado para outro momento;
Premissas adotadas por um banqueiro para garantir ou não crédito
(recursos) para seus clientes (processos). Nem todos os clientes
(processos) precisam de toda a linha de credito (recursos)
disponível para eles.
Vantagens e desvantagens do algoritmo do banqueiro: - Pouco
utilizado, pois é difícil saber quais recursos serão necessários; -
O número de processos é dinâmico e pode variar constantemente,
tornando o algoritmo custoso; - Na teoria o algoritmo é ótimo.
10.4 Tratamento de Deadlocks
Ignorar o problema: algoritmo da avestruz (Tanenbaum).
Detectar e recuperar.
Evitar (avoidance: solução dinãmica).
Prevenir (prevention: solução estática).
O Unix tenta evitar situações de deadlock, mas não as elimina
completamente.
10.5 Detectar e Recuperar
Detectar deadlock consiste em um algoritmo que basicamente testa se
há alguma ordem de terminação dos processos e solicitação de recursos.
Recuperar a situação:
preempção de recursos;
roll-back de processos;
terminação de processos.
11. Threads no UNIX
Conforme Miguel Pimenta Monteiro[4], somente ha pouco tempo a
especificação POSIX da API do Unix definiu uma interface standard para
a criação e terminação de threads. Esta interface é geralmente
implementada como uma biblioteca (libpthreads.a), podendo ou não ser
diretamente suportada pelo kernel do sistema operacional. Existem
versões do Unix em que os threads são diretamente implementados no
kernel e escalonados independentemente são os (kernel level threads),
enquanto que em outras versões o que o kernel vê são apenas processos,
"existindo" os threads unicamente na biblioteca de suporte, e
escalonados para execução apenas na "fatia de tempo" dos respectivos
processos são os (user level threads).
Quando um programa começa a executar na função main() constitui um
novo processo e pode considerar-se que contém já um thread. Novos
threads são criados através da chamada de um serviço e começam a
executar numa função que faz parte do código do programas.
Cada novo thread é representado por um identificador (thread
identifier ou tid) de tipo pthread_t. É necessário passar o endereço de
uma variável desse tipo, como primeiro parâmetro de pthread_create(),
para receber o respectivo tid. O segundo parâmetro serve para indicar
uma série de atributos e propriedades que o novo thread deverá ter, se
fornecer o valor NULL, o novo thread terá as propriedades por padrão,
adequadas na maior parte dos casos. O terceiro parâmetro informa a
função de início do thread. É uma função que deve existir com o
seguinte protótipo:
void * nome_da_função(void *arg).
A função aceita um apontador genérico como parâmetro, que serve
para passar qualquer informação, e retorna também um apontador
genérico, em vez de um simples código de terminação. Se o valor de
retorno for usado, é necessário que aponte para algo que não deixe de
existir quando o thread termina. Finalmente o quarto parâmetro é o
valor do apontador a ser passado à função de inicio, como seu
parâmetro. Uma vez criado o novo thread ele passa a executar
concorrentemente com o principal e com outros que porventura sejam
criados.
Quando um thread termina pode retornar um apontador para uma área
de memória que contenha qualquer tipo de resultado. Essa área de
memória deve sobreviver o thread, ou seja, não pode ser nenhuma
variável local, porque essas deixam de existir quando o thread termina.
Por padrão, quando um thread termina, o seu valor de retorno (o
apontador genérico) não é destruído, ficando retido em memória até que
algum outro thread execute um pthread_join() sobre ele. É possível
criar threads que não são joinable e que por isso, quando terminam,
libertam todos os seus recursos, incluindo o valor de retorno. No
entanto não é possível esperar por esses threads com pthread_join().
Quando se cria um novo thread é possível especificar uma série de
atributos e propriedades passando-os a pthread_create() através de uma
variável do tipo pthread_attr_t. Essa variável terá de ser previamente
inicializada com o serviço pthread_attr_init() e depois modificada
através da chamada de serviços específicos para cada atributo[5].
12. Kernel
Conforme texto do IME-USP6, o Kernel do Unix (e de virtualmente
qualquer outro sistema operacional) possui um papel de que convém ter
noções, a fim de se poder compreender melhor o funcionamento do
sistema, realizar diagnósticos e procedimentos administrativos como
adição de componentes de hardware. Algum conhecimento do papel do
Kernel é importante também para se ter uma noção mais clara do uso de
arquivos especiais e do diretório /proc.
O Kernel ordinariamente reside no filesystem como um outro arquivo
qualquer. No Linux, ele é em geral o arquivo /vnlinuz ou /boot/vmlinuz,
ou ainda /boot/vmlinuz-2.0.36. Ele é um programa, ainda que um pouco
diferente dos programas de aplicaçãocom o /Bin/Is. O Kernel é carregado
e posto em execução no boot da máquina, e a sua execução somente se
encerra com o shutdown.
De forma simplificada, o seu papel é num primeiro momento
reconhecer o hardware e inicializar os respectivos drivers. Em seguida
ele entra num estado administrativo onde funciona como intermediário
entre as aplicações e o hardware. Por exemplo, quando uma aplicação
necessita alocar mais memória, ela solicita isso ao Kernel. É o Kernel
que distribui o tempo de CPU aos vários processos ativos. É ele que
habitualmente realiza a entrada e saída de dados nas diferentes portas
de comunicação.
É por isso que a adição de hardware novo a uma máquina pode
requerer a substituição ou ao menos a reconfiguração do Kernel. Os
Kernels mais recentes do Linux oferecem vários mecanismos de
configuração que os tornam sobremaneira flexíveis, a ponto de ser rara
a necessidade de substituição do Kernel. Os dois mecanismos
fundamentais de se configurar a operação do Kernel são a passagem de
parâmetros no momento do boot (realizada pelo LILO) e a carga de
módulos, feita manualmente ou por mecanismos automáticos como o
Kerneld.
O diálogo entre as aplicações e o Kernel realiza-se
fundamentalmente através dos system calls, que são serviços que o
Kernel oferece, como por exemplo read(2). Os device special files são
maneiras de se referir ao Kernel os dispositivos físicos ou lógicos com
que se pretende operar, por exemplo a primeira porta serial ou a
segunda unidade de fita, ou o disco principal do sistema. Neles, o
importante não é o nome, mas sim os números de dispositivos ou mais
precisamente o major e o minor device numbers. Device special files são
criados através do comando mknod, ou através de interfaces mais
amigáveis, como o comando MAKEDEV.
6 Fonte: Unix: Conceitos e Comandos Básicos
Os sistemas Unix-Like mais recentes oferecem um outro mecanismo de
comunicação com o Kernel, que é o filesystem /proc. As entradas desse
filesystem são pseudo-arquivos cujo conteúdo reflete o estado atual de
inúmeras estruturas de dados internas do Kernel. Assim, um programa de
aplicação passa a poder comunicar-se com o Kernel através dos
mecanismos ordinários de leitura e escrita de arquivos.
Em muitos casos a comunicação entre as aplicações e o Kernel é
intermediada por bibliotecas, principalmente a libc. Elas oferecem
serviços de mais alto nível que os system calls do Kernel, tornando
mais simples o trabalho de programação.
13. Dispositivos de E/S
Processos num sistema UNIX habitualmente possuem três dispositivos
de I/O "padrões", a "saída padrão", a "entrada padrão" e a "saída de
erros". O Unix permite que esses dispositivos (e outros) sejam
definidos no momento da execução, podendo escolher o console, um "pipe"
(pipe é um canal de comunicação FIFO(fist in, first out - primeiro a
entrar, primeiro a sair) em modo simplex que pode ser usado para
comunicação unidirecional entre processos. Uma implementação é
geralmente integrada no subsistema de entrada e saída I/O de um sistema
operacional), a impressora, um circuito virtual de rede conectando duas
máquinas, uma linha física serial ou outras coisas.
Usuários de MS-DOS talvez já tenham feito coisas como C>DIR>PRN,
para imprimir a saída de um comando. Essa sintaxe foi herdada do UNIX
daí a semelhança $ Is –I>dev/lp1. Naturalmente tanto no MS-DOS quanto
no Unix existem formas mais apropriadas para se imprimir algo, mas no
momento esse exemplo convém. Shell do estilo "Bourne" (sh, ksh,bash)
permitem redirecionamento da saída de erros através da seguinte
sintaxe:
$ rm /bin/Is 2>/tmp/error
O "2" deve-se a que, internamente, a saída de erros corresponde ao
descritor 2 (o 0 é a entrada padrão e o 1 é a saída padrão). A entrada
pode ser redirecionada de forma semelhante:
$ wc . Acesso em 25/10/2010
Malanovicz, A. V. at all. Deadlock (Tanenbaum X Silberschatz) Disponível
em: Acesso em 27/10/2010
Monteiro, MP. Sistemas Operativos. Disponível em:
< http://paginas.fe.up.pt/~pfs/aulas/so2001/ap/cap4.pdf>. Acesso em
27/10/2010
-----------------------
[1] Fonte: Fernando Krahe. Nivelamento UNIX
[2] Fonte da ilustração:
http://www.cenapad.unicamp.br/servicos/treinamentos/tutorial_unix/unix_tutor
-4.html
[3] Fonte da Ilustração:
http://www.inf.ufrgs.br/gppd/disc/inf01151/trabalhos/sem2000-
2/deadlock/deadlock.htm
[4] Fonte: Miguel Pimenta Monteiro - Sistemas Operativos
[5] Fonte da ilustração: https://computing.llnl.gov/tutorials/pthreads/
[6] Fonte: Miguel Pimenta Monteiro - Sistemas Operativos