网络协议栈的部分底层实现(文件->网络,网络的开始),file结构中的private_data字段,socket结构体,sk_buff结构体,封装报头/解包/上下传递报文的本质

目录

网络协议栈的部分底层实现

文件到网络的过渡

private_data 

socket结构体

socket 和 file 中的方法集

网络的开始

如何区分sk字段指向的类型

管理报文(sk_buff结构体)

总结


网络协议栈的部分底层实现

文件到网络的过渡

我们从进程pcb开始梳理

  • pcb里的files字段存放了对应进程所有打开的文件信息 -> files_struct里的fd_array字段就是我们熟悉的文件描述符表 -> 文件结构体struct file
  • 它里面有一个重要的字段 -- private_data

private_data 

是一个通用指针,用于存储与文件相关的特定私有数据,这个字段在文件打开、读写、关闭等操作中被广泛使用

  • 使得不同类型的文件操作能够共享同一个struct file结构,而无需在每次操作时重新分配和初始化特定数据结构

socket结构体

这里的private_data字段就指向struct socket结构体,而该结构体内部有一file指针,指回了file结构体

  • 这样在文件操作过程中,如果使用了socket对象内的数据,可以在使用完后再返回文件操作

除了这个,socket里面还有一个等待队列(struct socket_wq),由wq字段指向该队列

  • 如果当前所需的网络资源没有就绪,就将当前进程挂接到等待队列中(队头后面跟着若干等待队列项),等待资源就绪

以及由ops字段指向的操作方法集struct proto_ops

  • 都是和连接相关的接口+udp协议收发接口(sendmsg,recvmsg)

当我们打开网络文件时

  • 其实是将套接字挂接在了struct file下
  • 这样在上层看来,通过fd就可以操作网络套接字了

从进程pcb开始,一直到socket结构体,实现了从文件到网络的过渡

  • 而从socket结构体中的sock结构开始,才是网络的开始,它会指向具体协议的结构体

socket 和 file 中的方法集

如果我们打开的是网络文件

  • 那么file中的f_op字段指向的就不再是磁盘相关的操作方法了,而是与网络相关
  • 定义了文件的基本操作(打开,读写,关闭等),适用于所有文件类型

但是,socket中也有网络文件操作方法集opts

  • 它里面包含的是专门用于套接字操作/处理网络相关的操作+udp收发
  • 注意!没有tcp的send和recv
  • 因为tcp报文需要进一步发送到sock中的sk_write(发送缓冲区)中
  • udp可以直接使用socket中的sendmsg将数据发送到下一层,而不链入队列

这两者有什么区别呢?

  • 其实,file,socket,sock等等,每一层都有自己的方法集,用于将数据处理后交给下层,所以他们是上下层关系

网络的开始

我们使用socket()创建套接字时,需要传入参数来表明自己是哪个类型的套接字

  • 比如:SOCK_STREAM SOCK_...
  • 而在底层,则会根据参数为我们创建对应的结构体 -- struct tcp_socket / udp_socket

这些协议结构体的第一个成员就是struct sock

  • 所以,实际上struct socket结构体中的sock类型指针sk,指向的是tcp/udp_socket类型中的sock结构体
  • 所以,实际上sock结构体是一个通用的网络套接字抽象

传输层和网络层,都使用了特定数据结构来表示协议

  • 也都定义了与特定协议匹配的方法集

如何区分sk字段指向的类型

那如何识别究竟指向的是什么类型呢?

  • socket结构体中包含一些字段,比如type啦,flag啦,它们可以帮助我们区分sock类型指针指向的究竟是哪个协议的结构体

假如以tcp_socket为例

  • sk字段指向的仅仅是sock类型
  • 如果想要访问tcp协议的其他属性,只需对sk强转即可:

这就相当于在c语言中实现了多态

管理报文(sk_buff结构体)

计算机会收到大量的报文,不同协议类型的/不同服务对应的,它们可能会挤压在计算机里的不同层

  • 这么多的报文,自然也就需要管理起来
  • 也就是系统定义的sk_buff结构体

可以看到,以sock结构体中的发送缓冲区为例

  • 它是以双链表结构将报文组织起来
  • 并且配有自旋锁,因为发送缓冲区可以被多个进程访问

其中,sk_buf结构体中定义了多个字段,指向当前报文的不同位置:

  • head指向报头字段的开始,end是整个报文的结束
  • data指向有效载荷的开始,tail是有效载荷的结束
  • 所以,可以看出来,sk_buff和报文实际存放位置是分开的,通过指针来找到数据 -- 对报文的修改都是在同一块区域做的,所以传递报文时不需要拷贝

因为sk_buf里有上面这四个指针决定了各部分数据的范围

  • 所以,封装报头/解包就是在移动head指针
  • 而如何移动,由所在层的协议决定

实际在代码中,上下传递报文就是在传递sk_buff

  • 在网络层时,报文作为一个整体被处理 ; 到了传输层,需要根据对应协议将报文分发给各自的接收缓冲区,所谓分发,就是将sk_buf组织到双链表中

  • 如果要从上层读取报文,本质上就是通过一大堆的数据结构,到sock结构中的双链表读取sk_buf,最后拿到上层

以上操作均由os完成

总结

通过上面的梳理,我们可以真切理解到,为什么说建立/维护连接是有成本

  • 每新增一个连接,就要分配一个文件描述符,并创建其之后串联的一大堆数据结构,并更新一些结构中的连接关系
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值