Controlador avanzado de Linux (2): mecanismos de bloqueo y sincronización en controladores de dispositivos


prefacio

El bloqueo y el no bloqueo son dos formas básicas de acceso al dispositivo. Con estos dos métodos, el controlador puede admitir de manera flexible el acceso con y sin bloqueo. Al escribir controladores de bloqueo y de no bloqueo, a menudo se utilizan colas de espera, por lo que este capítulo brindará una breve introducción a las colas de espera.

bloqueo y no bloqueo

阻塞调用Significa que el hilo actual se suspenderá antes de que se devuelva el resultado de la llamada. La función no regresa hasta que tiene un resultado. Algunas personas pueden equiparar el bloqueo de llamadas con llamadas síncronas, pero en realidad son diferentes. Para llamadas sincrónicas, el hilo actual sigue activo en muchos casos, pero lógicamente la función actual no regresa.
El concepto de no bloqueo corresponde a bloqueo, lo que significa que la función no bloqueará el hilo actual antes de que el resultado no se pueda obtener de inmediato, sino que regresará de inmediato. El hecho de que el objeto esté en modo de bloqueo tiene una fuerte correlación con el hecho de que la función esté bloqueando llamadas, pero no existe una correspondencia uno a uno. Puede haber un método de llamada sin bloqueo en el objeto de bloqueo. Podemos sondear el estado a través de una determinada API y llamar a la función de bloqueo en el momento adecuado para evitar el bloqueo. Para objetos que no son de bloqueo, llamar a una función especial también puede generar una llamada de bloqueo. Las funciones select()son un ejemplo de esto. El siguiente es select()un ejemplo de cómo llamar a una función en un bloque.

void main()
{
    
    
	FILE *fp;
	struct fd_set fds;
	struct timeval timeout={
    
    4, 0}; //select()函数等待4s,4s后轮询
	char buffer[256]={
    
    0};  //256字节的缓冲区
	fp = fopen(....);     //打开文件
 	while(1)
 	{
    
    
		FD_ZERO(&fds);  //清空集合
		FD_SET(fp, &fds);  //同上
		maxfdp=fp+1;   //描述符最大值加1
		switch(select(maxfdp, &fds, &fds, NULL, &timeout)) //select函数使用
		{
    
    
			case -1:
				exit(-1);
				break;   //select()函数错误,退出程序
			case 0:
				break;  //再次轮询
			default:
				if(FD_ISSET(fp, &fds)) //判断是否文件中有数据
				{
    
    
					read(fds, buffer, 256, ...); //接受文件数据
					if(FD_ISSET(fp, &fds)) //测试文件是否可写
					fwrite(fp, buffer...); //写入文件buffer清空
				}
		}
	}
}

cola de espera

Esta sección presentará el mecanismo de cola de espera comúnmente utilizado en la programación de controladores. Este mecanismo hace que el proceso de espera duerma temporalmente, y cuando llega la señal de espera, despierta el proceso en la cola de espera para continuar con la ejecución. Esta sección detalla el contenido de la cola de espera.

Descripción general de la cola de espera

En los controladores de Linux, los procesos de bloqueo se pueden 等待队列(Wait Queue)lograr usando . Debido a que la cola de espera es muy útil, en la era de Linux2.0, se introdujo el mecanismo de cola de espera. La estructura de datos básica de la cola de espera es one 双向链表, y esta lista enlazada almacena procesos durmientes. La cola de espera también está estrechamente integrada con el mecanismo de programación de procesos y se puede utilizar para implementar el mecanismo de notificación de eventos asincrónicos en el kernel. Las colas de espera se pueden utilizar para sincronizar el acceso a los recursos del sistema. Por ejemplo, cuando se hace un trabajo, no se permite hacer otro trabajo.
En el núcleo, las colas de espera son útiles, especialmente en el procesamiento de interrupciones, la sincronización de procesos, el tiempo y otras ocasiones. La reactivación de un proceso bloqueado se puede realizar mediante una cola de espera. Utiliza colas como estructura de datos básica y está estrechamente integrado con el mecanismo de programación de procesos.Se puede utilizar para implementar el mecanismo de notificación de eventos asincrónicos en el núcleo y sincronizar el acceso a los recursos del sistema.

Implementación de cola de espera.

