arm linux系统开发工具
2008-04-02 15:12:00 stargui 阅读数 508

经过一段时间努力,

终于在自己的arm linux平台上构建了一个比较完备的系统,

移植的硬件平台包括s3c2410  at91sam9260 和 at91sam9261。

因网上2410用得比较多就以2410为例把整个过程整理下来。

9260 9261的过程完全一样。

系统介绍:

硬件平台:arm(2410)

操作系统:linux

bootloader 自制或移植u-boot

toolchain

soft float arm-linux 自制 gcc 4.2.3 glibc 2.7 binutils 2.18 gdb 6.7.1

binutils 源代码: binutils-2.18.tar.bz2

gcc 源代码: gcc-4.2.3.tar.bz2

glibc 源代码: glibc-2.7.tar.bz2

gdb 源代码: gdb-6.7.1.tar.bz2

kernel 源代码:linux-2.6.24.3.tar.bz2

基本工具: 源代码:busybox-1.9.1.tar.bz2 udev

gui

tinyx 源代码:(启动)

Xfree86-4.7.0-src-1.tgz

Xfree86-4.7.0-src-2.tgz

......

Xfree86-4.7.0-src-1.tgz

require libs

gui toolkitatk-1.21.92.tar.bz2

glib-2.14.4.tar.bz2

cairo-1.4.12.tar.gz

pango-1.19.4.tar.bz2

gtk+-2.12.3.tar.bz2

require libs

窗口管理器: xfce4

xfce-4.4.2.src.tar.bz2

包括: xfwm4(窗口管理器) xfdesktop(桌面) thunar(文件管理器) panel(面板) mcs(设置管理)

具体系统运行的图片可在我的相册里看到。

下面将一步一步介绍构建整个系统的过程。

 
2018-11-08 19:20:13 xiezhenliang 阅读数 252

arm中断框架:
在这里插入图片描述
我把中断系统的硬件组成分为三个部分,CPU、中断控制器、外设中断源。imx6q的Interrupt Controller为GIC,支持多CPU Core。
在这里插入图片描述
【1】首先来看CPU 目标架构相关的的中断处理
中断向量表
arch/arm/kernel/entry-armv.S中
.section .vectors, “ax”, %progbits
__vectors_start:
W(b) vector_rst
W(b) vector_und
W(ldr) pc, __vectors_start + 0x1000
W(b) vector_pabt
W(b) vector_dabt
W(b) vector_addrexcptn
W(b) vector_irq //中断发生时跳转的地址
W(b) vector_fiq
.data

关于vector_irq的定义分析到跳转流程如下
.macro vector_stub, name, mode, correction=0
.align 5
vector_\name:
.if \correction
sub lr, lr, #\correction
.endif
vector_stub irq, IRQ_MODE, 4
.long __irq_usr @ 0 (USR_26 / USR_32)
.long __irq_invalid @ 1 (FIQ_26 / FIQ_32)
.long __irq_invalid @ 2 (IRQ_26 / IRQ_32)
.long __irq_svc @ 3 (SVC_26 / SVC_32)
.long __irq_invalid @ 4
.long __irq_invalid @ 5
.long __irq_invalid @ 6
.long __irq_invalid @ 7
.long __irq_invalid @ 8
.long __irq_invalid @ 9
.long __irq_invalid @ a
.long __irq_invalid @ b
.long __irq_invalid @ c
.long __irq_invalid @ d
.long __irq_invalid @ e
.long __irq_invalid @ f
从上面的定义可以看到,arm下只有usr状态的中断和svc状态的中断,继续分析
__irq_usr 和 __irq_svc的定义。

