Come funzionano i dispositivi speciali o i file di caratteri?


22

Sto cercando di capire i file speciali dei personaggi. Da Wikipedia , ho capito che questi file "forniscono un'interfaccia" per i dispositivi che trasmettono dati un carattere alla volta. La mia comprensione è che il sistema in qualche modo chiama il dispositivo a caratteri invece di chiamare direttamente il driver del dispositivo. Ma in che modo il file fornisce questa interfaccia? È un eseguibile che traduce la chiamata di sistema? Qualcuno può spiegare cosa succede.

Risposte:


19

In realtà sono proprio queste: interfacce. Codificati da un numero "maggiore" e "minore", forniscono un hook al kernel.

Esistono in due versioni (beh, tre, ma le pipe denominate non rientrano nell'ambito di questa spiegazione per ora): Character Devices e Block Devices.

I dispositivi a blocchi tendono ad essere dispositivi di archiviazione, in grado di bufferizzare l'output e archiviare i dati per il successivo recupero.

I Character Character sono dispositivi come schede audio o grafiche o dispositivi di input come tastiera e mouse.

In ogni caso, quando il kernel carica il driver corretto (o all'avvio o tramite programmi come udev ) scansiona i vari bus per vedere se qualche dispositivo gestito da quel driver è effettivamente presente sul sistema. In tal caso, imposta un dispositivo che "ascolta" sul numero maggiore / minore appropriato.

(Ad esempio, il processore del segnale digitale della prima scheda audio trovata dal sistema ottiene la coppia di numeri maggiore / minore di 14/3; la seconda ottiene 14,35, ecc.)

Sta a udev creare una voce /devdenominata dspcome dispositivo a caratteri contrassegnato come maggiore 14 minore 3.

(Nelle versioni significativamente più vecchie o con un minimo ingombro di Linux, /dev/potrebbe non essere caricato dinamicamente ma contenere solo tutti i possibili file di dispositivo staticamente.)

Quindi, quando un programma userspace tenta di accedere a un file contrassegnato come "file speciale di carattere" con il numero maggiore / minore appropriato (ad esempio, il tuo lettore audio che tenta di inviare l'audio digitale a /dev/dsp), il kernel sa che questi dati devono essere trasmesso tramite il conducente al quale è assegnato il numero maggiore / minore; presumibilmente detto che il conducente sa cosa farne a sua volta.


1
1. Quindi i numeri maggiori / minori sono analoghi alle porte?
bernie2436,

2. Quindi quando i programmi accedono a qualsiasi file, il kernel legge queste interfacce speciali per sapere se il programma dovrebbe ottenere interruzioni da un determinato dispositivo? Es: se un programma apre un file word, legge il file speciale del dispositivo a caratteri per sapere che il programma dovrebbe rispondere all'input da tastiera?
bernie2436,

1) Un po ' . È un'analogia di un povero ma lo farà.
Shadur,

2
2) Ti mancano circa tre o quattro strati di astrazione lì. Il programma in cui apri un file di testo senza sapere né preoccuparti del dispositivo a tastiera. La comunicazione con l'hardware sottostante avviene tramite l'emulatore di terminale (se si è in modalità console) o tramite il livello degli eventi X (se si è in modalità grafica), ciascuno dei quali ascolterà la tastiera e altre unità e decide cosa , se non altro, per passare al programma. Sto riassumendo un sistema multistrato abbastanza complesso qui; potresti fare bene a leggere su X Window System in generale.
Shadur,

1
Si noti inoltre che, su alcune versioni di UN * X, ci sono file speciali di caratteri per i dispositivi di archiviazione; una lettura o una scrittura nel file speciale si trasforma in una lettura o scrittura in una sequenza di blocchi sul dispositivo. (Nelle versioni recenti di FreeBSD, questi sono gli unici file speciali per i dispositivi di archiviazione; non ci sono file speciali a blocchi.)

10

Ogni file, dispositivo o altro, supporta 6 operazioni di base all'interno del VFS:

  1. Aperto
  2. Vicino
  3. Leggere
  4. Scrivi
  5. Cercare
  6. Raccontare

Inoltre, i file del dispositivo supportano il controllo I / O, che consente altre operazioni varie non coperte dai primi 6.

Nel caso di un personaggio speciale, cerca e racconta non sono implementati poiché supportano un'interfaccia di streaming . Cioè, leggere o scrivere direttamente come avviene con il reindirizzamento nella shell:

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

6

file_operationsEsempio eseguibile minimo

Quando vedi un esempio minimo, tutto diventa ovvio.

Le idee chiave sono:

  • file_operations contiene i callback per ogni syscall relativo al file
  • mknod <path> c <major> <minor> crea un dispositivo personaggio che usa quelli file_operations
  • per i dispositivi a caratteri che allocano dinamicamente i numeri dei dispositivi (la norma per evitare conflitti), trova il numero con cat /proc/devices

character_device.ko modulo del 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)

