Llamada al sistema de escucha de la pila del protocolo del kernel de Linux

Tabla de contenido

1. Descripción general de la función de escucha

2. Gestión de sockets de escucha TCP listening_hash

3. Implementación del kernel de escucha

3.1 sys_listen ()

3.1 inet_listen ()

3.2 inet_csk_listen_start ()

3.2.1 Crear una cola de solicitud de conexión request_sock_queue (core)

3.2.2 Registre el conector de monitoreo en la tabla hash de monitoreo global de TCP (núcleo)


1. Descripción general de la función de escucha

  1. Cree trabajos pendientes entrantes para crear una cola de solicitudes: cola semiconectada, cola completamente conectada
  2. Migrar el estado del socket a LISTEN
  3. Registre el calcetín de escucha en la tabla hash del socket de escucha global de TCP

2. Gestión de sockets de escucha TCP listening_hash

Para facilitar la consulta, el protocolo TCP gestiona todos los sockets de escucha con una tabla hash global. La información de la tabla hash es la siguiente:

path: net/ipv4/tcp_ipv4.c
struct inet_hashinfo __cacheline_aligned tcp_hashinfo;

/* This is for listening sockets, thus all sockets which possess wildcards. */
#define INET_LHTABLE_SIZE	32	/* Yes, really, this is all you need. */

//inet_hashinfo是TCP层面的多个哈希表的集合,下面只列出了和监听套接字管理相关的字段
struct inet_hashinfo {
	...
	/* All sockets in TCP_LISTEN state will be in here.  This is the only
	 * table where wildcard'd TCP sockets can exist.  Hash function here
	 * is just local port number.
	 */
	struct hlist_head		listening_hash[INET_LHTABLE_SIZE];

	//保护对该结构成员的互斥访问
	rwlock_t			lhash_lock ____cacheline_aligned;
	//对该结构的引用计数
	atomic_t			lhash_users;
};

La organización de TCP de los sockets de escucha se puede representar en la siguiente figura:
Inserte la descripción de la imagen aquí

3. Implementación del kernel de escucha

La función principal de listen es crear una cola de solicitudes (semi-conectada, completamente conectada) para el socket y poner el socket en la tabla hash de la monitorización global tcp

sys_listen
----inet_listen
	----inet_csk_listen_start
		----reqsk_queue_alloc
		----inet_hash

3.1 sys_listen ()

  1. Obtenga el calcetín de enchufe correspondiente de acuerdo con el fd pasado desde el espacio de usuario
  2. Verifique el número de conexiones solicitadas y no puede exceder el número máximo de conexiones completas establecido por el kernel
  3. Llame a la interfaz de enlace inet_listen de la familia de protocolos AF_INET
/*
 *	Perform a listen. Basically, we allow the protocol to do anything
 *	necessary for a listen, and if that works, we mark the socket as
 *	ready for listening.
 */

asmlinkage long sys_listen(int fd, int backlog)
{
	struct socket *sock;
	int err, fput_needed;
	int somaxconn;

	sock = sockfd_lookup_light(fd, &err, &fput_needed);
	if (sock) {
		somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
		if ((unsigned)backlog > somaxconn)
			backlog = somaxconn;

		err = security_socket_listen(sock, backlog);
		if (!err)
			err = sock->ops->listen(sock, backlog);

		fput_light(sock->file, fput_needed);
	}
	return err;
}

3.1 inet_listen ()

  1. Verificación del tipo de socket y verificación de estado. El tipo debe ser un socket de flujo y conectable, y el estado debe ser cerrado o escuchar
  2. Si el estado es escuchar, use directamente el backlog para modificar el tamaño de la cola completamente conectada y regresar
  3. Si el estado es cerrado, se realizarán operaciones como la creación y habilitación (registro) de la cola de solicitudes de socket.
int inet_listen(struct socket *sock, int backlog)
{
	struct sock *sk = sock->sk;
	unsigned char old_state;
	int err;

	lock_sock(sk);

	//套接口层socket的状态应该是未连接的、类型必须是SOCK_STREAM,
	//从这里可以看到,UDP套接字是不可以调用listen()的
	err = -EINVAL;
	if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
		goto out;

	//只能是CLOSE或LISTEN状态的套接口才能调用listen()
	old_state = sk->sk_state;
	if (!((1 << old_state) & (TCPF_CLOSE | TCPF_LISTEN)))
		goto out;

	/* Really, if the socket is already in listen state
	 * we can only allow the backlog to be adjusted.
	 */
	if (old_state != TCP_LISTEN) {
		//创建连接请求队列;并将TCB状态迁移到TCP_LISTEN
		err = inet_csk_listen_start(sk, backlog);
		if (err)
			goto out;
	}
	//将用户指定的backlog更新到套接字的sk_max_ack_backlog中,该变量就是
	//accept连接队列所允许的最大值,即如果服务器端程序迟迟不调用accept,那
	//么一旦已连接套接字超过该限定值,那么三次握手将无法完成,客户端会出现连
	//接失败的问题
	sk->sk_max_ack_backlog = backlog;
	err = 0;

out:
	release_sock(sk);
	return err;
}

