Driver di caratteri di base in Linux

Driver di caratteri di base in Linux
Passeremo attraverso il modo di Linux di implementare il driver del personaggio. Cercheremo prima di capire qual è il driver del personaggio e come il framework Linux ci consente di aggiungere il driver del personaggio. Successivamente, faremo l'applicazione dello spazio utente del test di esempio. Questa applicazione di test utilizza il nodo del dispositivo esposto dal driver per la scrittura e la lettura dei dati dalla memoria del kernel.

Descrizione

Iniziamo la discussione con il driver del personaggio in Linux. Il kernel classifica i driver in tre categorie:

Driver di personaggi - Questi sono i driver che non hanno troppi dati da affrontare. Pochi esempi di driver di caratteri sono driver touch screen, driver UART, ecc. Tutti questi sono i driver di caratteri poiché il trasferimento dei dati viene eseguito tramite carattere per carattere.

Blocca i conducenti - Questi sono i driver che si occupano di troppi dati. Il trasferimento dei dati viene eseguito il blocco per blocco poiché è necessario trasferire troppi dati. Esempio di conducenti a blocchi sono SATA, NVME, ecc.

Driver di rete - Questi sono i driver che funziona nel gruppo di rete di driver. Qui, il trasferimento dei dati viene eseguito sotto forma di pacchetti di dati. I driver wireless come Atheros rientrano in questa categoria.

In questa discussione, ci concentreremo solo sul driver del personaggio.

Ad esempio, prenderemo le semplici operazioni di lettura/scrittura per comprendere il driver del personaggio di base. Generalmente, qualsiasi driver di dispositivo ha queste due operazioni minime. L'operazione aggiuntiva potrebbe essere aperta, chiusa, IOCTL, ecc. Nel nostro esempio, il nostro driver ha la memoria nello spazio del kernel. Questa memoria è allocata dal driver del dispositivo e può essere considerata come memoria del dispositivo poiché non esiste un componente hardware. Il driver crea l'interfaccia del dispositivo nella directory /dev che può essere utilizzata dai programmi di spazio utente per accedere al driver ed eseguire le operazioni supportate dal driver. Per il programma utenti, queste operazioni sono proprio come qualsiasi altra operazione di file. Il programma di spazio utente deve aprire il file del dispositivo per ottenere l'istanza del dispositivo. Se l'utente desidera eseguire l'operazione di lettura, la chiamata di sistema di lettura può essere utilizzata per farlo. Allo stesso modo, se l'utente desidera eseguire l'operazione di scrittura, la chiamata di sistema di scrittura può essere utilizzata per ottenere l'operazione di scrittura.

Driver del personaggio

Consideriamo di implementare il driver del personaggio con le operazioni di dati di lettura/scrittura.

Iniziamo con l'istanza dei dati del dispositivo. Nel nostro caso, è "struct cdrv_device_data".

Se vediamo i campi di questa struttura, abbiamo CDEV, buffer di dispositivi, dimensioni del buffer, istanza di classe e oggetto dispositivo. Questi sono i campi minimi in cui dovremmo implementare il driver del personaggio. Dipende dall'implementatore da quali campi aggiuntivi vuole aggiungere per migliorare il funzionamento del driver. Qui, cerchiamo di ottenere il funzionamento minimo.

Successivamente, dovremmo creare l'oggetto della struttura dei dati del dispositivo. Usiamo le istruzioni per allocare la memoria in modo statico.

struct cdrv_device_data char_device [cdrv_max_minors];

Questa memoria può anche essere assegnata dinamicamente con "kmalloc". Manteniamo l'implementazione il più semplice possibile.

Dovremmo prendere l'implementazione delle funzioni di lettura e scrivere. Il prototipo di queste due funzioni è definito dal framework del driver del dispositivo di Linux. L'implementazione di queste funzioni deve essere definita dall'utente. Nel nostro caso, abbiamo considerato quanto segue:

Leggi: l'operazione per ottenere i dati dalla memoria del driver allo spazio utenti.

static ssize_t cdrv_read (file struct *, char __user *user_buffer, size_t size, loff_t *offset);

Scrivi: l'operazione per archiviare i dati alla memoria del driver dallo spazio utenti.

static ssize_t cdrv_write (file struct *, const char __user *user_buffer, size_t size, loff_t *offset);

Entrambe le operazioni, lettura e scrittura, devono essere registrate come parte di Struct File_Operations CDRV_FOPS. Questi sono registrati nel framework del driver del dispositivo Linux in init_cdrv () del driver. All'interno della funzione init_cdrv (), vengono eseguite tutte le attività di configurazione. Pochi compiti sono i seguenti:

  • Crea classe
  • Crea istanza del dispositivo
  • Assegnare il numero maggiore e minore per il nodo del dispositivo

