从Linux内核模块中调用用户空间函数

Vil*_*ray 5 c linux driver linux-device-driver linux-kernel

我正在编写一个简单的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;
}
Run Code Online (Sandbox Code Playgroud)

内核空间代码:

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 */
}
Run Code Online (Sandbox Code Playgroud)

我想过ioctl用来传递来自用户空间函数的数据,但我不知道如何处理函数调用处于循环中并且在下一次调用calculate_output发生之前执行更多代码的事实.

我设想这个工作的方式是:

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

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


需要注意的是:虽然定点算法在我的示例代码中可以很好地工作,但它不是实际代码中的一个选项; 我需要浮点提供的大范围 - 即使不是 - 我害怕重写代码以使用定点算法会混淆未来维护者的算法.

Rol*_*and 5

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

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

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

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

while (1) {
    read(fd, buf, sizeof buf);
    calculate_output(buf, output);
    write(fd, output, sizeof output);
}
Run Code Online (Sandbox Code Playgroud)

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

根据您在内核端“在这里做一些随机的事情”的内容,可能无法如此简单地做到这一点。如果您确实需要内核循环,那么您需要创建一个内核线程来运行该循环,然后使用 、 和 等一些变量,input_data以及input_ready几个output_data等待output_ready队列和您需要的任何锁定。

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

另一种(更丑陋、更不易移植)的方法是使用类似ioperm,iopl/dev/port完全在用户空间中完成所有操作,包括低级硬件访问。

  • 我最终基本上实现了这里描述的内容,尽管是在 **sysfs** 属性上而不是在字符设备节点上使用“read”/“write”。 (3认同)