Integração de Sistemas Embebidos MECom :: 5º ano Device Drivers em Linux - Introdução António Joaquim Esteves www.di.uminho.pt/~aje Bibliografia: capítulo 1, LDD 3ed, O Reilly DEP. DE INFORMÁTICA ESCOLA DE ENGENHARIA UNIVERSIDADE DO MINHO
- Sumário - O papel do device driver Estrutura do kernel do Linux Módulos e classes de dispositivos Questões de segurança
- Contexto - Um device driver é uma caixa negra que permite a um dispositivo de hardware responder a uma interface de programação interna bem definida. Um device driver esconde todos os pormenores do modo de funcionamento do dispositivo. As actividades dum utilizador são realizadas através de um conjunto normalizado de chamadas (calls), que é independente do tipo de driver. Converter estas chamadas em operações específicas do dispositivo, as quais actuam sobre o hardware, é a tarefa principal do device driver. Esta modularidade faz com que seja fácil escrever drivers em Linux.
- Contexto - Algumas das razões para escrever device drivers em Linux: Para tornar utilizáveis os novos dispositivos de hardware que surgem a um ritmo elevado. Para ter acesso a facilidades de um dispositivo particular, que de outro modo não seriam acessíveis. Para que os fabricantes de hardware disponibilizem os drivers em Linux para os seus produtos, alargando assim o seu mercado potencial à basta e crescente comunidade de utilizadores de Linux. Dada a natureza aberta do Linux, o código fonte dum driver pode assim ser espalhado rapidamente por milhões de utilizadores.
Device Drivers em Linux - Introdução - O papel dum device driver - A distinção entre mecanismo e política é uma das ideias mais interessantes associadas ao sistema Linux. A resolução da maioria dos problemas de programação pode ser decomposta em 2 partes: definir as capacidades a disponibilizar (o mecanismo) e como serão usadas essas capacidades (a política). Se os 2 assuntos forem tratados em duas partes distintas do programa, ou por 2 programas diferentes, o software é mais fácil de desenvolver e de adaptar a certas especificidades. Este princípio de separação em mecanismo e política também se aplica aos drivers. Ao escrever um driver, convém que o programador tenha o conceito seguinte em consideração: deve escrever código do kernel para aceder ao hardware, mas não deve impor ao utilizador uma política em concreto, dado que cada utilizador tem necessidades diferentes. O driver deve tratar de disponibilizar o hardware, mas deve deixar as questões de como usar esse hardware para as aplicações.
- O papel dum device driver - Também se pode ver um driver de outra perspectiva: é uma camada de software que se situa entre as aplicações e o dispositivo físico. Os drivers que não implementam uma dada política possuem tipicamente as seguintes características: Suportam operações síncronas e assíncronas, Podem ser abertos múltiplas vezes, Conseguem aproveitar na totalidade as potencialidade do hardware, Não possuem camadas de software para simplificar algumas tarefas ou para disponibilizar operações associadas a determinada política. Este tipo de driver funciona melhor para o utilizador final e é mais fácil de desenvolver e manter. Muitos drivers são disponilizados em conjunto com programas para ajudar o utilizador a configurar e a aceder ao dispositivo alvo. Também é comum fornecer junto com o driver uma biblioteca cliente, contendo facilidades que não estão implementadas no próprio driver.
- Estrutura do kernel do Linux - O kernel é o bloco de código executável responsável por gerir os pedidos de recursos por parte dos vários processos concorrentes. Considerando as diferentes tarefas efectuadas, o kernel pode ser decomposto nas seguintes partes (figura 1): Gestão de processos O kernel é responsável por (i) criar e matar os processos, (ii) gerir a ligação dos processos ao exterior, (iii) gerir a comunicação entre processos e (iv) controlar a forma como os processos partilham o CPU (escalonamento). Gestão de memória A memória dum computador é um recurso importante e a política usada na sua gestão influencia de forma crítica o desempenho do sistema. O kernel constrói um espaço de endereçamento virtual, para todos os processos, sobre os recursos disponíveis que são limitados. O kernel interage com o sub-sistema de gestão de memória através da evocação de funções, como por ex. malloc ou free.
- Estrutura do kernel do Linux - Figura 1
Sistemas de ficheiros - Estrutura do kernel do Linux - Quase tudo em Linux pode ser abordado sob a forma de ficheiro. O kernel constrói um sistema de ficheiros estruturado sobre hardware não estruturado. O Linux suporta diversos tipos de sistema de ficheiros, ou seja, diferentes formas de organizar a informação num meio físico. Controlo de dispositivos A maioria das operações num sistema faz-se sobre um dispositivo físico. Todas as operações de controlo dum dispositivo são efectuadas por código específico desse dispositivo esse código é um device driver. O kernel deve conter um device driver para cada periférico presente no sistema (disco, teclado, rato,...). É esta funcionalidade do kernel que mais nos interessa nesta disciplina.
Operação em rede - Estrutura do kernel do Linux - O funcionamento em rede deve ser gerido pelo sistema operativo, dado a maioria das operações em rede não ser específica dum processo os pacotes recebidos são eventos assíncronos. Os pacotes devem ser recolhidos, identificados e enviados antes que um processo tome conta deles. O sistema é responsável por (i) distribuir os pacotes de dados pelos programas e interfaces de rede; (ii) controlar a execução dos programas de acordo com a sua actividade de rede. O kernel implementa todas as funcionalidades associadas com o encaminhamento e resolução de endereços.
- Classes de dispositivos e de módulos m - A cada pedaço de código que pode ser adicionado ao kernel em tempo de execução chama-se módulo. O kernel do Linux dispõe de suporte para um número limitado de tipos (ou classes) de módulos onde se incluem os device drivers. Cada módulo é constituído por código objecto que pode ser linked (unlinked) dinamicamente com (do) kernel, usando o programa insmod (rmmod). Cada módulo implementa normalmente um dos seguintes tipos, e pode por isso ser classificado em: (i) char module, (ii) block module ou (iii) network module. Bons programadores escrevem um módulo diferente para cada nova funcionalidade a implementar, porque a decomposição é a chave para a escalabilidade e a expansão.
- Classes de dispositivos e de módulos m - Caracterização das 3 classes de dispositivos: Dispositivos do tipo character Um dispositivo do tipo character é aquele em que o acesso é feito usando um fluxo (stream) de bytes (como por ex. um ficheiro) um driver do tipo char deve implementar este comportamento. Um driver do tipo char implementa pelo menos as chamadas ao sistema open, close, read e write. A consola de texto (/dev/console) e as portas série (/dev/ttys0, ) são dispositivos do tipo char. O acesso a dispositivos do tipo char faz-se através de nodes do sistema de ficheiros, como por ex. /dev/tty1 e /dev/lp0.
- Classes de dispositivos e de módulos m - Dispositivos do tipo block Tal como os dispositivos do tipo char, o acesso aos dispositivos do tipo block faz-se através de nodes do directório /dev no sistema de ficheiros. Contudo, drivers do tipo block possuem uma interface com o kernel completamente distinta da dos drivers tipo char. Um dispositivo do tipo block (por ex. disco) é capaz de hospedar um sistema de ficheiros. Um dispositivo do tipo block suporta apenas operações de I/O em que se transfere um ou mais blocos completos. Um bloco tem normalmente um tamanho de 2 N bytes, com 2 N 512.
Interfaces de rede - Classes de dispositivos e de módulos m - Qualquer transacção de rede é feita através duma interface, ou seja, um dispositivo que é capaz de trocar dados com outro sistema hospedeiro. Uma interface é normalmente um dispositivo de hardware, mas também pode ser um dispositivo totalmente de software. Uma interface de rede é responsável por enviar e receber dados, sob orientação do sub-sistema de rede, sem saber como é que as transacções são convertidas nos pacotes realmente transmitidos. Um driver de rede desconhece por completo as ligações individuais apenas lida com pacotes. Como não é um dispositivo orientado ao fluxo, uma interface de rede não é facilmente traduzida por um node do sistema de ficheiros. Nestes dispositivos, o kernel chama funções para transmitir pacotes, em vez de funções de read e write.
- Classes de dispositivos e de módulos m - Há outras formas de classificação de módulos/drivers que são ortogonais aos tipos de dispositivo anteriores. Podem assim classificar-se os drivers de acordo com as camadas do kernel que utilizam. Por exemplo, pode falar-se de módulos USB, módulos série, módulos SCSI, módulos firewire, Cada dispositivo USB é controlado por um módulo USB que funciona com o sub-sistema USB, mas o dispositivo pode ser visto pelo sistema como: Um dispositivo do tipo char (uma porta série USB), Um dispositivo do tipo block (um leitor de cartões de memória USB), ou Um dispositivo de rede (uma interface Ethernet USB).
- Questões de segurança - No kernel distribuído oficialmente, apenas um utilizador autorizado pode carregar módulos nesse kernel. A chamada ao sistema init_module verifica se o processo que a evocou está autorizado a carregar módulos no kernel. Quando se funciona com um kernel oficial, apenas o superuser ou um intruso que adquiriu esse privilégio, pode explorar as potencialidade de código privilegiado (como são os módulos). Quem escreve drivers deve ser cuidadoso para evitar introduzir falhas de segurança, com as introduzidas por buffer overrun errors. A informação recebida de processos de utilizadores deve ser tratada com suspeição.
- Questões de segurança - Deve ter-se cuidado com a memória não inicializada. Também é preciso ter cuidado com o software recebido de terceiros, especialmente quando é relativo ao kernel como todos têm acesso ao código fonte do kernel, qualquer um pode fazer alterações e recompilar o kernel. Por exemplo, um kernel modificado de forma maliciosa pode permitir a qualquer utilizador carregar módulos, abrindo assim uma back door indesejada através do init_module.