用户和内核空间之间的共享信号量

精简版

是否可以在用户空间和内核空间之间共享信号量(或任何其他同步锁)? 命名的POSIX信号量具有内核持久性 ,这就是为什么我想知道是否有可能从内核上下文创建和/或访问它们。

由于有关正常使用POSIX信号量的大量信息,搜索互联网并没有多大帮助。

长版

我正在为实时系统开发一个统一的界面 ,我在其中添加了一些书籍,由信号量保护。 这些书籍保留是在资源分配和解除分配上完成的,这是在非实时环境中完成的。

使用RTAI,等待和发布信号量的线程需要处于实时上下文中。 这意味着使用RTAI的命名信号量意味着在用户空间中的每个等待/发布上切换实时和非实时上下文,更糟糕的是,为内核空间中的每个sem / wait创建一个短实时线程。

我正在寻找的是一种在内核和用户空间之间共享正常Linux或POSIX信号量的方法,这样我就可以安全地在非实时环境中等待/发布它。

任何有关此主题的信息将不胜感激。 如果这是不可能的,你还有其他任何想法如何完成这项任务? 1

1一种方法是添加系统调用,在内核空间中具有信号量,并让用户空间进程调用该系统调用,并且信号量将全部在内核空间中进行管理。 如果我不是因为这个而不必修补内核,我会更高兴。

嗯,你是在正确的方向,但不是 –

名为POSIX信号量的Linux基于FUTex,它代表快速用户空间互斥。 顾名思义,虽然它们的实现由内核辅助,但其中很大一部分是由用户代码完成的。 在内核和用户空间之间共享这样的信号量需要在内核中重新实现此基础结构。 可能,但肯定不容易。

另一方面,SysV信号量完全在内核中实现,只能通过标准系统调用(例如sem_timedwait()和朋友)访问用户空间。

这意味着每个与SysV相关的操作(信号量创建,获取或释放)实际上都是在内核中实现的,您只需从代码中调用底层内核函数即可从内核中获取相同的信号量。

因此,您的用户代码将只调用sem_timedwait() 。 这很容易。

内核部分有点棘手:你必须找到在内核中实现sem_timedwait()和相关调用的代码(它们都在文件ipc / sem.c中)并创建每个的副本在没有调用copy_from_user(...)copy_to_user(..)和朋友的情况下执行原始函数的函数。

原因是那些内核函数期望从带有指向用户缓冲区的指针的系统调用中调用,而你想用内核缓冲区中的参数调用它们。

