菜单

kaoqi
kaoqi
发布于 2026-09-20 / 8 阅读
0
0

kernel第2个函数第2段展开

具体来说就是smp_setup_processor_id函数中如下几个部分:

  • set_cpu_logical_map(0, mpidr);设置CPU逻辑地图数组变量。

  • set_my_cpu_offset(0);设置Per-CPU变量。

  • pr_info打印相关信息

建立CPU逻辑地图

所谓的CPU逻辑地图其实就是一个cpu-id编号到cpu对应MPIDR_EL1寄存器的数组变量。

在第一段中,通过MRS指令,获取并转储MIDR_EL1寄存器的值到mpidr内存中。在接下来的数组设置中,展开在/arch/arm64/include/asm/smp.h文件下:

/*
 * Logical CPU mapping.
 */
extern u64 __cpu_logical_map[NR_CPUS];
extern u64 cpu_logical_map(int cpu);

static inline void set_cpu_logical_map(int cpu, u64 hwid)
{
	__cpu_logical_map[cpu] = hwid;
}

可以看到, 这个变量最终是被一个外部声明的__cpu_logical_map变量定义的,这个变量存在于/arch/arm64/kernel/setup.c中被具体定义:

u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID };

可以看出,在原文中,此时kernel第2段还是仅仅只是追求建立一个cpu=0时的初始化,所以此时这个CPU逻辑地图(即数组中第i个cpu指向其MPIDR_EL1寄存器的描述值)只有一个组的元素。

这样,这里的含义就是获取到CPU0的mpidr寄存器的值,并建立了一个数组。

这种映射关系建立成功后,会通过cpu_logical_map[cpu-id]的方式进行对应MPIDR_EL1寄存器的值的访问。

u64 __cpu_logical_map[NR_CPUS] = { [0 ... NR_CPUS-1] = INVALID_HWID };

u64 cpu_logical_map(int cpu)
{
	return __cpu_logical_map[cpu];
}

设置Per-CPU的值

set_my_cpu_offset变量在/arch/arm64/include/asm/percpu.h文件中定义:

static inline void set_my_cpu_offset(unsigned long off)
{
	asm volatile(ALTERNATIVE("msr tpidr_el1, %0",
				 "msr tpidr_el2, %0",
				 ARM64_HAS_VIRT_HOST_EXTN)
			:: "r" (off) : "memory");
}

// 这个是相关的主函数原文:
set_my_cpu_offset(0);

汇编揭示出,这个函数的主要作用,就是将tpidr_el1、tpidr_el2寄存器的值设置为off的值,也就是0

TPIDR_EL1寄存器是用于存储当前CPU的Per-CPU数据偏移量的寄存器。这是专门用于保存Per-CPU变量的基地址指针或偏移量寄存器。

此时,还只是单纯的进行偏移量的设置,有关Per-CPU变量的具体用法,等到后面SMP框架搭起来的时候详细说。

pr_info打印函数

这是一个来自printk的函数,在/include/linux/printk.h中可以看到就是printk:

#define pr_info(fmt, ...) \
	printk(KERN_INFO pr_fmt(fmt), ##__VA_ARGS__)

这里有一个很有意思的问题:

正常情况下,kernel的启动使用earlycon命令启用对应的ttyAMA串口。但是,kernel的启动命令本身需要等到setup_arch函数的时候才会执行command的解析。也就是说,此时在smp_setup_processor_id函数中是并不会存在真正的,由printk导致的串口打印的。

printk函数本身存在于/kernel/printk/printk.c:

// 这里是/kernel/printk/printk.c的核心函数
asmlinkage __visible int printk(const char *fmt, ...)
{
	va_list args;
	int r;

	va_start(args, fmt);
	r = vprintk_func(fmt, args);
	va_end(args);

	return r;
}
EXPORT_SYMBOL(printk);

// 这里是转到vprintk_func的实现/kernel/printk/printk_safe.c
__printf(1, 0) int vprintk_func(const char *fmt, va_list args)
{
#ifdef CONFIG_KGDB_KDB
	/* Allow to pass printk() to kdb but avoid a recursion. */
	if (unlikely(kdb_trap_printk && kdb_printf_cpu < 0))
		return vkdb_printf(KDB_MSGSRC_PRINTK, fmt, args);
#endif

	/*
	 * 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)) {
		int len;

		len = vprintk_store(0, LOGLEVEL_DEFAULT, NULL, fmt, args); // 这里打印到环形缓冲区
		raw_spin_unlock(&logbuf_lock);
		defer_console_output();
		return len;
	}

	/* 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);
}

// 最终,这里是对应存储的代码
int vprintk_store(int facility, int level,
		  const struct dev_printk_info *dev_info,
		  const char *fmt, va_list args)
{
	static char textbuf[LOG_LINE_MAX];
	char *text = textbuf;
	size_t text_len;
	enum log_flags lflags = 0;

	/*
	 * The printf needs to come first; we need the syslog
	 * prefix which might be passed-in as a parameter.
	 */
	text_len = vscnprintf(text, sizeof(textbuf), fmt, args);

	/* mark and strip a trailing newline */
	if (text_len && text[text_len-1] == '\n') {
		text_len--;
		lflags |= LOG_NEWLINE;
	}

	/* strip kernel syslog prefix and extract log level or control flags */
	if (facility == 0) {
		int kern_level;

		while ((kern_level = printk_get_level(text)) != 0) {
			switch (kern_level) {
			case '0' ... '7':
				if (level == LOGLEVEL_DEFAULT)
					level = kern_level - '0';
				break;
			case 'c':	/* KERN_CONT */
				lflags |= LOG_CONT;
			}

			text_len -= 2;
			text += 2;
		}
	}

	if (level == LOGLEVEL_DEFAULT)
		level = default_message_loglevel;

	if (dev_info)
		lflags |= LOG_NEWLINE;

	return log_output(facility, level, lflags, dev_info, text, text_len);
}

可以看到,在极早期,只存在一个临时定义的static char textbuf[LOG_LINE_MAX];这样的数组,用来承接对应的打印。


评论