linux内核printk的一些并发处理

我们知道,linux内核为了支持在各种位置都能使用printk,做了不少的工作,这篇文章简单介绍一下printk的一些并发处理。
本文基于linux内核4.19.195.
printk最终会调用到vprintk_func函数。

__printf(1, 0) int vprintk_func(const char *fmt, va_list args)
{
	/*
	 * Try to use the main logbuf even in NMI. But avoid calling console
	 * drivers that might have their own locks.
	 */
	if ((this_cpu_read(printk_context) & PRINTK_NMI_DIRECT_CONTEXT_MASK) &&
	    raw_spin_trylock(&logbuf_lock)) { //看注释,以及这里是raw_spin_trylock
		int len;

		len = vprintk_store(0, LOGLEVEL_DEFAULT, NULL, 0, fmt, args);
		raw_spin_unlock(&logbuf_lock);
		defer_console_output();
		return len;
	}

	// nmi和vprintk_safe的分支都走的printk_safe_log_store,只是传入的buffer不一样

	/* Use extra buffer in NMI when logbuf_lock is taken or in safe mode. */
	if (this_cpu_read(printk_context) & PRINTK_NMI_CONTEXT_MASK)
		return vprintk_nmi(fmt, args);

	/* Use extra buffer to prevent a recursion deadlock in safe mode. */
	if (this_cpu_read(printk_context) & PRINTK_SAFE_CONTEXT_MASK)
		return vprintk_safe(fmt, args);

	/* No obstacles. */
	return vprintk_default(fmt, args);
}

可以看到,这个函数有三个分支:vprintk_nmi、vprintk_safe、vprintk_default。其中,vprintk_default是正常走的分支,vprintk_nmi是在nmi中断中调用printk走的分支,vprintk_safe是在不安全的上下文中调用printk走的分支。下面我们主要以vprintk_nmi为例分析。
我们知道,printk最终会将输出信息保存在一个buffer中。如果多核同时调用printk,则最简单的情况,都走到vprintk_default分支,最终是由logbuf_lock_irqsave以及logbuf_unlock_irqrestore来完成并发处理的。

#define logbuf_lock_irqsave(flags)			\
	do {						\
		printk_safe_enter_irqsave(flags);	\
		raw_spin_lock(&logbuf_lock);		\
	} while (0)
asmlinkage int vprintk_emit(int facility, int level,
			    const char *dict, size_t dictlen,
			    const char *fmt, va_list args)
{
	****
	/* This stops the holder of console_sem just where we want him */
	logbuf_lock_irqsave(flags);
	curr_log_seq = log_next_seq;
	printed_len = vprintk_store(facility, level, dict, dictlen, fmt, args);
	pending_output = (curr_log_seq != log_next_seq);
	logbuf_unlock_irqrestore(flags);
	*****
}

可以看到,这里为了做好并发处理,使用了关中断以及spin_lock实现的。我们知道nmi中断是无法被屏蔽掉的,那么我们如果在nmi中断中使用printk时,怎么保证并发安全呢?

static __printf(1, 0) int vprintk_nmi(const char *fmt, va_list args)
{
	struct printk_safe_seq_buf *s = this_cpu_ptr(&nmi_print_seq);

	return printk_safe_log_store(s, fmt, args);
}
static __printf(2, 0) int printk_safe_log_store(struct printk_safe_seq_buf *s,
						const char *fmt, va_list args)
{
	int add;
	size_t len;
	va_list ap;

again:
	len = atomic_read(&s->len);

	/* The trailing '\0' is not counted into len. */
	if (len >= sizeof(s->buffer) - 1) {
		atomic_inc(&s->message_lost);
		queue_flush_work(s);
		return 0;
	}

	/*
	 * Make sure that all old data have been read before the buffer
	 * was reset. This is not needed when we just append data.
	 */
	if (!len)
		smp_rmb();

	va_copy(ap, args);
	add = vscnprintf(s->buffer + len, sizeof(s->buffer) - len, fmt, ap);
	va_end(ap);
	if (!add)
		return 0;

	/*
	 * Do it once again if the buffer has been flushed in the meantime.
	 * Note that atomic_cmpxchg() is an implicit memory barrier that
	 * makes sure that the data were written before updating s->len.
	 */
	if (atomic_cmpxchg(&s->len, len, len + add) != len)
		goto again;

	queue_flush_work(s);
	return add;
}

通过代码可以看到,nmi中断并没有直接把printk要打印的东西输出到全局的buffer中,而是通过将内容输出到一个percpu的buffer—nmi_print_seq中,然后调用queue_flush_work(),利用irq_work机制把输出的内容memcpy到全局的buffer中,从而支持了nmi中断中使用printk,具体的memcpy动作在work函数__printk_safe_flush()中完成。
此外,printk的基本原理,可以参考https://github.com/kaka555/KAKAOS/blob/master/C/ubuntu/src/kernel/OS_LIB/myMicroLIB.c中函数ka_printf()的实现

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值