【问题标题】:Call a userspace function from within a Linux kernel module从 Linux 内核模块中调用用户空间函数
【发布时间】:2013-04-17 18:54:46
【问题描述】:

我正在编写一个简单的 Linux 字符设备驱动程序,以通过 I/O 端口将数据输出到硬件。我有一个函数可以执行浮点运算来计算硬件的正确输出;不幸的是,这意味着我需要将此函数保留在用户空间中,因为 Linux 内核不能很好地处理浮点运算。

这是设置的伪表示(请注意,此代码没有做任何具体的事情,它只是显示我的代码的相对布局):

用户空间功能

char calculate_output(char x){
    double y = 2.5*x;
    double z = sqrt(y);
    
    char output = 0xA3;

    if(z > 35.67){
        output = 0xC0;
    }
    
    return output;
}

内核空间代码

unsigned i;
for(i = 0; i < 300; i++){
    if(inb(INPUT_PORT) & NEED_DATA){
        char seed = inb(SEED_PORT);
        char output = calculate_output(seed);
        outb(output, OUTPUT_PORT);
    }

    /* do some random stuff here */
}

我考虑过使用ioctl 从用户空间函数传递数据,但我不确定如何处理函数调用处于循环中并且在下一次调用@987654324 之前执行更多代码的事实@ 出现。

我设想的工作方式是:

  1. 主用户空间程序将启动内核空间代码(可能通过ioctl
  2. 用户空间程序阻塞并等待内核空间代码
    • 内核空间程序询问用户空间程序输出数据,并阻塞等待
    • 用户空间程序解除阻塞,计算并发送数据 (ioctl?),然后再次阻塞
    • 内核空间程序解除阻塞并继续
  3. 内核空间程序完成并通知用户空间
  4. 用户空间解除阻塞并继续下一个任务

那么我如何在内核空间和用户空间之间进行通信,并且还有阻塞,这样我就不会让用户空间不断轮询设备文件以查看它是否需要发送数据?


警告:虽然在我的示例代码中定点运算可以很好地工作,但在实际代码中它不是一个选项;我需要浮点提供的大范围,而且——即使没有——我担心重写代码以使用定点算术会混淆未来维护者的算法。

【问题讨论】:

  • 您是否有机会拥有用户空间中的几乎所有东西,并且只在内核中提供一个瘦接口启用程序?
  • @ChrisStratton 这就是我想要实现的目标。在 for 循环中发生的东西是更多杂项硬件输出(简单的 outb 调用,仅此而已)。
  • @ChrisStratton 哦等等,你的意思是把for 循环移到内核之外吗?
  • 可以发送信号和执行用户空间应用程序,但它不是一个通用的接口。您是否计划让内核线程执行需要用户空间数据的操作?在这种情况下,您可以通过 write/ioctl 从用户空间写入 kfifo,然后从内核线程中读取此数据。不需要阻止。
  • IMO 的偏好应该始终是尽可能多地放在用户空间中。 Userland 代码往往更易于移植。考虑原始硬件平台停产的场景(我在几年前就看到过这种情况)。替代硬件平台更快、更便宜且架构不同——意味着新内核。因此,您在内核中放入的非硬件特定的定制越少越好。

标签: c linux linux-kernel driver linux-device-driver


【解决方案1】:

我认为最简单的解决方案是在您的内核驱动程序中创建一个字符设备,并使用您自己的虚拟文件操作。然后用户空间可以打开这个设备O_RDWR。你必须实现两个主要的文件操作:

  • read -- 这是内核将数据传回用户空间的方式。此函数在调用read() 系统调用的用户空间线程的上下文中运行,在您的情况下,它应该阻塞,直到内核有另一个需要知道其输出的种子值。

  • write-- 这是用户空间将数据传递到内核的方式。在您的情况下,内核只会获取对先前读取的响应并将其传递给硬件。

然后你会在用户空间中得到一个简单的循环:

while (1) {
    read(fd, buf, sizeof buf);
    calculate_output(buf, output);
    write(fd, output, sizeof output);
}

内核中根本没有循环——一切都在驱动事物的用户空间进程的上下文中运行,内核驱动程序只负责将数据移入/移出硬件。

根据您在内核方面的“在这里做一些随机的事情”是什么,可能无法如此简单地做到这一点。如果你真的需要内核循环,那么你需要创建一个内核线程来运行该循环,然后有一些变量沿着input_datainput_readyoutput_dataoutput_ready 的行,以及一对夫妇等待队列和您需要的任何锁定。

当内核线程读取数据时,将数据放入input_ready 并设置input_ready 标志并向输入等待队列发出信号,然后执行wait_event(&lt;output_ready is set&gt;)read 文件操作将执行 wait_event(&lt;input_ready is set&gt;) 并在数据准备就绪时将数据返回到用户空间。类似地,write 文件操作会将它从用户空间获取的数据放入output_data 并设置output_ready 并向输出等待队列发出信号。

另一种(更丑陋,更不便携)的方法是使用iopermiopl/dev/port 之类的东西完全在用户空间中完成所有操作,包括低级硬件访问。

【讨论】:

  • 我最终基本上实现了这里描述的内容,尽管在 sysfs 属性而不是字符设备节点上使用 read/write
【解决方案2】:

我建议您将执行所有“繁重工作”的代码移至用户模式——也就是说,一次性计算所有 300 个值,然后将它们传递给内核。

我什至不确定您是否可以让任意一段代码从内核调用用户模式。我确信这是可能的,因为例如“信号”就是这样做的,但我不相信你可以“以任何你喜欢的方式”做到这一点(几乎可以肯定,有一些限制,例如,什么你可以在那个函数中做)。这当然看起来不是一个好主意,而且多次回调到用户模式肯定会很慢。

【讨论】:

  • 不幸的是,执行顺序无法更改,这使得将循环移动到用户空间变得困难。我已经调整了我的示例代码以反映我所面临的顺序依赖(即早期的硬件输出可能会影响发送到函数的输入)。
  • 那么我建议你找到一种在内核中执行计算的方法(使用定点或类似的方法) - 将每个字节回调到用户模式并不是一个好主意 - 这需要 10-比您在此处显示的计算长 100 倍。
猜你喜欢
  • 2018-06-08
  • 2017-12-10
  • 2012-06-04
  • 2011-02-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2023-03-12
相关资源
最近更新 更多