2. Principais conceitos Neste capítulo serão abordados diversos tópicos centrais em sistemas operacionais, a saber: o que é um processo, como a memória é tratada pelo S.O. e o que são arquivos. Outro elemento importante apresentado é o shell. Vamos investigar também as funconalidades e a estrutura de sistemas operacionais, bem como discutir um elemento relativamente recente no cenário computacional, a máquina virtual. Algumas noções importantes Processos Um processo é um programa em execução, e este é constituído por diversos elementos, entre eles o seu código executável, os dados utilizados por este código, a pilha de execução, o valor dos registradores, manipuladores (handlers) de arquivos abertos por este processo, etc. O sistema operacional mantém internamente uma tabela dos processos em execução. Isto permite que o núcleo do S.O. possa empregar políticas de escalonamento de processos para permitir que diversos processos possam compartilhar da uma mesma CPU, dando ao usuário a ilusão de que estes diversos processos estão todos sendo executados simultanemente. Um processo pode criar mais processos, chamados de processos filhos, e que podem por sua vez também criar seus processos filhos. Tipicamente um S.O. oferece diretivas que permitem ao desenvolvedor programar um processo de tal modo que ele em um determinado momento crie novos processos. Outro aspecto importante é a capacidade dos processos de poderem trocar dados entre sí. Esta capacidade é implementada pelo núcleo através de um conjunto de rotinas específicas para este fim. Por último, quando estão sendo executados, dois ou mais processos podem entrar em uma situação de disputa por necessitarem acessar um mesmo recurso. Este tipo de situação pode levar a impasses que podem comprometer até a estabilidade do sistema como um todo. O sistema operacional deve implementar mecanismos de controle de acesso a recursos que impeçam e resolvam situações de impasse desta natureza. Memória Em sistemas operacionais mais primitivos, como o MS-DOS, somente um processo por vez poderia residir na memória. Já em sistemas operacionais modernos vários programas podem residir na memória principal simultaneamente. Em função disto, mecanismos para proteger o acesso a memória devem ser implementados no S.O., para impedir, por exemplo, que um processo possa modificar o conteúdo de posições de memória pertencentes a outro processo. A alocação da memória também é uma das atribuições do sistema operacional. Quanto de memória cada processo vai ganhar e onde na memória estes processos vão ser efetivamente colocados são decisões exclusivas do S.O. Um mecanismo importante implementado por quase todos os sistemas modernos é o de memória virtual que permite que parte da memória secundária (parte da área disponível no disco rígido, por exemplo) possa ser usada como uma extensão da memória principal. Para os processos este ganho extra de memória é transparente, e todas as questões envolvidas são gerenciadas pelo núcleo do sistema. Arquivos A persistência dos dados é obtida até os dias atuais através do armazenamentos dos dados (e também dos programas) em dispositivos de armazenamento secundário. O sistema operacional costuma oferecer uma interface amigável para que estes dados possam ser organizados, nomeados e acessados nestes dispostivos de memória de massa. O conceito de arquivo é resultado do mapeamento de um conjunto de dados (que pode ser um programa) para esta unidade de armazenamento secundário. Dentro deste contexto, o conjunto de rotinas que oferece estas funcionalidades faz parte de um módulo do kernel denominado sistema de arquivos. Como exemplo, pode-se tomar o NTFS, que é a denominação do sistema de arquivos do sistema operacional Windows NT, 2000, XP, Vista e Windows 7. A organização dos arquivos é alcançada através da implementação no dispositivo de massa, por parte do sistema de arquivos, de uma árvore de diretórios, permitindo o arranjo hierárquico destes dados. Um diretório (ou pasta) é um elemento que identifica um conjunto de arquivos e/ou diretórios. Resumidamente, as rotinas de um sistema de arquivos devem permitir: criar e apagar arquivos criar e apagar diretórios permitir a manipulação de arquivos e diretórios permitir mapear arquivos em armazenamento secundário oferecer rotinas para backup e manutenção do volume As funcionalidades básicas de um sistema operacional As principais funcionalidades oferecidas por um sistema operacional moderno são: gerência do processador gerência da memória gerência de E/S sistema de arquivos gerência de segurança Cada uma destas funcionalidades será abordada em capítulos subsequentes deste livro. Entretanto, um pequeno resumo sobre cada uma delas é apresentado a seguir. Gerência do processador Esta funcionalidade tem como objetivo distribuir o tempo do processador entre os diversos processos em execução. Esta distribuição não é igualitária, pois tipicamente os processos possuem prioridades de execução diferentes, o que faz com que um processo possa receber uma fatia maior de tempo da CPU em detrimento de outros processos que possuam prioridade menor. Outro aspecto importante é que o sistema operacional deve prover a ilusão de que existe um processador independente para cada processo, simplifando a programação das aplicações. A sincronização dos processos, de tal modo que situações de disputa por recursos sejam resolvidas, e a comunicação entre os processos, para permitir a troca de dados entre estes, também são mecanismos que devem ser providos pelo módulo de gerência do processador. Gerência de memória Para cada processo deve ser provida uma região de memória própria, autônoma e independente de outros processos. Isto gera um grande aumento na estabilidade e segurança do sistema como um todo, porque não permite que processos possam se interferir uns aos outros. O gerente de memória tem ainda como atribuição a reserva (ou alocação) de memória aos processos. A medida que um processo executa, sua demanda por memória pode aumentar ou diminuir. Isto faz com que novas porções de memória livre (não utilizada por outros processos) tenham que ser encontradas e associadas ao processo ou então que este tenha deixado de usar regiões de memória que agora podem ser liberadas (desalocadas). Caso a memória RAM disponível não seja suficiente para a execução dos processos, o sistema operacional pode “aumentá-la” de maneira transparente recorrendo ao espaço disponível em memória de massa (como o disponível em um HD, por exemplo). O mecanismo que responde a este tipo de funcionalidade é denominado de memória virtual. Gerência de E/S Cada dispositivo de E/S (periférico) possui características bem particulares no que diz respeito a forma como deve ser acessado. Em função disto, a maneira como é realizado o acesso ao dispositivo X pode ser completamente diferente da forma como deve ser feito o acesso ao dispositivo Y. Mesmo assim, há várias questões comuns em relação ao acesso aos periféricos. Um grupo de dispositivos pode ter em comum o fato de terem de suportar operações de escrita e leitura, que são implementadas de formas diferentes para cada um deles através de seus drivers específicos. Em linhas gerais, o papel do gerente de E/S é implementar a comunicação com cada dispositivo através destes drivers de dispositivo. Sistema de arquivos O sistema de arquivos trabalha em conjunto com o gerente de E/S e tem como meta possibilitar a criação e o manuseio de arquivos e diretórios, definindo uma interface única de acesso. A forma como os arquivos e diretórios são organizados no volume (ou partição) do dispositivo de memória de massa também é uma das principais responsabilidades do sistema de arquivos. Além disso, este módulo pode oferecer diversos outras funcionalidades extras, como o ciframento automático dos dados, mecanismos de auditoria, compactação dos dados e assim por diante. Gerência de segurança O objetivo da gerência de segurança é proteger os recursos do sistema contra acessos maliciosos e indevidos. Para alcançar esta meta, diversos mecanismos devem ser implementados para permitir que o sistema possa saber quais usuários estão conectados, o que cada um deles pode acessar dentro do sistema e de que modo este acesso pode ocorrer e além disso manter um histórico de tudo aquilo que é realizado pelo usuário enquanto este estiver conectado ao sistema. Algumas funcionalidades adicionais Pode-se citar como funcionalidades adicionais aquelas que não são imprescindíveis para que os usuários possam usar o sistema e executar programas. Entretanto, diversas funções foram surgindo ao longo da evolução dos sistermas operacionais e hoje são parte integrante da maioria dos sistemas modernos. Entre estas funções cabe destacar: o suporte a comunicação de dados (rede) interface gráfica do usuário (GUI – Graphical User Interface) recursos extras de segurança, como firewalls e anti-vírus gerência de energia suporte a contéudo multimídia suporte a navegação na web A estrutura de um sistema operacional Um sistema operacional é composto de vários componentes com objetivos e funcionalidades complementares. Alguns dos elementos mais importantes de um sistema operacional são: Núcleo (ou kernel) Este é o principal módulo do sistema operacional, pois é responsável pela gerência dos recursos do hardware usados pelos programas. No núcleo normalmente estão implementadas as rotinas responsáveis pela gerência do processador, pela gerência de memória, controle de acessos, etc. Drivers de dispositivo Os drivers de dispositivo controlam e viabilizam o acesso aos dispositivos físicos. Tipicamente há um driver para cada tipo de dispositivo e este é desenvolvido pelo fabricante do hardware e fornecido para ser conectado ao restante do sistema operacional. Bibliotecas do sistema As bibliotecas do sistema são compostas por conjuntos de rotinas pre-compilados e que podem ser usados por programas desenvolvidos pelo usuário, por exemplo. \possuem tipicamente a extensão .lib ou .dll (no windows). Shell ou interpretador de comandos É através deste software que o usuário pode emititr comandos para o sistema operacional. O interpretador de comandos é um programa que analisa aquilo que foi digitado pelo usuário e em funçaõ do resultado desta analise executa alguma ação específica. Nos S.O. modernos, o ambiente de janelas disponibilizado através da interface gráfica do usuário permite que o usuário empregue a abordagem de apontar e clicar (com o mouse) para invocar serviços do sistema operacional, ao invés de ter que digitá-los. Programas utilitários Estes programas facilitam a execução de atividades rotineiras, além de implementarem diversas funcionalidades de administração do sistema, como cadastro de usuários, formatação de memória de massa, etc. Figura 6: a estrutura de um sistema operacional Principais arquiteturas de sistemas operacionais Ao longo da evolução dos sistemas operacionais surgiram três diferentes tipos de arquiteturas que se tornam mais populares e que merecem destaque, a saber: a arquitetura monolítica, a arquitetura em camadas e a arquitetura de micro-núcleo. Cada uma destas arquiteturas é discutida a seguir. Arquitetura monolítica Neste tipo de arquitetura, os componentes do núcleo podem todos se intercomunicar se barreiras de nenhum tipo. Todos os elementos do kernel tem acesso irrestrito a todos os recursos oferecidos pelo hardware. O ponto positivo desta abordagem aparentemente simples é o baixo custo de execução, propiciando desta forma um bom desempenho. Esta comunicação direta entre componentes torna os sistemas monolíticos bastante enxutos. Entretanto estes sistemas apresentam duas desvantagens, que são a falta de estabilidade e a complexidade no seu desenvolvimento. A falta de estabilidade é devida ao fato de que se todos componentes podem se comunicar livremente, e são na prática fortemente acoplados (interdependentes), quando um componente falhar vários outros componentes serão afetados, o que irá comprometer a execução do sistema como um todo. Como a tendência neste tipo de arquitetura é os componentes do núcleo exibiram um alto grau de interdependência, quando um novo componente é projetado ou um antigo é modificado, isto pode afetar todos outros componentes, o que provavelmente irá exigir altereções nestes outros componentes. Os primeiros sistemas operacionais eram completamente monolíticos. Atualmente esta tendência está superada, só permanecendo significativa nos sistemas operacionais de tempo real e voltados para sistemas embarcados. Arquitetura em camadas Para contornar os problemas apresentados pela arquitetura monolítica, um sistema operacional pode fazer uso de uma estrutura em camadas. O nível mais baixo é responsável pela interface com o hardware (é a camada inferior). As camadas intermediárias fornecem os serviços de gerência, enquanto que na camada superior estão definidas as interfaces para as aplicações. Estas interfaces são provem o suporte às chamadas de sistema (system calls). Uma chamada de sistema é tipicamente uma solicitação de recurso por parte de um processo. Pode-se apontar algumas desvantagens nesta abordagem. Por exemplo, uma requisição de um processo demora mais tempo para chegar até o dispositivo periférico ou ao recurso a ser acessado, deteriorando o desempenho do sistema. Além disto a definição de quais serviços irão pertencer a quais camadas também pode ser uma questão bastante complexa. É muito comum a implementação de uma camada inferior de abstração do hardware para interagir com os dispositivos de E/S. Esta camada é mais conhecida como HAL (Hardware Abstraction Layer). Arquitetura de micro-núcleo Outra forma de estruturação é manter no núcleo somente o código de baixo nível necessário para a interação com os dispositivos e manter o código de alto nível, onde estão implementados os gerentes de recurso, fora do kernel, como se fossem processos que se comunicam utilizando as primitivas fornecidas pelo kernel. A decorrência natural desta abordagem foi o surgimento dos micro-kernels, que implementa somente a noção de atividade, de espaços de memória protegidos e de comunicação entre atividades. Os sistemas Mach e Chorus são exemplos dessa abordagem. Máquina Virtual É utilizada uma cópia fiel do hardware de tal modo que se possa ter um ambiente completo para execução do programa. É possível executar mais de uma máquina virtual em um mesmo hardware ou então criar diferentes máquinas para hardware distintos mantendo acima dela uma única interface. Um exemplo de máquina virtual muito empregado hoje em dia é a JVM (Java Virtual Machine), desenvolvida pela empresa Sun MicroSystems na década de 90. 3. Gerência de Processos Serão explorados neste capítulo tópicos relativos à gerência de processos, abarcando as políticas de escalonamento de processos (os principais algoritmos e estudos de caso) bem como o uso de semáforos, monitores, etc. São também abordados alguns problemas clássicos como o do jantar de filósofos e do produtor/consumidor. A evolução da gerência de processos O processador deve executar todos os processos disparados pelos usuários. Estes processos tem desempenho, duração de execução e prioridades diferentes entre si. É responsabilidade do sistema operacional organizar os processos de tal forma a executá-los da melhor forma possível, otimizando o uso do processador e da memória. Iremos explorar a organização básica do sistema de gerência de processos e um pouco de sua evolução. Sistemas mono-tarefa Os primeiros ambientes computacionais executavam apenas um processo de cada vez. Um exemplo de sistema mono-tarefa é o sistema operacional MS-DOS, que equipou a primeira geração de computadores pessoais da IBM. Nestes sistemas, cada processo era lido do disco para a memória principal e executado até sua conclusão. Os dados de entrada eram lidos na memória junto com o processo e os resultados da computação eram gravados de volta no disco durante e após a execução do processo. Sistemas multi-tarefa Sistemas que suportam a execução de mais de um processo concorrentemente são denominados multi-tarefa. Esta característica implica na implementação de diversos mecanismos como um escalonador de processos e gerência de memória. Num sistema multi-tarefa deve ser possível permitir ao processador suspender a execução de um processo para passar a executar um outro processso. Além disso deve ser possível para o kernel reativar um processo suspenso, a partir do ponto onde foi interrompido. Retirar do processo o acesso a um é denominado de preempção. Isto bloqueia ou suspende um processo. Sistemas operacionais que implementam este mecanismo são chamados de sistemas preemptivos. Estados de um processo Os estados e transições do ciclo de vida de um processo possuem os seguintes significado: Novo : o processo está sendo criado; as suas áreas (text, data, stack e heap) em memória estão sendo montadas.; Pronto : o processo está na memória, pronto para executar (ou para prosseguir sua execução), esperando a disponibilidade do processador. Todas processos prontos são organizados em uma fila gerenciada por algum algoritmo de escalonamento; Executando : a CPU está executando o processo;. Bloqueado ou Suspenso : o processo não pode executar porque depende de dados ainda não disponíveis (do disco ou da rede, por exemplo), ou então espera por sincronização (o fim de um processo ou a liberação de algum recurso compartilhado); Terminado : A execução do processo foi encerrada e este será removido da memória. terminar executando novo preemptar bloquear executar desbloquear criar bloqueado pronto suspender continuar Pronto e suspenso suspender liberar continuar Bloqueado e suspenso Figura 7: estados de um processo Observações importantes sobre processos Os sistemas operacionais modernos associam uma tarefa a cada processo, o que corresponde à execução de um programa seqüencial, ou seja, um fluxo único de instruções dentro do processo. Desta forma o processo deve ser encarado como um repositório de recursos empregados por uma ou mais tarefas para sua execução. Os processos são isolados entre si pelos mecanismos de proteção providos pelo hardware e pelo gerente de processos. Processos podem ser criados e destruídos e estas operações são oferecidas aos aplicativos através de system calls. A criação de processos no S.O. UNIX gera um esquema hierarquico entre os processos. À medida que processos são criados, é gerada uma árvore de processos. Cada fluxo de execução do sistema, no interior do kernel ou associado a um processo é denominado thread. Threads executando dentro de um processo são chamados de threads de usuário (user-level threads) enquanto que threads gerenciadas pelo kernel são denominadas de threads de núcleo (kernel-level threads). Escalonamento de processos Este componente do kernel decide a ordem de execução dos processos prontos. O algoritmo utilizado no escalonador caracteriza o comportamento do sistema operacional. Os principais fatores a serem levados em conta na escolha ou projeto de um escalonador de processos são: Percentual de utilização do processador Throughput, que é a quantidade de processos completados por unidade de tempo Turnaround, tempo total de execução de um processo, desde sua criação até seu término Tempo de espera de um processo na fila de processos prontos para execução Tempo de resposta, que é o tempo que o processo leva para produzir resposta a uma determinada requisição O escalonador de um sistema operacional pode ser preemptivo ou não-preemptivo. Num escalonador preemptivo um processo pode perder o processador caso termine seu quantum (sua fatia) de tempo, ou então execute alguma system call ou se ocorrer uma interrupção que redispare um processo de maior prioridade. Num escalonador não-preemptivo, o processo execução permanece com a CPU enquanto precisar, somente a abandonando caso termine de executar ou então requisite uma operação de entrada/saída ou libere explicitamente o processador, voltando para a fila de processos prontos. Os principais algoritmos de escalonamento de processos são: FCFS (First-Come, First Served) : atende os processos em seqüência, à medida em que estes se tornam prontos. SJF (Shortest Job First) : gera os menores tempos médios de execução e de espera; consiste em atribuir a CPU à menor (mais curta) tarefa da fila de tarefas prontas. Mas fica o problema de como estimar a duração de um processo. Por causa disto, este algoritmo é pouco utilizado. No entanto, ao associado à preempção por tempo, esse algoritmo pode ser de grande ajuda. Escalonamento com prioridade : no escalonamento por prioridades, cada processo é associado a uma prioridade, geralmente um número inteiro. Os valores de prioridade são então usados na escolha da próxima tarefa a receber a CPU, a cada troca de contexto. Mecanismos de comunicação As formas mais usuais de comunicação inter- processos são: compartilhamento de memória: dois ou mais processos compartilhando a mesma variável, por exemplo; mail boxes (caixas postais): processos depositam e retiram mensagens de um determinado tipo de dados; processo é suspenso se tenta depositar uma mensagem num mailbox cheio ou tenta retirar mensagem de uma mail box vazia; passagem de mensagens chamada remota de procedures Exemplos de diferentes formas de comunicação entre processos: No UNIX (pipeline): C1 | C2 ( a saída de C1 é a entrada de C2) Ex.: ls -la | grep joao C1 e C2 são executados concorrentemente se há + de 1 CPU : multiprocessamento (C1 na CPU1 e C2 na CPU2) com 1 CPU: multiprogramação No MS-DOS : C1 | C2 ( a saída de C1 é a entrada de C2, mas 1o é executado C1, então a saída vai para um arquivo temporário, só então inicia a execução de C2, lendo este arquivo temporário). Equivale a: C1 > f C2 < f comandos seqüenciais; não há concorrência 4. Gerência de Entrada e Saída Neste capítulo são apresentados os recursos que um sistema operacional deve oferecer para gerenciar a entrada e saída de dados do computador. Neste aspecto, um tópico de interesse abordado é o da políticas de escalonamento de acesso, e neste contexto é examinado o caso particular do escalonamento de acesso em discos rígidos. Os dispositivos de entrada permitem ao computador acessar informações do mundo externo que são por sua vez codificadas para poderem ser processadas pela CPU. Uma das responsabilidades do sistema operacional é gerenciar o acesso aos periféricos por parte de processos que estão sendo executados no computador. Isto é obtido através de um conjunto de rotinas e métodos de acesso implementados e oferecidos pelo sistema operacional. Um periférico é um elemento de hardware conectado através de um barramento de E/S ao computador. O barramento permite que dados possam trafegar entre o computador e o periférico. Existe uma gama bastante variada de periféricos, desde aqueles especializados em capturar informações externas, como o movimento do usuário, por exemplo, até aqueles que representam a informação para o usuário num formato específico, como um monitor de vídeo, passando por aqueles periféricos que simplesmente armazenam as informações de forma persistente, como discos rígidos. Foram desenvolvidas até hoje algumas formas diferentes para que periféricos se comuniquem com o computador. Diferentes periféricos tipicamente irão empregar diferentes métodos de comunicação. Dois elementos merecem destaque quando se aborda um esquema de comunicação computador-periférico. São a interface de comunicação e o controlador do dispositivo. A interface é a parte do periférico que se conecta diretamente ao barramento de E/S. Um componente associado a interface é o controlador, que geralmente é um processador especializado no controle do dispositivo específico. A comunicação com o controlador se dá através de um conjunto de registradores (espaços de memória) empregados para armazenar informações enviadas de ou para a CPU. Como exemplo, a CPU coloca nos registradores do controlador os comandos que este deve realizar junto ao dispositivo. Existem dois tipos principais de interfaces: a serial e a paralela. A interface serial trabalha com somente uma linha de transmissão da informação, e um dado é transmitido então sempre um bit a cada vez. A interface paralela possui várias linhas de transmissão, possibilitando que vários bits sejam transmitidos simultaneamente. O acesso aos registradores do controlador pode-se dar por meio de duas formas: ou a E/S é mapeada em espaço de E/S ou então ela é mapeada em memória. Com mapeamento em espaço de E/S, a CPU emprega instruções especiais para manipular os registradores dos periféricos. Neste caso, existem 2 espaços de endereçamento: o espaço que corresponde a memória principal e o espaço de E/S, que corresponde aos registradores dos dispositivos. No mapeamento em memória só há um espaço de endereçamento, sendo que alguns endereços específicos irão corresponder a registradores dos periféricos. Existem basicamente três maneiras da comunicação ser implementada entre a CPU e os dispositivos, não importando o tipo de mapeamento empregado. O primeiro tipo é a E/S programada, onde toda troca de dados é de responsabilidade do processo, devendo portanto ser implementada pelo programador, de modo que o programa em execução transmita comandos para o dispositivo e em seguida aguarde o término da operação por parte deste. A desvantagem desta abordagem é que o processo pode ter que ficar esperando muito tempo pelo dispositivo, resultando num desperdício. Uma alternativa seria de tempos em tempos verificar se o dispositivo concluiu suas operações, abordagem denominada de polling. O segundo tipo é a comunicação através de interrupções, que são geradas pelo dispositivo para avisar a CPU de que as operações requisitadas foram concluídas. Neste modo, a CPU não precisa realizar polling, ficando liberada para outras tarefas. Quando implementadas em harware, um controlador de interrupção (como o PICProgrammable Interrupt Controller) pode estabelecer a via de comunicação entre o dispositivo que gera a interrupçaõ e a CPU, permitindo o roteamento (isto é, a multiplexação) das interrupções causadas por diversos dispositivos para uma única CPU, por exemplo. Se as interrupções forem implementadas como parte do gerenciador de memória, elas podem ser então mapeadas para dentro do espaço de endereçamento de memória do sistema. Uma interrupção é denominada mascarável quando se trata de uma interrupção de hardware que pode ser ignorada pelo processador (se isso for coveniente para um processo em específico). As interrupt requests (IRQs) são interrupções deste tipo. Interrupções não-mascaráveis (NMI) nunca podem ser ignoradas pelo processador, sendo empregadas na implementação de temporizadores, por exemplo. Já uma interrupção de software é gerada a partir de uma instrução pertencente a um processo. As chamadas do sistema (system calls) são implementadas através de interrupções de software. Uma chamada do sistema desvia a execução para o início da rotina que trata aquela interrupção em particular. O último tipo é o Acesso Direto à Memória, conhecido pela sigla DMA, quando a interface do dispositivo consegue transferir os dados diretamente para a memória, não havendo envolvimento direto da CPU. Um dos principais módulos do sistema operacional é o subsistema de E/S, responsável pelo gerenciamento da comunicação com os diversos periféricos conectados ao computador. Este módulo é estruturado em diversas camadas. A camada inferior realiza a interação com o hardware dos dispositivos através dos drivers de dispositivo, empregando mecanismos como interrupções e DMA, por exemplo. Esta organização em camadas permite que o restante do sistema operacional não se preocupe com os detalhes da interação com os diferentes tipos de dispositivos existentes. Esta camada inferior fornece às camadas superiores uma série de serviços que padronizam o acesso aos periféricos. Esta padronização levou a classificação dos dispositivos em função da forma como os dados são transmitidos. Temos dois principais grupos de dispositivos: dispositivos orientados a caracter dispositivos orientados a bloco Os dispositivos orientados a caracter realizam a transmissão caracter a caracter, transmitindo um byte por vez, enquanto que os dispositivos orientados a bloco transmitem um conjunto de bytes de tamanho fixo. Figura 8: tipos de dispositivo A camada imediatamente acima usa os serviços disponibilizados pelos drivers e pode ser considerada “independente de dispositivo”. Esta camada implementa funções genéricas (pois servem para qualquer periférico) e serviços gerais de E/S, importantes para o funcionamento do sistema. Alguns destes serviços são: gerência de buffer: quando é necessário controlar um buffer onde dados serão armazenados temporariamente pois o dispositivo não consegue lidar toda a informação se esta for enviada de uma só vez. cache de dados: gerência do armazenamento na memória daqueles dados mais freqüentemente acessados. alocação de dispositivo: alguns periféricos (como a impressora, por exemplo) admitem somente um usuário por vez. Esse controle é feito através da técnica de spooling, que consiste em seqüencializar os pedidos de acesso e atendê-los um por um. Os pedidos de acesso são registrados em uma fila especial (chamada fila de spool), que é acessada por um tipo específico de processo chamado de daemon e que atende as requisições de E/S. controle de acesso: Nem todos os usuários podem acessar os dispositivos da mesma forma e é reposabilidade do SO garantir essa proteção. escalonamento de E/S: para dispositivos compartilhados por vários processos (como HDs), visando melhorar seu desempenho . tratamento de erros: O software de E/S deve ser capaz de tratar erros, informando à camada superior o sucesso ou o fracasso das operações de E/S. Para o usuário, o acesso a operações de entrada e saída é disponibilizado através das rotinas existentes nas linguagens de programação empregadas na implementação dos programas. O programador utiliza comandos de E/S de alto nível (por exemplo, a função printf ( ) ou scanf ( ) da linguagem C para E/S formatada de dados), que são traduzidos para um código que contém chamadas para rotinas de uma biblioteca de E/S padrão implementadas pelos fabricantes do compilador da linguagem. Estudo de caso: o disco rígido Vamos ver agora as particularidades dos mecanismos de E/S de um periférico muito importante num computador pessoal: o HD ou disco rígido. Os discos rígidos são meios de armazenamento de memória em massa, sendo constituídos por uma parte mecânica e uma parte eletrônica. Os dados são armazenados em um ou mais discos magnéticos rígidos. Os principais componentes de um HD são o disco mecânico, a cache e a interface de comunicação. Figura 9: a estrutura básica de um disco rígido Um HD pode ser composto por uma série de pratos sobrepostos. Cada prato possui duas faces graváveis. Em cada face os dados são gravados numa série de trilhas concêntricas. Cada trilha é dividida em blocos denominados setores, capazes de armazenar 512 bytes. A cache é um buffer de memória que existe dentro do HD e sua função é armazenar temporariamente os dados provenientes do computador. A interface é a parte do HD responsável pela conexão com o barramento do sistema, e associada ao controlador. Do ponto de vista do sistema operacional, um aspecto relevante é o chamado escalonamento do disco, que é a rotina do núcleo do sistema operacional que busca determinar quais pedidos de leitura e escrita serão atendidos e em que ordem. Vários processos podem estar realizando ao mesmo tempo pedidos de operações de E/S e sendo consequentemente bloqueados até que a operação solicitada seja realizada. O problema do escalonador do disco é ordenar e atender estes pedidos de E/S, buscando minimizar o tempo em que processos permanecem bloqueados. Numa operação de E/S em disco, um fator importante a ser levado em consideração é o tempo de seek, que é o tempo despendido pelo dispositivo para posicionar o cabeçote de leitura e gravação no ponto inicial no prato onde deverá ocorrer a operação de gravação ou leitura. Existem diversos algoritmos de escalonamento. A maioria tem como principal objetivo minimizar o tempo de seek da operação. Dentre estes, alguns merecem destaque: FCFS (First Come First Served): É o algoritmo de escalonamento mais simples. As solicitações de acesso são atendidas na ordem em que os pedidos são realizados. SSTF (Shortest Seek Time First): O próximo pedido de E/S a ser atendido é aquele que se refere a trilha mais próxima da trilha atual, isto é, aquele que envolve a menor movimentação do cabeçote de leitura/gravação. SCAN: Esse algoritmo é uma variação do SSTF. Ele se diferencia por manter um sentido preferencial para o movimento do cabeçote, como por exemplo, da trilha mais externa para a mais interna. Em relação a discos rígidos, com o passar do tempo os controladores foram evoluíndo e diversas padronizações foram surgindo. Dentre as mais importantes cabe destacar: MFM, RLL: Padrões antigos, não mais em uso. IDE: Interface simples, faz uso do barramento ISA, possuia baixa taxa de transferência, tipicamente voltada para discos menores que 528MB. EIDE: Melhorias do IDE, faz uso do barramento PCI, suportando discos de até 8.5 GB, com controlador na placa mãe; gerenciava até quatro discos. SCSI - Controlador para sistemas com altas velocidades e grande capacidade. Suporta grande quantidade de discos de grande capacidade. 5. Gerência de memória Neste capítulo é abordada a gerência de memória, explorando-se temas como memória virtual e paginação. Uma boa gerência de memória é fundamental para o bom desempenho do sistema computacional, daí a importância do tema. A memória principal (ou RAM) é um dos elementos mais importantes em um ambiente computacional. Nesta memória irão residir os processos (isto é, os aplicativos em execução), bem como rotinas do próprio kernel do sistema operacional. Se o espaço de memória RAM não for suficiente para abarcar todos processos que precisam ser executados, então é importante lançar mão de mecanismos como a memória virtual, que permite que a memória principal seja expandida de forma transparente para os processos, empregrando espaço livre nos dispositivos de memória de massa, usualmente nos discos rígidos. Um processo é gerenciado pelo kernel como um elemento independente, residindo em uma área de memória própria na memória principal. Essa área de memória contém as informações necessárias à execução desse processo, particionada da seguinte forma: Área HEAP : é empregada para armazenar dados através de alocação dinâmica. Esta área possui tamanho variável, podendo aumentar e diminuir conforme forem ocorrendo as alocações e liberações de memória realizadas pelo processo. Com o tempo esta área pode ficar fragmentada. Área DATA (ou de DADOS) : contém os dados estáticos empregados pelo processo (variáveis globais e locais, arrays, etc); tem tamanho fixo. Área TEXT : é onde o código (o conjunto de instruções a ser executado pelo processo) está armazenado; esta área possui tamanho fixo, calculado durante a compilação. Área STACK (ou PILHA): é empregada para gerenciar a pilha de execução do processo, que é a estrutura através da qual é feito o gerenciamento do fluxo de execução nas chamadas de rotinas e de seus argumentos (parâmetros); normalmente esta área começa em endereços altos de memória e cresce em direção aos endereços menores. No caso de aplicativos que possuam múltiplas threads (linhas de execução ou processos leves), esta área irá possuir somente a pilha do processo principal. Normalmente a pilha de execução de uma thread é mantida e alocada no heap. Mecanismos de alocação de memória O tipo mais simples de alocação de memória consiste em dividir a memória em partições fixas, de tamanhos iguais. Em cada partição pode ser carregado um processo. Essa abordagem é muito simples, mas possui muitas desvantagens: os processos podem ter tamanhos diferentes dos tamanhos das partições, o que gera em regiões sem uso no final de cada partição; o número máximo de processos na memória é limitado pelo número de partições e processos maiores que o tamanho da maior partição não vão poder ser carregados na memória, mesmo se todas as partições estiverem livres. Uma opção bem mais flexível é se cada partição puder ter seu tamanho ajustado para cada processo, ou seja, a memória é dividida em partições de tamanho variável. Na alocação contígua, os espaços de memória alocados devem formar áreas contíguas, isto é, sem descontinuidade. Apesar de simples, é uma estratégia pouco flexível e sujeita à fragmentação. Já na alocação segmentada, o espaço de memória de um processo é dividido em regiões, ou segmentos, que são alocados separadamente na memória. Além das quatro áreas básicas do processo (text, data, stack e heap), também podem haver segmentos para elementos como bibliotecas, pilhas de threads, buffers, etc. Ao estruturar a memória em segmentos, o espaço de memória de cada processo não é mais encarado como uma seqüência linear de endereços lógicos, mas sim como uma coleção de segmentos de tamanhos diferentes. Durante a execução dos processos, áreas de memória desalocadas por processos podem gerar áreas livres de memória entre os processos, o que é chamado de fragmentação externa. Esse problema afeta somente os mecanismos de alocação que trabalham com blocos de tamanho variável, como a alocação contígua e a alocação segmentada. A alocação paginada manipula blocos de mesmo tamanho, sendo por isso imune à este problema. A fragmentação é danosa porque limita a capacidade de alocação de memória no sistema. Para minimizar a ocorrência de fragmentação cada requisição de alocação deve ser primeiramente analisada para que o gerente de memória encontre a melhor área de memória livre para cada caso. Os principais critérios a serem levados em consideração para esta escolha são: best-fit : escolha da menor área possível que atenda a reuisição de alocação. Assim, as áreas livres são usadas de forma otimizada, mas eventuais resíduos podem ser pequenos demais para serem usados em seguida, nas próximas requisições, o que é ruim. worst-fit : consiste em escolher a maior área livre, de forma que áreas restantes sejam grandes e possam ser usadas em outras alocações. first-fit : escolha da primeira área livre que satisfaça o pedido de alocação; é rápida, ainda mais se a lista de áreas livres for muito longa. next-fit : é uma variação da first-fit, e consiste em percorrer a lista de áreas livres a partir da última área alocada ou liberada, para que o uso das áreas livres seja distribuído da maneira mais homogênea possível. Memória virtual O uso de um armazenamento externo como extensão da memória RAM é chamado de memória virtual; este mecanismo deve ser implementado de forma eficiente e transparente para processos. Quando processos inteiros são transferidos da memória para o disco rígido este procedimento é chamado de troca ou swapping. Entretanto, geralmente as transferências são feitas por páginas ou grupos de páginas, através de um mecanismo chamado de paginação. As páginas possuem um tamanho fixo, o que simplifica os algoritmos de escolha de páginas a serem removidas, bem como os mecanismos de transferência para o dispositivo. A idéia é transferir para o disco as páginas de memória pouco usadas (isto é, acessadas). Os principais algoritmos para troca de páginas são: FIFO: Um critério é o tempo em que os dados estão na memória. Páginas mais antigas podem ser removidas para dar lugar a novas. Esse algoritmo é bastamte simples: as páginas são dispostas numa fila de números de páginas com política FIFO (First In, First Out). Os números das páginas recém carregadas na memória são registrados no final da lista (ou fila). Ótimo: a melhor página a remover da memória é aquela que irá ficar mais tempo sem ser usada pelos processos. Mas como o comportamento futuro dos processos não pode ser previsto, este algoritmo não é implementável. LRU (Least Recently Used): é uma aproximação implementável do algoritmo ótimo. São escolhidas as páginas que estão na memória há mais tempo sem serem acessadas. As páginas antigas e menos usadas são as escolhas preferenciais NRU (Not Recently Used): leva em consideração o momento da última modificação da página; opta por aquleas que não foram recentemente modificadas (ou usadas). Em linhas gerais pode-se pensar nos principais critérios a serem usados para a escolha das páginas: a idade da página (que depende de há quanto tempo a página está na memória), a freqüência de acessos à página, a data ou hora do último acesso e a prioridade do processo ao qual a página pertence. 6. Sistemas de Arquivos Neste capítulo são apresentados os conceitos relativos a sistemas de arquivos, sua estrutura e funcionalidades, apresentando também as principais características de alguns dos sistemas de arquivos mais significativos. Principais conceitos O principal conceito quando se trata de armazenamento e manipulação de dados em memória de massa é o de arquivo. Um arquivo é basicamente um conjunto de dados armazenados em um dispositivo de armazenamento secundário e que é identificado com um nome único. Para facilitar a organização de uma grande quantidade de arquivos, outro conceito importante é o de diretório (ou pasta), que permite o arranjo de modo hierárquico de todos arquivos armazenados. Cada sistema operacional possui seu próprio sistema de arquivos. A seguir estão listados alguns dos principais sistemas de arquivos em uso hoje em dia: FAT : nos sistemas MS-DOS e Windows 3.1 até Windows 98 SE e Millenium; empregada também em pendrives, por exemplo. NTFS: nos sistemas Windows 2000 até Windows 7. Ext2 e Ext3: Linux. Cada arquivo é caracterizado por um conjunto de atributos, que podem variar de acordo com o sistema de arquivos utilizado. Os atributos mais comuns são o nome do arquivo, seu tipo (que pode indicar a natureza do dado armazenado no arquivo), data e hora de criação e última modificação ou acesso, seu tamanho, o indicação de quem criou o arquivo, etc. O uso dos arquivos é realizado por meio de um conjunto de operações implementadas através de system calls (as chamadas de sistema) e funções de bibliotecas. As principais operações que podem ser realizadas com arquivos são: Criação do arquivo Leitura dos dados do arquivo Gravação de dados no arquivo Alteração do valor de atributos do arquivo Deleção do arquivo O programa-exemplo (codificado em linguagem C) a seguir ilustra o processo de leitura de um arquivo byte a byte. Cada byte lido é mostrado na tela do computador. O nome do arquivo a ser lido deve ser passado como parâmetro na linha de comando do terminal, no momento em que este programa for ser executado. #include <stdio.h> #include <stdlib.h> void main(int argc, char *argv[]) { FILE *fp; //fp É UMA VARIÁVEL QUE REFERENCIA O HANDLER char ch; if(argc!=2) { printf("numero insuficiente de argumentos para o main\n"); exit(1); } if((fp=fopen(argv[1],"r"))==NULL) // ABRE O ARQUIVO PARA LEITURA { printf("O arquivo nao pode ser aberto\n"); exit(1); } ch=fgetc(fp); // LÊ UM CARACTER (BYTE) DO ARQUIVO while(ch!=EOF) { putchar(ch); // ESCREVE CARACTER NA TELA ch=fgetc(fp); // LÊ PRÓXIMO CARACTER (BYTE) DO ARQUIVO } fclose(fp); // FECHA ARQUIVO } Para poder ler ou escrever dados em um arquivo, um processo precisa primeiro abrir o arquivo. A abertura de um arquivo consiste em preparar as estruturas de memória necessárias para acessar os dados do arquivo. Assim, para abrir um arquivo, o kernel deve localizar o arquivo no dispositivo de memória de massa (através do seu nome), checar se o processo pode (tem permissão para) acessar aquele arquivo, criar as estruturas de dados para representar o arquivo aberto, colocar uma referência (handler) para esta estrutura na lista de arquivos abertos mantida pelo sistema e finalmente retornar para o processo a referência a esta estrutura. As duas formas mais comuns de se acessar os dados que estão armazenados em um arquivo são o acesso sequencial dos dados e o acesso direto. No acesso seqüencial, os dados são sempre lidos ou gravados sequencialmente, do início para o fim do arquivo. No modo de acesso direto, pode-se indicar a posição no arquivo onde a leitura ou gravação deve acontecer. Um disco rígido (ou HD) pode ser encarado como uma grande sequencia de blocos de bytes, também chamados de setores, e que possuem um tamanho fixo de 512 bytes. Usualmente o sistema de arquivos acessa as informações no HD lendo ou escrevendo conjuntos de setores a cada vez. Estes grupos de setores são denominados de blocos de alocação (ou clusters). O tamanho do bloco de alocação é determinado via de regra pelo próprio sistema de arquivos, e usualmente fica entre um a oito setores (isto é, um cluster possui entre 512 a 4 KB de tamanho físico). Versões antigas de sistemas de arquivos como a FAT poderiam empregar clusters de até 16KB de tamanho, o que ocasionava um grande disperdício de espaço em disco. Isto se deve ao fato de que um arquivo gravado em disco vai ser representado por um conjunto de clusters (todos eles com o mesmo tamanho). Se por exemplo o tamanho do arquivo for de 13 KB e o tamanho do cluster empregado por este sistema de arquivos for de 2KB, então serão usados para este arquivo sete clusters, sendo que o final do arquivo residirá em um cluster que não estará completamente cheio, e sim estará sendo usado pela metade. Isto se deve também ao fato de que um cluster só pode estar associado a um único arquivo, não podendo ser compartilhado por dois ou mais arquivos. Exemplo de organização de um sistema de arquivos: o caso da FAT 12 Quando se trata da forma como um sistema operacional organiza os dados em um volume (ou partição) um exemplo interessante de ser estudado em maior profundidade é o da FAT 12, dada sua simplicidade. Esta versão do sistema de arquivos FAT é a que era empregada em disquetes. A estrutura geral de u volume formatado como FAT 12 é o que aparece na figura abaixo: Estrutura de um disquete BOOT setor 0 (FAT 12) 1 setor = 1 cluster = 512 bytes FAT Cópia da FAT setores 1 a 9 setores 10 a 18 Diretório Raíz Dados setores 19 a 32 setor 33 em diante No primeiro setor estão armazenadas diversas informações importante sobre o volume de dados armazenado no disco. Por exemplo: 1o,2o,3o bytes : instrução (assembly) de salto para rotina de boot do 4o ao 11o byte : nome e versão do DOS (ex.: MSWIN4.0) 12o,13o : número de bytes por setor (512 ; no disco fica 00 02 ) 14o : número de setores por cluster 15o,16o : número de setores reservados 17o : número de FATs (2) 18o,19o : número máximo de entradas no diretório (raíz) (112 ou 224) 20o,21o : número total de setores no disco (0B 40 em um disco de 3.5) 22o : byte descritor do meio -> F0 (disco 3.5) , F8 (HD) , FC (5.25) 23o, 24o : número de setores usados por uma FAT 25o,26o : número de setores por trilha 27o , 28o : número de cabeças 29o , 30o : número de setores escondidos 40o a 43o : número de série do disco a partir do 44o : nome do volume a partir do 55o : FAT12 ou FAT16 últimos 2 bytes deste setor : 55 AA Estrutura de um arquivo de diretório FAT12 Um diretório no sistema de arquivos FAT é uma seqüência de entradas de 32 bytes cada, onde cada entrada corresponde a um arquivo (FAT12 e FAT16 convencionais). Na FAT empregada pelo Windows 95 (que é chamada de VFAT), para cada arquivo podem ser usadas mais de uma entrada de diretório, onde uma entrada segue o formato tradicional e as demais armazenam o nome do arquivo usando codificação UNICODE (2 bytes por caracter). Então, o formato de uma entrada de diretório FAT 12 fica (tamanho em bytes indicado abaixo de cada campo): nome ext 8 atribut 3 reservado 1 hora data 2 2 10 1o clust. 2 tam 4 Quanto ao byte de atributo, cada bit possui um significado: - Ex: 0 volume subdiret arquivo sistema 0 1 0 1 0 não usados Quanto ao cálculo do campo data, este fica: hidden read-only 0 0 = 0010 1000 =28 dia + 32 * mês + 512 * (ano – 1980) (dec) = 22 A1 (hexa) Ex: 01/05/97 B 1 + 32*5 + 512*(1997-1980) = 8865 Obs.: o campo dia usa 5 bits, o mês os 4 próximos bits e o ano os bits restantes Quanto ao cálculo do campo hora, este fica: segundos/2 + 32 * minutos + 1024 * horas Obs.: o campo segundos usa 5 bits, minutos usa 6 bits e horas os bits restantes Quanto a tabela da FAT 12 Cada entrada da FAT12 emprega 12 bits ou 1 byte e meio. Logo, em 3 bytes consecutivos temos 2 entradas. Exemplo (como mostrado pelo DiskEdit): F0 FF 02 54 00 32 . . . Se temos setores de 512 bytes, então cabem 341,33 entradas de FAT por setor. Observação: entrada com valor FFF = fim de encadeamento e FF7 = setor ruim Quanto a tabela da FAT 16 Cada entrada da FAT 16 emprega 16 bits ou 2 bytes . Se temos setores de 512 bytes, então em 1 setor cabem 256 entradas. Observação: entrada com valor FFFF = fim de encadeamento e FFF7 = setor ruim As duas primeiras entradas da FAT são reservadas (não usadas); isto vale para FAT12 e FAT16. A expressão a seguir fornece um valor que corresponde a uma projeção (uma aproximação) de quanto espaço em disco está sendo desperdiçado pelos arquivos gravados no disco. Este desperdício diz respeito a espaço no disco que já está alocado (associado a um arquivo) e que conseqüentemente não pode ser usado pelo sistema de arquivos (FAT) para armazenar novos arquivos, por exemplo. Espaço desperdiçado = quantidade de arquivos * metade do tamanho do cluster Exemplo: Se numa partição FAT temos 1800 arquivos gravados e o tamanho do cluster (ou bloco) é de 8 KB, então: Espaço desperdiçado = 1800 * 4KB = 7200 KB = 7.03 MB de espaço em disco que está locado mas não pode ser usado para armazenar novos arquivos. A árvore de diretórios: o caso do S.O. Linux A estrutura do sistema de arquivos apresentada aqui é a definida na versão 1.2 de 28 de março de 1995 do Linux Filesystem Structure Standard (FSSTND). O diretório raiz ( / ) é normalmente organizado (subdividido) em uma coleção de subdiretórios. Cada um destes subdiretórios armazena uma classe específica de arquivos, como pode ser visto na tabela a seguir: subdiretório bin Função (classe de arquivos armazenados) Executáveis essenciais (comandos); ex: cat, chmod, cp, date, dd, df, ed, kill, login, ls, mkdir, more, mount, mv, ps, pwd, rm, rmdir, umount,... boot Arquivos estáticos do carregador de boot (boot loader) dev Arquivos de dispositivos etc Arquivos e scripts para configuração do sistema; ex: csh.login, fstab, group, inittab, lilo.conf, passwd, profile, exports, hosts, networks, protocols, ... home Diretórios para usuários Bibliotecas compartilhadas (shared libraries) lib mnt Diretório para montagem de sistemas de arquivos temporários proc Pseudo sistema de arquivos (armazena informações de processos) root Diretório do administrador do sistema (root user) sbin Executáveis essenciais (para administração do sistema); ex: clock, getty, init, mkswap, swapon, swapoff, shutdown, fdisk, fsck, mkfs, badblocks, dumpe2fs, e2fsck, lilo, ifconfig, route, ... tmp Arquivos temporários usr É a 2a maior porção do sistema de arquivos; armazena arquivos compartilháveis, geralmente read-only (tabela a seguir) Dados variáveis; incluem arquivos de spool, arquivos de log e var arquivos temporários; não pode ser uma partição separada O diretório /usr comtém uma hierarquica bastante grande de subdiretórios e merece uma descrição detalhada: subdiretório X11R6 X386 bin Função (classe de arquivos armazenados) X Window System versão 11 release 6 X Window System versão 11 release 5 para plataforma x86 Comandos de usuário dict Lista de palavras (para spell checkers) doc documentação etc Arquivos de configuração não-locais games jogos include Arquivos .h (header files para programas em C) info GNU info system lib bibliotecas local Hierarquia local; usada pelo administrador do sistema quando instala software localmente man Man pages; subdiretórios man1 (programas de usuário), man2 (chamadas do sistema), man3 (funções de bibliotecas), man4 (dispositivos), man5 (formatos de arquivos), man6 (jogos), man7 (miscelânea), man8 (administração do sistema), man9 (funções e variáveis internas do kernel) sbin Executáveis não-essenciais share Dados dependentes de arquitetura src Código-fonte do sistema operacional O diretório /var também contém uma hierarquia de diretórios merecedora de uma análise mais detida. Sua estrutura é apresentada logo abaixo: subdiretório adm catman lib Função (classe de arquivos armazenados) (obsoleto); link simbólico para /var/log Manual pages formatadas localmente Subdiretórios para arquivos temporários e de log para aplicativos (emacs, games, ...) local Dados variáveis de software de /usr/local lock Arquivos de lock log named nis preserve run Arquivos de log Arquivos usados pelo servidor de nomes Internet (named) Arquivos do Network Information Service (NIS) Arquivos salvos após crash no vi e ex Contém arquivos com informações sobre o sistema desde o momento do boot spool Diretórios de spool (lpd – diretório de spool da impressora; mail – arquivos de mailbox; cron – arquivos cron; ...) tmp Arquivos temporários Algumas observações sobre o shell do Linux O shell é a camada do sistema operacional que interpreta os comandos do usuários e dispara os processos adequados para atender aos seus objetivos . Do ponto de vista do usuário, um shell é representado por uma janela de terminal na qual é possível digitar comandos e receber respostas do sistema operacional. O universo Linux oferece um número considerável de shells, mas a versão mais usada atualmente é chamada de bash (Bourne Again Shell). Entre as outras versões disponíveis destacam-se ainda o sh (Bourne shell original), o csh (C shell), o ksh (Korn shell), o tcsh (Enhanced C shell) e o zsh (Z shell). Redirecionamento de Entrada/Saída e Dutos (pipes) O Linux permite que a entrada de dados para um arquivo não precise ser necessariamente feita via teclado, caso esta seja a única forma de leitura adotada pelo programa. Uma forma alternativa é redirecionar a entrada de dados a partir de um arquivo, como no exemplo abaixo: ordena < lista Neste exemplo, o processo ordena coleta os dados a serem processados a partir do arquivo lista. Uma outra situação possível é redirecionar a saída de um processo para um arquivo, como no exemplo a seguir. classifica > resultado Aqui, o processo classifica gera dados de saída que são armazenados em um arquivo chamado resultado. Pode-se também fazer com que a saída de um processo saja adicionada ao final de um arquivo já existente (append), como no exemplo abaixo: classifica >> novo É possível ainda fazer com que a saída de um processo alimente com dados a entrada de um segundo processo. Este mecanismo é chamada de dutos ou pipes. O exemplo a seguir ilustra este tipo de situação: classifica | ordena Neste caso, os dados gerados pelo processo classifica são usados como entrada de dados para o processo ordena. Filtros do Linux Filtros são utilitários geralmente de pequeno tamanho mas muito poderosos, sendo seu uso bastante difundido na comunidade de usuários UNIX/Linux. cat : mostra, cria e concatena arquivos sort : ordena arquivos grep : procura padrão em arquivos wc : conta o número de linhas, palavras e caracteres de um arquivo head : imprime as n 1as linhas de um arquivo tail : imprime as n últimas linhas de um arquivo split : divide um arquivo em arquivos menores diff : compara arquivos e mostra diferenças O uso de caracteres especiais (meta-caracteres) Alguns caracteres possuem emprego especial, como por exemplo o caracter asterisco e o caracter ponto de interrrogação. O caracter especial asterisco: * substitui qualquer string Exemplo: ls a* (lista todos arquivos que começam por “a” ) rm * (remove todos arquivos do diretório corrente) O caracter especial ponto de interrogação: ? substitui um caracter Exemplo: ls a? (lista todos arquivos que começam com “a” e possuem só 2 caracteres) ls ?b? (lista todos arquivos de 3 caracteres que tenham “b” como 2a letra) Segurança e Permissões de acesso Permissões de acesso indicam quais tipos de operações um determinado usuário poderá realizar sobre um arquivo. Todos arquivos tem suas permissões de acesso divididas em 3 grupos: permissões de acesso do próprio usuário (daquele que criou o arquivo), permissões de acesso dos usuários que pertencem ao mesmo grupo deste usuário e por último as permissões de acesso de todos outros usuários da rede. As permissões de acesso são visíveis quando se utiliza o comando ls –l . Figura 10: as permissões de acesso de 3 arquivos . A estrutura do campo de permissão de acesso é a seguinte: - rwx Bit de Tipo: - : arquivo d : diretório b : dispositivo de bloco c : dispositivo caracter l : link s : socket rwx rwx User Group Others As permissões de acesso de um arquivo podem ser ajustadas através do comando chmod. Por exemplo: chmod ugo + rwx nomearquivo → adiciona permissão de leitura ( r ), escrita (w) e execução (x) para o próprio usuário proprietário (criador) do arquivo (u), para usuários do seu grupo (g) e para todos outros usuários cadastrados (o). Outra maneira de usar o chmod é empregando o modo absoluto, cujo funcionamento pode ser resumido através dos valores constantes da seguinte tabela: usuário permissão valor U r 400 U w 200 U x 100 G r 040 G w 020 G x 010 O r 004 O w 002 O x 001 Por exemplo: chmod 755 nomearquivo → dá permissões pra u,g,o de r,w,x (root). Outro comando importante é o chown, que permite alterar o dono de um determinado arquivo. Por exemplo: chown mateus casa.doc → o arquivo casa.doc agora é do usuário mateus Pode-se alterar ainda o grupo ao qual um arquivo pertence; para isto emprega-se o comando chgrp. Por exemplo: chgrp projetos casa.doc → o arquivo casa.doc agora pertence ao grupo projetos