Dependiendo de la plataforma, los códigos de instrucciones que proporciona son diferentes, por lo que la implementación de la cola de espera también es diferente. En Linux, la definición de la cola de espera se muestra en el siguiente código.

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

Cada variable miembro de esta estructura se presenta en detalle a continuación.
1.lock自旋锁
La función del bloqueo giratorio de bloqueo es muy simple y se utiliza para task_listproteger la lista enlazada. Al task_lsitagregar o eliminar elementos de la lista vinculada, el bloqueo se bloqueará dentro del kernel y se liberará cuando se complete la modificación. Es decir, el lock spin lock task_lsitrealiza un acceso exclusivo a la cola de espera durante la operación AND.
2.task_list变量
task_listEs una lista enlazada circular bidireccional que se utiliza para almacenar procesos de espera.

Uso de colas de espera.

En Linux, el tipo de cola de espera es struct wait_queue_head_t. El núcleo proporciona una serie de funciones para struct wait_queue_head_toperar. A continuación se presentará brevemente el método de operación de la cola de espera.
1.定义和初始化等待队列头
En Linux, el método para definir la cola de espera es el mismo que el método para definir una estructura común, y el método de definición es el siguiente:

struct wait_queue_head_t wait;

Una cola de espera debe inicializarse antes de que pueda usarse. init_waitqueue_head()La función se usa para inicializar una cola de espera y su forma de código es la siguiente:

#define DECLARE_WAIT_QUEUE_HEAD(name) \
	wait_queue_head_t name = __WAIT_QUEUE_HEAD_INITALIZER(name)

2.定义等待队列
En el kernel de Linux se menciona una macro para definir la cola de espera, el código de esta macro es el siguiente:

#define DECLARE_WAITQUEUE(name, tsk) \wait_queue_t name = __WAITQUEUE_INITIALIZER(name, tsk)

Esta macro se usa para definir e inicializar una namecola de espera llamada .
3.添加和移除等待队列
El kernel de Linux proporciona dos funciones para agregar y eliminar colas. Las definiciones de estas dos funciones son las siguientes:

void add_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);
void remove_wait_queue(wait_queue_head_t *q, wait_queue_t *wait);

add_wait_queue()La función se utiliza para agregar el elemento de la cola de espera wait a la lista enlazada de la cola de espera a la que apunta el encabezado q de la cola de espera. La función opuesta es que remove_wait_queue()esta función se utiliza para eliminar el elemento de cola espera de la cola de espera a la que apunta la cola de espera q.
4.等待事件
Algunas macros se proporcionan en el kernel de Linux para esperar los eventos correspondientes. Estas macros se definen de la siguiente manera:

#define wait_event(wq, condition)
#define wait_event_timeout(wq, condition, ret)
#define wait_event_interruptible(wq, condition, ret)
#define wait_event_interruptible_timeout(wq, condition, ret)
  • wait_eventLa función de la macro es dormir en la cola de espera hasta que conditionsea verdadera. Durante el período de espera, el proceso se suspenderá TASK_UNINTERRUPTIBLEhasta que conditionla variable se vuelva verdadera. Valor que se comprueba cada vez que se despierta el proceso condition.
  • wait_event_timeoutMacro es wait_eventsimilar, pero regresa inmediatamente si el tiempo de suspensión dado es negativo. conditionDevuelve el tiempo de sueño restante si se despierta durante el sueño y devuelve 0 de lo contrario, continúa durmiendo hasta que se alcanza o supera el tiempo de sueño dado.
  • wait_event_interruptibleLa diferencia entre la macro y wait_eventes que el proceso actual se establecerá como el estado durante el proceso de espera de llamar a esta macro TASK_INTERRUPTIBLE. Cada vez que se despierte, primero verifique conditionsi es verdadero y regrese si es verdadero; de lo contrario, verifique si el proceso se despierta con una señal y devuelva -ERESTARTSYSun código de error. Devuelve 0 si es conditionverdadero.
  • wait_event_interruptible_timeoutLas macros son similares a las macros, pero devuelven un código de error wait_event_timeoutsi son interrumpidas por una señal durante la suspensión . El kernel de Linux proporciona algunas macros para despertar los procesos en las colas correspondientes.Las definiciones de estas macros son las siguientes:ERESTARTSYS
    5.唤醒等待队列
