1、系统初始化过程中会调用sock_init函数进行套接字的初始化,主要是进行缓存的初始化

[cpp] view plaincopy
  1. static int __init sock_init(void)
  2. {
  3. int err;
  4. //初始化.sock缓存
  5. sk_init();
  6. //初始化sk_buff缓存
  7. skb_init();
  8. //初始化协议模块缓存
  9. init_inodecache();
  10. //注册文件系统类型
  11. err = register_filesystem(&sock_fs_type);
  12. if (err)
  13. goto out_fs;
  14. sock_mnt = kern_mount(&sock_fs_type);
  15. if (IS_ERR(sock_mnt)) {
  16. err = PTR_ERR(sock_mnt);
  17. goto out_mount;
  18. }
  19. .........................
  20. out:
  21. return err;
  22. out_mount:
  23. unregister_filesystem(&sock_fs_type);
  24. out_fs:
  25. goto out;
  26. }

2、INET协议族的初始化函数

[cpp] view plaincopy
  1. static int __init inet_init(void)
  2. {
  3. struct sk_buff *dummy_skb;
  4. struct inet_protosw *q;
  5. struct list_head *r;
  6. int rc = -EINVAL;
  7. BUILD_BUG_ON(sizeof(struct inet_skb_parm) > sizeof(dummy_skb->cb));
  8. sysctl_local_reserved_ports = kzalloc(65536 / 8, GFP_KERNEL);
  9. if (!sysctl_local_reserved_ports)
  10. goto out;
  11. //下面注册传输层协议操作集
  12. rc = proto_register(&tcp_prot, 1);
  13. if (rc)
  14. goto out_free_reserved_ports;
  15. rc = proto_register(&udp_prot, 1);
  16. if (rc)
  17. goto out_unregister_tcp_proto;
  18. rc = proto_register(&raw_prot, 1);
  19. if (rc)
  20. goto out_unregister_udp_proto;
  21. rc = proto_register(&ping_prot, 1);
  22. if (rc)
  23. goto out_unregister_raw_proto;
  24. //注册INET协议族的handler
  25. (void)sock_register(&inet_family_ops);
  26. .........................
  27. /*
  28. *  Add all the base protocols.
  29. */
  30. //将INET协议族协议数据包接收函数添加到系统中
  31. if (inet_add_protocol(&icmp_protocol, IPPROTO_ICMP) < 0)
  32. printk(KERN_CRIT "inet_init: Cannot add ICMP protocol\n");
  33. if (inet_add_protocol(&udp_protocol, IPPROTO_UDP) < 0)
  34. printk(KERN_CRIT "inet_init: Cannot add UDP protocol\n");
  35. if (inet_add_protocol(&tcp_protocol, IPPROTO_TCP) < 0)
  36. printk(KERN_CRIT "inet_init: Cannot add TCP protocol\n");
  37. #ifdef CONFIG_IP_MULTICAST
  38. if (inet_add_protocol(&igmp_protocol, IPPROTO_IGMP) < 0)
  39. printk(KERN_CRIT "inet_init: Cannot add IGMP protocol\n");
  40. #endif
  41. /* Register the socket-side information for inet_create. */
  42. for (r = &inetsw[0]; r < &inetsw[SOCK_MAX]; ++r)
  43. INIT_LIST_HEAD(r);
  44. //将inetsw_array中的元素按套接字类型注册到inetsw链表数组中
  45. for (q = inetsw_array; q < &inetsw_array[INETSW_ARRAY_LEN]; ++q)
  46. inet_register_protosw(q);
  47. /*
  48. *  Set the ARP module up
  49. */
  50. arp_init();
  51. /*
  52. *  Set the IP module up
  53. */
  54. ip_init();
  55. tcp_v4_init();
  56. /* Setup TCP slab cache for open requests. */
  57. tcp_init();
  58. /* Setup UDP memory threshold */
  59. udp_init();
  60. /* Add UDP-Lite (RFC 3828) */
  61. udplite4_register();
  62. ping_init();
  63. /*
  64. *  Set the ICMP layer up
  65. */
  66. if (icmp_init() < 0)
  67. panic("Failed to create the ICMP control socket.\n");
  68. .........................
  69. if (init_ipv4_mibs())
  70. printk(KERN_CRIT "inet_init: Cannot init ipv4 mibs\n");
  71. ipv4_proc_init();
  72. ipfrag_init();
  73. dev_add_pack(&ip_packet_type);
  74. rc = 0;
  75. out:
  76. return rc;
  77. out_unregister_raw_proto:
  78. proto_unregister(&raw_prot);
  79. out_unregister_udp_proto:
  80. proto_unregister(&udp_prot);
  81. out_unregister_tcp_proto:
  82. proto_unregister(&tcp_prot);
  83. out_free_reserved_ports:
  84. kfree(sysctl_local_reserved_ports);
  85. goto out;
  86. }

