1.Overview
Overview
In this article we trace the Linux kernel implementation of the listen syscall, following the call path from the syscall entry point down to the memory allocation routines for the accept queue and SYN queue. Understanding this path gives us insight into how the kernel prepares a TCP socket for incoming connections before a single client has connected.
The two internal queues at the center of this story are:
- SYN queue (半連接隊列) — holds incomplete connections waiting for the final ACK of the 3-way handshake.
- Accept queue (全連接隊列) — holds completed connections waiting for the application to call
accept().
2.Example Use Case
Example Use Case
A typical server program calls listen immediately after bind, and before entering its accept loop:
The second argument to listen is backlog, which is the hint we pass to the kernel for the desired queue capacity. As we will see, the kernel may silently reduce this value based on system-wide limits.
3.The listen Syscall Entry Point
The listen Syscall Entry Point
The syscall definition lives in net/socket.c. When we call listen(fd, backlog) from userspace, the kernel executes SYSCALL_DEFINE2(listen, ...):
The kernel first resolves the socket object from the file descriptor, then reads the kernel parameter net.core.somaxconn. If our backlog argument exceeds somaxconn, the kernel silently caps it. The final capped value is then passed down to the protocol layer via the function pointer sock->ops->listen.
4.Protocol Layer: inet_listen
Protocol Layer: inet_listen
The function pointer sock->ops->listen resolves to inet_listen in net/ipv4/af_inet.c:
sk_max_ack_backlog stores the maximum number of entries allowed in the accept queue. After inet_csk_listen_start returns, the socket transitions into TCP_LISTEN state and is ready to receive incoming SYN packets.
5.Socket Struct Hierarchy
Socket Struct Hierarchy
Before going deeper, we need to understand how the socket structs relate to each other. For TCP, every sock is physically a tcp_sock, and the kernel freely casts between the following four types:

sock— the base socket object.inet_sock— extendssockwith IP-level addressing fields.inet_connection_sock— extendsinet_sockwith connection management fields, including the accept queue.tcp_sock— extendsinet_connection_sockwith TCP-specific state and timers.
Because sock is always the first member of each successive struct, a pointer of any of these types can be safely cast to any of the others.
6.Initializing the Queues: inet_csk_listen_start
Initializing the Queues: inet_csk_listen_start
inet_csk_listen_start in net/ipv4/inet_connection_sock.c is where the accept queue and SYN queue are allocated. It casts the base sock to inet_connection_sock and calls reqsk_queue_alloc:
icsk_accept_queue is of type request_sock_queue, which is the single struct the kernel uses to hold both queues. Its location inside inet_connection_sock is highlighted in the diagram below:

The struct definition confirms the layout:
7.The request_sock_queue Struct
The request_sock_queue Struct
request_sock_queue wraps both queues under one roof:
The accept queue is a simple linked list because FIFO access is all we need — a head pointer and a tail pointer are sufficient. The SYN queue, on the other hand, is managed through listen_opt, a pointer to a listen_sock:
syn_table is a hash map. During the third step of the 3-way handshake, the server must quickly locate the matching half-open socket for the incoming ACK packet, which requires hash-based lookup rather than a linear scan. max_qlen_log and nr_table_entries together govern the maximum capacity of the SYN queue.
8.Memory Allocation: reqsk_queue_alloc
Memory Allocation: reqsk_queue_alloc
reqsk_queue_alloc in net/core/request_sock.c performs the actual allocation and initialization:
The function first computes the SYN queue length, capping it against sysctl_max_syn_backlog. Memory for listen_sock (which embeds syn_table) is then allocated via vzalloc or kzalloc depending on the total size. Finally, the accept queue head is set to NULL and the SYN queue pointer is attached to the request_sock_queue.
9.Summary of listen
Summary of listen
The reason we call listen before accept is that listen is what builds the two queues the kernel needs to process incoming connections during the 3-way handshake. Without these structures in place, there is nowhere to buffer connection state.
Queue length rules:
- Accept queue length =
min(backlog, net.core.somaxconn) - SYN queue length is constrained by
backlog,net.core.somaxconn, andnet.ipv4.tcp_max_syn_backlog
To tune the SYN queue capacity, we need to adjust all three parameters together. Changing only one of them may have no observable effect.







