ARM linxu启动过程分析(四)

2024-04-16 08:32
文章标签 分析 启动 过程 arm linxu

本文主要是介绍ARM linxu启动过程分析(四),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

1 Linux内核启动第三阶段start_kernel

内核从现在开始就进入C语言部分,内核启动第三阶段从init/main.c文件中的start_kernel()函数开始,到该函数结束。这一阶段对整个系统内存、cache、信号、设备等进行初始化,最后生成init进程后,调用cpu_idle()完成内核启动的第三阶段。Start_kernel()中调用了一系列的初始化函数,以完成kernel本身的设置。这些动作有些是公共的,有的则是需要配置才会执行的。

asmlinkage void __initstart_kernel(void)

{

char * command_line;

extern struct kernel_param__start___param[], __stop___param[];

/*中断是禁止的,作必要的设置之后,使能中断*/

lock_kernel();

/*如果内核配置成支持抢占,那么在这里禁止抢占,将0号进程的init_thread_info.preempt_count1,如果配置成不支持抢占,那么内核全局自旋锁kernel_flag上锁*/

page_address_init();   /*ARM9不支持高端内存(>896MB,一般嵌入式产品也不会用高端内存,所以这里是空函数*/

printk(KERN_NOTICE);

/*linux_banner的内容打印到log_buf缓冲区中去,等到串口或者其它终端初始化之后,在一次性打印到终端上去*/

printk(linux_banner);

setup_arch(&command_line);

/*   setup_arch()原型在arch/arm/kernel/setup.c中,根据处理器硬件平台设置系统;

解析linux命令行参数,设置0号进程的内存描述结构init_mm,系统内存管理初始化,

统计并注册系统各种资源,其它项目的初始化 */

setup_per_cpu_areas();//为系统中的每个cpuper_cpu变量申请空间

/* Mark the boot cpu "online"so that it can call console drivers in

 *printk() and can access its per-cpu storage.      */

smp_prepare_boot_cpu();

/*开启任何中断之前,初始化调度器.Full topology setup happens at smp_init()

 * time- but meanwhile we still have a functioning scheduler.

 初识化每个处理器的可运行进程队列,设置系统初始化进程,即0号进程;*/

sched_init();

preempt_disable();  //禁止抢占

build_all_zonelists();//建立系统内存页区(zone)链表

page_alloc_init();

printk(KERN_NOTICE "Kernelcommand line: %s/n", saved_command_line);

parse_early_param();//解析早期格式的内核参数

parse_args("Bootingkernel", command_line, __start___param,

          __stop___param- __start___param,

          &unknown_bootoption);//解析新格式内核参数

sort_main_extable();/* 排序内核内建的异常表,将__start___ex_table__stop___ex_table之间的*(__ex_table)区中的structexception_table_entry型全局结构变量按insn成员变量值从小到大排序,即将可能导致缺页异常的指令按期指令二进制代码值从小到大排序。*/

trap_init();/* Copy thevectors, stubs and kuser helpers (in entry-armv.S)

 * intothe vector page, mapped at 0xffff0000, and ensure these

 * arevisible to the instruction stream.  */

 /*  * Copy signal return handlers into the vector page, and

 * setsigreturn to be a pointer to these. */

rcu_init();

 /* *Initializes rcu mechanism.  Assumed to be called early.

 * Thatis before local timer(SMP) or jiffie timer (uniproc) is setup.

 * Notethat rcu_qsctr and friends are implicitly

 *initialized due to the choice of ``0'' for RCU_CTR_INVALID. */

init_IRQ();/*中断初始化,初始化系统中支持的最大可能的中断描述结构structirqdesc变量数组irq_desc[NR_IRQS],把每个结构变量irq_desc[n]都初始化为预先定义好的坏中断描述结构变量bad_irq_desc,并初始化该中断的链表表头成员结构变量pend.*/

pidhash_init();/*

 * Thepid hash table is scaled according to the amount of memory in the

 *machine.  From a minimum of 16 slots up to 4096 slots at onegigabyte or

 *more.设置系统中每种pidhash表中的hash链表数的移位值全局变量pidhash_shift,pidhash_shift设置为mini(12);分别为每种hash表的连续hash链表表头结构空间申请内存,把申请到的内存虚拟基址分别传给pid_hash[n](n=0~3),并将每种hash表中的每个hash链表表头结构structhlist_head中的first成员指针设置成NULL.

 */

init_timers();

softirq_init();

time_init();//检查系统定时器描述结构structsys_timer全局变量system_timer是否为空,如果为空将其指向dummy_gettimeoffset()函数。

/*

 * HACKALERT! This is early. We're enabling the console before

 *we've done PCI setups etc, and console_init() must be aware of

 *this. But we do want output early, in case something goes wrong.

 */

console_init();//初始化系统控制台结构,该函数执行后可调用printk()函数将log_buf

//中符合打印级别要求的系统信息打印到控制台上。

if (panic_later)

        panic(panic_later,panic_param);

profile_init();//对系统剖析做相关初始化,系统剖析用于系统调用

local_irq_enable(); //使能IRQ

#ifdef CONFIG_BLK_DEV_INITRD

if (initrd_start &&!initrd_below_start_ok &&

              initrd_start <min_low_pfn << PAGE_SHIFT) {

        printk(KERN_CRIT"initrd overwritten (0x%08lx < 0x%08lx) - "

           "disablingit./n",initrd_start,min_low_pfn << PAGE_SHIFT);

        initrd_start= 0;

}

#endif

vfs_caches_init_early();

mem_init();

//该函数执行完成之后,就不能再用像alloc_bootmem(),alloc_bootmem_low(),alloc_bootmem_pages()等申请低端内存的函数来申请内存,也就不能再申请大块连续的物理内存了。

kmem_cache_init();//slab分配器的相关初始化,执行高速缓存内存管理

setup_per_cpu_pageset();

numa_policy_init();

if (late_time_init)

        late_time_init();

calibrate_delay();/*

 * Thisis the number of bits of precision for the loops_per_jiffy. Each

 * bittakes on average 1.5/HZ seconds.  This (like the original) is alittle

 *better than 1%

 */

pidmap_init();

pgtable_cache_init();

prio_tree_init();

anon_vma_init();

#ifdef CONFIG_X86

if (efi_enabled)

       efi_enter_virtual_mode();

#endif

fork_init(num_physpages);//执行进程创建相关的初始化

proc_caches_init();

buffer_init();

unnamed_dev_init();

key_init();

security_init();  //security_init - initializes the security framework

vfs_caches_init(num_physpages);

radix_tree_init();

signals_init();

/*调用函数kmem_cache_create("sigqueue",sizeof(struct sigqueue), __alignof__(struct sigqueue), SLAB_PANIC,NULL, NULL); 为信号队列结构structsigqueque创建高速缓存内存描述结构kmem_cache_t变量,名字叫”sigqueue”,不要求其对象按处理器硬件cacheline大小对齐,没有定义其对象的构造和析构函数,将创建的kmem_cache_t结构变量的地址传给全局指针sigqueue_cachep.*/

/* rootfs populating mightneed page-writeback */

page_writeback_init();

#ifdef CONFIG_PROC_FS

proc_root_init();

 //在系统支持proc文件系统即配置了CONFIG_PROC_FS选项时被调用

#endif

cpuset_init();

check_bugs();

acpi_early_init(); /* beforeLAPIC and SMP init */

/* Do the rest non-__init'ed,we're now alive */

rest_init();

/*   创建init()进程,即1号进程,启动调度器,然后系统启动进程即0号进程进入空闲;*/

}

