Notas de estudio de desarrollo de controladores de Linux [9]: E / S de bloqueo y no bloqueo de Linux

Tabla de contenido

Uno, E / S de bloqueo y no bloqueo

Dos, métodos de bloqueo y no bloqueo de aplicaciones

Tres, cola de espera

1. Espera al jefe de la cola

2. Esperando elementos en cola

3. Elemento de la cola para agregar o quitar el encabezado de la cola de espera

4. Espere a que se despierte

5. Esperando el evento

Cuatro, sondeo

1. La función de selección

2. Función Pol

3. función epoll

Cinco, función de operación de encuesta bajo el controlador de Linux

Seis, ejemplo de escritura de programa IO de bloqueo

1. El método de esperar el evento

2. La forma de agregar elementos de la cola de espera

Siete, ejemplo de escritura de programa IO sin bloqueo


Uno, E / S de bloqueo y no bloqueo

El IO aquí se refiere a Entrada / Salida, es decir, entrada / salida, que es la operación de entrada / salida de la aplicación en el dispositivo de accionamiento. Cuando la aplicación está operando en el controlador de dispositivo, si no se puede obtener el recurso del dispositivo, el IO de bloqueo suspenderá el hilo correspondiente a la aplicación hasta que se pueda obtener el recurso del dispositivo. Para E / S sin bloqueo, el hilo correspondiente a la aplicación no se bloqueará, seguirá sondeando y esperando hasta que los recursos del dispositivo estén disponibles, o simplemente se dará por vencido.

La mayor ventaja de bloquear el acceso es que el proceso puede entrar en estado inactivo cuando el archivo del dispositivo no funciona, de modo que se pueden liberar los recursos de la CPU. Sin embargo, cuando se puede operar el archivo de dispositivo, el proceso debe ser despertado, generalmente el trabajo de despertar se completa en la función de interrupción. El kernel de Linux proporciona una cola de espera para realizar el trabajo de activación de los procesos bloqueados

1. Diagrama de bloqueo de E / S:

 

2. Diagrama esquemático de E / S sin bloqueo:

Dos, métodos de bloqueo y no bloqueo de aplicaciones

Método de bloqueo:

    /*open*/
    fd = open(filename, O_RDWR);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Manera sin bloqueo:

    /*open*/
    fd = open(filename, O_RDWR | O_NONBLOCK);
    if (fd < 0){
        printf("open %s failed!!\r\n", filename);
        return -1;
    }
    ret = read(fd, &data, sizeof(data));

Tres, cola de espera

1. Espera al jefe de la cola

Para usar una cola de espera en el controlador, se debe crear e inicializar un encabezado de cola de espera. El encabezado de la cola de espera está representado por la estructura wait_queue_head_t . La estructura wait_queue_head_ t se define en el archivo include / linux / wait.h. El contenido de la estructura es el siguiente:

struct __wait_queue_head {
    spinlock_t lock;
    struct list_head task_list;
};
typedef struct __wait_queue_head wait_queue_head_t;

Después de definir el encabezado de la cola de espera, es necesario inicializarlo. Utilice la función init_waitqueue_head para inicializar el encabezado de la cola de espera

void init_waitqueue_head(wait_queue_head_t *q)

2. Esperando elementos en cola

La cabeza de la cola de espera es la cabeza de una cola de espera. Cada proceso que accede al dispositivo es un elemento de la cola . Cuando el dispositivo no está disponible, los elementos de la cola de espera correspondientes a estos procesos deben agregarse a la cola de espera. Utilice la macro DECLARE_WAITQUEUE para definir e inicializar un elemento de la cola de espera. El contenido de la macro es el siguiente:

DECLARE_WAITQUEUE(name, tsk)

nombre es el nombre del elemento de cola de espera, tsk indica qué tarea (proceso) de este elemento de cola de espera pertenece, y por lo general se establece en la corriente . En el núcleo de Linux, la corriente es equivalente a una variable global, que representa el proceso actual . Por lo tanto, la macro DECLARE_WAITQUEUE crea e inicializa un elemento de la cola de espera para el proceso en ejecución.

3. Elemento de la cola para agregar o quitar el encabezado de la cola de espera

Cuando el dispositivo es inaccesible, el elemento de la cola de espera correspondiente al proceso debe agregarse a la cabecera de la cola de espera creada anteriormente, y el proceso puede entrar en el estado de suspensión solo después de agregarse a la cabecera de la cola de espera. Cuando se pueda acceder al dispositivo, elimine el elemento de la cola de espera correspondiente al proceso del encabezado de la cola de espera.

(1) Agregue la API de elemento de cola en espera:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : El encabezado de la cola de espera donde se agregará el elemento de la cola de espera.

