Controlador de caracteres básico en Linux

Controlador de caracteres básico en Linux
Pasaremos por la forma de Linux de implementar el controlador de caracteres. Primero intentaremos comprender qué es el controlador de caracteres y cómo el marco de Linux nos permite agregar el controlador de caracteres. Después de eso, haremos la aplicación de espacio de usuario de prueba de muestra. Esta aplicación de prueba utiliza el nodo del dispositivo expuesto por el controlador para escribir y leer los datos de la memoria del núcleo.

Descripción

Comencemos la discusión con el controlador de personajes en Linux. Kernel clasifica a los controladores en tres categorías:

Conductores de personajes - Estos son los controladores que no tienen demasiado datos para tratar. Pocos ejemplos de controladores de personajes son el controlador de pantalla táctil, el controlador UART, etc. Todos estos son los controladores de caracteres, ya que la transferencia de datos se realiza a través del carácter.

Bloquear a los conductores - Estos son los impulsores que tratan con demasiados datos. La transferencia de datos se realiza BLOCK por bloque ya que se deben transferir demasiados datos. El ejemplo de los controladores de bloques son SATA, NVME, etc.

Conductores de red - Estos son los controladores que funcionan en el grupo de redes de redes de redes. Aquí, la transferencia de datos se realiza en forma de paquetes de datos. Los conductores inalámbricos como Atheros se encuentran en esta categoría.

En esta discusión, nos centraremos solo en el controlador de personajes.

Como ejemplo, tomaremos las simples operaciones de lectura/escritura para comprender el controlador de caracteres básico. En general, cualquier controlador de dispositivo tiene estas dos operaciones mínimas. La operación adicional podría ser abierta, cierre, ioctl, etc. En nuestro ejemplo, nuestro controlador tiene la memoria en el espacio del núcleo. Esta memoria es asignada por el controlador del dispositivo y puede considerarse como la memoria del dispositivo, ya que no hay componente de hardware involucrado. El controlador crea la interfaz del dispositivo en el directorio /dev Directory que puede ser utilizado por los programas de espacio de usuario para acceder al controlador y realizar las operaciones admitidas por el controlador. Para el programa UserSapace, estas operaciones son como cualquier otra operación de archivo. El programa de espacio de usuario debe abrir el archivo del dispositivo para obtener la instancia del dispositivo. Si el usuario desea realizar la operación de lectura, la llamada del sistema de lectura se puede usar para hacerlo. Del mismo modo, si el usuario desea realizar la operación de escritura, la llamada del sistema de escritura se puede utilizar para lograr la operación de escritura.

Conductor de personaje

Consideremos implementar el controlador de caracteres con las operaciones de datos de lectura/escritura.

Comenzamos tomando la instancia de los datos del dispositivo. En nuestro caso, es "struct cdrv_device_data".

Si vemos los campos de esta estructura, tenemos CDEV, búfer de dispositivos, tamaño de búfer, instancia de clase y objeto de dispositivo. Estos son los campos mínimos donde debemos implementar el controlador de caracteres. Depende del implementador en el que los campos adicionales quieran agregar para mejorar el funcionamiento del controlador. Aquí, tratamos de lograr el funcionamiento mínimo.

A continuación, debemos crear el objeto de la estructura de datos del dispositivo. Utilizamos la instrucción para asignar la memoria de manera estática.

struct cdrv_device_data char_device [cdrv_max_minors];

Esta memoria también se puede asignar dinámicamente con "Kmalloc". Mantengamos la implementación lo más simple posible.

Deberíamos tomar la implementación de las funciones de lectura y escritura. El prototipo de estas dos funciones está definido por el marco del controlador del dispositivo de Linux. La implementación de estas funciones debe ser definida por el usuario. En nuestro caso, consideramos lo siguiente:

LEA: La operación para obtener los datos de la memoria del controlador al espacio de usuarios.

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

Escribir: la operación para almacenar los datos en la memoria del controlador desde el espacio de usuarios.

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

Ambas operaciones, leen y escriben, deben registrarse como parte de Struct File_Operations CDRV_FOPS. Estos están registrados en el marco del controlador del dispositivo Linux en el init_cdrv () del controlador. Dentro de la función init_cdrv (), todas las tareas de configuración se realizan. Pocas tareas son las siguientes:

  • Crear clase
  • Crear instancia de dispositivo
  • Asignar un número mayor y menor para el nodo del dispositivo

