lkml.org 
[lkml]   [2018]   [Oct]   [16]   [last100]   RSS Feed
Views: [wrap][no wrap]   [headers]  [forward] 
 
Messages in this thread
/
Date
From
SubjectRe: [PATCH v4] printk: Add line-buffered printk() API.
On Sat 2018-10-13 13:39:40, Tetsuo Handa wrote:
> Sometimes we want to print a whole line without being disturbed by
> concurrent printk() from interrupts and/or other threads, for printk()
> which does not end with '\n' can be disturbed.
>
> Mixed printk() output makes it hard to interpret. Assuming that we will go
> to a direction that we allow prefixing context identifier to each line of
> printk() output (so that we can group multiple lines into one block when
> parsing), this patch introduces API for line-buffered printk() output
> (so that we can make sure that printk() ends with '\n').
>
> Since functions introduced by this patch are merely wrapping
> printk()/vprintk() calls in order to minimize possibility of using
> "struct cont", it is safe to replace printk()/vprintk() with this API.

> diff --git a/include/linux/printk.h b/include/linux/printk.h
> index cf3eccf..f93d9c8 100644
> --- a/include/linux/printk.h
> +++ b/include/linux/printk.h
> @@ -286,6 +286,30 @@ static inline void printk_safe_flush_on_panic(void)
> }
> #endif
>
> +struct printk_buffer;
> +#if defined(CONFIG_PRINTK_LINE_BUFFERED)
> +struct printk_buffer *get_printk_buffer(void);
> +void flush_printk_buffer(struct printk_buffer *ptr);
> +__printf(2, 3)
> +int buffered_printk(struct printk_buffer *ptr, const char *fmt, ...);
> +__printf(2, 0)
> +int buffered_vprintk(struct printk_buffer *ptr, const char *fmt, va_list args);
> +void put_printk_buffer(struct printk_buffer *ptr);
> +#else
> +static inline struct printk_buffer *get_printk_buffer(void)
> +{
> + return NULL;
> +}
> +static inline void flush_printk_buffer(struct printk_buffer *ptr)
> +{
> +}
> +#define buffered_printk(ptr, fmt, ...) printk(fmt, ##__VA_ARGS__)
> +#define buffered_vprintk(ptr, fmt, args) vprintk(fmt, args)
> +static inline void put_printk_buffer(struct printk_buffer *ptr)
> +{
> +}
> +#endif

Is there any reason to allow to disable this feature?
The current cont buffer is always enabled.

