Bloqueo de sincronización del bloque de control de transmisión socket_lock_t de la capa de socket de pila del protocolo del kernel de linux

La estructura struct sock en el kernel de Linux es una estructura importante en L3, L4 y la capa de socket. Representa un socket. Esta estructura se utiliza tanto en el contexto del proceso como en el contexto de interrupción suave, así que asegúrese de que la estructura de los datos en estos escenarios La coherencia será muy importante y comprender su mecanismo de bloqueo también es muy importante para comprender el código.

Tabla de contenido

1 Estructura de bloqueo socket_lock_t

2 Acceda a la operación lock_sock / release_sock del contexto del proceso

2.1 calcetín_cerradura ()

2.2 release_sock ()

3 Operación de acceso al contexto de interrupción suave

4 Cuenta de referencia del bloque de control de transmisión

5 Resumen


1 Estructura de bloqueo socket_lock_t

struct sock {
...
	socket_lock_t		sk_lock;
...
}

El miembro sk_lock en el bloque de control de transmisión se usa para proteger el acceso a la estructura.

/* This is the per-socket lock.  The spinlock provides a synchronization
 * between user contexts and software interrupt processing, whereas the
 * mini-semaphore synchronizes multiple users amongst themselves.
 */
typedef struct {
	spinlock_t		slock;
	int			owned;
	wait_queue_head_t	wq;
	/*
	 * We express the mutex-alike socket_lock semantics
	 * to the lock validator by explicitly managing
	 * the slock as a lock variant (in addition to
	 * the slock itself):
	 */
	//用于调试,忽略。该宏定义的有bug,在代码实现时对该字段的访问并没有用宏控制
#ifdef CONFIG_DEBUG_LOCK_ALLOC
	struct lockdep_map dep_map;
#endif
} socket_lock_t;
  • slock: este bloqueo de giro es la clave para sincronizar el contexto del proceso y el contexto de interrupción suave;
  • poseído: un valor de 1 indica que el bloque de control de transmisión ha sido bloqueado por el contexto del proceso, y un valor de 0 indica que no está bloqueado por el contexto del proceso;
  • wq: cola de espera, cuando el contexto del proceso necesita mantener el bloque de control de transmisión, pero actualmente está bloqueado por la interrupción suave, el proceso esperará

Echemos un vistazo a cómo estos tres campos implementan el acceso síncrono entre el contexto del proceso y el contexto de interrupción suave.

2 Acceda a la operación lock_sock / release_sock del contexto del proceso

El contexto del proceso necesita llamar a lock_sock () para bloquear antes de acceder al bloque de control de transmisión, y llamar a release_sock () para liberarlo después de que se complete el acceso. La implementación de estas dos funciones es la siguiente:

2.1 calcetín_cerradura ()

static inline void lock_sock(struct sock *sk)
{
	lock_sock_nested(sk, 0);
}

void lock_sock_nested(struct sock *sk, int subclass)
{
	//注意:调用lock_sock()可能会导致休眠
	might_sleep();
	//持有自旋锁并关闭下半部
	spin_lock_bh(&sk->sk_lock.slock);
	//如果owned不为0,说明有进程持有该传输控制块,调用__lock_sock()等待,见下文
	if (sk->sk_lock.owned)
		__lock_sock(sk);
	//上面__lock_sock()返回后现场已经被还原,即持有锁并且已经关闭下半部。

	//将owned设置为1,表示本进程现在持有该传输控制块
	sk->sk_lock.owned = 1;
	//释放锁但是没有开启下半部
	spin_unlock(&sk->sk_lock.slock);
	/*
	 * The sk_lock has mutex_lock() semantics here:
	 */
	mutex_acquire(&sk->sk_lock.dep_map, subclass, 0, _RET_IP_);
	//开启下半部
    local_bh_enable();
}

//__lock_sock()将进程挂到sk->sk_lock中的等待队列wq上,直到没有进程再持有该该传输
//控制块时返回。注意:调用时已经持有sk->sk_lock,睡眠之前释放锁,返回前再次持有锁
static void __lock_sock(struct sock *sk)
{
	//定义一个等待队列结点
	DEFINE_WAIT(wait);

	//循环,直到sock_owned_by_user()返回0才结束
	for (;;) {
		//将调用进程挂接到锁的等待队列中
		prepare_to_wait_exclusive(&sk->sk_lock.wq, &wait,
					TASK_UNINTERRUPTIBLE);
		//释放锁并打开下半部
		spin_unlock_bh(&sk->sk_lock.slock);
		//执行一次调度
		schedule();
		//再次被调度到时会回到这里,首先持锁并关闭下半部
		spin_lock_bh(&sk->sk_lock.slock);
		//如果没有进程再次持有该传输控制块,那么返回
		if (!sock_owned_by_user(sk))
			break;
	}
	finish_wait(&sk->sk_lock.wq, &wait);
}

#define sock_owned_by_user(sk)	((sk)->sk_lock.owned)