下面来分析几个非常重要的函数:

首先主要分析setup_arch()函数,定义在arch/arm/kernel/setup.c文件中,它完成体系结构相关的初始化,内核移植的过程一般也就到此函数为止了,其余的就只是一些相关的外设驱动。

Setup_arch()中的第一个主要函数为setup_processor(),它只是简单的遍历__proc_info_begin开始的proc_info_list找到相应的表项,并从中得到proc_info结构。注意这些proc_info_list结构定义在arch/arm/mm/proc_arm920.S文件中。

然后就是setup_machine函数,它根据machine_arch_type[机器ID],在__arch_info_begin中找到对应的machine_desc表项。对于FS2410(实际上这里用的是SMDK2410的相关内容,因为二者基本相同,所以在移植的时候没有修改这一部分,用的还是SMDK2410的机器ID)arch/arm/mach-s3c2410/mach-smdk2410.c中定义为:

MACHINE_START(SMDK2410,"SMDK2410") /* @TODO: request a new identifier and switch

                         *to SMDK2410 */

/* Maintainer: Jonas Dietsche*/

.phys_ram    = S3C2410_SDRAM_PA,

.phys_io  =S3C2410_PA_UART,

.io_pg_offst   =(((u32)S3C24XX_VA_UART) >> 18) & 0xfffc,

.boot_params =S3C2410_SDRAM_PA + 0x100,

.map_io        = smdk2410_map_io,

.init_irq  =smdk2410_init_irq,

 .init_machine  = sdmk2410_init,

.timer           = &s3c24xx_timer,

