首页 » SEO优化 » phpparse_cmdline技巧_90后轨范员小伙分享Linux内核kernel启动分析下篇精品推荐

phpparse_cmdline技巧_90后轨范员小伙分享Linux内核kernel启动分析下篇精品推荐

访客 2024-12-10 0

扫一扫用手机浏览

文章目录 [+]

三、VMLINUX内核启动代码剖析

从下面开始就进入了真正的内核了,首先实行的代码是在

phpparse_cmdline技巧_90后轨范员小伙分享Linux内核kernel启动分析下篇精品推荐

arch/arm/kernel/head.S中的汇编代码,然后就进入了内核的c代码部分

phpparse_cmdline技巧_90后轨范员小伙分享Linux内核kernel启动分析下篇精品推荐
(图片来自网络侵删)

(init/Main. c),把稳,此刻内核的编译链接地址不再是0x0 了,而是0xC0008000

了。

内核汇编部分:(关于该部分参考我的另一篇详细的剖析)

刚刚进入内核汇编时的一些系统状态和寄存器值如下

Mmu 关闭,I Cache 和D Cache 打消并关闭,r0=0, rl=architecture ID。

从arch/arm/kernel/vmlinux. Ids文件中可以看出,解压后内核是从stext

段开始实行的。

1. 确保系统处于SVC模式并且FIR、IRQ都已经关闭、

2. 调用_lookup_processor_type函数,通过协处理器cpl5读取出系统cpu

的id,然后通过查無 [^核映像中 .proc. info, init段的所有

proc_info_list数据构造,以判断该内核是否支持该款cpu。

3. 调用_lookup_machine_type函数,检査由uboot通报进来的machine

architecture number是否被内核支持,也便是在内核

的 .arch. info, init段中查找看是否有对应 machine number的

machine_desc数据构造体存在。

下面进入start_kernel函数去实行,in init/Main. c文件

1. printk(linu:x_banner)打印内核的一些信息,版本,作者,编译器版本,日

期等信息

2. setup_arch(&command_line); /氺 in arm/kernel/setup. c /

函数原型:void —init |setup_arch|(char cmdline』)

批注[si4]:嗯,重中之重啊

a. setup_processor()+lookup_processor_type〇 获取对应处理器 id 的

proc_info_list 构造体 list,取出 cpu_name,打印关于 epuname,id,

proc_arch 的信息,设置上 system_utsname= list->arch_name(armv4t),

elf_platform= list->elf_name (v4), elf_hwcap = list->elf_hwcap;

/ l|2|4 /,接着实行 cpu_proc_init()函数

Cpu-single. h中有如下定义

#define cpu_proc_init —cpu_fn(CPU_NAME, _proc_init)

#def ine —cpu_fn (name, x) —catify_fn (name, x)

#def ine —catify_fn (name, x) narae##x

CPU_NAME = cpu_arm920

以是 cpu_proc_init();函数实际上是函数 cpu_arm920_proc_init()函

数,在pr〇c_arm920. S文件中定义的。

b. mdesc = setup—machine (machine—arch—type)函数

取出当前系统的在内核.arch. info, init段内对应的machine_desc数据

构造体的地址list,并打印出list-name内容

c. machine_name = mdesc->name 设置全局变量 machine_name

d. tags = phys_to_virt(mdesc_>boot_params),修正默认的参数地址,使

用uboot传运进^的参数的真实地址0x30000100并将其转换成虚拟地址

OxcOOOOlOO

e. 参数解析

if (tags->hdr. tag == ATAG_C0RE) {

if (meminfo. nr—banks != 0) / meminfo defined in setup, c /

squash_mem_tags(tags);

parse_tags(tags);

} _

static struct meminfo meminfo —initdata = { 0, };in steup. c

以是这里会调用parse_tags(tags) (in steup. c)函数。

Steup_arch ()-->parse_tags ()-->parse_tag (),

all function in steup. c

_tagtable_begin, _tagtable_end 这两个参数是在

arch/arm/kernel/vmlinux. Ids文件中确定的,这个区间存放了各种参

数的解析函数,可以直接引用

parSe_tag〇函数便是查找这个区间的各种参数头,来和通报寄来的参数

头进行匹配,找到了之后就利用个中的函数指针parse来进行参数解析,

详细怎么解析须要剖析各种参数的浸染了。
如果匹g不到的话,会打印

信息说 Ignoring unrecognised tag 0x%08x\n,表示 uboot 通报进来了

一个内核识别不了的参数。

f. struct mm_struct init_mm = INIT_MM(init_mm);

start_code = (unsigned long) &_text;

init_mm. end_code = (unsigned long) &_etext;

init_mm. end_data - (unsigned long) &_edata;

init mm. brk = (unsigned long) &—end;

_text, _etext, _edata, _end 都是在 arch/arm/kernel/vmlinux. Ids

文件中#定的

g. parse_cmdline(cmdline_p,from)解析 uboot 通报寄来的 commond_line

字符串,通过from指针内的参数解析到command_line这个全局数组里,

然后将该数组的地址赋值给上上级传进来的参数 command_line

(start_kernerl)。
这里只剖析了当中的mem、initrd部分,其余一处是

start_kernel()->parse_option〇用于解析别的部分。
将 mem 和 initrd

参数从原始 cormnand_line中扣出,留下的部分放在 cmdline_p中

(start_kernel传进来的参数,第二次剖析会用到)。
由于接下来是建立

内存的映射页表,须要用到mem、initrd两个参数的内容,以是在这里

提前解析出来。

h. paging_init(&meminfo,mdesc);/这部分的紧张事情建立页表,初始

化内存 / in arch/arm/imn/init. c 文件中

memtable_init (mi)为系统内存创建页表,是一个比较主要的函数,

详细的参考网址

http://bbs. sjtu. edu. cn/bbstcon, board, Embedded, reid, 1165977462.

html

的内容,须要把稳的是这个函数中将中断向量表映射到了 OxffffOOOO

开始处

调用 mdesc->map_io() 函数,在 smdk2410 中位于

arch/arm/mach-s3c2410/mach-sindk2410. c文件中的 smdk2410_map_io()

函数,关于这部分的外设的静态映射剖析请_考易松华老师

Linux静态映射剖析,smdk2410_map_io()函数紧张做了下M冗祥攀十青:

1 )、iotable_init(s3c_iodesc,ARRAY_SIZE(s3c_iodesc))建立

