提问人:Elias Näslund 提问时间:10/19/2023 最后编辑:Elias Näslund 更新时间:10/25/2023 访问量:47
写入 NFS 的线程会干扰其他线程
Thread writing to NFS interfere with other threads
问:
在一个嵌入式应用程序中,我正在运行一个带有 RT 补丁的基于 Yocto 的 linux 发行版。 一个线程将数据写入 NFS 文件系统上的文件,另一个线程每秒运行一次带有 popen 的 chronyc 跟踪。
问题在于,如果两个线程都在同一应用程序中运行,则 chronyc 跟踪不会在 100 毫秒内返回。 但是,如果每个线程都在自己的进程中运行,则它会按预期工作。正如我所了解的linux CFS(完全公平的调度程序),线程是否属于同一进程并不重要。
我希望在同一进程中执行两个线程应该与在自己的进程中执行每个线程相同。
- 我尝试过没有具有相同行为的 RT 补丁。
- 我尝试了 4.x、5.x 和 6.x 内核,结果相同。
- 我尝试写入 CIFS 而不是 NFS,结果相同。
- 写入 SD 卡时,它按预期工作。
- 写入 sshfs 文件系统的工作按预期进行。
- 我已经测试将 RT prio 70(chrt 70 超时 0.1 chronyc 跟踪)设置为超时和 chronyc 跟踪,没有改进。
有没有人知道这里发生了什么? 是否有一些内核参数需要更改? 该过程有一些限制?
有什么好方法可以调试这个问题吗?
重现问题的代码:
#include <iostream>
#include <fstream>
#include <thread>
bool done{false};
std::string path;
bool command(const char * cmd)
{
std::array<char, 128> buffer;
std::string result;
auto pipe = popen(cmd, "r");
if (!pipe)
{
std::cout << "Failed to open pipe\n";
return false;
}
while (fgets(buffer.data(), 128, pipe) != nullptr)
result += buffer.data();
return pclose(pipe) == EXIT_SUCCESS;
}
void tracking()
{
while (!done)
{
auto res = command("timeout 0.1 chronyc tracking");
if (!res)
std::cout << "Timeout waiting for chrony tracking\n";
else
std::cout << "Got chrony tracking\n";
std::this_thread::sleep_for(std::chrono::seconds(1));
}
}
void write_file()
{
while (!done)
{
// Open file, do some writing, close file
std::ofstream file;
file.open(path);
for (int i = 0; i < 100000; i++)
{
file << "lmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvbcdefghjklmnopqrstuvabcdefghjklmnopqrstuvlmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvbcdefghjklmnopqrstuvabcdefghjklmnopqrstuv";
}
file.close();
}
}
int main(int argc, char **argv)
{
if (argc != 3)
{
std::cout << argv[0] << " <both/chrony/file> /path/to/file\n";
return 1;
}
std::string cmd = argv[1];
path = argv[2];
if (cmd == "both")
{
std::jthread t1(tracking);
std::jthread t2(write_file);
std::this_thread::sleep_for(std::chrono::minutes(2));
done = true;
}
else if (cmd == "chrony")
{
std::jthread t1(tracking);
std::this_thread::sleep_for(std::chrono::minutes(2));
done = true;
}
else if (cmd == "file")
{
std::jthread t2(write_file);
std::this_thread::sleep_for(std::chrono::minutes(2));
done = true;
}
else
{
return 1;
}
return 0;
}
更新: 使用 perf,我收集了一些带有跟踪的调度信息。
chronyc 在一个线程中,NFS 在另一个线程中写入:
chronyc 4123/4123 [001] 2057.379201: 1 sched:sched_switch: prev_comm=chronyc prev_pid=4123 prev_prio=120 prev_state=R+ ==> next_comm=rcu_preempt next_pid=15 next_prio=98
c0b5783c __schedule ([kernel.kallsyms])
c0b5783c __schedule ([kernel.kallsyms])
c0b57fb8 preempt_schedule_irq ([kernel.kallsyms])
c0100c0c svc_preempt ([kernel.kallsyms])
c04163f0 nfs_page_clear_headlock ([kernel.kallsyms])
c04167e0 __nfs_pageio_add_request ([kernel.kallsyms])
c0417300 nfs_pageio_add_request ([kernel.kallsyms])
c041b664 nfs_page_async_flush ([kernel.kallsyms])
c041b9d0 nfs_writepages_callback ([kernel.kallsyms])
c026b88c write_cache_pages ([kernel.kallsyms])
c041bb28 nfs_writepages ([kernel.kallsyms])
c026de78 do_writepages ([kernel.kallsyms])
c025fd8c filemap_fdatawrite_wbc ([kernel.kallsyms])
c02604bc filemap_write_and_wait_range ([kernel.kallsyms])
c041c484 nfs_wb_all ([kernel.kallsyms])
c040c9b4 nfs_file_flush ([kernel.kallsyms])
c02e08b4 filp_close ([kernel.kallsyms])
c0309014 put_files_struct ([kernel.kallsyms])
c012b2f8 do_exit ([kernel.kallsyms])
c012bb1c do_group_exit ([kernel.kallsyms])
c012bb80 __wake_up_parent ([kernel.kallsyms])
chronyc 在一个线程中,CIFS 在另一个线程中写入:
chronyc 9269/9269 [001] 6244.795802: 1 sched:sched_switch: prev_comm=chronyc prev_pid=9269 prev_prio=120 prev_state=R+ ==> next_comm=rcuc/1 next_pid=24 next_prio=98
c0b5783c __schedule ([kernel.kallsyms])
c0b5783c __schedule ([kernel.kallsyms])
c0b57fb8 preempt_schedule_irq ([kernel.kallsyms])
c0100c0c svc_preempt ([kernel.kallsyms])
c07a87e8 macb_start_xmit ([kernel.kallsyms])
c098dc04 dev_hard_start_xmit ([kernel.kallsyms])
c09d43bc sch_direct_xmit ([kernel.kallsyms])
c098e28c __dev_queue_xmit ([kernel.kallsyms])
c0a0ae9c ip_finish_output2 ([kernel.kallsyms])
c0a0c890 __ip_queue_xmit ([kernel.kallsyms])
c0a2da94 __tcp_transmit_skb ([kernel.kallsyms])
c0a2f160 tcp_write_xmit ([kernel.kallsyms])
c0a2ff80 __tcp_push_pending_frames ([kernel.kallsyms])
c0a1b24c tcp_sendmsg_locked ([kernel.kallsyms])
c0a1ba90 tcp_sendmsg ([kernel.kallsyms])
c0964540 sock_sendmsg ([kernel.kallsyms])
c048eb10 smb_send_kvec ([kernel.kallsyms])
c04901f8 __smb_send_rqst ([kernel.kallsyms])
c0490a4c cifs_call_async ([kernel.kallsyms])
c04b85dc smb2_async_writev ([kernel.kallsyms])
c047ef1c cifs_writepages ([kernel.kallsyms])
c026de78 do_writepages ([kernel.kallsyms])
c025fd8c filemap_fdatawrite_wbc ([kernel.kallsyms])
c02604bc filemap_write_and_wait_range ([kernel.kallsyms])
c04814b0 cifs_flush ([kernel.kallsyms])
c02e08b4 filp_close ([kernel.kallsyms])
c0309014 put_files_struct ([kernel.kallsyms])
c012b2f8 do_exit ([kernel.kallsyms])
c012bb1c do_group_exit ([kernel.kallsyms])
c012bb80 __wake_up_parent ([kernel.kallsyms])
对于 CIFS,它只是失败的 chronyc 跟踪之一。执行需要很长时间,但在那之后,其余的都在 100 毫秒的限制范围内设置,超时并且跟踪中没有 cif 内容。
答:
事实证明,退出 i 线程会刷新其他线程上未完成的写入请求。
为了解决这个问题,可以在创建线程时丢掉CLONE_FILES。这意味着文件描述符不会在线程之间共享。据我所知,对于 std::threads 或 pthreads 没有简单的方法可以做到这一点,但可以实现您自己的函数来创建线程而无需设置CLONE_FILES。
供参考:https://www.spinics.net/lists/linux-nfs/msg99933.html https://man7.org/linux/man-pages/man2/clone.2.html
评论
chronyc tracking