新闻  |   论坛  |   博客  |   在线研讨会
Linux内核移植
mayer | 2009-06-01 18:34:20    阅读:2119   发布文章

Linux内核移植

rar点击下载

内核源码结构
arch目录包括了所有和体系结构相关的核心代码,对于每个构架的CPU。arch目录下面对应一个子目录,例如arch/arm、(arch/i386就是Intel CPU及与之相兼容体系结构的子目录)。
block目录块设备通用函数
crypto目录常用加密和散列算法,还有一些压缩和CRC校验算法。
drivers目录所有的设备驱动程序,每个不同的驱动占用一个子目录,比如drivers/block/为块设备驱动程序,drivers/char/为字符设备驱动程序,drivers/mtd/为NOR Flash、NAND Flash等存储设备的驱动程序。
Documentation目录关于内核各部分的通用解释和注释。
fs目录各种支持的文件系统,如fs/ext2、fs/ext3、fs/jffs2等
include目录内核头文件。有基本头文件(存放在include/linux/目录下)、各种驱动或功能部件的头文件(比如include/media/、include/mtd、include/net/)、各种体系相关的头文件(比如include/asm-arm/、include/asm-i386/)。当配置内核后,include/asm/是某个include/asm-xxx(比如include/asm-arm/的链接)。
init目录包含核心的初始化代码(不是系统的引导代码),有main.c文件中的start_kernel函数是内核引导后运行的第一个函数。
ipc目录进程间通信的代码。
kernel目录内核管理的核心代码,包括进程调度、定时器等,和平台相关的一部分代码放在arch/*/kernel目录下。
lib目录内核用到的一些库函数代码。,比如crc32.c、string.c。
mm目录内存管理代码,和平台相关的一部分代码放在arch/*/mm目录下。
net目录网络相关代码,每个子目录对应网络的一个方面。实现了各种常见的网络协议。
scripts目录用于配置、编译内核的脚本文件。
security目录安全、密钥相关的代码。主要是一个SELinux的模块。
sound目录常用音频设备的驱动程序等。
usr目录用来制作一个压缩的cpio归档文件:initrd的镜像,它可以作为内核启动后挂接(mount)的第一个文件系统(一般用不到)。
Linux Makefile分析
内核中的哪些文件被编译?它们是怎样被编译的?它们的连接时的顺序如何确定的?……这些都是通过Makefile来管理。从最简单的角度来总结Makefile的作用有以下三点。
1、决定编译哪些文件。
2、怎样编译这些文件。
3、怎样连接这些文件,最重要的是它们的顺序如何?
顶层的Makefile    它是所有Makefile文件的核心,从总体上控制着内核的编译、连接
.config    配置文件,在配置内核时产生。所有Makefile文件(包含顶层目录及各级子目录)都是根据.config来决定使用哪些文件
arch/*/Makefile    对应体系结构的Makefile,它用来决定哪些结构体系相关的文件参与内核的生成,并提供一些规则生成特定格式的内核映像
scripts/Makefile.*    Makefile共用的通用规则、脚本等
kbuild Makefiles    各级子目录下的Makefile,它们相对简单,被上一层Makefile调用来编译当前目录下的文件
内核文档Documentation\kbuild\makefiles.txt讲得很透彻。
(1)决定编译哪些文件。
①顶层的Makefile决定内核根目录下哪些子目录将被编进内核。
②arch/*/Makefile决定arch/$(ARCH)目录下哪些文件、哪些目录将被编进内核。
③各级子目录下的Makefile决定所在目录下哪些文件将被编进内核,哪些文件将被编成模块(即驱动程序),哪些子目录继续调用它们的Makefile


init-y        := init/
drivers-y    := drivers/ sound/
net-y        := net/
libs-y        := lib/
core-y        := usr/
可见,顶层Makefile将13个子目录分为5类:init-y、drivers-y、net-y、libs-y、core-y。
include $(srctree)/arch/$(ARCH)/Makefile
对于ARCH变量,可以执行make命令时传入,比如“make ARCH=arm ...”。另外,对于非x86平台,还需要制定交叉编译工具,这也可以在执行make命令时传入,比如“make COROSS_COMPILE=arm-linux- ...”。为了方便,常在Makefile中进行修改。
修改前:
ARCH        ?= $(SUBARCH)
CROSS_COMPILE    ?=
修改后:
ARCH        ?= arm
CROSS_COMPILE    ?= arm-linux

        head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
...
core-y                += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y                += $(MACHINE)
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2400/
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2412/
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2440/
...
libs-y                := arch/arm/lib/ $(libs-y)
除了前面的5类子目录外,又出现了另一类:head-y不过它直接以文件名出现。MMUEXT在/arch/arm/Makefile前面定义,对于没有MMU的处理器,MMUEXT的值为-nommu,使用头文件head-nommu.S;对于有MMU的处理器,MMUEXT的值为空,使用文件head.S。
编译内核时,将依次进入init-y、core-y、libs-y、drivers-y和net-y所列出的目录执行它们的Makefile,每个子目录都会生成一个built-in.o(libs-y所列目录下,有可能生成lib.a文件)。最后,head-y所表示的文件将和这些built-in.o一起被连接成内核印象文件vmlinux。