GPIO, IRQ, MEMCTRL, UART 的#态映射表

2)

、(cpu->map_io) (mach_desc, size)建立其他外设的静态映射表,

包括网卡,LCD,看门狗等

这里须要明确一点的是在map. h文件中的

S3C2410_ADDR(x) ((void _iomem )0xF0000000 + (x))

表明我们处理的10和外设的寄存器被静态映射到了 OxFOOOOOOO开

始地方,个中每一种外设的空间占1M大小。

这里必须得提到的是内存的三种映射和利用办法:1.处理器的GPI0,各

种外设(IRQ, UART, MEMCRTL, WATCHDOG, USB等)的寄存器的静态映射,

也便是上面赤色字体表示出来的。
这是映射到内核空间内,以是用户程

序是不能访问的,只能由内核来访问。
2.内核空间访问的虚拟地址(比

如是代码的地址或者是kmalloc空间的地址)都是在0x30008000开始后

的空间,也便是说这中虚拟地址是通过静态映射得到的,这部分也只能

由内核访问。
phys_to_virt和virt_to_phys,3.除此之外的物理地址

都是通过mmu的规射出来的 ,TTB已"^确定在0x30004000开始的16K

空间便是一级页表的空间。
通过这中办法映射的虚拟地址用户程序和内

核程序都可以访问,不过解析式通过 ramu来完成的而已。
参考

arch/arm/kernel/head. S中建立的4M空间手工页表便是按照mmu的规则

建立的(只不过是按照sector的办法建立)

3) 、对时钟,Uart的大略初始化。

h.request_standard_resources(&meminfo, mdesc) 为 memory ,

kernel_text, kernel_data,video_ram在资源树种申请标准资源。
它完成

实际的备源分配事情,如果参数new所描述的资源中的一部分或全部已经被

其它节点所占用,则函数返回与new相冲突的resource构造的指针。
否则

就返回NULL

i. cpu_init()函数剖析 in arch/arm/kernel/setup. c

打印一些关于cpu的信息,比如cpu id, cache大小等。
其余主要的是

设置了 IRQ、ABT、UND三种模式的stack空间,分别都是12个字节。
末了

将系统切换到svc模式。

i.用_mach_desc_SMDK2410_type构造体中特定成员来初始化系统的这么

写全局变量 init_arch_irq, init_machine, system_timer

3. sched_init()函数

初始&每个处理器的可运行行列步队,设置系统初始化进程即0号进程。
也便是

调用了 init_idie (current, smp_processor_id〇)初始化 idle 当提高程。

4. pre empt_d i sab 1 e ()禁止抢占

5. 建立系统内存页区(zone)链表build_all_zonelists〇

6. printk(KERN_N0TICE 〃Kernel command line: %s\n〃,saved_command_line);

打印出从uboot通报过来的command_line字符串,在setup_arch函数中获

得的。

7. parse_early_param〇,这里剖析的是系统能够辨别的一些早期参数(这个函数乃至可以去掉,_ s e t u p的形式的参数),而且在剖析的时候并不因此