MACHINE_END

接着需要解释的就是u-boot给内核传参数了,linux-2.6.14没有通过u-boot引导时的thekernel的第三个参数传递u-boot传递给内核参数的位置,即boot_params,而是通过指定机器描述结构的mdesc->boot_params来制订u-boot传给内核参数的位置,这时需要先就参数进行分析,由parse_tags(tags);来完成,其中tags= phys_to_virt(mdesc->boot_params);

paging_init(&meminfo,mdesc);是一个非常重要的函数尤其在移植内核时,主要完成页表的初始化。----〉调用memtable_init(mi);初始化内存页表----create_mapping(init_maps)创建页目录项荷页表项。

另外在paging_init()函数中还会调用mdesc->map_io();来完成平台相关的IO映射。

接着就是 设置平台相关的指针

init_arch_irq =mdesc->init_irq; //start_kernel接下来的init_IRQ()函数中会掉用该函数完成平台相关的中断初始化。

system_timer = mdesc->timer;//start_kernel接下来的time_init()函数中会掉用该结构体中的内容完成平台相关的定时器初始化。

init_machine =mdesc->init_machine;

setup_arch()的分析到此结束。

这里再简单分析一下reset_init()函数:

static void noinlinerest_init(void)

__releases(kernel_lock)

{

kernel_thread(init, NULL,CLONE_FS | CLONE_SIGHAND);  //启动init线程

numa_default_policy();

unlock_kernel();

preempt_enable_no_resched();

/* The boot idle thread mustexecute schedule()at least one to get things moving: */

schedule();  //启动调度器

cpu_idle();//cpu空闲,实际上就是启动进程进入空闲状态,系统交给调度器来管理

}

再来看一下启动init线程的过程:

pid_t kernel_thread(int(*fn)(void *), void *arg, unsigned long flags)

{

struct pt_regs regs;

memset(&regs, 0,sizeof(regs));

regs.ARM_r1 = (unsignedlong)arg;

regs.ARM_r2 = (unsignedlong)fn;

regs.ARM_r3 = (unsignedlong)do_exit;

regs.ARM_pc = (unsignedlong)kernel_thread_helper;

regs.ARM_cpsr = SVC_MODE;

returndo_fork(flags|CLONE_VM|CLONE_UNTRACED, 0, &regs, 0, NULL, NULL);

//调用do_fork函数创建init内核线程。

}

接着我们来看一下这个init线程的内容,它是init/main.c中的一个函数,代码如下:

static int init(void *unused)

{

lock_kernel();

set_cpus_allowed(current,CPU_MASK_ALL);

…… …… …… …….

/* Do this before initcalls,because some drivers want to access firmware files.      */

populate_rootfs();

do_basic_setup();//很重要的一个函数,做一些基本的设置

…… …… …… …….

/* Ok, we have completed theinitial bootup, and we're essentially up and running. Get rid of theinitmem segments and start the user-mode stuff..  */

free_initmem();

unlock_kernel();

system_state =SYSTEM_RUNNING;

numa_default_policy();

if (sys_open((const char__user *) "/dev/console", O_RDWR, 0) < 0)//打开终端

       printk(KERN_WARNING"Warning: unable to open an initial console./n");

(void) sys_dup(0);   //dup文件描述符1

(void) sys_dup(0);   //dup文件描述符2

…… …… …… …….

run_init_process("/sbin/init"); //启动init进程

run_init_process("/etc/init");  //启动init进程

run_init_process("/bin/init");  //启动init进程

run_init_process("/bin/sh");   //启动shell

panic("No init found. Try passing init= option to kernel.");

}

/*机器已经初始化完成,cpu子系统已经起来正在运行,内存和进程管理已经工作,接下来要做些真正的工作了 */

static void __initdo_basic_setup(void)

{

/* drivers will send hotplugevents */

init_workqueues(); //初始化工作队列

usermodehelper_init();//用户模式help程序

driver_init();  //设备初始化

#ifdef CONFIG_SYSCTL

sysctl_init();

#endif

/* Networking initializationneeds a process context */

sock_init();

do_initcalls(); //非常重要的一个函数,会在这里执行驱动模块加载函数,也就module_init()的函数。

}

do_initcalls()的一部分代码如下:

static void __initdo_initcalls(void)