上面函数中的inetsw_array的定义中有四个元素:

[cpp] view plaincopy
  1. static struct inet_protosw inetsw_array[] =
  2. {
  3. {
  4. .type =       SOCK_STREAM,
  5. .protocol =   IPPROTO_TCP,
  6. .prot =       &tcp_prot,
  7. .ops =        &inet_stream_ops,
  8. .no_check =   0,
  9. .flags =      INET_PROTOSW_PERMANENT |
  10. INET_PROTOSW_ICSK,
  11. },
  12. {
  13. .type =       SOCK_DGRAM,
  14. .protocol =   IPPROTO_UDP,
  15. .prot =       &udp_prot,
  16. .ops =        &inet_dgram_ops,
  17. .no_check =   UDP_CSUM_DEFAULT,
  18. .flags =      INET_PROTOSW_PERMANENT,
  19. },
  20. {
  21. .type =       SOCK_DGRAM,
  22. .protocol =   IPPROTO_ICMP,
  23. .prot =       &ping_prot,
  24. .ops =        &inet_dgram_ops,
  25. .no_check =   UDP_CSUM_DEFAULT,
  26. .flags =      INET_PROTOSW_REUSE,
  27. },
  28. {
  29. .type =       SOCK_RAW,
  30. .protocol =   IPPROTO_IP,    /* wild card */
  31. .prot =       &raw_prot,
  32. .ops =        &inet_sockraw_ops,
  33. .no_check =   UDP_CSUM_DEFAULT,
  34. .flags =      INET_PROTOSW_REUSE,
  35. }
  36. };

上面的函数会将这个数组中的元素按照type为索引注册到inetsw指针数组中。

函数2中调用的sock_register函数就是想协议族数组net_families中添加inet协议族的net_proto_family的数据定义,主要是协议族的创建方法inet_create下面是它的实现

[cpp] view plaincopy
  1. int sock_register(const struct net_proto_family *ops)
  2. {
  3. int err;
  4. if (ops->family >= NPROTO) {
  5. printk(KERN_CRIT "protocol %d >= NPROTO(%d)\n", ops->family,
  6. NPROTO);
  7. return -ENOBUFS;
  8. }
  9. spin_lock(&net_family_lock);
  10. if (rcu_dereference_protected(net_families[ops->family],
  11. lockdep_is_held(&net_family_lock)))
  12. err = -EEXIST;
  13. else {
  14. RCU_INIT_POINTER(net_families[ops->family], ops);//这里就相当于将ops赋予net_families[ops->families]
  15. err = 0;
  16. }
  17. spin_unlock(&net_family_lock);
  18. printk(KERN_INFO "NET: Registered protocol family %d\n", ops->family);
  19. return err;
  20. }

3、套接字的创建

套接字分BSD socket的传输层的socket(struct sock结构,与具体的传输层协议有关)。

3.1、BSD socket的创建

应用程序使用函数socket会产生系统调用,调用sys_socket函数来创建BSD socket:

