如何通过/proc文件解析传递给内核模块的大量数据?涉及IT技术。

9

编辑:我已经找到了可以轻松将大量内核数据写入用户空间的seq_file。现在我正在寻找相反的功能;一种API可以方便地从用户空间读取大量数据(超过一页)。

编辑2:我正在实现一个内核模块的端口,类似于<stdio.h>,能够打开/proc(以及后续的其他虚拟文件系统),并处理输入和输出,就像使用<stdio.h>一样。您可以在此处找到该项目。


我发现了很多关于内核如何向/proc写入大量数据(供用户空间程序使用)的问题,但没有关于相反情况的内容。让我详细解释一下:

这个问题基本上是关于输入被分解为什么算法(例如intint和字符串的混合),假设数据可能在多个缓冲区之间被分隔

例如,想象一下以下数据正在被发送到内核模块:

12345678 81234567 78123456 67812345 5678 1234 45678123 3456 7812 23456781

为了举例说明,假设Linux向/proc处理程序提供的页面大小为20个字节(而不是真实的4KB)。

从内核模块中读取数据的函数会将数据看作如下形式:

call 1:
"12345678 81234567 78"
call 2:
"123456 67812345 5678"
call 3:
" 1234 45678123 3456 "
call 4:
"7812 23456781"

正如你所看到的,在第一次调用时读取了78,但在下一个帧决定它是整数还是分割在帧之间之前,不应该对其进行处理。
现在我找到了似乎仅用于内核想向用户写入数据而非读取数据的seq_file(或者可能是HOWTO写得很糟糕)。

我的解决方案

到目前为止,我已经想出了以下解决方案(我是从记忆中写的,可能会漏掉一些错误检查,但请耐心等待):

在初始化阶段(比如init_module):

initialize mutex1 to 1 and mutex2 to 0
create /proc entry
call data_processor

/proc读取器:

1. down(mutex1)    /* down_interruptible of course, but let's not get into details */

2. copy_from_user to an internal buffer
   buffer_index = 0
   data_length = whatever the size is

3. strip spaces from end of buffer (except if all left from buffer is 1 space)
   if so, there_was_space_after = 1 else 0

4. up(mutex2)

我稍后会解释为什么要去掉空格。 get_int 函数:
wait_for_next = 0
number_was_cut = 0
last_number = 0

do
{
    1. down(mutex2)

    2. if (number_was_cut && !isdigit(buffer[buffer_index]))
           break     /* turns out it wasn't really cut
                        as beginning of next buffer is ' ' */
       number_was_cut = 0
       wait_for_next = 0

    3. while (buffer_index < data_length && !isdigit(buffer_index[buffer_index]))
           ++buffer_index;    /* skip white space */

    4. while (buffer_index < data_length && isdigit(buffer[buffer_index]))
           last_number = last_number * 10 + buffer[buffer_index++] - '0';

    5. if (buffer_index >= data_length && !there_was_space_after)
           number_was_cut = 1
           wait_for_next = 1
           up(mutex1)         /* let more data come in */
       else
           up(mutex2)         /* let get_int continue */
           break
} while (wait_for_next)

return last_number

data_processor函数(例如):

int first_num = get_int()
int sencod_num = get_int()
for i = first_num to second_num
    do_whatever(get_int())
解释:首先看一下data_processor。它不会涉及到数据如何读取的复杂性,因此它只获取整数并对其进行任何操作。现在让我们看看/proc reader。它基本上等待data_processor调用足够次数的get_int来消耗所有当前数据(步骤1),然后将下一个缓冲区复制到内部存储器中,使data_processor继续(步骤2)。然后它需要去掉尾随空格,以便稍微简化get_int(步骤3)。最后,它向get_int发出信号,表示可以开始读取数据(步骤4)。 get_int函数首先等待数据到达(步骤1)(暂时忽略步骤2),跳过任何不需要的字符(步骤3),然后开始读取数字(步骤4)。读取数字的结束有两种可能性:到达缓冲区的末尾(在这种情况下,如果/proc reader没有去掉任何空格,则数字可能被切割在帧之间),或者遇到空格。在前一种情况下,它需要向/proc reader发出信号以读取更多数据,并等待另一个周期将其余数字附加到当前数字中,在后一种情况下,它返回数字(步骤5)。如果从上一帧继续,则检查新帧是否以数字开头。如果不是,则上一个数字实际上是一个整数,应该返回。否则,它需要继续将数字附加到上一个数字上(步骤2)。

问题

这种方法的主要问题在于它过于复杂。当添加get_string或读取的整数可能为十六进制等时,它变得更加复杂。基本上,你必须重新发明sscanf!请注意,sscanf可以在get_int的第4步(或在get_string中也可以使用,但当输入为十六进制时,情况会更加棘手(想象一下16进制数字被切割在0和x0212ae4之间)。即使如此,它只是替换了get_int的第4步,其余的部分仍然应该保持不变。

实际上,我花费了很多时间进行测试和修复所有特殊情况的错误。这也是为什么它对我来说看起来不太优雅的另一个原因。

问题

我想知道是否有更好的方法来处理这个问题。我知道使用共享内存可能是一种选择,但我正在寻找这个任务的算法(更多是出于好奇心,因为我已经有了工作解决方案)。更具体地说:

  • Linux内核中是否已经有一个实现好的方法,可以像普通的C FILE一样处理数据,并自行将数据分成页面?
  • 如果没有,我是否过于复杂化了问题,错过了明显的简单解决方案?
  • 我认为fscanf也面临着类似的问题。那么这个问题是如何解决的呢?

附带问题:我在互斥锁上阻塞/proc读取器是否很糟糕?我的意思是,写入数据可能会阻塞,但我不确定这通常发生在用户空间还是内核空间。

3个回答

1

我最终决定写点正式的东西来解决这个问题。

kio

kio 简而言之,将是内核模块的 C 标准库 stdio.h 的一个端口。它将支持 /proc/sys/dev 文件系统中的读和写模式,无论是文本还是二进制。 kio 严格遵循标准,但在内核空间中进行了微小的调整以确保安全性。

当前状态:

  • 可以创建 /proc 文件
  • 已实现读取函数
  • 已实现写入函数
  • 文件一次只能被一个用户打开

1

你可能会对request_firmware()接口感兴趣。整个过程在交给你之前会被内核缓冲。

否则,也许sysfs二进制属性接口比proc更有用?


谢谢您的回复,但您能详细解释一下吗?据我所知,您可以通过request_firmware获取整个文件,但它并不是用于初始化数据的。我对了解更多关于sysfs很感兴趣。我确实在使用它处理其他事情,但据我所读,他们说“使用sysfs处理小数据”,即使我使用它处理大数据,这是否会有相同的问题,即以分页的方式提供数据给您? - Shahbaz

0

实现一个名为 get_token() 的独立函数,该函数:

  1. 从内部缓冲区读取,直到找到空格为止,然后返回到目前为止的文本。
  2. 当在缓冲区的末尾时,从用户空间读取更多数据到缓冲区中。

这样你的其他解析逻辑就大大简化了(不必手动解析整数等)。只需对返回的字符串调用 sscanf()或 strtol()。

限制是您需要对单个令牌强加最大长度,以便它可以适合缓冲区。


那并不完全可行。例如,1234abcd 是两个令牌,一个数字,然后是一个字符串。这个问题非常古老,但现在我想,可能实现 getc() 处理分页和另一个调用 getc 并解析整数/字符串等的函数会简单得多。尽管如此,这感觉像是重写 fscanf - Shahbaz

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接