Se puede ver en la implementación del código anterior:

  1. Al adquirir el bloqueo, la lógica es verificar si el campo de propiedad es 1. Si el campo no es 0, entonces ya hay un proceso que mantiene el bloqueo. En este momento, el proceso de llamada necesita dormir y esperar a que el campo se convierta en 0; si el valor de propiedad es 0, entonces ningún proceso retiene el bloqueo, directamente establecido como propiedad en 1. Se puede ver que el propósito final de adquirir el bloqueo es establecer el campo de propiedad en 1, pero el acceso (consulta y modificación) al campo de propiedad está protegido por el bloqueo de giro sk-> sk_lock.
  2. Un hecho muy importante es que después de cambiar de propiedad a 1, el bloqueo de giro ya no se mantiene y la mitad inferior se ha abierto (la mitad inferior aquí es la interrupción suave ). La ventaja de este diseño es que el procesamiento de la pila de protocolos no termina inmediatamente. Si simplemente mantiene presionado el bloqueo de giro desde el principio y cierra la mitad inferior, suelta el bloqueo de giro y abre la mitad inferior al final del proceso, disminuirá El rendimiento del sistema, peor aún, es que la parte inferior está apagada durante mucho tiempo, lo que también puede hacer que la tarjeta de red reciba una interrupción suave no llamada a tiempo, provocando la pérdida de paquetes.

2.2 release_sock ()

Una vez que el contexto del proceso finaliza la operación del bloque de control de transmisión, necesita llamar a release_sock () para liberar el bloque de control de transmisión. Como puede imaginar, el núcleo de la versión es establecer la propiedad en 0 y notificar a otros procesos que esperan el bloque de control de transferencia. Mire la implementación del código a continuación.

void release_sock(struct sock *sk)
{
	/*
	 * The sk_lock has mutex_unlock() semantics:
	 */
	//调试相关,忽略
	mutex_release(&sk->sk_lock.dep_map, 1, _RET_IP_);

	//获取自旋锁并关闭下半部
	spin_lock_bh(&sk->sk_lock.slock);
	//如果后备队列不为空,则调用__release_sock()处理后备队列中的数据包,见数据包的接收过程
	if (sk->sk_backlog.tail)
		__release_sock(sk);
	//设置owned为0,表示调用者不再持有该传输控制块
	sk->sk_lock.owned = 0;
	//如果等待队列不为空,则唤醒这些等待的进程
	if (waitqueue_active(&sk->sk_lock.wq))
		wake_up(&sk->sk_lock.wq);
	//释放自旋锁并开启下半部
	spin_unlock_bh(&sk->sk_lock.slock);
}

3 Operación de acceso al contexto de interrupción suave

En el proceso de recepción de TCP, existen los siguientes fragmentos de código:

int tcp_v4_rcv(struct sk_buff *skb)
{
...
process:
...
	//获取sk->sk_lock.slock自旋锁
	bh_lock_sock_nested(sk);
	//如果没有进程锁定该传输控制块,将数据接收到奥prequeue或者receive_queue中
	if (!sock_owned_by_user(sk)) {
        if (!tcp_prequeue(sk, skb))
            ret = tcp_v4_do_rcv(sk, skb);
	} else
		//如果进程已经锁定该传输控制块,那么先将数据接收到后备队列中
		sk_add_backlog(sk, skb);
	//释放自旋锁
	bh_unlock_sock(sk);
...

/* BH context may only use the following locking interface. */
#define bh_lock_sock(__sk)	spin_lock(&((__sk)->sk_lock.slock))
#define bh_lock_sock_nested(__sk) \
				spin_lock_nested(&((__sk)->sk_lock.slock), \
				SINGLE_DEPTH_NESTING)
#define bh_unlock_sock(__sk)	spin_unlock(&((__sk)->sk_lock.slock))

Se puede ver en el código anterior que cuando el contexto de interrupción suave opera el bloque de control de transmisión, el bloqueo de giro se mantiene, porque el código de procesamiento de interrupción suave saldrá lo más rápido posible, por lo que esto está bien.

La pregunta restante: ¿Qué significa la versión anidada de spin_lock ()? Esto aún no está claro.

4 Cuenta de referencia del bloque de control de transmisión

Una cosa más que mencionar aquí es que la destrucción del bloque de control de transmisión no está directamente relacionada con el bloqueo de sincronización mencionado anteriormente. Si se destruye está determinado por su miembro de recuento de referencia sk_refcnt, que es una variable atómica. Puede usar sock_get () y sock_put () Aumenta y disminuye su recuento de referencias.

5 Resumen

Al acceder a los miembros del bloque de control de transmisión, si solo accede a aquellos miembros que no cambiarán una vez creados, no es necesario asegurarse de que el bloque de control de transmisión no se libere durante el acceso (el recuento de referencia no se reducirá a 0). Debe sujetar el bloque de control de la transmisión. Pero si desea acceder a esos miembros mutables, primero debe bloquearlo. Es muy importante tener en cuenta este principio.

Supongo que te gusta

Origin blog.csdn.net/wangquan1992/article/details/108960282
Recomendado
Clasificación