__irq_usr:
usr_entry ----->保存现场
kuser_cmpxchg_check
irq_handler ---->处理中断
get_thread_info tsk
mov why, #0
b ret_to_user_from_irq —>恢复现场
UNWIND(.fnend )
ENDPROC(__irq_usr)
__irq_svc:
svc_entry —>保存现场
irq_handler ---->处理中断
#ifdef CONFIG_PREEMPT
get_thread_info tsk
ldr r8, [tsk, #TI_PREEMPT] @ get preempt count
ldr r0, [tsk, #TI_FLAGS] @ get flags
teq r8, #0 @ if preempt count != 0
movne r0, #0 @ force flags to 0
tst r0, #_TIF_NEED_RESCHED
blne svc_preempt
#endif
svc_exit r5, irq = 1 @ return from exception 恢复现场
UNWIND(.fnend )
ENDPROC(__irq_svc)

继续跟踪irq_handler 的处理
.macro irq_handler
#ifdef CONFIG_MULTI_IRQ_HANDLER
ldr r1, =handle_arch_irq
mov r0, sp
adr lr, BSYM(9997f)
ldr pc, [r1]
#else
arch_irq_handler_default
#endif
最终跳转到handle_arch_irq,在arch/arm/kernel/irq.c中使用set_handle_irq设置handle_arch_irq的函数指针
void __init set_handle_irq(void (*handle_irq)(struct pt_regs *))
{
if (handle_arch_irq)
return;
handle_arch_irq = handle_irq;
}

继续跟踪,在imx6q目标架构下,driver/irqchip/irq-gic.c中调用set_handle_irq。设置中断处理函数入口。
static void __exception_irq_entry gic_handle_irq(struct pt_regs *regs)
{
u32 irqstat, irqnr;
struct gic_chip_data *gic = &gic_data[0];
void __iomem *cpu_base = gic_data_cpu_base(gic);
do {
irqstat = readl_relaxed(cpu_base + GIC_CPU_INTACK);
irqnr = irqstat & GICC_IAR_INT_ID_MASK;
if (likely(irqnr > 15 && irqnr < 1021)) {
handle_domain_irq(gic->domain, irqnr, regs);
continue;
}
if (irqnr < 16) {
writel_relaxed(irqstat, cpu_base + GIC_CPU_EOI);
#ifdef CONFIG_SMP
handle_IPI(irqnr, regs);
#endif
continue;
}
break;
} while (1);
}
cpu架构相关的中断代码暂时跟踪到这里,继续跟踪基本上是和imx6q片上寄存器设置相关的代码,需要熟悉imx6q的架构和寄存器设置等内容。
【2】再看ARM 的GIC中断控制器相关分析
GIC的全称为Generic Interrupt Controller,是ARM公司提供的通用的中断控制器。目前共有V1 V2 V3 V4 四个版本。imx6q使用的为V2版本。Linux内核中关于GIC中断控制器的代码目录为 drivers/irqchip/irq-gic.c 和 drivers/irqchip/irq-common.c
先补充一下GIC中断控制器的几个知识点:
GIC中断控制器中的中断输入信号,分为两种,分别是PPI和SPI, PPI即Private Peripheral Interrupt,该类型的中断信号为CPU私有的中断信号,每个CPU都有其特定的PPI信号输入。对于IMX6Q使用的Cortex-A9结构,她的PPI中断信号包括5根:
a>.nLEGACYIRQ, interrupt ID=31,nLEGACYIRQ可以直接连到对应CPU的nIRQCPU信号线上,这种情况下,该CPU不参与其他属于该CPU的PPI以及SPI中断的响应,只是特别的为这一根中断线服务。
b>.nLEGACYFRQ, interrupt ID=28,功能同nLEGACYIRQ
c>.Private timer, interrupt ID=29
d>.Watch Dog Timer, interrupt ID=30
e>.Global Time, interrupt ID=27
SPI,即Shared Peripheral Interrupt,所有CPU之间共享的中断,通过寄存器GICD_TYPER可以设置SPI的个数。GIC支持多少个SPI中断,其输入信号就有多少个SPI的request signal。

从代码driver/irqchip/irq-gic.c中的gic_handle_irq可以看出,ID0-ID15,为CPU的PPI中断ID,ID16-ID1020为SPI中断ID。

Linux GIC源码分析
driver的加载入口:
IRQCHIP_DECLARE(cortex_a9_gic, “arm,cortex-a9-gic”, gic_of_init);
展开IRQCHIP_DECLARE 宏
#define IRQCHIP_DECLARE(name, compat, fn) OF_DECLARE_2(irqchip, name, compat, fn)
#define OF_DECLARE_2(table, name, compat, fn)
_OF_DECLARE(table, name, compat, fn, of_init_fn_2)
#define _OF_DECLARE(table, name, compat, fn, fn_type)
static const struct of_device_id _of_table##name
__used section(##table##_of_table)
= { .compatible = compat,
.data = (fn == (fn_type)NULL) ? fn : fn }
最终初始化了一个struct of_decice_id的静态常量,并存储在_irqchip_of_table_section中。在内核启动过程中根据设备树种的内容匹配当前芯片的of_decice_id,进行驱动的加载过程。


static int __init
gic_of_init(struct device_node *node, struct device_node *parent)
{
void __iomem *cpu_base;
void __iomem *dist_base;
u32 percpu_offset;
int irq;
if (WARN_ON(!node))
return -ENODEV;
dist_base = of_iomap(node, 0); //映射GIC 分发器的寄存器地址
WARN(!dist_base, “unable to map gic dist registers\n”);
cpu_base = of_iomap(node, 1); //映射GIC CPU INTERFACE的寄存器地址
WARN(!cpu_base, “unable to map gic cpu registers\n”);
if (of_property_read_u32(node, “cpu-offset”, &percpu_offset))
percpu_offset = 0; //处理cpu-offset属性
//gic_init_bases是GIC的主要处理过程
gic_init_bases(gic_cnt, -1, dist_base, cpu_base, percpu_offset, node);
if (!gic_cnt)
gic_init_physaddr(node);
//处理interrupt级联关系
if (parent) {
irq = irq_of_parse_and_map(node, 0);//解析second GIC的interrupt属性并进行mapping,返回的是irq number
gic_cascade_irq(gic_cnt, irq);
}
if (IS_ENABLED(CONFIG_ARM_GIC_V2M))
gicv2m_of_init(node, gic_data[gic_cnt].domain);
gic_cnt++;
return 0;
}

void __init gic_init_bases(unsigned int gic_nr, int irq_start,
void __iomem *dist_base, void __iomem *cpu_base,
u32 percpu_offset, struct device_node node)
{
irq_hw_number_t hwirq_base;
struct gic_chip_data gic;
int gic_irqs, irq_base, i;
BUG_ON(gic_nr >= MAX_GIC_NR);
gic = &gic_data[gic_nr];
/

* Initialize the CPU interface map to all CPUs.
* It will be refined as each CPU probes its ID.
/
for (i = 0; i < NR_GIC_CPU_IF; i++)
gic_cpu_map[i] = 0xff;
/

* Find out how many interrupts are supported.
* The GIC only supports up to 1020 interrupt sources.
/
gic_irqs = readl_relaxed(gic_data_dist_base(gic) + GIC_DIST_CTR) & 0x1f;
gic_irqs = (gic_irqs + 1) * 32;
if (gic_irqs > 1020)
gic_irqs = 1020;
gic->gic_irqs = gic_irqs;
/gic_irqs保存了GIC支持的最大中断数量,从GICD_TYPER寄存器中的低五位读出,如果读出值为N, 那么最大支持的中断数目是32(N+1)。GIC规定的最大中断数据不超过1020
/
if (node) { /
DT case /
gic->domain = irq_domain_add_linear(node, gic_irqs,
&gic_irq_domain_hierarchy_ops,
gic);
} else { /
Non-DT case /
/

* For primary GICs, skip over SGIs.
* For secondary GICs, skip over PPIs, too.
/
if (gic_nr == 0 && (irq_start & 31) > 0) {
hwirq_base = 16;
if (irq_start != -1)
irq_start = (irq_start & ~31) + 16;
} else {
hwirq_base = 32;
}
/gic_nr表示GIC_NUMBER, 0表示root GIC,hwirq表示硬件中断id,并不是GIC上的每个中断id都需要映射到Linux 中断系统中的id。例如软件中断SGI,用于cpu之间的通信,没有必要进行 中断ID的映射。hwirq_base 表示GIC上需要进行id map的base,16表示忽略掉16个SGI。对于系统
中的其他GIC,其PPI也没有必要进行mapping,因此hwirq_base=32
/
gic_irqs -= hwirq_base; /
calculate # of irqs to allocate */
/*从gic_irqs 中减去那些不需要mapping的interrupt ID /
irq_base = irq_alloc_descs(irq_start, 16, gic_irqs,
numa_node_id());
if (IS_ERR_VALUE(irq_base)) {
WARN(1, “Cannot allocate irq_descs @ IRQ%d, assuming pre-allocated\n”,
irq_start);
irq_base = irq_start;
}
/分配中断描述符 如果irq_start大于0,那么说明是指定IRQ number的分配,这种情况,irq_start=-1
不用指定IRQ NUMBER。让其自动搜索,参数2是起始搜索的IRQ NUMBER. gic_irqs则指明了要分配的
irq_number 的数目。
/
gic->domain = irq_domain_add_legacy(node, gic_irqs, irq_base,
hwirq_base, &gic_irq_domain_ops, gic);
/向系统中注册一个irq_domain的数据结构,关于irq_domain 后面专门分析/
}
if (WARN_ON(!gic->domain))
return;
if (gic_nr == 0) {
#ifdef CONFIG_SMP
set_smp_cross_call(gic_raise_softirq);
/设定一个多个cpu直接通信的callback函数。当一个cpu core上的软件控制行为需要传递到其他的cpu上时,
就会调用这个函数set_smp_cross_call,对于GIC,这里实际上触发IPI中断
/
register_cpu_notifier(&gic_cpu_notifier);
/注册cpu的通知事件,当cpu 状态发生变化时,GIC driver需要接收到这些事件,并对GIC 的
cpu interface进行设定
/
#endif
set_handle_irq(gic_handle_irq);
/设定所有中断的处理函数入口/
}
gic_dist_init(gic);
gic_cpu_init(gic);
gic_pm_init(gic);
/

GIC的电源初始化,主要分配两个per cpu的内存。这些内存在系统进行sleep状态时保存PPI的寄存器状态信息,在resume的时候,写回寄存器。对于root GIC,
需要注册一个和电源管理的时间通知事件回掉函数。
*/
}

static void __init gic_dist_init(struct gic_chip_data *gic)
{
unsigned int i;
u32 cpumask;
unsigned int gic_irqs = gic->gic_irqs; //GIC支持的IRQ数量
void __iomem base = gic_data_dist_base(gic); //获取GIC Distributor分发器基地址
writel_relaxed(GICD_DISABLE, base + GIC_DIST_CTRL);
/

GIC_DIST_CTRL寄存器用来控制全局中断向CPU Interface分发,写入0表示不向CPU Interface发送中断请求信号
即为关闭全部中断请求。
/
/

* Set all global interrupts to this CPU only.
*/
cpumask = gic_get_cpumask(gic);
cpumask |= cpumask << 8;
cpumask |= cpumask << 16;
for (i = 32; i < gic_irqs; i += 4)
/设定每个SPI类型的中断都是只送达该CPU/
writel_relaxed(cpumask, base + GIC_DIST_TARGET + i * 4 / 4);
/配置GIC Distributor的其他寄存器/
gic_dist_config(base, gic_irqs, NULL);
writel_relaxed(GICD_ENABLE, base + GIC_DIST_CTRL);
}

static void gic_cpu_init(struct gic_chip_data *gic)
{
void __iomem *dist_base = gic_data_dist_base(gic); //获取 GIC Distrbutor的基地址
void __iomem base = gic_data_cpu_base(gic); //获取 GIC CPU Interface的基地址
unsigned int cpu_mask, cpu = smp_processor_id(); //获取CPU的逻辑ID
int i;
/

* Get what the GIC says our CPU mask is.
/
BUG_ON(cpu >= NR_GIC_CPU_IF);
cpu_mask = gic_get_cpumask(gic); //获取cpu_mask
gic_cpu_map[cpu] = cpu_mask;
/

* Clear our mask from the other map entries in case they’re
* still undefined.
*/
for (i = 0; i < NR_GIC_CPU_IF; i++)
if (i != cpu)
gic_cpu_map[i] &= ~cpu_mask;
/设定SGI PPI的初始值/
gic_cpu_config(dist_base, NULL);
//让所有的interrupt interface都可以送达CPU
writel_relaxed(GICC_INT_PRI_THRESHOLD, base + GIC_CPU_PRIMASK);
//设定cpu interface的control register,enable group-0的中断,disable group-1的中断,group 0的interrupt source
//触发irq中断,而非fiq中断
gic_cpu_if_up();
}

2009-09-19 14:07:00 LUOPING198410 阅读数 1505

Linux 系统下ARM Linux交叉编译环境的建立目前流行的有三种途径。

一、使用别人编译好的开发工具链

cross-2.95.3.tar.bz2

arm-linux-gcc-3.3.2.tar.bz2

arm-elf-tools-20030314.sh

其中arm-elf-tools 是专门用来编译uclinux内核的。我们常用的cross-cross-2.95.3,下载cross-2.95.3.tar.bz2 解压后放到/usr/local/arm 目录下设置下PATH即可使用,这是最方便快捷的方式,缺点是cross-2.95.3 GCC版本是2.95.3,版本较低,不能编译2.6版本的Linux内核和版本较高的u-bootcross-2.95.3.tar.bz2一般是交叉编译2.4linux内核的,而arm-linux-gcc-3.3.2.tar.bz2一般是交叉编译2.6版本的内核的。

二、自己动手慢慢编译

这个方法是最麻烦的,需要下载很多源文件,步骤多比较繁琐,成功率不高,极其容易出错,即使是经验丰富程序员,自己编译一套完整的工具链也是很难成功的。

三、建立交叉编译工具的途径是使用 crosstool-0.43buildroot来编译

     如果是基于gccglibc来制作工具链,则使用crosstool来编译,如果要基于gccuClibc来制作工具链,可以使用buildroot来进行编译。

    uClibcglibc小,在已有的接口上是兼容的,更适合嵌入式系统,单uClibc没包括glibc中的所以的接口实现。

 

使用crosstool工具(crosstool-0.43.tar.gz

 

  $ tar zxvf crosstool-0.43.tar.gz

  $ cd crosstool-0.43

  $ cp glibc-2.3.6-version-info.h_err.patch  crosstool-0.43/patches/glibc-2.3.6/

 

/* glibc-2.3.6-version-info.h_err.patch为补丁程序*/

 

打开脚本demo-arm-softfloat.sh(具体使用那个sh文件根据硬件确定)

#!/bin/sh

# This script has one line for each known working toolchain

# for this architecture.  Uncomment the one you want.

# Generated by generate-demo.pl from buildlogs/all.dats.txt

 

set -ex

TARBALLS_DIR=$HOME/downloads             /*表示源码存放的位置*/

RESULT_TOP=/opt/crosstool                /*表示编译结果存放的位置*/

export TARBALLS_DIR RESULT_TOP

GCC_LANGUAGES="c,c++"                   /*表示制作出的工具链支持C、C++语言,如果想支持其他语言可以在里面增加如java可以GCC_LANGUAGES="c,c++,java" */

export GCC_LANGUAGES

 

# Really, you should do the mkdir before running this,

# and chown /opt/crosstool to yourself so you don't need to run as root.

 

mkdir -p $RESULT_TOP                           /*建立目录*/

 

#eval `cat arm-softfloat.dat gcc-2.95.3-glibc-2.1.3.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-2.95.3-glibc-2.2.2.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-2.95.3-glibc-2.2.5.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.2.3-glibc-2.2.5.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.2.3-glibc-2.3.2.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.2.3-glibc-2.3.2-tls.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.3.6-glibc-2.2.2.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.3.6-glibc-2.2.5.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.3.6-glibc-2.3.2.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.3.6-glibc-2.3.2-tls.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.4.5-glibc-2.2.5.dat` sh all.sh --notest

#eval `cat arm-softfloat.dat gcc-3.4.5-glibc-2.3.5.dat` sh all.sh --notest

eval `cat arm-softfloat.dat gcc-3.4.5-glibc-2.3.6.dat` sh all.sh --notest

/*这个是会生成的版本、注意gcc-3.4.5-glibc-2.3.6.dat、arm-softfloat.dat all.sh 他们是三个相关的shell文件,下面分别介绍*/

echo Done.

 

1demo-arm-softfloat.sh中修改

TARBALLS_DIR=/work/tools/create_crosstools/src_gcc_glibc     /*修改源码存放的位置,包括gcc-3.4.5-glibc-2.3.6.dat 所包含的所有包,如果可以上网,执行$ ./demo-arm-softfloat.sh 后可以自行下载*/

RESULT_TOP=/opt/crosstool             /*修改编译结果存放的位置*/

 

2arm-softfloat.dat

 

KERNELCONFIG=`pwd`/arm.config

TARGET=arm-softfloat-linux-gnu     /*编译出来的样式为arm-softfloat-linux-gnu ,把它修改成TARGET=arm-linux,编译出来的样式为arm-linux-gcc、arm-linux-ld等*/

TARGET_CFLAGS="-O"

GCC_EXTRA_CONFIG="--with-float=soft"

GLIBC_EXTRA_CONFIG="--without-fp"

 

3all.sh

PREFIX=${PREFIX-$RESULT_TOP/$TOOLCOMBO/$TARGET}   

修改成

PREFIX=${PREFIX-$RESULT_TOP/$TOOLCOMBO}

/*把原来最终结果存放在/work/tools/gcc-3.4.5-glibc-2.3.6/arm-linux下改为/work/tools/gcc-3.4.5-glibc-2.3.6/*/

其中TARGET在arm-softfloat.dat定义的

 

4gcc-3.4.5-glibc-2.3.6.dat

BINUTILS_DIR=binutils-2.15

GCC_DIR=gcc-3.4.5

GLIBC_DIR=glibc-2.3.6

LINUX_DIR=linux-2.6.8 (可以修改内核版本)

LINUX_SANITIZED_HEADER_DIR=linux-libc-headers-2.6.12.0

GLIBCTHREADS_FILENAME=glibc-linuxthreads-2.3.6

GLIBC_EXTRA_CONFIG="$GLIBC_EXTRA_CONFIG --with-tls --with-__thread  --enable-kernel=2.4.18"

 

以上所提到的就是生成交叉编译器所需要的压缩包的版本

5、进入crosstool-0.43

 

$ cd crosstools-0.43

$ ./demo-arm-softfloat.sh

 

 /*执行,经过23小时后在/work/tools/目录下生成gcc-3.4.5-glibc-2.3.6子目录,交叉编译器、库、头文件都在里面,设置PATH环境变量即可*/

 

$ arm-linux-gcc –v  /*测试*/

可能的错误:
ubuntu中默认gcc的版本是,对于crosstools 0.43而言gcc可能太新了。
解决的办法很简单:装个gcc4.1,然后把/usr/bin/gcclinkgcc4.2指向4.1即可。
sudo apt-get install gcc-4.1
ls -l /usr/bin/gcc   #你可以看看现在的gcc指向哪里
sudo rm /usr/bin/gcc
sudo ln -s /usr/bin/gcc-4.1 /usr/bin/gcc   
附录: 
binutils是二进制文件的处理工具,它主要包含了一些辅助开发工具,例如,objdump显示反汇编码、nm列出符号表、readelf显示elf文件信息及段信息、strip将不必要的代码去掉以减少可执行文件大小等。这些工具在嵌入式开发初期,尤其是在移植调试操作系统时非常有用。 
  gcc是编译工具,用来编译内核代码的工具,使用它可以编译汇编语言和c语言的程序,生成arm的代码。 
  glibc是链接和运行库,它的编译需要指定编译器为arm交叉编译器,否则编译出的glibc代码将会是同时有armx86代码的混合体。所有需要用到的工具都可以通过下载源码自行编译,然后在宿主机上进行安装,就可以建立起arm的交叉编译环境。
2009-10-14 16:10:00 skywalkzf 阅读数 1063

Arm linux 内核移植及系统初始化过程分析
本文主要介绍内核移植过程中涉及文件的分布及其用途,以及简单介绍系统的初始化过程。整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。了解系统的初始化过程,有益于更好地移植内核。

1.    内核移植2.    涉及文件分布介绍
2.1.    内核移植2.2.    涉及的头文件
/linux-2.6.18.8/include
[root@localhost include]# tree -L 1
.
|-- Kbuild
|-- acpi
|-- asm -> asm-arm
|-- asm-alpha
|-- asm-arm   ------------------------------->(1)
|-- asm-sparc
|-- asm-sparc64
|-- config
|-- keys
|-- linux        ------------------------------->(2)
|-- math-emu
|-- media
|-- mtd
|-- net
|-- pcmcia
|-- rdma
|-- rxrpc
|-- scsi
|-- sound
`-- video

内核移植过程中涉及到的头文件包括处理器相关的头文件(1)和处理器无关的头文件(2)。

2.3.    内核移植2.4.    涉及的源文件
/linux-2.6.18.8/arch/arm
[root@localhost arm]# tree -L 1
.
|-- Kconfig
|-- Kconfig-nommu
|-- Kconfig.debug
|-- Makefile
|-- boot  ------------------------------->(2)
|-- common
|-- configs
|-- kernel  ------------------------------->(3)
|-- lib
|-- mach-at91rm9200
……
|-- mach-omap1
|-- mach-omap2
|-- mach-realview
|-- mach-rpc
|-- mach-s3c2410   ------------------------------->(4)
|-- mach-sa1100
|-- mach-versatile
|-- mm    ------------------------------->(5)
|-- nwfpe
|-- oprofile
|-- plat-omap
|-- tools    ------------------------------->(1)
`-- vfp

(1)
/linux-2.6.18.8/arch/arm/tools
[root@localhost tools]# tree -L 1
.
|-- Makefile
|-- gen-mach-types
`-- mach-types

Mach-types 文件定义了不同系统平台的系统平台号。移植linux内核到新的平台上需要对新的平台登记系统平台号。

Mach-types文件格式如下:
# machine_is_xxx    CONFIG_xxxx        MACH_TYPE_xxx        number
s3c2410        ARCH_S3C2410        S3C2410                     182
smdk2410        ARCH_SMDK2410    SMDK2410                   193

之所以需要这些信息,是因为脚本文件linux/arch/arm/tools/gen-mach-types需要linux/arch/tools/mach-types来产生linux/include/asm-arm/mach-types.h文件,该文件中设置了一些宏定义,需要这些宏定义来为目标系统选择合适的代码。

(2)
linux-2.6.18.8/arch/arm/boot/compressed
[root@localhost compressed]# tree -L 1
.
|-- Makefile
|-- Makefile.debug
|-- big-endian.S
|-- head-at91rm9200.S
|-- head.S
|-- ll_char_wr.S
|-- misc.c
|-- ofw-shark.c
|-- piggy.S
`-- vmlinux.lds.in

Head.s 是内核映像的入口代码,是自引导程序。自引导程序包含一些初始化程序,这些程序都是体系结构相关的。在对系统作完初始化设置工作后,调用misc.c文件中的decompress_kernel()函数解压缩内核映像到指定的位置,然后跳转到kernel的入口地址。

Vmlinux.lds.in用来生成内核映像的内存配置文件。

(3)
linux-2.6.18.8/arch/arm/kernel
[root@localhost kernel]# tree -L 1
.
|-- Makefile
|-- apm.c
|-- armksyms.c
|-- arthur.c
|-- asm-offsets.c
|-- bios32.c
|-- calls.S
|-- dma.c
|-- ecard.c
|-- entry-armv.S
|-- entry-common.S
|-- entry-header.S
|-- fiq.c
|-- head-common.S
|-- head-nommu.S
|-- head.S
|-- init_task.c
|-- io.c
|-- irq.c
|-- isa.c
|-- module.c
|-- process.c
|-- ptrace.c
|-- ptrace.h
|-- semaphore.c
|-- setup.c
|-- smp.c
|-- sys_arm.c
|-- time.c
|-- traps.c
`-- vmlinux.lds.S

内核入口处也是由一段汇编语言实现的,由head.s和head-common.s两个文件组成。
Head.s 是内核的入口文件, 在head.s的末尾处 #include "head-common.S"。 经过一系列的初始化后,跳转到linux-2.6.18.8/init/main.c中的start_kernel()函数中,开始内核的基本初始化过程。


/linux-2.6.18.8/init
[root@localhost init]# tree
.
|-- Kconfig
|-- Makefile
|-- calibrate.c
|-- do_mounts.c
|-- do_mounts.h
|-- do_mounts_initrd.c
|-- do_mounts_md.c
|-- do_mounts_rd.c
|-- initramfs.c
|-- main.c
`-- version.c

(4)
/linux-2.6.18.8/arch/arm/mach-s3c2410
[root@localhost mach-s3c2410]# tree -L 1
.
|-- Kconfig
|-- Makefile
|-- Makefile.boot
|-- bast-irq.c
|-- bast.h
|-- clock.c
|-- clock.h
|-- common-smdk.c
|-- common-smdk.h
|-- cpu.c
|-- cpu.h
|-- devs.c
|-- devs.h
|-- dma.c
|-- gpio.c
|-- irq.c
|-- irq.h
|-- mach-anubis.c
|-- mach-smdk2410.c
|-- pm-simtec.c
|-- pm.c
|-- pm.h
|-- s3c2400-gpio.c
|-- s3c2400.h
|-- s3c2410-clock.c
|-- s3c2410-gpio.c
|-- s3c2410.c
|-- s3c2410.h
|-- sleep.S
|-- time.c
|-- usb-simtec.c
`-- usb-simtec.h

这个目录中的文件都是板级相关的,其中比较重要是如下几个:
linux/arch/arm/mach-s3c2410/cpu.c
linux/arch/arm/mach-s3c2410/common-smdk.c
linux/arch/arm/mach-s3c2410/devs.c
linux/arch/arm/mach-s3c2410/mach-smdk2410.c
linux/arch/arm/mach-s3c2410/Makefile.boot
linux/arch/arm/mach-s3c2410/s3c2410.c

3.    处理器和设备4.   
这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。
4.1.    处理器、设备4.2.    描述
设备描述主要两个结构体完成:struct resource和struct platform_device。
先来看看着两个结构体的定义:
struct resource {
    resource_size_t start;
    resource_size_t end;
    const char *name;
    unsigned long flags;
    struct resource *parent, *sibling, *child;
};

Resource结构体主要是描述了设备在系统中的起止地址、名称、标志以及为了链式描述方便指向本结构体类型的指针。Resource定义的实例将被添加到platform_device结构体对象中去。

struct platform_device {
    const char    * name;
    u32        id;
    struct device    dev;
    u32        num_resources;
    struct resource    * resource;
};

Platform_device结构体包括结构体的名称、ID号、平台相关的信息、设备的数目以及上面定义的resource信息。Platform_device结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。

4.3.    处理器、设备4.4.    操作
(1) int platform_device_register(struct platform_device * pdev);    注册设备
(2) void platform_device_unregister(struct platform_device * pdev); 注销设备
(3) int platform_add_devices(struct platform_device **devs, int num);添加设备,通过调用上面两个函数实现。
4.5.    添加Nand flash设备4.6.   
下面以nand flash 设备的描述为例,具体介绍下设备的描述和注册过程。

// resource结构体实例s3c_nand_resource 对nand flash 控制器描述,包括控制器的起止地址和标志。
static struct resource s3c_nand_resource[] = {
    [0] = {
        .start = S3C2410_PA_NAND,
        .end   = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,
        .flags = IORESOURCE_MEM,
    }
};

//platform_device结构体实例s3c_device_nand定义了设备的名称、ID号并把resource对象作为其成员之一。
struct platform_device s3c_device_nand = {
    .name          = "s3c2410-nand",
    .id          = -1,
    .num_resources      = ARRAY_SIZE(s3c_nand_resource),
    .resource      = s3c_nand_resource,
};

// nand flash 的分区情况,由mtd_partition结构体定义。
static struct mtd_partition smdk_default_nand_part[] = {
    [0] = {
        .name    = "Boot Agent",
        .size    = SZ_16K,
        .offset    = 0,
    },
    [1] = {
        .name    = "S3C2410 flash partition 1",
        .offset = 0,
        .size    = SZ_2M,
    },
    [2] = {
        .name    = "S3C2410 flash partition 2",
        .offset = SZ_4M,
        .size    = SZ_4M,
    },
    [3] = {
        .name    = "S3C2410 flash partition 3",
        .offset    = SZ_8M,
        .size    = SZ_2M,
    },
    [4] = {
        .name    = "S3C2410 flash partition 4",
        .offset = SZ_1M * 10,
        .size    = SZ_4M,
    },
    [5] = {
        .name    = "S3C2410 flash partition 5",
        .offset    = SZ_1M * 14,
        .size    = SZ_1M * 10,
    },
    [6] = {
        .name    = "S3C2410 flash partition 6",
        .offset    = SZ_1M * 24,
        .size    = SZ_1M * 24,
    },
    [7] = {
        .name    = "S3C2410 flash partition 7",
        .offset = SZ_1M * 48,
        .size    = SZ_16M,
    }
};

static struct s3c2410_nand_set smdk_nand_sets[] = {
    [0] = {
        .name        = "NAND",
        .nr_chips    = 1,
        .nr_partitions    = ARRAY_SIZE(smdk_default_nand_part),
        .partitions    = smdk_default_nand_part,
    },
};

/* choose a set of timings which should suit most 512Mbit
* chips and beyond.
*/

static struct s3c2410_platform_nand smdk_nand_info = {
    .tacls        = 20,
    .twrph0        = 60,
    .twrph1        = 20,
    .nr_sets    = ARRAY_SIZE(smdk_nand_sets),
    .sets        = smdk_nand_sets,
};

/* devices we initialise */
// 最后将nand flash 设备加入到系统即将注册的设备集合中。
static struct platform_device __initdata *smdk_devs[] = {
    &s3c_device_nand,
    &smdk_led4,
    &smdk_led5,
    &smdk_led6,
    &smdk_led7,
};

然后通过smdk_machine_init()函数,调用设备添加函数platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)) 完成设备的注册。具体过程参见系统初始化的相关部分。
5.    系统初始化
5.1.    系统初始化的主干线
Start_kernel() èsetup_arch() èreset_init() è kernel_thread(init …) è init() è do_basic_setup() èdriver_init() è do_initcall()

Start_kernel()函数负责初始化内核各个子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。Start_kernel()函数在init/main.c中实现。

asmlinkage void __init start_kernel(void)
{
    char * command_line;
    extern struct kernel_param __start___param[], __stop___param[];

    smp_setup_processor_id();

    /*
     * Need to run as early as possible, to initialize the
     * lockdep hash:
     */
    lockdep_init();

    local_irq_disable();
    early_boot_irqs_off();
    early_init_irq_lock_class();

/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
    lock_kernel();
    boot_cpu_init();
    page_address_init();
    printk(KERN_NOTICE);
    printk(linux_banner);
    setup_arch(&command_line);
//setup processor and machine and destinate some pointers for do_initcalls() functions
// for example init_machine pointer is initialized with smdk_machine_init() function , and //init_machine() function is called by customize_machine(), and the function is processed by //arch_initcall(fn). Therefore  smdk_machine_init() is issured.    by edwin
    setup_per_cpu_areas();
    smp_prepare_boot_cpu();    /* arch-specific boot-cpu hooks */

    /*
     * Set up the scheduler prior starting any interrupts (such as the
     * timer interrupt). Full topology setup happens at smp_init()
     * time - but meanwhile we still have a functioning scheduler.
     */
    sched_init();
    /*
     * Disable preemption - early bootup scheduling is extremely
     * fragile until we cpu_idle() for the first time.
     */
    preempt_disable();
    build_all_zonelists();
    page_alloc_init();
    printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);
    parse_early_param();
    parse_args("Booting kernel", command_line, __start___param,
           __stop___param - __start___param,
           &unknown_bootoption);
    sort_main_extable();
    unwind_init();
    trap_init();
    rcu_init();
    init_IRQ();
    pidhash_init();
    init_timers();
    hrtimers_init();
    softirq_init();
    timekeeping_init();
    time_init();
    profile_init();
    if (!irqs_disabled())
        printk("start_kernel(): bug: interrupts were enabled early/n");
    early_boot_irqs_on();
    local_irq_enable();

    /*
     * HACK ALERT! 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();
    if (panic_later)
        panic(panic_later, panic_param);

    lockdep_info();

    /*
     * Need to run this when irqs are enabled, because it wants
     * to self-test [hard/soft]-irqs on/off lock inversion bugs
     * too:
     */
    locking_selftest();

#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) - "
            "disabling it./n",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();
#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();
    vfs_caches_init(num_physpages);
    radix_tree_init();
    signals_init();
    /* rootfs populating might need page-writeback */
    page_writeback_init();
#ifdef CONFIG_PROC_FS
    proc_root_init();
#endif
    cpuset_init();
    taskstats_init_early();
    delayacct_init();

    check_bugs();

    acpi_early_init(); /* before LAPIC and SMP init */

    /* Do the rest non-__init'ed, we're now alive */
    rest_init();
}

分析start_kernel()源码, 其中setup_arch() 和 reset_init()是两个比较关键的函数。下面将具体分析这两个函数。
5.2.    setup_arch()函数分析
首先我们来分析下setup_arch()函数。
Setup_arch()函数主要工作是安装cpu和machine,并为start_kernel()后面的初始化函数指针指定值。
其中setup_processor()函数调用linux/arch/arm/kernel/head_common.S 中的lookup_processor_type函数查询处理器的型号并安装。

Setup_machine()函数调用inux/arch/arm/kernel/head_common.S 中的lookup_machine_type(__machine_arch_type)函数根据体系结构号__machine_arch_type,在__arch_info_begin和__arch_info_end段空间查询体系结构。问题是__machine_arch_type是在什么时候赋的初值?__arch_info_begin和__arch_info_end段空间到底放的是什么内容?
__machine_arch_type是一个全局变量,在linux/boot/decompress/misc.c的解压缩函数中得以赋值。
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id)
{
    __machine_arch_type    = arch_id;
}

__arch_info_begin和__arch_info_end段空间到底放的内容由链接器决定,存放是.arch.info.init段的内容。这个段是通过段属性__attribute__指定的。Grep一下.arch.info.init 得到./include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = {       / 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义。

#define MACHINE_START(_type,_name)            /
static const struct machine_desc __mach_desc_##_type    /
__attribute_used__                    /
__attribute__((__section__(".arch.info.init"))) = {    /
    .nr        = MACH_TYPE_##_type,        /
    .name        = _name,

#define MACHINE_END                /
};

inux/arch/arm/mach-s3c2410/mach-smdk2410.c中对.arch.info.init段的初始化如下。
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
                    * to SMDK2410 */
    /* Maintainer: Jonas Dietsche */
    .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    = s3c24xx_init_irq,
    .init_machine    = smdk_machine_init,
    .timer        = &s3c24xx_timer,
MACHINE_END

由此可见在.arch.info.init段内存放了__desc_mach_desc_SMDK2410结构体。初始化了相应的初始化函数指针。问题又来了, 这些初始化指针函数是什么时候被调用的呢?
分析发现,不一而同。
如s3c24xx_init_irq()函数是通过start_kernel()里的init_IRQ()函数调用init_arch_irq()实现的。因为在MACHINE_START结构体中  .init_irq    = s3c24xx_init_irq,而在setup_arch()函数中init_arch_irq = mdesc->init_irq, 所以调用init_arch_irq()就相当于调用了s3c24xx_init_irq()。
又如smdk_machine_init()函数的初始化。在MACHINE_START结构体中,函数指针赋值,.init_machine    = smdk_machine_init。而init_machine()函数被linux/arch/arm/kernel/setup.c文件中的customize_machine()函数调用并被arch_initcall(Fn)宏处理,arch_initcall(customize_machine)。 被arch_initcall(Fn)宏处理过函数将linux/init/main.c
do_initcalls()函数调用。 具体参看下边的部分。

void __init setup_arch(char **cmdline_p)
{
    struct tag *tags = (struct tag *)&init_tags;
    struct machine_desc *mdesc;
    char *from = default_command_line;

    setup_processor();
    mdesc = setup_machine(machine_arch_type);//machine_arch_type =SMDK2410  by edwin
    machine_name = mdesc->name;

    if (mdesc->soft_reboot)
        reboot_setup("s");

    if (mdesc->boot_params)
        tags = phys_to_virt(mdesc->boot_params);

    /*
     * If we have the old style parameters, convert them to
     * a tag list.
     */
    if (tags->hdr.tag != ATAG_CORE)
        convert_to_tag_list(tags);
    if (tags->hdr.tag != ATAG_CORE)
        tags = (struct tag *)&init_tags;

    if (mdesc->fixup)
        mdesc->fixup(mdesc, tags, &from, &meminfo);

    if (tags->hdr.tag == ATAG_CORE) {
        if (meminfo.nr_banks != 0)
            squash_mem_tags(tags);
        parse_tags(tags);
    }

    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;

    memcpy(saved_command_line, from, COMMAND_LINE_SIZE);
    saved_command_line[COMMAND_LINE_SIZE-1] = '/0';
    parse_cmdline(cmdline_p, from);
    paging_init(&meminfo, mdesc);
    request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
    smp_init_cpus();
#endif

    cpu_init();

    /*
     * Set up various architecture-specific pointers
     */
    init_arch_irq = mdesc->init_irq;
    system_timer = mdesc->timer;
    init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
    conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
    conswitchp = &dummy_con;
#endif
#endif
}
5.3.    rest_init()函数分析
下面我们来分析下rest_init()函数。
Start_kernel()函数负责初始化内核各子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。在init内核线程中,将执行下列init()函数的程序。Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的init进程等重要工作。

static void noinline rest_init(void)
    __releases(kernel_lock)
{
    kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
    numa_default_policy();
    unlock_kernel();

    /*
     * The boot idle thread must execute schedule()
     * at least one to get things moving:
     */
    preempt_enable_no_resched();
    schedule();
    preempt_disable();

    /* Call into cpu_idle with preempt disabled */
    cpu_idle();
}


static int init(void * unused)
{
    lock_kernel();
    /*
     * init can run on any cpu.
     */
    set_cpus_allowed(current, CPU_MASK_ALL);
    /*
     * Tell the world that we're going to be the grim
     * reaper of innocent orphaned children.
     *
     * We don't want people to have to make incorrect
     * assumptions about where in the task array this
     * can be found.
     */
    child_reaper = current;

    smp_prepare_cpus(max_cpus);

    do_pre_smp_initcalls();

    smp_init();
    sched_init_smp();

    cpuset_init_smp();

    /*
     * Do this before initcalls, because some drivers want to access
     * firmware files.
     */
    populate_rootfs();   //挂接根文件系统

    do_basic_setup();   //初始化设备驱动程序

    /*
     * check if there is an early userspace init.  If yes, let it do all
     * the work        //启动用户空间的init进程
     */

    if (!ramdisk_execute_command)
        ramdisk_execute_command = "/init";

    if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
        ramdisk_execute_command = NULL;
        prepare_namespace();
    }

    /*
     * Ok, we have completed the initial bootup, and
     * we're essentially up and running. Get rid of the
     * initmem segments and start the user-mode stuff..
     */
    free_initmem();
    unlock_kernel();
    mark_rodata_ro();
    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);
    (void) sys_dup(0);

    if (ramdisk_execute_command) {
        run_init_process(ramdisk_execute_command);
        printk(KERN_WARNING "Failed to execute %s/n",
                ramdisk_execute_command);
    }

    /*
     * We try each of these until one succeeds.
     *
     * The Bourne shell can be used instead of init if we are
     * trying to recover a really broken machine.
     */
    if (execute_command) {
        run_init_process(execute_command);
        printk(KERN_WARNING "Failed to execute %s.  Attempting "
                    "defaults.../n", execute_command);
    }
    run_init_process("/sbin/init");
    run_init_process("/etc/init");
    run_init_process("/bin/init");
    run_init_process("/bin/sh");

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

5.3.1.    挂接根文件系统
Linux/init/ramfs.c
void __init populate_rootfs(void)
{
    char *err = unpack_to_rootfs(__initramfs_start,
             __initramfs_end - __initramfs_start, 0);
    if (err)
        panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
    if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
        int fd;
        printk(KERN_INFO "checking if image is initramfs...");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start, 1);
        if (!err) {
            printk(" it is/n");
            unpack_to_rootfs((char *)initrd_start,
                initrd_end - initrd_start, 0);
            free_initrd();
            return;
        }
        printk("it isn't (%s); looks like an initrd/n", err);
        fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
        if (fd >= 0) {
            sys_write(fd, (char *)initrd_start,
                    initrd_end - initrd_start);
            sys_close(fd);
            free_initrd();
        }
#else
        printk(KERN_INFO "Unpacking initramfs...");
        err = unpack_to_rootfs((char *)initrd_start,
            initrd_end - initrd_start, 0);
        if (err)
            panic(err);
        printk(" done/n");
        free_initrd();
#endif
    }
#endif
}

5.3.2.    初始化设备5.3.3.    驱动程序
linux/init/main.c
static void __init do_basic_setup(void)
{
    /* drivers will send hotplug events */
    init_workqueues();
    usermodehelper_init();
    driver_init();   /* 初始化驱动程序模型。调用驱动初始化函数初始化子系统。 */

#ifdef CONFIG_SYSCTL
    sysctl_init();
#endif

    do_initcalls();
}


linux/init/main.c
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
    initcall_t *call;
    int count = preempt_count();

    for (call = __initcall_start; call < __initcall_end; call++) {
        char *msg = NULL;
        char msgbuf[40];
        int result;

        if (initcall_debug) {
            printk("Calling initcall 0x%p", *call);
            print_fn_descriptor_symbol(": %s()",
                    (unsigned long) *call);
            printk("/n");
        }

        result = (*call)();

        ……
……
……
    }

    /* Make sure there is no pending stuff from the initcall sequence */
    flush_scheduled_work();
}
分析上面一段代码可以看出,设备的初始化是通过do_basic_setup()函数调用do_initcalls()函数,实现__initcall_start, __initcall_end段之间的指针函数执行的。而到底是那些驱动函数怎么会被集中到这个段内的呢?我们知道系统内存空间的分配是由链接器ld读取链接脚本文件决定。链接器将同样属性的文件组织到相同的段里面去,如所有的.text段都被放在一起。在链接脚本里面可以获得某块内存空间的具体地址。我们来看下linux-2.6.18.8/arch/arm/kernel/vmlinux.lds.S文件。由于文件过长,只贴出和__initcall_start, __initcall_end相关的部分。
__initcall_start = .;
            *(.initcall1.init)
            *(.initcall2.init)
            *(.initcall3.init)
            *(.initcall4.init)
            *(.initcall5.init)
            *(.initcall6.init)
            *(.initcall7.init)
        __initcall_end = .;
从脚本文件中我们可以看出, 在__initcall_start, __initcall_end之间放置的是属行为(.initcall*.init)的函数数据 。在linux/include/linux/init.h文件中可以知道,(.initcall*.init)属性是由__define_initcall(level, fn)宏设定的。

#define __define_initcall(level,fn) /
    static initcall_t __initcall_##fn __attribute_used__ /
    __attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn)        __define_initcall("1",fn)
#define postcore_initcall(fn)        __define_initcall("2",fn)
#define arch_initcall(fn)        __define_initcall("3",fn)
#define subsys_initcall(fn)        __define_initcall("4",fn)
#define fs_initcall(fn)            __define_initcall("5",fn)
#define device_initcall(fn)        __define_initcall("6",fn)
#define late_initcall(fn)        __define_initcall("7",fn)
#define __initcall(fn)      device_initcall(fn)

由此可以判断,所有的设备驱动函数都必然通过*_initcall(fn)宏的处理。以此为入口,可以查询所有的设备驱动。
core_initcall(fn)
static int __init consistent_init(void)        linux/arch/arm/mm/consistent.c
static int __init v6_userpage_init(void)      linux/arch/arm/mm/copypage-v6.c
static int __init init_dma(void)             linux/arch/arm/kernel/dma.c
static int __init s3c2410_core_init(void)     linux/arch/arm/mach-s3c2410/s3c2410.c

postcore_initcall(fn)
static int ecard_bus_init(void)                 linux/arch/arm/kernel/ecard.c

arch_initcall(fn)
static __init int bast_irq_init(void)             linux/arch/arm/mach-s3c2410/bast-irq.c
static int __init s3c_arch_init(void)         linux/arch/arm/mach-s3c2410/cpu.c
static __init int pm_simtec_init(void)         linux/arch/arm/mach-s3c2410/pm-simtec.c
static int __init customize_machine(void)     linux/arch/arm/kernel/setup.c

subsys_initcall(fn)
static int __init ecard_init(void)             linux/arch/arm/kernel/ecard.c
int __init scoop_init(void)                 linux/arch/arm/common/scoop.c
static int __init topology_init(void)         linux/arch/arm/kernel/setup.c

fs_initcall(fn)
static int __init alignment_init(void)         linux/arch/arm/mm/alignment.c

device_initcall(fn)
static int __init leds_init(void)             linux/arch/arm/kernel/time.c
static int __init timer_init_sysfs(void)         linux/arch/arm/kernel/time.c

late_initcall(fn)
static int __init crunch_init(void)             arch/arm/kernel/crunch.c
static int __init arm_mrc_hook_init(void)     linux/arch/arm/kernel/traps.c

5.3.4.    启动用户空间的程序

2011-03-25 14:48:00 figthter_cui 阅读数 1554

================================================================
浅谈分析Arm linux 内核移植及系统初始化的过程(一)

================================================================

学习嵌入式ARM linux,主要想必三个方向发展:
1、嵌入式linux应用软件开发
2、linux内核的剪裁和移植
3、嵌入式linux底层驱动的开发

本文就Arm linux 内核移植及系统初始化过程进行分析:咨询QQ:313807838

主要介绍内核移植过程中涉及文件的分布及其用途,以及简单介绍系统的初始化过程。整个arm linux内核的启动可分为三个阶段:第一阶段主要是进行cpu和体系结构的检查、cpu本身的初始化以及页表的建立等;第二阶段主要是对系统中的一些基础设施进行初始化;最后则是更高层次的初始化,如根设备和外部设备的初始化。了解系统的初始化过程,有益于更好地移植内核。

1. 内核移植

2. 涉及文件分布介绍
2.1. 内核移植

2.2. 涉及的头文件
/linux-2.6.18.8/include
[root@localhost include]# tree -L 1
.
|-- Kbuild
|-- acpi
|-- asm -> asm-arm
|-- asm-alpha
|-- asm-arm ------------------------------->(1)
|-- asm-sparc
|-- asm-sparc64
|-- config
|-- keys
|-- linux ------------------------------->(2)
|-- math-emu
|-- media
|-- mtd
|-- net
|-- pcmcia
|-- rdma
|-- rxrpc
|-- scsi
|-- sound
`-- video

内核移植过程中涉及到的头文件包括处理器相关的头文件(1)和处理器无关的头文件(2)。

2.3. 内核移植2.4. 涉及的源文件
/linux-2.6.18.8/arch/arm
[root@localhost arm]# tree -L 1
.
|-- Kconfig
|-- Kconfig-nommu
|-- Kconfig.debug
|-- Makefile
|-- boot ------------------------------->(2)
|-- common
|-- configs
|-- kernel ------------------------------->(3)
|-- lib
|-- mach-at91rm9200
……
|-- mach-omap1
|-- mach-omap2
|-- mach-realview
|-- mach-rpc
|-- mach-s3c2410 ------------------------------->(4)
|-- mach-sa1100
|-- mach-versatile
|-- mm ------------------------------->(5)
|-- nwfpe
|-- oprofile
|-- plat-omap
|-- tools ------------------------------->(1)
`-- vfp

(1)
/linux-2.6.18.8/arch/arm/tools
[root@localhost tools]# tree -L 1
.
|-- Makefile
|-- gen-mach-types
`-- mach-types

Mach-types 文件定义了不同系统平台的系统平台号。移植linux内核到新的平台上需要对新的平台登记系统平台号。

Mach-types文件格式如下:
# machine_is_xxx CONFIG_xxxx MACH_TYPE_xxx number
s3c2410 ARCH_S3C2410 S3C2410 182
smdk2410 ARCH_SMDK2410 SMDK2410 193

之所以需要这些信息,是因为脚本文件linux/arch/arm/tools/gen-mach-types需要linux/arch/tools/mach-types来产生linux/include/asm-arm/mach-types.h文件,该文件中设置了一些宏定义,需要这些宏定义来为目标系统选择合适的代码。

(2)
linux-2.6.18.8/arch/arm/boot/compressed
[root@localhost compressed]# tree -L 1
.
|-- Makefile
|-- Makefile.debug
|-- big-endian.S
|-- head-at91rm9200.S
2 浅谈分析Arm linux 内核移植及系统初始化的过程
|-- head.S
|-- ll_char_wr.S
|-- misc.c
|-- ofw-shark.c
|-- piggy.S
`-- vmlinux.lds.in

Head.s 是内核映像的入口代码,是自引导程序。自引导程序包含一些初始化程序,这些程序都是体系结构相关的。在对系统作完初始化设置工作后,调用misc.c文件中的decompress_kernel()函数解压缩内核映像到指定的位置,然后跳转到kernel的入口地址。

Vmlinux.lds.in用来生成内核映像的内存配置文件。

(3)
linux-2.6.18.8/arch/arm/kernel
[root@localhost kernel]# tree -L 1
.
|-- Makefile
|-- apm.c
|-- armksyms.c
|-- arthur.c
|-- asm-offsets.c
|-- bios32.c
|-- calls.S
|-- dma.c
|-- ecard.c
|-- entry-armv.S
|-- entry-common.S
|-- entry-header.S
|-- fiq.c
|-- head-common.S
|-- head-nommu.S
|-- head.S
|-- init_task.c
|-- io.c
|-- irq.c
|-- isa.c
|-- module.c
|-- process.c
|-- ptrace.c
|-- ptrace.h
|-- semaphore.c
|-- setup.c
|-- smp.c
|-- sys_arm.c
|-- time.c
|-- traps.c
`-- vmlinux.lds.S

内核入口处也是由一段汇编语言实现的,由head.s和head-common.s两个文件组成。
Head.s 是内核的入口文件, 在head.s的末尾处 #i nclude "head-common.S"。 经过一系列的初始化后,跳转到linux-2.6.18.8/init/main.c中的start_kernel()函数中,开始内核的基本初始化过程。


/linux-2.6.18.8/init
[root@localhost init]# tree
.
|-- Kconfig
|-- Makefile
|-- calibrate.c
|-- do_mounts.c
|-- do_mounts.h
|-- do_mounts_initrd.c
|-- do_mounts_md.c
|-- do_mounts_rd.c
|-- initramfs.c
|-- main.c
`-- version.c

(4)
/linux-2.6.18.8/arch/arm/mach-s3c2410
[root@localhost mach-s3c2410]# tree -L 1
.
|-- Kconfig
|-- Makefile
|-- Makefile.boot
|-- bast-irq.c
|-- bast.h
|-- clock.c
|-- clock.h
|-- common-smdk.c
|-- common-smdk.h
|-- cpu.c
|-- cpu.h
|-- devs.c
|-- devs.h
|-- dma.c
|-- gpio.c
|-- irq.c
|-- irq.h
|-- mach-anubis.c
|-- mach-smdk2410.c
|-- pm-simtec.c
|-- pm.c
|-- pm.h
|-- s3c2400-gpio.c
|-- s3c2400.h
|-- s3c2410-clock.c
|-- s3c2410-gpio.c
|-- s3c2410.c
|-- s3c2410.h
|-- sleep.S
|-- time.c
|-- usb-simtec.c
`-- usb-simtec.h

这个目录中的文件都是板级相关的,其中比较重要是如下几个:
linux/arch/arm/mach-s3c2410/cpu.c
linux/arch/arm/mach-s3c2410/common-smdk.c
linux/arch/arm/mach-s3c2410/devs.c
linux/arch/arm/mach-s3c2410/mach-smdk2410.c
linux/arch/arm/mach-s3c2410/Makefile.boot
linux/arch/arm/mach-s3c2410/s3c2410.c

3. 处理器和设备4.
这里主要介绍处理器和设备的描述和操作过程。设备描述在linux/arch/arm/mach-s3c2410/devs.c和linux/arch/arm/mach-s3c2410/common-smdk.c中实现。最后以nand flash为例具体介绍。






================================================================
浅谈分析Arm linux 内核移植及系统初始化的过程(二)

================================================================
3 浅谈分析Arm linux 内核移植及系统初始化的过程。咨询QQ:313807838
4.1. 处理器、设备4.2. 描述
设备描述主要两个结构体完成:struct resource和struct platform_device。
先来看看着两个结构体的定义:
struct resource {
resource_size_t start;
resource_size_t end;
const char *name;
unsigned long flags;
struct resource *parent, *sibling, *child;
};

Resource结构体主要是描述了设备在系统中的起止地址、名称、标志以及为了链式描述方便指向本结构体类型的指针。Resource定义的实例将被添加到platform_device结构体对象中去。

struct platform_device {
const char * name;
u32 id;
struct device dev;
u32 num_resources;
struct resource * resource;
};

Platform_device结构体包括结构体的名称、ID号、平台相关的信息、设备的数目以及上面定义的resource信息。Platform_device结构对象将被直接通过设备操作函数注册导系统中去。具体注册和注销过程在下一节介绍。

4.3. 处理器、设备4.4. 操作
(1) int platform_device_register(struct platform_device * pdev); 注册设备
(2) void platform_device_unregister(struct platform_device * pdev); 注销设备
(3) int platform_add_devices(struct platform_device **devs, int num);添加设备,通过调用上面两个函数实现。
4.5. 添加Nand flash设备4.6.
下面以nand flash 设备的描述为例,具体介绍下设备的描述和注册过程。

// resource结构体实例s3c_nand_resource 对nand flash 控制器描述,包括控制器的起止地址和标志。
static struct resource s3c_nand_resource[] = {
[0] = {
.start = S3C2410_PA_NAND,
.end = S3C2410_PA_NAND + S3C24XX_SZ_NAND - 1,
.flags = IORESOURCE_MEM,
}
};

//platform_device结构体实例s3c_device_nand定义了设备的名称、ID号并把resource对象作为其成员之一。
struct platform_device s3c_device_nand = {
.name = "s3c2410-nand",
.id = -1,
.num_resources = ARRAY_SIZE(s3c_nand_resource),
.resource = s3c_nand_resource,
};

// nand flash 的分区情况,由mtd_partition结构体定义。
static struct mtd_partition smdk_default_nand_part[] = {
[0] = {
.name = "Boot Agent",
.size = SZ_16K,
.offset = 0,
},
[1] = {
.name = "S3C2410 flash partition 1",
.offset = 0,
.size = SZ_2M,
},
[2] = {
.name = "S3C2410 flash partition 2",
.offset = SZ_4M,
.size = SZ_4M,
},
[3] = {
.name = "S3C2410 flash partition 3",
.offset = SZ_8M,
.size = SZ_2M,
},
[4] = {
.name = "S3C2410 flash partition 4",



================================================================

浅谈分析Arm linux 内核移植及系统初始化的过程(三)

================================================================

4、浅谈分析Arm linux 内核移植及系统初始化的过程 QQ:313807838
.offset = SZ_1M * 10,
.size = SZ_4M,
},
[5] = {
.name = "S3C2410 flash partition 5",
.offset = SZ_1M * 14,
.size = SZ_1M * 10,
},
[6] = {
.name = "S3C2410 flash partition 6",
.offset = SZ_1M * 24,
.size = SZ_1M * 24,
},
[7] = {
.name = "S3C2410 flash partition 7",
.offset = SZ_1M * 48,
.size = SZ_16M,
}
};

static struct s3c2410_nand_set smdk_nand_sets[] = {
[0] = {
.name = "NAND",
.nr_chips = 1,
.nr_partitions = ARRAY_SIZE(smdk_default_nand_part),
.partitions = smdk_default_nand_part,
},
};

/* choose a set of timings which should suit most 512Mbit
* chips and beyond.
*/

static struct s3c2410_platform_nand smdk_nand_info = {
.tacls = 20,
.twrph0 = 60,
.twrph1 = 20,
.nr_sets = ARRAY_SIZE(smdk_nand_sets),
.sets = smdk_nand_sets,
};

/* devices we initialise */
// 最后将nand flash 设备加入到系统即将注册的设备集合中。
static struct platform_device __initdata *smdk_devs[] = {
&s3c_device_nand,
&smdk_led4,
&smdk_led5,
&smdk_led6,
&smdk_led7,
};

然后通过smdk_machine_init()函数,调用设备添加函数platform_add_devices(smdk_devs, ARRAY_SIZE(smdk_devs)) 完成设备的注册。具体过程参见系统初始化的相关部分。
5. 系统初始化
5.1. 系统初始化的主干线
Start_kernel() èsetup_arch() èreset_init() è kernel_thread(init …) è init() è do_basic_setup() èdriver_init() è do_initcall()

Start_kernel()函数负责初始化内核各个子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。Start_kernel()函数在init/main.c中实现。

asmlinkage void __init start_kernel(void)
{
char * command_line;
extern struct kernel_param __start___param[], __stop___param[];

smp_setup_processor_id();

/*
* Need to run as early as possible, to initialize the
* lockdep hash:
*/
lockdep_init();

local_irq_disable();
early_boot_irqs_off();
early_init_irq_lock_class();

/*
* Interrupts are still disabled. Do necessary setups, then
* enable them
*/
lock_kernel();
boot_cpu_init();
page_address_init();
printk(KERN_NOTICE);
printk(linux_banner);
setup_arch(&command_line);
//setup processor and machine and destinate some pointers for do_initcalls() s

5、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
// for example init_machine pointer is initialized with smdk_machine_init() , and //init_machine() is called by customize_machine(), and the is processed by //arch_initcall(fn). Therefore smdk_machine_init() is issured. by edwin
setup_per_cpu_areas();
smp_prepare_boot_cpu(); /* arch-specific boot-cpu hooks */

/*
* Set up the scheduler prior starting any interrupts (such as the
* timer interrupt). Full topology setup happens at smp_init()
* time - but meanwhile we still have a ing scheduler.
*/
sched_init();
/*
* Disable preemption - early bootup scheduling is extremely
* fragile until we cpu_idle() for the first time.
*/
preempt_disable();
build_all_zonelists();
page_alloc_init();
printk(KERN_NOTICE "Kernel command line: %s/n", saved_command_line);
parse_early_param();
parse_args("Booting kernel", command_line, __start___param,
__stop___param - __start___param,
&unknown_bootoption);
sort_main_extable();
unwind_init();
trap_init();
rcu_init();
init_IRQ();
pidhash_init();
init_timers();
hrtimers_init();
softirq_init();
timekeeping_init();
time_init();
profile_init();
if (!irqs_disabled())
printk("start_kernel(): bug: interrupts were enabled early/n");
early_boot_irqs_on();
local_irq_enable();

/*
* HACK ALERT! 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();
if (panic_later)
panic(panic_later, panic_param);

lockdep_info();

/*
* Need to run this when irqs are enabled, because it wants
* to self-test [hard/soft]-irqs on/off lock inversion bugs
* too:
*/
locking_selftest();

#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) - "

6、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
"disabling it./n",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();
#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();
vfs_caches_init(num_physpages);
radix_tree_init();
signals_init();
/* rootfs populating might need page-writeback */
page_writeback_init();
#ifdef CONFIG_PROC_FS
proc_root_init();
#endif
cpuset_init();
taskstats_init_early();
delayacct_init();

check_bugs();

acpi_early_init(); /* before LAPIC and SMP init */

/* Do the rest non-__init'ed, we're now alive */
rest_init();
}

分析start_kernel()源码, 其中setup_arch() 和 reset_init()是两个比较关键的函数。下面将具体分析这两个函数。
5.2. setup_arch()函数分析
首先我们来分析下setup_arch()函数。
Setup_arch()函数主要工作是安装cpu和machine,并为start_kernel()后面的初始化函数指针指定值。
其中setup_processor()函数调用linux/arch/arm/kernel/head_common.S 中的lookup_processor_type函数查询处理器的型号并安装。

Setup_machine()函数调用inux/arch/arm/kernel/head_common.S 中的lookup_machine_type(__machine_arch_type)函数根据体系结构号__machine_arch_type,在__arch_info_begin和__arch_info_end段空间查询体系结构。问题是__machine_arch_type是在什么时候赋的初值?__arch_info_begin和__arch_info_end段空间到底放的是什么内容?
__machine_arch_type是一个全局变量,在linux/boot/decompress/misc.c的解压缩函数中得以赋值。
decompress_kernel(ulg output_start, ulg free_mem_ptr_p, ulg free_mem_ptr_end_p, int arch_id)
{
__machine_arch_type = arch_id;
}

__arch_info_begin和__arch_info_end段空间到底放的内容由链接器决定,存放是.arch.info.init段的内容。这个段是通过段属性__attribute__指定的。Grep一下.arch.info.init 得到./include/asm/mach/arch.h:53: __attribute__((__section__(".arch.info.init"))) = { / 在linux/include/asm-arm/mach/arch.h 中发现MACHINE_START宏定义。

#define MACHINE_START(_type,_name) /
static const struct machine_desc __mach_desc_##_type /
__attribute_used__ /
__attribute__((__section__(".arch.info.init"))) = { /
.nr = MACH_TYPE_##_type, /
.name = _name,

#define MACHINE_END /
};

inux/arch/arm/mach-s3c2410/mach-smdk2410.c中对.arch.info.init段的初始化如下。

================================================================

浅谈分析Arm linux 内核移植及系统初始化的过程(四)

================================================================

7、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
MACHINE_START(SMDK2410, "SMDK2410") /* @TODO: request a new identifier and switch
* to SMDK2410 */
/* Maintainer: Jonas Dietsche */
.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 = s3c24xx_init_irq,
.init_machine = smdk_machine_init,
.timer = &s3c24xx_timer,
MACHINE_END

由此可见在.arch.info.init段内存放了__desc_mach_desc_SMDK2410结构体。初始化了相应的初始化函数指针。问题又来了, 这些初始化指针函数是什么时候被调用的呢?
分析发现,不一而同。
如s3c24xx_init_irq()函数是通过start_kernel()里的init_IRQ()函数调用init_arch_irq()实现的。因为在MACHINE_START结构体中 .init_irq = s3c24xx_init_irq,而在setup_arch()函数中init_arch_irq = mdesc->init_irq, 所以调用init_arch_irq()就相当于调用了s3c24xx_init_irq()。
又如smdk_machine_init()函数的初始化。在MACHINE_START结构体中,函数指针赋值,.init_machine = smdk_machine_init。而init_machine()函数被linux/arch/arm/kernel/setup.c文件中的customize_machine()函数调用并被arch_initcall(Fn)宏处理,arch_initcall(customize_machine)。 被arch_initcall(Fn)宏处理过函数将linux/init/main.c
do_initcalls()函数调用。 具体参看下边的部分。

void __init setup_arch(char **cmdline_p)
{
struct tag *tags = (struct tag *)&init_tags;
struct machine_desc *mdesc;
char *from = default_command_line;

setup_processor();
mdesc = setup_machine(machine_arch_type);//machine_arch_type =SMDK2410 by edwin
machine_name = mdesc->name;

if (mdesc->soft_reboot)
reboot_setup("s");

if (mdesc->boot_params)
tags = phys_to_virt(mdesc->boot_params);

/*
* If we have the old style parameters, convert them to
* a tag list.
*/
if (tags->hdr.tag != ATAG_CORE)
convert_to_tag_list(tags);
if (tags->hdr.tag != ATAG_CORE)
tags = (struct tag *)&init_tags;

if (mdesc->fixup)
mdesc->fixup(mdesc, tags, &from, &meminfo);

if (tags->hdr.tag == ATAG_CORE) {
if (meminfo.nr_banks != 0)
squash_mem_tags(tags);
parse_tags(tags);
}

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;

memcpy(saved_command_line, from, COMMAND_LINE_SIZE);

8、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
saved_command_line[COMMAND_LINE_SIZE-1] = '/0';
parse_cmdline(cmdline_p, from);
paging_init(&meminfo, mdesc);
request_standard_resources(&meminfo, mdesc);

#ifdef CONFIG_SMP
smp_init_cpus();
#endif

cpu_init();

/*
* Set up various architecture-specific pointers
*/
init_arch_irq = mdesc->init_irq;
system_timer = mdesc->timer;
init_machine = mdesc->init_machine;

#ifdef CONFIG_VT
#if defined(CONFIG_VGA_CONSOLE)
conswitchp = &vga_con;
#elif defined(CONFIG_DUMMY_CONSOLE)
conswitchp = &dummy_con;
#endif
#endif
}
5.3. rest_init()函数分析
下面我们来分析下rest_init()函数。
Start_kernel()函数负责初始化内核各子系统,最后调用reset_init(),启动一个叫做init的内核线程,继续初始化。在init内核线程中,将执行下列init()函数的程序。Init()函数负责完成根文件系统的挂接、初始化设备驱动程序和启动用户空间的init进程等重要工作。

static void noinline rest_init(void)
__releases(kernel_lock)
{
kernel_thread(init, NULL, CLONE_FS | CLONE_SIGHAND);
numa_default_policy();
unlock_kernel();

/*
* The boot idle thread must execute schedule()
* at least one to get things moving:
*/
preempt_enable_no_resched();
schedule();
preempt_disable();

/* Call into cpu_idle with preempt disabled */
cpu_idle();
}


static int init(void * unused)
{
lock_kernel();
/*
* init can run on any cpu.
*/
set_cpus_allowed(current, CPU_MASK_ALL);
/*
* Tell the world that we're going to be the grim
* reaper of innocent orphaned children.
*
* We don't want people to have to make incorrect
* assumptions about where in the task array this
* can be found.
*/
child_reaper = current;

smp_prepare_cpus(max_cpus);

do_pre_smp_initcalls();

smp_init();
sched_init_smp();

cpuset_init_smp();

/*
* Do this before initcalls, because some drivers want to access
* firmware files.
*/
populate_rootfs(); //挂接根文件系统

do_basic_setup(); //初始化设备驱动程序

/*
* check if there is an early userspace init. If yes, let it do all
* the work //启动用户空间的init进程

9、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
*/

if (!ramdisk_execute_command)
ramdisk_execute_command = "/init";

if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
ramdisk_execute_command = NULL;
prepare_namespace();
}

/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
free_initmem();
unlock_kernel();
mark_rodata_ro();
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);
(void) sys_dup(0);

if (ramdisk_execute_command) {
run_init_process(ramdisk_execute_command);
printk(KERN_WARNING "Failed to execute %s/n",
ramdisk_execute_command);
}

/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
* trying to recover a really broken machine.
*/
if (execute_command) {
run_init_process(execute_command);
printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults.../n", execute_command);
}
run_init_process("/sbin/init");
run_init_process("/etc/init");
run_init_process("/bin/init");
run_init_process("/bin/sh");

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

5.3.1. 挂接根文件系统
Linux/init/ramfs.c
void __init populate_rootfs(void)
{
char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
if (err)
panic(err);
#ifdef CONFIG_BLK_DEV_INITRD
if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
int fd;
printk(KERN_INFO "checking if image is initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 1);
if (!err) {
printk(" it is/n");
unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
free_initrd();
return;
}
printk("it isn't (%s); looks like an initrd/n", err);

================================================================

浅谈分析Arm linux 内核移植及系统初始化的过程(五)

================================================================

10、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
if (fd >= 0) {
sys_write(fd, (char *)initrd_start,
initrd_end - initrd_start);
sys_close(fd);
free_initrd();
}
#else
printk(KERN_INFO "Unpacking initramfs...");
err = unpack_to_rootfs((char *)initrd_start,
initrd_end - initrd_start, 0);
if (err)
panic(err);
printk(" done/n");
free_initrd();
#endif
}
#endif
}

5.3.2. 初始化设备5.3.3. 驱动程序
linux/init/main.c
static void __init do_basic_setup(void)
{
/* drivers will send hotplug events */
init_workqueues();
usermodehelper_init();
driver_init(); /* 初始化驱动程序模型。调用驱动初始化函数初始化子系统。 */

#ifdef CONFIG_SYSCTL
sysctl_init();
#endif

do_initcalls();
}


linux/init/main.c
extern initcall_t __initcall_start[], __initcall_end[];

static void __init do_initcalls(void)
{
initcall_t *call;
int count = preempt_count();

for (call = __initcall_start; call < __initcall_end; call++) {
char *msg = NULL;
char msgbuf[40];
int result;

if (initcall_debug) {
printk("Calling initcall 0x%p", *call);
print_fn_deor_symbol(": %s()",
(unsigned long) *call);
printk("/n");
}

result = (*call)();

……
……
……
}

/* Make sure there is no pending stuff from the initcall sequence */
flush_scheduled_work();
}
分析上面一段代码可以看出,设备的初始化是通过do_basic_setup()函数调用do_initcalls()函数,实现__initcall_start, __initcall_end段之间的指针函数执行的。而到底是那些驱动函数怎么会被集中到这个段内的呢?我们知道系统内存空间的分配是由链接器ld读取链接脚本文件决定。链接器将同样属性的文件组织到相同的段里面去,如所有的.text段都被放在一起。在链接脚本里面可以获得某块内存空间的具体地址。我们来看下linux-2.6.18.8/arch/arm/kernel/vmlinux.lds.S文件。由于文件过长,只贴出和__initcall_start, __initcall_end相关的部分。
__initcall_start = .;
*(.initcall1.init)
*(.initcall2.init)
*(.initcall3.init)
*(.initcall4.init)
*(.initcall5.init)
*(.initcall6.init)
*(.initcall7.init)
__initcall_end = .;
从脚本文件中我们可以看出, 在__initcall_start, __initcall_end之间放置的是属行为(.initcall*.init)的函数数据 。在linux/include/linux/init.h文件中可以知道,(.initcall*.init)属性是由__define_initcall(level, fn)宏设定的。

#define __define_initcall(level,fn) /
static initcall_t __initcall_##fn __attribute_used__ /

11、浅谈分析Arm linux 内核移植及系统初始化的过程 咨询QQ:313807838
__attribute__((__section__(".initcall" level ".init"))) = fn

#define core_initcall(fn) __define_initcall("1",fn)
#define postcore_initcall(fn) __define_initcall("2",fn)
#define arch_initcall(fn) __define_initcall("3",fn)
#define subsys_initcall(fn) __define_initcall("4",fn)
#define fs_initcall(fn) __define_initcall("5",fn)
#define device_initcall(fn) __define_initcall("6",fn)
#define late_initcall(fn) __define_initcall("7",fn)
#define __initcall(fn) device_initcall(fn)

由此可以判断,所有的设备驱动函数都必然通过*_initcall(fn)宏的处理。以此为入口,可以查询所有的设备驱动。
core_initcall(fn)
static int __init consistent_init(void) linux/arch/arm/mm/consistent.c
static int __init v6_userpage_init(void) linux/arch/arm/mm/copypage-v6.c
static int __init init_dma(void) linux/arch/arm/kernel/dma.c
static int __init s3c2410_core_init(void) linux/arch/arm/mach-s3c2410/s3c2410.c

postcore_initcall(fn)
static int ecard_bus_init(void) linux/arch/arm/kernel/ecard.c

arch_initcall(fn)
static __init int bast_irq_init(void) linux/arch/arm/mach-s3c2410/bast-irq.c
static int __init s3c_arch_init(void) linux/arch/arm/mach-s3c2410/cpu.c
static __init int pm_simtec_init(void) linux/arch/arm/mach-s3c2410/pm-simtec.c
static int __init customize_machine(void) linux/arch/arm/kernel/setup.c

subsys_initcall(fn)
static int __init ecard_init(void) linux/arch/arm/kernel/ecard.c
int __init scoop_init(void) linux/arch/arm/common/scoop.c
static int __init topology_init(void) linux/arch/arm/kernel/setup.c

fs_initcall(fn)
static int __init alignment_init(void) linux/arch/arm/mm/alignment.c

device_initcall(fn)
static int __init leds_init(void) linux/arch/arm/kernel/time.c
static int __init timer_init_sysfs(void) linux/arch/arm/kernel/time.c

late_initcall(fn)
static int __init crunch_init(void) arch/arm/kernel/crunch.c
static int __init arm_mrc_hook_init(void) linux/arch/arm/kernel/traps.c

================================================================

================================================================

没有更多推荐了,返回首页