Programma test utente:

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 a monte con boilerplate per eseguirlo effettivamente:

Esempi più complessi:


Questo è stato super utile grazie! Solo una domanda, cosa fa esattamente questo *off = 1;, e perché è impostato 1?
SilverSlash,

1
@SilverSlash quel valore viene passato attraverso più readchiamate allo stesso open(descrittore di file. L'autista può fare quello che vuole con esso. Il solito semantico è contenere il numero di byte letti. In questo esempio, tuttavia, abbiamo solo una semantica più semplice: 0per la prima lettura, 1dopo la prima lettura. Prova a eseguirlo e metti un passo di printk o GDB debug.
Ciro Santilli 3 改造 中心 法轮功 六四 事件

4

"Personaggio alla volta" è un termine improprio (così come l'idea che i dispositivi del personaggio non supportano necessariamente la ricerca e la descrizione). In effetti, i dispositivi "blocco alla volta" (cioè strettamente orientati alla registrazione, come un'unità nastro *) devono essere dispositivi a caratteri. Quindi è l'idea che un dispositivo a caratteri debba essere necessariamente non ricercabile: i driver dei dispositivi a caratteri definiscono una file_operationsstruttura completa che è libera di definire o meno a seconda che il dispositivo supporti l'operazione. I dispositivi di carattere che la maggior parte delle persone considera come esempi sono null, urandom, dispositivi TTY, scheda audio, mouse, ecc ... che sono tutti non ricercabili a causa delle specifiche di cosa sono quei dispositivi, ma / dev / vcs, / dev / fb0 , e / dev / kmem sono anche dispositivi a caratteri e sono tutti ricercabili.

Come ho già detto, un driver di dispositivo a caratteri definisce una struttura di file_operations che ha i puntatori di funzione per tutte le operazioni che qualcuno potrebbe voler chiamare su un file - cercare, leggere, scrivere, ioctl, ecc. - e questi vengono chiamati ciascuno una volta quando la chiamata di sistema corrispondente viene eseguito con questo file di dispositivo aperto. E leggere e scrivere può quindi fare quello che vuole con i suoi argomenti: può rifiutare di accettare una scrittura troppo grande o solo scrivere ciò che si adatta; può leggere solo i dati corrispondenti a un record anziché l'intero numero richiesto di byte.

Allora, cos'è un dispositivo a blocchi? Fondamentalmente, i dispositivi a blocchi sono unità disco. Nessun altro tipo di dispositivo (ad eccezione delle unità disco virtuali , come ramdisk e loopback) è un dispositivo a blocchi. Sono integrati nel sistema di richiesta I / O, nel livello del filesystem, nel sistema buffer / cache e nel sistema di memoria virtuale in un modo in cui i dispositivi a caratteri non lo sono, anche quando si accede ad es. / Dev / sda da un processo utente . Anche i "dispositivi non elaborati" che la pagina menziona come un'eccezione sono dispositivi a caratteri .

* Alcuni sistemi UNIX hanno implementato la cosiddetta "modalità a blocco fisso", che consente al gruppo del kernel e di dividere le richieste di I / O per adattarsi ai limiti di blocco configurati più o meno nello stesso modo in cui viene fatto per le unità disco - come blocco dispositivo. Per la "modalità a blocco variabile" è necessario un dispositivo a caratteri, che preserva i limiti di blocco dal programma utente poiché una singola chiamata di scrittura (2) scrive un blocco e una chiamata di sola lettura (2) restituisce un blocco. Poiché la commutazione della modalità è ora implementata come ioctl anziché come file di dispositivo separato, viene utilizzato un dispositivo a caratteri. Le unità nastro a record variabile sono per lo più "non ricercabili" poiché la ricerca implica il conteggio di un numero di record anziché di un numero di byte e l'operazione di ricerca nativa viene implementata come ioctl.


1

I dispositivi a caratteri possono essere creati dai moduli del kernel (o dal kernel stesso). Quando viene creato un dispositivo, il creatore fornisce puntatori a funzioni che implementano la gestione di chiamate standard come open, read, ecc. Il kernel Linux quindi associa tali funzioni al dispositivo carattere, quindi ad esempio quando un'applicazione in modalità utente chiama read () funzione su un file di dispositivo a caratteri, si tradurrà in una syscall e quindi il kernel indirizzerà questa chiamata a una funzione di lettura specificata durante la creazione del driver. C'è un tutorial passo-passo sulla creazione di un dispositivo a caratteri qui , puoi creare un progetto di esempio e attraversarlo usando un debugger per capire come viene creato l'oggetto dispositivo e quando vengono richiamati i gestori.

Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.