Driver de personagem básico no Linux

Driver de personagem básico no Linux
Vamos passar pela maneira Linux de implementar o driver de personagem. Primeiro tentaremos entender o que é o driver de personagem e como a estrutura do Linux nos permite adicionar o driver de personagem. Depois disso, faremos o aplicativo de espaço de teste de teste de amostra. Este aplicativo de teste usa o nó do dispositivo exposto pelo driver para escrever e ler os dados da memória do kernel.

Descrição

Vamos começar a discussão com o driver de personagem no Linux. O kernel categoriza os motoristas em três categorias:

Drivers de personagem - Estes são os drivers que não têm muitos dados para lidar. Poucos exemplos de motoristas de personagens são driver de tela de toque, driver UART, etc. Estes são todos os drivers de personagem, já que a transferência de dados é feita através do personagem por personagem.

Drivers de bloqueio - Estes são os drivers que lidam com muitos dados. A transferência de dados é feita em bloco por bloco, pois muitos dados precisam ser transferidos. Exemplo de drivers de bloco são SATA, NVME, etc.

Drivers de rede - Estes são os motoristas que funcionam no grupo de drivers de rede. Aqui, a transferência de dados é feita na forma de pacotes de dados. Motoristas sem fio como Atheros estão nessa categoria.

Nesta discussão, vamos nos concentrar apenas no driver de personagem.

Como exemplo, levaremos as operações simples de leitura/gravação para entender o driver de caracteres básico. Geralmente, qualquer driver de dispositivo tem essas duas operações mínimas. Operação adicional pode ser aberta, fechada, ioctl, etc. Em nosso exemplo, nosso motorista tem a memória no espaço do kernel. Essa memória é alocada pelo driver do dispositivo e pode ser considerada a memória do dispositivo, pois não há componente de hardware envolvido. O driver cria a interface do dispositivo no diretório /dev que pode ser usado pelos programas espaciais do usuário para acessar o driver e executar as operações suportadas pelo driver. Para o programa UsersPace, essas operações são como qualquer outra operações de arquivo. O programa espacial do usuário precisa abrir o arquivo do dispositivo para obter a instância do dispositivo. Se o usuário quiser executar a operação de leitura, a chamada do sistema de leitura pode ser usada para fazê -lo. Da mesma forma, se o usuário quiser executar a operação de gravação, a chamada do sistema de gravação poderá ser usada para obter a operação de gravação.

Driver de personagem

Vamos considerar implementar o driver de caracteres com as operações de dados de leitura/gravação.

Começamos com a instância dos dados do dispositivo. No nosso caso, é "struct cdrv_device_data".

Se vemos os campos dessa estrutura, temos CDEV, buffer de dispositivo, tamanho de buffer, instância de classe e objeto de dispositivo. Estes são os campos mínimos onde devemos implementar o driver de personagem. Depende do implementador do qual campos adicionais ele deseja adicionar para melhorar o funcionamento do driver. Aqui, tentamos alcançar o funcionamento mínimo.

Em seguida, devemos criar o objeto da estrutura de dados do dispositivo. Usamos a instrução para alocar a memória de maneira estática.

struct cdrv_device_data char_device [cdrv_max_minors];

Esta memória também pode ser alocada dinamicamente com "Kmalloc". Vamos manter a implementação o mais simples possível.

Devemos aceitar a implementação das funções de leitura e gravação. O protótipo dessas duas funções é definido pela estrutura do driver do dispositivo do Linux. A implementação dessas funções precisa ser definida pelo usuário. No nosso caso, consideramos o seguinte:

Leia: a operação para obter os dados da memória do driver para o espaço dos usuários.

static ssize_t cdrv_read (arquivo struct *, char __user *user_buffer, tamanho_t tamanho, loff_t *deslocamento);

Escreva: a operação para armazenar os dados para a memória do driver do espaço dos usuários.

static ssize_t cdrv_write (arquivo struct *, const char __user *user_buffer, tamanho_t tamanho, loff_t *deslocamento);

Ambas as operações, leia e escreva, precisam ser registradas como parte da STRIG FILE_OPERATATIONS CDRV_FOPS. Eles são registrados na estrutura do driver do dispositivo Linux no init_cdrv () do driver. Dentro da função init_cdrv (), todas as tarefas de configuração são executadas. Poucas tarefas são as seguintes:

  • Criar classe
  • Crie instância do dispositivo
  • Alocar número maior e menor para o nó do dispositivo

