Re: [PATCH] printk: inject caller information into the body of message
From: Tetsuo Handa
Date: Wed Oct 10 2018 - 06:15:10 EST
On 2018/10/10 6:19, Tetsuo Handa wrote:
> Do you think that adding cmpxchg() & retry logic to this API generates better
> result than simple fallback? buffered_printk() does not add a new locking dependency
> is a good point of this API. Showing the backtrace (by enabling a debug kernel config
> option for this API) will be sufficient.
>
This is an idea for reporting out of buffers event. I would add a kernel config
option for whether to report that event. Maybe I should offload the reporting
to a workqueue context, for reporting from get_printk_context() requires that
get_printk_context() callers have to be printk()-safe, and
get_printk_context() callers might be already in vprintk_safe()/vprintk_nmi()
context even if it is possible to call printk().
include/linux/printk.h | 71 +++++++++++++++++
kernel/printk/printk.c | 201 +++++++++++++++++++++++++++++++++++++++++++++++++
2 files changed, 272 insertions(+)
diff --git a/include/linux/printk.h b/include/linux/printk.h
index cf3eccf..bcccf1f 100644
--- a/include/linux/printk.h
+++ b/include/linux/printk.h
@@ -173,6 +173,36 @@ int printk_emit(int facility, int level,
asmlinkage __printf(1, 2) __cold
int printk(const char *fmt, ...);
+/*
+ * A structure for line-buffering printk() output.
+ */
+struct printk_buffer {
+ unsigned short int used; /* Valid bytes in buf[]. */
+ char buf[1024 - 32]; /* This is LOG_LINE_MAX bytes. */
+};
+/*
+ * A macro for allowing "struct printk_buffer" on stack or in .bss section.
+ *
+ * You can use this macro for allocation on stack only when you are sure that
+ * that location is never tight about stack usage, for e.g. interrupt might
+ * consume some stack from that location. You can use this macro for allocation
+ * in .bss section only when you are sure that access to this variable is
+ * appropriately serialized, for concurrent access to this variable can lead to
+ * memory corruption.
+ *
+ * If you are not sure, you should use get_printk_buffer()/put_printk_buffer()
+ * instead. You don't need to check for get_printk_buffer() == NULL, for
+ * buffered_printk()/buffered_vprintk() will fallback to printk()/vprintk()
+ * in that case.
+ */
+#define DEFINE_PRINTK_BUFFER(name) struct printk_buffer name = { }
+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);
/*
* Special printk facility for scheduler/timekeeping use only, _DO_NOT_USE_ !
@@ -220,6 +250,30 @@ int printk(const char *s, ...)
{
return 0;
}
+struct printk_buffer {
+ char dummy; /* Not used. */
+};
+#define DEFINE_PRINTK_BUFFER(name) struct printk_buffer name
+static inline struct printk_buffer *get_printk_buffer(void)
+{
+ return NULL;
+}
+static inline __printf(2, 3)
+int buffered_printk(struct printk_buffer *ptr, const char *fmt, ...)
+{
+ return 0;
+}
+static inline __printf(2, 0)
+int buffered_vprintk(struct printk_buffer *ptr, const char *fmt, va_list args)
+{
+ return 0;
+}
+static inline void flush_printk_buffer(struct printk_buffer *ptr)
+{
+}
+static inline void put_printk_buffer(struct printk_buffer *ptr)
+{
+}
static inline __printf(1, 2) __cold
int printk_deferred(const char *s, ...)
{
@@ -300,19 +354,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__)
/*
* Like KERN_CONT, pr_cont() should only be used when continuing
* a line with no newline ('\n') enclosed. Otherwise it defaults
@@ -320,6 +389,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/kernel/printk/printk.c b/kernel/printk/printk.c
index 9bf5404..453db95 100644
--- a/kernel/printk/printk.c
+++ b/kernel/printk/printk.c
@@ -1949,6 +1949,207 @@ asmlinkage int printk_emit(int facility, int level,
}
EXPORT_SYMBOL(printk_emit);
+#define MAX_PRINTK_BUFFERS 16
+static struct printk_buffer printk_buffers[MAX_PRINTK_BUFFERS];
+static DECLARE_BITMAP(printk_buffers_in_use, MAX_PRINTK_BUFFERS);
+
+/**
+ * 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)
+{
+#ifdef CONFIG_STACKTRACE
+ static unsigned long trace_entries[MAX_PRINTK_BUFFERS][20];
+ static struct stack_trace trace[MAX_PRINTK_BUFFERS];
+ static unsigned long stamp[MAX_PRINTK_BUFFERS];
+ static int reported;
+#endif
+ long i;
+
+ for (i = 0; i < MAX_PRINTK_BUFFERS; i++) {
+ if (test_bit(i, printk_buffers_in_use) ||
+ test_and_set_bit(i, printk_buffers_in_use))
+ continue;
+ printk_buffers[i].used = 0;
+#ifdef CONFIG_STACKTRACE
+ if (!reported) {
+ stamp[i] = jiffies;
+ trace[i].nr_entries = 0;
+ trace[i].entries = trace_entries[i];
+ trace[i].max_entries = 20;
+ trace[i].skip = 0;
+ save_stack_trace(&trace[i]);
+ }
+#endif
+ return &printk_buffers[i];
+ }
+#ifdef CONFIG_STACKTRACE
+ if (!cmpxchg(&reported, 0, 1)) {
+ /*
+ * Report who is reserving the buffers, for it might be due to
+ * missing put_printk_buffer() calls.
+ *
+ * Note that this report is racy.
+ * Someone might be about to call put_printk_buffer().
+ * Someone might be about to set stamp[i] to jiffies.
+ * Someone might have just set trace[i].nr_entries to 0.
+ * But it does not worth introducing a lock dependency.
+ */
+ pr_info("printk: All buffers are in use. Falling back to unbuffered mode.\n");
+ for (i = 0; i < MAX_PRINTK_BUFFERS; i++) {
+ unsigned int j;
+
+ if (!test_bit(i, printk_buffers_in_use))
+ continue;
+ pr_info("buffer[%lu] was reserved %lu jiffies ago by\n",
+ i, jiffies - stamp[i]);
+ for (j = 0; j < trace[i].nr_entries; j++)
+ pr_info(" %pS\n", (void *)trace[i].entries[j]);
+ }
+ }
+#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;
+
+ BUILD_BUG_ON(sizeof(ptr->buf) != LOG_LINE_MAX);
+ 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;
+ } 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);
+ }
+ unbuffered:
+ return vprintk(fmt, args);
+}
+
+/**
+ * buffered_printk - Try to printk() in line buffered mode.
+ *
+ * @ptr: Pointer to "struct printk_buffer". It can be NULL.
+ * @fmt: printk() format string, followed by arguments.
+ *
+ * Returns the return value of printk().
+ *
+ * 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_printk(struct printk_buffer *ptr, const char *fmt, ...)
+{
+ va_list args;
+ int r;
+
+ va_start(args, fmt);
+ r = buffered_vprintk(ptr, fmt, args);
+ va_end(args);
+ return r;
+}
+EXPORT_SYMBOL(buffered_printk);
+
+/**
+ * flush_printk_buffer - Flush incomplete line in printk_buffer.
+ *
+ * @ptr: Pointer to "struct printk_buffer". It can be NULL.
+ *
+ * Returns nothing.
+ *
+ * Flush if @ptr contains partial data. But usually there is no need to call
+ * this function because @ptr is flushed by put_printk_buffer().
+ */
+void flush_printk_buffer(struct printk_buffer *ptr)
+{
+ if (!ptr || !ptr->used)
+ return;
+ /* buffered_vprintk() keeps 0 <= ptr->used < sizeof(ptr->buf) true. */
+ ptr->buf[ptr->used] = '\0';
+ printk("%s", ptr->buf);
+ ptr->used = 0;
+}
+EXPORT_SYMBOL(flush_printk_buffer);
+
+/**
+ * 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 >= MAX_PRINTK_BUFFERS)
+ return;
+ 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);
+}
+EXPORT_SYMBOL(put_printk_buffer);
+
int vprintk_default(const char *fmt, va_list args)
{
int r;
--
1.8.3.1