El código de ejemplo completo para el controlador de dispositivo de caracteres básico es el siguiente:

#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
struct CDEV CDEV;
Char Buffer [buf_len];
size_t size;
struct class* cdrv_class;
dispositivo struct* cdrv_dev;
;
struct cdrv_device_data char_device [cdrv_max_minors];
static ssize_t cdrv_write (archivo struct *archivo, 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, size);
printk ("Escribir: bytes =%d \ n", tamaño);
if (len buffer + *offset, user_buffer, len))
regreso -fault;
*offset += len;
devolver len;

static ssize_t cdrv_read (archivo struct *archivo, 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, size);
if (len buffer + *offset, len))
regreso -fault;
*offset += len;
printk ("leer: bytes =%d \ n", tamaño);
devolver len;

static int cdrv_open (struct inode *inode, struct archivo *archivo)
printk (kern_info "cdrv: dispositivo abierto \ n");
regresar 0;

static int cdrv_release (struct inode *inode, struct archivo *archivo)
printk (kern_info "CDRV: dispositivo cerrado \ n");
regresar 0;

const struct file_operations cdrv_fops =
.propietario = this_module,
.Abrir = CDRV_OPEN,
.leer = cdrv_read,
.escribir = cdrv_write,
.Release = CDRV_RELEASE,
;
int init_cdrv (void)

int count, ret_val;
printk ("init el controlador de caracteres básico ... inicio \ n");
ret_val = registro_chrdev_region (mkdev (cdrv_major, 0), cdrv_max_minors,
"CDRV_DEVICE_DRIVER");
if (ret_val != 0)
printk ("register_chrdev_region (): falló con código de error:%d \ n", ret_val);
return ret_val;

para (contar = 0; contar < CDRV_MAX_MINORS; count++)
CDEV_INIT (& char_device [cuenta].CDEV, & CDRV_FOPS);
CDEV_ADD (& char_device [cuenta].CDEV, MKDEV (CDRV_Major, Count), 1);
char_device [recuento].cdrv_class = class_create (this_module, cdrv_class_name);
if (is_err (char_device [cuenta].cdrv_class))
printk (kern_alert "CDRV: registro de la clase del dispositivo fallido \ n");
return ptr_err (char_device [count].cdrv_class);

char_device [recuento].size = buf_len;
printk (kern_info "clase de dispositivo CDRV registrada correctamente \ n");
char_device [recuento].cdrv_dev = dispositivo_create (char_device [count].cdrv_class, null, mkdev (cdrv_major, count), null, cdrv_device_name);

regresar 0;

Void Cleanup_cdrv (void)

int cuenta;
para (contar = 0; contar < CDRV_MAX_MINORS; count++)
dispositivo_destroy (char_device [count].CDRV_CLASS, & char_device [cuenta].cdrv_dev);
class_destroy (char_device [cuenta].cdrv_class);
CDEV_DEL (& char_device [cuenta].CDEV);

unregister_chrdev_region (mkdev (cdrv_major, 0), cdrv_max_minors);
printk ("Salir del controlador de caracteres básico ... \ n");

módulo_init (init_cdrv);
MODULE_EXIT (CleanUp_cdrv);
MODULE_LICENSE ("GPL");
MODULE_AUTHOR ("Sushil Rathore");
Módulo_description ("controlador de caracteres de muestra");
Módulo_version ("1.0 ");

Creamos una muestra de makefile para compilar el controlador de caracteres básico y la aplicación de prueba. Nuestro código de controlador está presente en CRDV.C y el código de la aplicación de prueba está presente en CDRV_APP.C.

obj-m+= cdrv.O
todo:
Módulos de make -c/lib/modules/$ (shell uname -r)/build/m = $ (pwd)
$ (Cc) cdrv_app.c -o cdrv_app
limpio:
hacer -c/lib/módulos/$ (shell uname -r)/build/m = $ (pwd) limpio
RM CDRV_APP
~

Después de que se realice la emisión al File, debemos obtener los siguientes registros. También obtenemos el CDRV.KO y ejecutable (CDRV_APP) para nuestra aplicación de prueba:

root@haxv-srathore-2:/home/cienauser/kernel_articles# make
hacer -c/lib/módulos/4.15.0-197-Generic/Build/M =/Home/Cienauser/Kernel_ARTicles Módulos
hacer [1]: ingresar directorio '/usr/src/linux-headers-4.15.0-197-Genérico '
CC [m]/home/cienauser/kernel_articles/cdrv.O
Módulos de construcción, etapa 2.
Módulos modpost 1
Cc/home/cienauser/kernel_articles/cdrv.modificación.O
Ld [m]/home/cienauser/kernel_articles/cdrv.KO
hacer [1]: dejar el directorio '/usr/src/linux-headers-4.15.0-197-Genérico '
CC CDRV_APP.c -o cdrv_app

Aquí está el código de muestra para la aplicación de prueba. Este código implementa la aplicación de prueba que abre el archivo de dispositivo creado por el controlador CDRV y le escribe los "datos de prueba". Luego, lee los datos del controlador y los imprime después de leer los datos para imprimir como "datos de prueba".

#incluir
#incluir
#define dispositivo_file "/dev/cdrv_dev"
char *data = "Datos de prueba";
char read_buff [256];
int main ()

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

Perror ("Archivo de apertura: \ n");
regreso -1;

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

Perror ("Escribir archivo: \ n");
regreso -1;

printf ("escrito bytes =%d, data =%s \ n", rc, data);
cerrar (fd);
fd = open (dispositivo_file, o_rdonly);
Si (FD<0)

Perror ("Archivo de apertura: \ n");
regreso -1;

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

perror ("archivo de lectura: \ n");
regreso -1;

printf ("leer bytes =%d, data =%s \ n", rc, read_buff);
cerrar (fd);
regresar 0;

Una vez que tenemos todas las cosas en su lugar, podemos usar el siguiente comando para insertar el controlador de caracteres básico en el núcleo de Linux:

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

Después de insertar el módulo, recibimos los siguientes mensajes con DMESG y obtenemos el archivo del dispositivo creado en /dev AS /dev /cdrv_dev:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: Carga de módulos de módulo de no debilitación núcleo.
[160.015688] CDRV: VERIFICACIÓN DEL MÓDULO Fallado: Falta de la firma y/o la tecla requerida - Tainting Kernel
[160.016173] Init El controlador de caracteres básico ... comenzar
[160.016225] Clase de dispositivo CDRV registrada correctamente
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Ahora, ejecute la aplicación de prueba con el siguiente comando en el shell de Linux. El mensaje final imprime los datos de lectura del controlador, que es exactamente lo mismo que escribimos en la operación de escritura:

root@haxv-srathore-2:/home/cienauser/kernel_articles# ./cdrv_app
bytes escritos = 10, datos = datos de prueba
Leer bytes = 10, datos = datos de prueba
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Tenemos pocas impresiones adicionales en la ruta de escritura y lectura que se puede ver con la ayuda del comando dmesg. Cuando emitimos el comando DMESG, obtenemos el siguiente resultado:

root@haxv-srathore-2:/home/cienauser/kernel_articles# dmesg
[160.015595] CDRV: Carga de módulos de módulo de no debilitación núcleo.
[160.015688] CDRV: VERIFICACIÓN DEL MÓDULO Fallado: Falta de la firma y/o la tecla requerida - Tainting Kernel
[160.016173] Init El controlador de caracteres básico ... comenzar
[160.016225] Clase de dispositivo CDRV registrada correctamente
[228.533614] CDRV: dispositivo abierto
[228.533620] Escritura: bytes = 10
[228.533771] CDRV: dispositivo cerrado
[228.533776] CDRV: dispositivo abierto
[228.533779] Leer: bytes = 10
[228.533792] CDRV: dispositivo cerrado
root@haxv-srathore-2:/home/cienauser/kernel_articles#

Conclusión

Hemos pasado por el controlador de caracteres básico que implementa las operaciones básicas de escritura y lectura. También discutimos la muestra MakeFile para compilar el módulo junto con la aplicación de prueba. La aplicación de prueba fue escrita y discutida para realizar las operaciones de escritura y lectura del espacio de usuario. También demostramos la compilación y ejecución del módulo y la aplicación de prueba con registros. La aplicación de prueba escribe pocos bytes de datos de prueba y luego lo lee hacia atrás. El usuario puede comparar ambos datos para confirmar el funcionamiento correcto del controlador y la aplicación de prueba.