A partir de la lógica del código anterior, la aplicación puede modificar el parámetro sk_max_ack_backlog llamando a listen () varias veces .

3.2 inet_csk_listen_start ()

  1. Crear cola de solicitudes
  2. Borrar campos relacionados con ack
  3. Migre el estado del socket para escuchar
  4. Determine si el socket está vinculado a una dirección y un puerto; si no está vinculado, se asignará uno al azar y volverá si está vinculado
//nr_table_entries就是listen()调用是用户程序传入的backlog参数
int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)
{
	struct inet_sock *inet = inet_sk(sk);
	struct inet_connection_sock *icsk = inet_csk(sk);

	//根据用户指定的backlog值分配SYN请求队列,该函数分析见下文
	int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
	if (rc != 0)
		return rc;

	//初始化该监听套接字TCB中的计数变量
	sk->sk_max_ack_backlog = 0;
	sk->sk_ack_backlog = 0;
	//清零延迟ACK相关数据成员
	inet_csk_delack_init(sk);

	/* There is race window here: we announce ourselves listening,
	 * but this transition is still not validated by get_port().
	 * It is OK, because this socket enters to hash table only
	 * after validation is complete.
	 */
	//将TCB的状态迁移到TCP_LISTEN
	sk->sk_state = TCP_LISTEN;
	//如果listen()之前已经bind()过,那么该函数会直接返回0;如果之前未bind()过,
	//那么该函数会为该监听套接字绑定一个端口,即实现自动绑定。关于端口绑定,可以参
	//考《TCP之系统调用bind()》
	if (!sk->sk_prot->get_port(sk, inet->num)) {
		//端口绑定成功
		inet->sport = htons(inet->num);
		//路由相关操作
		sk_dst_reset(sk);
		//监听状态的套接字需要注册到TCP的监听套接字散列表中,即(tcphashinfo->listening_hash)
		//对于TCP,该回调函数实际上是inet_hash(),见下文
		sk->sk_prot->hash(sk);

		return 0;
	}
	//绑定端口出错时,设置TCB状态为TCP_CLOSE,并且销毁已经分配的accept连接队列和SYN请求队列
	sk->sk_state = TCP_CLOSE;
	__reqsk_queue_destroy(&icsk->icsk_accept_queue);
	return -EADDRINUSE;
}

3.2.1 Crear una cola de solicitud de conexión request_sock_queue (core)

La estructura request_sock_queue  de la cola de solicitudes de conexión basada en sockets de transmisión es la siguiente:

/** struct request_sock_queue - queue of request_socks
 *
 * @rskq_accept_head - FIFO head of established children
 * @rskq_accept_tail - FIFO tail of established children
 * @rskq_defer_accept - User waits for some data after accept()
 * @syn_wait_lock - serializer
 *
 * %syn_wait_lock is necessary only to avoid proc interface having to grab the main
 * lock sock while browsing the listening hash (otherwise it's deadlock prone).
 *
 * This lock is acquired in read mode only from listening_get_next() seq_file
 * op and it's acquired in write mode _only_ from code that is actively
 * changing rskq_accept_head. All readers that are holding the master sock lock
 * don't need to grab this lock in read mode too as rskq_accept_head. writes
 * are always protected from the main sock lock.
 */
struct request_sock_queue {
	struct request_sock	*rskq_accept_head; //全连接队列
	struct request_sock	*rskq_accept_tail;
	rwlock_t		syn_wait_lock;
	u8			rskq_defer_accept;
	/* 3 bytes hole, try to pack */
	struct listen_sock	*listen_opt; //半连接队列
};

/** struct listen_sock - listen state
 *
 * @max_qlen_log - log_2 of maximal queued SYNs/REQUESTs
 */
struct listen_sock {
	u8			max_qlen_log;
	/* 3 bytes hole, try to use */
	int			qlen;
	int			qlen_young;
	int			clock_hand;
	u32			hash_rnd;
	u32			nr_table_entries;
	struct request_sock	*syn_table[0];//半连接队列节点数据
};

/* struct request_sock - mini sock to represent a connection request
 */
