Mecanismo de tareas de interrupción suave del núcleo

1. Introducción a la interrupción suave IRQ

Soft interrupt (SoftIRQ) es un mecanismo de retardo basado en interrupciones provisto por el kernel. El kernel de Linux define las siguientes interrupciones suaves:

enum
{
	HI_SOFTIRQ=0,			/*高优先级的tasklet*/
	TIMER_SOFTIRQ,
	NET_TX_SOFTIRQ,
	NET_RX_SOFTIRQ,
	BLOCK_SOFTIRQ,
	TASKLET_SOFTIRQ,  /*普通tasklet*/
	SCHED_SOFTIRQ,
#ifdef CONFIG_HIGH_RES_TIMERS
	HRTIMER_SOFTIRQ,
#endif
};

Además, el núcleo mantiene una matriz IRQ global para registrar información detallada de diferentes interrupciones suaves:

static struct softirq_action softirq_vec[32]  __cacheline_aligned_in_smp;

El tipo de la variable global es struct softirq_action , que es el prototipo de la función de todas las operaciones de procesamiento de interrupción de IRQ. Es decir , las variables globales mantenidas por el sistema registran la información de cada función de procesamiento de interrupción de IRQ , pero su estructura es muy simple:

struct softirq_action
{
	void	(*action)(struct softirq_action *); /*中断处理函数指针*/
	void	*data; /*传递的参数*/
};

Dado que los desarrolladores del kernel no sugieren que añadimos interrupción de encargo suave, pretende proporcionar un mecanismo especial tasklet, podemos tasklet para alcanzar nuestras propias tareas de procesamiento de interrupción. Tasklet es un mecanismo de retraso de tareas basado en interrupciones suaves proporcionadas por el núcleo a los desarrolladores. El tasklet es solo uno de varios softirqs definidos por el kernel. Los controladores de dispositivos a menudo implementan ciertas operaciones de retraso a través de tasklets. En la interrupción suave, el tasklet se divide en dos categorías: una es HI_SOFTIRQ de alta prioridad ; la otra es TASKLET_SOFTIRQ . La información detallada sobre tasklet se presenta a continuación:

2. tasklet_struct de la estructura de tasklet

struct tasklet_struct
{
	struct tasklet_struct *next;
	unsigned long state;
	atomic_t count;
	void (*func)(unsigned long);
	unsigned long data;
};
Número de serie Nombre variable Efecto
1 siguiente Se usa para conectar objetos de tasklet en el sistema para formar una lista vinculada
2 estado Registre el estado del tasklet: TASKLET_STATE_SCHED indica que se ha enviado el tasklet. TASKLET_STATE_RUN indica que el tasklet se está ejecutando (solo para SMP)
3 contar 0: habilitar, se puede programar para su ejecución. 1: deshabilitar, no ejecutable;
4 4 func Cuerpo ejecutivo de Tasklet, implementado por el desarrollador
5 5 datos Parámetros pasados ​​a func

3. Inicialización del mecanismo de Tasklet

sistema Linux durante la inicialización llamando softirq_init función de HI_SOFTIRQ y TASKLET_SOFTIRQ montado en el cuerpo de la función realizada, respectivamente tasklet_hi_action y la tasklet_action :

void __init softirq_init(void)
{
	open_softirq(TASKLET_SOFTIRQ, tasklet_action, NULL);
	open_softirq(HI_SOFTIRQ, tasklet_hi_action, NULL);
}

La llamada función de ejecución de la instalación es inicializar los parámetros del miembro (un puntero de función de devolución de llamada y un parámetro pasado) correspondientes a estas dos interrupciones en la variable global IRQ, y esta parte se realiza mediante open_softirq (). La función open_softirq () es muy sencilla de implementar:

static struct softirq_action softirq_vec[32] __cacheline_aligned_in_smp;
void open_softirq(int nr, void (*action)(struct softirq_action*), void *data)
{
	softirq_vec[nr].data = data;
	softirq_vec[nr].action = action;
}

Mediante la ejecución de las dos funciones anteriores, se completa la tarea de inicialización del mecanismo de tarea, a saber:

softirq_vec [ TASKLET_SOFTIRQ ] .action = tasklet_hi_action ;
softirq_vec [ HI_SOFTIRQ ] .action = tasklet_action ;

4. Inicialización de Tasklet

Existen dos métodos de inicialización para los objetos de tasklet, uno es el modo estático y el otro es el modo dinámico :

4.1 Inicialización estática del objeto Tasklet

La declaración del objeto tasklet consta de dos funciones: cuando se declara el objeto, se debe implementar el nombre del objeto del tasklet , la función de la función de operación de retardo y los parámetros a pasar .

#define DECLARE_TASKLET(name, func, data) \     /*可以被调度执行*/
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(0), func, data }

#define DECLARE_TASKLET_DISABLED(name, func, data) \ /*不可以被调度执行*/
struct tasklet_struct name = { NULL, 0, ATOMIC_INIT(1), func, data }

