Linux soft lockup分析【转】

转自:https://www.shuzhiduo.com/A/n2d9gj3oJD/

关键词:watchdog、soft lockup、percpu thread、lockdep等。

近日遇到一个soft lockup问题,打印类似“[ 56.032356] NMI watchdog: BUG: soft lockup - CPU#0 stuck for 23s! [cat:153]“。

这是lockup检测机制在起作用,lockup检测机制包括soft lockup detector和hard lockup detector。

借机分析下soft lockup机制以及什么情况下导致soft watchdog异常、对watchdog的配置、如何定位异常点。

这里跳过hard lockup detector的分析。

1. soft lockup机制分析

lockup_detector_init()函数首先获取sample_period以及watchdog_cpumask,然后根据情况创建线程,启动喂狗程序;创建hrtimer启动看门狗。

然后有两个重点一个是创建内核线程的API以及struct smp_hotplug_thread结构体。

 
  1. void __init lockup_detector_init(void)
  2. {
  3. set_sample_period();----------------------------------------获取变量sample_period,为watchdog_thresh*2/5,即4秒喂一次狗。
  4. ...
  5. cpumask_copy(&watchdog_cpumask, cpu_possible_mask);
  6.  
  7. if (watchdog_enabled)
  8. watchdog_enable_all_cpus();
  9. }
  10.  
  11. static int watchdog_enable_all_cpus(void)
  12. {
  13. int err = ;
  14.  
  15. if (!watchdog_running) {----------------------------------如果当前watchdog_running没有再运行,那么为每个CPU创建一个watchdog/x线程,这些线程每隔sample_period时间喂一次狗。watchdog_threadswatchdog/x线程的主要输入参数,watchdog_cpumask规定了为哪些CPU创建线程。
  16. err = smpboot_register_percpu_thread_cpumask(&watchdog_threads,
  17. &watchdog_cpumask);
  18. if (err)
  19. pr_err("Failed to create watchdog threads, disabled\n");
  20. else
  21. watchdog_running = ;
  22. } else {
  23. err =update_watchdog_all_cpus();
  24.  
  25. if (err) {
  26. watchdog_disable_all_cpus();
  27. pr_err("Failed to update lockup detectors, disabled\n");
  28. }
  29. }
  30.  
  31. if (err)
  32. watchdog_enabled = ;
  33.  
  34. return err;
  35. }
  36.  
  37. static void watchdog_disable_all_cpus(void)
  38. {
  39. if (watchdog_running) {
  40. watchdog_running = ;
  41. smpboot_unregister_percpu_thread(&watchdog_threads);
  42. }
  43. }
  44.  
  45. static int update_watchdog_all_cpus(void)
  46. {
  47. int ret;
  48.  
  49. ret =watchdog_park_threads();
  50. if (ret)
  51. return ret;
  52.  
  53. watchdog_unpark_threads();
  54.  
  55. return ;
  56. }
  57.  
  58. static int watchdog_park_threads(void)
  59. {
  60. int cpu, ret = ;
  61.  
  62. atomic_set(&watchdog_park_in_progress, );
  63.  
  64. for_each_watchdog_cpu(cpu) {
  65. ret = kthread_park(per_cpu(softlockup_watchdog, cpu));---------------------------设置struct kthread->flagsKTHREAD_SHOULD_PARK位,在watchdog/x线程中会调用unpark成员函数进行处理。
  66. if (ret)
  67. break;
  68. }
  69.  
  70. atomic_set(&watchdog_park_in_progress, );
  71.  
  72. return ret;
  73. }
  74.  
  75. static void watchdog_unpark_threads(void)
  76. {
  77. int cpu;
  78.  
  79. for_each_watchdog_cpu(cpu)
  80. kthread_unpark(per_cpu(softlockup_watchdog, cpu));-------------------------------清空struct kthread->flagsKTHREAD_SHOULD_PARK位,在watchdog/x线程中会调用park成员函数。
  81. }
 
 

1.1 watchdog_threads结构体介绍

