[experimental] 内核网络模块重构日记

序言

出于网络模块中纷乱的epoll、wait_queue回调,过于简单实现的 iface_poll 设计,目不暇接的句柄处理,以及这导致的一些资源冲突,以及这些资源冲突导致的进一步的设计的妥协(一系列的全局资源),我实验性地尝试重构整个 net 模块,尝试简化并缕清 iface - socket - inode 的关系,压缩整个模块所需要的抽象层次,目的是减少重复实现,实现接口与资源的解耦,优化整个 net 子系统的api,并在这基础上进一步实现先前由于种种未能实现的功能、解决潜在的bug:

进度

  • 把处理阻塞/非阻塞的逻辑移到socket inode中
  • 移除socket inode多余的引用计数
  • 将posix handle item集成进socket inode中
  • 移除全局socket set
  • 移除全局socket handle
  • 重构socket handle,绑定smoltcp句柄与内核socket
  • Inet Socket 直接存储其 [smoltcp::iface::SocketHandle],其他 Address Family 的 Socket 则采用更加直观的维护内部可变性的方法,而非维护一个全局的 Global Socket Handle 系统。
    • inet datagram ( udp )
    • inet stream ( tcp )
    • inet raw
    • unix datagram
    • unix stream
  • 检查实现与标准
    • 添加 inet udp tcp setsockopt 选项
  • 对于阻塞接口,添加缺少的中断处理

初步设计

以下设计均为我于 2024-08-03T16:00:00Z 作出的大致设计,非常非常非常欢迎 各位使用过 or 曾经开发过网络子系统的各位开发者给出建议。

SocketInode

作为kernel中对资源的一般性抽象,负责映射底层Socket的接口,并管理相关的 epoll wait_queue 资源。

Socket

trait,规范通用socket行为

INET Socket

需要 Iface 的资源

UNIX Socket

kernel communication 的 socket 抽象

Iface

持有 Inet Socket 的硬件资源,几乎所有 Inet Socket 的行为都要 poll_iface。这也是需要分开管理 Inet Socket 的原因。

累了明天再补充,反正这么晚也没人看,如果要追更直接回帖我会看到的

1 个赞

直接上图片可能会好点,不过是我手绘的()

Inet Socket 设计

整个socket是一个巨大的state machine,需要根据state所在层次详细拆分。

Bound & Unbound State

Bound & unbound 直接作为 Inet Socket 的第一层状态机,以表明其是否绑定 Iface

Socket Family Specific State Machine

  • TCP
  • UDP
  • RAW

net设备的驱动也应该重写,现在的e1000驱动是直接复制虚拟网卡的驱动改的,重复了

1 个赞

你的意思是e1000e和vitrtonet的驱动很相似,可以把驱动抽象出来给不同的网卡复用吗?