setup_arch (&command_line)传出来的 command_line 为根本,而因此最原

生态的saved_coramand_line为根本的。

8. parse_args (’’Booting kernel' command_line, —start________par am,

—stop___param - —start______par am,

&unknown_bootoption);

对付比较新的版本真正起浸染的函数,与parse_early_param〇;比较,此处对解析

列表的处理范围加大了,解析列表中除了包括系统以setup定义的启动参数,还包括模

块中定义的param参数以及系统不能辨别的参数。

c〇mmand_line是setup_arch函数通报出来的值;

_start___ param是param参数的起始地址,在System, map文件中能看到

_stop___param - _start___param是参数个数

unknown_bootoption是对应与启动参数不是param的相应处理函数(查看

parse_one ()就知道怎么回事)

函数parse_one()既可以处理param参数,又可以处理_setup的形式的参

数,还可以处理不能识别的参数。
当然这些都是依赖通报进来的参数进行分支处

理的。

参数的处理详情参考网络上另一篇文章:Linux启动bootargs参数剖析

9. sort_main_extable ()

将放在—start___ ex_table 到—stop____ ex_table 之间的(_ex_table)区

域中的struct exception_table_entry型全局构造变量按insn成员变量值

从小到大排序,即将可能_致缺&非常的指令按其指令二进制代码值从小到

大排序。

10.在前面的 setup_arch->paging_init-> memtable_init 函数中为系统创建

页表的时候,中断向量表的虚地址 init_ maps,是用

alloc_ bootmem_ low_ pages分配的,ARM规定中断向量表的地址只能是0或

OxFFFFOOOO,#以该函数里有部分代码的浸染便是映射一页到0或

OxFFFFOOOO。

trap_init函数做了一下的事情:把放在.Lcvectors处的系统8个意外入口

跳转指令搬到高端中断向量 OxffffOOOO处,再将_ stubs_start到

_S tubs_e nd之间的各种意外初始化代码搬到0xffff0200处。
将系统调用的

返回句^拷贝到〇x ffff〇5〇〇处。
刷新OxffffOOOO处1页范围的指令cache,

将 D0MAINJJSER 的访问权限由 DOMAIN_MANAGER 改成 D0MAIN_CLIENT 权限。

11. rcu_init 〇函数初始化当前cpu的读、复制、更新数据构造(struct rcu_data)

全局变量 per cpu rcu data 和 per cpu rcu bh data.

12. init_IRQ 函数

初始化系统中支持的最大可能中断数的中断描述构造struct irqdesc变量数

组ir(L<lesc[NR_IRQS],把每个构造变量irq_desc[n]都初始化成预先定义好

的坏中断描述结"构变量bacUrcLdesc,并初始化该中断的链表表头成员构造

变量pend。

实行init_arch_irq函数,该函数是在setup_arch函数末了初始化的一个全

局函数指针,指向了 smdk2410_init_irq 函数(inmach-smdk2410. c),实际

上是调用了 s3C 24xx_init_irq_函数在该函数中,首先打消所有的中断未决

标志,之后就初始化中断的触发办法和屏蔽位,还有中断句柄初始化,这里

不是终极用户的中断函数,而是do_level_IRQ或者do_edge_IRQ函数,在这

两个函数中都利用过_do_irq函数来找到真正终极驱动程序注册在系统中

的中断处理函数。

status = 0;

do {

if (ret == IRQ

HANDLED)

status |= action->flags;

retval |= ret:

action = action->next;

} while (action);

接着初始化外部中断的一些参数,末了补充初始化uart和ADC中断。

13. pidhash_init ()函数

设置系泰中每种 pidjiash表中的 hash链表数的移位值全局变量

pidhash_shift, 将 pidhash_shift 设置成min (12)。
分别为每种 hash 表的

连续hash链表表头构造^请内存,把申请到的内存虚拟基址分别传给

pid_hash[n] (n=l~3),并将每种hash表中的每个hash链表表头构造struct

hlist_head中的first成员指针设置成NULL

14. init_timers ()函数

初始Ifc当前处理器的韶光向量基本构造struct tveC_t_baSe_S全局变量

per_cpu_tvec_bases,初始化 per_cpu_tvec_bases 的自选锁成员变量 lock

5: void _init jllit tilTISrS (void)

1: {

) : //这个函数便是 ti rrers_nb这个构造体的 cal I函 数

>: timer_cpu_notify(& timers—nb, (unsigned long)CPU—UP—PREPARE,

1: (void ) (long) smp_processor_id ());

l

h / /这个是用的机制和 cpuf r eq的机制是一样的,通过 not i f i er_chai n_r egi st er (&cpu_chai n, nb)注册以

): / /只不过这里的链是 cpu_chai n,而 cpufreq是其他的链

