Linuxカーネルネットワークプロトコルスタック8-socketリスニング
いくつかの問題は以下のいくつかの問題を理解している学生は直接以下を無視することができます.
1、listenライブラリ関数は主に何をしましたか.2、最大同時接続要求数とは?3、接続待ちキューとは?socketリスニングは比較的簡単で、まずアプリケーションコードを見てみましょう.
ここで、最初のパラメータserver_sockfdはサービス側socketに対応するファイル記述子であり、第2のパラメータ5は、リスニングsocketが処理できる最大同時接続要求数を表し、2.6.26カーネルでは256である.Listenライブラリ関数呼び出しの主な作業は以下のステップに分けることができる:1、socketファイル記述子に基づいてカーネル内の対応するsocket構造体変数を見つける;このプロセスは「ソケットアドレスバインド」の文で説明されており、ここでは再記述しない.2、socketの状態を設定し、待機接続キューを初期化する.3、socketをlistenハッシュテーブルに入れる.Listen呼び出しコード追跡の次はlistenライブラリ関数に対応するカーネル処理関数です.
『ソケットの作成』の説明によれば、ここでsock->ops->listen(sock,backlog)が実際に呼び出したのはnet/ipv 4/Af_であるinet.c:inet_Listen()関数:
上のコードでは、sock状態がTCP_である場合に注意してください.LISTENの場合、listen()ライブラリ関数を呼び出し続けることもできます.sockの最大同時接続要求数を設定する役割を果たします.次にinet_を見てみましょうcsk_listen_start()関数:
ここでnr_table_entriesはパラメータbacklogが最大値で調整された値である.関連するデータ構造は、次のコードにいくつかのデータ構造が記載されているのを見てみましょう.
1 、 request_sock
ソケットがリスニングされると、他のホストからのtcpソケットの接続要求が受け入れられると(握手プロトコルが3回完了する)、request_が確立されます.sockは、要求socketとの間のtcp接続を確立する.このrequest_sockは先進的な先頭キューに配置され、acceptシステム呼び出しの処理を待つ.
2 、 listen_sock
新しく作成されたrequest_sockはsyn_に保存されていますtable;これはハッシュ配列でnr_がありますtable_entries項目;
メンバーmax_qlen_logは2の対数でrequest_を表すsockキューの最大値;
qlenはキューの現在の長さです.
hash_rndはランダム数であり、ハッシュ値を計算するために使用される.
3 、 request_sock_queue
構造体struct request_sock_Queueのrskq_accept_headとrskq_accept_tailはrequest_をそれぞれ指すsockキューのキューヘッダとキューヘッダ;
接続キューの初期化待ち
まずreqsk_を見てみましょうqueue_alloc()のソースコード:
プロセス全体でrequest_を計算します.sockのサイズを指定してスペースを申請し、request_を初期化します.sock_queueの対応するメンバーの値.
TCP_LISTENのソケット管理
「ポート管理」では、socketを管理するハッシュ・テーブル構造inet_について説明します.hashinfo、メンバーlistening_hash[INET_LHTABLE_SIZE]TCP_に格納LISTEN状態のsock;
socketがlisten()呼び出しによって接続待ちキューの初期化が完了すると、現在のsockを構造体に配置する必要があります.
ここでnet/ipv 4/Inet_を呼び出しましたhashtables.c:inet_hash()メソッド:
これを知って、文初さんが出した3つの質問に答えると、何の問題もないでしょう.
1、listenライブラリ関数は主に何をしましたか.2、最大同時接続要求数とは?3、接続待ちキューとは?socketリスニングは比較的簡単で、まずアプリケーションコードを見てみましょう.
listen( server_sockfd, 5) ;
ここで、最初のパラメータserver_sockfdはサービス側socketに対応するファイル記述子であり、第2のパラメータ5は、リスニングsocketが処理できる最大同時接続要求数を表し、2.6.26カーネルでは256である.Listenライブラリ関数呼び出しの主な作業は以下のステップに分けることができる:1、socketファイル記述子に基づいてカーネル内の対応するsocket構造体変数を見つける;このプロセスは「ソケットアドレスバインド」の文で説明されており、ここでは再記述しない.2、socketの状態を設定し、待機接続キューを初期化する.3、socketをlistenハッシュテーブルに入れる.Listen呼び出しコード追跡の次はlistenライブラリ関数に対応するカーネル処理関数です.
asmlinkage long sys_listen(int fd, int backlog)
{
struct socket *sock;
int err, fput_needed;
int somaxconn;
// socket
sock = sockfd_lookup_light(fd, &err, &fput_needed);
if (sock) {
// backlog
somaxconn = sock_net(sock->sk)->core.sysctl_somaxconn;
if ((unsigned)backlog > somaxconn)
backlog = somaxconn;
err = security_socket_listen(sock, backlog);
// listen
if (!err)
err = sock->ops->listen(sock, backlog);
fput_light(sock->file, fput_needed);
}
return err;
}
『ソケットの作成』の説明によれば、ここでsock->ops->listen(sock,backlog)が実際に呼び出したのはnet/ipv 4/Af_であるinet.c: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;
// 1 socket , ,
if (sock->state != SS_UNCONNECTED || sock->type != SOCK_STREAM)
goto out;
old_state = sk->sk_state;
// 2 sock TCP_CLOSE TCP_LISTEN, ,
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.
*/
// 3 sock TCP_LISTEN ,
if (old_state != TCP_LISTEN) {
err = inet_csk_listen_start(sk, backlog);
if (err)
goto out;
}
// 4 sock
sk->sk_max_ack_backlog = backlog;
err = 0;
out:
release_sock(sk);
return err;
}
上のコードでは、sock状態がTCP_である場合に注意してください.LISTENの場合、listen()ライブラリ関数を呼び出し続けることもできます.sockの最大同時接続要求数を設定する役割を果たします.次に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);
// sock TCP_LISTEN
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;
}
ここでnr_table_entriesはパラメータbacklogが最大値で調整された値である.関連するデータ構造は、次のコードにいくつかのデータ構造が記載されているのを見てみましょう.
1 、 request_sock
struct request_sock {
struct request_sock *dl_next; /* Must be first */
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;
};
ソケットがリスニングされると、他のホストからのtcpソケットの接続要求が受け入れられると(握手プロトコルが3回完了する)、request_が確立されます.sockは、要求socketとの間のtcp接続を確立する.このrequest_sockは先進的な先頭キューに配置され、acceptシステム呼び出しの処理を待つ.
2 、 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];
};
新しく作成されたrequest_sockはsyn_に保存されていますtable;これはハッシュ配列でnr_がありますtable_entries項目;
メンバーmax_qlen_logは2の対数でrequest_を表すsockキューの最大値;
qlenはキューの現在の長さです.
hash_rndはランダム数であり、ハッシュ値を計算するために使用される.
3 、 request_sock_queue
struct request_sock_queue {
struct request_sock *rskq_accept_head;
struct request_sock *rskq_accept_tail;
rwlock_t syn_wait_lock;
u16 rskq_defer_accept;
/* 2 bytes hole, try to pack */
struct listen_sock *listen_opt;
};
構造体struct request_sock_Queueのrskq_accept_headとrskq_accept_tailはrequest_をそれぞれ指すsockキューのキューヘッダとキューヘッダ;
接続キューの初期化待ち
まず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;
// 1 nr_table_entries 8~ sysctl_max_syn_backlog
nr_table_entries = min_t(u32, nr_table_entries, sysctl_max_syn_backlog);
nr_table_entries = max_t(u32, nr_table_entries, 8);
// 2 2
nr_table_entries = roundup_pow_of_two(nr_table_entries + 1);
// 3
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;
// 4 listen_sock max_qlen_log 3, nr_table_entries
for (lopt->max_qlen_log = 3;
(1 << lopt->max_qlen_log) < nr_table_entries;
lopt->max_qlen_log++);
// 5
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;
}
プロセス全体でrequest_を計算します.sockのサイズを指定してスペースを申請し、request_を初期化します.sock_queueの対応するメンバーの値.
TCP_LISTENのソケット管理
「ポート管理」では、socketを管理するハッシュ・テーブル構造inet_について説明します.hashinfo、メンバーlistening_hash[INET_LHTABLE_SIZE]TCP_に格納LISTEN状態のsock;
socketがlisten()呼び出しによって接続待ちキューの初期化が完了すると、現在のsockを構造体に配置する必要があります.
if (!sk->sk_prot->get_port(sk, inet->num)) {
//
inet->sport = htons(inet->num);
sk_dst_reset(sk);
// socket inet_hashinfo
sk->sk_prot->hash(sk);
return 0;
}
ここでnet/ipv 4/Inet_を呼び出しましたhashtables.c:inet_hash()メソッド:
void inet_hash(struct sock *sk)
{
if (sk->sk_state != TCP_CLOSE) {
local_bh_disable();
__inet_hash(sk);
local_bh_enable();
}
}
static void __inet_hash(struct sock *sk)
{
// inet_hashinfo
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));
// hash ,
list = &hashinfo->listening_hash[inet_sk_listen_hashfn(sk)];
lock = &hashinfo->lhash_lock;
inet_listen_wlock(hashinfo);
// sock
__sk_add_node(sk, list);
sock_prot_inuse_add(sock_net(sk), sk->sk_prot, 1);
write_unlock(lock);
wake_up(&hashinfo->lhash_wait);
}
これを知って、文初さんが出した3つの質問に答えると、何の問題もないでしょう.