> extern int kptr_restrict;
>
> #ifndef pr_fmt
> @@ -300,19 +324,34 @@ static inline void printk_safe_flush_on_panic(void)
> */
> #define pr_emerg(fmt, ...) \
> printk(KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_emerg(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_EMERG pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_alert(fmt, ...) \
> printk(KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_alert(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_ALERT pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_crit(fmt, ...) \
> printk(KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_crit(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_CRIT pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_err(fmt, ...) \
> printk(KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_err(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_ERR pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_warning(fmt, ...) \
> printk(KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_warning(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_WARNING pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_warn pr_warning
> +#define bpr_warn bpr_warning
> #define pr_notice(fmt, ...) \
> printk(KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_notice(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_NOTICE pr_fmt(fmt), ##__VA_ARGS__)
> #define pr_info(fmt, ...) \
> printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)
> +#define bpr_info(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

Nitpick: Please, put bpr_* definitions into a separate paragraph.
The above is very hard to read.

> /*
> * Like KERN_CONT, pr_cont() should only be used when continuing
> * a line with no newline ('\n') enclosed. Otherwise it defaults
> @@ -320,6 +359,8 @@ static inline void printk_safe_flush_on_panic(void)
> */
> #define pr_cont(fmt, ...) \
> printk(KERN_CONT fmt, ##__VA_ARGS__)
> +#define bpr_cont(ptr, fmt, ...) \
> + buffered_printk(ptr, KERN_CONT fmt, ##__VA_ARGS__)
>
> /* pr_devel() should produce zero code unless DEBUG is defined */
> #ifdef DEBUG
> diff --git a/init/Kconfig b/init/Kconfig
> index 1e234e2..1fb01de 100644
> --- a/init/Kconfig
> +++ b/init/Kconfig
> @@ -604,6 +604,37 @@ config PRINTK_SAFE_LOG_BUF_SHIFT
> 13 => 8 KB for each CPU
> 12 => 4 KB for each CPU
>
> +config PRINTK_LINE_BUFFERED
> + bool "Allow line buffered printk()"
> + default y
> + depends on PRINTK
> + help
> + The line buffered printk() tries to buffer printk() output up to '\n'
> + so that incomplete lines won't be mixed when there are multiple
> + threads concurrently calling printk() which does not end with '\n'.

I would prefer to always enable it.


> +config PRINTK_NUM_LINE_BUFFERS
> + int "Number of buffers for line buffered printk()"
> + range 1 4096
> + default 16
> + depends on PRINTK_LINE_BUFFERED
> + help
> + Specify the number of statically preallocated "struct printk_buffer"
> + for line buffered printk(). You don't need to specify a large number
> + here because "struct printk_buffer" makes difference only when there
> + are multiple threads concurrently calling printk() which does not end
> + with '\n', and line buffered printk() will fallback to normal
> + printk() when out of statically preallocated "struct printk_buffer"
> + happened.

I would prefer to start with a hard-coded number. There is a sane
fallback. We need to motivate people to send feedback so that we could
tune it and eventually remove the fallback (current cont buffer code).


> +config PRINTK_REPORT_OUT_OF_LINE_BUFFERS
> + bool "Report out of buffers for line buffered printk()"
> + default n
> + depends on PRINTK_LINE_BUFFERED && STACKTRACE
> + help
> + Select this if you want to know who is using statically preallocated
> + "struct printk_buffer" when out of "struct printk_buffer" happened.
> +

I like this approach with the configurable debug functionality. It is
safe and straightforward.

Just please, move it into a separate patch. It would help a lot
with review and discussion.

Nitpick: I suggest to make it a bit shorter, e.g. DEBUG_BUFFERED_PRINTK.


> #
> # Architectures with an unreliable sched_clock() should select this:
> #
> diff --git a/kernel/printk/printk.c b/kernel/printk/printk.c
> index 9bf5404..afc8bed 100644
> --- a/kernel/printk/printk.c
> +++ b/kernel/printk/printk.c
> @@ -1949,6 +1949,245 @@ asmlinkage int printk_emit(int facility, int level,
> }
> EXPORT_SYMBOL(printk_emit);
>
> +#ifdef CONFIG_PRINTK_LINE_BUFFERED
> +/*
> + * A structure for line-buffered printk() output.
> + */
> +static struct printk_buffer {
> + unsigned short int used; /* Valid bytes in buf[]. */
> + char buf[LOG_LINE_MAX];
> +} printk_buffers[CONFIG_PRINTK_NUM_LINE_BUFFERS] __aligned(1024);
> +static DECLARE_BITMAP(printk_buffers_in_use, CONFIG_PRINTK_NUM_LINE_BUFFERS);
> +
> +
> +#ifdef CONFIG_PRINTK_REPORT_OUT_OF_LINE_BUFFERS
> +static struct {
> + unsigned long stamp;
> + struct stack_trace trace;
> + unsigned long entries[20];
> +} printk_buffers_dump[CONFIG_PRINTK_NUM_LINE_BUFFERS];
> +static int buffer_users_report_scheduled;

[...]
> +/**
> + * get_printk_buffer - Try to get printk_buffer.
> + *
> + * Returns pointer to "struct printk_buffer" on success, NULL otherwise.
> + *
> + * If this function returned "struct printk_buffer", the caller is responsible
> + * for passing it to put_printk_buffer() so that "struct printk_buffer" can be
> + * reused in the future.
> + *
> + * Even if this function returned NULL, the caller does not need to check for
> + * NULL, for passing NULL to buffered_printk() simply acts like normal printk()
> + * and passing NULL to flush_printk_buffer()/put_printk_buffer() is a no-op.
> + */
> +struct printk_buffer *get_printk_buffer(void)
> +{

It does not make much sense to use the buffered printk in context
where printk_safe() or printk_nmi() is used. I suggest to define
something like:

static inline bool in_printk_safe_buffered_context(void)
{
int this_printk_context = this_cpu_read(printk_context);

if (this_printk_context && PRINTK_NMI_DIRECT_CONTEXT_MASK)
return false;

if (this_printk_context &&
(PRINTK_SAFE_CONTEXT_MASH || PRINTK_NMI_CONTEXT_MASK))
return true;

return true;

and do

/*
* Messages are already concatenated when temporary
* stored into the safe per-CPU buffers.
*/
if (in_printk_safe_buffered_context())
return NULL;

> +#ifdef CONFIG_PRINTK_REPORT_OUT_OF_LINE_BUFFERS
> + static DECLARE_WORK(work, report_buffer_users);
> +#endif
> + long i;
> +
> + for (i = 0; i < CONFIG_PRINTK_NUM_LINE_BUFFERS; i++) {
> + if (test_bit(i, printk_buffers_in_use) ||
> + test_and_set_bit(i, printk_buffers_in_use))

I would use test_and_set_bit_lock() to make it more likely that
the barriers are right. Otherwise, there is missing the complementary
barrier with clear_bit() in put_printk_buffer().


> + continue;
> + printk_buffers[i].used = 0;
> +#ifdef CONFIG_PRINTK_REPORT_OUT_OF_LINE_BUFFERS
> + printk_buffers_dump[i].stamp = jiffies;
> + printk_buffers_dump[i].trace.nr_entries = 0;
> + printk_buffers_dump[i].trace.entries =
> + printk_buffers_dump[i].entries;
> + printk_buffers_dump[i].trace.max_entries = 20;

#define PRINTK_BUFFERS_MAX_TRACE_ENTRIES 20


> + printk_buffers_dump[i].trace.skip = 0;
> + save_stack_trace(&printk_buffers_dump[i].trace);
> +#endif

Please, put this into a helper function to avoid bloating
get_printk_buffer().


> + return &printk_buffers[i];
> + }

> +#ifdef CONFIG_PRINTK_REPORT_OUT_OF_LINE_BUFFERS
> + /*
> + * Oops, out of "struct printk_buffer" happened. Fallback to normal
> + * printk(). You might notice it by partial lines being printed.
> + *
> + * If you think that it might be due to missing put_printk_buffer()
> + * calls, you can enable CONFIG_PRINTK_REPORT_OUT_OF_LINE_BUFFERS.
> + * Then, who is using the buffers will be reported (from workqueue
> + * context because reporting CONFIG_PRINTK_NUM_LINE_BUFFERS entries
> + * from atomic context might be too slow). If it does not look like
> + * missing put_printk_buffer() calls, you might want to increase
> + * CONFIG_PRINTK_NUM_LINE_BUFFERS.
> + *
> + * But if it turns out that allocating "struct printk_buffer" on stack
> + * or in .bss section or from kzalloc() is more suitable than tuning
> + * CONFIG_PRINTK_NUM_LINE_BUFFERS, we can update to do so.
> + */
> + if (!in_nmi() && !cmpxchg(&buffer_users_report_scheduled, 0, 1))
> + queue_work(system_unbound_wq, &work);

We should limit the number of this reports especially when the buffers
leaked and are never released again. I would either limit the total
count of these reports or I would allow scheduling only when
any get_printk_buffer() succeeded in the meantime.

Also we should know when the debugging makes sense. Therefore, we should
write something even when the debugging is off. Something like:

#else
printk_once("Out of printk buffers. Please, consider enabling with CONFIG_DEBUG_BUFFERED_PRINTK\n");
#endif

> + return NULL;
> +}
> +EXPORT_SYMBOL(get_printk_buffer);
> +
> +/**
> + * buffered_vprintk - Try to vprintk() in line buffered mode.
> + *
> + * @ptr: Pointer to "struct printk_buffer". It can be NULL.
> + * @fmt: printk() format string.
> + * @args: va_list structure.
> + *
> + * Returns the return value of vprintk().
> + *
> + * Try to store to @ptr first. If it fails, flush @ptr and then try to store to
> + * @ptr again. If it still fails, use unbuffered printing.
> + */
> +int buffered_vprintk(struct printk_buffer *ptr, const char *fmt, va_list args)
> +{
> + va_list tmp_args;
> + unsigned short int i;
> + int r;
> +
> + if (!ptr)
> + goto unbuffered;
> + for (i = 0; i < 2; i++) {

> + unsigned int pos = ptr->used;
> + char *text = ptr->buf + pos;
> +
> + va_copy(tmp_args, args);
> + r = vsnprintf(text, sizeof(ptr->buf) - pos, fmt, tmp_args);
> + va_end(tmp_args);
> + if (r + pos < sizeof(ptr->buf)) {
> + /*
> + * Eliminate KERN_CONT at this point because we can
> + * concatenate incomplete lines inside printk_buffer.
> + */
> + if (r >= 2 && printk_get_level(text) == 'c') {
> + memmove(text, text + 2, r - 2);
> + ptr->used += r - 2;

I believe that KERN_CONT is always passed via @fmt parameter. Therefore
we could skip it already in fmt and avoid this memmove. Also note that
printk_get_level() is safe even for empty string. The following
should work:

if (printk_get_level(fmt) == 'c')
fmt += 2;

> + } else {
> + ptr->used += r;
> + }
> + /* Flush already completed lines if any. */
> + while (1) {
> + char *cp = memchr(ptr->buf, '\n', ptr->used);
> +
> + if (!cp)
> + break;
> + *cp = '\0';
> + printk("%s\n", ptr->buf);
> + i = cp - ptr->buf + 1;
> + ptr->used -= i;
> + memmove(ptr->buf, ptr->buf + i, ptr->used);
> + }
> + return r;
> + }
> + if (i)
> + break;
> + flush_printk_buffer(ptr);

It makes sense to repeat the cycle only when something was flushed.
I would would modify flush_printk_buffer() to return the number of
flushed characters.

Also it might be easier to read with goto, I mean to use:

try_again: instead of for (i = 0; i < 2; i++) {

and then

if (flush_printk_buffer(ptr))
goto try_again;



> + }
> + unbuffered:
> + return vprintk(fmt, args);
> +}
> +
> +
> +/**
> + * put_printk_buffer - Release printk_buffer.
> + *
> + * @ptr: Pointer to "struct printk_buffer". It can be NULL.
> + *
> + * Returns nothing.
> + *
> + * Flush and release @ptr.
> + */
> +void put_printk_buffer(struct printk_buffer *ptr)
> +{
> + long i = ptr - printk_buffers;
> +
> + if (!ptr || i < 0 || i >= CONFIG_PRINTK_NUM_LINE_BUFFERS)
> + return;

It would deserve a warning when i is out of bounds.


> + if (ptr->used)
> + flush_printk_buffer(ptr);
> + /* Make sure in_use flag is cleared after setting ptr->used = 0. */
> + wmb();
> + clear_bit(i, printk_buffers_in_use);

I would replace this with clear_bit_lock(). It would do the barrier
correctly.

In each case, the comment in arch/x86/include/asm/bitops.h above
clear_bit() talks about smp_mb__before_atomic() and/or
smp_mb__after_atomic().

Finally, I would prefer to put the buffered_printk code into a
separate source file, e.g. kernel/printk/buffered_printk.c.

Best Regards,
Petr

\
 
 \ /
  Last update: 2018-10-16 16:08    [W:0.050 / U:0.272 seconds]
©2003-2020 Jasper Spaans|hosted at Digital Ocean and TransIP|Read the blog|Advertise on this site