在介绍如何创建watchdog/x线程之前,有必要先介绍一些struct smp_hotplug_thread线程。

  1. struct smp_hotplug_thread {
  2. struct task_struct __percpu **store;--------------------------存放percpu strcut task_strcut指针的指针。
  3. struct list_head list;
  4. int (*thread_should_run)(unsigned int cpu);-------检查是否应该运行watchdog/x线程。
  5. void (*thread_fn)(unsigned int cpu);--------------watchdog/x线程的主函数。
  6. void (*create)(unsigned int cpu);
  7. void (*setup)(unsigned int cpu);------------------在运行watchdog/x线程之前的准备工作。
  8. void (*cleanup)(unsigned int cpu, bool online);---在退出watchdog/x线程之后的清楚工作。
  9. void (*park)(unsigned int cpu);-------------------当CPU offline时,需要临时停止。
  10. void (*unpark)(unsigned int cpu);-----------------当CPU变成online时,进行准备工作。
  11. cpumask_var_t cpumask;--------------------------------允许哪些CPU online
  12. bool selfparking;
  13. const char *thread_comm;------------------------------watchdog/x线程名称。
  14. };

watchdog_threads是soft lockup监控线程的实体,基于此创建 watchdog/x线程。

  1. static struct smp_hotplug_thread watchdog_threads = {
  2. .store = &softlockup_watchdog,
  3. .thread_should_run =watchdog_should_run,
  4. .thread_fn =watchdog,
  5. .thread_comm = "watchdog/%u",
  6. .setup =watchdog_enable,
  7. .cleanup =watchdog_cleanup,
  8. .park =watchdog_disable,
  9. .unpark =watchdog_enable,
  10. };
  11.  
  12. static void watchdog_enable(unsigned int cpu)
  13. {
  14. struct hrtimer *hrtimer = raw_cpu_ptr(&watchdog_hrtimer);
  15.  
  16. /* kick off the timer for the hardlockup detector */
  17. hrtimer_init(hrtimer, CLOCK_MONOTONIC, HRTIMER_MODE_REL);
  18. hrtimer->function =watchdog_timer_fn;------------------------------------------创建一个hrtimer,超时函数为watchdog_timer_fn,这里面会检查watchdog_touch_ts变量是否超过20秒没有被更新。如果是,则有soft lockup
  19.  
  20. /* Enable the perf event */
  21. watchdog_nmi_enable(cpu);
  22.  
  23. /* done here because hrtimer_start can only pin to smp_processor_id() */
  24. hrtimer_start(hrtimer, ns_to_ktime(sample_period),
  25. HRTIMER_MODE_REL_PINNED);---------------------------------------------启动一个超时为sample_period(4秒)的hrtimerHRTIMER_MODE_REL_PINNED表示此hrtimer和当前CPU绑定。
  26.  
  27. /* initialize timestamp */watchdog_set_prio(SCHED_FIFO, MAX_RT_PRIO - );---------------------------------设置当前线程为实时FIFO,并且优先级为实时99.这个优先级表示高于所有的非实时线程,但是实时优先级最低的。
  28. __touch_watchdog();-------------------------------------------------------------更新watchdog_touch_ts变量,相当于喂狗操作。
  29. }
  30.  
  31. static void watchdog_set_prio(unsigned int policy, unsigned int prio)
  32. {
  33. struct sched_param param = { .sched_priority = prio };
  34.  
  35. sched_setscheduler(current, policy, &param);
  36. }
  37.  
  38. /* Commands for resetting the watchdog */
  39. static void __touch_watchdog(void)
  40. {
  41. __this_cpu_write(watchdog_touch_ts, get_timestamp());----------------------------喂狗的操作就是更新watchdog_touch_ts变量,也即当前时间戳。
  42. }
  43.  
  44. static void watchdog_disable(unsigned int cpu)-------------------------------------相当于watchdog_enable()反操作,将线程恢复为普通线程;取消hrtimer
  45. {
  46. struct hrtimer *hrtimer = raw_cpu_ptr(&watchdog_hrtimer);
  47.  
  48. watchdog_set_prio(SCHED_NORMAL, );
  49. hrtimer_cancel(hrtimer);
  50. /* disable the perf event */
  51. watchdog_nmi_disable(cpu);
  52. }
  53.  
  54. static void watchdog_cleanup(unsigned int cpu, bool online)
  55. {
  56. watchdog_disable(cpu);
  57. }
  58.  
  59. static int watchdog_should_run(unsigned int cpu)
  60. {
  61. return __this_cpu_read(hrtimer_interrupts) !=
  62. __this_cpu_read(soft_lockup_hrtimer_cnt);------------------------------------hrtimer_interrupts记录了产生hrtimer的次数;在watchdog()中,将hrtimer_interrupts赋给soft_lockup_hrtimer_cnt。两者相等表示没有hrtimer产生,不需要运行watchdog/x线程;相反不等,则需要watchdog/x线程运行。
  63. }
  64. static void watchdog(unsigned int cpu)
  65. {
  66. __this_cpu_write(soft_lockup_hrtimer_cnt,
  67. __this_cpu_read(hrtimer_interrupts));-----------------------------------更新soft_lockup_hrtimer_cnt,在watch_should_run()中就返回false,表示线程不需要运行,即不需要喂狗。
  68. __touch_watchdog();--------------------------------------------------------------虽然就是一句话,但是却很重要的喂狗操作。
  69.  
  70. if (!(watchdog_enabled & NMI_WATCHDOG_ENABLED))
  71. watchdog_nmi_disable(cpu);
  72. }