Los objetos declarados usando DECLARE_TASKLET están en el estado habilitado, los objetos declarados usando DECLARE_TASKLET_DISABLED están en el estado deshabilitado.

4.2 Inicialización dinámica de objetos de tasklet

La inicialización dinámica es definir directamente una variable tasklet_struct, y luego usar la función tasklet_init () para completar la operación de inicialización.

void tasklet_init(struct tasklet_struct *t,
		  void (*func)(unsigned long), unsigned long data)
{
	t->next = NULL;
	t->state = 0;
	atomic_set(&t->count, 0);
	t->func = func;
	t->data = data;
}

5. Envío de objeto de Tasklet

Los llamados objetos tasklet presentados, tasket realmente añaden a la declaración de tasklet_vec lista gestionada, tasklet_vec es una variable de cada CPU del tipo utilizado en el sistema de todos los objetos presentados por tasklet función tasklet_schedule constituyen la lista. (Linux-2.6.22)

void fastcall __tasklet_schedule(struct tasklet_struct *t)
{
	unsigned long flags;

	local_irq_save(flags);
	t->next = __get_cpu_var(tasklet_vec).list; /*使用头插法插入一个新节点 */
	__get_cpu_var(tasklet_vec).list = t;
	raise_softirq_irqoff(TASKLET_SOFTIRQ);
	local_irq_restore(flags);
}
static inline void tasklet_schedule(struct tasklet_struct *t)
{
	if (!test_and_set_bit(TASKLET_STATE_SCHED, &t->state))/*调度位为0,再提交;如果为1,则证明已经提交,无需再提交*/
		__tasklet_schedule(t);
}
  • Función test_and_set_bit (nr, vaddr) : modifica el valor de vaddr a nr y devuelve el valor cuando no se modifica vaddr.
  • local_irq_save : Desactiva la respuesta de interrupción local.
  • local_irq_restore : activa la interrupción local.
  • Nota: La condición cuando se programa es que el estado no está establecido en el bit TASKLET_STATE_SCHED; y la autoridad de ejecución del tasklet se juzga por count, y el tasklet solo se puede ejecutar cuando count = 0 .

El análisis es el siguiente:

  • Al programar un objeto de tasklet, si el bit de estado no es TASKLET_STATE_SCHED, significa que el objeto de tasklet no se ha programado, luego establezca el valor de estado en TASKLET_STATE_SCHED (si el valor de estado ya es TASKLET_STATE_SCHED, lo que indica que se ha programado, no hay necesidad de reprogramarlo);
  • Cierre la interrupción local y luego inserte el objeto tasklet en la lista tasklet_vec . El kernel de Linux2.6.22 usa el método de interpolación de cabecera , y algunas versiones de Linux usan el método de interpolación de cola , y los miembros de struct tasklet_head son diferentes.
  • Envíe una solicitud de interrupción de software, raise_softirq_irqoff (TASKLET_SOFTIRQ) para notificar al kernel que hay un == "TASKLET_SOFTIRQ" == esperando el procesamiento en el procesador actual, utiliza un bit variable entero para indicar que el bit está pendiente Operación IRQ, 0 significa no, 1 significa sí.
  • Vuelva a habilitar la interrupción local.

5. Cuerpo de ejecución interno de Tasklet

tarea tasklet es tasklet_schedule después de la presentación para el sistema de cadena respectivo, el sistema atravesar la lista enlazada entonces se ejecuta secuencialmente tasklet tarea. Como las dos interrupciones HI_SOFTIRQ y TASKLET_SOFTIRQ solo tienen una prioridad diferente, la otra es básicamente la misma en el procesamiento. Aquí solo se presenta la acción tasklet_ de TASKLET_SOFTIRQ .

