【问题标题】:copy_to_user returns an error in a char device read functioncopy_to_user 在字符设备读取函数中返回错误
【发布时间】:2017-12-18 06:58:41
【问题描述】:

我已经为我的内核模块实现了一个字符设备,并为它实现了一个读取功能。读取函数调用copy_to_user 将数据返回给调用者。我最初以阻塞方式(使用wait_event_interruptible)实现了读取功能,但即使我以非阻塞方式实现读取,问题也会重现。我的代码在 MIPS 处理器上运行。

用户空间程序打开 char 设备并读入分配在堆栈上的缓冲区。

我发现偶尔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 添加了一些伪代码,希望它能充分说明代码所做的事情。不幸的是,我(还)无法将其缩小到一个小的、可复制的示例。
  • @YSK 你已经声明了两次int rc。从int rc = copy_to_user(output_buffer, internal_buffer, bytes_to_copy); 行中删除int
  • 与任何其他“可能阻止”函数一样,不应使用自旋锁调用copy_to_user
  • @Gaurav 好点 - 不幸的是我没有伪代码编译器:)

标签: c linux-kernel mips embedded-linux


【解决方案1】:

这需要进行相当多的调试,但最终 Tsyvarev 的提示(关于不使用自旋锁调用 copy_to_user 的评论)似乎是原因。

我们的进程有一个后台线程,它偶尔会启动一个新进程 (fork + exec)。当我们禁用此线程时,一切正常。我们最好的理论是,fork 使我们所有的内存页面在写入时复制,所以当我们试图复制到它们时,内核必须做一些使用自旋锁无法完成的工作。希望它至少有一些意义(虽然我猜这只会适用于子进程,而父进程页面将简单地保持可写状态,但谁知道......)。

我们将代码重写为无锁,问题就消失了。

现在我们只需要验证我们的无锁代码在不同架构上确实是安全的。很简单。

【讨论】:

  • 我遇到了与你的程序有类似情况的程序问题,即一个线程使用 fork() + exec() 启动新进程,另一个线程使用 copy_to_user() 和自旋锁读取设备输入() 在内核中。但我可以确认的是,SIGSEGV 在新启动的进程中偶尔会发生崩溃。我想知道我们是否有相同的根本原因。
  • @gzh 可能...我从来没有完全理解为什么copy_to_user 失败了,但确实如此。但是你明白为什么你有SIGSEGV吗? copy_to_user 失败可以为您的应用解释这一点吗?我认为您的下一个故障排除步骤应该是获取核心转储并对其进行分析。但无论如何 - 在自旋锁下调用 copy_to_user 似乎是个坏主意......
  • 我已经获得了子进程的核心转储,但我找不到任何可以从核心转储中引发 SIGSEGV 的证据。 si_addr 是较低的地址,例如 0xf,与回溯显示的那些处理相距甚远。我可以从 coredump 确认加载依赖库时子进程在 ld-linux.so 中死了。我还注意到,有时 read 系统调用会返回负值而不是 -1。
  • @gzh 啊-核心转储的乐趣...您在哪个平台上运行? x86 还是别的什么?有没有可能是你的内核代码弄乱了进程的内存?
  • 我用的是ARM平台。子进程中只有一个线程。我能想象到的唯一可能导致 SIGSEGV 是内核中的驱动程序模块,或者在运行 fork() 时被继承的文件描述符损坏的内存。
猜你喜欢
  • 1970-01-01
  • 2013-01-10
  • 1970-01-01
  • 2016-01-21
  • 1970-01-01
  • 1970-01-01
  • 2017-07-07
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多