O código de exemplo completo para o driver de dispositivo de caractere básico é o seguinte:

#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#incluir
#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
estrutura cdev cdev;
buffer de char [buf_len];
tamanho_t tamanho;
classe de estrutura* cdrv_class;
dispositivo de estrutura* cdrv_dev;
;
struct cdrv_device_data char_device [cdrv_max_minors];
static ssize_t cdrv_write (arquivo struct *, const char __user *user_buffer,
tamanho_t tamanho, loff_t * deslocamento)

struct cdrv_device_data *cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> tamanho - *deslocamento, tamanho);
printk ("Escrevendo: bytes =%d \ n", tamanho);
if (len buffer + *deslocamento, user_buffer, len))
retornar -efault;
*deslocamento += len;
retornar Len;

static ssize_t cdrv_read (arquivo struct *, char __user *user_buffer,
tamanho_t tamanho, loff_t *deslocamento)

struct cdrv_device_data *cdrv_data = & char_device [0];
ssize_t len ​​= min (cdrv_data-> tamanho - *deslocamento, tamanho);
if (len buffer + *deslocamento, len))
retornar -efault;
*deslocamento += len;
printk ("Leia: bytes =%d \ n", tamanho);
retornar Len;

static int cdrv_open (struct inode *inode, arquivo struct *arquivo)
printk (kern_info "cdrv: dispositivo aberto \ n");
retornar 0;

static int cdrv_release (struct inode *inode, arquivo struct *arquivo)
printk (kern_info "cdrv: dispositivo fechado \ n");
retornar 0;

const struct file_operations cdrv_fops =
.proprietário = this_module,
.aberto = cdrv_open,
.Leia = cdrv_read,
.write = cdrv_write,
.release = cdrv_release,
;
int init_cdrv (void)

int count, ret_val;
printk ("init o driver de caracteres básicos… start \ n");
ret_val = register_chrdev_region (mkdev (cdrv_major, 0), cdrv_max_minors,
"cdrv_device_driver");
if (ret_val != 0)
printk ("registr_chrdev_region (): falhou com o código de erro:%d \ n", ret_val);
retornar ret_val;

para (contagem = 0; contagem < CDRV_MAX_MINORS; count++)
CDev_init (& char_device [contagem].CDev, & cdrv_fops);
CDev_add (& char_device [contagem].CDev, mkdev (cdrv_major, contagem), 1);
char_device [contagem].cdrv_class = classe_create (this_module, cdrv_class_name);
if (is_err (char_device [contagem].cdrv_class))
printk (kern_alert "cdrv: registro da classe de dispositivo falhou \ n");
Retorne ptr_err (char_device [contagem].cdrv_class);

char_device [contagem].tamanho = buf_len;
Printk (Kern_info "CDRV Class de dispositivo registrado com sucesso \ n");
char_device [contagem].cdrv_dev = device_create (char_device [contagem].cdrv_class, null, mkdev (cdrv_major, contagem), null, cdrv_device_name);

retornar 0;

Void Cleanup_cdrv (void)

int contagem;
para (contagem = 0; contagem < CDRV_MAX_MINORS; count++)
Device_Destroy (char_device [contagem].cdrv_class, & char_device [contagem].cdrv_dev);
Class_Destroy (char_device [contagem].cdrv_class);
CDev_del (& char_device [contagem].CDev);

ungister_chrdev_region (mkdev (cdrv_major, 0), cdrv_max_minors);
printk ("Sair do driver básico de caracteres… \ n");

module_init (init_cdrv);
Module_exit (Cleanup_CDRV);
Module_license ("GPL");
Module_author ("Sushil Rathore");
Module_description ("driver de caracteres de amostra");
Module_version ("1.0 ");

Criamos uma amostra makefile para compilar o driver de caracteres básico e o aplicativo de teste. Nosso código de motorista está presente no CRDV.C e o código do aplicativo de teste está presente em cdrv_app.c.

obj-m+= cdrv.o
todos:
make -c/lib/módulos/$ (shell uname -r)/build/m = $ (pwd) módulos
$ (Cc) cdrv_app.C -o cdrv_app
limpar:
make -c/lib/módulos/$ (shell uname -r)/build/m = $ (pwd) limpo
rm cdrv_app
~

Depois que a emissão é feita para o Makefile, devemos obter os seguintes toras. Também temos o CDRV.KO e executável (cdrv_app) para nosso aplicativo de teste:

root@haxv-srathore-2:/home/cienauser/kernel_articles#
Make -c/lib/módulos/4.15.0-197-genérico/build/m =/home/cienauser/kernel_articles módulos
Faça [1]: Inserindo o diretório '/usr/src/linux-headers-4.15.0-197 Generic '
CC [M]/Home/Cienauser/Kernel_articles/CDRV.o
Módulos de construção, estágio 2.
Módulos MODPOST 1
CC/Home/Cienauser/Kernel_articles/CDRV.mod.o
LD [M]/Home/Cienauser/Kernel_articles/CDRV.Ko
Faça [1]: Deixando o Diretório '/usr/src/linux-headers-4.15.0-197 Generic '
cc cdrv_app.C -o cdrv_app

Aqui está o código de amostra para o aplicativo de teste. Este código implementa o aplicativo de teste que abre o arquivo de dispositivo criado pelo driver CDRV e grava os "dados de teste". Em seguida, ele lê os dados do driver e o imprime depois de ler os dados a serem impressos como "dados de teste".

#incluir
#incluir
#Define Device_File "/dev/cdrv_dev"
char *data = "dados de teste";
char read_buff [256];
int main ()

int fd;
int rc;
fd = aberto (device_file, o_wronly, 0644);
if (fd<0)

perror ("Arquivo de abertura: \ n");
retornar -1;

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

perror ("Arquivo de escrita: \ n");
retornar -1;

printf ("escrito bytes =%d, dados =%s \ n", rc, dados);
fechar (FD);
fd = aberto (device_file, o_rdonly);
if (fd<0)

perror ("Arquivo de abertura: \ n");
retornar -1;

rc = leia (fd, read_buff, strlen (dados) +1);
if (rc<0)

perror ("Arquivo de leitura: \ n");
retornar -1;

printf ("leia bytes =%d, dados =%s \ n", rc, read_buff);
fechar (FD);
retornar 0;

Depois de termos todas as coisas, podemos usar o seguinte comando para inserir o driver básico de caracteres no kernel Linux:

root@haxv-srathore-2:/home/cienauser/kernel_articles# insmod cdrv.Ko
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Depois de inserir o módulo, recebemos as seguintes mensagens com DMESG e criamos o arquivo de dispositivo em /dev AS /dev /cdrv_dev:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: Carregando o kernel do módulo fora da árvore.
[160.015688] CDRV: Falha na verificação do módulo: assinatura e/ou chave necessária que falta - kernel contaminante
[160.016173] init o driver de caracteres básico… iniciar
[160.016225] Classe de dispositivo CDRV registrada com sucesso
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Agora, execute o aplicativo de teste com o seguinte comando no shell Linux. A mensagem final imprime os dados de leitura do driver, exatamente o mesmo que escrevemos na operação de gravação:

root@haxv-srathore-2:/home/cienauser/kernel_articles# ./cdrv_app
bytes escritos = 10, dados = dados de teste
Leia bytes = 10, dados = dados de teste
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Temos poucas impressões adicionais no caminho de gravação e leitura que podem ser vistas com a ajuda do comando dmesg. Quando emitimos o comando DMESG, obtemos a seguinte saída:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: Carregando o kernel do módulo fora da árvore.
[160.015688] CDRV: Falha na verificação do módulo: assinatura e/ou chave necessária que falta - kernel contaminante
[160.016173] init o driver de caracteres básico… iniciar
[160.016225] Classe de dispositivo CDRV registrada com sucesso
[228.533614] CDRV: dispositivo aberto
[228.533620] Escrita: Bytes = 10
[228.533771] CDRV: dispositivo fechado
[228.533776] CDRV: dispositivo aberto
[228.533779] Leia: Bytes = 10
[228.533792] CDRV: dispositivo fechado
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Conclusão

Passamos pelo driver de personagem básico que implementa as operações básicas de gravação e leitura. Também discutimos a amostra Makefile para compilar o módulo junto com o aplicativo de teste. O aplicativo de teste foi escrito e discutido para executar as operações de gravação e leitura do espaço do usuário. Também demonstramos a compilação e a execução do módulo e do aplicativo de teste com logs. O aplicativo de teste escreve poucos bytes de dados de teste e depois o lê de volta. O usuário pode comparar os dados para confirmar o funcionamento correto do driver e o aplicativo de teste.