tcp listen调用和accept调用

news/2024/5/17 19:25:26 标签: tcp, struct, table, socket, transition, validation

在看listen的代码之前.我们也先来看相关的数据结构:
inet_connection_sock它包含了一个icsk_accept_queue的域,这个域是一个request_sock_queue类型,.我们就先来看这个结构:

request_sock_queue也就表示一个request_sock队列.这里我们知道,tcp中分为半连接队列(处于SYN_RECVD状态)和已完成连接队列(处于established状态).这两个一个是刚接到syn,等待三次握手完成,一个是已经完成三次握手,等待accept来读取.

这里每个syn分节到来都会新建一个request_sock结构,并将它加入到listen_sock的request_sock hash表中.然后3次握手完毕后,将它放入到request_sock_queue的rskq_accept_head和rskq_accept_tail队列中.这样当accept的时候就直接从这个队列中读取了.

  1. struct request_sock_queue {   
  2. ///一个指向头,一个指向结尾.  
  3.      struct request_sock *rskq_accept_head;   
  4.      struct request_sock *rskq_accept_tail;   
  5.      rwlock_t         syn_wait_lock;   
  6.      u8           rskq_defer_accept;   
  7.     /* 3 bytes hole, try to pack */  
  8. ///相应的listen_socket结构.  
  9.      struct listen_sock   *listen_opt;   
  10. };  
  11. struct listen_sock {   

  12.      u8           max_qlen_log;   
  13. /*qlen最大长度取对数log,即log2 (max_qlen),这个值在进入SYN/ACK定时器时有用*/
  14.     /* 3 bytes hole, try to use */    
  15.     int          qlen;   
  16. ‍‍///当前的半连接队列的长度.
  17.     int          qlen_young;   
  18. /*‍也是指当前的半开连接队列长度,不过这个值会当重传syn/ack的时候(这里要注意是这个syn/ack第一次重传的时候才会减一)自动减
  19. 一,也就是重传了SYN/ACK的request_sock不在是新的request_sock,在SYN/ACK定时器时有用*/
  20.     int          clock_hand;   
  21. /*每次SYN-ACK定时器超时时,我们需要遍历SYN队列哈希表,但表太大了,所以每次都只遍历部分哈希表,而每次遍历完,将哈希索引值放在clock_hand这里,下次遍历时直接从clock_hand开始,而不用从头开始*/
  22.      u32          hash_rnd;   
  23. ///这个值表示了当前的syn_backlog(半开连接队列)的最大值  
  24.      u32          nr_table_entries;   
  25. ///半连接队列.  
  26.      struct request_sock *syn_table[0];   
  27. };  

request_sock的结构就不在这里贴出来了,我们只要知道每一个SYN请求都会新建一个request_sock结构,并将它加入到listen_sock的syn_table哈希表中,然后接收端会发送一个SYN/ACK段给SYN请求端,当SYN请求端将3次握手的最后一个ACK发送给接收端后,并且接收端判断ACK正确,则将request_sock从syn_table哈希表中删除,将request_sock加入到request_sock_queue的rskq_accept_head和rskq_accept_tail队列中,最后的accept系统调用不过是判断accept队列是否存在完成3次请求的request_sock,从这个队列中将request_sock结构释放,然后在BSD层新建一个socket结构,并将它和接收端新建的子sock结构关联起来。

我们可以想到,listen系统调用必然要分配一个listen_sock结构,其实也正如此,inet_listen系统调用最终会调用‍inet_csk_listen_start函数,

它的主要工作是新分配一个listen socket,将它加入到inet_connection_sock的icsk_accept_queue域的listen_opt中.然后对当前使用端口进行判断.最终返回,

  1. int inet_csk_listen_start(struct sock *sk, const int nr_table_entries)   
  2. {   
  3.      struct inet_sock *inet = inet_sk(sk);   
  4.      struct inet_connection_sock *icsk = inet_csk(sk);   
  5. ///新分配一个listen_sock,并让request_sock_queue->listen_opt指向它
  6.     int rc = reqsk_queue_alloc(&icsk->icsk_accept_queue, nr_table_entries);   
  7.   
  8.     if (rc != 0)   
  9.         return rc;   
  10. ///先将这两个ack_backlog赋值为0.  
  11.      sk->sk_max_ack_backlog = 0;   
  12.      sk->sk_ack_backlog = 0;   
  13.      inet_csk_delack_init(sk);   
  14.   
  15.     /* There is race window here: we announce ourselves listening,
  16.       * but this transition is still not validated by get_port().
  17.       * It is OK, because this socket enters to hash table only
  18.       * after validation is complete.
  19.       */  
  20. ///设置状态.  
  21.      sk->sk_state = TCP_LISTEN;   
  22. ///get_port上面已经分析过了.这里之所以还要再次判断一下端口,是为了防止多线程,也就是另一个线程在我们调用listen之前改变了这个端口的信息.  
  23.     if (!sk->sk_prot->get_port(sk, inet->num)) {   
  24. //端口可用的情况,将端口值付给sport,并加入到inet_hashinfo(上面已经分析过)的listening_hash hash链表中.  
  25.          inet->sport = htons(inet->num);   
  26.   
  27.          sk_dst_reset(sk);   
  28. ///这里调用__inet_hash实现的.  
  29.          sk->sk_prot->hash(sk);   
  30.   
  31.         return 0;   
  32.      }   
  33. ///不可用,则返回错误.  
  34.      sk->sk_state = TCP_CLOSE;   
  35.      __reqsk_queue_destroy(&icsk->icsk_accept_queue);   
  36.     return -EADDRINUSE;   
  37. }  

