目录
一、内核源码获取查看
1.1、Source Insight使用
?二、查看链接脚本
三、分析head.S
3.1、到入口前代码
3.2、内核启动的汇编阶段
四、main.c内核启动的c语言阶段
4.1、内核打印函数printk
4.2、启动信息
五、rest_init函数
5.1、进程0、进程1、进程2?编辑
5.2、init进程的2种状态
5.3、init进程干了什么
六、宏MACHINE_START
6.1、结构体?machine_desc
6.2、树莓派内核的MACHINE_START
一、内核源码获取查看
- 获取下载
- 拿到Ubuntu中解压编译,参考
- 打包源码到window下
tar -czf smp.tar.gz linux-rpi-4.14.y
cp smp.tar.gz /mnt/hgfs/gongxian-20-04/ - 使用Source Insight阅读代码
1.1、Source Insight使用
- 创建工程
- 选择保存路径和工程名称
- 点击Add Tree
- 点击,完成最后一步
?二、查看链接脚本
- kernel的连接脚本并不是直接提供的,而是提供了一个汇编文件vmlinux.lds.S,然后在编译的时候再去编译这个汇编文件得到真正的链接脚本vmlinux.lds
- vmlinux.lds.S在arch/arm/kernel/目录下
- 从vmlinux.lds中ENTRY(stext)可以知道入口符号是stext
OUTPUT_ARCH(arm)
ENTRY(stext)
jiffies = jiffies_64;
SECTIONS
{
/DISCARD/ : {
*(.ARM.exidx.exit.text)
*(.ARM.extab.exit.text)
*(.ARM.exidx.cpuexit.text)
*(.ARM.extab.cpuexit.text)
*(.exitcall.exit)
*(.discard)
*(.discard.*)
}
. = 0x80000000 + 0x00008000;
.head.text : {
_text = .;
*(.head.text)
}
- 查找发现2个文件中有ENTRY(stext)
head-nommu.S ,head.S - head.S是启用了MMU情况下的kernel启动文件
head-nommu.S是未使用mmu情况下的kernel启动文件 在加上查看编译后的文件发现,有head.o文件,所以可以确定是head.S
三、分析head.S
3.1、到入口前代码
- KERNEL_RAM_VADDR,这个宏定义了内核运行时的虚拟地址。
- 入口就是ENTRY(stext)处,前面的__HEAD定义了后面的代码属于段名为.head.text的段
- 阅读注释
内核的起始部分代码是被解压代码调用的,内核运行是有条件的 MMU = 关, D-cache = 关,?I-cache = 不在乎, r0 = 0,??r1 = 机器码, r2 = tag地址 此代码基本上是位置无关的,因此将内核链接到0xc0008000 有关机器的完整列表,请参见arch/arm/tools/mach-types
3.2、内核启动的汇编阶段
bl __hyp_stub_install
@ ensure svc mode and all interrupts masked
safe_svcmode_maskall r9
mrc p15, 0, r9, c0, c0 @ get processor id
bl __lookup_processor_type @ r5=procinfo r9=cpuid
movs r10, r5 @ invalid processor (r5=0)?
THUMB( it eq ) @ force fixup-able long branch encoding
beq __error_p @ yes, error 'p'
ldr r8, =PLAT_PHYS_OFFSET @ always constant in this case
/*
* r1 = machine no, r2 = atags or dtb,
* r8 = phys_offset, r9 = cpuid, r10 = procinfo
*/
bl __vet_atags
bl __fixup_smp
bl __fixup_pv_table
bl __create_page_tables
ldr r13, =__mmap_switched @ address to jump to after
- __hyp_stub_install
arm虚拟化功能有关 - __lookup_processor_type
从cp15协处理器的c0寄存器中读取出硬件的CPU ID号 然后调用这个函数来进行合法性检验,如果合法则继续启动,如果不合法则停止启动转向__error_p启动失败 - __vet_atags
校验uboot给内核的传参ATAGS格式是否正确 - __create_page_tables
这个函数用来建立页表 kernel建立页表其实分为2步:kernel先建立了一个段式页表,再去建立一个细页表 启动的早期建立段式页表,并在内核启动前期使用;内核启动后期就会建立细页表并启用 - __mmap_switched
复制数据段、清除bss段(目的是构建C语言运行环境) 保存起来cpu id号、机器码、tag传参的首地址 b?? ?start_kernel跳转到C语言运行阶段
四、main.c内核启动的c语言阶段
start_kernel函数位于main.c中 位置在:linux-rpi-4.14.y/init/main.c
4.1、内核打印函数printk
4.2、启动信息
[ 0.000000] Booting Linux on physical CPU 0x0
[ 0.000000] Linux version 4.14.114-v7 (xw@ubuntu) (gcc version 4.8.3 20140303 (prerelease) (crosstool-NG linaro-1.13.1+bzr2650 - Linaro GCC 2014.03)) #1 SMP Tue Nov 1 05:49:10 PDT 2022
[ 0.000000] CPU: ARMv7 Processor [410fd034] revision 4 (ARMv7), cr=10c5383d
。。。。
[ 0.000000] Kernel command line: 8250.nr_uarts=1 bcm2708_fb.fbwidth=656 bcm2708_fb.fbheight=416 bcm2708_fb.fbswap=1 vc_mem.mem_base=0x3ec00000 vc_mem.mem_size=0x40000000 dwc_otg.lpm_enable=0 console=tty1 console=ttyAMA0,115200 root=/dev/mmcblk0p2 rootfstype=ext4 elevator=deadline fsck.repair=yes rootwait
- 启动信息前面的[]数字是启动所用时间
- 第一条启动信息是smp_setup_processor_id();函数打印的
smp就是对称多处理器 它读取当前物理CPU,映射到逻辑CPU0,设置当前task id为0,打印启动信息 - 第二条是pr_notice("%s", linux_banner);打印的
pr_notice可以理解为是printk函数的封装形式 - setup_arch? ?位置:arch/arm/kernel/setup.c
setup_processor函数用来查找CPU信息 - parse_early_param??解析cmdline传参和其他传参
- trap_init:设置异常向量表
mm_init:内存管理模块初始化 console_init:控制台初始化 等一系列初始化。。。
总结:start_kernel函数中调用了很多的xx_init函数,全都是内核工作需要的模块的初始化函数,这些初始化之后内核就具有了一个基本的可以工作的条件了 ?
五、rest_init函数
- rest_init中调用kernel_thread函数启动了2个内核线程,分别是:kernel_init和kthreadd
- 调用schedule_preempt_disabled->schedule函数开启了内核的调度系统,从此linux系统开始转起来了
- 最终调用cpu_startup_entry->do_idle函数结束了整个内核的启动。也就是说linux内核最终结束了一个函数cpu_idle
- linux内核最终的状态是:有事干的时候去执行有意义的工作,没活干的时候就去死循环
调度系统
5.1、进程0、进程1、进程2
- ubuntu下ps -aux可以看到当前系统运行的所有进程
进程0不是一个用户进程,而属于内核进程,所以不显示0进程 - 进程0:进程0就是idle进程,叫空闲进程,也就是死循环
进程1:kernel_init函数就是进程1,这个进程被称为init进程 进程2:kthreadd函数就是进程2,这个进程是linux内核的守护进程。这个进程是用来保证linux内核自己本身能正常工作的
5.2、init进程的2种状态
- init进程2种状态,内核态向用户态的转变
- init进程在内核态下面时,通过一个函数do_execve来执行一个用户空间编译连接的应用程序就跳跃到用户态了。注意这个跳跃过程中进程号是没有改变的
- init进程刚开始运行的时候是内核态,它属于一个内核线程,然后他自己运行了一个用户态下面的程序后把自己强行转成了用户态
- init进程在内核态:挂载根文件系统并试图找到用户态下的那个init程序
init程序和内核不在一起,在根文件下 - init进程在用户态:所有的用户进程都直接或者间接派生自init进程
- init启动了login进程、命令行进程、shell进程
5.3、init进程干了什么
- 打开了/dev/console控制台文件
复制了2次文件描述符,一共得到了3个文件描述符 这三个文件描述符分别是012,这三个文件描述符就是:标准输入、标准输出、标准错误 后续的进程1衍生出来的所有的进程默认都具有这3个三件描述符 - 内核态下的进程1,挂载根文件系统
kernel_init->kernel_init_freeable->prepare_namespace函数中挂载根文件系统 root=/dev/mmcblk0p2? 这一句就是告诉内核根文件系统在哪里 rootfstype=ext4? ?告诉内核rootfs文件系统的类型 挂载成功? ?[ ? ?2.395288] VFS: Mounted root (ext4 filesystem) readonly on device 179:2. - 用户态下的进程1
kernel_init->run_init_process执行用户态下的init程序
六、宏MACHINE_START
#define MACHINE_START(_type,_name) \
static const struct machine_desc __mach_desc_##_type \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = MACH_TYPE_##_type, \
.name = _name,
#define MACHINE_END \
};
- 定义了一个结构体类型为machine_desc的结构体变量
- 这个结构体变量会被定义到一个特定段.arch.info.init
- 会被链接器链接到这个.arch.info.init段中
.init.arch.info : {
__arch_info_begin = .; //开始
*(.arch.info.init)
__arch_info_end = .; //结尾
}
6.1、结构体?machine_desc
struct machine_desc {
unsigned int nr; /* architecture number */
const char *name; /* architecture name */
unsigned long atag_offset; /* tagged list (relative) */
const char *const *dt_compat; /* array of device tree
* 'compatible' strings */
unsigned int nr_irqs; /* number of IRQs */
#ifdef CONFIG_ZONE_DMA
phys_addr_t dma_zone_size; /* size of DMA-able area */
#endif
unsigned int video_start; /* start of video RAM */
unsigned int video_end; /* end of video RAM */
unsigned char reserve_lp0 :1; /* never has lp0 */
unsigned char reserve_lp1 :1; /* never has lp1 */
unsigned char reserve_lp2 :1; /* never has lp2 */
enum reboot_mode reboot_mode; /* default restart mode */
unsigned l2c_aux_val; /* L2 cache aux value */
unsigned l2c_aux_mask; /* L2 cache aux mask */
void (*l2c_write_sec)(unsigned long, unsigned);
const struct smp_operations *smp; /* SMP operations */
bool (*smp_init)(void);
void (*fixup)(struct tag *, char **);
void (*dt_fixup)(void);
long long (*pv_fixup)(void);
void (*reserve)(void);/* reserve mem blocks */
void (*map_io)(void);/* IO mapping function */
void (*init_early)(void);
void (*init_irq)(void);
void (*init_time)(void);
void (*init_machine)(void);
void (*init_late)(void);
#ifdef CONFIG_MULTI_IRQ_HANDLER
void (*handle_irq)(struct pt_regs *);
#endif
void (*restart)(enum reboot_mode, const char *);
};
- nr:机器码
- name:开发板名字
- init_machine:硬件驱动的加载和初始化函数执行
6.2、树莓派内核的MACHINE_START
- 经过查找,发现BCM_2835机器码为4828
用的文件是board_bcm2835.c grep "MACHINE_START(*BCM2835*" * -nr tools/mach-types:553:bcm2835 MACH_BCM2835 BCM2835 4828
arch/arm/mach-bcm/board_bcm2835.c:126:DT_MACHINE_START(BCM2835, "BCM2835") - 使用函数bcm2835_init,硬件驱动的加载和初始化函数执行
DT_MACHINE_START(BCM2835, "BCM2835")
.map_io = bcm2835_map_io,
.init_machine = bcm2835_init,
.init_early = bcm2835_init_early,
.dt_compat = bcm2835_compat,
.smp = smp_ops(bcm2836_smp_ops),
MACHINE_END
- 通过宏可以看出,树莓派没有用到机器码
#define DT_MACHINE_START(_name, _namestr) \
static const struct machine_desc __mach_desc_##_name \
__used \
__attribute__((__section__(".arch.info.init"))) = { \
.nr = ~0, \
.name = _namestr,
|