zoukankan      html  css  js  c++  java
  • kernel(二)源码浅析


    title: kernel(二)源码浅析
    tags: linux
    date: 2018-11-08 18:02:34

    kernel(二)源码浅析

    建立工程

    1. 移除所有Arch,添加Arch/arm 下除了 Mach_xxx 开头的,Mach_xxx 表示机器型号,添加2410,2440,剔除 Plat_xxx,加入plat-s3c24xx

      Arch/arm/
      
      boot
      common
      configs
      kernel
      lib
      
      mach-s3c2410
      mach-s3c2440
      plat-s3c24xx
      
      mm
      nwfpe
      oprofile
      tools
      vfp
      
    2. 移除include目录,先排除所有Asm相关,只加入asm-arm顶层文件以及2440相关的如下

      include/
      排除所有 asm-xxx
      
      include/asm-arm/下添加 所有顶层以及以下目录
      arch-s3c2410
      hardware
      mach
      plat-s3c24xx
      

    启动简析

    uboot启动通过theKernel (0, bd->bi_arch_number, bd->bi_boot_params);中的第二个参数是机器ID,内核通过比对机器ID判断是否支持启动.gd->bd->bi_arch_number = MACH_TYPE_S3C2440;linux会这么做:

    1. 处理uboot传入的参数
    2. 挂接根文件系统
    3. 最终目的:运行应用程序(在根文件系统上)

    内核跳转之前,Uboot设置内核的启动参数.内核的参数是按照tag组织的.也就是在某个地址(0x30000100,在100ask24x0.c中定义),按照某种格式存储,这种格式具体为【size....tagid....tag值】

    mark

    mark

    head.s

    我们发现在archarmootcompressed也存在一个head.S的文件,有些内核编译出来比较大,他会以压缩的形式存在也就是包含了自解压的代码,这个文件就是讲压缩的文件解压,在这里不做分析。我们的入口为archarmkernelhead.S

    入口点

    链接脚本有写,也就是说_stext段为最早的入口点,搜索下head.S中的入口点.text.head

     .text.head : {						#先放所有文件的 .text.head 段
      _stext = .;
      _sinittext = .;
      *(.text.head)
     }
    

    查询处理器

    查看是否支持__lookup_processor_type

    	.section ".text.head", "ax"
    	.type	stext, %function
    ENTRY(stext)
    	msr	cpsr_c, #PSR_F_BIT | PSR_I_BIT | SVC_MODE @ ensure svc mode
    						@ and irqs disabled
    	mrc	p15, 0, r9, c0, c0		@ get processor id
    	bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
    	movs	r10, r5				@ invalid processor (r5=0)?
    	beq	__error_p			@ yes, error 'p'
    	bl	__lookup_machine_type		@ r5=machinfo
    	movs	r8, r5				@ invalid machine (r5=0)?
    	beq	__error_a			@ yes, error 'a'
    	bl	__create_page_tables
    
    

    内核能够支持哪些处理器,是在编译内核时定义下来的。内核启动时去读寄存器:获取 ID。看内核是否可以支持这个处理器。若能支持则继续运行,不支持则跳到 _error_p中去,这是个死循环

    mrc	p15, 0, r9, c0, c0		@ get processor id
    bl	__lookup_processor_type		@ r5=procinfo r9=cpuid
    movs	r10, r5				@ invalid processor (r5=0)?
    beq	__error_p			@ yes, error 'p'
    
    

    查询机器ID

    如果不支持这个机器ID则跳转到__error_a,这也是死循环

    bl	__lookup_machine_type		@ r5=machinfo
    movs	r8, r5				@ invalid machine (r5=0)?
    beq	__error_a			@ yes, error 'a'
    

    机器ID是存在R1的,因为theKernel (0, bd->bi_arch_number, bd->bi_boot_params)

    3:	.long	.
    	.long	__arch_info_begin
    	.long	__arch_info_end
    
    
    @  __arch_info_begin 和 __arch_info_end 是在链接脚本中定义的
    @   __arch_info_begin = .;
    @   *(.arch.info.init)
    @  __arch_info_end = .;
     
    
    /*
     * Lookup machine architecture in the linker-build list of architectures.
     * Note that we can't use the absolute addresses for the __arch_info
     * lists since we aren't running with the MMU on (and therefore, we are
     * not in the correct address space).  We have to calculate the offset.
     *
     *  r1 = machine architecture number
     * Returns:
     *  r3, r4, r6 corrupted
     *  r5 = mach_info pointer in physical address space
     */
    	.type	__lookup_machine_type, %function
    __lookup_machine_type:
    	adr	r3, 3b					@ r3= address of 3b,这个时候mmu还没有启动,是物理地址
    	ldmia	r3, {r4, r5, r6}	@ r4=.,r5=__arch_info_begin,r6=__arch_info_end
    								@ 这个.代表了3这个标号的虚拟地址
    		
    	sub	r3, r3, r4				@ get offset between virt&phys 虚拟地址与物理地址的偏差	
    								@将r5,r6转换为实际的物理地址
    	add	r5, r5, r3				@ convert virt addresses to    
    	add	r6, r6, r3				@ physical address space
    
    	
    1:	ldr	r3, [r5, #MACHINFO_TYPE]	@ get machine type
    	teq	r3, r1					@ matches loader number?
    	beq	2f						@ found
    	add	r5, r5, #SIZEOF_MACHINE_DESC	@ next machine_desc
    	cmp	r5, r6
    	blo	1b
    	mov	r5, #0					@ unknown machine
    2:	mov	pc, lr
    
    

    首先是将虚拟地址转换为物理地址,因为这个时候UBOOT 启动内核时,MMU 还没启动,r3 这是实际存在的地址

    3:	.long	.
    	.long	__arch_info_begin
    	.long	__arch_info_end
    	
    adr	r3, 3b	
    

    接下来的r4, r5, r6都是虚拟地址了. .代表虚拟地址。是标号为 3的指令的虚拟地址.可以通过r3.(虚拟地址)来计算偏差.

    sub	r3, r3, r4	@ r3=r3-r4,也就r3=物理地址-虚拟地址的偏差
    实际物理地址=虚拟地址+r3即可
    
    add	r5, r5, r3				@ convert virt addresses to    
    add	r6, r6, r3				@ physical address space
    

    查看下__arch_info_begin和__arch_info_end具体是什么,这个是在链接脚本定义如下的,也就是代表了一个段

    __arch_info_begin = .;
     *(.arch.info.init)
    __arch_info_end = .;
    

    .arch.info.initarch.h 中有定义 ,定义某个结构体(machine_desc)的段属性

    #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_START(S3C2440, "SMDK2440")
    	/* Maintainer: Ben Dooks <ben@fluff.org> */
    	.phys_io	= S3C2410_PA_UART,
    	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    	.boot_params	= S3C2410_SDRAM_PA + 0x100,
    
    	.init_irq	= s3c24xx_init_irq,
    	.map_io		= smdk2440_map_io,
    	.init_machine	= smdk2440_machine_init,
    	.timer		= &s3c24xx_timer,
    MACHINE_END
    

    展开看看

      static const struct machine_desc __mach_desc_S3C2440	
      __used							
      __attribute__((__section__(".arch.info.init"))) = {	
      .nr		= MACH_TYPE_S3C2440,		
      .name		= SMDK2440,
      .phys_io	= S3C2410_PA_UART,
    	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    	.boot_params	= S3C2410_SDRAM_PA + 0x100,
    
    	.init_irq	= s3c24xx_init_irq,
    	.map_io		= smdk2440_map_io,
    	.init_machine	= smdk2440_machine_init,
    	.timer		= &s3c24xx_timer,
     };
    

    查看下machine_desc 这个结构体内容,可以发现支持多少单板,就有多少这个宏的使用

     struct machine_desc {
    	/*
    	 * Note! The first four elements are used
    	 * by assembler code in head-armv.S
    	 */
    	unsigned int		nr;		/* architecture number	*/
    	unsigned int		phys_io;	/* start of physical io	*/
    	unsigned int		io_pg_offst;	/* byte offset for io 
    						 * page tabe entry	*/
    
    	const char		*name;		/* architecture name	*/
    	unsigned long		boot_params;	/* tagged list		*/
    
    	unsigned int		video_start;	/* start of video RAM	*/
    	unsigned int		video_end;	/* end of video RAM	*/
    
    	unsigned int		reserve_lp0 :1;	/* never has lp0	*/
    	unsigned int		reserve_lp1 :1;	/* never has lp1	*/
    	unsigned int		reserve_lp2 :1;	/* never has lp2	*/
    	unsigned int		soft_reboot :1;	/* soft reboot		*/
    	void			(*fixup)(struct machine_desc *,
    					 struct tag *, char **,
    					 struct meminfo *);
    	void			(*map_io)(void);/* IO mapping function	*/
    	void			(*init_irq)(void);
    	struct sys_timer	*timer;		/* system tick timer	*/
    	void			(*init_machine)(void);
    };
    

    接下去就是从这个结构体读取第一个参数nr也就是ID来逐个比较了.这个在内核中定义与uboot定义是一致的

    #define MACH_TYPE_S3C2440              362
    

    启动MMU

    bl	__create_page_tables    @创建页表
    ldr	r13, __switch_data		@ address to jump to after,这是使能mmu后的跳转地址
    							@ mmu has been enabled
    adr	lr, __enable_mmu		@ return (PIC) address	使能mmu
    add	pc, r10, #PROCINFO_INITFUNC
    
    @__enable_mmu 中会调用 __turn_mmu_on,最后 mov	pc, r13
    b	__turn_mmu_on
    	.align	5
    	.type	__turn_mmu_on, %function
    __turn_mmu_on:
    	mov	r0, r0
    	mcr	p15, 0, r0, c1, c0, 0		@ write control reg
    	mrc	p15, 0, r3, c0, c0, 0		@ read id reg
    	mov	r3, r3
    	mov	r3, r3
    	mov	pc, r13                     @这个是关键,pc最后=r13=__switch_data
    

    其他操作

    复制数据段,清bss段等操作

    start_kernel

    启动mmu后会跳转到__switch_data,如何跳到 __switch_data ,在 __enable_mmu 中会调用 __turn_mmu_on这个函数最后 mov pc, r13,在调用__enable_mmu 前是先赋值的ldr r13, __switch_data

    ldr	r13, __switch_data		@ address to jump to after
    ....
    	b	start_kernel
    

    注意 这是内核的第一个 C 函数,接下来要处理UBOOT 传输的第三个启动参数bd->bi_boot_params.这个文件在init/main.c,在以下函数处理参数

    setup_arch(&command_line);
    setup_command_line(command_line);
    

    一览流程如下

    start_kernel
    	setup_arch			//解析uboot传入的参数,只是先存起来字符串
    	setup_command_line	//只是先存起来字符串
    	parse_args
    		do_early_param
    			从__setup_start 中调用early函数
    	unknown_bootoption
    		obsolete_checksetup
    			从__setup_start 中调用非early函数 段属性
    	rest_init
    		kernel_init
    			prepare_namespace
    				mount_root	//根文件系统,
                   init_post	// 执行应用程序
    

    setup_arch(解析tag)

    这里是先查找mdesc这个结构体,这个结构体在上面分析机器ID的时候已经发现他保存了一系列参数.boot_params就是uboot存放参数的地址.然后在parse_tags(tags)处理具体的tag

    if (mdesc->boot_params)
    	tags = phys_to_virt(mdesc->boot_params);
    	
    //在上面定义机器id结构体的时候,.boot_params	= S3C2410_SDRAM_PA + 0x100,=0x30000100
    #define S3C2410_CS6 (0x30000000)
    #define S3C2410_SDRAM_PA    (S3C2410_CS6)
    //我们在uboot的时候存储参数的地址也是这个
    board_init -----gd->bd->bi_boot_params = 0x30000100;
    然后开始处理tags
    
    
      static const struct machine_desc __mach_desc_S3C2440	
      __used							
      __attribute__((__section__(".arch.info.init"))) = {	
      .nr		= MACH_TYPE_S3C2440,		
      .name		= SMDK2440,
      .phys_io	= S3C2410_PA_UART,
    	.io_pg_offst	= (((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,
    	.boot_params	= S3C2410_SDRAM_PA + 0x100,
    
    	.init_irq	= s3c24xx_init_irq,
    	.map_io		= smdk2440_map_io,
    	.init_machine	= smdk2440_machine_init,
    	.timer		= &s3c24xx_timer,
     };
    
    

    setup_command_line

    所谓命令行,就是uboot设置的bootargs,linux通过getenv("bootargx")获取参数,如果没有设置这个参数,内部有一个默认参数. 这里只是将命令行复制到指定的数组,并没有处理

    char *from = default_command_line;		//这是默认的命令行参数
    
    static char default_command_line[COMMAND_LINE_SIZE] __initdata = CONFIG_CMDLINE;
    #define CONFIG_CMDLINE "root=/dev/hda1 ro init=/bin/bash console=ttySAC0"
    
    memcpy(boot_command_line, from, COMMAND_LINE_SIZE);
    boot_command_line[COMMAND_LINE_SIZE-1] = '';
    parse_cmdline(cmdline_p, from); //		
    // cmdline_p 是 传递的参数,用作拷贝
    // from 是默认参数
    

    挂载根文件系统

    创建一个线程,可以理解为运行程序kernel_init

    rest_init
    		kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
    	kernel_init
    		>prepare_namespace
    			>mount_root 挂在根文件系统
            >init_post(); 执行应用程序
    

    处理命令行

    uboot设置命令tag,多了参数commandline,源自环境变量bootargs查看下环境变量bootargs,使用print查看,也可搜索下代码

    "bootargs="	CONFIG_BOOTARGS			""
    //include/configs/100ask24x0.h
    #define CONFIG_BOOTARGS "noinitrd root=/dev/mtdblock3 init=/linuxrc console=ttySAC0"
    
    • root=/dev/mtdblock3表示根文件系统从第四个FLASH分区开始(从0开始计数)可以往上看分区空间
    • init=/linuxrc指示第一个应用程序
    • console=ttySAC0,内核打印信息从串口0 打印

    mark

    我们需要知道ROOT_DEV是什么,可以看到在函数prepare_namespace中有saved_root_name存储这个这个数组.

    if (saved_root_name[0]) {
        root_device_name = saved_root_name;
        if (!strncmp(root_device_name, "mtd", 3)) {
            mount_block_root(root_device_name, root_mountflags);
            goto out;
        }
        ROOT_DEV = name_to_dev_t(root_device_name);
        if (strncmp(root_device_name, "/dev/", 5) == 0)
            root_device_name += 5;
    }
    

    搜索下saved_root_nameroot_dev_setup对齐赋值,再继续查找函数的引用,只有一个宏使用了它

    static int __init root_dev_setup(char *line)
    {
    	strlcpy(saved_root_name, line, sizeof(saved_root_name));
    	return 1;
    }
    

    解析下这个宏__setup("root=", root_dev_setup);

    #define __setup(str, fn)					
    	__setup_param(str, fn, fn, 0)
    
    #define __setup_param(str, unique_id, fn, early)			
    	static char __setup_str_##unique_id[] __initdata = str;	
    	static struct obs_kernel_param __setup_##unique_id	
    		__attribute_used__				
    		__attribute__((__section__(".init.setup")))	
    		__attribute__((aligned((sizeof(long)))))	
    		= { __setup_str_##unique_id, fn, early }
    
    
    
    
    static char __setup_str_root_dev_setup[] __initdata = "root=";
    static struct obs_kernel_param __setup_root_dev_setup 
        __attribute_used__
        __attribute__((__section__(".init.setup")))	
    	__attribute__((aligned((sizeof(long)))))	
        ={
        	__setup_str_root_dev_setup,root_dev_setup,root_dev_setup,0
     	}
     	
    这个结构体的原型如下
    struct obs_kernel_param 
    {
    	const char *str;
    	int (*setup_func)(char *);
    	int early;
    };
    

    最终大概分析一下也就是定义了一个char数组和一个有特殊段属性.init.setup的结构体.注意这里的early是0.这个段属性肯定是在lds中定义,搜索下这个段的起始和结束地址的调用情况.

      __setup_start = .;
       *(.init.setup)
      __setup_end = .;
      
      obsolete_checksetup
      do_early_param
      
    static int __init do_early_param(char *param, char *val)
    {
    	struct obs_kernel_param *p;
    
    	for (p = __setup_start; p < __setup_end; p++) {
    		if (p->early && strcmp(param, p->str) == 0) {
    			if (p->setup_func(val) != 0)
    				printk(KERN_WARNING
    				       "Malformed early option '%s'
    ", param);
    		}
    	}
    	/* We accept everything at this stage. */
    	return 0;
    }
    
    static int __init obsolete_checksetup(char *line)
    {
    	struct obs_kernel_param *p;
    	int had_early_param = 0;
    
    	p = __setup_start;
    	do {
    		int n = strlen(p->str);
    		if (!strncmp(line, p->str, n)) {
    			if (p->early) {
    				/* Already done in parse_early_param?
    				 * (Needs exact match on param part).
    				 * Keep iterating, as we can have early
    				 * params and __setups of same names 8( */
    				if (line[n] == '' || line[n] == '=')
    					had_early_param = 1;
    			} else if (!p->setup_func) {
    				printk(KERN_WARNING "Parameter %s is obsolete,"
    				       " ignored
    ", p->str);
    				return 1;
    			} else if (p->setup_func(line + n))
    				return 1;
    		}
    		p++;
    	} while (p < __setup_end);
    
    	return had_early_param;
    }
    

    可以看出obsolete_checksetup先判断这个结构的early属性,为0则执行setup_func方法,这符合我们的这个宏__setup("root=", root_dev_setup);

    结论:挂接根文件系统的参数是由命令行给出的,内核函数去分析这个命令行,去赋值ROOT_DEV

    mark

    分区

    分区表是没有的,是代码里面写死的,我们可以启动内核的时候发现有以下输出

    Creating 4 MTD partitions on "NAND 256MiB 3,3V 8-bit":
    0x00000000-0x00040000 : "bootloader"
    0x00040000-0x00060000 : "params"
    0x00060000-0x00260000 : "kernel"
    0x00260000-0x10000000 : "root"
    
    

    可以搜索这个"bootloader"发现在arm/plat-s3c24xx/common-smdk.c下面定义

    static struct mtd_partition smdk_default_nand_part[] = {
    	[0] = {
            .name   = "bootloader",
            .size   = 0x00040000,
    		.offset	= 0,
    	},
    	[1] = {
            .name   = "params",
            .offset = MTDPART_OFS_APPEND,
            .size   = 0x00020000,
    	},
    	[2] = {
            .name   = "kernel",
            .offset = MTDPART_OFS_APPEND,
            .size   = 0x00200000,
    	},
    	[3] = {
            .name   = "root",
            .offset = MTDPART_OFS_APPEND,
            .size   = MTDPART_SIZ_FULL,
    	}
    };
    

    MTDPART_OFS_APPEND 这个 offset 意思是紧接着上面一个分区的意思

  • 相关阅读:
    mac 切换 默认xcode 版本
    mac 查看jenkins 管理员密码地址
    解决 mac ox 终端显示bogon 的问题
    eclipse 修改默认作者信息
    mac 查看 本地网络代理
    appium 解决 启动case 时不 重装 setting 和 unlock.apk的解决方案实践
    appium 输入时间慢的解决方案
    命令 关闭 appium 命令
    解决 appium could not start ios-webkit-debug-proxy
    PPT总结
  • 原文地址:https://www.cnblogs.com/zongzi10010/p/10023696.html
Copyright © 2011-2022 走看看