Como o dispositivo de caracteres ou os arquivos especiais de caracteres funcionam?


22

Estou tentando entender arquivos especiais de caracteres. Na wikipedia , entendo que esses arquivos "fornecem uma interface" para dispositivos que transmitem dados um caractere por vez. Meu entendimento é que o sistema de alguma forma chama o dispositivo de caracteres em vez de ligar diretamente para o driver de dispositivo. Mas como o arquivo fornece essa interface? É um executável que traduz a chamada do sistema? Alguém pode explicar o que está acontecendo.

Respostas:


19

Eles são exatamente isso - interfaces. Codificados por um número "maior" e "menor", eles fornecem um gancho para o kernel.

Eles vêm em dois tipos (bem, três, mas pipes nomeados estão fora do escopo desta explicação no momento): Dispositivos de caracteres e Dispositivos de bloco.

Os dispositivos de bloco tendem a ser dispositivos de armazenamento, capazes de armazenar em buffer a saída e armazenar dados para recuperação posterior.

Dispositivos de caracteres são coisas como placas de áudio ou gráficos ou dispositivos de entrada como teclado e mouse.

Em cada caso, quando o kernel carrega o driver correto (no momento da inicialização ou através de programas como o udev ), ele varre os vários barramentos para verificar se algum dispositivo manipulado por esse driver está realmente presente no sistema. Nesse caso, ele configura um dispositivo que 'escuta' no número maior / menor apropriado.

(Por exemplo, o processador de sinal digital da primeira placa de áudio encontrada pelo seu sistema obtém o par de números maior / menor de 14/3; o segundo obtém 14,35 etc.)

Cabe ao udev criar uma entrada /devnomeada dspcomo um dispositivo de caractere marcado como major 14 menores 3.

(Nas versões significativamente mais antigas ou com uma pegada mínima do Linux, /dev/pode não ser carregado dinamicamente, mas apenas conter todos os arquivos de dispositivo possíveis estaticamente.)

Então, quando um programa no espaço do usuário tenta acessar um arquivo marcado como um 'arquivo especial de caractere' com o número principal / secundário apropriado (por exemplo, seu reprodutor de áudio tentando enviar áudio digital /dev/dsp), o kernel sabe que esses dados precisam Ser transmitido através do driver ao qual o número principal / secundário está anexado; presumivelmente, o referido motorista sabe o que fazer com ele por sua vez.


1
1. Então, números maiores / menores são análogos às portas?
Bernie2436

2. Então, quando os programas acessam qualquer arquivo, o kernel lê essas interfaces especiais para saber se o programa deve receber interrupções de um dispositivo específico? Ex: se um programa abre um arquivo do word, ele lê o arquivo especial do dispositivo de caracteres para saber que o programa deve responder à entrada do teclado?
Bernie2436

1) Um pouco . É a analogia de um homem pobre, mas serve.
Shadur

2
2) Faltam três ou quatro camadas de abstração lá. O programa em que você abre um arquivo de texto não sabe nem se importa com o que é o dispositivo de teclado. A comunicação com o hardware subjacente ocorre através do emulador de terminal (se você estiver no modo console) ou através da camada de eventos X (se estiver no modo gráfico), um dos quais ouvirá o teclado e outras unidades e decidirá o que , se houver, para passar para o programa. Estou resumindo um sistema multicamada bastante complexo aqui; convém ler o X Window System em geral.
Shadur

1
Observe também que, em alguns tipos de UN * X, há arquivos especiais de caracteres para dispositivos de armazenamento; uma leitura ou gravação no arquivo especial se transforma em uma leitura ou gravação em uma sequência de blocos no dispositivo. (Em versões recentes do FreeBSD, essas são as única arquivos especiais para dispositivos de armazenamento, não são nenhum bloco arquivos especiais.)

10

Todo arquivo, dispositivo ou outro, suporta 6 operações básicas no VFS:

  1. Abrir
  2. Fechar
  3. Ler
  4. Escrever
  5. Procurar
  6. Contar

Além disso, os arquivos do dispositivo oferecem suporte ao controle de E / S, o que permite outras operações diversas não cobertas pelos 6 primeiros.

No caso de um personagem especial, procurar e informar não são implementados, pois eles suportam uma interface de streaming . Ou seja, ler ou escrever diretamente, como é feito com o redirecionamento no shell:

echo 'foo' > /dev/some/char
sed ... < /dev/some/char

6

file_operationsExemplo mínimo executável

Depois de ver um exemplo mínimo, tudo se torna óbvio.

As idéias principais são:

  • file_operations contém os retornos de chamada para cada syscall relacionado a arquivos
  • mknod <path> c <major> <minor> cria um dispositivo de caractere que usa esses file_operations
  • para dispositivos de caracteres que alocam dinamicamente números de dispositivos (a norma para evitar conflitos), localize o número com cat /proc/devices

character_device.ko módulo do kernel:

#include <asm/uaccess.h> /* copy_from_user, copy_to_user */
#include <linux/errno.h> /* EFAULT */
#include <linux/fs.h> /* register_chrdev, unregister_chrdev */
#include <linux/jiffies.h>
#include <linux/module.h>
#include <linux/printk.h> /* printk */
#include <uapi/linux/stat.h> /* S_IRUSR */

#define NAME "lkmc_character_device"

MODULE_LICENSE("GPL");

static int major;