struct request_sock {
	struct request_sock		*dl_next; /* Must be first member! */
	u16				mss;
	u8				retrans;
	u8				cookie_ts; /* syncookie: encode tcpopts in timestamp */
	/* The following two fields can be easily recomputed I think -AK */
	u32				window_clamp; /* window clamp at creation time */
	u32				rcv_wnd;	  /* rcv_wnd offered first time */
	u32				ts_recent;
	unsigned long			expires;
	const struct request_sock_ops	*rsk_ops;//配套的接口函数
	struct sock			*sk;
	u32				secid;
	u32				peer_secid;
};
  1. Cree un listen_opt de semi-unión, el tamaño se corrige en el intervalo [8, max_syn_backlog], y es una potencia entera de 2 (conveniente tomar el resto)
  2. Inicialice la cola completamente conectada rskq_accept_head está vacía
  3. Crear un bloqueo de lectura-escritura sincrónico syn_wait_lock 
/*
 * Maximum number of SYN_RECV sockets in queue per LISTEN socket.
 * One SYN_RECV socket costs about 80bytes on a 32bit machine.
 * It would be better to replace it with a global counter for all sockets
 * but then some measure against one socket starving all other sockets
 * would be needed.
 *
 * It was 128 by default. Experiments with real servers show, that
 * it is absolutely not enough even at 100conn/sec. 256 cures most
 * of problems. This value is adjusted to 128 for very small machines
 * (<=32Mb of memory) and to 1024 on normal or better ones (>=256Mb).
 * Note : Dont forget somaxconn that may limit backlog too.
 */
int sysctl_max_syn_backlog = 256;
int reqsk_queue_alloc(struct request_sock_queue *queue,
		      unsigned int nr_table_entries)
{
	size_t lopt_size = sizeof(struct listen_sock);
	struct listen_sock *lopt;

	//从下面的逻辑可以看出,backlog是如何影响半连接队列的哈希桶大小的:
	//1. 如果用户指定的backlog超过了系统最大值(/proc/sys/net/ipv4/tcp_max_syn_backlog),
	//	  那么取系统允许的最大值
	//2. 确保哈系桶的大小不小于8
	//最后通过roundup_pow_of_two()向上调整,使得最终的nr_table_entries值为2的整数幂
	nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
	nr_table_entries = max_t(u32, nr_table_entries, 8);
	nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
    
	//为request_sock和其内部的散列表listen_opt分配内存空间
	lopt_size += nr_table_entries * sizeof(struct request_sock *);
	if (lopt_size > PAGE_SIZE)
		lopt = __vmalloc(lopt_size,
			GFP_KERNEL | __GFP_HIGHMEM | __GFP_ZERO,
			PAGE_KERNEL);
	else
		lopt = kzalloc(lopt_size, GFP_KERNEL);
	if (lopt == NULL)
		return -ENOMEM;

	//初始化max_qlen_log为nr_table_entries以2为底的对数,即2^max_qlen_log=nr_table_entries
	for (lopt->max_qlen_log = 3;
	     (1 << lopt->max_qlen_log) < nr_table_entries;
	     lopt->max_qlen_log++);

	//生成一个随机数,该随机数用于访问listen_opt哈希表时计算哈希值
	get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
	//初始化锁
	rwlock_init(&queue->syn_wait_lock);
	//初始化accept连接队列为空
	queue->rskq_accept_head = NULL;
	//记录listen_opt哈希表的桶大小到nr_table_entries 
	lopt->nr_table_entries = nr_table_entries;

	//将listen_opt记录到监听套接字连接请求队列中,即
	//tcp_sock.inet_conn.icsk_accept_queue.listen_opt
	write_lock_bh(&queue->syn_wait_lock);
	queue->listen_opt = lopt;
	write_unlock_bh(&queue->syn_wait_lock);

	return 0;
}

Se puede ver que la operación principal de reqsk_queue_alloc () es crear una cola semi-conectada listen_opt.

3.2.2 Registre el conector de monitoreo en la tabla hash de monitoreo global de TCP (núcleo)

void inet_hash(struct sock *sk)
{
	//非TCP_CLOSE状态(如上,listen()调用过程中,到这里状态应该是TCP_LISTEN),
	//调用__inet_hash()
	if (sk->sk_state != TCP_CLOSE) {
		local_bh_disable();
		__inet_hash(sk);
		local_bh_enable();
	}
}

static void __inet_hash(struct sock *sk)
{
	struct inet_hashinfo *hashinfo = sk->sk_prot->hashinfo;
	struct hlist_head *list;
	rwlock_t *lock;

	//对于TCP,并且是listen()调用流程,TCB状态一定是TCP_LISTEN,肯定不满足该条件
	if (sk->sk_state != TCP_LISTEN) {
		__inet_hash_nolisten(sk);
		return;
	}

	//将该监听套接字加入到TCP的全局哈希表listenning_hash中
	BUG_TRAP(sk_unhashed(sk));
	list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
	lock = &hashinfo->lhash_lock;
	inet_listen_wlock(hashinfo);
	__sk_add_node(sk, list);
	//还会累加对协议结构的引用计数
	sock_prot_inuse_add(sk->sk_prot, 1);
	write_unlock(lock);
	wake_up(&hashinfo->lhash_wait);
}

Supongo que te gusta

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