在配置内核的时候,生成配置文件.config。内核顶层Makefile使用如下语句间接包含.config文件,以后就根据.config中定义的各个变量决定编译哪些文件。之所以说“间接”包含,是因为包含的是include/config/auto.conf文件,而它只是将.config文件中的注释去掉,并根据顶层Makefile中的定义增加一些变量而已。
# Read in config
-include include/config/auto.conf

(2) 怎样编译这些文件。
  即编译选项、连接选项是什么。
 
 

 

  




 

(3)怎样连接这些文件,最重要的是它们的顺序如何?
前面分析有哪些文件要编进内核时,顶层的Makefile和arch/$(ARCH)/Makefile定义了6类目录(或文件):head-y、init-y、drivers-y、net-y、libs-y、core-y。它们的初始值如下(以ARM体系为例)。
arch/arm/Makefile中:
#Default value
head-y        := arch/arm/kernel/head$(MMUEXT).o arch/arm/kernel/init_task.o
……
core-y                += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
core-y                += $(MACHINE)
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2400/
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2412/
core-$(CONFIG_ARCH_S3C2410)    += arch/arm/mach-s3c2440/
……
libs-y                := arch/arm/lib/ $(libs-y)
顶层Makefile中:
init-y        := init/
drivers-y    := drivers/ sound/
net-y        := net/
libs-y        := lib/
core-y        := usr/
可见除了head-y之外,其余的都是目录名。在顶层Makefile中,这些目录名的后面直接加上built-in.o或lib.a,表示要连接进内核的文件,如下:
init-y        := $(patsubst %/, %/built-in.o, $(init-y))
core-y        := $(patsubst %/, %/built-in.o, $(core-y))
drivers-y    := $(patsubst %/, %/built-in.o, $(drivers-y))
net-y        := $(patsubst %/, %/built-in.o, $(net-y))
libs-y1        := $(patsubst %/, %/lib.a, $(libs-y))
libs-y2        := $(patsubst %/, %/built-in.o, $(libs-y))
libs-y        := $(libs-y1) $(libs-y2)
上面的patsubst是字符串处理函数,它的用法如下:
$(patsubst pattern, replacement, text)
表示查找“text”中符合格式“pattern”的字,用“replacement”替换它们。比如上面的“init-y”初值为“init/”,经过init-y        := $(patsubst %/, %/built-in.o, $(init-y))的变换后,“init-y”变为了“init/built-in.o”。
顶层Makefile中,再往下看。
vmlinux-init := $(head-y) $(init-y)
vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
vmlinux-all  := $(vmlinux-init) $(vmlinux-main)
vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds
vmlinux-all表示搜有构成内核映像文件vmlinux的目标文件,可知这些目标文件的顺序为:head-y、init-y、core-y、libs-y、drivers-y、net-y,即arch/arm/kernel/head.o、arch/arm/kernel/init_task.o、init/build-in.o、usr/built-in.o等。
vmlinux-lds  := arch/$(ARCH)/kernel/vmlinux.lds表示连接脚本为arch/$(ARCH)/kernel/vmlinux.lds,它由arch/arm/kernel/vmlinux.lds.S文件生成,规则在scripts/Makefile.build中,如下所示:
$(obj)/%.lds: $(src)/%.lds.S FORCE
    $(call if_changed_dep,cpp_lds_S)
现将生成的arch/arm/kernel/vmlinux.lds摘录如下:

使用make menuconfig ARCH=arm CROSS_COMPILE=arm-linux-,需要安装Ncurses libraries
以一个例子说明config条目格式,下面代码选自fs/Kconfig。
      config JFFS2_FS_XATTR