static void tasklet_action(struct softirq_action *a)
{
	struct tasklet_struct *list;
	local_irq_disable();
	list = __get_cpu_var(tasklet_vec).list;     /*从tasklet_vec上取下链表,实际是链表的头指针*/
	__get_cpu_var(tasklet_vec).list = NULL; /*清空tasklet_vec链表,实际只是清list指针*/
	local_irq_enable();
	while (list) {
		struct tasklet_struct *t = list;
		list = list->next;
		if (tasklet_trylock(t)) {
			if (!atomic_read(&t->count)) {/*count=0  使能*/
				if (!test_and_clear_bit(TASKLET_STATE_SCHED, &t->state))/*清空调度位,返回原来值*/
					BUG();/*如果原来为0,属异常情况,应结束退出*/
				t->func(t->data);
				tasklet_unlock(t);
				continue;
			}
			tasklet_unlock(t);
		}

		local_irq_disable();
		t->next = __get_cpu_var(tasklet_vec).list;/*重新插入到tasklet_var中*/
		__get_cpu_var(tasklet_vec).list = t;
		__raise_softirq_irqoff(TASKLET_SOFTIRQ);
		local_irq_enable();
	}
}
  • Con la premisa de desactivar la interrupción local, mueva el puntero del encabezado de la lista tasklet_vec pendiente en la CPU actual a la lista de variables temporales y regístrelo, luego borre la lista de tareas y luego vuelva a habilitar la interrupción local. El propósito de la interrupción local es permitir que el sistema reprograme nuevas tareas de tasklet a la lista tasklet_vec ;
  • Recorrer la lista vinculada
    • Utilice tasklet_trylock para determinar si la tarea de tasklet actual se procesa en otras CPU;
      • Obtenga el valor de conteo de la tarea del tasklet actual. Si count = 0, significa que la tarea actual (habilitar) se puede programar para su ejecución. Si el valor es 1, significa que la tarea del tasklet actual (deshabilitar) no se puede programar;
      • Verifique el bit TASKLET_STATE_SCHED del estado, si es 1, se enviará normalmente, pero si es 0, significa que la función de procesamiento actual está programando un tasklet que no se ha enviado. Esta es una situación anormal, y saldrá y volverá directamente;
      • Ejecute el cuerpo de ejecución registrado por tasklet
      • Desbloquee y vuelva a ejecutar la siguiente tarea de tarea.
    • Si el valor de retorno de tasklet_trylock es 0, significa que la tarea puede procesarse en otras CPU, entonces debe volver a enviar la tarea tasklet a la lista tasklet_vec, y luego activar una interrupción suave para esperar a que se vuelva a ejecutar la siguiente interrupción suave;
  • Como puede ver en el código anterior,Antes de que se ejecute una tarea de tasklet, se borra TASKLET_STATE_SCHED en su estado, lo que significa que a menos que se vuelva a enviar el tasklet, la próxima interrupción programada no volverá a ejecutar la tarea de tasklet. Se trata de un one-shot cuenta con: presentar una vez, programación una vez, después de correr, borrado de la lista de CPU tasklet_var, y se sometió a no ser que la tarea tasklet nuevo .
  • Cuando se ejecuta el tasklet, la interrupción se vuelve a habilitar, que es la intención original del diseño de interrupción suave. Si durante la ejecución, llegan otros segmentos intermedios suaves y el nuevo tasklet también está en la CPU, entonces el nuevo tasklet se programará en tasklet_vec , en este momento no afectará la lista de la lista de ejecución, la tarea de tasket se ha ejecutado TASKLET_STATE_SCHED bit Se borra y TASKLET_STATE_SCHED de la nueva tarea de tasklet está habilitado, el nuevo tasklet se ejecutará en la próxima programación de IRQ

6. Tasklet otras operaciones

6.1 tasklet_disable

tasklet_disable y tasklet_disable_nosync pueden hacer que la tarea de tasklet no se pueda programar para ejecutarse, de hecho, la operación más crítica es atomic_inc (& t-> count); porque cuando count = 0 ( enable ), el tasklet se puede llamar y ejecutar.

static inline void tasklet_disable_nosync(struct tasklet_struct *t)
{
	atomic_inc(&t->count);
	smp_mb__after_atomic_inc();
}
static inline void tasklet_disable(struct tasklet_struct *t)
{
	tasklet_disable_nosync(t);
	tasklet_unlock_wait(t);
	smp_mb();
}

6.2 tasklet_enable

tasklet_enable es para lograr que el tasklet pueda ser programado para ejecutarse por el recuento-1 en el objeto tasklet.
Nota: Un tasklet en el estado deshabilitado se puede enviar a tasklet_vec, pero no se programará para su ejecución

static inline void tasklet_enable(struct tasklet_struct *t)
{
	smp_mb__before_atomic_dec();
	atomic_dec(&t->count);
}

6.3 tasklet_kill

Esta función se utiliza para borrar el bit de estado de TASKLET_STATE_SCHED en un objeto de tarea , de modo que IRQ ya no puede programar y ejecutar esta tarea. Si la tarea de tasklet actual se está ejecutando, tasklet_kill estará ocupado hasta que se complete la tarea; si se ha enviado un objeto al sistema pero no se ha programado su ejecución, tasklet_kill se suspenderá hasta que el tasklet se elimine de la lista tasklet_vec. Por lo tanto, la función es una función que puede estar bloqueada.

  • Se puede ver que después de programar un tasklet, se ejecutará una vez.
void tasklet_kill(struct tasklet_struct *t)
{
	if (in_interrupt())
		printk("Attempt to kill tasklet from interrupt\n");

	while (test_and_set_bit(TASKLET_STATE_SCHED, &t->state)) {
		do
			yield();
		while (test_bit(TASKLET_STATE_SCHED, &t->state));
	}
	tasklet_unlock_wait(t);
	clear_bit(TASKLET_STATE_SCHED, &t->state);
}
81 artículos originales publicados · Me gusta 69 · Visitantes 50,000+

Supongo que te gusta

Origin blog.csdn.net/s2603898260/article/details/103655201
Recomendado
Clasificación