1. Tổng quan và môi trường phân tích
Quá trình phân tích dựa trên mã nguồn nhân Linux phiên bản 3.10. Tài liệu tham khảo chính được lấy từ kho lưu trữ mã nguồn chính thức, cho phép tra cứu chéo các hàm và cấu trúc dữ liệu mạng.
2. Tầng ứng dụng và điểm vào kernel
Tại không gian người dùng, lập trình viên gọi hàm chuẩn POSIX để chuyển socket sang trạng thái chờ kết nối:
/*
* socket_fd: định danh socket cần lắng nghe
* max_pending: số lượng kết nối tối đa được phép xếp hàng chờ
*/
int listen(int socket_fd, int max_pending);
Yêu cầu này sẽ đi qua lớp bao bọc hệ thống và được định tuyến tới hàm xử lý chính trong kernel. Cơ chế phân phối lệnh thông qua bảng syscall hoặc sys_socketcall tùy thuộc vào kiến trúc hệ thống.
3. Hàm sys_listen() – Cổng vào hệ thống
Trong tệp net/socket.c, kernel thực hiện các bước xác thực cơ bản và giới hạn tham số trước khi chuyển giao cho tầng giao thức:
// net/socket.c
SYSCALL_DEFINE2(listen, int, fd, int, backlog)
{
struct socket *target_skt;
int ret_val, need_fput;
int sys_limit;
// Truy xuất cấu trúc socket từ file descriptor
target_skt = sockfd_lookup_light(fd, &ret_val, &need_fput);
if (target_skt) {
// Đọc giới hạn hàng đợi toàn cục từ sysctl
sys_limit = sock_net(target_skt->sk)->core.sysctl_somaxconn;
// Nếu người dùng yêu cầu vượt quá ngưỡng hệ thống, ép buộc giảm xuống
if ((unsigned int)backlog > sys_limit)
backlog = sys_limit;
// Kiểm tra bảo mật (LSM/SELinux)
ret_val = security_socket_listen(target_skt, backlog);
if (!ret_val)
// Gọi hàm lắng nghe đặc thù của giao thức
ret_val = target_skt->ops->listen(target_skt, backlog);
fput_light(target_skt->file, need_fput);
}
return ret_val;
}
Con trỏ hàm target_skt->ops->listen thường trỏ đến inet_listen đối với họ giao thức IPv4/IPv6.
4. inet_listen() – Xử lý đặc thù giao thức Internet
Tại net/ipv4/af_inet.c, kernel tiến hành kiểm tra tính hợp lệ của trạng thái socket và thiết lập hàng đợi:
// net/ipv4/af_inet.c
int inet_listen(struct socket *sock, int backlog)
{
struct sock *sk = sock->sk;
unsigned char prev_state;
int status = -EINVAL;
lock_sock(sk);
// Ràng buộc: chỉ áp dụng cho socket dòng chảy (TCP) và chưa kết nối
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto cleanup;
prev_state = sk->sk_state;
// Chỉ cho phép nếu đang ở trạng thái CLOSE hoặc LISTEN
if (!((1 << prev_state) & (TCPF_CLOSE | TCPF_LISTEN)))
goto cleanup;
// Nếu chưa ở trạng thái LISTEN, tiến hành khởi tạo hàng đợi
if (prev_state != TCP_LISTEN) {
// Bỏ qua các logic liên quan đến TCP FastOpen
status = inet_csk_listen_start(sk, backlog);
if (status)
goto cleanup;
}
// Cập nhật ngưỡng chứa kết nối đã hoàn tất bắt tay
sk->sk_max_ack_backlog = backlog;
status = 0;
cleanup:
release_sock(sk);
return status;
}
5. inet_csk_listen_start() – Kích hoạt trạng thái nghe
Hàm này trong net/ipv4/inet_connection_sock.c chịu trách nhiệm cấp phát bộ nhớ cho hàng đợi chờ và đăng ký socket vào bảng băm lắng nghe:
// net/ipv4/inet_connection_sock.c
int inet_csk_listen_start(struct sock *sk, const int queue_size)
{
struct inet_sock *inet = inet_sk(sk);
struct inet_connection_sock *icsk = inet_csk(sk);
// Cấp phát và khởi tạo cấu trúc hàng đợi chấp nhận kết nối
int alloc_res = reqsk_queue_alloc(&icsk->icsk_accept_queue, queue_size);
if (alloc_res != 0)
return alloc_res;
// Thiết lập lại các bộ đếm hàng đợi
sk->sk_max_ack_backlog = 0;
sk->sk_ack_backlog = 0;
inet_csk_delack_init(sk);
// Chuyển trạng thái TCP sang LISTEN
sk->sk_state = TCP_LISTEN;
// Xác minh cổng mạng và đưa vào bảng định tuyến lắng nghe
if (!sk->sk_prot->get_port(sk, inet->inet_num)) {
inet->inet_sport = htons(inet->inet_num);
sk_dst_reset(sk);
// Thêm sock vào hash table toàn cục để xử lý gói SYN đến
sk->sk_prot->hash(sk);
return 0;
}
// Xử lý xung đột cổng
sk->sk_state = TCP_CLOSE;
__reqsk_queue_destroy(&icsk->icsk_accept_queue);
return -EADDRINUSE;
}
6. Cơ chế cấp phát hàng đợi: reqsk_queue_alloc()
Kernel phân biệt rõ ràng giữa hai loại hàng đợi trong quá trình bắt tay ba bước TCP. Cấu trúc quản lý được định nghĩa như sau:
// include/net/request_sock.h
struct request_sock_queue {
// Hàng đợi toàn phần (Accept Queue): chứa kết nối đã hoàn tất bắt tay
struct request_sock *rskq_accept_head;
struct request_sock *rskq_accept_tail;
// Hàng đợi bán phần (SYN Queue): chờ phản hồi ACK từ client
struct listen_sock *listen_opt;
// ... các trường đồng bộ hóa
};
Hàng đợi toàn phần hoạt động theo cơ chế FIFO, do đó chỉ cần con trỏ đầu và cuối. Ngược lại, hàng đợi bán phần cần tra cứu nhanh khi nhận gói ACK thứ ba, nên kernel sử dụng bảng băm:
// include/net/request_sock.h
struct listen_sock {
u8 max_qlen_log;
u32 nr_table_entries;
// ...
struct request_sock *syn_table[0]; // Mảng linh hoạt chứa các bucket băm
};
Quá trình tính toán kích thước và cấp phát bộ nhớ diễn ra trong net/core/request_sock.c:
// net/core/request_sock.c
int reqsk_queue_alloc(struct request_sock_queue *queue, unsigned int req_entries)
{
size_t mem_size = sizeof(struct listen_sock);
struct listen_sock *lopt;
// Điều chỉnh kích thước bảng băm dựa trên tham số hệ thống
req_entries = min_t(u32, req_entries, sysctl_max_syn_backlog);
req_entries = max_t(u32, req_entries, 8);
req_entries = roundup_pow_of_two(req_entries + 1);
// Tính tổng bộ nhớ cần thiết cho cấu trúc và mảng băm
mem_size += req_entries * sizeof(struct request_sock *);
// Lựa chọn phương thức cấp phát tùy thuộc vào kích thước
if (mem_size > PAGE_SIZE)
lopt = vzalloc(mem_size);
else
lopt = kzalloc(mem_size, GFP_KERNEL);
if (!lopt)
return -ENOMEM;
// Tính toán log2 của độ dài hàng đợi để tối ưu phép toán bitmask
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < req_entries;
lopt->max_qlen_log++);
get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd));
rwlock_init(&queue->syn_wait_lock);
// Khởi tạo trạng thái rỗng cho hàng đợi toàn phần
queue->rskq_accept_head = NULL;
lopt->nr_table_entries = req_entries;
write_lock_bh(&queue->syn_wait_lock);
queue->listen_opt = lopt;
write_unlock_bh(&queue->syn_wait_lock);
return 0;
}
7. Tổng kết kỹ thuật
Hệ thống gọi listen thực hiện chuỗi thao tác cốt lõi sau:
- Ánh xạ file descriptor sang cấu trúc
socketvàsocktương ứng trong kernel. - Kiểm tra ràng buộc loại socket (chỉ hỗ trợ
SOCK_STREAM) và trạng thái TCP hiện tại. - Chuyển đổi trạng thái máy trạng thái TCP sang
TCP_LISTEN. - Khởi tạo cấu trúc hàng đợi kép: hàng đợi bán phần (SYN queue) dùng bảng băm để tra cứu nhanh, và hàng đợi toàn phần (Accept queue) dùng danh sách liên kết đơn FIFO.
- Thiết lập ngưỡng
sk->sk_max_ack_backlogdựa trên tham sốbacklogvà giới hạnsomaxconn, đồng thời reset bộ đếm hiện tạisk->sk_ack_backlogvề 0. - Đăng ký socket vào bảng băm lắng nghe toàn cục, cho phép bộ xử lý mạng định tuyến các gói SYN đến đúng tiến trình đang chờ.