#define wake_up(x)			__wake_up(x, TASK_NORMAL, 1, NULL)
#define wake_up_interruptible(x)   __wake_up(x, TASK_INTERRUPTIBLE, 1, NULL)
  • wake_upLa macro activa la cola de espera, que puede activar el proceso y el TASK_INTERRUPTIBLEestado TASK_UNINTERRUPTIBLE. Esta macro y wait_event/wait_event_timeoutse utiliza en pares.
  • wake_up_interruptiblewake_up()La única diferencia entre macro y es que solo puede despertar TASK_INTERRUPTIBLEel estado del proceso. Esta macro puede reactivar wait_event_interruptible、wait_event_interruptible_timeoutun proceso que se puso en suspensión mediante la macro.

Experimento del mecanismo de sincronización

Esta sección explicará un experimento usando un mecanismo de sincronización implementado por una cola de espera.A través del experimento en esta sección, los lectores pueden tener una comprensión más profunda del mecanismo de sincronización en Linux.

Diseño del mecanismo de sincronización

El diseño del mecanismo de sincronización de procesos primero requiere una cola de espera. Todos los procesos que esperan que se complete un evento se adjuntan a la cola de espera. Una estructura de datos que contiene la cola puede realizar esta intención. El código de definición de esta estructura de datos es el siguiente:

struct CustomEvent{
    
    
	int eventNum;  //事件号
	wait_queue_head_t *p; //系统等待队列首指针
	struct CustomEvent *next; //队列链指针
}

Aquí hay una breve explicación de esta estructura:

  • La línea 2 eventNumindica el número de evento que el proceso está esperando
  • La línea 3 es una cola de espera y el proceso espera en esta cola de espera.
  • La línea 4 es un puntero para conectar esta estructura.
    Para lograr el propósito del experimento, se diseñan dos punteros para representar la cabeza y la cola de la lista de la cadena de eventos, respectivamente.La definición de estas dos estructuras se muestra en el siguiente código.
CustomEvent *lpevent_head = NULL; //链头指针
CustomEvent *lpevent_end = NULL;  //链尾指针

Cada evento consta de una lista vinculada, y cada lista vinculada contiene una cola de espera para este evento. Esta estructura se muestra en la siguiente figura.
inserte la descripción de la imagen aquí
Para realizar el diseño del experimento se define una función FindEventNum()para encontrar la lista de espera correspondiente a un evento de una lista de eventos, el código de esta función es el siguiente:

CustomEvent *FindEventNum(int eventNum, CustomEvent **prev)
{
    
    
	CustomEvent *tmp = lpevent_head;
	*prev = NULL;
	while(tmp)
	{
    
    
		if(tmp->eventNum == eventNum)
			return tmp;
		*prev = tmp;
		tmp = tmp->next;
	}
	return NULL;
}

Aquí hay una breve introducción a esta función:

  • Línea 1, la función recibe dos parámetros, el primer parámetro eventNumes el número de serie del evento y el segundo parámetro es el evento anterior del evento devuelto. Si la función encuentra el evento deseado, lo devuelve, de lo contrario, devuelve NULL.
  • La línea 3 se tmpasignará como encabezado de la lista de eventos.
  • 4 líneas, apuntará preva NULL.
  • Las líneas 5 a 11 son un while()bucle para encontrar el puntero de estructura del evento deseado.
  • Línea 7, juzgue tmpsi el número de evento apuntado es eventNumel mismo que , si es el mismo, regrese, indicando que se encuentra, de lo contrario, continúe buscando a lo largo de la lista enlazada.
  • 10 líneas, se tmpmoverá hacia atrás.
  • 12 líneas, si no se encuentra, devuelve valor NULL.
    Para realizar el diseño del experimento se define una función de llamada al sistema sys_CustomEvent_open(), la cual asigna un nuevo evento y devuelve el número de evento del nuevo evento asignado, la definición de la función es la siguiente:
asmlinkage int sys_CustomEvent_open(int eventNum)
{
    
    
	CustomEvent *new;
	CustomEvent *prev;
	if(eventNum)
		if(!FindEentNum(eventNum, &prev))
			return -1;
		else
			return eventNum;
	else
	{
    
    
		new = (CustomEvent *)kmalloc(sizeof(CustomEvent), GFP_KERNEL);
		new->p = (wait_queue_head_t *)kmalloc(sizeof(wait_queue_head_t), GFP_KERNEL);
		new->next = NULL;
		new->p->task_list.next = &new->p->task_list;
		new->p->task_list.prev = &new->p->task_list;
		if(!lpevent_head)
		{
    
    
			new->eventNum = 2; //从2开始按偶数递增事件号
			lpevent_end->next = lpevent_end = new;
			return new->eventNum;
		}
		else
		{
    
    
			//事件队列不为空,按偶数递增一个事件号
			new->eventNum = lpevent_end->eventNum + 2;
			lpevent_end->next = new;
			lpevent_end = new;
		}
		return new->eventNum;
	}
	return 0;
}

