Controlador de dispositivo de caracteres de Linux (1) --- cdev, file_operations, inode, detalles de la estructura del archivo, cómo las aplicaciones de nivel superior pueden acceder al controlador subyacente

Materiales de referencia:

      "Introducción al desarrollo de controladores Linux y combate real", el concepto y el código fuente se refieren principalmente a "Introducción al desarrollo del controlador Linux y combate real" para mayor precisión. Al mismo tiempo, agradezco sinceramente a otros internautas por compartir. La mayor parte del contenido está mecanografiado a mano, corríjame por cualquier error u omisión, ¡gracias!

 Controlador de dispositivo de caracteres del controlador de dispositivo linux
 https://www.linuxprobe.com/linux-device-driver.html

 Estructura del controlador de dispositivo de caracteres de Linux (1) -estructura de cdev, análisis de conocimiento relacionado con el número de dispositivo
 https://blog.csdn.net/zqixiao_09/article/details/50839042


Prefacio


    Sin acumular silicio, un paso no puede llegar a miles de kilómetros, y sin acumular pequeños arroyos, no hay río.
    

1. El concepto de dispositivo de caracteres y dispositivo de bloque. Dispositivo de
    caracteres: se refiere a un dispositivo que solo puede leer y escribir datos un byte por un byte, y no puede leer aleatoriamente ciertos datos en la memoria del dispositivo. La lectura de datos debe seguir la secuencia de datos. Los dispositivos de caracteres son dispositivos orientados a la transmisión. Los dispositivos de caracteres comunes incluyen mouse, teclado, puerto serie, consola y dispositivos LED.

    Bloquear dispositivo: se refiere a un dispositivo que puede leer una cierta longitud de datos desde cualquier ubicación del dispositivo. Los datos leídos no tienen que estar en orden y pueden ubicarse en una ubicación específica del dispositivo. Los dispositivos de bloque incluyen discos duros, discos, discos U y tarjetas SD.

    Cada dispositivo de caracteres o dispositivo de bloque corresponde a un archivo de dispositivo en el directorio / dev. El programa de usuario de Linux usa el controlador para operar dispositivos de caracteres y bloquear dispositivos a través de archivos de dispositivo (o nodos de dispositivo).

2. La estructura cdev
    usa la estructura cdev para describir los dispositivos de caracteres en el kernel de Linux. Esta estructura es una abstracción de todos los dispositivos de caracteres y contiene características comunes a una gran cantidad de dispositivos de caracteres. Defina el número de dispositivo (dividido en números de dispositivo mayor y menor) a través de su miembro dev_t para determinar la singularidad del dispositivo de carácter. A través de su miembro file_operations para definir las funciones de interfaz proporcionadas por el controlador de dispositivo de caracteres al VFS, como open (), read (), write (), etc.

    1) cdevj 结构 体 :
    struct cdev {         struct kobject kobj;             estructura módulo * propietario;          struct file_operations * ops;         struct list_head list;             dev_t dev;         recuento de int sin firmar;                     } ;






  •     kobj: la estructura de kobject incorporada, utilizada para la gestión del modelo de controlador de dispositivo del kernel; generalmente no se utiliza.
  •     propietario: puntero al módulo que contiene la estructura, utilizado para el recuento de referencias. Por lo general, THIS_MODULE; la existencia del miembro propietario refleja la estrecha relación entre el controlador y el módulo del kernel. El módulo struct es la abstracción del kernel de un módulo. El miembro en el dispositivo de caracteres puede reflejar a qué módulo pertenece el dispositivo. En la escritura del controlador En general, el usuario lo inicializa explícitamente. Owner = THIS_MODULE.
  •     ops: puntero al conjunto de operaciones del dispositivo de caracteres. El conjunto de operaciones define las funciones de interfaz proporcionadas por el controlador del dispositivo de caracteres al VFS, como abrir (), leer (), escribir (), etc. En la función cdev_init (), está vinculada a la estructura cdev.
  •     dev: el número de dispositivo inicial del dispositivo de caracteres. Un dispositivo puede tener varios números de dispositivo.
  •     count: el número de dispositivos controlados por el dispositivo de caracteres; cuando se usa rmmod para desinstalar el módulo, si el miembro de count no es 0, el sistema no permite que se desinstale el módulo. Al miembro dev y al miembro count se le asignarán valores válidos en cdev_add;
  •     lista: esta estructura conecta los dispositivos de caracteres que utilizan este controlador en una lista vinculada. La estructura de la lista es una lista doblemente enlazada, que se utiliza para conectar otras estructuras en una lista doblemente enlazada. Esta estructura se usa ampliamente en el kernel de Linux y debe dominarse.

    struct list_head {         struct list_head * siguiente, * anterior;     };

    2) La relación entre cdev e inodo se
    muestra en la figura El miembro de lista de la estructura cdev está conectado al miembro i_devices de la estructura de inodo. Entre ellos, i_devices también es una estructura list_head. Haga que la estructura cdev y el nodo inodo formen una lista doblemente enlazada. La estructura de inodo representa los archivos de dispositivo en el directorio / dev.

                                                    

                                                                       Figura 1 La relación entre cdev e inodo
    Cada dispositivo de caracteres tiene un archivo de dispositivo en el directorio / dev. Abrir el archivo de dispositivo es equivalente a abrir el dispositivo de caracteres correspondiente. Por ejemplo, si la aplicación abre el archivo de dispositivo A, el sistema generará un nodo de inodo. De esta manera, la estructura de caracteres cdev se puede encontrar a través del campo i_cdev del nodo inodo. A través del puntero de operaciones de cdev, puede encontrar la función de operación del dispositivo A.
    
    Ejemplo: Construir la estructura del dispositivo cdev
    struct xxx_dev {         struct cdev cdev;         char * data;         struct semáforo sem;         ...     }; 3. Estructura File_operations





    
    

    Esta estructura es una de las estructuras más importantes en el dispositivo de caracteres. Los punteros de función miembro en la estructura file_operations son el contenido principal del diseño del controlador del dispositivo de caracteres. Estas funciones se utilizan realmente en la aplicación para Linux open (), read () , Write (), close (), seek (), ioctl () y otras llamadas al sistema finalmente son llamadas.

