diff options
author | Steven Rostedt <rostedt@goodmis.org> | 2014-06-04 19:11:38 -0400 |
---|---|---|
committer | Linus Torvalds <torvalds@linux-foundation.org> | 2014-06-04 19:54:17 -0400 |
commit | 458df9fd4815b47809875d57f42e16401674b621 (patch) | |
tree | b9ccaebf9eaf1ffc70036accdabccdc021ac17f5 /kernel | |
parent | 939f04bec1a4ef6ba4370b0f34b01decc844b1b1 (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.c | 47 |
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 | */ |
234 | static DEFINE_RAW_SPINLOCK(logbuf_lock); | 239 | static 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 | ||
2540 | static DEFINE_PER_CPU(int, printk_pending); | 2557 | static DEFINE_PER_CPU(int, printk_pending); |
2541 | static DEFINE_PER_CPU(char [PRINTK_BUF_SIZE], printk_sched_buf); | ||
2542 | 2558 | ||
2543 | static void wake_up_klogd_work_func(struct irq_work *irq_work) | 2559 | static 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 | ||
2571 | int printk_sched(const char *fmt, ...) | 2588 | int 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 | } |