[cpp] view plaincopy
  1. SYSCALL_DEFINE3(socket, int, family, int, type, int, protocol)
  2. {
  3. int retval;
  4. struct socket *sock;
  5. int flags;
  6. /* Check the SOCK_* constants for consistency.  */
  7. BUILD_BUG_ON(SOCK_CLOEXEC != O_CLOEXEC);
  8. BUILD_BUG_ON((SOCK_MAX | SOCK_TYPE_MASK) != SOCK_TYPE_MASK);
  9. BUILD_BUG_ON(SOCK_CLOEXEC & SOCK_TYPE_MASK);
  10. BUILD_BUG_ON(SOCK_NONBLOCK & SOCK_TYPE_MASK);
  11. flags = type & ~SOCK_TYPE_MASK;
  12. if (flags & ~(SOCK_CLOEXEC | SOCK_NONBLOCK))
  13. return -EINVAL;
  14. type &= SOCK_TYPE_MASK;
  15. if (SOCK_NONBLOCK != O_NONBLOCK && (flags & SOCK_NONBLOCK))
  16. flags = (flags & ~SOCK_NONBLOCK) | O_NONBLOCK;
  17. retval = sock_create(family, type, protocol, &sock);//调用sock_create创建套接字,参数分别是协议族号、套接字类型,使用的传输层协议、执行要创建的套接字的指针的地址。
  18. if (retval < 0)
  19. goto out;
  20. retval = sock_map_fd(sock, flags & (O_CLOEXEC | O_NONBLOCK));
  21. if (retval < 0)
  22. goto out_release;
  23. out:
  24. /* It may be already another descriptor 8) Not kernel problem. */
  25. return retval;
  26. out_release:
  27. sock_release(sock);
  28. return retval;
  29. }

函数sock_create会调用__sock_create函数进行套接字的创建:

[cpp] view plaincopy
  1. int __sock_create(struct net *net, int family, int type, int protocol,
  2. struct socket **res, int kern)
  3. {
  4. int err;
  5. struct socket *sock;
  6. const struct net_proto_family *pf;
  7. /*
  8. *      合法性检查
  9. */
  10. if (family < 0 || family >= NPROTO)
  11. return -EAFNOSUPPORT;
  12. if (type < 0 || type >= SOCK_MAX)
  13. return -EINVAL;
  14. /* Compatibility.
  15. This uglymoron is moved from INET layer to here to avoid
  16. deadlock in module load.
  17. */
  18. if (family == PF_INET && type == SOCK_PACKET) {
  19. static int warned;
  20. if (!warned) {
  21. warned = 1;
  22. printk(KERN_INFO "%s uses obsolete (PF_INET,SOCK_PACKET)\n",
  23. current->comm);
  24. }
  25. family = PF_PACKET;
  26. }
  27. err = security_socket_create(family, type, protocol, kern);
  28. if (err)
  29. return err;
  30. sock = sock_alloc();//分配inode结构并获得对应的socket结构
  31. if (!sock) {
  32. if (net_ratelimit())
  33. printk(KERN_WARNING "socket: no more sockets\n");
  34. return -ENFILE; /* Not exactly a match, but its the
  35. closest posix thing */
  36. }
  37. sock->type = type;
  38. rcu_read_lock();
  39. pf = rcu_dereference(net_families[family]);
  40. err = -EAFNOSUPPORT;
  41. if (!pf)
  42. goto out_release;
  43. /*
  44. * We will call the ->create function, that possibly is in a loadable
  45. * module, so we have to bump that loadable module refcnt first.
  46. */
  47. if (!try_module_get(pf->owner))//模块检测
  48. goto out_release;
  49. /* Now protected by module ref count */
  50. rcu_read_unlock();
  51. //这里调用inet_create函数对INET协议族进行创建
  52. err = pf->create(net, sock, protocol, kern);
  53. if (err < 0)
  54. goto out_module_put;
  55. /*
  56. * Now to bump the refcnt of the [loadable] module that owns this
  57. * socket at sock_release time we decrement its refcnt.
  58. */
  59. if (!try_module_get(sock->ops->owner))
  60. goto out_module_busy;
  61. /*
  62. * Now that we're done with the ->create function, the [loadable]
  63. * module can have its refcnt decremented
  64. */
  65. module_put(pf->owner);
  66. err = security_socket_post_create(sock, family, type, protocol, kern);
  67. if (err)
  68. goto out_sock_release;
  69. *res = sock;
  70. return 0;
  71. out_module_busy:
  72. err = -EAFNOSUPPORT;
  73. out_module_put:
  74. sock->ops = NULL;
  75. module_put(pf->owner);
  76. out_sock_release:
  77. sock_release(sock);
  78. return err;
  79. out_release:
  80. rcu_read_unlock();
  81. goto out_sock_release;
  82. }

其中的参数protocol的取值如下:

[cpp] view plaincopy
  1. /* Standard well-defined IP protocols.  */
  2. enum {
  3. IPPROTO_IP = 0,       /* Dummy protocol for TCP       */
  4. IPPROTO_ICMP = 1,     /* Internet Control Message Protocol    */
  5. IPPROTO_IGMP = 2,     /* Internet Group Management Protocol   */
  6. IPPROTO_IPIP = 4,     /* IPIP tunnels (older KA9Q tunnels use 94) */
  7. IPPROTO_TCP = 6,      /* Transmission Control Protocol    */
  8. IPPROTO_EGP = 8,      /* Exterior Gateway Protocol        */
  9. IPPROTO_PUP = 12,     /* PUP protocol             */
  10. IPPROTO_UDP = 17,     /* User Datagram Protocol       */
  11. IPPROTO_IDP = 22,     /* XNS IDP protocol         */
  12. IPPROTO_DCCP = 33,        /* Datagram Congestion Control Protocol */
  13. IPPROTO_RSVP = 46,        /* RSVP protocol            */
  14. IPPROTO_GRE = 47,     /* Cisco GRE tunnels (rfc 1701,1702)    */
  15. IPPROTO_IPV6   = 41,      /* IPv6-in-IPv4 tunnelling      */
  16. IPPROTO_ESP = 50,            /* Encapsulation Security Payload protocol */
  17. IPPROTO_AH = 51,             /* Authentication Header protocol       */
  18. IPPROTO_BEETPH = 94,         /* IP option pseudo header for BEET */
  19. IPPROTO_PIM    = 103,     /* Protocol Independent Multicast   */
  20. IPPROTO_COMP   = 108,                /* Compression Header protocol */
  21. IPPROTO_SCTP   = 132,     /* Stream Control Transport Protocol    */
  22. IPPROTO_UDPLITE = 136,    /* UDP-Lite (RFC 3828)          */
  23. IPPROTO_RAW    = 255,     /* Raw IP packets           */
  24. IPPROTO_MAX
  25. };

3.2、INET层socket(inet_socket)和传输层socket(struct sock)创建

函数inet_create完成了上述功能,并初始化了sock的属性值,将socket的sk属性指向sock结构