struct file_operations {     / * El recuento de módulos que poseen la estructura, generalmente THIS_MODULE * /     struct module * owner;     / * Se usa para modificar la posición actual de lectura y escritura del archivo * /     loff_t (* llseek) (struct file *, loff_t, int);     / * Leer datos sincrónicamente desde el dispositivo * /     ssize_t (* leer) (archivo de estructura *, char __user *, size_t, loff_t *);





    / * Escribe datos en el dispositivo * /
    ssize_t (* write) (archivo de estructura *, const char __user *, size_t, loff_t *);

    ssize_t (* aio_read) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    ssize_t (* aio_write) (struct kiocb *, const struct iovec *, unsigned long, loff_t);
    int (* readdir) (archivo de estructura *, void *, filldir_t);
   
    / * Función de sondeo para determinar si la lectura o escritura sin bloqueo es posible actualmente * /
    unsigned int (* poll) (struct file *, struct poll_table_struct *);

    / * Ejecutar comandos de E / S del dispositivo * /
    int (* ioctl) (struct inode *, struct file *, unsigned int, unsigned long);

    long (* unlocked_ioctl) (archivo de estructura *, unsigned int, unsigned long);
    long (* compat_ioctl) (archivo de estructura *, unsigned int, unsigned long);

    / * Se utiliza para solicitar asignar la memoria del dispositivo al espacio de direcciones del proceso * /
    int (* mmap) (archivo de estructura *, estructura vm_area_struct *);
 
    / * Abrir el archivo de dispositivo * /
    int (* abrir) (inodo de estructura *, archivo de estructura * );
    int (* flush) (archivo de estructura *, fl_owner_t id);
  
    / * Cerrar archivo de dispositivo * /
    int (* liberación) (estructura de inodo *, archivo de estructura *);