1.2 创建喂狗线程watchdog/x

在分析了watchdog_threads之后,再来看看如何创建watchdog/x线程。

  1. int smpboot_register_percpu_thread_cpumask(struct smp_hotplug_thread *plug_thread,
  2. const struct cpumask *cpumask)
  3. {
  4. unsigned int cpu;
  5. int ret = ;
  6.  
  7. if (!alloc_cpumask_var(&plug_thread->cpumask, GFP_KERNEL))
  8. return -ENOMEM;
  9. cpumask_copy(plug_thread->cpumask, cpumask);
  10.  
  11. get_online_cpus();
  12. mutex_lock(&smpboot_threads_lock);
  13. for_each_online_cpu(cpu) {------------------------------------------------遍历所有online CPU,为每个CPU创建一个percpuwatchdog/x线程。
  14. ret =__smpboot_create_thread(plug_thread, cpu);
  15. if (ret) {
  16. smpboot_destroy_threads(plug_thread);-----------------------------创建失败则释放相关资源。
  17. free_cpumask_var(plug_thread->cpumask);
  18. goto out;
  19. }
  20. if (cpumask_test_cpu(cpu, cpumask))
  21. smpboot_unpark_thread(plug_thread, cpu);--------------------------如果当前CPU不在cpumask中,则清空KTHREAD_SHOULD_PARK,进而调用watchdog_theradsumpark成员函数。
  22. }
  23. list_add(&plug_thread->list, &hotplug_threads);
  24. out:
  25. mutex_unlock(&smpboot_threads_lock);
  26. put_online_cpus();
  27. return ret;
  28. }
  29.  
  30. static int
  31. __smpboot_create_thread(struct smp_hotplug_thread *ht, unsigned int cpu)
  32. {
  33. struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
  34. struct smpboot_thread_data *td;
  35.  
  36. if (tsk)
  37. return ;
  38.  
  39. td = kzalloc_node(sizeof(*td), GFP_KERNEL, cpu_to_node(cpu));
  40. if (!td)
  41. return -ENOMEM;
  42. td->cpu = cpu;
  43. td->ht = ht;
  44.  
  45. tsk =kthread_create_on_cpu(smpboot_thread_fn, td, cpu,
  46. ht->thread_comm);-----------------------------------------在指定CPU上创建watchdog/x线程,处理函数为smpboot_thread_fn()。
  47. if (IS_ERR(tsk)) {
  48. kfree(td);
  49. return PTR_ERR(tsk);
  50. }
  51. /*
  52. * Park the thread so that it could start right on the CPU
  53. * when it is available.
  54. */
  55. kthread_park(tsk);--------------------------------------------------------在CPU上立即启动watchdog/x线程。
  56. get_task_struct(tsk);-----------------------------------------------------增加对线程的引用计数。
  57. *per_cpu_ptr(ht->store, cpu) = tsk;---------------------------------------store存放线程结构体指针的指针。
  58. if (ht->create) {
  59. if (!wait_task_inactive(tsk, TASK_PARKED))
  60. WARN_ON();
  61. else
  62. ht->create(cpu);
  63. }
  64. return ;
  65. }
  66.  
  67. static int smpboot_thread_fn(void *data)
  68. {
  69. struct smpboot_thread_data *td = data;
  70. struct smp_hotplug_thread *ht = td->ht;
  71.  
  72. while () {
  73. set_current_state(TASK_INTERRUPTIBLE);
  74. preempt_disable();
  75. if (kthread_should_stop()) {----------------------------------------如果可以终止线程,调用cleanup,退出线程。
  76. __set_current_state(TASK_RUNNING);
  77. preempt_enable();
  78. /* cleanup must mirror setup */
  79. if (ht->cleanup && td->status != HP_THREAD_NONE)
  80. ht->cleanup(td->cpu, cpu_online(td->cpu));
  81. kfree(td);
  82. return ;
  83. }
  84.  
  85. if (kthread_should_park()) {----------------------------------------如果KTHREAD_SHOULD_PARK置位,调用park()暂停进程执行。
  86. __set_current_state(TASK_RUNNING);
  87. preempt_enable();
  88. if (ht->park && td->status == HP_THREAD_ACTIVE) {
  89. BUG_ON(td->cpu != smp_processor_id());
  90. ht->park(td->cpu);
  91. td->status = HP_THREAD_PARKED;
  92. }
  93. kthread_parkme();
  94. /* We might have been woken for stop */
  95. continue;
  96. }
  97.  
  98. BUG_ON(td->cpu != smp_processor_id());
  99.  
  100. /* Check for state change setup */
  101. switch (td->status) {
  102. case HP_THREAD_NONE:-----------------------------------------------相当于第一次运行,调用setup()进行初始化操作。
  103. __set_current_state(TASK_RUNNING);
  104. preempt_enable();
  105. if (ht->setup)
  106. ht->setup(td->cpu);
  107. td->status = HP_THREAD_ACTIVE;
  108. continue;
  109.  
  110. case HP_THREAD_PARKED:---------------------------------------------从parked状态恢复。
  111. __set_current_state(TASK_RUNNING);
  112. preempt_enable();
  113. if (ht->unpark)
  114. ht->unpark(td->cpu);
  115. td->status = HP_THREAD_ACTIVE;
  116. continue;
  117. }
  118.  
  119. if (!ht->thread_should_run(td->cpu)) {-----------------------------如果不需要进程运行,schedule()主动放弃CPU给其他线程使用。
  120. preempt_enable_no_resched();
  121. schedule();
  122. } else {
  123. __set_current_state(TASK_RUNNING);
  124. preempt_enable();
  125. ht->thread_fn(td->cpu);----------------------------------------调用struct smpboot_thread_fn->thread_fnwatchdog(),进行喂狗操作。
  126. }
  127. }
  128. }
  129.  
  130. void smpboot_unregister_percpu_thread(struct smp_hotplug_thread *plug_thread)----将创建的内核线程移除操作。
  131. {
  132. get_online_cpus();
  133. mutex_lock(&smpboot_threads_lock);
  134. list_del(&plug_thread->list);
  135. smpboot_destroy_threads(plug_thread);
  136. mutex_unlock(&smpboot_threads_lock);
  137. put_online_cpus();
  138. free_cpumask_var(plug_thread->cpumask);
  139. }
  140.  
  141. static void smpboot_destroy_threads(struct smp_hotplug_thread *ht)
  142. {
  143. unsigned int cpu;
  144.  
  145. /* We need to destroy also the parked threads of offline cpus */
  146. for_each_possible_cpu(cpu) {
  147. struct task_struct *tsk = *per_cpu_ptr(ht->store, cpu);
  148.  
  149. if (tsk) {
  150. kthread_stop(tsk);
  151. put_task_struct(tsk);
  152. *per_cpu_ptr(ht->store, cpu) = NULL;
  153. }
  154. }
  155. }

