aboutsummaryrefslogtreecommitdiffstats
path: root/kernel
diff options
context:
space:
mode:
authorSteven Rostedt <rostedt@goodmis.org>2014-06-04 19:11:38 -0400
committerLinus Torvalds <torvalds@linux-foundation.org>2014-06-04 19:54:17 -0400
commit458df9fd4815b47809875d57f42e16401674b621 (patch)
treeb9ccaebf9eaf1ffc70036accdabccdc021ac17f5 /kernel
parent939f04bec1a4ef6ba4370b0f34b01decc844b1b1 (diff)
printk: remove separate printk_sched buffers and use printk buf instead
To prevent deadlocks with doing a printk inside the scheduler, printk_sched() was created. The issue is that printk has a console_sem that it can grab and release. The release does a wake up if there's a task pending on the sem, and this wake up grabs the rq locks that is held in the scheduler. This leads to a possible deadlock if the wake up uses the same rq as the one with the rq lock held already. What printk_sched() does is to save the printk write in a per cpu buffer and sets the PRINTK_PENDING_SCHED flag. On a timer tick, if this flag is set, the printk() is done against the buffer. There's a couple of issues with this approach. 1) If two printk_sched()s are called before the tick, the second one will overwrite the first one. 2) The temporary buffer is 512 bytes and is per cpu. This is a quite a bit of space wasted for something that is seldom used. In order to remove this, the printk_sched() can use the printk buffer instead, and delay the console_trylock()/console_unlock() to the queued work. Because printk_sched() would then be taking the logbuf_lock, the logbuf_lock must not be held while doing anything that may call into the scheduler functions, which includes wake ups. Unfortunately, printk() also has a console_sem that it uses, and on release, the up(&console_sem) may do a wake up of any pending waiters. This must be avoided while holding the logbuf_lock. Signed-off-by: Steven Rostedt <rostedt@goodmis.org> Signed-off-by: Jan Kara <jack@suse.cz> Signed-off-by: Andrew Morton <akpm@linux-foundation.org> Signed-off-by: Linus Torvalds <torvalds@linux-foundation.org>
Diffstat (limited to 'kernel')
-rw-r--r--kernel/printk/printk.c47
1 files changed, 29 insertions, 18 deletions
diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index 4e22230f1f6c..247b0c1fadfc 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -68,6 +68,9 @@ int console_printk[4] = {
68 DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */ 68 DEFAULT_CONSOLE_LOGLEVEL, /* default_console_loglevel */
69}; 69};
70 70
71/* Deferred messaged from sched code are marked by this special level */
72#define SCHED_MESSAGE_LOGLEVEL -2
73
71/* 74/*
72 * Low level drivers may need that to know if they can schedule in 75 * Low level drivers may need that to know if they can schedule in
73 * their unblank() callback or not. So let's export it. 76 * their unblank() callback or not. So let's export it.
@@ -229,7 +232,9 @@ struct printk_log {
229}; 232};
230 233
231/* 234/*
232 * The logbuf_lock protects kmsg buffer, indices, counters. 235 * The logbuf_lock protects kmsg buffer, indices, counters. This can be taken
236 * within the scheduler's rq lock. It must be released before calling
237 * console_unlock() or anything else that might wake up a process.
233 */ 238 */
234static DEFINE_RAW_SPINLOCK(logbuf_lock); 239static DEFINE_RAW_SPINLOCK(logbuf_lock);
235 240
@@ -1577,14 +1582,19 @@ asmlinkage int vprintk_emit(int facility, int level,
1577 static int recursion_bug; 1582 static int recursion_bug;
1578 static char textbuf[LOG_LINE_MAX]; 1583 static char textbuf[LOG_LINE_MAX];
1579 char *text = textbuf; 1584 char *text = textbuf;
1580 size_t text_len; 1585 size_t text_len = 0;
1581 enum log_flags lflags = 0; 1586 enum log_flags lflags = 0;
1582 unsigned long flags; 1587 unsigned long flags;
1583 int this_cpu; 1588 int this_cpu;
1584 int printed_len = 0; 1589 int printed_len = 0;
1590 bool in_sched = false;
1585 /* cpu currently holding logbuf_lock in this function */ 1591 /* cpu currently holding logbuf_lock in this function */
1586 static volatile unsigned int logbuf_cpu = UINT_MAX; 1592 static volatile unsigned int logbuf_cpu = UINT_MAX;
1587 1593
1594 if (level == SCHED_MESSAGE_LOGLEVEL) {
1595 level = -1;
1596 in_sched = true;
1597 }
1588 1598
1589 boot_delay_msec(level); 1599 boot_delay_msec(level);
1590 printk_delay(); 1600 printk_delay();
@@ -1631,7 +1641,12 @@ asmlinkage int vprintk_emit(int facility, int level,
1631 * The printf needs to come first; we need the syslog 1641 * The printf needs to come first; we need the syslog
1632 * prefix which might be passed-in as a parameter. 1642 * prefix which might be passed-in as a parameter.
1633 */ 1643 */
1634 text_len = vscnprintf(text, sizeof(textbuf), fmt, args); 1644 if (in_sched)
1645 text_len = scnprintf(text, sizeof(textbuf),
1646 KERN_WARNING "[sched_delayed] ");
1647
1648 text_len += vscnprintf(text + text_len,
1649 sizeof(textbuf) - text_len, fmt, args);
1635 1650
1636 /* mark and strip a trailing newline */ 1651 /* mark and strip a trailing newline */
1637 if (text_len && text[text_len-1] == '\n') { 1652 if (text_len && text[text_len-1] == '\n') {
@@ -1713,6 +1728,10 @@ asmlinkage int vprintk_emit(int facility, int level,
1713 lockdep_on(); 1728 lockdep_on();
1714 local_irq_restore(flags); 1729 local_irq_restore(flags);
1715 1730
1731 /* If called from the scheduler, we can not call up(). */
1732 if (in_sched)
1733 return printed_len;
1734
1716 /* 1735 /*
1717 * Disable preemption to avoid being preempted while holding 1736 * Disable preemption to avoid being preempted while holding
1718 * console_sem which would prevent anyone from printing to console 1737 * console_sem which would prevent anyone from printing to console
@@ -2532,21 +2551,19 @@ late_initcall(printk_late_init);
2532/* 2551/*
2533 * Delayed printk version, for scheduler-internal messages: 2552 * Delayed printk version, for scheduler-internal messages:
2534 */ 2553 */
2535#define PRINTK_BUF_SIZE 512
2536
2537#define PRINTK_PENDING_WAKEUP 0x01 2554#define PRINTK_PENDING_WAKEUP 0x01
2538#define PRINTK_PENDING_SCHED 0x02 2555#define PRINTK_PENDING_OUTPUT 0x02
2539 2556
2540static DEFINE_PER_CPU(int, printk_pending); 2557static DEFINE_PER_CPU(int, printk_pending);
2541static DEFINE_PER_CPU(char [PRINTK_BUF_SIZE], printk_sched_buf);
2542 2558
2543static void wake_up_klogd_work_func(struct irq_work *irq_work) 2559static void wake_up_klogd_work_func(struct irq_work *irq_work)
2544{ 2560{
2545 int pending = __this_cpu_xchg(printk_pending, 0); 2561 int pending = __this_cpu_xchg(printk_pending, 0);
2546 2562
2547 if (pending & PRINTK_PENDING_SCHED) { 2563 if (pending & PRINTK_PENDING_OUTPUT) {
2548 char *buf = __get_cpu_var(printk_sched_buf); 2564 /* If trylock fails, someone else is doing the printing */
2549 pr_warn("[sched_delayed] %s", buf); 2565 if (console_trylock())
2566 console_unlock();
2550 } 2567 }
2551 2568
2552 if (pending & PRINTK_PENDING_WAKEUP) 2569 if (pending & PRINTK_PENDING_WAKEUP)
@@ -2570,21 +2587,15 @@ void wake_up_klogd(void)
2570 2587
2571int printk_sched(const char *fmt, ...) 2588int printk_sched(const char *fmt, ...)
2572{ 2589{
2573 unsigned long flags;
2574 va_list args; 2590 va_list args;
2575 char *buf;
2576 int r; 2591 int r;
2577 2592
2578 local_irq_save(flags);
2579 buf = __get_cpu_var(printk_sched_buf);
2580
2581 va_start(args, fmt); 2593 va_start(args, fmt);
2582 r = vsnprintf(buf, PRINTK_BUF_SIZE, fmt, args); 2594 r = vprintk_emit(0, SCHED_MESSAGE_LOGLEVEL, NULL, 0, fmt, args);
2583 va_end(args); 2595 va_end(args);
2584 2596
2585 __this_cpu_or(printk_pending, PRINTK_PENDING_SCHED); 2597 __this_cpu_or(printk_pending, PRINTK_PENDING_OUTPUT);
2586 irq_work_queue(&__get_cpu_var(wake_up_klogd_work)); 2598 irq_work_queue(&__get_cpu_var(wake_up_klogd_work));
2587 local_irq_restore(flags);
2588 2599
2589 return r; 2600 return r;
2590} 2601}