.: register_cpu_noti fier(&

timors_nb );

>

ji //设置软中断行动函数描述构造变量softirq_ueC[z1](系统定时器)的设置

1: //也便是设置timer定时器到之后的处理函数

): open—softirq (TIMER—SOFTIRQ, run_timer_sof tirq , NULL);

15. softirqjnit 函数

内核的软中断机制初始化函数,void _____ init softirq_init (void)

{ 一

/HI_SCFTI RQffl于实现 bottom hal f, TASKLET_SCFTI RQffl 于公共的 t ask 丨 et V

open_so f t irq ( TASKLET_SOFTIR〇 , tasklet_act ion , NULL );

open_sof tirq (HI_SOFTIRQJ tasklet_hi_act ion , NULL);

}

16. time_init()这个函数是用来做体系干系的timer的初始化

void ___init time init(v

id)

if (

timer - >offset == NULL)

system_ timer - >offset = dummy—get t imeo f fset;

system_ timer - > i n i t ();

还记得在setup_arch末了初始化了三个全局的变量吗?下图所示

x Set up uarious architecture-specific pointers

/ /运 行 rrach-smdk2410- c文件中定义的构造体 __nBch_desc_SNEK2410中的特定函数和构造 |

init_arch_irq - mdesc->init_irq;/x smdk2410_init_irq

system一 timer - mdesc- > t imer ; /« 3c24xx_tim9r «/

inj t_machine - mdesc-> ini t_machine ;/« smdk2410_init_fs2410 x/

static void ____ init

s3c2410 timer init

(void)

{

s3c2410_timer_setup();

setup—irq ^ s3c241 D_ timer_irq );

struct sys_timer

.init =

.offset =

.resume =

s3 c2 4xx_t imer = {

s3 c2 410_t imer_ini t,

s3 c2 410_get t imeo f fset,

s3 c2 410—t imer—setup

很显然,t i m e_i n i t函数中的i f条件不成立,以是便是直接实行了函数

s3c2410_timer_init函数。
可以看出,系统利用了 timer4来作为系统的定

时器。

17. console_init 函数

初始化系统的掌握台构造,该函数实行后调用printk函数将log_buf中所有

符合打印级别的系统信息打印到掌握台上

a. tty_register_ldisc ()注册默认的TTY线路规程

大象研究tty的驱动就会创造了在用户和硬件之间tty的驱动是分了三

层最底层当然是tty驱动程序了,紧张卖力从硬件接管数据,和格式化

上层发下来的数据后给硬件。
在驱动程序之上便是线路规程了,他卖力

把从tty核心层或者tty驱动层接管的数据进行分外的按着某个协议的格式化,就像是Pf5p或者蓝牙协议,然后在分发出去的。
在tty线路规

程之上便是tty核心层了。

b.接下来便是实行平台干系的掌握台初始化代码

s3c24xx_serial_initconsole

/

在vmlinux. Ids. S中连接脚本汇编中有这段代码

—con_initcall_start =.;

(. con_initcall. init)

一con initcall—end =.;

因此我们再此处调用的便是C〇n_initcall. init段的代码,

将fn函数放到.con_initcall. init的输入段中,如下:

^define console_initcall(fn) \

static initcall_t —initcall_##fn \

—attribute_used_

—attribute—((—section—(〃. con_initcall. init")))=fn

j

//在我们串口驱动里面有

这么一个注册语句:console_initcall (s3c24xx_serial_initconsole);

//因此我们的掌握台初始化流

程便是:start_kernel->console_init_>s3c24xx_serial_initconsole

call = 一con_initcall_start; / console_initcall /

while (call < —con_initcall_end) {

(call)();

call++;

18_ profile_init ()函数

/对系&阐发做干系初始化,系统阐发用于系统调用/

//profile是用来对系统阐发的,在系统调试的时候有用

//须要打开内核选项,并且在bootargs中有profile这一项才能开启这个功

/

profile只是内核的一个调试性能的工具,这个可以通过menuconfig

中 profiling support 打开。

1. 如何利用profile:

首先确认内核支持profile,然后在内核启动时加入以下参数:

profile=l或者其它参数,新的内核支持profile=schedule 1

2.

内核启动后会创建/ proc/ profile文件,这个文件可以通过

readprofile 读取,

$口 readprofile -m / proc/kallsyms | sort -nr >

Vcur_ profile. log,

或者 readprofile _r _m / proc/kallsyms | sort - nr,

或者 readprofile _r && sleep 1 && readprofile _ m/ proc/kallsyms

标签:

相关文章