static ssize_t read(struct file *filp, char __user *buf, size_t len, loff_t *off)
{
    size_t ret;
    char kbuf[] = {'a', 'b', 'c', 'd'};

    ret = 0;
    if (*off == 0) {
        if (copy_to_user(buf, kbuf, sizeof(kbuf))) {
            ret = -EFAULT;
        } else {
            ret = sizeof(kbuf);
            *off = 1;
        }
    }
    return ret;
}

static const struct file_operations fops = {
    .owner = THIS_MODULE,
    .read = read,
};

static int myinit(void)
{
    major = register_chrdev(0, NAME, &fops);
    return 0;
}

static void myexit(void)
{
    unregister_chrdev(major, NAME);
}

module_init(myinit)
module_exit(myexit)

Programa de teste da Userland:

insmod /character_device.ko
dev="lkmc_character_device"
major="$(grep "$dev" /proc/devices | cut -d ' ' -f 1)"
mknod "/dev/$dev" c "$major" 0
cat /dev/lkmc_character_device
# => abcd
rm /dev/lkmc_character_device
rmmod character_device

GitHub QEMU + Buildroot upstream com boilerplate para realmente executá-lo:

Exemplos mais complexos:


Isso foi super útil, obrigado! Apenas uma pergunta, o que exatamente isso faz *off = 1;e por que está definida 1?
SilverSlash

1
@SilverSlash esse valor é passado por várias readchamadas para o mesmo open(descritor de arquivo. O motorista pode fazer o que quiser com ele. A semântica usual é conter o número de bytes lidos. Neste exemplo, no entanto, temos apenas uma semântica mais simples: 0para a primeira leitura, 1após a primeira leitura. Tente executá-lo e coloque uma etapa printk ou GDB na depuração.
Ciro Santilli #

4

"Caráter de cada vez" é um nome impróprio (como é a ideia de que os dispositivos de caracteres necessariamente não suportam a busca e a revelação). De fato, os dispositivos "bloco por vez" (ou seja, estritamente orientados a registros, como uma unidade de fita *) devem ser dispositivos de caracteres. Assim é a ideia de que um dispositivo de caractere deve necessariamente ser inseparável - os drivers de dispositivo de caractere definem uma file_operationsestrutura completa que é livre para definir llseek ou não, de acordo com o suporte do dispositivo à operação. Os dispositivos de caracteres que a maioria das pessoas considera como exemplos são nulos, urandom, dispositivos TTY, placa de som, mouse, etc ... que são inseparáveis ​​por causa das especificidades do que esses dispositivos são, mas / dev / vcs, / dev / fb0 , e / dev / kmem também são dispositivos de caracteres e são todos procuráveis.

Como mencionei, um driver de dispositivo de caractere define uma estrutura de file_operations que possui ponteiros de função para todas as operações que alguém pode querer chamar em um arquivo - busca, leitura, gravação, ioctl etc. - e são chamados uma vez quando a chamada de sistema correspondente é executado com este arquivo de dispositivo aberto. E ler e escrever pode, portanto, fazer o que quiser com seus argumentos - pode se recusar a aceitar uma gravação muito grande ou apenas escrever o que for adequado; ele pode ler apenas os dados correspondentes a um registro e não o número total de bytes solicitado.

Então, o que é um dispositivo de bloco então? Basicamente, os dispositivos de bloco são unidades de disco. Nenhum outro tipo de dispositivo (exceto unidades de disco virtual , como ramdisk e loopback) é um dispositivo de bloco. Eles são integrados ao sistema de solicitação de E / S, à camada do sistema de arquivos, ao sistema de buffer / cache e ao sistema de memória virtual de uma maneira que os dispositivos de caracteres não são, mesmo quando você está acessando, por exemplo, / dev / sda a partir de um processo do usuário . Até os "dispositivos brutos" mencionados nessa página como exceção são dispositivos de caracteres .

* Alguns sistemas UNIX implementaram o que agora é chamado de "modo de bloco fixo" - que permite que o kernel agrupe e divida solicitações de E / S para ajustar os limites do bloco configurado da maneira mais ou menos da mesma maneira que é feito para unidades de disco - como um bloco dispositivo. Um dispositivo de caracteres é necessário para o "modo de bloco variável", que preserva os limites do bloco do programa do usuário, pois uma única chamada de gravação (2) grava um bloco e uma única chamada de leitura (2) retorna um bloco. Como a troca de modo é implementada agora como um ioctl, e não como um arquivo de dispositivo separado, um dispositivo de caractere é usado. As unidades de fita de registro variável geralmente são "não procuráveis" porque a busca envolve contar um número de registros em vez de um número de bytes, e a operação de busca nativa é implementada como um ioctl.


1

Os dispositivos de caracteres podem ser criados pelos módulos do kernel (ou pelo próprio kernel). Quando um dispositivo é criado, o criador fornece indicadores para funções que implementam manipular chamadas padrão, como abrir, ler etc. O kernel do Linux associa essas funções ao dispositivo de caracteres, por exemplo, quando um aplicativo no modo de usuário chama o read () função em um arquivo de dispositivo de caractere, resultará em um syscall e o kernel encaminhará essa chamada para uma função de leitura especificada ao criar o driver. Há um tutorial passo a passo sobre a criação de um dispositivo de caractere aqui . Você pode criar um projeto de amostra e percorrê-lo usando um depurador para entender como o objeto do dispositivo é criado e quando os manipuladores são chamados.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.