有一些可以抽象出来的东西,但并不是必须的
主要是我觉得这部分驱动的代码比较啰嗦,就比如每个驱动里的struct和函数的名字都带前缀,这完全没必要(写成smoltcp里的几种Socket的形式会比较好

你别说smoltcp的几种Socket看起来简洁,但是我外面想要复用他的enum分类麻烦得要死,rust 又不支持 cpp 的 SFINAE,导致我每个特化都要单独写个tcp / udp函数,要不就得在外面加一层 enum 或者 trait ,有点好气又好笑

SFINAE is the best thing in the world. ——鲁迅

新issue: [BUG REPORT] socket shutdown wrong implemented · Issue #887 · DragonOS-Community/DragonOS · GitHub

目前该实现是完全错误的,这可以参考 linux glibc 对于 shutdown() 中 how 的值定义:
https://www.man7.org/linux/man-pages/man2/shutdown.2.html#NOTES

The constants SHUT_RD, SHUT_WR, SHUT_RDWR have the value 0, 1, 2, respectively, and are defined in <sys/socket.h> since glibc-2.1.91.

这是重构时发现的问题,暂时未有已知造成的影响,也会在重构过程中得到修复。

注:其实 posix 标准并没有规定 SHUT_RD, SHUT_WR 和 SHUT_RDWR 的具体值,这里特指 linux glibc 的实现

关于Socket重构后一些与epoll管理相关的问题,对于维护socket的epoll_items的list,主要是为了在epoll的回调中获取到管理socket文件的epoll_guard来判断socket是否加入到其ready_list中,那么现在每种socket结构体下都有一个epoll_items,对于这个list的操作目前有add_epoll,remove_epoll,clear_epoll以及epoll的回调,这些操作是不是应该放在Socket的外层统一管理,比如inode?或着Socket trait中公共实现

确实可以丢进inode里了

目前非inet的socket的缓冲区由socket各自实现,考虑将其读写逻辑基本一致,是不是单独成结构体开放申请、读写、销毁接口给各个非inet socket(inet socket缓冲区由smoltcp维护了)服务会更简洁,可维护性扩展性会更好?

可以考虑,只是内核-用户态层面的数据所需要的缓冲区应该是同构的,linux上的实现是重用的吗?

喜报
现在本分支已成功重新实现进入系统()

1 个赞

@SMALLC 这里是可能返回err但是未处理?
https://github.com/DragonOS-Community/DragonOS/blob/c64a1f6964b9e6798f3a348d3d3fa267dc22faee/kernel/src/net/socket/unix/stream/mod.rs#L104

关于connect时listener的push_incoming(endpoint)之前和@SMALLC讨论过一个逻辑问题,这一部分是参考asterinas(用aos简替)的实现,aos的endpoint是实在的文件路径,而我们是直接用的inode,在connect时客户端和服务端会存有对端的endpoint,服务端会将建立连接的connected结构体(未成型的socket)推入backlog队列中,然后accept时将其推出封装成新的stream/seqpacket socket给服务端用于与客户端通信。
现在问题是:因为aos的endpoint是路径,所以服务端的listener socket和accpet出的新socket的本端地址是一样的,但是inode不一样,每一个socket都有不同的inode,所以之前dragonos connect时客户端存的对端inode是listenner的inode(目前因为还没实现非阻塞,是通过获取对端inode的buffer进行写入),这样客户端就会写入listener中,而listener是不允许读写的,服务端也不能正常接收到信息。
解决方法:1.客户端和服务端在connect时共用一对buffer(即客户端的读写端互为对方的写读端),不通过对端的endpoint获取buffer,因其扩展性不好,不易维护,故弃用该方案
2.connect时listener的backlog(incomming_connect)队列改为存成型socket的inode,即在connect时服务端就创建新的connect inode,客户端和listener的队列存的都是这个inode,便于客户端正确获取对端信息。

2 个赞

这个也许可以用宏实现?我感觉如果是SFINAE能实现的我觉得大概也可以抽象一个宏出来?代码大概在哪块,我瞅瞅

关于unix socket bind时socket与新创建的文件绑定问题讨论

背景:unix socket bind时,会根据用户传进来的path创建一个socket文件,并和使用fd找到的socket链接在一起。但是由于socket在创建时已为inode,在bind时还要为socket创建一个带有路径的inode?这是冲突的。

目前的解决思路是找到办法使得bind时使用path创建的文件能够使用它找到被bind的socket inode。目前想到的解决方案有三个,一是在全局建立表记录所有调用过bind绑定文件地址的unix socket inode,使用path创建的文件查询unix socket inode。二是使用path创建的inode作为unix socket addr的成员,在bind时对addr进行更新,在全局建立表记记录listen状态的socket,使用该socket的addr里的path创建的文件查询。三是找个文件系统收留socket文件,使其能用link去绑定,当然实现sockfs自然是最终方案。

其实就是希望解决能使用用户传进来的path与目标socket进行绑定,后续能通过这个路径找到该socket。

3 个赞

这个可以新开一个帖子,在这里太下面了