linux内核printk的一些并发处理
创始人
2024-03-14 15:24:37
0

我们知道,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_trylockint 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()的实现

相关内容

热门资讯

监控摄像头接入GB28181平... 流程简介将监控摄像头的视频在网站和APP中直播,要解决的几个问题是:1&...
Windows10添加群晖磁盘... 在使用群晖NAS时,我们需要通过本地映射的方式把NAS映射成本地的一块磁盘使用。 通过...
protocol buffer... 目录 目录 什么是protocol buffer 1.protobuf 1.1安装  1.2使用...
在Word、WPS中插入AxM... 引言 我最近需要写一些文章,在排版时发现AxMath插入的公式竟然会导致行间距异常&#...
Fluent中创建监测点 1 概述某些仿真问题,需要创建监测点,用于获取空间定点的数据࿰...
educoder数据结构与算法...                                                   ...
MySQL下载和安装(Wind... 前言:刚换了一台电脑,里面所有东西都需要重新配置,习惯了所...
MFC文件操作  MFC提供了一个文件操作的基类CFile,这个类提供了一个没有缓存的二进制格式的磁盘...
有效的括号 一、题目 给定一个只包括 '(',')','{','}'...
【Ctfer训练计划】——(三... 作者名:Demo不是emo  主页面链接:主页传送门 创作初心ÿ...