sem_timedwait()为例 – 相关的内核函数是ipc / sem.c中的sys_timedwait() (参见http://lxr.free-electrons.com/source/ipc/sem.c#L1537 )。 如果你在你的内核代码中复制这个函数,只需删除执行copy_from_user()copy_to_user() ,只需使用传递的指针(因为你将从内核空间调用它们),你将得到内核等效的函数,从内核空间,沿着用户空间获取SysV信号量 – 只要你从内核中的用户上下文中调用它们 (如果你不知道这最后一句是什么意思,我强烈建议你阅读Linux设备驱动程序,第3版) 。

祝你好运。

我能想到的一个解决方案是在主内核模块上有一个/proc (或/sys或者其他)文件,其中向其写入0 (或从中读取/写入)将导致它发出up / downsemaphore 。 导出该信号量允许其他内核模块直接访问它,而用户应用程序将通过/proc文件系统。

我还在等着看原问题是否有答案。

无论如何我对此并不是很有经验,但这是我的看法。 如果你看看glibc的sem_open和sem_wait的实现,它实际上只是在/ dev / shm中创建一个文件,从中创建一个结构,并在其上使用primefaces操作。 如果要从用户空间访问命名信号量,则可能必须修补tmpfs子系统。 但是,我认为这很难,因为确定一个文件是否是一个命名信号量并不是直截了当的。

一种更简单的方法可能是重用内核的信号量实现并让内核管理用户空间进程的信号量。 为此,您将编写一个与设备文件关联的内核模块。 然后为设备文件定义两个ioctl,一个用于等待,一个用于post。 这是编写内核模块的好教程,包括设置设备文件和为其添加I / O操作。 http://www.freesoftwaremagazine.com/articles/drivers_linux 。 我不知道如何实现ioctl操作,但我认为你可以只为file_operations结构的ioctl成员分配一个函数。 不确定函数签名应该是什么,但你可以通过在内核源代码中挖掘来解决它。

我相信你知道,即使是最好的解决方案也可能非常难看。 如果我在你的位置,我会简单地承认战斗并使用集合点来同步进程

我已经阅读了你的项目自述文件 ,我有以下观察。 提前道歉:

首先,已经存在实时系统的通用接口。 它被称为POSIX; 当然VxWorks,Integrity和QNX都符合POSIX标准,根据我的经验,如果你在POSIX API中开发,可移植性问题很少。 POSIX是否合理是另一回事,但它是我们所有人都使用的。

[大多数RTOS都符合POSIX的原因是因为其中一个主要市场是防御设备。 并且美国国防部不会让你使用操作系统作为他们的非IT设备(例如雷达),除非它符合POSIX ……这几乎使得在没有给它POSIX的情况下做RTOS的商业上是不可能的]

其次,通过应用PREMPT_RT补丁集,可以将Linux本身变成一个非常好的实时操作系统。 在所有RTOS中,从有效利用所有这些多核CPU的角度来看,这可能是目前最好的。 然而,它并不像其他操作系统那么硬实时操作系统,所以它的交换条件。

RTAI采用了一种不同的方法,实际上将自己的RTOS置于Linux下,使Linux只能在其操作系统中运行一个任务。 这种方法在某种程度上是可以接受的,但是RTAI的一个重要原因是现在的实时位(据我所知) 符合POSIX(尽管API看起来像是刚刚在前面停留了rt_一些POSIX函数名称)以及与其他事物的交互现在正如您所发现的那样,非常复杂。

PREEMPT_RT是一个比RTAI更具侵入性的补丁集,但回报是其他一切(如POSIX和valgrind)保持完全正常。 还有像FTrace这样的好东西。 因此,簿记是仅使用现有工具的情况,而不必编写新工具。 此外,看起来PREEMPT_RT正在逐步进入主流Linux内核。 这将使得像RTAI这样的其他补丁集几乎毫无意义。

所以Linux + PREEMPT_RT为我们提供了实时POSIX和一堆工具,就像所有其他RTOS一样; 全面共识。 哪种听起来像是你项目的目标。

我为没有帮助你的项目的“如何”而道歉,而且查询“为什么?”对我来说是非常不合理的。 它也是。 但我觉得重要的是要知道那里有确定的东西似乎与你想要做的事情有很大的重叠。 取消国王POSIX将是困难的。

我想以不同的方式回答这个问题:你不想这样做。 有充分的理由说明为什么没有接口来执行此类操作,并且有充分的理由说明为什么所有其他内核子系统的设计和实现都不需要在用户和内核空间之间共享锁。 如果您开始使用可能阻止内核执行某些操作的用户空间,锁定顺序和意外位置的隐式锁定的复杂性将很快失控。

让我回想一下15年前我做过的一个很长的调试会议,至少可以解释一下你可能遇到的复杂问题。 我参与了开发一个文件系统,其中大部分代码都在用户区。 像FUSE这样的东西。

内核将执行文件系统操作,将其打包到消息中并将其发送到userland守护程序并等待回复。 userland守护程序读取消息,执行操作并将响应写入内核,该内核将唤醒并继续操作。 简单的概念。

您需要了解的有关文件系统的一件事是锁定。 当你查找文件的名称时,例如“foo / bar”,内核以某种方式获取目录“foo”的节点,然后锁定它并询问它是否有文件“bar”。 文件系统代码以某种方式找到“bar”, 锁定它然后解锁“foo”。 锁定协议非常简单(除非您正在进行重命名),父级始终在子级之前被锁定,并且子级在释放父级锁之前被锁定。 该文件的查找消息是在目录仍然被锁定时将被发送到我们的userland守护程序的消息,当守护程序回复时,内核将继续先锁定“bar”然后解锁“foo”。

我甚至不记得我们正在调试的症状,但我记得这个问题并非简单可重复,它需要数小时的文件系统折磨程序才能显现出来。 但几周之后我们就知道发生了什么。 假设我们文件的完整路径是“/ a / b / c / foo / bar”。 我们正在对“bar”进行查找,这意味着我们正在锁定“foo”。 守护进程是一个普通的用户空进程,因此它执行的某些操作可以阻止并且也可以被抢占。 它实际上是通过网络进行通话,因此可以阻止很长时间。 当我们等待userland守护进程时,其他一些进程由于某种原因想要查找“foo”。 要做到这一点,它有“c”的节点,当然是锁定的,并要求它查找“foo”。 它设法找到它并尝试锁定它(它必须先被锁定才能释放“c”上的锁)并等待“foo”上的锁被释放。 另一个进程是想要查找“c”,它当然会在锁定“b”时等待锁定。 另一个过程等待“b”并保持“a”。 另一个过程想要“a”并持有“/”锁。

这不是问题,还没有。 这有时也会发生在正常的文件系统中,锁可以一直级联到根,你等待一段时间用于慢速磁盘,磁盘响应,拥塞缓解,每个人都获得锁定,一切运行正常。 但在我们的例子中,长时间保持锁定的原因是因为我们的分布式文件系统的远程服务器没有响应。 X秒后,userland守护程序超时,并且在响应内核之前“bar”上的查找操作失败之前,它会将消息记录到带有时间戳的syslog。 时间戳需要的东西之一是时区信息,所以它需要打开“/ etc / localtime”,当然要做到这一点,它需要开始查找“/ etc”并为此需要锁定“/ ”。 “/”已被其他人锁定,因此userland守护程序等待其他人解锁“/”而其他人等待5个进程的链并锁定守护进程以响应。 系统最终陷入完全僵局。

现在,也许你的代码不会有这样的问题。 你在谈论一个实时系统,所以可能存在普通内核所没有的控制水平。 但我不确定是否添加一个意想不到的锁定复杂层甚至可以让你保留系统的实时属性,或者确保你在userland中做的任何事情都不会创建死锁级联。 如果你没有页面,如果你从不接触任何文件描述符,如果你从未做过内存操作和其他一些我现在无法想到的事情,你可以放弃在userland和kernel之间共享的锁,但是这很难,你可能会发现意想不到的问题。

我正在考虑内核和用户直接共享内容的方式,即没有系统调用/ copyin-out成本。 我记得的一件事是RDMA模型,其中内核直接从用户空间写入/读取,当然也是同步的。 您可能想要探索该模型,看看它是否适合您的目的。