我正在开发的 Linux 内核驱动程序之一是在内核中使用网络通信(sock_create()
、sock->ops->bind()
等)。
问题是会有多个套接字来接收数据。所以我需要一些可以模拟select()
或poll()
在内核空间中的东西。由于这些函数使用文件描述符,除非我使用系统调用来创建套接字,否则我不能使用系统调用,但这似乎没有必要,因为我在内核中工作。
所以我正在考虑将默认sock->sk_data_ready
处理程序包装在我自己的处理程序(custom_sk_data_ready()
)中,这将解锁一个信号量。然后我可以编写自己的kernel_select()
函数来尝试锁定信号量并阻塞等待直到它打开。这样内核函数就会进入休眠状态,直到信号量被custom_sk_data_ready()
. 一旦kernel_select()
获得锁,它就会解锁并调用custom_sk_data_ready()
重新锁定它。所以唯一的额外初始化是custom_sk_data_ready()
在绑定套接字之前运行,这样第一次调用就custom_select()
不会错误地触发。
我看到一个可能的问题。如果发生多次接收,则多次调用custom_sk_data_ready()
将尝试解锁信号量。因此,为了不丢失多个调用并跟踪sock
正在使用的调用,必须有一个指向正在使用的套接字的表或指针列表。并且custom_sk_data_ready()
必须在表/列表中标记它通过了哪个套接字。
这个方法好听吗?或者我应该在使用标准系统调用时解决用户/内核空间问题吗?
初步发现:
结构中的所有回调函数sock
都在中断上下文中调用。这意味着他们无法入睡。为了允许主内核线程在准备好的套接字列表上休眠,使用互斥锁,但custom_sk_data_ready()
必须像互斥锁上的自旋锁一样(mutex_trylock()
重复调用)。这也意味着任何动态分配都必须使用该GFP_ATOMIC
标志。
额外的可能性:
对于每个打开的套接字,将每个套接字替换sk_data_ready()
为自定义的 ( custom_sk_data_ready()
) 并创建一个工作 ( struct work_struct
) 和工作队列 ( struct workqueue_struct
)。process_msg()
每个工人都将使用一个通用功能。创建一个内核模块级全局列表,其中每个列表元素都有一个指向套接字的指针并包含工作结构。当一个socket上的数据准备好时,custom_sk_data_ready()
会执行并找到与同一个socket匹配的list元素,然后queue_work()
用list元素的工作队列和worker调用。然后process_msg()
调用该函数,既可以通过struct work_struct *
参数的内容(地址)找到匹配的列表元素,也可以使用container_of()
宏获取保存工作结构的列表结构的地址。
哪种技术最可靠?