sock->ops->listen(sock, backlog);
我们首先看到是找到我们已经建立的socket然后通过钩子结构struct proto_ops中的listen钩子函数执行监听过程。关于socket的这个钩子结构是何时挂入的,请朋友们看创建那节的分析过程,在那一节中的我们将会看出是挂入的inet_stream_ops
结构。关于struct proto_ops的内容已经在那篇文章中列出了。我们直接从inet_stream_ops结构开始看起。请朋友们注意backlog是我们从练习程序中的连接个数10。
我们来看一下这个结构中的listen钩子
const struct proto_ops inet_stream_ops = { 。。。。。。 .listen = inet_listen, 。。。。。。 };
|
很明显,挂入的是inet_listen函数,这个函数在/net/ipv4/af_inet.c中的194行处
sys_socketcall()-->sys_listen()-->inet_listen()
int inet_listen(struct socket *sock, int backlog) { struct sock *sk = sock->sk; unsigned char old_state; int err;
lock_sock(sk);
err = -EINVAL; if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM) goto out;
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) { err = inet_csk_listen_start(sk, backlog); if (err) goto out; } sk->sk_max_ack_backlog = backlog; err = 0;
out: release_sock(sk); return err; }
|
函数中首先是对socket的状态进行检测,然后如果没有处于监听状态则进入inet_csk_listen_start()函数中
sys_socketcall()-->sys_listen()-->inet_listen()-->inet_csk_listen_start()
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); int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);
if (rc != 0) return rc;
sk->sk_max_ack_backlog = 0; sk->sk_ack_backlog = 0; 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. */ sk->sk_state = TCP_LISTEN; if (!sk->sk_prot->get_port(sk, inet->num)) { inet->sport = htons(inet->num);
sk_dst_reset(sk); sk->sk_prot->hash(sk);
return 0; }
sk->sk_state = TCP_CLOSE; __reqsk_queue_destroy(&icsk->icsk_accept_queue); return -EADDRINUSE; }
|
这个函数在/net/ipv4/inet_connection_sock.c中的562行处,我们来看一下,首先是通过inet_sk()将我们的socket转换成TCP的socket结构,并用inet指针指向他,接着用inet_csk()将socket转换成struct inet_connection_sock结构指针并用icsk指向。这个结构是专门用来连接用的结构
struct inet_connection_sock { /* inet_sock has to be the first member! */ struct inet_sock icsk_inet; struct request_sock_queue icsk_accept_queue; struct inet_bind_bucket *icsk_bind_hash; unsigned long icsk_timeout; struct timer_list icsk_retransmit_timer; struct timer_list icsk_delack_timer; __u32 icsk_rto; __u32 icsk_pmtu_cookie; const struct tcp_congestion_ops *icsk_ca_ops; const struct inet_connection_sock_af_ops *icsk_af_ops; unsigned int (*icsk_sync_mss)(struct sock *sk, u32 pmtu); __u8 icsk_ca_state; __u8 icsk_retransmits; __u8 icsk_pending; __u8 icsk_backoff; __u8 icsk_syn_retries; __u8 icsk_probes_out; __u16 icsk_ext_hdr_len; struct { __u8 pending; /* ACK is pending */ __u8 quick; /* Scheduled number of quick acks */ __u8 pingpong; /* The session is interactive */ __u8 blocked; /* Delayed ACK was blocked by socket lock */ __u32 ato; /* Predicted tick of soft clock */ unsigned long timeout; /* Currently scheduled timeout */ __u32 lrcvtime; /* timestamp of last received data packet */ __u16 last_seg_size; /* Size of last incoming segment */ __u16 rcv_mss; /* MSS used for delayed ACK decisions */ } icsk_ack; struct { int enabled;
/* Range of MTUs to search */ int search_high; int search_low;
/* Information on the current probe. */ int probe_size; } icsk_mtup; u32 icsk_ca_priv[16]; #define ICSK_CA_PRIV_SIZE (16 * sizeof(u32)) };
|
还是我们那句话“先混个面熟”,用时再说具体的变量作用,因为我们下面要用到这个结构所以这里全部贴出。我们再看inet_csk_listen_start()函数,这里需要我们看一下在以前练习中提到的程序有一句代码:
listen
(
server_sockfd
,
10
);
这二个参数我们不用多介绍了,nr_table_entries就是传递过来的数值10,所以我们在代码中阅读一定要注意,这个数值是用于总共允许多少个客户端的socket连接数目,如果超过了这个数目客户端的socket只好睡眠等待了。我们看到reqsk_queue_alloc()函数,这个函数就是为了保证我们上面所述的功能
sys_socketcall()-->sys_listen()-->inet_listen()-->inet_csk_listen_start()-->reqsk_queue_alloc()
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;
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); 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;
for (lopt->max_qlen_log = 3; (1 << lopt->max_qlen_log) < nr_table_entries; lopt->max_qlen_log++);
get_random_bytes(&lopt->hash_rnd, sizeof(lopt->hash_rnd)); rwlock_init(&queue->syn_wait_lock); queue->rskq_accept_head = NULL; lopt->nr_table_entries = nr_table_entries;
write_lock_bh(&queue->syn_wait_lock); queue->listen_opt = lopt; write_unlock_bh(&queue->syn_wait_lock);
return 0; }
|
我们在这里对比一下unix的socket的监听http://blog.chinaunix.net/u2/64681/showart.php?id=1327663,那里非常的简单只是检查一下连接数并设置一下相应的状态为监听状态就完成了。而TCP的监听过程相对比较复杂了。我们看到上面的函数中又出现了一种新的结构struct listen_sock,这个结构是专用于监听状态作用
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]; };
|
代码中的求最小宏min_t()引用了 int sysctl_max_syn_backlog = 256;非常容易理解后面的求最大值,接着看到roundup_pow_of_two宏,在/include/linux/log2.h文件中
#define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ (n == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ ) #define roundup_pow_of_two(n) \ ( \ __builtin_constant_p(n) ? ( \ (n == 1) ? 1 : \ (1UL << (ilog2((n) - 1) + 1)) \ ) : \ __roundup_pow_of_two(n) \ ) static inline __attribute__((const)) unsigned long __roundup_pow_of_two(unsigned long n) { return 1UL << fls_long(n - 1); }
|
其中__builtin_constant_p(n)是gcc编译器检查n是否为常数,我们介绍一下__roundup_pow_of_two函数,这个函数是主要作用是判断long类型的n是32位还是64位并向左移动想当于求2的次幂,而fls_long()代码纯粹是判断是32位和64位了
static inline unsigned fls_long(unsigned long l) { if (sizeof(l) == 4) return fls(l); return fls64(l); }
|
我们经过最大值的
max_t之后
进入
(1UL << (ilog2((n) - 1) + 1))
#define ilog2(n) \ ( \ __builtin_constant_p(n) ? ( \ (n) < 1 ? ____ilog2_NaN() : \ (n) & (1ULL << 63) ? 63 : \ (n) & (1ULL << 62) ? 62 : \ (n) & (1ULL << 61) ? 61 : \ (n) & (1ULL << 60) ? 60 : \ (n) & (1ULL << 59) ? 59 : \ (n) & (1ULL << 58) ? 58 : \ (n) & (1ULL << 57) ? 57 : \ (n) & (1ULL << 56) ? 56 : \ (n) & (1ULL << 55) ? 55 : \ (n) & (1ULL << 54) ? 54 : \ (n) & (1ULL << 53) ? 53 : \ (n) & (1ULL << 52) ? 52 : \ (n) & (1ULL << 51) ? 51 : \ (n) & (1ULL << 50) ? 50 : \ (n) & (1ULL << 49) ? 49 : \ (n) & (1ULL << 48) ? 48 : \ (n) & (1ULL << 47) ? 47 : \ (n) & (1ULL << 46) ? 46 : \ (n) & (1ULL << 45) ? 45 : \ (n) & (1ULL << 44) ? 44 : \ (n) & (1ULL << 43) ? 43 : \ (n) & (1ULL << 42) ? 42 : \ (n) & (1ULL << 41) ? 41 : \ (n) & (1ULL << 40) ? 40 : \ (n) & (1ULL << 39) ? 39 : \ (n) & (1ULL << 38) ? 38 : \ (n) & (1ULL << 37) ? 37 : \ (n) & (1ULL << 36) ? 36 : \ (n) & (1ULL << 35) ? 35 : \ (n) & (1ULL << 34) ? 34 : \ (n) & (1ULL << 33) ? 33 : \ (n) & (1ULL << 32) ? 32 : \ (n) & (1ULL << 31) ? 31 : \ (n) & (1ULL << 30) ? 30 : \ (n) & (1ULL << 29) ? 29 : \ (n) & (1ULL << 28) ? 28 : \ (n) & (1ULL << 27) ? 27 : \ (n) & (1ULL << 26) ? 26 : \ (n) & (1ULL << 25) ? 25 : \ (n) & (1ULL << 24) ? 24 : \ (n) & (1ULL << 23) ? 23 : \ (n) & (1ULL << 22) ? 22 : \ (n) & (1ULL << 21) ? 21 : \ (n) & (1ULL << 20) ? 20 : \ (n) & (1ULL << 19) ? 19 : \ (n) & (1ULL << 18) ? 18 : \ (n) & (1ULL << 17) ? 17 : \ (n) & (1ULL << 16) ? 16 : \ (n) & (1ULL << 15) ? 15 : \ (n) & (1ULL << 14) ? 14 : \ (n) & (1ULL << 13) ? 13 : \ (n) & (1ULL << 12) ? 12 : \ (n) & (1ULL << 11) ? 11 : \ (n) & (1ULL << 10) ? 10 : \ (n) & (1ULL << 9) ? 9 : \ (n) & (1ULL << 8) ? 8 : \ (n) & (1ULL << 7) ? 7 : \ (n) & (1ULL << 6) ? 6 : \ (n) & (1ULL << 5) ? 5 : \ (n) & (1ULL << 4) ? 4 : \ (n) & (1ULL << 3) ? 3 : \ (n) & (1ULL << 2) ? 2 : \ (n) & (1ULL << 1) ? 1 : \ (n) & (1ULL << 0) ? 0 : \ ____ilog2_NaN() \ ) : \ (sizeof(n) <= 4) ? \ __ilog2_u32(n) : \ __ilog2_u64(n) \ )
|
最后我们的连接个数被确定下来。接下来我们看到了一个新的数据结构
struct request_sock
是用来代表socket连接请求用的数据结构
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; };
|
这个结构大小与我们的连接数确定了我们要在通用的高速缓存中分配内存给struct listen_sock 结构变量指针
lopt
,我们看到分配成功后将lopt的nr_table_entries 连接数设置为我们最大连接数,然后将
inet_csk_listen_start
函数中的
inet_connection_sock
结构变量
icsk
中的
icsk_accept_queue
与这里新分配的
listen_sock
建起关联,
icsk_accept_queue
是一个
struct request_sock_queue
结构
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; };
|
这个结构是专门用于请求连接的socket所使用的队列结构。我们看到在代码中
queue->listen_opt = lopt;
这句代码将icsk中的
icsk_accept_queue
->listen_opt
与这里的lopt挂上钩了。我们再回到
inet_csk_listen_start
()函数中,继续往下看,接着看到调用了
inet_csk_delack_init
()函数将TCP的sock结构中转化为
inet_connection_sock
结构指针然后初始化他内部的结构变量
icsk_ack
为0。
struct { __u8 pending; /* ACK is pending */ __u8 quick; /* Scheduled number of quick acks */ __u8 pingpong; /* The session is interactive */ __u8 blocked; /* Delayed ACK was blocked by socket lock */ __u32 ato; /* Predicted tick of soft clock */ unsigned long timeout; /* Currently scheduled timeout */ __u32 lrcvtime; /* timestamp of last received data packet */ __u16 last_seg_size; /* Size of last incoming segment */ __u16 rcv_mss; /* MSS used for delayed ACK decisions */ } icsk_ack;
|
这个结构变量是为了连接中的“应答”使用的。然后我们看到
sk->sk_state = TCP_LISTEN;
将
TCP
的
socket
状态设置为了
TCP_LISTEN
。我们接下来看到是对端口的操作,这部分内容已经在我的博客文章
http://blog.chinaunix.net/u2/64681/showart_1387214.html
中详细讲到了,这里就不再论述了。此后会进入sk->sk_prot->hash(sk)代码处执行
参考http://blog.chinaunix.net/u2/64681/showart_1360583.html 那里的
struct
proto tcp_prot
结构变量可以看到
.
hash
=
inet_hash
很显然是执行的钩子函数inet_hash这个函数在/net/ipv4/inet_hashtables.c中的379行处
sys_socketcall()-->sys_listen()-->inet_listen()-->inet_csk_listen_start()-->inet_hash()
void inet_hash(struct sock *sk) { if (sk->sk_state != TCP_CLOSE) { local_bh_disable(); __inet_hash(sk); local_bh_enable(); } }
|
接着又进入
sys_socketcall()-->sys_listen()-->inet_listen()-->inet_csk_listen_start()-->inet_hash()--> __inet_hash()
static void __inet_hash(struct sock *sk) { struct inet_hashinfo *hashinfo = sk->sk_prot->h.hashinfo; struct hlist_head *list; rwlock_t *lock;
if (sk->sk_state != TCP_LISTEN) { __inet_hash_nolisten(sk); return; }
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(sock_net(sk), sk->sk_prot, 1); write_unlock(lock); wake_up(&hashinfo->lhash_wait); }
|
这函数主要是将
sock
挂入与已经初始化的
TCP
的
hash
表中,关于
sk->sk_prot->h.hashinfo
的
TCP
的
hash
表的初始化请看我在
TCP
的
socket
地址绑定中的分析部分
http://blog.chinaunix.net/u2/64681/showart_1387214.html
在那里是将
tcp_hashinfo
挂入的
struct inet_hashinfo __cacheline_aligned tcp_hashinfo = { .lhash_lock = __RW_LOCK_UNLOCKED(tcp_hashinfo.lhash_lock), .lhash_users = ATOMIC_INIT(0), .lhash_wait = __WAIT_QUEUE_HEAD_INITIALIZER(tcp_hashinfo.lhash_wait), };
|
这个结构是inet_hashinfo数据结构类型,所以在其内部有一个专用于listen的hash队列
struct
hlist_head listening_hash
[
INET_LHTABLE_SIZE
];
这里取得hash链头后,通过
__sk_add_node
()将sock挂入到hash队列中。接着根据全局的网络空间结构变量init_net取得当前cpu的结构信息中的关于协议的使用计数,对其加1操作。最后唤醒在hash表队列中等待进程,这是通过wake_up(&hashinfo->lhash_wait)来实现的。这个唤醒函数请朋友们参阅深入理解内核函数来理解,这里就不看了。