You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

同进程内NFS写入线程干扰chronyc tracking的问题求助

问题背景

在带RT补丁的Yocto嵌入式Linux系统中,同一进程内存在两个线程:一个线程向NFS文件系统写入文件,另一个线程每秒通过popen执行一次chronyc tracking。异常现象为:当两个线程同属一个进程时,chronyc tracking无法在100ms内返回;但分属不同进程时则正常。按照Linux CFS(完全公平调度器)原理,线程所属进程不应影响调度结果。

已完成的测试

  • 移除RT补丁后现象一致
  • 测试4.x、5.x、6.x版本内核均出现相同问题
  • 替换为CIFS文件系统写入时现象相同
  • 写入SD卡或sshfs文件系统时工作正常
  • 为timeout和chronyc tracking设置RT优先级70无改善

复现代码

#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 << "lmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvbcdefghjklmnopqrstuvabcdefghjklmnopqrstuvlmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvabcdefghjklmnopqrstuvbcdefghjklmnopqrstuvabcdefghjklmnopqrstuv";
                }
                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调度追踪信息

同进程(NFS写入+chronyc)

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])

同进程(CIFS写入+chronyc)

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 tracking超时,后续均能在100ms内返回。

问题解答

1. 现象原因

问题核心是进程级资源锁竞争与同步IO阻塞,而非CFS调度逻辑:

  • 同进程内,写线程执行file.close()时,会触发NFS/CIFS的同步刷盘操作(如nfs_file_flush/cifs_flush),该操作会等待内核将脏页写入远程服务器,同时持有进程的files_struct(文件描述符表)相关锁。
  • pclose会等待chronyc子进程退出,父进程处理子进程退出的路径(do_group_exit/put_files_struct)需要访问同一份files_struct资源,导致锁竞争,进而让chronyc的等待逻辑被阻塞,最终触发timeout 0.1超时。
  • 分属不同进程时,资源完全隔离,写进程的刷盘操作不会影响chronyc进程的资源访问;SD卡/sshfs正常是因为前者刷盘逻辑为本地块设备,锁机制不同,后者为用户态文件系统,同步阻塞逻辑不涉及内核进程级锁。

2. 可调整的内核参数

  • NFS相关:
    • echo 0 > /sys/module/nfs/parameters/wbt_enable:关闭写回节流,减少同步刷盘等待时长
    • echo 1 > /sys/module/nfs/parameters/nfs4_disable_idmapping:简化身份映射,降低操作开销
    • 调整vm.dirty_writeback_centisecs为更小值(如100,即1秒),加快脏页回写
  • CIFS相关:
    • echo 5 > /sys/module/cifs/parameters/ops_timeout:缩短CIFS操作超时时间
    • echo 128 > /sys/module/cifs/parameters/max_credits:增加并发操作数,缓解单操作阻塞
  • 调度相关:
    • echo 1 > /proc/sys/kernel/sched_child_runs_first:让子进程优先调度,减少chronyc启动延迟

3. 进程层面的限制

存在两类进程级限制:

  • 共享资源锁竞争:进程的files_struct是所有线程共享的核心资源,写线程的刷盘操作会持有该资源的锁,导致同一进程内处理pclose的线程被阻塞。
  • 进程上下文同步阻塞:NFS/CIFS的同步刷盘操作是在进程上下文内完成的同步等待,会占用进程的调度时间片,导致同一进程内的其他线程无法及时获得CPU时间,间接延迟chronyc的执行。

4. 有效的调试方法

  • 系统调用追踪:用perf trace -p <pid>监控目标进程的popen/pclose/close系统调用耗时,定位阻塞点。
  • 内核锁追踪:用ftrace开启lock_acquire/lock_release事件,查看files_struct相关锁的持有、等待时长。
  • 线程状态监控:用pidstat -t -d 1 <pid>实时查看进程内各线程的CPU占用、IO等待时间,对比同进程/不同进程的差异。
  • 手动替换popen:用fork+exec手动实现子进程创建,排除popen封装逻辑的影响,直接监控chronyc的调度延迟。
  • 内核代码调试:在nfs_file_flush、cifs_flush、put_files_struct等关键函数中添加打印,追踪锁的持有周期。

内容的提问来源于stack exchange,提问作者Elias Nässlund

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.08 02:29:59