1.3 hrtimer看门狗

在分析了喂狗线程watchdog/x之后,再来分析看门狗是如何实现的?

看门狗是通过启动一个周期为4秒的hrtimer来实现的,这个hrtimer和CPU绑定,使用的变量都是percpu的。确保每个CPU之间不相互干扰。

每次hrtimer超时,都会唤醒watchdog/x线程,并进行一次喂狗操作。

因为hrtimer超时函数在软中断中调用,在中断产生后会比线程优先得到执行。

所以在watchdog/x线程没有得到执行的情况下,通过is_softlockup()来判断看门狗是否超过20秒没有得到喂狗。

  1. static enum hrtimer_restart watchdog_timer_fn(struct hrtimer *hrtimer)
  2. {
  3. unsigned long touch_ts = __this_cpu_read(watchdog_touch_ts);
  4. struct pt_regs *regs = get_irq_regs();
  5. int duration;
  6. int softlockup_all_cpu_backtrace = sysctl_softlockup_all_cpu_backtrace;
  7.  
  8. if (atomic_read(&watchdog_park_in_progress) != )
  9. return HRTIMER_NORESTART;
  10.  
  11. /* kick the hardlockup detector */watchdog_interrupt_count();------------------------------------------------------------------没产生一次中断,hrtimer_interrupts计数加1.hrtimer_interrupts记录了产生hrtimer的次数。
  12.  
  13. /* kick the softlockup detector */
  14. wake_up_process(__this_cpu_read(softlockup_watchdog));---------------------------------------唤醒watchdog/x线程,进行喂狗操作。
  15.  
  16. /* .. and repeat */
  17. hrtimer_forward_now(hrtimer, ns_to_ktime(sample_period));------------------------------------重新设置超时点,形成周期性时钟。
  18. ...
  19. duration =is_softlockup(touch_ts);----------------------------------------------------------返回非0表示,看门狗超时。
  20. if (unlikely(duration)) {--------------------------------------------------------------------看门狗超时情况的处理。
  21. if (kvm_check_and_clear_guest_paused())
  22. return HRTIMER_RESTART;
  23.  
  24. /* only warn once */
  25. if (__this_cpu_read(soft_watchdog_warn) == true) {
  26. if (__this_cpu_read(softlockup_task_ptr_saved) !=
  27. current) {
  28. __this_cpu_write(soft_watchdog_warn, false);
  29. __touch_watchdog();
  30. }
  31. return HRTIMER_RESTART;
  32. }
  33.  
  34. if (softlockup_all_cpu_backtrace) {
  35. if (test_and_set_bit(, &soft_lockup_nmi_warn)) {
  36. /* Someone else will report us. Let's give up */
  37. __this_cpu_write(soft_watchdog_warn, true);
  38. return HRTIMER_RESTART;
  39. }
  40. }
  41.  
  42. pr_emerg("BUG: soft lockup - CPU#%d stuck for %us! [%s:%d]\n",
  43. smp_processor_id(), duration,
  44. current->comm, task_pid_nr(current));-------------------------------------------------打印哪个CPU被卡死duration秒,以及死在哪个进程。
  45. __this_cpu_write(softlockup_task_ptr_saved, current);
  46. print_modules();
  47. print_irqtrace_events(current);-----------------------------------------------------------显示开关中断、软中断信息,禁止中断和软中断也是造成soft lockup的一个原因。
  48. if (regs)---------------------------------------------------------------------------------有寄存器显示寄存器信息,同时显示栈信息。
  49. show_regs(regs);
  50. else
  51. dump_stack();
  52.  
  53. if (softlockup_all_cpu_backtrace) {
  54. trigger_allbutself_cpu_backtrace();
  55.  
  56. clear_bit(, &soft_lockup_nmi_warn);
  57. /* Barrier to sync with other cpus */
  58. smp_mb__after_atomic();
  59. }
  60.  
  61. add_taint(TAINT_SOFTLOCKUP, LOCKDEP_STILL_OK);
  62. if (softlockup_panic)---------------------------------------------------------------------如果定义softlockup_panic则进入panic()。
  63. panic("softlockup: hung tasks");
  64. __this_cpu_write(soft_watchdog_warn, true);
  65. } else
  66. __this_cpu_write(soft_watchdog_warn, false);
  67.  
  68. return HRTIMER_RESTART;
  69. }