bool "JFFS2 XATTR support (EXPERIMENTAL)"
depends on JFFS2_FS && EXPERIMENTAL
default n
help
  Extended attributes are name:value pairs associated with inodes by
  the kernel or by users (see the attr(5) manual page, or visit
  <http://acl.bestbits.at/> for details).
 
  If unsure, say N.
 






1.引导阶段代码分析
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,存入r9
    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'

内核映像中,定义了若干个proc_info_list结构(它的结构原型在include /asm-arm/procinfo.h中定义),它表示支持的CPU。对于ARM架构的CPU,这些结构体的源码在arch/arm/mm/目录下,比如proc-arm920.S中的如下代码,它表示arm920CPU的proc_info_list结构。
    .section ".proc.info.init", #alloc, #execinstr

    .type    __arm920_proc_info,#object
__arm920_proc_info:
    .long    0x41009200
    .long    0xff00fff0
……
不同的proc_info_list结构被用来支持不同的CPU,它们都是定义在.section ".proc.info.init"。在连接内核时,这些结构被组织在一起,开始地址为__proc_info_begin,结束地址为__proc_info_end,在arch/arm/kernel/vmlinux.lds.S
        __proc_info_begin = .;
            *(.proc.info.init)
        __proc_info_end = .;
__lookup_processor_type函数就是根据前面读出的processor id(r9),从proc_info_list结构中找出匹配的,arch/arm/kernel/head-common.S
  /*
 * Read processor ID register (CP#15, CR0), and look up in the linker-built
 * supported processor list.  Note that we can't use the absolute addresses
 * for the __proc_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.
 *
 *    r9 = cpuid
 * Returns:
 *    r3, r4, r6 corrupted
 *    r5 = proc_info pointer in physical address space
 *    r9 = cpuid (preserved)
 */
__lookup_processor_type:
    adr    r3, 3f
    ldmda    r3, {r5 - r7}
    sub    r3, r3, r7            @ get offset between virt&phys
    add    r5, r5, r3            @ convert virt addresses to
    add    r6, r6, r3            @ physical address space
1:    ldmia    r5, {r3, r4}    @ value, mask
    and    r4, r4, r9            @ mask wanted bits
    teq    r3, r4
    beq    2f
    add    r5, r5, #PROC_INFO_SZ        @ sizeof(proc_info_list)
    cmp    r5, r6
    blo    1b
    mov    r5, #0                @ unknown processor
2:    mov    pc, lr
ENDPROC(__lookup_processor_type)
……
/*
 * Look in <asm/procinfo.h> and arch/arm/kernel/arch.[ch] for
 * more information about the __proc_info and __arch_info structures.
 */
    .long    __proc_info_begin
    .long    __proc_info_end
3:    .long    .
在arch/arm/mm/Makefile中,它表示需要配置CONFIG_CPU_ARM920T(配置菜单中,System Type->ARM system type)。
obj-$(CONFIG_CPU_ARM920T)    += proc-arm920.o
内核中对于每种支持的开发板都会使用宏MACHINE_START、MACHINE_END来定义一个machine_desc结构,它定义了开发板相关的一些属性及函数。

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
MACHINE_START和MACHINE_END在arch/arm/include/asm/mach/arch.h文件中定义,如下:
#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                \
};
所以上一段代码展开来就是:
static const struct machine_desc __mach_desc_S3C2440    \
__used                            \
 __attribute__((__section__(".arch.info.init"))) = {    \
    .nr        = MACH_TYPE_S3C2440,        \
    .name        = "SMDK2440",
……
};
其中的MACH_TYPE_S3C2440在arch/arm/tools/mach-types中定义,machine_desc的结果在arch/arm/include/asm/mach/arch.h中定义。所有的machine_desc结构都处于".arch.info.init"段中,在连接内核时,他们被组织在一起,开始地址为__arch_info_begin,结束地址为__arch_info_end。这可以从连接脚本文件/arch/arm/kernel/vmlinux.lds
__arch_info_begin = .;
            *(.arch.info.init)
        __arch_info_end = .;
__lookup_machine_type函数的代码如下:
3:    .long    .
    .long    __arch_info_begin
    .long    __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
 */
__lookup_machine_type:
    adr    r3, 3b
    ldmia    r3, {r4, r5, r6}
    sub    r3, r3, r4            @ get offset between virt&phys
    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
ENDPROC(__lookup_machine_type)
2.start_kernel函数部分代码分析
进入start_kernel函数(在init/main.c)之后,如果在串口上没有看到内核的启动信息,一般有两种原因:Bootloader传入的命令参数不对,或者是setup_arch函数(在arch/arm/kernel/setup.c中)针对开发板的设置不对。


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

    unwind_init();

    setup_processor();
    mdesc = setup_machine(machine_arch_type);
    machine_name = mdesc->name;

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

    if (__atags_pointer)
        tags = phys_to_virt(__atags_pointer);
    else 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);
        save_atags(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(boot_command_line, from, COMMAND_LINE_SIZE);
    boot_command_line[COMMAND_LINE_SIZE-1] = '\0';
    parse_cmdline(cmdline_p, from);
    paging_init(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
    early_trap_init();
}
 
(2)paging_init函数分析
paging_init -> devicemaps_init -> mdesc->map_io()
static void __init smdk2440_map_io(void)
{
    s3c24xx_init_io(smdk2440_iodesc, ARRAY_SIZE(smdk2440_iodesc));
    s3c24xx_init_clocks(16934400);
    s3c24xx_init_uarts(smdk2440_uartcfgs, ARRAY_SIZE(smdk2440_uartcfgs));
}
注意要修改s3c24xx_init_clocks,这个频率在YLP2440上是12MHz。应该改为12000000。

(3)console_init函数分析
void __init console_init(void)
{
    initcall_t *call;

    /* Setup the default TTY line discipline. */
    tty_ldisc_begin();

    /*
     * set up the console device so that later boot sequences can
     * inform about problems etc..
     */
    call = __con_initcall_start;
    while (call < __con_initcall_end) {
        (*call)();
        call++;
    }
}
它调用地址范围__con_initcall_start至__con_initcall_end 之间的定义的每个函数,这些函数使用con_initcall宏来指定。

*博客内容为网友个人发布,仅代表博主个人观点,如有侵权请联系工作人员删除。

参与讨论
登录后参与讨论
推荐文章
最近访客