analysis on tcp ip protocol stack
DESCRIPTION
This is my summary report for the operating system course. Of course, I am just one of the three in the group. I hope it is of help for you.TRANSCRIPT
Linux TCP/IP 协议栈分析
徐悦甡
CCNT, ZJU04/10/23
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
04/10/23 CCNT, ZJU
应用层
传输层
网际层
网络接入层
系统调用
BSD Socket层
Inet Socket 层
IP 层
硬件接口层
Linux协议栈概述(二)
04/10/23 CCNT, ZJU
数据发送部分与接收部分相对应 仅仅是数据流向不同 本次以数据发送端为例
与 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 目录下。
应用层
传输层
网际层
网络接入层
系统调用
BSD Socket层
Inet Socket 层
IP 层
硬件接口层
write
read
Linux中数据发送总流程
04/10/23 CCNT, ZJU
write sendtosend
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
04/10/23 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
具体的填充过程
04/10/23 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&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 的命名空间指针
04/10/23 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
04/10/23 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
04/10/23 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
04/10/23 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 中的指针
04/10/23 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
04/10/23 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 协议栈中发送数据的最后一个函数
04/10/23 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
04/10/23 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&AQ&A
04/10/23 Middleware, CCNT, ZJU