static void watchdog_interrupt_count(void)
  {
      __this_cpu_inc(hrtimer_interrupts);
  }

  1. static int is_softlockup(unsigned long touch_ts)
  2. {
  3. unsigned long now = get_timestamp();
  4.  
  5. if ((watchdog_enabled & SOFT_WATCHDOG_ENABLED) && watchdog_thresh){
  6. /* Warn about unreasonable delays. */
  7. if (time_after(now, touch_ts + get_softlockup_thresh()))
  8. return now - touch_ts;
  9. }
  10. return ;
  11. }

2. 对watchdog的设置

对watchdog行为的设置有两个途径:通过命令行传入参数和通过proc设置。

2.1 通过命令行设置

通过命令行传入参数,可以对soft lockup进行开关设置、超时过后是否panic等等行为。

  1. static int __init softlockup_panic_setup(char *str)
  2. {
  3. softlockup_panic = simple_strtoul(str, NULL, );
  4.  
  5. return ;
  6. }
  7. __setup("softlockup_panic=", softlockup_panic_setup);
  8.  
  9. static int __init nowatchdog_setup(char *str)
  10. {
  11. watchdog_enabled = ;
  12. return ;
  13. }
  14. __setup("nowatchdog", nowatchdog_setup);
  15.  
  16. static int __init nosoftlockup_setup(char *str)
  17. {
  18. watchdog_enabled &= ~SOFT_WATCHDOG_ENABLED;
  19. return ;
  20. }
  21. __setup("nosoftlockup", nosoftlockup_setup);
  22.  
  23. #ifdef CONFIG_SMP
  24. static int __init softlockup_all_cpu_backtrace_setup(char *str)
  25. {
  26. sysctl_softlockup_all_cpu_backtrace =
  27. !!simple_strtol(str, NULL, );
  28. return ;
  29. }
  30. __setup("softlockup_all_cpu_backtrace=", softlockup_all_cpu_backtrace_setup);
  31. static int __init hardlockup_all_cpu_backtrace_setup(char *str)
  32. {
  33. sysctl_hardlockup_all_cpu_backtrace =
  34. !!simple_strtol(str, NULL, );
  35. return ;
  36. }
  37. __setup("hardlockup_all_cpu_backtrace=", hardlockup_all_cpu_backtrace_setup);
  38. #endif