Il codice di esempio completo per il driver del dispositivo di caratteri di base è il seguente:

#includere
#includere
#includere
#includere
#includere
#includere
#includere
#define cdrv_major 42
#define cdrv_max_minors 1
#define buf_len 256
#define cdrv_device_name "cdrv_dev"
#define cdrv_class_name "cdrv_class"
struct cdrv_device_data
struct CDEV CDEV;
char buffer [buf_len];
dimensione size_t;
struct class* cdrv_class;
Struct Device* CDRV_DEV;
;
struct cdrv_device_data char_device [cdrv_max_minors];
statico ssize_t cdrv_write (file struct *, const char __user *user_buffer,
size_t size, loff_t * offset)

struct cdrv_device_data *cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> size - *offset, dimensione);
printk ("Writing: bytes =%d \ n", dimensione);
if (len buffer + *offset, user_buffer, len))
return -fault;
*offset += len;
restituire len;

static ssize_t cdrv_read (file struct *, char __user *user_buffer,
size_t size, loff_t *offset)

struct cdrv_device_data *cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> size - *offset, dimensione);
if (len buffer + *offset, len))
return -fault;
*offset += len;
printk ("Leggi: byte =%d \ n", dimensione);
restituire len;

static int cdrv_open (struct inode *inode, file struct *file)
printk (kern_info "cdrv: dispositivo aperto \ n");
restituzione 0;

static int cdrv_release (struct inode *inode, file struct *file)
printk (kern_info "cdrv: dispositivo chiuso \ n");
restituzione 0;

const struct file_operations cdrv_fops =
.Proprietario = this_module,
.open = cdrv_open,
.leggi = cdrv_read,
.write = cdrv_write,
.Release = cdrv_release,
;
int init_cdrv (void)

int count, ret_val;
printk ("init il driver del personaggio di base ... start \ n");
ret_val = register_chdev_region (mkdev (cdrv_major, 0), cdrv_max_minors,
"cdrv_device_driver");
if (ret_val != 0)
printk ("Register_chdev_region (): non riuscito con codice di errore:%d \ n", ret_val);
return ret_val;

per (count = 0; count < CDRV_MAX_MINORS; count++)
CDEV_INIT (& char_device [conteggio].CDEV, & CDRV_FOPS);
CDEV_ADD (& char_device [conteggio].CDEV, MKDEV (CDRV_MAJOR, COUNT), 1);
char_device [conteggio].cdrv_class = class_create (this_module, cdrv_class_name);
if (is_err (char_device [count].cdrv_class))
printk (Kern_alert "CDRV: registri la classe del dispositivo non riuscito \ n");
return ptr_err (char_device [count].cdrv_class);

char_device [conteggio].size = buf_len;
printk (Kern_info "Classe di dispositivo CDRV registrato correttamente \ n");
char_device [conteggio].cdrv_dev = dispositivo_create (char_device [count].cdrv_class, null, mkdev (cdrv_major, count), null, cdrv_device_name);

restituzione 0;

void cleanup_cdrv (void)

conteggio int;
per (count = 0; count < CDRV_MAX_MINORS; count++)
dispositivo_destroy (char_device [count].cdrv_class e char_device [count].cdrv_dev);
class_destroy (char_device [count].cdrv_class);
CDEV_DEL (& char_device [conteggio].CDEV);

UNREGISTER_CHRDEV_RIGION (MKDEV (CDRV_MAJOR, 0), CDRV_MAX_MINORS);
printk ("Uscire dal driver del personaggio di base ... \ n");

module_init (init_cdrv);
module_exit (pulip_cdrv);
Module_license ("GPL");
Module_author ("sushil rathore");
Module_description ("driver del carattere campione");
Module_version ("1.0 ");

Creiamo un makefile di esempio per compilare il driver dei caratteri di base e l'app di prova. Il nostro codice del driver è presente in CRDV.C e il codice dell'app di test è presente in CDRV_APP.C.

OBJ-M+= CDRV.o
Tutto:
make -c/lib/moduli/$ (shell uname -r)/build/m = $ (pwd) moduli
$ (Cc) cdrv_app.c -o cdrv_app
pulito:
fare -c/lib/moduli/$ (shell uname -r)/build/m = $ (pwd) pulito
RM CDRV_APP
~

Dopo che l'emissione è stata effettuata al makefile, dovremmo ottenere i seguenti registri. Otteniamo anche il CDRV.KO e eseguibile (CDRV_APP) per la nostra app di test:

root@haxv-srathore-2:/home/cienauser/kernel_articles# make
fare -c/lib/moduli/4.15.0-197-generico/build/m =/home/cienauser/kernel_articles moduli
Make [1]: immettere la directory '/usr/src/linux-headers-4.15.0-197-generico '
CC [M]/Home/Cienauser/Kernel_articles/CDRV.o
Moduli di costruzione, Fase 2.
Moduli Modpost 1
CC/Home/Cienauser/Kernel_articles/CDRV.mod.o
LD [M]/Home/Cienauser/Kernel_articles/CDRV.ko
Make [1]: Leaving Directory '/USR/SRC/Linux-Headers-4.15.0-197-generico '
CC CDRV_APP.c -o cdrv_app

Ecco il codice di esempio per l'app di test. Questo codice implementa l'app di test che apre il file del dispositivo creato dal driver CDRV e scrive i "dati di test" ad esso. Quindi, legge i dati dal driver e li stampa dopo aver letto i dati da stampare come "dati di prova".

#includere
#includere
#define dispositivo_file "/dev/cdrv_dev"
char *data = "dati di test";
char read_buff [256];
int main ()

int fd;
int rc;
fd = open (dispositivo_file, o_wronly, 0644);
if (fd<0)

Perror ("File di apertura: \ n");
restituzione -1;

rc = write (fd, dati, strlen (dati) +1);
if (RC<0)

Perror ("File di scrittura: \ n");
restituzione -1;

printf ("byte scritti =%d, data =%s \ n", rc, data);
chiudere (FD);
fd = open (dispositivo_file, o_rdonly);
if (fd<0)

Perror ("File di apertura: \ n");
restituzione -1;

rc = read (fd, read_buff, strlen (dati) +1);
if (RC<0)

Perror ("File di lettura: \ n");
restituzione -1;

printf ("leggi byte =%d, data =%s \ n", rc, read_buff);
chiudere (FD);
restituzione 0;

Una volta che abbiamo tutte le cose in atto, possiamo usare il seguente comando per inserire il driver di caratteri di base sul kernel Linux:

root@haxv-sratore-2:/home/cienauser/kernel_articles# insmod cdrv.ko
root@haxv-sratore-2:/home/cienauser/kernel_articles#

Dopo aver inserito il modulo, otteniamo i seguenti messaggi con DMESG e creiamo il file del dispositivo creato in /dev as /dev /cdrv_dev:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: caricamento del modulo fuori albero kernel.
[160.015688] CDRV: Verifica del modulo non riuscita: firma e/o chiave richiesta mancante - Kernel di contatto
[160.016173] Inizia il driver del personaggio di base ... Avvia
[160.016225] Classe di dispositivo CDRV registrata correttamente
root@haxv-sratore-2:/home/cienauser/kernel_articles#

Ora, eseguire l'app di test con il seguente comando nella shell Linux. Il messaggio finale stampa i dati di lettura dal driver che è esattamente lo stesso di quello che abbiamo scritto nell'operazione di scrittura:

root@haxv-sratore-2:/home/cienauser/kernel_articles# ./cdrv_app
byte scritti = 10, data = test dati
Leggi byte = 10, data = test dati
root@haxv-sratore-2:/home/cienauser/kernel_articles#

Abbiamo alcune stampe aggiuntive nel percorso di scrittura e lettura che può essere vista con l'aiuto del comando dmesg. Quando emettiamo il comando DMESG, otteniamo il seguente output:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: caricamento del modulo fuori albero kernel.
[160.015688] CDRV: Verifica del modulo non riuscita: firma e/o chiave richiesta mancante - Kernel di contatto
[160.016173] Inizia il driver del personaggio di base ... Avvia
[160.016225] Classe di dispositivo CDRV registrata correttamente
[228.533614] CDRV: dispositivo aperto
[228.533620] scrittura: byte = 10
[228.533771] CDRV: dispositivo chiuso
[228.533776] CDRV: dispositivo aperto
[228.533779] Leggi: byte = 10
[228.533792] CDRV: dispositivo chiuso
root@haxv-sratore-2:/home/cienauser/kernel_articles#

Conclusione

Abbiamo esaminato il driver di caratteri di base che implementa le operazioni di scrittura e lettura di base. Abbiamo anche discusso del campione makefile per compilare il modulo insieme all'app di test. L'app di test è stata scritta e discussa per eseguire le operazioni di scrittura e lettura dallo spazio utente. Abbiamo anche dimostrato la compilation e l'esecuzione del modulo e dell'app di test con registri. L'app di test scrive pochi byte di dati di test e poi li legge indietro. L'utente può confrontare sia i dati per confermare il funzionamento corretto del driver e dell'app di test.