[cpp] view plaincopy
  1. static int inet_create(struct net *net, struct socket *sock, int protocol,
  2. int kern)
  3. {
  4. struct sock *sk;
  5. struct inet_protosw *answer;
  6. struct inet_sock *inet;
  7. struct proto *answer_prot;
  8. unsigned char answer_flags;
  9. char answer_no_check;
  10. int try_loading_module = 0;
  11. int err;
  12. if (unlikely(!inet_ehash_secret))
  13. if (sock->type != SOCK_RAW && sock->type != SOCK_DGRAM)
  14. build_ehash_secret();
  15. sock->state = SS_UNCONNECTED;
  16. /* Look for the requested type/protocol pair. */
  17. lookup_protocol:
  18. err = -ESOCKTNOSUPPORT;
  19. rcu_read_lock();
  20. //根据传输层协议的类型创建sock结构
  21. //遍历inetsw链表
  22. list_for_each_entry_rcu(answer, &inetsw[sock->type], list) {
  23. err = 0;
  24. /* Check the non-wild match. */
  25. if (protocol == answer->protocol) {
  26. if (protocol != IPPROTO_IP)
  27. break;//找到了适配的inetsw[]元素
  28. } else {
  29. /* Check for the two wild cases. */
  30. if (IPPROTO_IP == protocol) {
  31. protocol = answer->protocol;
  32. break;
  33. }
  34. if (IPPROTO_IP == answer->protocol)
  35. break;
  36. }
  37. err = -EPROTONOSUPPORT;
  38. }
  39. //到这里answer指向了合适的inetsw结构,若是TCP协议,answer指向内容如下
  40. /*
  41. *   .type =       SOCK_STREAM,
  42. *   .protocol =   IPPROTO_TCP,
  43. *   .prot =       &tcp_prot,
  44. *   .ops =        &inet_stream_ops,
  45. *   .no_check =   0,
  46. *   .flags =      INET_PROTOSW_PERMANENT |
  47. *             INET_PROTOSW_ICSK,
  48. */
  49. if (unlikely(err)) {
  50. if (try_loading_module < 2) {
  51. rcu_read_unlock();
  52. /*
  53. * Be more specific, e.g. net-pf-2-proto-132-type-1
  54. * (net-pf-PF_INET-proto-IPPROTO_SCTP-type-SOCK_STREAM)
  55. */
  56. if (++try_loading_module == 1)
  57. request_module("net-pf-%d-proto-%d-type-%d",
  58. PF_INET, protocol, sock->type);
  59. /*
  60. * Fall back to generic, e.g. net-pf-2-proto-132
  61. * (net-pf-PF_INET-proto-IPPROTO_SCTP)
  62. */
  63. else
  64. request_module("net-pf-%d-proto-%d",
  65. PF_INET, protocol);
  66. goto lookup_protocol;
  67. } else
  68. goto out_rcu_unlock;
  69. }
  70. err = -EPERM;
  71. if (sock->type == SOCK_RAW && !kern && !capable(CAP_NET_RAW))
  72. goto out_rcu_unlock;
  73. err = -EAFNOSUPPORT;
  74. if (!inet_netns_ok(net, protocol))
  75. goto out_rcu_unlock;
  76. sock->ops = answer->ops;
  77. answer_prot = answer->prot;
  78. answer_no_check = answer->no_check;
  79. answer_flags = answer->flags;
  80. rcu_read_unlock();
  81. WARN_ON(answer_prot->slab == NULL);
  82. err = -ENOBUFS;
  83. //分配sock结构体内存,这里在inet_init函数初始化好的高速缓冲区中分配内存,然后做一些初始化工作。后面有进一步分析。
  84. sk = sk_alloc(net, PF_INET, GFP_KERNEL, answer_prot);
  85. if (sk == NULL)
  86. goto out;
  87. err = 0;
  88. sk->sk_no_check = answer_no_check;
  89. if (INET_PROTOSW_REUSE & answer_flags)
  90. sk->sk_reuse = 1;
  91. inet = inet_sk(sk);//后面有进一步分析,为何可以强制转换?!!
  92. inet->is_icsk = (INET_PROTOSW_ICSK & answer_flags) != 0;
  93. inet->nodefrag = 0;
  94. if (SOCK_RAW == sock->type) {
  95. inet->inet_num = protocol;
  96. if (IPPROTO_RAW == protocol)
  97. inet->hdrincl = 1;
  98. }
  99. if (ipv4_config.no_pmtu_disc)
  100. inet->pmtudisc = IP_PMTUDISC_DONT;
  101. else
  102. inet->pmtudisc = IP_PMTUDISC_WANT;
  103. inet->inet_id = 0;
  104. //对sk进行初始化设置并将sock中的sk指针指向sk结构
  105. sock_init_data(sock, sk);
  106. //进一步设置sk的其他属性信息
  107. sk->sk_destruct     = inet_sock_destruct;
  108. sk->sk_protocol     = protocol;
  109. sk->sk_backlog_rcv = sk->sk_prot->backlog_rcv;
  110. inet->uc_ttl = -1;
  111. inet->mc_loop    = 1;
  112. inet->mc_ttl = 1;
  113. inet->mc_all = 1;
  114. inet->mc_index   = 0;
  115. inet->mc_list    = NULL;
  116. sk_refcnt_debug_inc(sk);
  117. if (inet->inet_num) {
  118. /* It assumes that any protocol which allows
  119. * the user to assign a number at socket
  120. * creation time automatically
  121. * shares.
  122. */
  123. inet->inet_sport = htons(inet->inet_num);
  124. /* Add to protocol hash chains. */
  125. sk->sk_prot->hash(sk);//调用inet_hash函数
  126. }
  127. if (sk->sk_prot->init) {
  128. err = sk->sk_prot->init(sk);//调用tcp_v4_init_sock函数进行进一步的初始化,由于在函数sk_alloc中一些属性被设置成0了,所以在此调用进行初始化
  129. if (err)
  130. sk_common_release(sk);
  131. }
  132. out:
  133. return err;
  134. out_rcu_unlock:
  135. rcu_read_unlock();
  136. goto out;
  137. }

关于套接字struct sock与struct inet_sock、struct tcp_sock、struct inet_connection_sock等结构之间的关系有待进一步了解。

