All sorts of instrumentation exported through /proc/rcu Documentation/filesystems/proc.txt | 4 include/linux/rcupdate.h | 24 +++ kernel/rcupdate.c | 245 ++++++++++++++++++++++++++++++++++++- 3 files changed, 270 insertions(+), 3 deletions(-) diff -puN Documentation/filesystems/proc.txt~15-rcu-debug Documentation/filesystems/proc.txt --- linux-2.6.0-rtcache/Documentation/filesystems/proc.txt~15-rcu-debug 2004-03-03 17:21:40.000000000 +0530 +++ linux-2.6.0-rtcache-dipankar/Documentation/filesystems/proc.txt 2004-03-03 17:21:40.000000000 +0530 @@ -223,6 +223,7 @@ Table 1-3: Kernel info in /proc partitions Table of partitions known to the system pci Depreciated info of PCI bus (new way -> /proc/bus/pci/, decoupled by lspci (2.4) + rcu Read-Copy Update information (2.5) rtc Real time clock scsi SCSI info (see text) slabinfo Slab pool info @@ -347,6 +348,9 @@ available. In this case, there are 0 ch ZONE_DMA, 4 chunks of 2^1*PAGE_SIZE in ZONE_DMA, 101 chunks of 2^4*PAGE_SIZE available in ZONE_NORMAL, etc... +The rcu file gives information about Read-Copy Update synchronization +primitive. It indicates the number for RCU requests and actual +updates for every CPU. 1.3 IDE devices in /proc/ide ---------------------------- diff -puN include/linux/rcupdate.h~15-rcu-debug include/linux/rcupdate.h --- linux-2.6.0-rtcache/include/linux/rcupdate.h~15-rcu-debug 2004-03-03 17:21:40.000000000 +0530 +++ linux-2.6.0-rtcache-dipankar/include/linux/rcupdate.h 2004-03-03 17:21:40.000000000 +0530 @@ -41,6 +41,7 @@ #include #include #include +#include /** * struct rcu_head - callback structure for use with RCU @@ -70,6 +71,7 @@ struct rcu_ctrlblk { long maxbatch; /* Max requested batch number. */ cpumask_t rcu_cpu_mask; /* CPUs that need to switch in order */ /* for current batch to proceed. */ + unsigned long grace_start; }; /* Is batch a before batch b ? */ @@ -96,6 +98,14 @@ struct rcu_data { long batch; /* Batch # for current RCU batch */ struct list_head nxtlist; struct list_head curlist; + long nr_rcureqs; + long nr_rcupdates; + unsigned long grace_start; + unsigned long grace_ticks; + unsigned long max_grace_ticks; + unsigned long max_rcu_ticks; + unsigned long max_updates; + unsigned long max_tick_updates; }; DECLARE_PER_CPU(struct rcu_data, rcu_data); @@ -106,6 +116,14 @@ extern struct rcu_ctrlblk rcu_ctrlblk; #define RCU_batch(cpu) (per_cpu(rcu_data, (cpu)).batch) #define RCU_nxtlist(cpu) (per_cpu(rcu_data, (cpu)).nxtlist) #define RCU_curlist(cpu) (per_cpu(rcu_data, (cpu)).curlist) +#define RCU_nr_rcureqs(cpu) (per_cpu(rcu_data, (cpu)).nr_rcureqs) +#define RCU_nr_rcupdates(cpu) (per_cpu(rcu_data, (cpu)).nr_rcupdates) +#define RCU_grace_start(cpu) (per_cpu(rcu_data, (cpu)).grace_start) +#define RCU_grace_ticks(cpu) (per_cpu(rcu_data, (cpu)).grace_ticks) +#define RCU_max_grace_ticks(cpu) (per_cpu(rcu_data, (cpu)).max_grace_ticks) +#define RCU_max_rcu_ticks(cpu) (per_cpu(rcu_data, (cpu)).max_rcu_ticks) +#define RCU_max_updates(cpu) (per_cpu(rcu_data, (cpu)).max_updates) +#define RCU_max_tick_updates(cpu) (per_cpu(rcu_data, (cpu)).max_tick_updates) #define RCU_QSCTR_INVALID 0 @@ -124,6 +142,11 @@ static inline int rcu_pending(int cpu) #define rcu_read_lock() preempt_disable() #define rcu_read_unlock() preempt_enable() +static inline unsigned long rcu_grace_period(int cpu) +{ + return RCU_grace_ticks(cpu); +} + extern void rcu_init(void); extern void rcu_check_callbacks(int cpu, int user); @@ -131,6 +154,7 @@ extern void rcu_check_callbacks(int cpu, extern void FASTCALL(call_rcu(struct rcu_head *head, void (*func)(void *arg), void *arg)); extern void synchronize_kernel(void); +extern void rcu_print_info(void); #endif /* __KERNEL__ */ #endif /* __LINUX_RCUPDATE_H */ diff -puN kernel/rcupdate.c~15-rcu-debug kernel/rcupdate.c --- linux-2.6.0-rtcache/kernel/rcupdate.c~15-rcu-debug 2004-03-03 17:21:40.000000000 +0530 +++ linux-2.6.0-rtcache-dipankar/kernel/rcupdate.c 2004-03-04 11:00:10.510767544 +0530 @@ -38,9 +38,13 @@ #include #include #include +#include #include #include +#include #include +#include +#include #include #include #include @@ -55,6 +59,12 @@ DEFINE_PER_CPU(struct rcu_data, rcu_data static DEFINE_PER_CPU(struct tasklet_struct, rcu_tasklet) = {NULL}; #define RCU_tasklet(cpu) (per_cpu(rcu_tasklet, cpu)) +#define RCU_TRACE_SIZE 15000 + +static int rcu_trace_buf[NR_CPUS][ALIGN((RCU_TRACE_SIZE * sizeof(int)), SMP_CACHE_BYTES) / sizeof(int)] __cacheline_aligned; +static int rcu_trace; +static unsigned long rcu_trace_start; + /** * call_rcu - Queue an RCU update request. * @head: structure to be used for queueing the RCU updates. @@ -76,6 +86,7 @@ void call_rcu(struct rcu_head *head, voi local_irq_save(flags); cpu = smp_processor_id(); list_add_tail(&head->list, &RCU_nxtlist(cpu)); + RCU_nr_rcureqs(cpu)++; local_irq_restore(flags); } @@ -83,16 +94,36 @@ void call_rcu(struct rcu_head *head, voi * Invoke the completed RCU callbacks. They are expected to be in * a per-cpu list. */ -static void rcu_do_batch(struct list_head *list) +static void rcu_do_batch(int cpu, struct list_head *list) { struct list_head *entry; struct rcu_head *head; + unsigned long start = jiffies; + unsigned long ticks; + int count = 0; while (!list_empty(list)) { entry = list->next; list_del(entry); head = list_entry(entry, struct rcu_head, list); head->func(head->arg); + RCU_nr_rcupdates(cpu)++; + count++; + } + ticks = jiffies - start; + if (ticks > 1 * HZ) + printk("RCU: rcu callbacks too long - (%lu) ticks\n", + jiffies - start); + if (ticks > RCU_max_rcu_ticks(cpu)) { + RCU_max_rcu_ticks(cpu) = ticks; + RCU_max_tick_updates(cpu) = count; + } + if (count > RCU_max_updates(cpu)) + RCU_max_updates(cpu) = count; + if (rcu_trace) { + unsigned long bucket = (jiffies - rcu_trace_start) >> 2; + if (bucket < RCU_TRACE_SIZE) + rcu_trace_buf[cpu][bucket] = count; } } @@ -111,6 +142,7 @@ static void rcu_start_batch(long newbatc return; } rcu_ctrlblk.rcu_cpu_mask = cpu_online_map; + rcu_ctrlblk.grace_start = jiffies; } /* @@ -132,16 +164,23 @@ static void rcu_check_quiescent_state(vo */ if (RCU_last_qsctr(cpu) == RCU_QSCTR_INVALID) { RCU_last_qsctr(cpu) = RCU_qsctr(cpu); + RCU_grace_start(cpu) = jiffies; + RCU_grace_ticks(cpu) = 0UL; return; } - if (RCU_qsctr(cpu) == RCU_last_qsctr(cpu)) + if (RCU_qsctr(cpu) == RCU_last_qsctr(cpu)) { + RCU_grace_ticks(cpu) = jiffies - RCU_grace_start(cpu); + if (RCU_grace_ticks(cpu) > RCU_max_grace_ticks(cpu)) + RCU_max_grace_ticks(cpu) = RCU_grace_ticks(cpu); return; + } spin_lock(&rcu_ctrlblk.mutex); if (!cpu_isset(cpu, rcu_ctrlblk.rcu_cpu_mask)) goto out_unlock; cpu_clear(cpu, rcu_ctrlblk.rcu_cpu_mask); + RCU_grace_ticks(cpu) = 0UL; RCU_last_qsctr(cpu) = RCU_QSCTR_INVALID; if (!cpus_empty(rcu_ctrlblk.rcu_cpu_mask)) goto out_unlock; @@ -186,7 +225,7 @@ static void rcu_process_callbacks(unsign } rcu_check_quiescent_state(); if (!list_empty(&list)) - rcu_do_batch(&list); + rcu_do_batch(cpu, &list); } void rcu_check_callbacks(int cpu, int user) @@ -265,3 +304,203 @@ void synchronize_kernel(void) EXPORT_SYMBOL(call_rcu); EXPORT_SYMBOL(synchronize_kernel); + +#ifdef CONFIG_PROC_FS + +static void *rcustat_start(struct seq_file *m, loff_t *pos) +{ + static int cpu; + cpu = *pos; + return *pos < NR_CPUS ? &cpu : NULL; +} + +static void *rcustat_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return rcustat_start(m, pos); +} + +static void rcustat_stop(struct seq_file *m, void *v) +{ +} + +static int show_rcustat(struct seq_file *m, void *v) +{ + int cpu = *(int *)v; + + if (!cpu_online(cpu)) + return 0; + seq_printf(m, "CPU : %d\n", cpu); + seq_printf(m, "RCU requests : %ld\n", RCU_nr_rcureqs(cpu)); + seq_printf(m, "RCU updates : %ld\n", RCU_nr_rcupdates(cpu)); + seq_printf(m, "RCU max grace time : %ld\n", RCU_max_grace_ticks(cpu)); + seq_printf(m, "RCU max update time : %ld\n", RCU_max_rcu_ticks(cpu)); + seq_printf(m, "RCU max updates : %ld\n", RCU_max_updates(cpu)); + seq_printf(m, "RCU max time updates : %ld\n\n", RCU_max_tick_updates(cpu)); + return 0; +} + +struct seq_operations rcustat_op = { + .start = rcustat_start, + .next = rcustat_next, + .stop = rcustat_stop, + .show = show_rcustat, +}; + +static int rcustat_open(struct inode *inode, struct file *file) +{ + return seq_open(file, &rcustat_op); +} +static struct file_operations proc_rcustat_operations = { + .open = rcustat_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static void *rcutrace_start(struct seq_file *m, loff_t *pos) +{ + static int cpu; + int *rt; + + cpu = (int)m->private; + rt = &rcu_trace_buf[cpu][0]; + return *pos < RCU_TRACE_SIZE ? rt + *pos : NULL; +} + +static void *rcutrace_next(struct seq_file *m, void *v, loff_t *pos) +{ + ++*pos; + return rcutrace_start(m, pos); +} + +static void rcutrace_stop(struct seq_file *m, void *v) +{ +} + +static int show_rcutrace(struct seq_file *m, void *v) +{ + int *rt = v; + int cpu; + + cpu = (int)m->private; + seq_printf(m, "%d %d\n", rt - &rcu_trace_buf[cpu][0], *rt); + return 0; +} + +struct seq_operations rcutrace_op = { + .start = rcutrace_start, + .next = rcutrace_next, + .stop = rcutrace_stop, + .show = show_rcutrace, +}; + +static int rcutrace_open(struct inode *inode, struct file *file) +{ + struct proc_dir_entry *de; + int f; + + de = PROC_I(inode)->pde; + f = seq_open(file, &rcutrace_op); + if (file->private_data) + ((struct seq_file *)(file->private_data))->private = de->data; + return f; +} +static struct file_operations proc_rcutrace_operations = { + .open = rcutrace_open, + .read = seq_read, + .llseek = seq_lseek, + .release = seq_release, +}; + +static ssize_t rtstart_read(struct file *file, char *buf, size_t count, + loff_t *ppos) +{ + char c; + + if (count) { + c = rcu_trace + '0'; + if (put_user(c, buf)) + return -EFAULT; + } + return count; +} + +static ssize_t rtstart_write(struct file *file, const char *buf, size_t count, + loff_t *ppos) +{ + char c; + + if (count) { + if (get_user(c, buf)) + return -EFAULT; + if (c >= '0' && c <= '9') + rcu_trace = c - '0'; + rcu_trace_start = jiffies; + memset(rcu_trace_buf, 0, sizeof(rcu_trace_buf)); + } + return count; +} + +static struct file_operations proc_rtstart_operations = { + .read = rtstart_read, + .write = rtstart_write, +}; + +extern struct proc_dir_entry proc_root; + +static void rcu_proc_init(void) +{ + struct proc_dir_entry *entry; + char buf[8]; + int i; + + entry = proc_mkdir("rcu", &proc_root); + if (entry) { + struct proc_dir_entry *rcu_entry; + rcu_entry = create_proc_entry("stat", 0, entry); + if (rcu_entry) + rcu_entry->proc_fops = &proc_rcustat_operations; + for (i = 0; i < NR_CPUS; i++) { + if (cpu_online(i)) { + sprintf(buf, "cpu%d", i); + rcu_entry = create_proc_entry(buf, 0, entry); + if (!rcu_entry) + continue; + rcu_entry->proc_fops = &proc_rcutrace_operations; + rcu_entry->data = (void *)i; + } + } + rcu_entry = create_proc_entry("rcutrace_start", 0, entry); + if (rcu_entry) + rcu_entry->proc_fops = &proc_rtstart_operations; + } +} + +static int __init rcu_late_init(void) +{ + rcu_proc_init(); + return 0; +} + +__initcall(rcu_late_init); + +#endif + +void rcu_print_info(void) +{ + int i; + printk("RCU: curbatch=%ld maxbatch=%ld cpumask=0x%lx\n", + rcu_ctrlblk.curbatch, rcu_ctrlblk.maxbatch, + rcu_ctrlblk.rcu_cpu_mask); + for (i = 0; i < NR_CPUS; i++) { + if (!cpu_online(i)) + continue; + printk("RCU: cpu=%d requests=%ld updates=%ld\n", + i, RCU_nr_rcureqs(i), RCU_nr_rcupdates(i)); + printk("RCU: cpu=%d qsctr=%ld last_qsctr=%ld batch=%ld\n", + i, RCU_qsctr(i), RCU_last_qsctr(i), RCU_batch(i)); + printk("RCU: grace period = %lu ticks\n", rcu_grace_period(i)); + } +} +EXPORT_SYMBOL(rcu_print_info); _