copy_to_user returns 字符设备读取函数出错
copy_to_user returns an error in a char device read function
我已经为我的内核模块实现了一个字符设备,并为它实现了一个读取函数。读取函数调用copy_to_user
到return数据给调用者。我最初以阻塞方式(使用 wait_event_interruptible
)实现读取功能,但即使我以非阻塞方式实现读取,问题也会重现。我的代码是 运行 在 MIPS 处理器上。
用户space程序打开字符设备并读入分配在堆栈上的缓冲区。
我发现 copy_to_user
偶尔会无法复制任何字节。此外,即使我将 copy_to_user
替换为对 memcpy
的调用(仅用于检查目的......我知道这不是正确的做法),并立即打印出目标缓冲区之后,我看到 memcpy
未能复制任何字节。
我不太确定如何进一步调试 - 我如何确定为什么内存没有被复制?有没有可能是进程上下文错误?
编辑:这是一些伪代码,概述了代码当前的样子:
用户模式(重复运行):
char buf[BUF_LEN];
FILE *f = fopen(char_device_file, "rb");
fread(buf, 1, BUF_LEN, f);
fclose(f);
内核模式:
char_device =
create_char_device(char_device_name,
NULL,
read_func,
NULL,
NULL);
int read_func(char *output_buffer, int output_buffer_length, loff_t *offset)
{
int rc;
if (*offset == 0)
{
spin_lock_irqsave(&lock, flags);
while (get_available_bytes_to_read() == 0)
{
spin_unlock_irqrestore(&lock, flags);
if (wait_event_interruptible(self->wait_queue, get_available_bytes_to_read() != 0))
{
// Got a signal; retry the read
return -ERESTARTSYS;
}
spin_lock_irqsave(&lock, flags);
}
rc = copy_to_user(output_buffer, internal_buffer, bytes_to_copy);
spin_unlock_irqrestore(&lock, flags);
}
else rc = 0;
return rc;
}
调试了很多,但最终 Tsyvarev 的提示(关于不调用 copy_to_user
并使用自旋锁的注释)似乎是原因。
我们的进程有一个后台线程,偶尔会启动一个新进程 (fork
+ exec
)。当我们禁用该线程时,一切正常。我们拥有的最好的理论是,fork 使我们所有的内存页都是写时复制,因此当我们试图复制到它们时,内核必须做一些使用自旋锁无法完成的工作。希望它至少有一定的意义(尽管我猜这只适用于子进程,而父进程的页面将只是保持可写,但谁知道...)。
我们重写了无锁代码,问题消失了。
现在我们只需要验证我们的无锁代码在不同的架构上确实是安全的。简单易行。
我已经为我的内核模块实现了一个字符设备,并为它实现了一个读取函数。读取函数调用copy_to_user
到return数据给调用者。我最初以阻塞方式(使用 wait_event_interruptible
)实现读取功能,但即使我以非阻塞方式实现读取,问题也会重现。我的代码是 运行 在 MIPS 处理器上。
用户space程序打开字符设备并读入分配在堆栈上的缓冲区。
我发现 copy_to_user
偶尔会无法复制任何字节。此外,即使我将 copy_to_user
替换为对 memcpy
的调用(仅用于检查目的......我知道这不是正确的做法),并立即打印出目标缓冲区之后,我看到 memcpy
未能复制任何字节。
我不太确定如何进一步调试 - 我如何确定为什么内存没有被复制?有没有可能是进程上下文错误?
编辑:这是一些伪代码,概述了代码当前的样子:
用户模式(重复运行):
char buf[BUF_LEN];
FILE *f = fopen(char_device_file, "rb");
fread(buf, 1, BUF_LEN, f);
fclose(f);
内核模式:
char_device =
create_char_device(char_device_name,
NULL,
read_func,
NULL,
NULL);
int read_func(char *output_buffer, int output_buffer_length, loff_t *offset)
{
int rc;
if (*offset == 0)
{
spin_lock_irqsave(&lock, flags);
while (get_available_bytes_to_read() == 0)
{
spin_unlock_irqrestore(&lock, flags);
if (wait_event_interruptible(self->wait_queue, get_available_bytes_to_read() != 0))
{
// Got a signal; retry the read
return -ERESTARTSYS;
}
spin_lock_irqsave(&lock, flags);
}
rc = copy_to_user(output_buffer, internal_buffer, bytes_to_copy);
spin_unlock_irqrestore(&lock, flags);
}
else rc = 0;
return rc;
}
调试了很多,但最终 Tsyvarev 的提示(关于不调用 copy_to_user
并使用自旋锁的注释)似乎是原因。
我们的进程有一个后台线程,偶尔会启动一个新进程 (fork
+ exec
)。当我们禁用该线程时,一切正常。我们拥有的最好的理论是,fork 使我们所有的内存页都是写时复制,因此当我们试图复制到它们时,内核必须做一些使用自旋锁无法完成的工作。希望它至少有一定的意义(尽管我猜这只适用于子进程,而父进程的页面将只是保持可写,但谁知道...)。
我们重写了无锁代码,问题消失了。
现在我们只需要验证我们的无锁代码在不同的架构上确实是安全的。简单易行。