序言
出于网络模块中纷乱的epoll、wait_queue回调,过于简单实现的 iface_poll
设计,目不暇接的句柄处理,以及这导致的一些资源冲突,以及这些资源冲突导致的进一步的设计的妥协(一系列的全局资源),我实验性地尝试重构整个 net
模块,尝试简化并缕清 iface
- socket
- inode
的关系,压缩整个模块所需要的抽象层次,目的是减少重复实现,实现接口与资源的解耦,优化整个 net
子系统的api,并在这基础上进一步实现先前由于种种未能实现的功能、解决潜在的bug:
初步设计
以下设计均为我于 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
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中公共实现
目前非inet的socket的缓冲区由socket各自实现,考虑将其读写逻辑基本一致,是不是单独成结构体开放申请、读写、销毁接口给各个非inet socket(inet socket缓冲区由smoltcp维护了)服务会更简洁,可维护性扩展性会更好?
可以考虑,只是内核-用户态层面的数据所需要的缓冲区应该是同构的,linux上的实现是重用的吗?
关于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能实现的我觉得大概也可以抽象一个宏出来?代码大概在哪块,我瞅瞅
SMALLC
20
关于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 个赞