{

initcall_t *call;

int count = preempt_count();

for (call = __initcall_start;call < __initcall_end; call++)

{

              char *msg;

              (*call)();//真正执行驱动模块加载函数的地方

}

最后一个要分析的问题是:关于设备驱动什么时候调用,以网卡驱动为例来进行分析,这里采用的是CS8900A

驱动里面的模块注册函数module_init(xxx_init);

网卡注册过程:

cs8900_init()---register_netdve(&cs8900_dev)---register_netdevice()

cs8900_dev定义如下:

int cs8900_probe (structnet_device *dev);

static struct net_devicecs8900_dev =

{

        init:cs8900_probe

};

其调用过程:

Register_netdev(structnet_device *dev)----register_netdevice(structnet_device * dev)—dev->init

这样实际在启动过程中会执行cs8900_probe,里面会对网卡进行侦测,注册中断,DMA中断等一些操作。

cs8900_init()会在内核启动的时候被执行:

Start_kernel()---reset_init()---〉起了init内核线程----do_basic_setup()---do_initcalls()

--------for(call=__initcall_start;call<__initcall_end;call++){

(*call)();--------------此处会执行cs8900_init,以及其他驱动程序的module_init函数。

}

Include/linux/init.h中关于initcall的定义,这里简单描述,如果感兴趣可以查阅该文件。

#ifndef MODULE

#ifndef __ASSEMBLY__

#define__define_initcall(level,fn) /

staticinitcall_t __initcall_##fn __attribute_used__ /

__attribute__((__section__(".initcall"level ".init"))) = fn

//Initcall定义了7个级别,我们关系的设备initcall位于级别6

#definecore_initcall(fn)            __define_initcall("1",fn)

#definepostcore_initcall(fn)             __define_initcall("2",fn)

#definearch_initcall(fn)            __define_initcall("3",fn)

#definesubsys_initcall(fn)         __define_initcall("4",fn)

#definefs_initcall(fn)                __define_initcall("5",fn)

#definedevice_initcall(fn)             __define_initcall("6",fn)

#definelate_initcall(fn)             __define_initcall("7",fn)

 

#define__initcall(fn)  device_initcall(fn)

 

#define__exitcall(fn) /

staticexitcall_t __exitcall_##fn __exit_call = fn

#define console_initcall(fn)/

static initcall_t__initcall_##fn /

__attribute_used____attribute__((__section__(".con_initcall.init")))=fn

#define security_initcall(fn)/

static initcall_t__initcall_##fn /

__attribute_used____attribute__((__section__(".security_initcall.init"))) =fn

#define __setup_param(str,unique_id, fn, early)                 /

static char__setup_str_##unique_id[] __initdata = str; /

static structobs_kernel_param __setup_##unique_id   /

       __attribute_used__                          /

       __attribute__((__section__(".init.setup")))    /

       __attribute__((aligned((sizeof(long)))))   /

        ={ __setup_str_##unique_id, fn, early }

#define__setup_null_param(str, unique_id)   __setup_param(str, unique_id, NULL, 0)

#define __setup(str,fn)             __setup_param(str, fn, fn, 0)

#define__obsolete_setup(str)     __setup_null_param(str,__LINE__)

#define early_param(str,fn)             __setup_param(str, fn, fn, 1)

void __initparse_early_param(void);

#endif /* __ASSEMBLY__ */

#definemodule_init(x)      __initcall(x);

#definemodule_exit(x)     __exitcall(x);

#else /* MODULE   定义为模块 */

#definecore_initcall(fn)            module_init(fn)

#definepostcore_initcall(fn)             module_init(fn)

#definearch_initcall(fn)            module_init(fn)

#definesubsys_initcall(fn)         module_init(fn)

#definefs_initcall(fn)                module_init(fn)

#definedevice_initcall(fn)         module_init(fn)

#definelate_initcall(fn)             module_init(fn)

#definesecurity_initcall(fn)       module_init(fn)

 

#definemodule_init(initfn)               /

static inline initcall_t__inittest(void)            /

{ return initfn;}                             /

int init_module(void)__attribute__((alias(#initfn)));

#definemodule_exit(exitfn)                            /

static inline exitcall_t__exittest(void)           /

{ return exitfn;}                             /

void cleanup_module(void)__attribute__((alias(#exitfn)));

#define __setup_param(str,unique_id, fn)       /* nothing */

#define__setup_null_param(str, unique_id)    /* nothing */

#define __setup(str, func)               /* nothing */

#define __obsolete_setup(str)                /* nothing */

#endif



这篇关于ARM linxu启动过程分析(四)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/908287

相关文章

浅析Spring Security认证过程

类图 为了方便理解Spring Security认证流程,特意画了如下的类图,包含相关的核心认证类 概述 核心验证器 AuthenticationManager 该对象提供了认证方法的入口,接收一个Authentiaton对象作为参数; public interface AuthenticationManager {Authentication authenticate(Authenti

作业提交过程之HDFSMapReduce

作业提交全过程详解 (1)作业提交 第1步:Client调用job.waitForCompletion方法,向整个集群提交MapReduce作业。 第2步:Client向RM申请一个作业id。 第3步:RM给Client返回该job资源的提交路径和作业id。 第4步:Client提交jar包、切片信息和配置文件到指定的资源提交路径。 第5步:Client提交完资源后,向RM申请运行MrAp

性能分析之MySQL索引实战案例

文章目录 一、前言二、准备三、MySQL索引优化四、MySQL 索引知识回顾五、总结 一、前言 在上一讲性能工具之 JProfiler 简单登录案例分析实战中已经发现SQL没有建立索引问题,本文将一起从代码层去分析为什么没有建立索引? 开源ERP项目地址:https://gitee.com/jishenghua/JSH_ERP 二、准备 打开IDEA找到登录请求资源路径位置

MySQL数据库宕机,启动不起来,教你一招搞定!

作者介绍:老苏,10余年DBA工作运维经验,擅长Oracle、MySQL、PG、Mongodb数据库运维(如安装迁移,性能优化、故障应急处理等)公众号:老苏畅谈运维欢迎关注本人公众号,更多精彩与您分享。 MySQL数据库宕机,数据页损坏问题,启动不起来,该如何排查和解决,本文将为你说明具体的排查过程。 查看MySQL error日志 查看 MySQL error日志,排查哪个表(表空间

springboot3打包成war包,用tomcat8启动

1、在pom中,将打包类型改为war <packaging>war</packaging> 2、pom中排除SpringBoot内置的Tomcat容器并添加Tomcat依赖,用于编译和测试,         *依赖时一定设置 scope 为 provided (相当于 tomcat 依赖只在本地运行和测试的时候有效,         打包的时候会排除这个依赖)<scope>provided

内核启动时减少log的方式

内核引导选项 内核引导选项大体上可以分为两类:一类与设备无关、另一类与设备有关。与设备有关的引导选项多如牛毛,需要你自己阅读内核中的相应驱动程序源码以获取其能够接受的引导选项。比如,如果你想知道可以向 AHA1542 SCSI 驱动程序传递哪些引导选项,那么就查看 drivers/scsi/aha1542.c 文件,一般在前面 100 行注释里就可以找到所接受的引导选项说明。大多数选项是通过"_

【机器学习】高斯过程的基本概念和应用领域以及在python中的实例

引言 高斯过程(Gaussian Process,简称GP)是一种概率模型,用于描述一组随机变量的联合概率分布,其中任何一个有限维度的子集都具有高斯分布 文章目录 引言一、高斯过程1.1 基本定义1.1.1 随机过程1.1.2 高斯分布 1.2 高斯过程的特性1.2.1 联合高斯性1.2.2 均值函数1.2.3 协方差函数(或核函数) 1.3 核函数1.4 高斯过程回归(Gauss

用命令行的方式启动.netcore webapi

用命令行的方式启动.netcore web项目 进入指定的项目文件夹,比如我发布后的代码放在下面文件夹中 在此地址栏中输入“cmd”,打开命令提示符,进入到发布代码目录 命令行启动.netcore项目的命令为:  dotnet 项目启动文件.dll --urls="http://*:对外端口" --ip="本机ip" --port=项目内部端口 例: dotnet Imagine.M

SWAP作物生长模型安装教程、数据制备、敏感性分析、气候变化影响、R模型敏感性分析与贝叶斯优化、Fortran源代码分析、气候数据降尺度与变化影响分析

查看原文>>>全流程SWAP农业模型数据制备、敏感性分析及气候变化影响实践技术应用 SWAP模型是由荷兰瓦赫宁根大学开发的先进农作物模型,它综合考虑了土壤-水分-大气以及植被间的相互作用;是一种描述作物生长过程的一种机理性作物生长模型。它不但运用Richard方程,使其能够精确的模拟土壤中水分的运动,而且耦合了WOFOST作物模型使作物的生长描述更为科学。 本文让更多的科研人员和农业工作者

MOLE 2.5 分析分子通道和孔隙

软件介绍 生物大分子通道和孔隙在生物学中发挥着重要作用,例如在分子识别和酶底物特异性方面。 我们介绍了一种名为 MOLE 2.5 的高级软件工具,该工具旨在分析分子通道和孔隙。 与其他可用软件工具的基准测试表明,MOLE 2.5 相比更快、更强大、功能更丰富。作为一项新功能,MOLE 2.5 可以估算已识别通道的物理化学性质。 软件下载 https://pan.quark.cn/s/57