[PATCH 3/4] printk: introduce console_unlock_for_printk()

From: Sergey Senozhatsky
Date: Wed Mar 09 2016 - 01:10:02 EST


Even though we already have asynchronous printk()->vprintk_emit(),
there are still good chances to get lockups, because we don't have
asynchronous console_unlock(). So any process doing console_lock()
and console_unlock() will end up looping in console_unlock(), pushing
the messages to console drivers (possibly with IRQs or preemption
disabled), regardless the fact that we have a dedicated kthread for
that particular job.

Apart from that, console_lock()/console_unlock() can be executed by
user processes as a part of system calls:

a) SyS_open()
...
chrdev_open()
tty_open()
console_device()
console_lock()
console_unlock()
for (;;) {
call_console_drivers()
}

b) SyS_read()
...
sysfs_read_file()
dev_attr_show()
show_cons_active()
console_lock()
console_unlock()
for (;;) {
call_console_drivers()
}

c) doing `cat /proc/consoles`
SyS_read()
vfs_read()
proc_reg_read()
seq_read()
c_stop()
console_unlock()
for (;;) {
call_console_drivers()
}

etc.

This can add unnecessary latencies to the user space processes.

This patch splits console_unlock() in two parts:
-- the fast path up() console semaphore and wake up printing kthread
(if there is one, of course), otherwise
-- the slow path: does what console_unlock() did previously, emit
the messages and then up() console semaphore

The actual printing loop is, thus, moved to a new function,
console_unlock_for_printk(). There are 3 places that
unconditionally call it:
a) direct printing from vprintk_emit()
b) console_flush_on_panic()
c) printing kthread callback

Signed-off-by: Sergey Senozhatsky <sergey.senozhatsky@xxxxxxxxx>
---
kernel/printk/printk.c | 51 +++++++++++++++++++++++++++++++++++++++++++-------
1 file changed, 44 insertions(+), 7 deletions(-)

diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
index de45d86..ddaf62e 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -303,6 +303,8 @@ static struct task_struct *printk_thread;
/* Wait for printing wakeups from async vprintk_emit() */
static DECLARE_WAIT_QUEUE_HEAD(printing_wait);

+static void console_unlock_for_printk(void);
+
static int printing_func(void *data)
{
while (1) {
@@ -314,7 +316,7 @@ static int printing_func(void *data)
remove_wait_queue(&printing_wait, &wait);

console_lock();
- console_unlock();
+ console_unlock_for_printk();
}

return 0;
@@ -1900,7 +1902,7 @@ asmlinkage int vprintk_emit(int facility, int level,
* /dev/kmsg and syslog() users.
*/
if (console_trylock())
- console_unlock();
+ console_unlock_for_printk();
lockdep_on();
}

@@ -2339,20 +2341,20 @@ out:
#define PRINT_MSGS_BEFORE_OOPS 100

/**
- * console_unlock - unlock the console system
+ * console_unlock_for_printk - unlock the console system
*
* Releases the console_lock which the caller holds on the console system
* and the console driver list.
*
* While the console_lock was held, console output may have been buffered
- * by printk(). If this is the case, console_unlock(); emits
+ * by printk(). If this is the case, console_unlock_for_printk() emits
* the output prior to releasing the lock.
*
* If there is output waiting, we wake /dev/kmsg and syslog() users.
*
- * console_unlock(); may be called from any context.
+ * console_unlock_for_printk() may be called from any context.
*/
-void console_unlock(void)
+static void console_unlock_for_printk(void)
{
static char ext_text[CONSOLE_EXT_LOG_MAX];
static char text[LOG_LINE_MAX + PREFIX_MAX];
@@ -2511,6 +2513,41 @@ skip:
if (wake_klogd)
wake_up_klogd();
}
+
+
+/**
+ * console_unlock - unlock the console system
+ *
+ * Releases the console_lock which the caller holds on the console system.
+ *
+ * The fast path is to wake up the printing kthread (if the system can
+ * perform asynchronous printing) and return; the slow path is to emit
+ * the messages directly invoking console_unlock_for_printk().
+ *
+ * console_unlock() may be called from any context.
+ */
+void console_unlock(void)
+{
+ bool in_panic = console_loglevel == CONSOLE_LOGLEVEL_MOTORMOUTH;
+
+ if (in_panic) {
+ /*
+ * If the system is in panic console_flush_on_panic() issued
+ * from panic_cpu will flush the messages.
+ */
+ console_locked = 0;
+ up_console_sem();
+ return;
+ }
+
+ if (!printk_sync && printk_thread) {
+ console_locked = 0;
+ up_console_sem();
+ wake_up(&printing_wait);
+ } else {
+ console_unlock_for_printk();
+ }
+}
EXPORT_SYMBOL(console_unlock);

/**
@@ -2567,7 +2604,7 @@ void console_flush_on_panic(void)
*/
console_trylock();
console_may_schedule = 0;
- console_unlock();
+ console_unlock_for_printk();
}

/*
--
2.8.0.rc0.1.gd285ab0