网络协议栈深入分析(四)--套接字内核初始化和创建过程相关推荐

  1. 网络协议栈深入分析(五)--套接字的绑定、监听、连接和断开

    1.套接字的绑定 创建完套接字服务器端会在应用层使用bind函数进行套接字的绑定,这时会产生系统调用,sys_bind内核函数进行套接字. 系统调用函数的具体实现 [cpp] view plainco ...

  2. Linux网络编程:原始套接字的魔力【续】

    如何从链路层直接发送数据帧        本来以为这部分都弄完了,结果有朋友反映说看了半天还是没看到如何从链路层直接发送数据.因为上一篇里面提到的是从链路层"收发"数据,结果只&q ...

  3. 【Linux网络编程】UDP 套接字编程

    [Linux网络编程]UDP 套接字编程 [1]用户数据报协议(UDP) UDP是一个简单的传输层协议,不保证UDP数据报会到达其最终目的地,不保证各个数据报的先后顺序跨网络后保持不变,也不保证每个数 ...

  4. python socket 域名_Python网络编程中的套接字名和DNS解析。

    距离上一次TCP的文章,这一次要讲的是套接字名和DNS,并且还会涉及到网络数据的发送接受和网络错误的发生和处理. 下面说套接字名,在创建和部署每个套接字对象时总共需要做5个主要的决定,主机名和IP地址 ...

  5. UNIX网络编程.卷1,套接字联网API(第3版)(中文版)(Stevens经典著作,两位顶级网络编程专家应邀执笔修订)...

    UNIX网络编程.卷1,套接字联网API(第3版)(中文版)(Stevens经典著作,两位顶级网络编程专家应邀执笔修订) 基本信息 原书名: Unix Network Programming, Vol ...

  6. socket:内核初始化及创建流(文件)详细过程

    socket中文名称为套接字,属于传输协议及功能接口的封装.socket首先初始化注册(socket)文件系统,然后由使用它的模块传入具体的地址族(类型)family,如ipv4模块中的(void)s ...

  7. Linux内核--网络协议栈深入分析(一)--与sk_buff有关的几个重要的数据结构

    本文分析基于Linux Kernel 3.2.1 原创作品,转载请标明http://blog.csdn.net/yming0221/article/details/7971463 更多请查看专栏htt ...

  8. 向一个无法连接的网络尝试了一个套接字操作_python3从零学习-5.8.1、socket—底层网络接口...

    源代码: Lib/socket.py 这个模块提供了访问BSD*套接字*的接口.在所有现代Unix系统.Windows.macOS和其他一些平台上可用. 这个Python接口是用Python的面向对象 ...

  9. Linux网络编程之Socket套接字

    一.Socket到底是什么 socket 这个英文单词的原意是"插口""插槽", 在网络编程中,它的意思是可以通过插口接入的方式,快速完成网络连接和数据收发.你 ...

最新文章

  1. 查看 Laravel 的 SQL 语句的方法
  2. 安装.Net的痛苦经历!
  3. Python+Opencv识别两张相似图片
  4. 带有Swagger的Spring Rest API –公开文档
  5. ef mysql modelfirst_MySQL –EF edmx(Model First)– Sql Server table
  6. 【C语言】求s(n)=a+aa+aaa+...+aa...a的值
  7. 专家视角 | 小荷的 Oracle Database 18c 新特性快速一瞥
  8. 南邮 md5 collision
  9. 调用百度API实现人像动漫化(C++)
  10. Spring Cloud Stream
  11. UBuntu CMake工程配置基础
  12. crontab 定时执行任务
  13. 元胞自动机模型01——认识元细胞机模型
  14. 在Flask中上传本地图片到服务器
  15. C语言如何实现寻找峰值函数,findpeaks 寻找峰值函数
  16. 用ARCGIS做DEM地形分析
  17. aligned_alloc
  18. 几万年前,孙悟空大闹地府后删库跑路了!那阎王生死簿又该怎么写呢?
  19. 如何给网站添加IE浏览器升级提示
  20. allegro 走线切换层_PCB 18种特殊走线的画法与技巧

热门文章

  1. python装饰器记录每一个函数的执行时间
  2. pandas聚合dataframe某一列的值中的所有元素
  3. 使用英文做LDA建模
  4. Nginx 502报错(django+nginx,而非php-fmp)
  5. python根据年月日计算天数_「每日一练」Python实现输入年月日计算第几天
  6. PS2018学习笔记(30-35节)
  7. ios 内联函数 inline ---分解LFLiveKit
  8. 【Python爬虫学习笔记3】requests库
  9. bzoj 4196 树链剖分 模板
  10. SpringMVC @ModelAttribute注解