inet_accept系统调用最终会调用inet_csk_accept,‍inet_csk_accept调用reqsk_queue_get_child从accept队列中取一个request_sock,得到request_sock对应的sock,然后将request_scok删除,这里的目的主要是为了得到request_soc的对应的sock,还在BSD层生成相应的sock结构,‍reqsk_queue_get_child代码如下(2.6.32内核):

static inline struct sock *reqsk_queue_get_child(struct request_sock_queue *queue,
       struct sock *parent)
{
struct request_sock *req = reqsk_queue_remove(queue);
struct sock *child = req->sk;

WARN_ON(child == NULL);

sk_acceptq_removed(parent);
__reqsk_free(req);
return child;
}



http://www.niftyadmin.cn/n/1402578.html

相关文章

玩转调试机制——终极隐藏之道

来源:http://bbs.2cto.com/simple/?t97534.html 引言 近几年来,出现了很多可以在系统被黑过后,仍然保持持久化的技术和方法。其中大多将焦点关注于系统调用表,还有的集中于修改中断处理句柄,剩下的则在VFS&#xff…

Linux进程切换分析

Linux切换并没有使用X86CPU的切换方法,Linux切换的实质就是cr3切换(内存空间切换,在switch_mm函数中) 寄存器切换(包括EIP,ESP等,均在switch_to函数中)。这里我们讲述下switch_to主流…

request_mem_region,ioremap

Linux在头文件include/linux/ioport.h中定义了三个对I/O内存资源进行操作的宏: (1)request_mem_region()宏,请求分配指定的I/O内存资源。 (2)check_mem_region()宏,检查指定的I/O内存资源是否已被占用。 (3)release_mem_region()宏&#xff0…

写DM9000网卡芯片驱动的预备知识

ETHERNET的接口实质是MAC通过MII总线控制PHY的过程。 MAC是Media Access Control 的缩写,即媒体访问控制子层协议。该协议位于OSI七层协议中数据链路层的下半部分,主要负责控制与连接物理层的物理介质。在发送数据的时候,MAC协议可以事先判断…

Linux内核和用户空间通信的方式(一)— proc文件和mmap共享内存

之所以想写这篇文章,是有两个方面原因。其一是内核版有一个关于《内核可以从线性地址直接计算物理地址,用来做什么呢?》的讨论,偶说计算出物理地址可以共享给用户空间读写。dreamice兄说能否说一下详细的应用。其二是alb*版主提到…

Linux内核和用户空间通信的方法(二)— 使用netlink

理论篇在 Linux 2.4 版以后版本的内核中,几乎全部的中断过程与用户态进程的通信都是使用 netlink 套接字实现的,例如iprote2网络管理工具,它与内核的交互就全部使用了netlink,著名的内核包过滤框架Netfilter在与用户空间的通读&am…

mini2440网卡驱动DM9000之dm9000_start_xmit

/* 调用时机:当网卡有数据需要发送的时候,该函数被调用 */ static int dm9000_start_xmit(struct sk_buff *skb, struct net_device *dev) { unsigned long flags; board_info_t *db netdev_priv(dev); dm9000_dbg(db, 3, "%s:\n", __func…

Kdump之makedumpfile源码分析

出自:http://blog.chinaunix.net/uid-10328574-id-2951118.html kexec是一个快速启动机制,允许在已经运行的内核的上下文启动另一个Linux内核,不需要经过BIOS。BIOS可能会消耗很多时间,特别是带有众多数量的外设的大型服务器。这种办法可以为…