Aquí hay una breve introducción a esta función:

  • 1 línea, esta función se usa para crear un nuevo evento, y el parámetro es el número de evento recién creado.
  • Las líneas 3 y 4 definen punteros a dos eventos.
  • Línea 5, juzgue si el evento es 0, si es 0, recree un evento.
  • Líneas 6~9, busque el evento según el número de evento, si lo encuentra, devuelva el número de evento, si no lo encuentra, devuelva -1. FindEventNum()La función encuentra el evento correspondiente según el número de evento.
  • Las líneas 12~31 se utilizan para reasignar un evento.
  • Línea 12, llame kmalloc()a la función para asignar un evento.
  • En la línea 13, asigne la cola de espera correspondiente al evento y señale el enlace de la estructura de tareas de la cola de espera hacia sí mismo.
  • Líneas 17-22, si no hay un encabezado de lista de cadenas de eventos, asigne el evento recién asignado al encabezado de la lista de cadenas de eventos y devuelva el número de evento recién asignado.
  • Líneas 25-28, si no hay un encabezado de lista de enlaces de eventos, conecte el evento recién asignado a la lista de enlaces.
  • La línea 30 devuelve el número de evento recién asignado.
    Lo siguiente define una función de llamada al sistema que bloquea el proceso de un evento, y el evento no finaliza hasta que se despierta el evento en espera. El código para esta función es el siguiente:
asmlinkage int sys_CustomEvent_wait(int eventNum)
{
    
    
	CustomEvent *tmp;
	CustomEvent *prev = NULL;
	if((tmp = FindEventNum(eventNum, &prev)) != NULL)
	{
    
    
		DEFINE_WAIT(wait); //初始化一个wait_queue_head
		//当前进程进入阻塞队列
		prepare_to_wait(tmp->p, &wait, TASK_INTERRUPTIBLE); 
		
		schedule(); //重新调度
		finish_wait(tmp->p, &wait); //进程被唤醒从阻塞队列退出
		return eventNum;
	}
	return -1;
}

Aquí hay una breve introducción a esta función:

  • Línea 1, la función implementa una llamada al sistema esperando que la cola espere.
  • Las líneas 3 y 4 definen punteros a dos eventos.
  • Línea 5, al eventNumencontrar la estructura del evento, si la búsqueda falla, devuelve -1.
  • La línea 7 define e inicializa una cola de espera.
  • La línea 8 pone el proceso actual en la cola de espera.
  • Línea 9, reprogramar un nuevo proceso.
  • Línea 10, cuando el proceso se despierta, el proceso sale de la cola de espera.
  • La línea 11 devuelve el número de evento.
    Hay funciones que ponen un proceso a dormir, y hay funciones que lo despiertan. La función para despertar y esperar un evento específico es sys_CustomEvent_signal(), el código de esta función es el siguiente:
asmlinkage int sys_CustomEvent_signal(int eventNum)
{
    
    
	CustomEvent *tmp = NULL;
	CustomEvent *prev = NULL;
	if(!(tmp = FindEventNum(eventNum, &prev)))
		return 0;
	wake_up(tmp->p); //唤醒等待事件的进程
	return -1;
}

Aquí hay una breve introducción a esta función:

  • Línea 1, la función recibe un parámetro, este parámetro es el número de evento del evento que se despertará, y las funciones que esperan en este evento se despertarán.
  • Línea 1, la función recibe un parámetro, este parámetro es el número de evento del evento que se despertará, y las funciones que esperan en este evento se despertarán.
  • Las líneas 2 y 3 definen dos punteros de estructura.
  • 5 líneas, volver si no se encuentra ningún evento.
  • La línea 7 activa todos los procesos en la cola de espera.
  • 8 líneas, devuelve 1, lo que indica éxito.
    Se define una función para cerrar un evento, que primero despierta la cola de espera en el evento y luego limpia el espacio ocupado por el evento. El código de la función es el siguiente:
asmlinkage int sys_CustomEvent_close(int eventNum)
{
    
    
	CustomEvent *prev=NULL;
	CustomEvent *releaseItem;
	if(releaseItem = FindEventNum(eventNum, &prev))
	{
    
    
		if(releaseItem == lpevent_end)
			lpevent_end = prev;
		else if(releaseItem == lpevent_head)
			lpevent_head = lpevent_head->next;
		else
			prev->next = releaseNum->next;
		sys_CustomEvent_signal(eventNum);
		if(releaseNum){
    
    
			kfree(releaseNum);
		}
		return releasNum;
	}
	return 0;
}

Aquí hay una breve introducción a esta función:

  • 1 línea, la función indica el evento de cierre. Si el apagado falla, devuelve 0; de lo contrario, devuelve el número de evento del apagado.
  • Las líneas 3 y 4 definen dos punteros de estructura.
  • Línea 5, busque el evento que debe cerrarse.
  • La línea 7, si es el último evento de la lista enlazada, lpevent_endapuntará al evento anterior.
  • Línea 9, si es el primer evento en la lista enlazada, apuntará lpevent_headal segundo evento.
  • Línea 10, si el evento es un evento intermedio, elimine el evento intermedio y conéctelo con un puntero.
  • Línea 13, despierte el evento que debe cerrarse.
  • Línea 14, borre la memoria ocupada por el evento.
  • La línea 18 devuelve el número de evento.

Verificación experimental

Compile el código anterior en el kernel e inicie el sistema con el nuevo kernel. Entonces hay 4 nuevas llamadas al sistema en el sistema. Las 4 nuevas llamadas al sistema son respectivamente __NR_CustomEvetn_open、__NR_CustomEvent_wait、__NR_CustomEvent_signal和__NR_myevent_close. Use estas 4 llamadas al sistema para escribir programas para verificar el mecanismo de sincronización.
Primero, debe abrir un evento. El código para completar esta función es el siguiente. Este código se abre para ver una función con un número de evento de 2 y luego sale.

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_open(int flag){
    
    
	return syscall(__NR_CustomEvent_open, flag);
}
int main(int argc, char **argv)
{
    
    
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_open(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

Después de abrir una función con un número de evento de 2, se pueden colocar varios procesos en estado de espera en este evento. El código para poner un proceso en estado de espera es el siguiente: ejecute el siguiente código varias veces y pase el parámetro 2, y el proceso se pondrá en la cola de espera del evento 2.

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_wait(int flag){
    
    
	return syscall(__NR_CustomEvent_wait, flag);
}
int main(int argc, char **argv)
{
    
    
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_wait(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

Si se realiza la operación anterior, varios procesos se colocarán en estado de espera. En este momento, se llama al siguiente código y se pasa el parámetro 2 para activar varios procesos que esperan el evento 2.

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int CustomEvent_wait(int flag){
    
    
	return syscall(__NR_CustomEvent_signal, flag);
}
int main(int argc, char **argv)
{
    
    
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_signal(atoi(argv[1]));
	printf("%d\n",i);
	return 0;
}

Cuando no se necesita un evento, el evento se puede eliminar y todos los procesos que esperan este evento regresarán y se ejecutarán. El código para completar esta función es el siguiente:

#include <linux/unistd.h>
#include <stdio.h>
#include <stdlib.h>
int myevent_close(int flag){
    
    
	return syscall(__NR_CustomEvent_close, flag);
}
int main(int argc, char **argv)
{
    
    
	int i;
	if(argc != 2)
		return -1;
	i = CustomEvent_close(atoi(argv[1]));
	printf("%d\n", i);
	return 0;
}

resumen

El bloqueo y el no bloqueo se utilizan a menudo en los controladores. El bloqueo permite que el proceso ingrese a la cola de espera cuando la operación de E/S no se puede realizar temporalmente. Este último regresa inmediatamente cuando la operación de E/S no se puede realizar temporalmente. Estos dos métodos tienen sus propias ventajas y desventajas, y deben usarse selectivamente en aplicaciones prácticas. Dado que el bloqueo y el no bloqueo también se implementan mediante colas de espera, este capítulo también explica brevemente el uso de algunas colas de espera.

Supongo que te gusta

Origin blog.csdn.net/m0_56145255/article/details/131653510
Recomendado
Clasificación