概述
linux-mips启动分析 (1)
系统加电起动后,MIPS 处理器默认的程序入口是0xBFC00000,此地址在无缓存的KSEG1的地址区域内,对应的物理地址是 0x1FC00000,即CPU从0x1FC00000开始取第一条指令,这个地址在硬件上已经确定为FLASH的位置,Bootloader将 Linux 内核映像拷贝到 RAM 中某个空闲地址处,然后一般有个内存移动操作,目的地址在 arch/mips/Makefile 内指定:
load-$(CONFIG_MIPS_PB1550) += 0xFFFFFFFF80100000,
则最终bootloader定会将内核移到物理地址 0x00100000 处。
上面Makefile 里指定的的 load 地址,最后会被编译系统写入到 arch/mips/kernel/vmlinux.lds 中:
OUTPUT_ARCH(mips)
ENTRY(kernel_entry)
jiffies = jiffies_64;
SECTIONS
{
. = 0xFFFFFFFF80100000;
/* read-only */
_text = .; /* Text and read-only data */
.text : {
*(.text)
...
这个文件最终会以参数 -Xlinker --script -Xlinker vmlinux.lds 的形式传给 gcc,并最终传给链接器 ld 来控制其行为。
ld 会将 .text 节的地址链接到 0xFFFFFFFF80100000 处。
关于内核 ELF 文件的入口地址(Entry point),即 bootloader 移动完内核后,直接跳转到的地址,由ld 写入 ELF的头中,其会依次用下面的方法尝试设置入口点,当遇到成功时则停止:
a. 命令行选项 -e entry
b. 脚本中的 ENTRY(symbol)
c. 如果有定义 start 符号,则使用start符号(symbol)
d. 如果存在 .text 节,则使用第一个字节的地址。
e. 地址0
注意到上面的 ld script 中,用 ENTRY 宏设置了内核的 entry point 是 kernel_entry,因此内核取得控制权后执行的第一条指令是在 kernel_entry 处。
*********************************************
linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。
而此处正是内核入口函数kernel_entry(),该函数定义在 /arch/mips/kernel/head.s文件里。
kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备,
接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零,
最后跳转到 /init/main.c 中的 start_kernel()初始化硬件平台相关的代码。
*********************************************
NESTED(kernel_entry, 16, sp) # kernel entry point
声明函数 kernel_entry,函数的堆栈为 16 byte,返回地址保存在 $sp 寄存器中。
-----------------------------
声明函数入口
#define NESTED(symbol, framesize, rpc)
.globl symbol;
.align 2;
.type symbol,@function;
.ent symbol,0;
symbol: .frame sp, framesize, rpc
汇编伪指令 frame 用来声明堆栈布局。
它有三个参数:
1)第一个参数 framereg:声明用于访问局部堆栈的寄存器,一般为 $sp。
2)第二个参数 framesize:申明该函数已分配堆栈的大小,应该符合 $sp + framesize = 原来的 $sp。
3)第三个参数 returnreg:这个寄存器用来保存返回地址。
----------------------------
kernel_entry_setup # cpu specific setup
----------------------------
这个宏一般为空的,在 include/asm-mips/mach-generic/kernel-entry-init.h 文件中定义。
某些MIPS CPU需要额外的设置一些控制寄
存器,和具体的平台相关,一般为空宏;某些多核MIPS,启动时所
有的core的入口一起指向 kernel_entry,然后在该宏里分叉,
boot core 继续往下,其它的则不停的判断循环,直到boot core 唤醒之
----------------------------
setup_c0_status_pri
设置 cp0_status 寄存器
----------------------------
.macro setup_c0_status_pri
#ifdef CONFIG_64BIT
setup_c0_status ST0_KX 0
#else
setup_c0_status 0 0
#endif
.endm
----------------------------
ARC64_TWIDDLE_PC
除非 CONFIG_ARC64,否则为空操作
-----------------------------
#ifdef CONFIG_MIPS_MT_SMTC
mtc0 zero, CP0_TCCONTEXT__bss_start
mfc0 t0, CP0_STATUS
ori t0, t0, 0xff1f
xori t0, t0, 0x001e
mtc0 t0, CP0_STATUS
#endif /* CONFIG_MIPS_MT_SMTC */
宏定义 CONFIG_MIPS_MT_SMTC 是使用多核的 SMTC Linux 时定义的。一般情况下不考虑。
MIPS已经开发出 SMP Linux的改进版,叫做SMTC(线程上下文对称多处理) Linux。
SMTC Linux能理解轻量级 TC 的概念,并能因此减少某些与SMP Linux相关的开销。
----------------------------
PTR_LA t0, __bss_start # clear .bss
LONG_S zero, (t0)
PTR_LA t1, __bss_stop - LONGSIZE
1:
PTR_ADDIU t0, LONGSIZE
LONG_S zero, (t0)
bne t0, t1, 1b
清除 BSS 段,清 0。
变量 __bss_start 和 __bss_stop 在连接文件arch/mips/kernel/vmlinux.lds 中定义。
--------------------------------
LONG_S a0, fw_arg0 # firmware arguments
LONG_S a1, fw_arg1
LONG_S a2, fw_arg2
LONG_S a3, fw_arg3
把 bootloader 传递给内核的启动参数保存在 fw_arg0,fw_arg1,fw_arg2,fw_arg3 变量中。
变量 fw_arg0 为内核参数的个数,其余分别为字符串指针,为 *** = XXXX 的格式。
----------------------------------
MTC0 zero, CP0_CONTEXT # clear context register
清除 CP0 的 context register,这个寄存器用来保存页表的起始地址。
----------------------------------
PTR_LA $28, init_thread_union
初始化 $gp 寄存器,这个寄存器的地址指向一个 union,
THREAD_SIZE 大小,最低处是一个thread_info 结构
---------------------------------
PTR_LI sp, _THREAD_SIZE - 32
PTR_ADDU sp, $28
设置 $sp 寄存器,堆栈指针。 $sp = (init_thread_union 的地址) + _THREAD_SIZE - 32
的得出 $sp 指向这个 union 结构的结尾地址 - 32 字节地址。
-----------------------------------
set_saved_sp sp, t0, t1
把 这个 CPU 核的堆栈地址 $sp 保存到 kernelsp[NR_CPUS] 数组。
---------------------------------
如果定义了 CONFIG_SMP 宏,即多 CPU 核。
.macro set_saved_sp stackp temp temp2
#ifdef CONFIG_MIPS_MT_SMTC
mfc0 temp, CP0_TCBIND
#else
MFC0 temp, CP0_CONTEXT
#endif
LONG_SRL temp, PTEBASE_SHIFT
LONG_S stackp, kernelsp(temp)
.endm
如果没有定义 CONFIG_SMP 宏,单 CPU 核。
.macro set_saved_sp stackp temp temp2
LONG_S stackp, kernelsp
.endm
变量 kernelsp 的定义,在 arch/mips/kernel/setup.c 文件中。
unsigned long kernelsp[NR_CPUS];
把 这个 CPU 核的堆栈地址 $sp 保存到 kernelsp[NR_CPUS] 数组。
---------------------------------
PTR_SUBU sp, 4 * SZREG # init stack pointer
---------------------------------
j start_kernel
END(kernel_entry)
最后跳转到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬件平台相关的代码。
----------------------------------
******************************************
这个 init_thread_union 变量在 arch/mips/kernel/init_task.c 文件中定义。
union thread_union init_thread_union
__attribute__((__section__(".data.init_task"),
__aligned__(THREAD_SIZE))) =
{ INIT_THREAD_INFO(init_task) };
******************************************
问题:
1)这个 init_thread_union 结构体指针是怎么初始化的?
******************************************
linux-mips启动分析(2)
linux 内核启动的第一个阶段是从 /arch/mips/kernel/head.s文件开始的。
而此处正是内核入口函数kernel_entry(),该函数定义在 /arch/mips/kernel/head.s文件里。
kernel_entry()函数是体系结构相关的汇编语言,它首先初始化内核堆栈段,来为创建系统中的第一个进程进行准备, 接着用一段循环将内核映像的未初始化数据段(bss段,在_edata和_end之间)清零, 最后跳转到 /arch/mips/kernel/main.c 中的 start_kernel()初始化硬件平台相关的代码。
下面讲述 start_kernel() 函数。
********************************************
asmlinkage void __init start_kernel(void)
{
---------------------------------
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];
定义了核的参数数据结构
---------------------------------
smp_setup_processor_id();
设置 SMP 多核的 CPU 核的 ID 号,单核不进行任何操作,我们不关心。
---------------------------------
unwind_init();
在 MIPS 体系结构中,这个函数是个空函数(可能调用setup_arch,配置核的相关函数)
---------------------------------
lockdep_init();
初始化核依赖关系哈希表。
---------------------------------
local_irq_disable();
关闭当前 CPU 核的中断
---------------------------------
early_boot_irqs_off();
通过一个静态全局变量 early_boot_irqs_enabled 来帮助我们调试代码,
通过这个标记可以帮助我们知道是否在“early bootup code”,
也可以通过这个标志警告是否有无效的中断打开。
和 early_boot_irqs_on() 函数配置使用,参考下面。
---------------------------------
early_init_irq_lock_class();
每一个中断都有一个 IRQ 描述符 ( struct irq_desc )来进行描述。
这个函数的主要作用是设置所有的 IRQ 描述符 ( struct irq_desc )的锁是统一的锁,
还是每一个 IRQ 描述符 ( struct irq_desc )都有一个小锁。
参考《linux-mips启动分析(2-1)》。
---------------------------------
lock_kernel();
获取大内核锁,这种大内核锁锁定整个内核。
--------------------------------
tick_init();
如果没有定义 CONFIG_GENERIC_CLOCKEVENTS 宏定义,则这个函数为空函数,
如果定义了这个宏,这执行初始化 tick 控制功能,注册 clockevents 的框架。
---------------------------------
boot_cpu_init();
对于 CPU 核的系统来说,设置第一个 CPU 核为活跃 CPU 核。
对于单 CPU 核系统来说,设置 CPU 核为活跃 CPU 核。
参考《linux-mips启动分析(2-1)》。
---------------------------------
page_address_init();
当定义了 CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。
其他情况为空函数。
参考《linux-mips启动分析(2-1)》。
---------------------------------
printk(KERN_NOTICE);
printk(linux_banner);
输出打印版本信息。
---------------------------------
setup_arch(&command_line);
每种体系结构都有自己的 setup_arch() 函数,这些是体系结构相关的。
如何确定编译那个体系结构的 setup_arch() 函数呢?
主要由 linux 源码树顶层 Makefile 中 ARCH 变量来决定的。
例如: MIPS 体系结构的。
SUBARCH := mips
ARCH ?= $(SUBARCH)
---------------------------------
setup_command_line(command_line);
保存未改变的 comand_line 到字符数组 static_command_line[] 中。
保存 boot_command_line 到字符数组 saved_command_line[] 中。
参考《linux-mips启动分析(2-1)》。
---------------------------------
unwind_setup();
空函数。
---------------------------------
setup_per_cpu_areas();
如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。
如果定义了 CONFIG_SMP 宏,
则这个 setup_per_cpu_areas() 函数给每个CPU分配内存,并拷贝 .data.percpu 段的数据。
参考《linux-mips启动分析(2-1)》。
---------------------------------
如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。
如果定义了 CONFIG_SMP 宏,这个函数
smp_prepare_boot_cpu();
---------------------------------
sched_init();
核心进程调度器初始化,调度器的初始化优先于任何中断的建立(包括 timer 中断)。
并且初始化进程 0 ,即 idle 进程,但是并没有设置 idle 进程的 NEED_RESCHED 标志,
以完成内核剩余的启动部分。
---------------------------------
preempt_disable();
进制内核的抢占。使当前进程的 struct thread_info 结构 preempt_count 成员的值增加 1。
参考《linux-mips启动分析(2-1)》。
---------------------------------
建立各个节点的管理区的 zonelist,便于分配内存的 fallback 使用。
这个链表的作用: 这个链表是为了在一个分配不能够满足时可以考察下一个管理区来设置了。
在考察结束时,分配将从 ZONE_HIGHMEM 回退到 ZONE_NORMAL,
在分配时从 ZONE_NORMAL 退回到 ZONE_DMA 就不会回退了。
build_all_zonelists();
---------------------------------
参考《linux-mips启动分析(2-1)》。
page_alloc_init();
---------------------------------
在 MIPS 体系结构下,这个函数已经在 arch_mem_init() 函数中调用了一次。
这个函数的具体分析详细分析,请看《linux-mips启动分析(4)》。
所以这个函数直接返回。
parse_early_param();
---------------------------------
打印 linux 启动命令行参数。
printk(KERN_NOTICE "Kernel command line: %sn", boot_command_line);
---------------------------------
这个函数在 《linux-mips启动分析(4)》文件有分析,可以参考。
这个函数的意思对 linux 启动命令行参数进行再分析和处理。
这两个变量 __start___param 和 __stop___param 在
链接脚本 arch/mips/kernel/vmlinux.lds 中定义。
最后一个参数为,当不能够识别 linux 启动命令行参数时,调用的函数。
parse_args("Booting kernel", static_command_line, __start___param,
__stop___param - __start___param, &unknown_bootoption);
---------------------------------
检查中断是否已经打开了,如果已将打开了,关闭中断。
if (!irqs_disabled()) {
local_irq_disable();
}
---------------------------------
sort_main_extable();
这个函数对内核建立的异常处理调用函数表( exception table )
根据异常的向量号进行堆排序。
---------------------------------
设置 CPU 的异常处理函数,TLB 重填,cache 出错,还有通用异常处理表的初始化。
trap_init();
---------------------------------
初始化 RCU 机制,这个步骤必须比本地 timer 的初始化早。
rcu_init();
---------------------------------
用来初始化中断处理硬件相关的寄存器和 中断描述符数组 irq_desc[] 数组,
每个中断号都有一个对应的中断描述符。
参考《linux-mips启动分析(11)》。
init_IRQ();
--------------------------------
系统在初始化阶段动态的分配了 4 个 hashtable,并把它们的地址存入 pid_hash[] 数组。
便于从 PID 查找 进程描述符地址。
参考《linux-mips启动分析(12)》。
pidhash_init();
---------------------------------
1)初始化本 CPU 上的定时器(timer)相关的数据结构
2)向 cpu_chain 通知链注册元素 timers_nb,该元素的回调函数用于初始化指定 CPU 上的定时器相关的数据结构。
3) 初始化时钟的软中断处理函数
参考《linux-mips启动分析(13)》。
init_timers();
---------------------------------
这个 hrtimers_init() 函数对高精度时钟进行初始化。
参考《linux-mips启动分析(14)》。
hrtimers_init();
---------------------------------
softirq_init();
---------------------------------
调用 timekeeping_init() 函数来初始化系统计时器。
参考《linux-mips启动分析(16)》。
timekeeping_init();
---------------------------------
调用 time_init() 函数,初始化系统时钟源,
对 MIPS 体系结构的 clocksource_mips 时钟源进行配置,和注册。
参考《linux-mips启动分析(17》。
time_init();
---------------------------------
这个函数对内核的 profile 功能进行初始化,这是一个内核调式工具,
通过这个可以发现内核在内核态的什么地方花费时间最多,
即发现内核的“hot spot”——执行最频繁的内核代码。
参考《linux-mips启动分析(18)。
profile_init();
---------------------------------
检测是否已经打开了中断。
如果已经打开了中断,则打印提示信息,报告一个 bug 。
if (!irqs_disabled())
printk(打印提示信息,报告一个 bug );
通过一个静态全局变量 early_boot_irqs_enabled 来帮助我们调试代码,
通过这个标记可以帮助我们知道是否在“early bootup code”,
也可以通过这个标志警告是否有无效的中断打开。
和 early_boot_irqs_off () 函数配置使用,参考上面。
early_boot_irqs_on();
使能本地 CPU 的中断。
local_irq_enable();
====================================================
总结:上面是早期启动阶段,在上面的执行过程中,中断是关闭的。
# 输出Linux版本信息(printk(linux_banner))
# 设置与体系结构相关的环境(setup_arch())
# 页表结构初始化(paging_init())
# 设置系统自陷入口(trap_init())
#初始化系统IRQ(init_IRQ())
# 核心进程调度器初始化(sched_init())
# 时间、定时器初始化(包括读取CMOS时钟、估测主频、初始化定时器中断等,time_init())
# 提取并分析核心启动参数(从环境变量中读取参数,设置相应标志位等待处理,(parse_options())
======================================================
console_init();
这个 console_init() 函数初始化内核显示终端,在这里仅仅进行一些初级的初始化。
参考《linux-mips启动分析(19)》。
---------------------------------
如果这个 panic_later 字符串已经设置了,则停止系统的启动。
if (panic_later)
panic(panic_later, panic_param);
---------------------------------
如果没有定义 LOCKDEP 这个宏,则这个 lockdep_info() 函数就为空函数。
lockdep_info();
---------------------------------
如果没有定义 CONFIG_DEBUG_LOCKING_API_SELFTESTS 这个宏,
则这个 locking_selftest() 函数为空函数。
locking_selftest();
---------------------------------
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start && !initrd_below_start_ok
&& initrd_start < min_low_pfn << PAGE_SHIFT) {
initrd_start = 0;
}
#endif
---------------------------------
vfs_caches_init_early();
---------------------------------
cpuset_init_early();
---------------------------------
mem_init();
---------------------------------
kmem_cache_init();
---------------------------------
setup_per_cpu_pageset();
---------------------------------
numa_policy_init();
---------------------------------
if (late_time_init)
late_time_init();
---------------------------------
calibrate_delay();
---------------------------------
pidmap_init();
---------------------------------
pgtable_cache_init();
---------------------------------
prio_tree_init();
---------------------------------
anon_vma_init();
---------------------------------
fork_init(num_physpages);
---------------------------------
}
*******************************************
问题:
1)核依赖关系哈希表是如何使用的?什么功能?
2)在变量 __per_cpu_end 和 __per_cpu_start 之间保存了什么数据?
linux-mips启动分析 (2-1)
这个文件讲解了 start_kernel() 函数中调用的一些小函数,可以作为参考。
有下列函数:
early_init_irq_lock_class()
boot_cpu_init()
page_address_init()
setup_command_line()
setup_per_cpu_areas()
preempt_disable()
**********************************************
如果没有定义宏 CONFIG_TRACE_IRQFLAGS 和 CONFIG_GENERIC_HARDIRQS,
函数 early_init_irq_lock_class() 就是空的;
#if defined(CONFIG_TRACE_IRQFLAGS) && defined(CONFIG_GENERIC_HARDIRQS)
extern void early_init_irq_lock_class(void);
#else
static inline void early_init_irq_lock_class(void)
{
}
#endif
如果没有定义宏 CONFIG_TRACE_IRQFLAGS 和 CONFIG_GENERIC_HARDIRQS,
则 early_init_irq_lock_class() 函数在 kernel/irq/handle.c 文件中,
进行了定义;
static struct lock_class_key irq_desc_lock_class;
void early_init_irq_lock_class(void)
{
int i;
for (i = 0; i < NR_IRQS; i++)
lockdep_set_class(&irq_desc[i].lock, &irq_desc_lock_class);
}
每一个中断都有一个 IRQ 描述符 ( struct irq_desc )来进行描述。
这个函数的主要作用是设置所有的 IRQ 描述符 ( struct irq_desc )的锁是统一的锁,
还是每一个 IRQ 描述符 ( struct irq_desc )都有一个小锁。
********************************************
这个 boot_cpu_init() 函数设置 CPU 位图信息。
参考《linux的CPU信息管理》。
----------------------------------------
static void __init boot_cpu_init(void)
{
int cpu = smp_processor_id();
获取当前 CPU 的 ID 号。
cpu_set(cpu, cpu_online_map);
cpu_set(cpu, cpu_present_map);
cpu_set(cpu, cpu_possible_map);
}
*****************************************
page_address_init();
当定义了 CONFIG_HIGHMEM 宏,并且没有定义 WANT_PAGE_VIRTUAL 宏时,非空函数。
其他情况 page_address_init() 函数为空函数。
static struct page_address_map page_address_maps[LAST_PKMAP];
void __init page_address_init(void)
{
int i;
INIT_LIST_HEAD(&page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_maps); i++)
list_add(&page_address_maps[i].list, &page_address_pool);
for (i = 0; i < ARRAY_SIZE(page_address_htable); i++) {
INIT_LIST_HEAD(&page_address_htable[i].lh);
spin_lock_init(&page_address_htable[i].lock);
}
spin_lock_init(&pool_lock);
}
把结构体 struct page_address_map 类型的数组 page_address_maps 加入链表 page_address_pool 上。
********************************************
字符数组 command_line[] 和 boot_command_line[]在 arch_mem_init() 函数中赋值,
为命令行启动参数字符串组合。参考《linux-mips启动分析(4)》。
这两个字符指针 saved_command_line 和 static_command_line 在 main.c 文件中定义。
----------------------------------------
static void __init setup_command_line(char *command_line)
{
使用引导内存分配器分配内存 command_line 的保存空间,并拷贝到这个空间中。
saved_command_line = alloc_bootmem(strlen (boot_command_line)+1);
static_command_line = alloc_bootmem(strlen (command_line)+1);
strcpy (saved_command_line, boot_command_line);
strcpy (static_command_line, command_line);
}
******************************************
如果没有定义 CONFIG_SMP 宏,则这个函数为空函数。
如果定义了 CONFIG_SMP 宏,
则这个 setup_per_cpu_areas() 函数给每个CPU分配 pre-cpu 结构内存,
并拷贝 .data.percpu 段的数据。
参考《每CPU变量的数据组织和访问》。
----------------------------------------
static void __init setup_per_cpu_areas(void)
{
unsigned long size, i;
char *ptr;
取得 CPU 核的数目。
unsigned long nr_possible_cpus = num_possible_cpus();
为每个 CPU 分配 size 大小的空间。
size = ALIGN(PERCPU_ENOUGH_ROOM, PAGE_SIZE);
ptr = alloc_bootmem_pages(size * nr_possible_cpus);
for_each_possible_cpu(i) {
__per_cpu_offset[i] = ptr - __per_cpu_start;
memcpy(ptr, __per_cpu_start, __per_cpu_end - __per_cpu_start);
ptr += size;
}
}
----------------------------------------
变量 __per_cpu_end 和 __per_cpu_start 在链接
文件 arch/mips/kernel/vmlinux.lds 中定义。
在该函数中,为每个CPU分配一段专有数据区,并将.data.percpu中的数据拷贝到其中,
每个CPU各有一份。由于数据从__per_cpu_start处转移到各CPU自己的专有数据区中了,
因此存取其中的变量就不能再用原先的值了,比如存取per_cpu__runqueues
就不能再用per_cpu__runqueues了,需要做一个偏移量的调整,
即需要加上各CPU自己的专有数据区首地址相对于__per_cpu_start的偏移量。
在这里也就是__per_cpu_offset[i],其中CPU i的专有数据区相对于
__per_cpu_start的偏移量为__per_cpu_offset[i]。
这样,就可以方便地计算专有数据区中各变量的新地址,比如对于per_cpu_runqueues,
其新地址即变成per_cpu_runqueues + __per_cpu_offset[i]。
----------------------------------------
定义 per_cpu 数据时使用这个 DEFINE_PER_CPU(type, name) 宏定义
它的定义如下:
如果定义了 CONFIG_SMP:
#define DEFINE_PER_CPU(type, name)
__attribute__((__section__(".data.percpu"))) __typeof__(type) per_cpu__##name
访问 per_cpu 数据的实现:
#define per_cpu(var, cpu) (*({
extern int simple_identifier_##var(void);
RELOC_HIDE(&per_cpu__##var, __per_cpu_offset[cpu]); }))
#define __get_cpu_var(var) per_cpu(var, smp_processor_id())
#define __raw_get_cpu_var(var) per_cpu(var, raw_smp_processor_id())
如果没有定义 CONFIG_SMP:
#define DEFINE_PER_CPU(type, name)
__typeof__(type) per_cpu__##name
访问 per_cpu 数据的实现:
#define per_cpu(var, cpu) (*((void)(cpu), &per_cpu__##var))
#define __get_cpu_var(var) per_cpu__##var
#define __raw_get_cpu_var(var) per_cpu__##var
*****************************************
如果没有定义 CONFIG_PREEMPT 这个宏,这个 preempt_disable()函数为空函数。
如果定义了这个 CONFIG_PREEMPT 这个宏。
----------------------------------------
这个 preempt_disable()函数在 include/linux/preempt.h 文件中实现。
#define preempt_disable()
do {
inc_preempt_count();
barrier();
} while (0)
主要由宏 inc_preempt_count() 来完成工作。
#define inc_preempt_count() add_preempt_count(1)
# define add_preempt_count(val) do { preempt_count() += (val); } while (0)
#define preempt_count() (current_thread_info()->preempt_count)
----------------------------------------
由以上可以看出这个 preempt_disable()函数主要工作是使当前进程
的 struct thread_info 结构 preempt_count 成员的值增加 1。
****************************************
void __init page_alloc_init(void)
{
hotcpu_notifier(page_alloc_cpu_notify, 0);
}
---------------------------------
这个 hotcpu_notifier()宏函数在 include/linux/cpu.h 文件中实现的,
这个函数有两个版本,通过 CONFIG_HOTPLUG_CPU 宏定义来进行控制。
我们的系统不支持 CPU 的热插拔,所以没有定义这个 CONFIG_HOTPLUG_CPU 宏。
所以它的实现如下所示:
#define hotcpu_notifier(fn, pri) do { (void)(fn); } while (0)
它的第一个参数是一个函数指针,这个函数的意思是直接调用这个函数指针就行了。
不过经过测试,好像也没有调用 page_alloc_cpu_notify() 函数。
******************************************
问题:
1)这个 page_alloc_init() 函数好像没有执行任何的操作,不知道对不对?
linux-mips启动分析(3)
在 start_kernel() 函数中调用了 setup_arch() 函数。
每种体系结构都有自己的 setup_arch() 函数,这些是体系结构相关的。
如何确定编译那个体系结构的 setup_arch() 函数呢?
主要由 linux 源码树顶层 Makefile 中 ARCH 变量来决定的。
例如: MIPS 体系结构的。
SUBARCH := mips
ARCH ?= $(SUBARCH)
下面我们分析一下 MIPS 体系结构的 setup_arch() 函数。
从《linux-mips启动分析(3)》到《linux-mips启动分析(5)》文件中 一直在讲述 setup_arch() 函数。
********************************************
位于 /arch/mips/kernel/setup.c 文件中。
参数 cmdline_p 为字符的指针的指针,没有赋值。
可能为了以后把内核启动参数保存到这个指针指向的字符串中。
void __init setup_arch(char **cmdline_p)
{
cpu_probe();
调用函数cpu_probe(),该函数通过MIPS CPU的PRID寄存器来确定CPU类型,
从而确定使用的指令集和其他一些CPU参数,如TLB等
prom_init();
prom_init() 函数是和硬件相关的,做一些低层的初始化,接受引导装载程序传给内核的参数,
确定 mips_machgroup,mips_machtype 这两个变量,这两个变量分别对应着相应的芯片组合开发板;
打印 cpu_probe() 函数检测到的 CPU 的 Processor ID。
如果有浮点处理器,也打印浮点处理器的 Processor ID。
cpu_report();
应用程序通过终端接口设备使用特定的接口规程与终端进行交互,与操作系统内核本身交互的终端称为控制台,
它可以是内核本身的内部显示终端,也可以是通过串口连接的外部哑终端。
由于大多数情况下控制台都是内核显示终端,因此内核显示终端也常常直接称为控制台。
内核终端对用户来说具有若干个虚拟终端子设备,它们共享同一物理终端,
但同一时刻只能有一个虚拟终端操作硬件屏幕。
宏 CONFIG_VT 的意思是否支持虚拟终端。
当配置了宏 CONFIG_VGA_CONSOLE 时为内核本身的内部显示终端。
当配置了宏 CONFIG_DUMMY_CONSOLE 时为通过串口连接的外部哑终端。
用变量 conswitchp 来进行指定。
#if defined(CONFIG_VT)
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
对内存进行初始化。
arch_mem_init(cmdline_p);
这个函数遍历每一个内存空间范围(物理地址),在资源管理器中进行资源申请,
并对内核代码和数据段进行资源申请。
resource_init();
#ifdef CONFIG_SMP
plat_smp_setup();
#endif
}
*****************************************
第一函数:
cpu_probe ( )函数的部分源码如下:
==========================================================
这个 cpu_data[] 数组定义在 arch/mips/kernel/setup.c 文件中。
定义如下所示:
struct cpuinfo_mips cpu_data[NR_CPUS] __read_mostly;
它的类型为 struct cpuinfo_mips 结构体:
struct cpuinfo_mips {
unsigned long udelay_val;
unsigned long asid_cache;
unsigned long options;
unsigned long ases;
unsigned int processor_id;
unsigned int fpu_id;
unsigned int cputype;
int isa_level;
int tlbsize;
struct cache_desc icache; /* Primary I-cache */
struct cache_desc dcache; /* Primary D or combined I/D cache */
struct cache_desc scache; /* Secondary cache */
struct cache_desc tcache; /* Tertiary/split secondary cache */
void *data; /* Additional data */
} __attribute__((aligned(SMP_CACHE_BYTES)));
----------------------------------------
static inline void cpu_probe(void) {
struct cpuinfo_mips *c = ¤t_cpu_data;
----------------------------------------
#define current_cpu_data cpu_data[smp_processor_id()]
smp_processor_id() 函数获得当前 CPU 的 ID 号。
所以 cpuinfo_mips 变量为当前 CPU 的数据结构指针。
下面对这个 CPU 的数据结构进行填充。
----------------------------------------
c->processor_id = read_c0_prid();
----------------------------------------
获取CP0_CONFIG寄存器的值,根据CP0控制寄存器PRID来确定CPU的类型
+------------------+----------------+----------------+----------------+
|Company Options| Company ID | Processor ID | Revision |
+------------------+----------------+----------------+----------------+
31 24 23 16 15 8 7 0
这个寄存器的 [23:16]位表明 CPU 的公司 ID 。
----------------------------------------
switch (c->processor_id & 0xff0000){
根据 PRID 的 [23:16]位来选择 CPU 的公司 ID。
case PRID_COMP_CLXRISC: /* CLXRISC Implementation. */
decode_configs(c);
switch (c->processor_id & 0xff000000) {
case PRID_IMP_CLXRISC:
对 CPU 的功能和特性进行描述。
c->options |= MIPS_CPU_MCHECK;
c->cputype = CPU_CLXRISC;
c->isa_level = MIPS_CPU_ISA_M32R1;
c->tlbsize = 32;
break;
default:
c->cputype = CPU_UNKNOWN;
break;
}
break;
}
----------------------------------------
根据 CPU 的特性进行检测 CPU 是否支持浮点运算单元,取得浮点运算单元的 ID 号。
并检测 CPU 是否支持 3D 图像运算,如果支持表明 CPU 支持。
if (c->options & MIPS_CPU_FPU) {
c->fpu_id = cpu_get_fpu_id();
if (c->isa_level == MIPS_CPU_ISA_M32R1 ||
c->isa_level == MIPS_CPU_ISA_M32R2 ||
c->isa_level == MIPS_CPU_ISA_M64R1 ||
c->isa_level == MIPS_CPU_ISA_M64R2) {
if (c->fpu_id & MIPS_FPIR_3D)
c->ases |= MIPS_ASE_MIPS3D;
}
}
}
从中可以看出,cpu_probe()通过 CPU 的CP0控制寄存器 PRID 来对 CPU 功能和特性进行描述,
这些特性在后面用来决定调用相应的异常处理和内存管理程序。
----------------------------------------
这个函数的 linux 内核移植相关部分:
1)在 include/asm-mips/cpu.h 中添加 CPU 公司的 ID。
2)修改添加 cpu_probe()函数中关于 CPU 公司 ID 的处理。
********************************************
第二函数:
prom_init() 函数的部分源码如下:
----------------------------------------
void __init prom_init(void)
{
unsigned long memsize;
prom_argc = (int) fw_arg0;
prom_argv = (char **) fw_arg1;
prom_envp = (char **) fw_arg2;
这三个 fw_arg0、fw_arg1、fw_arg2 变量的赋值,参考 《linux-mips启动分析(1).txt》,
在 /arch/mips/kernel/head.s 文件中初始化的。
mips_machgroup = MACH_GROUP_CLXRISC;
mips_machtype = 0;
初始化 mips_machgroup,mips_machtype 这两个变量,这两个变量分别对应着相应的芯片组合开发板;
把内核启动参数拷贝到 arcs_cmdline[]字符数组中。
prom_init_cmdline();
memsize = 128;
if (memsize < 0x1000)
memsize *= 1024*1024;
在 结构体变量 boot_mem_map 中赋值指定的内存范围(物理地址)映像图。
add_memory_region(0, memsize, BOOT_MEM_RAM);
add_memory_region(0x28000000, memsize, BOOT_MEM_RAM);
}
----------------------------------------
这个函数的 linux 内核移植相关部分:
这个函数是和具体的硬件相关的,做一些底层的操作,移植 linux 内核时,需要自己手动写整个函数。
----------------------------------------
prom_init() 函数调用 prom_init_cmdline() 函数,
void prom_init_cmdline(void)
{
char *cp;
int actr;
actr = 1; /* Always ignore argv[0] */
变量 arcs_cmdline 为定义的字符数组,默认为字符串 CONFIG_CMDLINE。
cp = &(arcs_cmdline[0]);
while(actr < prom_argc) {
strcpy(cp, prom_argv[actr]);
cp += strlen(prom_argv[actr]);
*cp++ = ' ';
actr++;
}
if (cp != &(arcs_cmdline[0])) /* get rid of trailing space */
--cp;
*cp = '