Linux TCP/IP  协议栈分析 徐悦甡 CCNT, ZJU 11/26/11
Linux 协议栈概述(一) 与 TCP/IP 分层模型相对应 BSD socket 层  屏蔽协议差异,提供通用接口,每个 socket 在内核中以 struct socket 结构体现,这一部分的文件主要有: /net/socket.c  /net/protocols.c etc INET socket 层 当用于 TCP/IP 协议栈时,即建立了 AF_INET 形式的 socket 时,需要额外参数的支持,于是就有了 struct sock 结构,文件主要有: /net/ipv4/protocol.c /net/ipv4/af_inet.c  /net/core/sock.c etc 11/26/11 CCNT, ZJU 应用层 传输层 网际层 网络接入层 系统调用 BSD Socket 层 Inet Socket  层 IP  层 硬件接口层
Linux 协议栈概述(二) 与 TCP/IP 分层模型相对应 IP 层 处理网络层的操作,网络层用 struct packet_type 结构表示。文件主要有: /net/ipv4/ip_forward.c  ip_fragment.c ip_input.c ip_output.c  硬件接口层 每个网络设备以 struct net_device 表示,通用的处理在 /net/core/dev.c 中,驱动程序都在 /driver/net 目录下。 11/26/11 CCNT, ZJU 数据发送部分与接收部分相对应 仅仅是数据流向不同 本次以数据发送端为例 应用层 传输层 网际层 网络接入层 系统调用 BSD Socket 层 Inet Socket  层 IP  层 硬件接口层 write read
Linux 中数据发送总流程 11/26/11 CCNT, ZJU write sendto send sys_write sys_send sock_create sys_sendto sock_sendmsg inet_sendmsg tcp_sendmsg tcp_send_skb tcp_transmit_skb ip_queue_xmit ip_queue_xmit2 ip_output ip_finish_output ip_finish_output2 dev_queue_xmit dev_hard_start_xmit 应用层 BSD Socket 层 Inet Socket 层 IP 层 硬件接口层 /net/ipv4; /net/core
应用层 —— sys_sendto sendto&send 只是 glibc 函数库中封装的函数,最终都会调用到内核函数 sys_sendto 11/26/11 CCNT, ZJU asmlinkage long sys_sendto( int  fd, void __user *buff, size_t len, unsigned flags, struct sockaddr __user *addr, int addr_len) fd  : socket 文件描述符 buff :指向需要发送的数据 len :需要发送的数据的长度 flags :标志位 addr :数据报文要发送的对方端点的地址信息 addr_len :地址信息的长度 核心工作:填充 msghdr 结构 struct  msghdr  {      void             *msg_name;      int              msg_namelen;      struct iovec     *msg_iov;      __kernel_size_t -msg_iovlen;      void             *msg_control;      __kernel_size_t -msg_controllen;      unsigned         msg_flags;  };
应用层 —— sys_sendto 具体的填充过程 11/26/11 CCNT, ZJU iov.iov_base = buff; iov.iov_len = len; msg.msg_name = NULL; msg.msg_iov = &iov; msg.msg_iovlen = 1; …… msg.msg_name = NULL; msg.msg_namelen = 0; if (addr) {        ……    msg.msg_name = address;    msg.msg_namelen = addr_len; }    Step1 : msg_name/msg_namelen 是数据报文要发向的对端的地址信息,即 sendto 系统调用中的 addr 和 addr_len) 。当使用 send 时,它们的值为 NULL 和 0 Step2 :填充 iovec ,存放待发送数据的缓冲区, iov_base 指向缓冲区的起始地址,  iov_len 是缓冲区的长度,指向 length Step3 : msghdr->msg_iovlen , msg_ iovlen  是缓冲区的数量,对于 sendto 和 send 来讲, msg_iovlen 都是 1 struct iovec {         void __user     *iov_base;         __kernel_size_t iov_len;     };
BSD Socket 层(一) —— sock_create sock_create/__sock_create 原型: int sock_create(int family, int type, int protocol, struct socket ) 实例: sock_create(AF_INET, SOCK_DGRAM, IPPROTO_IP,  &sock ) int sock_create(int family, int type, int protocol, struct socket **res)  { return __sock_create(current -> nsproxy ->net_ns ,  family ,  type ,  protocol ,  res ,  0 );  }  : 真正的工作由 __sock_create 来做 current 是指向当前 task 的指针, task 的类型为 struct task_struct nsproxy 是它的一个成员变量,是 task 的命名空间指针 11/26/11 CCNT, ZJU struct nsproxy {     struct uts_namespace *uts_ns;     struct ipc_namespace *ipc_ns;     struct mnt_namespace *mnt_ns;     struct pid_namespace *pid_ns;     struct net      *net_ns; };
BSD Socket 层(二)  —— sock_create __sock_create 11/26/11 CCNT, ZJU static int __sock_create(struct net *net, int family, int type, int protocol,struct  socket * *res, int kern){   ……      if (family < 0 || family >= NPROTO)         return -EAFNOSUPPORT;     if (type < 0 || type >= SOCK_MAX)         return -EINVAL; …… } 对 family 和 type 进行检查,查看是否超出正常范围
BSD Socket 层(三)  —— sock_create 申请一个 socket node 11/26/11 CCNT, ZJU   sock = sock_alloc();     if (!sock) {         if (net_ratelimit())             printk(KERN_WARNING “socket: no more sockets\n”);         return -ENFILE;       } 转入 sock_alloc
BSD Socket 层(四)  —— sock_create sock_alloc 11/26/11 CCNT, ZJU static struct socket *sock_alloc(void) { ……      inode = new_inode(sock_mnt->mnt_sb);   ……     sock = SOCKET_I(inode);     ……     inode->i_mode = S_IFSOCK | S_IRWXUGO;     inode->i_uid = current_fsuid();     inode->i_gid = current_fsgid();    percpu_add(sockets_in_use, 1);     return sock; } sock_mnt 是一个全局变量,在 sock_init 中被初始化的,并挂载到 VFS 层上 Step1 :  从 sock_mnt->mnt_sb 即 socket 的超级块中申请一个节点 Step2 :通过 SOCKET_I 宏,取得 inode 对应的 socket 的地址 Step3 :设置 inode 的 mode , uid , gid Step4 :增加 sockets_in_use 的统计计数
BSD Socket 层(五)  —— sock_create 通过 RCU 机制,获得 pf 对应的 net_families 中的指针 11/26/11 CCNT, ZJU    rcu_read_lock();     pf = rcu_dereference(net_families[family]);     ……     rcu_read_unlock(); RCU 机制 读 - 拷贝 - 更新( read-copy-update ) ,  被 2.6 内核正式引入。是为了保护在多数情况下被多个 CPU 读的数据结构而设计的一种同步技术,允许多个读者和写者并发执行,不利用传统意义上的锁机制
BSD Socket 层(六)  —— sock_create 通过函数指针调用用户指定协议簇中的函数去创建 socket 11/26/11 CCNT, ZJU   err = pf->create(net, sock, protocol, kern); 对于 TCP/IP 来说, family 是 PF_INET PF_INET (  linux/net/ipv4/af_inet.c  )对应的协议域定义 static const struct net_proto_family inet_family_ops = {     .family = PF_INET,     .create =  inet_create,     .owner    = THIS_MODULE, }; 对于 TCP/IP 来说, family 是 PF_INET PF_INET (  linux/net/ipv4/af_inet.c  )对应的协议域定义
硬件接口层(一) —— dev_queue_xmit dev_queue_xmit 发送 sk_buff,  将其加入到 driver 的 queue 中 可以认为是 TCP/IP 协议栈中发送数据的最后一个函数 11/26/11 CCNT, ZJU int dev_queue_xmit(struct sk_buff *skb){      struct net_device *dev = skb->dev; ……      txq = dev_pick_tx(dev, skb);         if (q->enqueue) {          rc = __dev_xmit_skb(skb, q, dev, txq);         ……      } Step1 :获得指向发送设备的指针,并得到发送设备的发送队列 Step2 :获得出口流量控制对象的结构 Step3 :如果该设备有 enqueue 的处理函数,刚使用该流量控制对象发送数据包
硬件接口层(二) —— dev_queue_xmit dev_queue_xmit 11/26/11 CCNT, ZJU     if (dev->flags & IFF_UP) {          int cpu = smp_processor_id();           if (txq->xmit_lock_owner != cpu) {              HARD_TX_LOCK(dev, txq, cpu);               if (!netif_tx_queue_stopped(txq)) {                 rc = dev_hard_start_xmit(skb, dev, txq);   …… Step1 : 获取当前 CPU 的 id Step3 :判断别的 CPU 是否正在使用该设备,如果没有,则尝试获得锁,从而获取 device 的使用权 Step2 :由 dev_hard_start_xmit 直接负责发送
Q&A 11/26/11 Middleware, CCNT, ZJU

Analysis on tcp ip protocol stack

  • 1.
    Linux TCP/IP 协议栈分析 徐悦甡 CCNT, ZJU 11/26/11
  • 2.
    Linux 协议栈概述(一) 与TCP/IP 分层模型相对应 BSD socket 层  屏蔽协议差异,提供通用接口,每个 socket 在内核中以 struct socket 结构体现,这一部分的文件主要有: /net/socket.c /net/protocols.c etc INET socket 层 当用于 TCP/IP 协议栈时,即建立了 AF_INET 形式的 socket 时,需要额外参数的支持,于是就有了 struct sock 结构,文件主要有: /net/ipv4/protocol.c /net/ipv4/af_inet.c /net/core/sock.c etc 11/26/11 CCNT, ZJU 应用层 传输层 网际层 网络接入层 系统调用 BSD Socket 层 Inet Socket 层 IP 层 硬件接口层
  • 3.
    Linux 协议栈概述(二) 与TCP/IP 分层模型相对应 IP 层 处理网络层的操作,网络层用 struct packet_type 结构表示。文件主要有: /net/ipv4/ip_forward.c ip_fragment.c ip_input.c ip_output.c 硬件接口层 每个网络设备以 struct net_device 表示,通用的处理在 /net/core/dev.c 中,驱动程序都在 /driver/net 目录下。 11/26/11 CCNT, ZJU 数据发送部分与接收部分相对应 仅仅是数据流向不同 本次以数据发送端为例 应用层 传输层 网际层 网络接入层 系统调用 BSD Socket 层 Inet Socket 层 IP 层 硬件接口层 write read
  • 4.
    Linux 中数据发送总流程 11/26/11CCNT, ZJU write sendto send sys_write sys_send sock_create sys_sendto sock_sendmsg inet_sendmsg tcp_sendmsg tcp_send_skb tcp_transmit_skb ip_queue_xmit ip_queue_xmit2 ip_output ip_finish_output ip_finish_output2 dev_queue_xmit dev_hard_start_xmit 应用层 BSD Socket 层 Inet Socket 层 IP 层 硬件接口层 /net/ipv4; /net/core
  • 5.
    应用层 —— sys_sendtosendto&send 只是 glibc 函数库中封装的函数,最终都会调用到内核函数 sys_sendto 11/26/11 CCNT, ZJU asmlinkage long sys_sendto( int  fd, void __user *buff, size_t len, unsigned flags, struct sockaddr __user *addr, int addr_len) fd  : socket 文件描述符 buff :指向需要发送的数据 len :需要发送的数据的长度 flags :标志位 addr :数据报文要发送的对方端点的地址信息 addr_len :地址信息的长度 核心工作:填充 msghdr 结构 struct  msghdr  {     void             *msg_name;     int              msg_namelen;      struct iovec     *msg_iov;      __kernel_size_t -msg_iovlen;      void             *msg_control;      __kernel_size_t -msg_controllen;      unsigned         msg_flags; };
  • 6.
    应用层 —— sys_sendto具体的填充过程 11/26/11 CCNT, ZJU iov.iov_base = buff; iov.iov_len = len; msg.msg_name = NULL; msg.msg_iov = &iov; msg.msg_iovlen = 1; …… msg.msg_name = NULL; msg.msg_namelen = 0; if (addr) {        ……    msg.msg_name = address;    msg.msg_namelen = addr_len; }   Step1 : msg_name/msg_namelen 是数据报文要发向的对端的地址信息,即 sendto 系统调用中的 addr 和 addr_len) 。当使用 send 时,它们的值为 NULL 和 0 Step2 :填充 iovec ,存放待发送数据的缓冲区, iov_base 指向缓冲区的起始地址, iov_len 是缓冲区的长度,指向 length Step3 : msghdr->msg_iovlen , msg_ iovlen 是缓冲区的数量,对于 sendto 和 send 来讲, msg_iovlen 都是 1 struct iovec {         void __user     *iov_base;         __kernel_size_t iov_len;     };
  • 7.
    BSD Socket 层(一)—— sock_create sock_create/__sock_create 原型: int sock_create(int family, int type, int protocol, struct socket ) 实例: sock_create(AF_INET, SOCK_DGRAM, IPPROTO_IP, &sock ) int sock_create(int family, int type, int protocol, struct socket **res) { return __sock_create(current -> nsproxy ->net_ns ,  family ,  type ,  protocol ,  res ,  0 ); } : 真正的工作由 __sock_create 来做 current 是指向当前 task 的指针, task 的类型为 struct task_struct nsproxy 是它的一个成员变量,是 task 的命名空间指针 11/26/11 CCNT, ZJU struct nsproxy {     struct uts_namespace *uts_ns;     struct ipc_namespace *ipc_ns;     struct mnt_namespace *mnt_ns;     struct pid_namespace *pid_ns;     struct net      *net_ns; };
  • 8.
    BSD Socket 层(二) —— sock_create __sock_create 11/26/11 CCNT, ZJU static int __sock_create(struct net *net, int family, int type, int protocol,struct  socket * *res, int kern){ ……      if (family < 0 || family >= NPROTO)         return -EAFNOSUPPORT;     if (type < 0 || type >= SOCK_MAX)         return -EINVAL; …… } 对 family 和 type 进行检查,查看是否超出正常范围
  • 9.
    BSD Socket 层(三) —— sock_create 申请一个 socket node 11/26/11 CCNT, ZJU   sock = sock_alloc();     if (!sock) {         if (net_ratelimit())             printk(KERN_WARNING “socket: no more sockets\n”);         return -ENFILE;       } 转入 sock_alloc
  • 10.
    BSD Socket 层(四) —— sock_create sock_alloc 11/26/11 CCNT, ZJU static struct socket *sock_alloc(void) { ……      inode = new_inode(sock_mnt->mnt_sb); ……     sock = SOCKET_I(inode);     ……     inode->i_mode = S_IFSOCK | S_IRWXUGO;     inode->i_uid = current_fsuid();     inode->i_gid = current_fsgid();   percpu_add(sockets_in_use, 1);     return sock; } sock_mnt 是一个全局变量,在 sock_init 中被初始化的,并挂载到 VFS 层上 Step1 : 从 sock_mnt->mnt_sb 即 socket 的超级块中申请一个节点 Step2 :通过 SOCKET_I 宏,取得 inode 对应的 socket 的地址 Step3 :设置 inode 的 mode , uid , gid Step4 :增加 sockets_in_use 的统计计数
  • 11.
    BSD Socket 层(五) —— sock_create 通过 RCU 机制,获得 pf 对应的 net_families 中的指针 11/26/11 CCNT, ZJU   rcu_read_lock();     pf = rcu_dereference(net_families[family]);    ……     rcu_read_unlock(); RCU 机制 读 - 拷贝 - 更新( read-copy-update ) , 被 2.6 内核正式引入。是为了保护在多数情况下被多个 CPU 读的数据结构而设计的一种同步技术,允许多个读者和写者并发执行,不利用传统意义上的锁机制
  • 12.
    BSD Socket 层(六) —— sock_create 通过函数指针调用用户指定协议簇中的函数去创建 socket 11/26/11 CCNT, ZJU   err = pf->create(net, sock, protocol, kern); 对于 TCP/IP 来说, family 是 PF_INET PF_INET ( linux/net/ipv4/af_inet.c )对应的协议域定义 static const struct net_proto_family inet_family_ops = {     .family = PF_INET,     .create =  inet_create,     .owner    = THIS_MODULE, }; 对于 TCP/IP 来说, family 是 PF_INET PF_INET ( linux/net/ipv4/af_inet.c )对应的协议域定义
  • 13.
    硬件接口层(一) —— dev_queue_xmitdev_queue_xmit 发送 sk_buff, 将其加入到 driver 的 queue 中 可以认为是 TCP/IP 协议栈中发送数据的最后一个函数 11/26/11 CCNT, ZJU int dev_queue_xmit(struct sk_buff *skb){      struct net_device *dev = skb->dev; ……      txq = dev_pick_tx(dev, skb);        if (q->enqueue) {          rc = __dev_xmit_skb(skb, q, dev, txq);         ……      } Step1 :获得指向发送设备的指针,并得到发送设备的发送队列 Step2 :获得出口流量控制对象的结构 Step3 :如果该设备有 enqueue 的处理函数,刚使用该流量控制对象发送数据包
  • 14.
    硬件接口层(二) —— dev_queue_xmitdev_queue_xmit 11/26/11 CCNT, ZJU     if (dev->flags & IFF_UP) {          int cpu = smp_processor_id();           if (txq->xmit_lock_owner != cpu) {              HARD_TX_LOCK(dev, txq, cpu);              if (!netif_tx_queue_stopped(txq)) {                 rc = dev_hard_start_xmit(skb, dev, txq); …… Step1 : 获取当前 CPU 的 id Step3 :判断别的 CPU 是否正在使用该设备,如果没有,则尝试获得锁,从而获取 device 的使用权 Step2 :由 dev_hard_start_xmit 直接负责发送
  • 15.