2.2 通过sysfs节点调节watchdog

watchdog相关的配置还可以通过proc文件系统进行配置。

  1. /proc/sys/kernel/nmi_watchdog-------------------------hard lockup开关,proc_nmi_watchdog()。
  2. /proc/sys/kernel/soft_watchdog------------------------soft lockup开关,proc_soft_watchdog()。
  3. /proc/sys/kernel/watchdog-----------------------------watchdog总开关,proc_watchdog()。
  4. /proc/sys/kernel/watchdog_cpumask---------------------watchdog cpumaksproc_watchdog_cpumask()。
  5. /proc/sys/kernel/watchdog_thresh----------------------watchdog超时阈值设置,proc_watchdog_thresh()。

3. 定位soft lockup异常

引起soft lockup的原因一般是死循环或者死锁, 死循环可以通过栈回溯找到问题点;死锁问题需要打开内核的lockdep功能。

打开内核的lockdep功能可以参考《Linux死锁检测-Lockdep》。

下面看一个while(1)引起的soft lockup异常分析:

    1. [ 5656.032325] NMI watchdog: BUG: soft lockup - CPU# stuck for 22s! [cat:]-----------------------CPU、进程等信息粗略定位。
    2. [ 5656.039314] Modules linked in:
    3. [ 5656.042386]
    4. [ 5656.042386] CURRENT PROCESS:
    5. [ 5656.042386]
    6. [ 5656.048229] COMM=cat PID=
    7. [ 5656.051117] TEXT=-000c5a68 DATA=000c6f1c-000c7175 BSS=000c7175-000c8000
    8. [ 5656.058432] USER-STACK=7fc1ee50 KERNEL-STACK=bd0b7080
    9. [ 5656.058432]
    10. [ 5656.065069] PC: 0x8032a1b2 (clk_summary_show+0x62/0xb4)--------------------------------------------PC指向出问题的点,更加精确的定位。
    11. [ 5656.070302] LR: 0x8032a186 (clk_summary_show+0x36/0xb4)
    12. [ 5656.075531] SP: 0xbd8b1b74...
    13. [ 5656.217622]
    14. Call Trace:-----------------------------------------------------------------------------------------通过Call Trace,可以了解如何做到PC指向的问题点的。来龙去脉一目了然。
    15. [<80155c5e>] seq_read+0xc2/0x46c
    16. [<802826ac>] full_proxy_read+0x58/0x98
    17. [<8013239c>] do_readv_writev+0x31c/0x384
    18. [<>] vfs_readv+0x54/0x8c
    19. [<80160b52>] default_file_splice_read+0x166/0x2b0
    20. [<801606ee>] do_splice_to+0x76/0xb0
    21. [<801607de>] splice_direct_to_actor+0xb6/0x21c
    22. [<801609c2>] do_splice_direct+0x7e/0xa8
    23. [<80132a5a>] do_sendfile+0x21a/0x45c
    24. [<>] SyS_sendfile64+0xf6/0xfc
    25. [<>] csky_systemcall+0x96/0xe0
posted @ 2022-01-09 19:00  Sky&Zhang  阅读(515)  评论(0编辑  收藏  举报