Linux内核网络协议栈6

http://blog.chinaunix.net/uid-22359610-id-1991600.html

几个问题了解以下几个问题的同学可以直接忽略下文:

1、listen库函数主要做了什么?2、什么是最大并发连接请求数?3、什么是等待连接队列?Socket监听相对还是比较简单的,先看下应用程序代码:

    listen(server_sockfd,5);

其中,第一个参数server_sockfd为服务端socket所对应的文件描述符,第二个参数5代表监听socket能处理的最大并发连接请求数,在2.6.26内核中,该值为256;listen库函数调用的主要工作可以分为以下几步:1、根据socket文件描述符找到内核中对应的socket结构体变量;这个过程在《socket地址绑定》一文中描述过,这里不再重述;2、设置socket的状态并初始化等待连接队列;3、将socket放入listen哈希表中;listen调用代码跟踪下面是listen库函数对应的内核处理函数:

    asmlinkage long sys_listen(intfd,intbacklog){struct socket*sock;interr,fput_needed;intsomaxconn;//根据文件描述符取得内核中的socketsock=sockfd_lookup_light(fd,&err,&fput_needed);if(sock){//根据系统中的设置调整参数backlogsomaxconn=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);}returnerr;}

根据《创建socket》一文的介绍,例子中,这里sock->ops->listen(sock, backlog)实际上调用的是net/ipv4/Af_inet.c:inet_listen()函数:

    intinet_listen(struct socket*sock,intbacklog){struct sock*sk=sock->sk;unsigned char old_state;interr;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,ifthe socketisalreadyinlisten state*we can only allow the backlogtobe 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);returnerr;}

上面的代码中,有点值得注意的是,当sock状态已经是TCP_LISTEN时,也可以继续调用listen()库函数,其作用是设置sock的最大并发连接请求数;下面看看inet_csk_listen_start()函数:

    intinet_csk_listen_start(struct sock*sk,constintnr_table_entries){struct inet_sock*inet=inet_sk(sk);struct inet_connection_sock*icsk=inet_csk(sk);//初始化连接等待队列intrc=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_LISTENsk->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 tcpoptsintimestamp*//*The following two fields can be easily recomputed I think-AK*/u32 window_clamp;/*windowclamp at creationtime*/u32 rcv_wnd;/*rcv_wnd offered firsttime*/u32 ts_recent;unsigned long expires;conststruct request_sock_ops*rsk_ops;struct sock*sk;u32 secid;u32 peer_secid;};

socket在侦听的时候,那些来自其它主机的tcp socket的连接请求一旦被接受(完成三次握手协议),便会建立一个request_sock,建立与请求socket之间的一个tcp连接。该request_sock会被放在一个先进先出的队列中,等待accept系统调用的处理;

2、listen_sock

    struct listen_sock{u8 max_qlen_log;/*3 bytes hole,trytouse*/intqlen;intqlen_young;intclock_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,trytopack*/struct listen_sock*listen_opt;};

结构体struct request_sock_queue中的rskq_accept_head和rskq_accept_tail分别指向request_sock队列的队列头和队列尾;

等待连接队列初始化

先看下reqsk_queue_alloc()的源代码:

    intreqsk_queue_alloc(struct request_sock_queue*queue,unsignedintnr_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);elselopt=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管理

在《端口管理》一文中提到管理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/ipv4/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个问题,应该没什么问题了吧J

人,总是很难改正自己的缺点,

Linux内核网络协议栈6

相关文章:

你感兴趣的文章:

标签云: