【进度记录】Netlink 协议和 uevent 机制的作用原理与设计实现

本贴记录Netlink 协议和 uevent 机制开发进度以及当前正在解决的问题

当前进度

已经手搓完成了 Netlink 协议和 Uvenet 机制的大部分代码,初步进入测试阶段,正在通过调试尝试发现和解决实际运行过程中的问题

当前正在解决的问题

  • 8.10: socket_handle() todo!
    socket trait 中基于smoltcp 的 socket 结构体构造了一个 GlobalSocketHandle 类型的变量 handle 作为数据成员。在 handle 初始化的时候需要该 socket 结构体对象传入;而由于 NetlinkSock 不包含 smoltcp 的 socket 结构体,无法直接通过添加 handle 字段并初始化的方式来实现该方法,从而引发了 panic。

  • 8.9: (已解决)metadata() 和 posix_item() 会被调用并由于未实现而发生panic
    在 Netlinksock 中添加 metadata 和 posix_item 字段并实现这两个函数即可。

  • 8.8:(已解决)bind 系统调用 和 netlink_bind 函数的调用问题
    SYS_BIND 的具体执行函数没法执行 NetlinkSock 实现的 bind 方法,抛出 ENOSYS。

    • 解决方法:SocketInode 的 SYS_BIND中最终调用 bind 函数的 socket 类型是根据传给它的 Socket_type 决定的,在 new_socket() 中模式匹配部分指定对应的 new 函数即可。NetlinkSocket 一般使用的socket_type 为 RAW 和 DGRAM(Datagram)
  • 8.4:(已解决)用户空间传递了错误的参数给 bind 系统调用
    用户空间使用 nix 库创建套接字并尝试 bind 的时候,内核中通过打日志发现到SYS_BIND的时候,addr.family 不是预期的16(Netlink 的AddrFamily 为16)而是2(INet = 2)

    • 解决方法:NetlinkAddr 才是和内核的 SockAddr 结构体相兼容的,而不是 nix 库的SockAddr。用户空间传参的时候转换一下即可。
  • 7.30:(已解决)NetlinkSocket 和 Socket 的类型转换
    由于设计的原因,当前有一个 NetlinkSocket trait 和一个NetlinkSock 的 struct,分别用于实现Linux 6.1.9 中的netlink_socket 和 sock。bind 系统调用中使用的套接字接口是 Socket,但是该trait 更像是一个函数操作集而不同于 Linux 6.1.9 中的Sock结构体,因此当初实现的时候选择构造了一个实现了 Socket trait 的 NetlinkSocket trait。如今由于netlink_bind 函数接受的是 NetlinkSocket ,而 Socket 似乎是无法转换为 NetlinkSocket 的。

Netlink 协议和 uevent 机制的作用原理与设计实现解析

Netlink 协议

在用户空间使用 netlink 套接字和内核通信

在用户空间使用 netlink 套接字和内核通信,和传统的套接字有所区别。相同的都是需要首先使用 socket 系统调用,创建用户空间套接字,不同的是内核也要创建对应的内核套接字,两者通过 nl_table 链表进行绑定; nl_table 是 netlink 机制的核心数据结构,围绕此结构的内核活动有:

  • 用户空间应用程序使用 socket 系统调用创建用户空间的套接字,然后在 bind 系统调用时,内核 netlink_bind 函数将调用 netlink_insert (sk, portid) 将此用户态套接字和应用程序的进程 pid 插入 nl_table,这里参数 portid 就是进程 pid;
  • 创建内核套接字时,调用 netlink_insert(sk, 0) 将此用户态套接字插入 nl_table(因为是内核套接字,这里 portid 是0);
  • 用户空间向内核发送 netlink 消息时,调用 netlink_lookup 函数,根据协议簇和 portid 在nl_table 快速查找对应的内核套接字对象;
  • 当内核空间向用户空间发送 netlink 消息时,调用调用 netlink_lookup 函数,根据协议簇和 portid 在 nl_table 快速查找对应的用户套接字对象.

uevent 机制

uevent 机制是 Linux 内核用于通知用户空间有关设备事件(如设备添加、移除或更改)的机制。它是 Linux 内核设备模型的一部分,通过 uevent ,内核可以将设备事件发送到用户空间,用户空间的进程可以监听这些事件并作出相应的处理。

工作原理

  1. 设备事件生成
  • 当内核中的设备状态发生变化(如设备插入、移除或属性变化)时,内核会生成一个 uevent 事件。
  1. 通过 netlink 发送事件
  • 内核通过 netlink 套接字将 uevent 事件发送到用户空间。netlink 是一种用于内核和用户空间进程之间通信的机制。
  1. 用户空间处理
  • 用户空间的进程(如 udevd 或其他监听 uevent 事件的进程)接收到 uevent 事件后,可以根据事件类型和内容执行相应的操作,如加载驱动程序、创建设备节点或更新设备配置。

关键组件

  • sysfs
    • sysfs 是一个虚拟文件系统,提供了内核对象的视图。设备的属性和状态可以通过 sysfs 文件系统进行访问和修改。
  • netlink
    • netlink 是一种 IPC(进程间通信)机制,允许内核和用户空间进程之间进行双向通信。uevent 事件通过 netlink 套接字发送到用户空间。
  • uevent 文件
    • 每个设备在 sysfs 中都有一个 uevent 文件,当写入该文件时,可以触发 uevent 事件。

目前设计实现的具体方案

trait 和 struct

  • trait NetlinkSocket
  • Struct NetlinkSock
  • Struct NetlinkTable
    Linux 6.1.9 中 hash 字段使用的是自己实现的 rhashtable,由于自己实现 rhashtable 以及相关接口比较繁杂,暂时使用了功能近似的 hashmap 替代。
  • NL_TABLE: RwLock<Vec>
  • Struct SkBuff
    SkBuff 第一版实现,原本使用的是 smoltcp 库中的 PacketBuffer<'a> 封装,但由于生命周期标注的存在,会影响到上层很多其他结构的生命周期,并且由于缺少很多必要的 trait 和 属性,最终参照 aya_ebpf 的 __sk_buff 实现了现有的 SkBuff 。
  • struct UeventSock

function

enum 和 const

备注

相关讨论

对比Linux 6.1.9,未实现的部分功能

  • struct Net:网络命名空间

未来拓展

  • netlink 协议族有其他的类别,例如 route 和 generic netlink
  • classic netlink 还是 generic netlink?
    • generic netlink 使用 classic netlink 的API。generic netlink 是 netlink 的一种扩展,它支持1023个子协议号,弥补了 netlink 协议类型较少的缺陷。generic netlink 基于 netlink,但是在内核中,generic netlink 的接口与 netlink 并不相同。generic netlink 的API是通过netlink 的API来实现的,因此 generic netlink 使用 classic netlink 的API。
    • 经典 Netlink 和通用 Netlink 之间的主要区别是子系统标识符的动态分配和自省的可用性。 在理论上,协议没有显著的区别,然而,在实践中,经典 Netlink 实验了通用 Netlink 中被放弃的概念(实际上,它们通常只在一个单一子系统的一小角落中使用)。
1 个赞

可以使用intertrait库来做,参考“dyn device转 dyn platformDevice(或者I8042PlatformDevice)”:

2 个赞
  • 8.4:bind 系统调用 和 netlink_bind 函数的调用问题
    用户空间使用 nix 库创建套接字并尝试 bind 的时候,内核中通过打日志发现到SYS_BIND的时候,addr.family 不是预期的16(Netlink 的AddrFamily 为16)而是2(INet = 2)

  • 8.10: socket_handle() todo!
    socket trait 中基于smoltcp 的 socket 结构体构造了一个 GlobalSocketHandle 类型的变量 handle 作为数据成员。在handle 初始化的时候需要该 socket 结构体对象传入;而由于 NetlinkSock 不包含 smoltcp 的 socket 结构体,无法直接通过添加 handle 字段并初始化的方式来实现该方法,从而引发了 panic。
1 个赞

这个实现是模仿rcore的,我在重构时进行了优化;你的代码仓库分支可以贴上来,明天的 network sig 我们可以来探讨这个问题。

目前转移到在网络子模块的重构分支下,将与其他重构工作一起配合开发

8.30 今天被一个事情困扰了:背景是最近要写netlink_recvmsg作为socket read接口的实现,而之前写 netlink 这一块的时候主要是参照Linux的sk_buff结构,来作为网络数据包,然后参照了aya_eBPF 库的 sk_buff,搞了一个sk_buff, 涉及到比较多的相关的操作。然后今晚看 zcoreasterinas 的datagram这一块 好像都是直接没有这个数据包的结构,直接用vec进行数据的读取。我们的inet里的datagram参考他们似乎也是这样的,不过看到后面是smoltcp的recv_slice调用。就我现在有点不知道是该继续沿用Linux或者aya_ebpf这一套去操作底层的unsafe实现一大堆sk_buff的这个操作还是全都简化成类似于Arc<Mutex<Vec<Vec>>>的data。

DragonOS-Community/DragonOS at feat-network-syscall (github.com)

1 个赞

个人感觉直接简单实现即可。没必要一开始过度设计了哈哈哈。

是的,目前在参考zcore这一块简化设计