    int (* fsync) (archivo de estructura *, dentry de estructura *, sincronización de datos int);
    int (* aio_fsync) (struct kiocb *, int datasync);
    int (* fasync) (int, archivo de estructura *, int);
    int (* bloqueo) (estructura archivo *, int, estructura archivo_bloqueo *);
    ssize_t (* página de envío) (archivo de estructura *, página de estructura *, int, tamaño_t, loff_t *, int);
    unsigned long (* get_unmapped_area) (archivo de estructura *, unsigned long, unsigned long, unsigned long, unsigned long);
    int (* check_flags) (int);
    int (* flock) (struct archivo *, int, struct file_lock *);
    ssize_t (* splice_write) (struct pipe_inode_info *, archivo de estructura *, loff_t *, size_t, unsigned int);
    ssize_t (* splice_read) (archivo de estructura *, loff_t *, struct pipe_inode_info *, size_t, unsigned int);
    int (* setlease) (estructura archivo *, largo, estructura archivo_bloqueo **);
};

  • El miembro propietario es un puntero al módulo propietario de esta estructura. Este miembro se utiliza para mantener el recuento de referencias del módulo. Cuando el módulo todavía está en uso, el módulo no se puede desinstalar con rmmod. Generalmente inicializado en THIS_MODULE.
  • La función llseek () se utiliza para cambiar la posición actual de lectura / escritura en el archivo y devolver la nueva posición. El parámetro loff_t es de tipo "long long". El tipo "long long" "long long" tiene un ancho de 64 bits incluso en una máquina de 32 bits, por compatibilidad con máquinas de 64 bits.
  • La función read () se utiliza para obtener datos del dispositivo. La función devuelve el número de bytes leídos cuando tiene éxito y un código de error negativo cuando falla.
  • La función write () se utiliza para escribir datos en el dispositivo. La función devuelve el número de bytes escritos en caso de éxito y un código de error negativo en caso de error.
  • La función ioctl () proporciona una forma de ejecutar comandos específicos del dispositivo. Por ejemplo, para restablecer el dispositivo, esto no es una operación de lectura ni una operación de escritura, y no es adecuado para ser implementado con los métodos read () y write (). Si se pasa un comando indefinido a ioctl en la aplicación, se devolverá un error de -ENOTTY, lo que indica que el dispositivo no admite este comando.
  • open () se usa para abrir un dispositivo, y el dispositivo se puede inicializar en esta función. Si esta función se copia a NULL, el dispositivo siempre se abrirá correctamente y no afectará al dispositivo.
  • La función release () se utiliza para liberar los recursos solicitados en la función open () y el sistema la llamará cuando el recuento de referencias del archivo sea 0. Corresponde al método close () del programa de referencia, pero no cada vez que se llama al método close (), se lanzará la función release () y se llamará después de que se liberen todas las aperturas del archivo del dispositivo.


4. La relación entre la estructura cdev y la estructura file_operations:
    En términos generales, los desarrolladores de controladores colocarán los datos específicos de un dispositivo específico en la estructura cdev para formar una nueva estructura. Como se muestra en la siguiente figura, "El dispositivo de caracteres personalizados contiene los datos de un dispositivo específico. Este" dispositivo personalizado "contiene una estructura cdev. Hay un puntero a file_operations en la estructura cdev. De esta manera, las funciones en file_operations se pueden usar Para operar el dispositivo de caracteres u otros datos en el "dispositivo de caracteres personalizados", desempeñando así el papel de controlar el dispositivo.                      

                            

                                                                                 Figura 2


5. Estructura de
    inodo El kernel usa la estructura de inodo para representar archivos internamente, en realidad representa un cierto archivo en el hardware físico, y un archivo tiene solo un inodo correspondiente. Inode generalmente se pasa como un parámetro de una función en la estructura file_operations. Por ejemplo, la función open () pasará un puntero de inodo para indicar el nodo de archivo abierto actualmente. Cabe señalar que el sistema ha asignado valores apropiados a los miembros del inodo, y el controlador solo necesita usar la información en el nodo sin cambiarla.
    La función open () es: int (* open) (struct inode *, struct file *); La
    estructura del inodo contiene mucha información relacionada con el archivo. Aquí solo se introducen los campos útiles para escribir un controlador.
struct inode {     dev_t i_rdev; / * número de dispositivo * /     struct cdev * i_cdev; / * cdev es la estructura interna del kernel que representa el dispositivo de caracteres * /


    struct list_head i_devices;
};
    i_rdev: indica el número de dispositivo correspondiente al archivo de dispositivo.
    i_devices: como se muestra en la Figura 1, este miembro apunta al miembro i_devices de otros nodos de inodo. Forme una lista doblemente enlazada de struct list_head.
    i_cdev: este miembro apunta a la estructura cdev.
    
    
6. Estructura de
    archivo La estructura de archivo representa un archivo abierto y su característica es que un archivo puede corresponder a múltiples estructuras de archivo. Es creado por el kernel cuando se vuelve a abrir y pasa a todas las funciones que operan en el archivo. Hasta la función de cierre final, el kernel libera esta estructura de datos después de que se cierran todas las instancias del archivo.
    En el código fuente del kernel, el puntero al archivo struct generalmente se llama filp. La estructura del archivo tiene los siguientes miembros importantes:

struct file {     mode_t fmode; / * Modo de archivo, como FMODE_READ, FMODE_WRITE * /     ... loff_t     f_pos; / * loff_t f_pos indica la posición de lectura y escritura del archivo. loff_t es un número de 64 bits y debe convertirse a 32 bits cuando sea necesario. * /     unsigned int f_flags; / * Banderas de archivo, como: O_NONBLOCK * /     struct file_operations * f_op;     void * private_data; / * Muy importante, se utiliza para almacenar el puntero de estructura de descripción de dispositivo convertido * /     ... };







  •   mode_t f_mode: este modo de archivo identifica el archivo como legible, grabable o ambos a través de FMODE_READ, FMODE_WRITE. Es posible que deba marcar este campo en la función open o ioctl para confirmar el permiso de lectura / escritura del archivo. No es necesario que verifique directamente el permiso de lectura o escritura, porque el kernel mismo necesita verificar su permiso al realizar octl y otras operaciones.
  •   loff_t f_pos: la posición del archivo de lectura y escritura actual. Son 64 bits. Si desea conocer la ubicación actual del archivo actual, el controlador puede leer este valor sin cambiar su ubicación. Para lectura y escritura, cuando reciben un puntero loff_t como último parámetro, sus operaciones de lectura y escritura se utilizarán para actualizar la posición del archivo, sin la necesidad de realizar operaciones filp -> f_pos directamente. El propósito del método llseek es cambiar la ubicación del archivo.
  •   unsigned int f_flags: banderas de archivos, como O_RDONLY, O_NONBLOCK y O_SYNC. El conductor debe verificar el indicador O_NONBLOCK para ver si se solicita una operación sin bloqueo; rara vez se usan otros indicadores. En particular, cuando verifique los permisos de lectura / escritura, use f_mode en lugar de f_flags. Todos los indicadores se definen en el archivo de encabezado <linux / fcntl.h>.
  •     struct file_operations * f_op: Cuando el archivo necesita realizar varias operaciones rápidamente, el kernel asigna este puntero como parte de su realización de apertura, lectura, escritura y otras funciones del archivo. El kernel nunca ha guardado el valor de filp-> f_op como la siguiente referencia, es decir, puede cambiar varias operaciones relacionadas con el archivo, lo cual es muy eficiente.
  •   void * private_data: antes de que el controlador llame al método open, la llamada al sistema abierto establece este puntero en NULL. Puede usarlo como un campo de datos que necesite o ignorarlo. Por ejemplo, puede apuntar a un dato asignado, pero debe recordar liberarlos en el método de liberación antes de que el kernel destruya la estructura del archivo. Espacio de memoria de datos. Private_data es muy útil para guardar información de estado durante las llamadas al sistema
  •     struct dentry * f_dentry: la estructura de entrada de directorio (dentry) asociada con el archivo. Los escritores de controladores de dispositivos generalmente no necesitan preocuparse por la estructura de dentry, excepto para acceder a la estructura de inodo como filp-> f_dentry-> d_inode.

 

7. ¿Cómo accede la aplicación de la capa superior al controlador de la capa inferior?
    1) Cada dispositivo de caracteres xxx corresponde a un archivo de dispositivo / dev / xxx. Cuando usamos la función open ("/ dev / xxx", O_RDWR) para abrir un archivo de dispositivo. El sistema Linux asignará una estructura de archivo de estructura en la capa VFS para describir el archivo de dispositivo abierto.
    2) En el sistema Linux, cada archivo tiene una estructura de estructura de inodo para describir, esta estructura registra toda la información del archivo. Cuando se abre abre el archivo de dispositivo / dev / xxx, el sistema de archivos de Linux encontrará la estructura de estructura de inodo correspondiente al archivo de acuerdo con el nombre del archivo. La estructura registra el número de dispositivo dev_t i_rdev del dispositivo xxx, el tipo de dispositivo mode_t imode y la estructura struct cdev i_cdev que describe el dispositivo de caracteres. Si imode es un tipo de dispositivo de caracteres, vaya al mapa cdev del dispositivo de caracteres y busque la estructura correspondiente i_cdev que describe el dispositivo de caracteres de acuerdo con el número de dispositivo i_rdev. Devuelve la primera dirección de la estructura i_cdev al miembro i_cdev en la estructura del inodo.
    3) El conjunto de funciones de operación del dispositivo de caracteres se registra en la estructura i_cdev del dispositivo de caracteres, es decir, la estructura struct file_operations xxx_ops. Todas las funciones de operación correspondientes a los dispositivos de caracteres se definen en esta estructura. Copie la dirección de la estructura const struct file_operations xxx_ops y asígnela al puntero de la estructura const struct file_operations * f_op de la estructura del archivo struct de la capa VFS.
    4) La capa VFS devuelve un descriptor de archivo (fd) a la capa de aplicación. Este fd corresponde a la estructura del archivo struct. Luego, la aplicación superior puede encontrar el archivo de estructura a través de fd, y luego encontrar la interfaz de función para operar el dispositivo de caracteres mediante el archivo de estructura.
    5) La capa de aplicación puede usar fd para llamar a las funciones read (), write () y ioctl () para operar el dispositivo.

 

Supongo que te gusta

Origin blog.csdn.net/the_wan/article/details/108433530
Recomendado
Clasificación