首页 文章

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

提问于
浏览
20

短版

是否可以在用户空间和内核空间之间共享信号量(或任何其他同步锁)? Named POSIX semaphores have kernel persistence,这就是为什么我想知道是否可以从内核上下文创建和/或访问它们 .

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

长版

我正在开发一个unified interface to real-time systems,我在其中添加了一些书籍,由信号量保护 . 这些书籍保留是在资源分配和解除分配上完成的,这是在非实时环境中完成的 .

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

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

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

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

7 回答

  • 0

    那么,你是在正确的方向,但不是 -

    名为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信号量 - so long as you call them from user context in the kernel (如果您不知道最后一句话是什么意思,我强烈建议您阅读Linux设备驱动程序,第3版) .

    祝你好运 .

  • 2

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

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

  • 2

    我是我的看法 . 如果你看看glibc的sem_opensem_wait的实现,它会从中获取一个结构,并在其上使用原子操作 . 如果要从用户空间访问命名信号量,则可能必须修补tmpfs子系统 . 但是,我认为这很难,因为确定一个文件是否是一个命名信号量并不是直截了当的 .

    一种更简单的方法可能就是重用内核's semaphore implementation and have the kernel manage the semaphore for userspace processes. To do this, you would write a kernel module which you associate with a device file. Then define two ioctl'来实现设备文件,一个用于等待,一个用于发布 . 这是一篇关于编写内核模块的好教程,包括设置设备文件和为其添加I / O操作 . http://www.freesoftwaremagazine.com/articles/drivers_linux . 我不知道如何实现ioctl操作,但我认为你可以只为file_operations结构的ioctl成员分配一个函数 . 不确定函数签名应该是什么,但你可以通过在内核源代码中挖掘来解决它 .

  • 3

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

  • 14

    我已经阅读了你的项目README,我有以下观察 . 提前道歉:

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

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

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

    RTAI采用了一种不同的方法,实际上将自己的RTOS置于Linux下,使Linux只能在其操作系统中运行一个任务 . 这种方法在某种程度上是可以接受的,但RTAI的一个重要影响是现在的实时位(据我所知) not POSIX兼容(尽管API看起来像是发现它们,非常复杂 .

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

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

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

  • 5

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

    让我回想一下我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之间共享的锁,但是这很难,你可能会发现意想不到的问题 .

  • 1

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

相关问题