Message ID | 20220622225102.2112026-8-joel@joelfernandes.org (mailing list archive) |
---|---|
State | Superseded |
Headers | show |
Series | Implement call_rcu_lazy() and miscellaneous fixes | expand |
Hi "Joel, Thank you for the patch! Yet something to improve: [auto build test ERROR on linus/master] [also build test ERROR on v5.19-rc3 next-20220622] [cannot apply to paulmck-rcu/dev] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch] url: https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269 config: hexagon-randconfig-r045-20220622 (https://download.01.org/0day-ci/archive/20220623/202206230936.goRWmVwu-lkp@intel.com/config) compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 46be5faaf03466c3751f8a2882bef5a217e15926) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=hexagon SHELL=/bin/bash kernel/rcu/ If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> All error/warnings (new ones prefixed by >>): >> kernel/rcu/rcuscale.c:663:6: warning: no previous prototype for function 'kfree_rcu_lazy' [-Wmissing-prototypes] void kfree_rcu_lazy(struct rcu_head *rh) ^ kernel/rcu/rcuscale.c:663:1: note: declare 'static' if the function is not intended to be used outside of this translation unit void kfree_rcu_lazy(struct rcu_head *rh) ^ static >> kernel/rcu/rcuscale.c:789:6: warning: no previous prototype for function 'call_rcu_lazy_test1' [-Wmissing-prototypes] void call_rcu_lazy_test1(struct rcu_head *rh) ^ kernel/rcu/rcuscale.c:789:1: note: declare 'static' if the function is not intended to be used outside of this translation unit void call_rcu_lazy_test1(struct rcu_head *rh) ^ static >> kernel/rcu/rcuscale.c:810:14: error: call to undeclared function 'rcu_scale_get_jiffies_till_flush'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] orig_jif = rcu_scale_get_jiffies_till_flush(); ^ >> kernel/rcu/rcuscale.c:813:3: error: call to undeclared function 'rcu_scale_set_jiffies_till_flush'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration] rcu_scale_set_jiffies_till_flush(2 * HZ); ^ 2 warnings and 2 errors generated. vim +/rcu_scale_get_jiffies_till_flush +810 kernel/rcu/rcuscale.c 661 662 /* Used if doing RCU-kfree'ing via call_rcu_lazy(). */ > 663 void kfree_rcu_lazy(struct rcu_head *rh) 664 { 665 struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh); 666 kfree(obj); 667 } 668 669 static int 670 kfree_scale_thread(void *arg) 671 { 672 int i, loop = 0; 673 long me = (long)arg; 674 struct kfree_obj *alloc_ptr; 675 u64 start_time, end_time; 676 long long mem_begin, mem_during = 0; 677 bool kfree_rcu_test_both; 678 DEFINE_TORTURE_RANDOM(tr); 679 680 VERBOSE_SCALEOUT_STRING("kfree_scale_thread task started"); 681 set_cpus_allowed_ptr(current, cpumask_of(me % nr_cpu_ids)); 682 set_user_nice(current, MAX_NICE); 683 kfree_rcu_test_both = (kfree_rcu_test_single == kfree_rcu_test_double); 684 685 start_time = ktime_get_mono_fast_ns(); 686 687 if (atomic_inc_return(&n_kfree_scale_thread_started) >= kfree_nrealthreads) { 688 if (gp_exp) 689 b_rcu_gp_test_started = cur_ops->exp_completed() / 2; 690 else 691 b_rcu_gp_test_started = cur_ops->get_gp_seq(); 692 } 693 694 do { 695 if (!mem_during) { 696 mem_during = mem_begin = si_mem_available(); 697 } else if (loop % (kfree_loops / 4) == 0) { 698 mem_during = (mem_during + si_mem_available()) / 2; 699 } 700 701 for (i = 0; i < kfree_alloc_num; i++) { 702 alloc_ptr = kmalloc(kfree_mult * sizeof(struct kfree_obj), GFP_KERNEL); 703 if (!alloc_ptr) 704 return -ENOMEM; 705 706 if (kfree_rcu_by_lazy) { 707 call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy); 708 continue; 709 } 710 711 // By default kfree_rcu_test_single and kfree_rcu_test_double are 712 // initialized to false. If both have the same value (false or true) 713 // both are randomly tested, otherwise only the one with value true 714 // is tested. 715 if ((kfree_rcu_test_single && !kfree_rcu_test_double) || 716 (kfree_rcu_test_both && torture_random(&tr) & 0x800)) 717 kfree_rcu(alloc_ptr); 718 else 719 kfree_rcu(alloc_ptr, rh); 720 } 721 722 cond_resched(); 723 } while (!torture_must_stop() && ++loop < kfree_loops); 724 725 if (atomic_inc_return(&n_kfree_scale_thread_ended) >= kfree_nrealthreads) { 726 end_time = ktime_get_mono_fast_ns(); 727 728 if (gp_exp) 729 b_rcu_gp_test_finished = cur_ops->exp_completed() / 2; 730 else 731 b_rcu_gp_test_finished = cur_ops->get_gp_seq(); 732 733 pr_alert("Total time taken by all kfree'ers: %llu ns, loops: %d, batches: %ld, memory footprint: %lldMB\n", 734 (unsigned long long)(end_time - start_time), kfree_loops, 735 rcuscale_seq_diff(b_rcu_gp_test_finished, b_rcu_gp_test_started), 736 (mem_begin - mem_during) >> (20 - PAGE_SHIFT)); 737 738 if (shutdown) { 739 smp_mb(); /* Assign before wake. */ 740 wake_up(&shutdown_wq); 741 } 742 } 743 744 torture_kthread_stopping("kfree_scale_thread"); 745 return 0; 746 } 747 748 static void 749 kfree_scale_cleanup(void) 750 { 751 int i; 752 753 if (kfree_rcu_by_lazy) 754 rcu_force_call_rcu_to_lazy(false); 755 756 if (torture_cleanup_begin()) 757 return; 758 759 if (kfree_reader_tasks) { 760 for (i = 0; i < kfree_nrealthreads; i++) 761 torture_stop_kthread(kfree_scale_thread, 762 kfree_reader_tasks[i]); 763 kfree(kfree_reader_tasks); 764 } 765 766 torture_cleanup_end(); 767 } 768 769 /* 770 * shutdown kthread. Just waits to be awakened, then shuts down system. 771 */ 772 static int 773 kfree_scale_shutdown(void *arg) 774 { 775 wait_event(shutdown_wq, 776 atomic_read(&n_kfree_scale_thread_ended) >= kfree_nrealthreads); 777 778 smp_mb(); /* Wake before output. */ 779 780 kfree_scale_cleanup(); 781 kernel_power_off(); 782 return -EINVAL; 783 } 784 785 // Used if doing RCU-kfree'ing via call_rcu_lazy(). 786 unsigned long jiffies_at_lazy_cb; 787 struct rcu_head lazy_test1_rh; 788 int rcu_lazy_test1_cb_called; > 789 void call_rcu_lazy_test1(struct rcu_head *rh) 790 { 791 jiffies_at_lazy_cb = jiffies; 792 WRITE_ONCE(rcu_lazy_test1_cb_called, 1); 793 } 794 795 static int __init 796 kfree_scale_init(void) 797 { 798 long i; 799 int firsterr = 0; 800 unsigned long orig_jif, jif_start; 801 802 // Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs 803 // do not remove laziness of the lazy ones (since the test tries 804 // to stress call_rcu_lazy() for OOM). 805 // 806 // Also, do a quick self-test to ensure laziness is as much as 807 // expected. 808 if (kfree_rcu_by_lazy) { 809 /* do a test to check the timeout. */ > 810 orig_jif = rcu_scale_get_jiffies_till_flush(); 811 812 rcu_force_call_rcu_to_lazy(true); > 813 rcu_scale_set_jiffies_till_flush(2 * HZ); 814 rcu_barrier(); 815 816 jif_start = jiffies; 817 jiffies_at_lazy_cb = 0; 818 call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1); 819 820 smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1); 821 822 rcu_scale_set_jiffies_till_flush(orig_jif); 823 824 if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) { 825 pr_alert("Lazy CBs are not being lazy as expected!\n"); 826 return -1; 827 } 828 829 if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) { 830 pr_alert("Lazy CBs are being too lazy!\n"); 831 return -1; 832 } 833 } 834 835 kfree_nrealthreads = compute_real(kfree_nthreads); 836 /* Start up the kthreads. */ 837 if (shutdown) { 838 init_waitqueue_head(&shutdown_wq); 839 firsterr = torture_create_kthread(kfree_scale_shutdown, NULL, 840 shutdown_task); 841 if (torture_init_error(firsterr)) 842 goto unwind; 843 schedule_timeout_uninterruptible(1); 844 } 845 846 pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n", 847 kfree_mult * sizeof(struct kfree_obj), 848 kfree_rcu_by_lazy); 849 850 kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]), 851 GFP_KERNEL); 852 if (kfree_reader_tasks == NULL) { 853 firsterr = -ENOMEM; 854 goto unwind; 855 } 856 857 for (i = 0; i < kfree_nrealthreads; i++) { 858 firsterr = torture_create_kthread(kfree_scale_thread, (void *)i, 859 kfree_reader_tasks[i]); 860 if (torture_init_error(firsterr)) 861 goto unwind; 862 } 863 864 while (atomic_read(&n_kfree_scale_thread_started) < kfree_nrealthreads) 865 schedule_timeout_uninterruptible(1); 866 867 torture_init_end(); 868 return 0; 869 870 unwind: 871 torture_init_end(); 872 kfree_scale_cleanup(); 873 return firsterr; 874 } 875
Hi "Joel, Thank you for the patch! Yet something to improve: [auto build test ERROR on linus/master] [also build test ERROR on v5.19-rc3 next-20220622] [cannot apply to paulmck-rcu/dev] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch] url: https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269 config: s390-randconfig-r044-20220622 (https://download.01.org/0day-ci/archive/20220623/202206231028.gVHd7wR5-lkp@intel.com/config) compiler: clang version 15.0.0 (https://github.com/llvm/llvm-project 46be5faaf03466c3751f8a2882bef5a217e15926) reproduce (this is a W=1 build): wget https://raw.githubusercontent.com/intel/lkp-tests/master/sbin/make.cross -O ~/bin/make.cross chmod +x ~/bin/make.cross # install s390 cross compiling tool for clang build # apt-get install binutils-s390x-linux-gnu # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a # save the config file mkdir build_dir && cp config build_dir/.config COMPILER_INSTALL_PATH=$HOME/0day COMPILER=clang make.cross W=1 O=build_dir ARCH=s390 SHELL=/bin/bash If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> All errors (new ones prefixed by >>): /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.o: in function `kfree_scale_init': >> kernel/rcu/rcuscale.c:810: undefined reference to `rcu_scale_get_jiffies_till_flush' >> /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.c:813: undefined reference to `rcu_scale_set_jiffies_till_flush' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: kernel/rcu/rcuscale.c:822: undefined reference to `rcu_scale_set_jiffies_till_flush' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: certs/system_keyring.o: in function `load_system_certificate_list': certs/system_keyring.c:207: undefined reference to `x509_load_certificate_list' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/fsl-edma.o: in function `fsl_edma_probe': drivers/dma/fsl-edma.c:302: undefined reference to `devm_ioremap_resource' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/fsl-edma.c:327: undefined reference to `devm_ioremap_resource' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/dma/idma64.o: in function `idma64_platform_probe': drivers/dma/idma64.c:644: undefined reference to `devm_ioremap_resource' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_init': drivers/clocksource/timer-of.c:151: undefined reference to `iounmap' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_base_init': drivers/clocksource/timer-of.c:159: undefined reference to `of_iomap' /opt/cross/gcc-11.3.0-nolibc/s390x-linux/bin/s390x-linux-ld: drivers/clocksource/timer-of.o: in function `timer_of_cleanup': drivers/clocksource/timer-of.c:151: undefined reference to `iounmap' vim +810 kernel/rcu/rcuscale.c 794 795 static int __init 796 kfree_scale_init(void) 797 { 798 long i; 799 int firsterr = 0; 800 unsigned long orig_jif, jif_start; 801 802 // Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs 803 // do not remove laziness of the lazy ones (since the test tries 804 // to stress call_rcu_lazy() for OOM). 805 // 806 // Also, do a quick self-test to ensure laziness is as much as 807 // expected. 808 if (kfree_rcu_by_lazy) { 809 /* do a test to check the timeout. */ > 810 orig_jif = rcu_scale_get_jiffies_till_flush(); 811 812 rcu_force_call_rcu_to_lazy(true); > 813 rcu_scale_set_jiffies_till_flush(2 * HZ); 814 rcu_barrier(); 815 816 jif_start = jiffies; 817 jiffies_at_lazy_cb = 0; 818 call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1); 819 820 smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1); 821 822 rcu_scale_set_jiffies_till_flush(orig_jif); 823 824 if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) { 825 pr_alert("Lazy CBs are not being lazy as expected!\n"); 826 return -1; 827 } 828 829 if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) { 830 pr_alert("Lazy CBs are being too lazy!\n"); 831 return -1; 832 } 833 } 834 835 kfree_nrealthreads = compute_real(kfree_nthreads); 836 /* Start up the kthreads. */ 837 if (shutdown) { 838 init_waitqueue_head(&shutdown_wq); 839 firsterr = torture_create_kthread(kfree_scale_shutdown, NULL, 840 shutdown_task); 841 if (torture_init_error(firsterr)) 842 goto unwind; 843 schedule_timeout_uninterruptible(1); 844 } 845 846 pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n", 847 kfree_mult * sizeof(struct kfree_obj), 848 kfree_rcu_by_lazy); 849 850 kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]), 851 GFP_KERNEL); 852 if (kfree_reader_tasks == NULL) { 853 firsterr = -ENOMEM; 854 goto unwind; 855 } 856 857 for (i = 0; i < kfree_nrealthreads; i++) { 858 firsterr = torture_create_kthread(kfree_scale_thread, (void *)i, 859 kfree_reader_tasks[i]); 860 if (torture_init_error(firsterr)) 861 goto unwind; 862 } 863 864 while (atomic_read(&n_kfree_scale_thread_started) < kfree_nrealthreads) 865 schedule_timeout_uninterruptible(1); 866 867 torture_init_end(); 868 return 0; 869 870 unwind: 871 torture_init_end(); 872 kfree_scale_cleanup(); 873 return firsterr; 874 } 875
Hi "Joel, Thank you for the patch! Perhaps something to improve: [auto build test WARNING on linus/master] [also build test WARNING on v5.19-rc3 next-20220622] [cannot apply to paulmck-rcu/dev] [If your patch is applied to the wrong git tree, kindly drop us a note. And when submitting patch, we suggest to use '--base' as documented in https://git-scm.com/docs/git-format-patch] url: https://github.com/intel-lab-lkp/linux/commits/Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 base: https://git.kernel.org/pub/scm/linux/kernel/git/torvalds/linux.git de5c208d533a46a074eb46ea17f672cc005a7269 config: x86_64-randconfig-s022 (https://download.01.org/0day-ci/archive/20220623/202206231529.kLjzriV0-lkp@intel.com/config) compiler: gcc-11 (Debian 11.3.0-3) 11.3.0 reproduce: # apt-get install sparse # sparse version: v0.6.4-31-g4880bd19-dirty # https://github.com/intel-lab-lkp/linux/commit/6c59cb940f39b882c20e6858c41df7c1470b930a git remote add linux-review https://github.com/intel-lab-lkp/linux git fetch --no-tags linux-review Joel-Fernandes-Google/Implement-call_rcu_lazy-and-miscellaneous-fixes/20220623-065447 git checkout 6c59cb940f39b882c20e6858c41df7c1470b930a # save the config file mkdir build_dir && cp config build_dir/.config make W=1 C=1 CF='-fdiagnostic-prefix -D__CHECK_ENDIAN__' O=build_dir ARCH=x86_64 SHELL=/bin/bash kernel/rcu/ If you fix the issue, kindly add following tag where applicable Reported-by: kernel test robot <lkp@intel.com> sparse warnings: (new ones prefixed by >>) >> kernel/rcu/rcuscale.c:663:6: sparse: sparse: symbol 'kfree_rcu_lazy' was not declared. Should it be static? >> kernel/rcu/rcuscale.c:786:15: sparse: sparse: symbol 'jiffies_at_lazy_cb' was not declared. Should it be static? >> kernel/rcu/rcuscale.c:787:17: sparse: sparse: symbol 'lazy_test1_rh' was not declared. Should it be static? >> kernel/rcu/rcuscale.c:788:5: sparse: sparse: symbol 'rcu_lazy_test1_cb_called' was not declared. Should it be static? >> kernel/rcu/rcuscale.c:789:6: sparse: sparse: symbol 'call_rcu_lazy_test1' was not declared. Should it be static?
On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote: > Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming > properties of call_rcu_lazy() with kfree_rcu(). > > With this test, we find similar memory footprint and time call_rcu_lazy() > free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy() > can survive OOM during extremely frequent calls. > > If we really push it, i.e. boot system with low memory and compare > kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more > resilient and is much harder to produce OOM as compared to kfree_rcu(). Another approach would be to make rcutorture's forward-progress testing able to use call_rcu_lazy(). This would test lazy callback flooding. Yet another approach would be to keep one CPU idle other than a kthread doing call_rcu_lazy(). Of course "idle" includes redirecting those pesky interrupts. It is almost certainly necessary for rcutorture to exercise the call_rcu_lazy() path regularly. Thanx, Paul > Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org> > --- > kernel/rcu/rcu.h | 6 ++++ > kernel/rcu/rcuscale.c | 64 +++++++++++++++++++++++++++++++++++++++++- > kernel/rcu/tree_nocb.h | 17 ++++++++++- > 3 files changed, 85 insertions(+), 2 deletions(-) > > diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h > index 71c0f45e70c3..436faf80a66b 100644 > --- a/kernel/rcu/rcu.h > +++ b/kernel/rcu/rcu.h > @@ -473,6 +473,12 @@ void do_trace_rcu_torture_read(const char *rcutorturename, > unsigned long c); > void rcu_gp_set_torture_wait(int duration); > void rcu_force_call_rcu_to_lazy(bool force); > + > +#if IS_ENABLED(CONFIG_RCU_SCALE_TEST) > +unsigned long rcu_scale_get_jiffies_till_flush(void); > +void rcu_scale_set_jiffies_till_flush(unsigned long j); > +#endif > + > #else > static inline void rcutorture_get_gp_data(enum rcutorture_type test_type, > int *flags, unsigned long *gp_seq) > diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c > index 277a5bfb37d4..58ee5c2cb37b 100644 > --- a/kernel/rcu/rcuscale.c > +++ b/kernel/rcu/rcuscale.c > @@ -95,6 +95,7 @@ torture_param(int, verbose, 1, "Enable verbose debugging printk()s"); > torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable"); > torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() scale test?"); > torture_param(int, kfree_mult, 1, "Multiple of kfree_obj size to allocate."); > +torture_param(int, kfree_rcu_by_lazy, 0, "Use call_rcu_lazy() to emulate kfree_rcu()?"); > > static char *scale_type = "rcu"; > module_param(scale_type, charp, 0444); > @@ -658,6 +659,13 @@ struct kfree_obj { > struct rcu_head rh; > }; > > +/* Used if doing RCU-kfree'ing via call_rcu_lazy(). */ > +void kfree_rcu_lazy(struct rcu_head *rh) > +{ > + struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh); > + kfree(obj); > +} > + > static int > kfree_scale_thread(void *arg) > { > @@ -695,6 +703,11 @@ kfree_scale_thread(void *arg) > if (!alloc_ptr) > return -ENOMEM; > > + if (kfree_rcu_by_lazy) { > + call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy); > + continue; > + } > + > // By default kfree_rcu_test_single and kfree_rcu_test_double are > // initialized to false. If both have the same value (false or true) > // both are randomly tested, otherwise only the one with value true > @@ -737,6 +750,9 @@ kfree_scale_cleanup(void) > { > int i; > > + if (kfree_rcu_by_lazy) > + rcu_force_call_rcu_to_lazy(false); > + > if (torture_cleanup_begin()) > return; > > @@ -766,11 +782,55 @@ kfree_scale_shutdown(void *arg) > return -EINVAL; > } > > +// Used if doing RCU-kfree'ing via call_rcu_lazy(). > +unsigned long jiffies_at_lazy_cb; > +struct rcu_head lazy_test1_rh; > +int rcu_lazy_test1_cb_called; > +void call_rcu_lazy_test1(struct rcu_head *rh) > +{ > + jiffies_at_lazy_cb = jiffies; > + WRITE_ONCE(rcu_lazy_test1_cb_called, 1); > +} > + > static int __init > kfree_scale_init(void) > { > long i; > int firsterr = 0; > + unsigned long orig_jif, jif_start; > + > + // Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs > + // do not remove laziness of the lazy ones (since the test tries > + // to stress call_rcu_lazy() for OOM). > + // > + // Also, do a quick self-test to ensure laziness is as much as > + // expected. > + if (kfree_rcu_by_lazy) { > + /* do a test to check the timeout. */ > + orig_jif = rcu_scale_get_jiffies_till_flush(); > + > + rcu_force_call_rcu_to_lazy(true); > + rcu_scale_set_jiffies_till_flush(2 * HZ); > + rcu_barrier(); > + > + jif_start = jiffies; > + jiffies_at_lazy_cb = 0; > + call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1); > + > + smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1); > + > + rcu_scale_set_jiffies_till_flush(orig_jif); > + > + if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) { > + pr_alert("Lazy CBs are not being lazy as expected!\n"); > + return -1; > + } > + > + if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) { > + pr_alert("Lazy CBs are being too lazy!\n"); > + return -1; > + } > + } > > kfree_nrealthreads = compute_real(kfree_nthreads); > /* Start up the kthreads. */ > @@ -783,7 +843,9 @@ kfree_scale_init(void) > schedule_timeout_uninterruptible(1); > } > > - pr_alert("kfree object size=%zu\n", kfree_mult * sizeof(struct kfree_obj)); > + pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n", > + kfree_mult * sizeof(struct kfree_obj), > + kfree_rcu_by_lazy); > > kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]), > GFP_KERNEL); > diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h > index b481f1ea57c0..255f2945b0fc 100644 > --- a/kernel/rcu/tree_nocb.h > +++ b/kernel/rcu/tree_nocb.h > @@ -257,6 +257,21 @@ static bool wake_nocb_gp(struct rcu_data *rdp, bool force) > } > > #define LAZY_FLUSH_JIFFIES (10 * HZ) > +unsigned long jiffies_till_flush = LAZY_FLUSH_JIFFIES; > + > +#ifdef CONFIG_RCU_SCALE_TEST > +void rcu_scale_set_jiffies_till_flush(unsigned long jif) > +{ > + jiffies_till_flush = jif; > +} > +EXPORT_SYMBOL(rcu_scale_set_jiffies_till_flush); > + > +unsigned long rcu_scale_get_jiffies_till_flush(void) > +{ > + return jiffies_till_flush; > +} > +EXPORT_SYMBOL(rcu_scale_get_jiffies_till_flush); > +#endif > > /* > * Arrange to wake the GP kthread for this NOCB group at some future > @@ -275,7 +290,7 @@ static void wake_nocb_gp_defer(struct rcu_data *rdp, int waketype, > * of callback storm, no need to wake up too early. > */ > if (waketype == RCU_NOCB_WAKE_LAZY) { > - mod_timer(&rdp_gp->nocb_timer, jiffies + LAZY_FLUSH_JIFFIES); > + mod_timer(&rdp_gp->nocb_timer, jiffies + jiffies_till_flush); > WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype); > } else if (waketype == RCU_NOCB_WAKE_BYPASS) { > mod_timer(&rdp_gp->nocb_timer, jiffies + 2); > -- > 2.37.0.rc0.104.g0611611a94-goog >
On Sat, Jun 25, 2022 at 09:13:27PM -0700, Paul E. McKenney wrote: > On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote: > > Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming > > properties of call_rcu_lazy() with kfree_rcu(). > > > > With this test, we find similar memory footprint and time call_rcu_lazy() > > free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy() > > can survive OOM during extremely frequent calls. > > > > If we really push it, i.e. boot system with low memory and compare > > kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more > > resilient and is much harder to produce OOM as compared to kfree_rcu(). > > Another approach would be to make rcutorture's forward-progress testing > able to use call_rcu_lazy(). This would test lazy callback flooding. > > Yet another approach would be to keep one CPU idle other than a > kthread doing call_rcu_lazy(). Of course "idle" includes redirecting > those pesky interrupts. > > It is almost certainly necessary for rcutorture to exercise the > call_rcu_lazy() path regularly. Currently I added a test like the following which adds a new torture type, my thought was to stress the new code to make sure nothing crashed or hung the kernel. That is working well except I don't exactly understand the total-gps print showing 0, which the other print shows 1188 GPs. I'll go dig into that tomorrow.. thanks! The print shows TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] TREE11 no success message, 7 successful version messages diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c index 7120165a9342..cc6b7392d801 100644 --- a/kernel/rcu/rcutorture.c +++ b/kernel/rcu/rcutorture.c @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = { #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU +#ifdef CONFIG_RCU_LAZY + +/* + * Definitions for lazy RCU torture testing. + */ +unsigned long orig_jiffies_till_flush; + +static void rcu_sync_torture_init_lazy(void) +{ + rcu_sync_torture_init(); + + orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush(); + rcu_lazy_set_jiffies_till_flush(50); +} + +static void rcu_lazy_cleanup(void) +{ + rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush); +} + +static struct rcu_torture_ops rcu_lazy_ops = { + .ttype = RCU_LAZY_FLAVOR, + .init = rcu_sync_torture_init_lazy, + .cleanup = rcu_lazy_cleanup, + .readlock = rcu_torture_read_lock, + .read_delay = rcu_read_delay, + .readunlock = rcu_torture_read_unlock, + .readlock_held = torture_readlock_not_held, + .get_gp_seq = rcu_get_gp_seq, + .gp_diff = rcu_seq_diff, + .deferred_free = rcu_torture_deferred_free, + .sync = synchronize_rcu, + .exp_sync = synchronize_rcu_expedited, + .get_gp_state = get_state_synchronize_rcu, + .start_gp_poll = start_poll_synchronize_rcu, + .poll_gp_state = poll_state_synchronize_rcu, + .cond_sync = cond_synchronize_rcu, + .call = call_rcu_lazy, + .cb_barrier = rcu_barrier, + .fqs = rcu_force_quiescent_state, + .stats = NULL, + .gp_kthread_dbg = show_rcu_gp_kthreads, + .check_boost_failed = rcu_check_boost_fail, + .stall_dur = rcu_jiffies_till_stall_check, + .irq_capable = 1, + .can_boost = IS_ENABLED(CONFIG_RCU_BOOST), + .extendables = RCUTORTURE_MAX_EXTEND, + .name = "rcu_lazy" +}; + +#define LAZY_OPS &rcu_lazy_ops, + +#else // #ifdef CONFIG_RCU_LAZY + +#define LAZY_OPS + +#endif // #else #ifdef CONFIG_RCU_LAZY + #ifdef CONFIG_TASKS_TRACE_RCU @@ -3145,7 +3203,7 @@ rcu_torture_init(void) unsigned long gp_seq = 0; static struct rcu_torture_ops *torture_ops[] = { &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops, - TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS + TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS &trivial_ops, }; diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 new file mode 100644 index 000000000000..436013f3e015 --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 @@ -0,0 +1,18 @@ +CONFIG_SMP=y +CONFIG_PREEMPT_NONE=n +CONFIG_PREEMPT_VOLUNTARY=n +CONFIG_PREEMPT=y +#CHECK#CONFIG_PREEMPT_RCU=y +CONFIG_HZ_PERIODIC=n +CONFIG_NO_HZ_IDLE=y +CONFIG_NO_HZ_FULL=n +CONFIG_RCU_TRACE=y +CONFIG_HOTPLUG_CPU=y +CONFIG_MAXSMP=y +CONFIG_CPUMASK_OFFSTACK=y +CONFIG_RCU_NOCB_CPU=y +CONFIG_DEBUG_LOCK_ALLOC=n +CONFIG_RCU_BOOST=n +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n +CONFIG_RCU_EXPERT=y +CONFIG_RCU_LAZY=y diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot new file mode 100644 index 000000000000..9b6f720d4ccd --- /dev/null +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot @@ -0,0 +1,8 @@ +maxcpus=8 nr_cpus=43 +rcutree.gp_preinit_delay=3 +rcutree.gp_init_delay=3 +rcutree.gp_cleanup_delay=3 +rcu_nocbs=0-7 +rcutorture.torture_type=rcu_lazy +rcutorture.nocbs_nthreads=8 +rcutorture.fwd_progress=0
On Fri, Jul 08, 2022 at 04:25:09AM +0000, Joel Fernandes wrote: > On Sat, Jun 25, 2022 at 09:13:27PM -0700, Paul E. McKenney wrote: > > On Wed, Jun 22, 2022 at 10:51:00PM +0000, Joel Fernandes (Google) wrote: > > > Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming > > > properties of call_rcu_lazy() with kfree_rcu(). > > > > > > With this test, we find similar memory footprint and time call_rcu_lazy() > > > free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy() > > > can survive OOM during extremely frequent calls. > > > > > > If we really push it, i.e. boot system with low memory and compare > > > kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more > > > resilient and is much harder to produce OOM as compared to kfree_rcu(). > > > > Another approach would be to make rcutorture's forward-progress testing > > able to use call_rcu_lazy(). This would test lazy callback flooding. > > > > Yet another approach would be to keep one CPU idle other than a > > kthread doing call_rcu_lazy(). Of course "idle" includes redirecting > > those pesky interrupts. > > > > It is almost certainly necessary for rcutorture to exercise the > > call_rcu_lazy() path regularly. > > Currently I added a test like the following which adds a new torture type, my > thought was to stress the new code to make sure nothing crashed or hung the > kernel. That is working well except I don't exactly understand the total-gps > print showing 0, which the other print shows 1188 GPs. I'll go dig into that > tomorrow.. thanks! > > The print shows > TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] > TREE11 no success message, 7 successful version messages Nice!!! It is very good to see you correctly using the rcu_torture_ops facility correctly! And this could be good for your own testing, and I am happy to pull it in for that purpose (given it being fixed, having a good commit log, and so on). After all, TREE10 is quite similar -- not part of CFLIST, but useful for certain types of focused testing. However, it would be very good to get call_rcu_lazy() testing going more generally, and in particular in TREE01 where offloading changes dynamically. A good way to do this is to add a .call_lazy() component to the rcu_torture_ops structure, and check for it in a manner similar to that done for the .deferred_free() component. Including adding a gp_normal_lazy module parameter. This would allow habitual testing on a few scenarios and focused lazy testing on all of them via the --bootargs parameter. On the total-gps=0, the usual suspicion would be that the lazy callbacks never got invoked. It looks like you were doing about a two-minute run, so maybe a longer run? Though weren't they supposed to kick in at 15 seconds or so? Or did this value of zero come about because this run used exactly 300 grace periods? Thanx, Paul > diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c > index 7120165a9342..cc6b7392d801 100644 > --- a/kernel/rcu/rcutorture.c > +++ b/kernel/rcu/rcutorture.c > @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = { > > #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU > > +#ifdef CONFIG_RCU_LAZY > + > +/* > + * Definitions for lazy RCU torture testing. > + */ > +unsigned long orig_jiffies_till_flush; > + > +static void rcu_sync_torture_init_lazy(void) > +{ > + rcu_sync_torture_init(); > + > + orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush(); > + rcu_lazy_set_jiffies_till_flush(50); > +} > + > +static void rcu_lazy_cleanup(void) > +{ > + rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush); > +} > + > +static struct rcu_torture_ops rcu_lazy_ops = { > + .ttype = RCU_LAZY_FLAVOR, > + .init = rcu_sync_torture_init_lazy, > + .cleanup = rcu_lazy_cleanup, > + .readlock = rcu_torture_read_lock, > + .read_delay = rcu_read_delay, > + .readunlock = rcu_torture_read_unlock, > + .readlock_held = torture_readlock_not_held, > + .get_gp_seq = rcu_get_gp_seq, > + .gp_diff = rcu_seq_diff, > + .deferred_free = rcu_torture_deferred_free, > + .sync = synchronize_rcu, > + .exp_sync = synchronize_rcu_expedited, > + .get_gp_state = get_state_synchronize_rcu, > + .start_gp_poll = start_poll_synchronize_rcu, > + .poll_gp_state = poll_state_synchronize_rcu, > + .cond_sync = cond_synchronize_rcu, > + .call = call_rcu_lazy, > + .cb_barrier = rcu_barrier, > + .fqs = rcu_force_quiescent_state, > + .stats = NULL, > + .gp_kthread_dbg = show_rcu_gp_kthreads, > + .check_boost_failed = rcu_check_boost_fail, > + .stall_dur = rcu_jiffies_till_stall_check, > + .irq_capable = 1, > + .can_boost = IS_ENABLED(CONFIG_RCU_BOOST), > + .extendables = RCUTORTURE_MAX_EXTEND, > + .name = "rcu_lazy" > +}; > + > +#define LAZY_OPS &rcu_lazy_ops, > + > +#else // #ifdef CONFIG_RCU_LAZY > + > +#define LAZY_OPS > + > +#endif // #else #ifdef CONFIG_RCU_LAZY > + > > #ifdef CONFIG_TASKS_TRACE_RCU > > @@ -3145,7 +3203,7 @@ rcu_torture_init(void) > unsigned long gp_seq = 0; > static struct rcu_torture_ops *torture_ops[] = { > &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops, > - TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS > + TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS > &trivial_ops, > }; > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > new file mode 100644 > index 000000000000..436013f3e015 > --- /dev/null > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > @@ -0,0 +1,18 @@ > +CONFIG_SMP=y > +CONFIG_PREEMPT_NONE=n > +CONFIG_PREEMPT_VOLUNTARY=n > +CONFIG_PREEMPT=y > +#CHECK#CONFIG_PREEMPT_RCU=y > +CONFIG_HZ_PERIODIC=n > +CONFIG_NO_HZ_IDLE=y > +CONFIG_NO_HZ_FULL=n > +CONFIG_RCU_TRACE=y > +CONFIG_HOTPLUG_CPU=y > +CONFIG_MAXSMP=y > +CONFIG_CPUMASK_OFFSTACK=y > +CONFIG_RCU_NOCB_CPU=y > +CONFIG_DEBUG_LOCK_ALLOC=n > +CONFIG_RCU_BOOST=n > +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n > +CONFIG_RCU_EXPERT=y > +CONFIG_RCU_LAZY=y > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > new file mode 100644 > index 000000000000..9b6f720d4ccd > --- /dev/null > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > @@ -0,0 +1,8 @@ > +maxcpus=8 nr_cpus=43 > +rcutree.gp_preinit_delay=3 > +rcutree.gp_init_delay=3 > +rcutree.gp_cleanup_delay=3 > +rcu_nocbs=0-7 > +rcutorture.torture_type=rcu_lazy > +rcutorture.nocbs_nthreads=8 > +rcutorture.fwd_progress=0 > -- > 2.37.0.rc0.161.g10f37bed90-goog >
Ah, with all the threads, I missed this one :(. Sorry about that. On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote: > > Currently I added a test like the following which adds a new torture type, my > > thought was to stress the new code to make sure nothing crashed or hung the > > kernel. That is working well except I don't exactly understand the total-gps > > print showing 0, which the other print shows 1188 GPs. I'll go dig into that > > tomorrow.. thanks! > > > > The print shows > > TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] > > TREE11 no success message, 7 successful version messages > > Nice!!! It is very good to see you correctly using the rcu_torture_ops > facility correctly! > > And this could be good for your own testing, and I am happy to pull it > in for that purpose (given it being fixed, having a good commit log, > and so on). After all, TREE10 is quite similar -- not part of CFLIST, > but useful for certain types of focused testing. > > However, it would be very good to get call_rcu_lazy() testing going > more generally, and in particular in TREE01 where offloading changes > dynamically. A good way to do this is to add a .call_lazy() component > to the rcu_torture_ops structure, and check for it in a manner similar > to that done for the .deferred_free() component. Including adding a > gp_normal_lazy module parameter. This would allow habitual testing > on a few scenarios and focused lazy testing on all of them via the > --bootargs parameter. Ok, if you don't mind I will make this particular enhancement to the torture test in a future patchset, since I kind of decided on doing v3 with just fixes to what I have and more testing. Certainly happy to enhance these tests in a future version. > On the total-gps=0, the usual suspicion would be that the lazy callbacks > never got invoked. It looks like you were doing about a two-minute run, > so maybe a longer run? Though weren't they supposed to kick in at 15 > seconds or so? Or did this value of zero come about because this run > used exactly 300 grace periods? It was zero because it required the RCU_FLAVOR torture type, where as my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it :) Thanks! - Joel > > diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c > > index 7120165a9342..cc6b7392d801 100644 > > --- a/kernel/rcu/rcutorture.c > > +++ b/kernel/rcu/rcutorture.c > > @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = { > > > > #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU > > > > +#ifdef CONFIG_RCU_LAZY > > + > > +/* > > + * Definitions for lazy RCU torture testing. > > + */ > > +unsigned long orig_jiffies_till_flush; > > + > > +static void rcu_sync_torture_init_lazy(void) > > +{ > > + rcu_sync_torture_init(); > > + > > + orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush(); > > + rcu_lazy_set_jiffies_till_flush(50); > > +} > > + > > +static void rcu_lazy_cleanup(void) > > +{ > > + rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush); > > +} > > + > > +static struct rcu_torture_ops rcu_lazy_ops = { > > + .ttype = RCU_LAZY_FLAVOR, > > + .init = rcu_sync_torture_init_lazy, > > + .cleanup = rcu_lazy_cleanup, > > + .readlock = rcu_torture_read_lock, > > + .read_delay = rcu_read_delay, > > + .readunlock = rcu_torture_read_unlock, > > + .readlock_held = torture_readlock_not_held, > > + .get_gp_seq = rcu_get_gp_seq, > > + .gp_diff = rcu_seq_diff, > > + .deferred_free = rcu_torture_deferred_free, > > + .sync = synchronize_rcu, > > + .exp_sync = synchronize_rcu_expedited, > > + .get_gp_state = get_state_synchronize_rcu, > > + .start_gp_poll = start_poll_synchronize_rcu, > > + .poll_gp_state = poll_state_synchronize_rcu, > > + .cond_sync = cond_synchronize_rcu, > > + .call = call_rcu_lazy, > > + .cb_barrier = rcu_barrier, > > + .fqs = rcu_force_quiescent_state, > > + .stats = NULL, > > + .gp_kthread_dbg = show_rcu_gp_kthreads, > > + .check_boost_failed = rcu_check_boost_fail, > > + .stall_dur = rcu_jiffies_till_stall_check, > > + .irq_capable = 1, > > + .can_boost = IS_ENABLED(CONFIG_RCU_BOOST), > > + .extendables = RCUTORTURE_MAX_EXTEND, > > + .name = "rcu_lazy" > > +}; > > + > > +#define LAZY_OPS &rcu_lazy_ops, > > + > > +#else // #ifdef CONFIG_RCU_LAZY > > + > > +#define LAZY_OPS > > + > > +#endif // #else #ifdef CONFIG_RCU_LAZY > > + > > > > #ifdef CONFIG_TASKS_TRACE_RCU > > > > @@ -3145,7 +3203,7 @@ rcu_torture_init(void) > > unsigned long gp_seq = 0; > > static struct rcu_torture_ops *torture_ops[] = { > > &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops, > > - TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS > > + TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS > > &trivial_ops, > > }; > > > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > > new file mode 100644 > > index 000000000000..436013f3e015 > > --- /dev/null > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > > @@ -0,0 +1,18 @@ > > +CONFIG_SMP=y > > +CONFIG_PREEMPT_NONE=n > > +CONFIG_PREEMPT_VOLUNTARY=n > > +CONFIG_PREEMPT=y > > +#CHECK#CONFIG_PREEMPT_RCU=y > > +CONFIG_HZ_PERIODIC=n > > +CONFIG_NO_HZ_IDLE=y > > +CONFIG_NO_HZ_FULL=n > > +CONFIG_RCU_TRACE=y > > +CONFIG_HOTPLUG_CPU=y > > +CONFIG_MAXSMP=y > > +CONFIG_CPUMASK_OFFSTACK=y > > +CONFIG_RCU_NOCB_CPU=y > > +CONFIG_DEBUG_LOCK_ALLOC=n > > +CONFIG_RCU_BOOST=n > > +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n > > +CONFIG_RCU_EXPERT=y > > +CONFIG_RCU_LAZY=y > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > > new file mode 100644 > > index 000000000000..9b6f720d4ccd > > --- /dev/null > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > > @@ -0,0 +1,8 @@ > > +maxcpus=8 nr_cpus=43 > > +rcutree.gp_preinit_delay=3 > > +rcutree.gp_init_delay=3 > > +rcutree.gp_cleanup_delay=3 > > +rcu_nocbs=0-7 > > +rcutorture.torture_type=rcu_lazy > > +rcutorture.nocbs_nthreads=8 > > +rcutorture.fwd_progress=0 > > -- > > 2.37.0.rc0.161.g10f37bed90-goog > >
On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote: > Ah, with all the threads, I missed this one :(. Sorry about that. I know that feeling... > On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote: > > > > Currently I added a test like the following which adds a new torture type, my > > > thought was to stress the new code to make sure nothing crashed or hung the > > > kernel. That is working well except I don't exactly understand the total-gps > > > print showing 0, which the other print shows 1188 GPs. I'll go dig into that > > > tomorrow.. thanks! > > > > > > The print shows > > > TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] > > > TREE11 no success message, 7 successful version messages > > > > Nice!!! It is very good to see you correctly using the rcu_torture_ops > > facility correctly! > > > > And this could be good for your own testing, and I am happy to pull it > > in for that purpose (given it being fixed, having a good commit log, > > and so on). After all, TREE10 is quite similar -- not part of CFLIST, > > but useful for certain types of focused testing. > > > > However, it would be very good to get call_rcu_lazy() testing going > > more generally, and in particular in TREE01 where offloading changes > > dynamically. A good way to do this is to add a .call_lazy() component > > to the rcu_torture_ops structure, and check for it in a manner similar > > to that done for the .deferred_free() component. Including adding a > > gp_normal_lazy module parameter. This would allow habitual testing > > on a few scenarios and focused lazy testing on all of them via the > > --bootargs parameter. > > Ok, if you don't mind I will make this particular enhancement to the > torture test in a future patchset, since I kind of decided on doing v3 > with just fixes to what I have and more testing. Certainly happy to > enhance these tests in a future version. No need to gate v3 on those tests. > > On the total-gps=0, the usual suspicion would be that the lazy callbacks > > never got invoked. It looks like you were doing about a two-minute run, > > so maybe a longer run? Though weren't they supposed to kick in at 15 > > seconds or so? Or did this value of zero come about because this run > > used exactly 300 grace periods? > > It was zero because it required the RCU_FLAVOR torture type, where as > my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it > :) Heh! Then it didn't actually do any testing. Done that as well! Thanx, Paul > Thanks! > > - Joel > > > > > diff --git a/kernel/rcu/rcutorture.c b/kernel/rcu/rcutorture.c > > > index 7120165a9342..cc6b7392d801 100644 > > > --- a/kernel/rcu/rcutorture.c > > > +++ b/kernel/rcu/rcutorture.c > > > @@ -872,6 +872,64 @@ static struct rcu_torture_ops tasks_rude_ops = { > > > > > > #endif // #else #ifdef CONFIG_TASKS_RUDE_RCU > > > > > > +#ifdef CONFIG_RCU_LAZY > > > + > > > +/* > > > + * Definitions for lazy RCU torture testing. > > > + */ > > > +unsigned long orig_jiffies_till_flush; > > > + > > > +static void rcu_sync_torture_init_lazy(void) > > > +{ > > > + rcu_sync_torture_init(); > > > + > > > + orig_jiffies_till_flush = rcu_lazy_get_jiffies_till_flush(); > > > + rcu_lazy_set_jiffies_till_flush(50); > > > +} > > > + > > > +static void rcu_lazy_cleanup(void) > > > +{ > > > + rcu_lazy_set_jiffies_till_flush(orig_jiffies_till_flush); > > > +} > > > + > > > +static struct rcu_torture_ops rcu_lazy_ops = { > > > + .ttype = RCU_LAZY_FLAVOR, > > > + .init = rcu_sync_torture_init_lazy, > > > + .cleanup = rcu_lazy_cleanup, > > > + .readlock = rcu_torture_read_lock, > > > + .read_delay = rcu_read_delay, > > > + .readunlock = rcu_torture_read_unlock, > > > + .readlock_held = torture_readlock_not_held, > > > + .get_gp_seq = rcu_get_gp_seq, > > > + .gp_diff = rcu_seq_diff, > > > + .deferred_free = rcu_torture_deferred_free, > > > + .sync = synchronize_rcu, > > > + .exp_sync = synchronize_rcu_expedited, > > > + .get_gp_state = get_state_synchronize_rcu, > > > + .start_gp_poll = start_poll_synchronize_rcu, > > > + .poll_gp_state = poll_state_synchronize_rcu, > > > + .cond_sync = cond_synchronize_rcu, > > > + .call = call_rcu_lazy, > > > + .cb_barrier = rcu_barrier, > > > + .fqs = rcu_force_quiescent_state, > > > + .stats = NULL, > > > + .gp_kthread_dbg = show_rcu_gp_kthreads, > > > + .check_boost_failed = rcu_check_boost_fail, > > > + .stall_dur = rcu_jiffies_till_stall_check, > > > + .irq_capable = 1, > > > + .can_boost = IS_ENABLED(CONFIG_RCU_BOOST), > > > + .extendables = RCUTORTURE_MAX_EXTEND, > > > + .name = "rcu_lazy" > > > +}; > > > + > > > +#define LAZY_OPS &rcu_lazy_ops, > > > + > > > +#else // #ifdef CONFIG_RCU_LAZY > > > + > > > +#define LAZY_OPS > > > + > > > +#endif // #else #ifdef CONFIG_RCU_LAZY > > > + > > > > > > #ifdef CONFIG_TASKS_TRACE_RCU > > > > > > @@ -3145,7 +3203,7 @@ rcu_torture_init(void) > > > unsigned long gp_seq = 0; > > > static struct rcu_torture_ops *torture_ops[] = { > > > &rcu_ops, &rcu_busted_ops, &srcu_ops, &srcud_ops, &busted_srcud_ops, > > > - TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS > > > + TASKS_OPS TASKS_RUDE_OPS TASKS_TRACING_OPS LAZY_OPS > > > &trivial_ops, > > > }; > > > > > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11 b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > > > new file mode 100644 > > > index 000000000000..436013f3e015 > > > --- /dev/null > > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11 > > > @@ -0,0 +1,18 @@ > > > +CONFIG_SMP=y > > > +CONFIG_PREEMPT_NONE=n > > > +CONFIG_PREEMPT_VOLUNTARY=n > > > +CONFIG_PREEMPT=y > > > +#CHECK#CONFIG_PREEMPT_RCU=y > > > +CONFIG_HZ_PERIODIC=n > > > +CONFIG_NO_HZ_IDLE=y > > > +CONFIG_NO_HZ_FULL=n > > > +CONFIG_RCU_TRACE=y > > > +CONFIG_HOTPLUG_CPU=y > > > +CONFIG_MAXSMP=y > > > +CONFIG_CPUMASK_OFFSTACK=y > > > +CONFIG_RCU_NOCB_CPU=y > > > +CONFIG_DEBUG_LOCK_ALLOC=n > > > +CONFIG_RCU_BOOST=n > > > +CONFIG_DEBUG_OBJECTS_RCU_HEAD=n > > > +CONFIG_RCU_EXPERT=y > > > +CONFIG_RCU_LAZY=y > > > diff --git a/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > > > new file mode 100644 > > > index 000000000000..9b6f720d4ccd > > > --- /dev/null > > > +++ b/tools/testing/selftests/rcutorture/configs/rcu/TREE11.boot > > > @@ -0,0 +1,8 @@ > > > +maxcpus=8 nr_cpus=43 > > > +rcutree.gp_preinit_delay=3 > > > +rcutree.gp_init_delay=3 > > > +rcutree.gp_cleanup_delay=3 > > > +rcu_nocbs=0-7 > > > +rcutorture.torture_type=rcu_lazy > > > +rcutorture.nocbs_nthreads=8 > > > +rcutorture.fwd_progress=0 > > > -- > > > 2.37.0.rc0.161.g10f37bed90-goog > > >
On 7/12/2022 4:58 PM, Paul E. McKenney wrote: > On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote: >> Ah, with all the threads, I missed this one :(. Sorry about that. > > I know that feeling... > >> On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote: >> >>>> Currently I added a test like the following which adds a new torture type, my >>>> thought was to stress the new code to make sure nothing crashed or hung the >>>> kernel. That is working well except I don't exactly understand the total-gps >>>> print showing 0, which the other print shows 1188 GPs. I'll go dig into that >>>> tomorrow.. thanks! >>>> >>>> The print shows >>>> TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] >>>> TREE11 no success message, 7 successful version messages >>> >>> Nice!!! It is very good to see you correctly using the rcu_torture_ops >>> facility correctly! >>> >>> And this could be good for your own testing, and I am happy to pull it >>> in for that purpose (given it being fixed, having a good commit log, >>> and so on). After all, TREE10 is quite similar -- not part of CFLIST, >>> but useful for certain types of focused testing. >>> >>> However, it would be very good to get call_rcu_lazy() testing going >>> more generally, and in particular in TREE01 where offloading changes >>> dynamically. A good way to do this is to add a .call_lazy() component >>> to the rcu_torture_ops structure, and check for it in a manner similar >>> to that done for the .deferred_free() component. Including adding a >>> gp_normal_lazy module parameter. This would allow habitual testing >>> on a few scenarios and focused lazy testing on all of them via the >>> --bootargs parameter. >> >> Ok, if you don't mind I will make this particular enhancement to the >> torture test in a future patchset, since I kind of decided on doing v3 >> with just fixes to what I have and more testing. Certainly happy to >> enhance these tests in a future version. > > No need to gate v3 on those tests. > >>> On the total-gps=0, the usual suspicion would be that the lazy callbacks >>> never got invoked. It looks like you were doing about a two-minute run, >>> so maybe a longer run? Though weren't they supposed to kick in at 15 >>> seconds or so? Or did this value of zero come about because this run >>> used exactly 300 grace periods? >> >> It was zero because it required the RCU_FLAVOR torture type, where as >> my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it >> :) > > Heh! Then it didn't actually do any testing. Done that as well! Sorry to not be clear, I meant the switch-case list below, not the torture list in rcutorture.c! It was in the rcutorture.c so was being tested, just reporting zero gp_seq as I pointed. /* * Send along grace-period-related data for rcutorture diagnostics. */ void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags, unsigned long *gp_seq) { switch (test_type) { case RCU_FLAVOR: case RCU_LAZY_FLAVOR: *flags = READ_ONCE(rcu_state.gp_flags); *gp_seq = rcu_seq_current(&rcu_state.gp_seq); break; default: break; } } EXPORT_SYMBOL_GPL(rcutorture_get_gp_data);
On Tue, Jul 12, 2022 at 05:15:23PM -0400, Joel Fernandes wrote: > > > On 7/12/2022 4:58 PM, Paul E. McKenney wrote: > > On Tue, Jul 12, 2022 at 04:27:05PM -0400, Joel Fernandes wrote: > >> Ah, with all the threads, I missed this one :(. Sorry about that. > > > > I know that feeling... > > > >> On Fri, Jul 8, 2022 at 7:06 PM Paul E. McKenney <paulmck@kernel.org> wrote: > >> > >>>> Currently I added a test like the following which adds a new torture type, my > >>>> thought was to stress the new code to make sure nothing crashed or hung the > >>>> kernel. That is working well except I don't exactly understand the total-gps > >>>> print showing 0, which the other print shows 1188 GPs. I'll go dig into that > >>>> tomorrow.. thanks! > >>>> > >>>> The print shows > >>>> TREE11 ------- 1474 GPs (12.2833/s) [rcu_lazy: g0 f0x0 total-gps=0] > >>>> TREE11 no success message, 7 successful version messages > >>> > >>> Nice!!! It is very good to see you correctly using the rcu_torture_ops > >>> facility correctly! > >>> > >>> And this could be good for your own testing, and I am happy to pull it > >>> in for that purpose (given it being fixed, having a good commit log, > >>> and so on). After all, TREE10 is quite similar -- not part of CFLIST, > >>> but useful for certain types of focused testing. > >>> > >>> However, it would be very good to get call_rcu_lazy() testing going > >>> more generally, and in particular in TREE01 where offloading changes > >>> dynamically. A good way to do this is to add a .call_lazy() component > >>> to the rcu_torture_ops structure, and check for it in a manner similar > >>> to that done for the .deferred_free() component. Including adding a > >>> gp_normal_lazy module parameter. This would allow habitual testing > >>> on a few scenarios and focused lazy testing on all of them via the > >>> --bootargs parameter. > >> > >> Ok, if you don't mind I will make this particular enhancement to the > >> torture test in a future patchset, since I kind of decided on doing v3 > >> with just fixes to what I have and more testing. Certainly happy to > >> enhance these tests in a future version. > > > > No need to gate v3 on those tests. > > > >>> On the total-gps=0, the usual suspicion would be that the lazy callbacks > >>> never got invoked. It looks like you were doing about a two-minute run, > >>> so maybe a longer run? Though weren't they supposed to kick in at 15 > >>> seconds or so? Or did this value of zero come about because this run > >>> used exactly 300 grace periods? > >> > >> It was zero because it required the RCU_FLAVOR torture type, where as > >> my torture type was lazy. Adding RCU_LAZY_FLAVOR to the list fixed it > >> :) > > > > Heh! Then it didn't actually do any testing. Done that as well! > > Sorry to not be clear, I meant the switch-case list below, not the > torture list in rcutorture.c! It was in the rcutorture.c so was being > tested, just reporting zero gp_seq as I pointed. > > /* > * Send along grace-period-related data for rcutorture diagnostics. > */ > void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags, > unsigned long *gp_seq) > { > switch (test_type) { > case RCU_FLAVOR: > case RCU_LAZY_FLAVOR: > *flags = READ_ONCE(rcu_state.gp_flags); > *gp_seq = rcu_seq_current(&rcu_state.gp_seq); > break; > default: > break; > } > } > EXPORT_SYMBOL_GPL(rcutorture_get_gp_data); Ah, that would do it! Thank you for the clarification. Thanx, Paul
diff --git a/kernel/rcu/rcu.h b/kernel/rcu/rcu.h index 71c0f45e70c3..436faf80a66b 100644 --- a/kernel/rcu/rcu.h +++ b/kernel/rcu/rcu.h @@ -473,6 +473,12 @@ void do_trace_rcu_torture_read(const char *rcutorturename, unsigned long c); void rcu_gp_set_torture_wait(int duration); void rcu_force_call_rcu_to_lazy(bool force); + +#if IS_ENABLED(CONFIG_RCU_SCALE_TEST) +unsigned long rcu_scale_get_jiffies_till_flush(void); +void rcu_scale_set_jiffies_till_flush(unsigned long j); +#endif + #else static inline void rcutorture_get_gp_data(enum rcutorture_type test_type, int *flags, unsigned long *gp_seq) diff --git a/kernel/rcu/rcuscale.c b/kernel/rcu/rcuscale.c index 277a5bfb37d4..58ee5c2cb37b 100644 --- a/kernel/rcu/rcuscale.c +++ b/kernel/rcu/rcuscale.c @@ -95,6 +95,7 @@ torture_param(int, verbose, 1, "Enable verbose debugging printk()s"); torture_param(int, writer_holdoff, 0, "Holdoff (us) between GPs, zero to disable"); torture_param(int, kfree_rcu_test, 0, "Do we run a kfree_rcu() scale test?"); torture_param(int, kfree_mult, 1, "Multiple of kfree_obj size to allocate."); +torture_param(int, kfree_rcu_by_lazy, 0, "Use call_rcu_lazy() to emulate kfree_rcu()?"); static char *scale_type = "rcu"; module_param(scale_type, charp, 0444); @@ -658,6 +659,13 @@ struct kfree_obj { struct rcu_head rh; }; +/* Used if doing RCU-kfree'ing via call_rcu_lazy(). */ +void kfree_rcu_lazy(struct rcu_head *rh) +{ + struct kfree_obj *obj = container_of(rh, struct kfree_obj, rh); + kfree(obj); +} + static int kfree_scale_thread(void *arg) { @@ -695,6 +703,11 @@ kfree_scale_thread(void *arg) if (!alloc_ptr) return -ENOMEM; + if (kfree_rcu_by_lazy) { + call_rcu_lazy(&(alloc_ptr->rh), kfree_rcu_lazy); + continue; + } + // By default kfree_rcu_test_single and kfree_rcu_test_double are // initialized to false. If both have the same value (false or true) // both are randomly tested, otherwise only the one with value true @@ -737,6 +750,9 @@ kfree_scale_cleanup(void) { int i; + if (kfree_rcu_by_lazy) + rcu_force_call_rcu_to_lazy(false); + if (torture_cleanup_begin()) return; @@ -766,11 +782,55 @@ kfree_scale_shutdown(void *arg) return -EINVAL; } +// Used if doing RCU-kfree'ing via call_rcu_lazy(). +unsigned long jiffies_at_lazy_cb; +struct rcu_head lazy_test1_rh; +int rcu_lazy_test1_cb_called; +void call_rcu_lazy_test1(struct rcu_head *rh) +{ + jiffies_at_lazy_cb = jiffies; + WRITE_ONCE(rcu_lazy_test1_cb_called, 1); +} + static int __init kfree_scale_init(void) { long i; int firsterr = 0; + unsigned long orig_jif, jif_start; + + // Force all call_rcu() to call_rcu_lazy() so that non-lazy CBs + // do not remove laziness of the lazy ones (since the test tries + // to stress call_rcu_lazy() for OOM). + // + // Also, do a quick self-test to ensure laziness is as much as + // expected. + if (kfree_rcu_by_lazy) { + /* do a test to check the timeout. */ + orig_jif = rcu_scale_get_jiffies_till_flush(); + + rcu_force_call_rcu_to_lazy(true); + rcu_scale_set_jiffies_till_flush(2 * HZ); + rcu_barrier(); + + jif_start = jiffies; + jiffies_at_lazy_cb = 0; + call_rcu_lazy(&lazy_test1_rh, call_rcu_lazy_test1); + + smp_cond_load_relaxed(&rcu_lazy_test1_cb_called, VAL == 1); + + rcu_scale_set_jiffies_till_flush(orig_jif); + + if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start < 2 * HZ)) { + pr_alert("Lazy CBs are not being lazy as expected!\n"); + return -1; + } + + if (WARN_ON_ONCE(jiffies_at_lazy_cb - jif_start > 3 * HZ)) { + pr_alert("Lazy CBs are being too lazy!\n"); + return -1; + } + } kfree_nrealthreads = compute_real(kfree_nthreads); /* Start up the kthreads. */ @@ -783,7 +843,9 @@ kfree_scale_init(void) schedule_timeout_uninterruptible(1); } - pr_alert("kfree object size=%zu\n", kfree_mult * sizeof(struct kfree_obj)); + pr_alert("kfree object size=%zu, kfree_rcu_by_lazy=%d\n", + kfree_mult * sizeof(struct kfree_obj), + kfree_rcu_by_lazy); kfree_reader_tasks = kcalloc(kfree_nrealthreads, sizeof(kfree_reader_tasks[0]), GFP_KERNEL); diff --git a/kernel/rcu/tree_nocb.h b/kernel/rcu/tree_nocb.h index b481f1ea57c0..255f2945b0fc 100644 --- a/kernel/rcu/tree_nocb.h +++ b/kernel/rcu/tree_nocb.h @@ -257,6 +257,21 @@ static bool wake_nocb_gp(struct rcu_data *rdp, bool force) } #define LAZY_FLUSH_JIFFIES (10 * HZ) +unsigned long jiffies_till_flush = LAZY_FLUSH_JIFFIES; + +#ifdef CONFIG_RCU_SCALE_TEST +void rcu_scale_set_jiffies_till_flush(unsigned long jif) +{ + jiffies_till_flush = jif; +} +EXPORT_SYMBOL(rcu_scale_set_jiffies_till_flush); + +unsigned long rcu_scale_get_jiffies_till_flush(void) +{ + return jiffies_till_flush; +} +EXPORT_SYMBOL(rcu_scale_get_jiffies_till_flush); +#endif /* * Arrange to wake the GP kthread for this NOCB group at some future @@ -275,7 +290,7 @@ static void wake_nocb_gp_defer(struct rcu_data *rdp, int waketype, * of callback storm, no need to wake up too early. */ if (waketype == RCU_NOCB_WAKE_LAZY) { - mod_timer(&rdp_gp->nocb_timer, jiffies + LAZY_FLUSH_JIFFIES); + mod_timer(&rdp_gp->nocb_timer, jiffies + jiffies_till_flush); WRITE_ONCE(rdp_gp->nocb_defer_wakeup, waketype); } else if (waketype == RCU_NOCB_WAKE_BYPASS) { mod_timer(&rdp_gp->nocb_timer, jiffies + 2);
Reuse the kfree_rcu() test in order to be able to compare the memory reclaiming properties of call_rcu_lazy() with kfree_rcu(). With this test, we find similar memory footprint and time call_rcu_lazy() free'ing takes compared to kfree_rcu(). Also we confirm that call_rcu_lazy() can survive OOM during extremely frequent calls. If we really push it, i.e. boot system with low memory and compare kfree_rcu() with call_rcu_lazy(), I find that call_rcu_lazy() is more resilient and is much harder to produce OOM as compared to kfree_rcu(). Signed-off-by: Joel Fernandes (Google) <joel@joelfernandes.org> --- kernel/rcu/rcu.h | 6 ++++ kernel/rcu/rcuscale.c | 64 +++++++++++++++++++++++++++++++++++++++++- kernel/rcu/tree_nocb.h | 17 ++++++++++- 3 files changed, 85 insertions(+), 2 deletions(-)