esperar : el elemento de la cola de espera que se agregará.

(2) Elimine la API de elementos de la cola de espera:

void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait)

q : El encabezado de la cola de espera donde se agregará el elemento de la cola de espera.

esperar : el elemento de la cola de espera que se eliminará.

4. Espere a que se despierte

Cuando el dispositivo está listo para su uso, es necesario reactivar el proceso que ha entrado en el estado de suspensión. Las siguientes dos funciones se pueden utilizar para reactivar:

void wake_up(wait_queue_head_t *q) 
void wake_up_interruptible(wait_queue_head_t *q)

El parámetro q es la cabecera de la cola de espera para ser despertados Estas dos funciones despertarán todos los procesos en la cabecera de la cola de espera. La función wake_up puede despertar los procesos en los estados TASK_INTERRUPTIBLE y TASK_UNINTERRUPTIBLE, mientras que la función wake_up_interruptible solo puede despertar los procesos en el estado TASK_INTERRUPTIBLE.

5. Esperando el evento

Además de despertar activamente, también puede configurar la cola de espera para esperar un evento. Cuando se cumple este evento, el proceso en la cola de espera se activará automáticamente. Las funciones de la API relacionadas con el evento en espera son las siguientes:

esperar_evento (wq, condición)

Esperando que se despierte la cola de espera con wq como cabecera de la cola de espera, siempre que se deba cumplir la condición (verdadero), de lo contrario se ha bloqueado. Esta función establecerá el proceso en el estado TASK_UNINTERRUPTIBLE

wait_event_timeout (wq, condición, tiempo de espera)

La función es similar a wait_event, pero esta función puede agregar un período de tiempo de espera en jiffies. Esta función tiene un valor de retorno. Si devuelve 0, significa que el tiempo de espera ha terminado y la condición es falsa. Si es 1, la condición es verdadera, es decir, se cumple la condición.

wait_event_interruptible (wq, condición)

Similar a la función wait_event, pero esta función establece el proceso en TASK_INTERRUPTIBLE, lo que significa que puede ser interrumpido por una señal.

wait_event_interruptible_timeout (wq, condición, tiempo de espera)

Similar a la función wait_event_timeout, esta función también establece el proceso en TASK_INTERRUPTIBLE, que puede ser interrumpido por una señal.

Cuatro, sondeo

Si la aplicación de usuario accede al dispositivo de forma no bloqueante, el controlador de dispositivo debe proporcionar un método de procesamiento sin bloqueo, es decir, sondeo . Poll, epoll y select se pueden usar para procesar el sondeo. La aplicación usa la función select, epoll o poll para consultar si el dispositivo se puede operar, y si se puede operar, lee desde el dispositivo o escribe datos en el dispositivo. Cuando la aplicación llama a la función select, epoll o poll, se ejecutará la función poll en el controlador del dispositivo, por lo que es necesario escribir la función poll en el controlador del dispositivo. Veamos primero las funciones select, epoll o poll utilizadas en la aplicación

1. La función de selección

int select(int nfds, fd_set *readfds, fd_set *writefds, fd_set *exceptfds, struct timeval *timeout)

nfds : Entre los tres tipos de conjuntos de descripciones de archivos que se supervisarán, el descriptor de archivo más grande más uno.

readfds, writefds y exceptfds : estos tres punteros apuntan al conjunto de descriptores. Estos tres parámetros indican qué descriptores están relacionados, qué condiciones deben cumplirse, etc., estos tres parámetros son de tipo fd_set, y cada bit de la variable de tipo fd_set Ambos representan un descriptor de archivo. readfds se usa para monitorear los cambios de lectura del conjunto de descriptores especificado, es decir, para monitorear si estos archivos se pueden leer. Siempre que haya un archivo en estos conjuntos que se pueda leer, seclect devolverá un valor mayor que 0 para indicar que el archivo se puede leer. Si no hay ningún archivo para leer, determinará si se agotó el tiempo de espera según el parámetro de tiempo de espera. Puede establecer readfs en NULL, lo que significa que no le importan los cambios de lectura de archivos. writefds es similar a readfs, excepto que writefs se usa para monitorear si estos archivos se pueden escribir. exceptfds se utiliza para controlar las anomalías de estos archivos.

Por ejemplo, si queremos leer datos de un archivo de dispositivo, podemos definir una variable fd_set , que debe pasarse al parámetro readfds. Cuando definimos una variable fd_set, podemos usar las siguientes macros para operar:

void FD_ZERO(fd_set *set) 
void FD_SET(int fd, fd_set *set) 
void FD_CLR(int fd, fd_set *set) 
int FD_ISSET(int fd, fd_set *set)

2. Función Pol

En un solo hilo, el número de descriptores de archivo que la función de selección puede monitorear tiene el límite máximo, generalmente 1024. Puede modificar el kernel para aumentar el número de descriptores de archivo que se pueden monitorear, ¡pero esto reducirá la eficiencia! En este momento, puede utilizar la función de encuesta. La función de encuesta no es esencialmente diferente de seleccionar, pero la función de encuesta no tiene un límite máximo de descriptores de archivos.

int poll(struct pollfd *fds, nfds_t nfds, int timeout)

El conjunto de descriptores de archivo que debe supervisar fds y los eventos que se van a supervisar son una matriz, y los elementos de la matriz son todas estructuras

struct pollfd {

     int fd; / * descriptor de archivo * /

     eventos cortos; / * evento solicitado * /

     revents cortos; / * Eventos devueltos * /

};

Los eventos son los eventos que se deben monitorear. Los tipos de eventos que se pueden monitorear son los siguientes:

POLLIN tiene datos para leer.

POLLPRI tiene datos urgentes para leer.

POLLOUT puede escribir datos.

Se produjo un error en el descriptor de archivo especificado por POLLERR.

El descriptor de archivo especificado por POLLHUP está suspendido.

Solicitud de POLLNVAL no válida.

POLLRDNORM es equivalente a POLLIN

nfds : el número de descriptores de archivos que la función de sondeo supervisará.

timeout : período de tiempo de espera, en ms.

3. función epoll

Las funciones tradicionales selcet y poll sufrirán de ineficiencia a medida que aumente el número de fd monitoreados, y la función poll debe atravesar todos los descriptores cada vez para verificar los descriptores listos, lo cual es una pérdida de tiempo. Por esta razón, nació epoll, que está preparada para manejar una gran concurrencia y las funciones de epoll se utilizan a menudo en la programación de redes. La aplicación necesita usar la función epoll_create para crear un identificador epoll primero

int epoll_create(int size)

Cinco, función de operación de encuesta bajo el controlador de Linux

Cuando la aplicación llama a la función de selección o encuesta para realizar un acceso sin bloqueo al controlador, se ejecuta la función de encuesta en el conjunto de operaciones file_operations del controlador. Por lo tanto, el escritor del controlador debe proporcionar la función de encuesta correspondiente. El prototipo de la función de encuesta es el siguiente:

unsigned int (*poll) (struct file *filp, struct poll_table_struct *wait)

filp : el archivo de dispositivo (descriptor de archivo) que se abrirá.

esperar : puntero a la estructura poll_table_struct, pasada por la aplicación. Generalmente, este parámetro se pasa a la función poll_wait.

Seis, ejemplo de escritura de programa IO de bloqueo

https://github.com/denghengli/linux_driver/tree/master/15_blockio

1. El método de esperar el evento

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
    /*等待等待队列中的 reles_value 事件,使访问的线程进入阻塞*/
    wait_event_interruptible(dev->r_wait, reles_value);
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

2. La forma de agregar elementos de la cola de espera

#include <linux/wait.h> 
#include <linux/ide.h> 

/*定时器回调函数,在设备资源可用的时候唤醒等待队列中的线程*/
static void timer_func(unsigned long arg)
{
    struct key_dev *dev = (struct key_dev*)arg;
    ...
    wake_up(&dev->r_wait);/*唤醒等待队列中的线程*/
}
static  ssize_t key_read(struct file *filp, char __user *buf, size_t cnt, loff_t *offt)
{
    ...
	/*定义并初始化一个等待队列项*/
	DECLARE_WAITQUEUE(wait, current);
	if (reles_value == 0){
        add_wait_queue(&dev->r_wait, &wait);//将队列项加入等待队列
        __set_current_state(TASK_INTERRUPTIBLE);/* 设置任务状态,设置为可被打断的状态,可以接受信号 */
        schedule(); /* 进行一次任务切换 */
        
        /*等待被唤醒,可能被信号和可用唤醒*/
        if(signal_pending(current)) { /* 判断是否为信号引起的唤醒 */
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
        return -ERESTARTSYS;
        }
        /*可用设备唤醒*/
        __set_current_state(TASK_RUNNING); /*设置为运行状态 */
        remove_wait_queue(&dev->r_wait, &wait); /*将等待队列移除 */
	}
    ...
    return ret;
}
static int key_open(struct inode *inode, struct file *filp)
{ 
    ...
    /*初始化等待队列头*/  
    init_waitqueue_head(&dev->r_wait);
    return 0;
}

Siete, ejemplo de escritura de programa IO sin bloqueo

https://github.com/denghengli/linux_driver/tree/master/16_noblockio

 

 

Supongo que te gusta

Origin blog.csdn.net/m0_37845735/article/details/107307000
Recomendado
Clasificación