zoukankan      html  css  js  c++  java
  • arm-linux内核编译过程小结

    记在前面的杂七杂八

    1. 内核的生成,实际上最终的目的是生成一个binary文件zImage,大小2-5MB的数量级。
    2. 用户可以从kernel.org得到的tar.gz格式的内核源代码,此代码解压后,就会生成初始状态的内核源代码树,这种状态称为内核的初始状态
    3. 通过make mrproper/make distclean等指令,可以使内核恢复到刚解压的状态。其中make mrproper只清除包括.config文件在内的,为内核编译及连接而生成的诸多配置文件。distclean对象执行mrproper命令,清除内核编译后生成的所有对象文件,备份文件等。
    4. 如果将初始化状态的内核直接编译,虽然能生成vmlinux,但大多数情况下会引起内核严重错误(kernel panic)。构建内核前,需要执行的最重要,最需谨慎处理的部分是内核配置(kernel configureation)过程。内核配置过程也是适当选择与自身相吻合的各种内核要素的过程。
    5. 内核的配置可以用xconfig,menuconfig,gconfig等,最终都是会执行一个二进制文件,如menuconfig最终执行的是mconf,这个程序在./script/kconfig/目录下。
    6. 在构建内核时,各个*.o的目录下都有一个.*.cmd,这个文件是记录这个.o最终执行的编译命令的,如vmlinux.cmd和.vmlinux.o.cmd。
    7. 一句make一般来说,默认的目标有两个,一个是vmlinux,一个是zImage
    8. 通过emulator启动goldfish的时候,实际上启动的是zImage,这货才2.5MB左右,启动命令如下:

      emulator -show-kernel -kernel /mnt/VMDisk1/kernel/goldfish/arch/arm/boot/zImage -avd test -qemu -s
      • 1
      • 1
    9. 在图形化界面下,内核的配置也会有很多很多问题,一般每个系统均提供自定义配置文件,这些配置文件都是与具体芯片相关的(Soc, System on Chip),如下:

    tigger@ubuntu:/mnt/VMDisk1/kernel/goldfish$ cd arch/
    alpha/      blackfin/   frv/        ia64/       microblaze/ openrisc/   s390/       sparc/      unicore32/  
    arm/        c6x/        h8300/      m32r/       mips/       parisc/     score/      tile/       x86/        
    avr32/      cris/       hexagon/    m68k/       mn10300/    powerpc/    sh/         um/         xtensa/ 
    
    tigger@ubuntu:/mnt/VMDisk1/kernel/goldfish/arch/arm/configs$ ll
    total 632
    drwxr-xr-x  2 tigger tigger  4096 2014-10-14 20:09 ./
    drwxr-xr-x 88 tigger tigger  4096 2014-11-01 02:28 ../
    -rw-r--r--  1 tigger tigger  1998 2014-10-11 02:28 acs5k_defconfig
    -rw-r--r--  1 tigger tigger  2011 2014-10-11 02:28 acs5k_tiny_defconfig
    -rw-r--r--  1 tigger tigger  2509 2014-10-14 20:09 afeb9260_defconfig
    -rw-r--r--  1 tigger tigger  2241 2014-10-11 02:28 ag5evm_defconfig
    -rw-r--r--  1 tigger tigger  2617 2014-10-11 02:28 am200epdkit_defconfig
    ......
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15

    内核的构建

    • 如想使用goldfish的配置,可以:
        //根据goldfish_armv7_defconfig生成.config文件
        make goldfish_armv7_defconfig
        //调整一些具体细节项
        make menuconfig
        //然后就可以编译了
        make
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • .config文件是构建内核所需的内核配置目录,它是在CONFIG_XXX变量中用y,n,m三个状态进行配置的目录,这种形态的内核配置系统叫做kconfig。根据kconfig提供的三个状态(y,n,m)决定是否构建内核相应的模块(Kconfig系统中y,n,m只是bool类型的配置选项,实际上.config中可能有hex/int/bool/tristate/string这多种类型的选项)。

      • 状态为y时:相应的二进制文件,与vmlinux链接。
      • 状态为m时:不会和vmlinux链接,但作为模块执行编译。
      • 状态为n时:不编译。
    • mconf通过.config配置文件,生成autoconf.h头文件

    tigger@ubuntu:/mnt/VMDisk1/kernel/goldfish$ find ./ -name autoconf.h
    ./include/linux/autoconf.h
    ./include/generated/autoconf.h
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3

    这两个文件是一样的,也不知道谁复制的谁,大体看一下文件内容:

    #define CONFIG_RING_BUFFER 1
    #define CONFIG_NF_CONNTRACK_H323 1
    #define CONFIG_KERNEL_GZIP 1
    #define CONFIG_INPUT_KEYBOARD 1
    #define CONFIG_IP_NF_TARGET_REDIRECT 1
    #define CONFIG_CRC32 1
    #define CONFIG_NF_NAT_PROTO_SCTP 1
    #define CONFIG_HAVE_AOUT 1
    #define CONFIG_VFP 1
    #define CONFIG_AEABI 1
    #define CONFIG_FB_TILEBLITTING 1
    #define CONFIG_HIGH_RES_TIMERS 1
    #define CONFIG_BLK_DEV_DM 1
    #define CONFIG_VLAN_8021Q 1
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14

    可以发现,在.config中定义的的宏,在预处理阶段被处理成了各种#define语句。

    • 利用kconfig完成内核配置,准备好.config文件后,即可构建内核。构建内核是指,编译内核,链接各个二进制文件,最终生成一个二进制文件zImage的一系列过程。

    • 从linux2.6开始,内核采用kbuild系统来进行编译了,kbuild是指上是一堆脚本的组合。

    linux内核Makefile分类: 
    * Kernel Makefile: 位于内核源代码的顶层目录,也叫Top Makefile。主要用于指定编译内核的目标文件(vmlinux)和模块。在编译内核或模块时,这个文件会被首先读取,并根据内容设置环境变量。 
    * kbuild Makefile: kbuild系统使用kbuild Makefile来编译内核或模块,Kbuild Makefile指定哪些编译近内核,哪些编译为模块。 
    * arch Makefile: 位于./arch/$(ARCH)/Makefile,是系统对应平台的Makefile。top makefile会包含这个文件来指定平台相关信息,只有平台开发人员需要关心这个文件。


    vmlinux的生成

    编译后,vmlinux是在内核目录树的根目录下生成的一个ELF文件,这里以goldfish下执行make为例,查看vmlinux的生成。 当执行make命令的时候,会先扫描内核的根目录的Makefile:

    ##(后续只列举关键内容),./Makefile
    ##.PHONY: $(PHONY)是在Makefile最后定义的, .PHONY是将一个目标声明为伪目标,
    ##这样make在执行规则时不会试图去查找隐含规则来创建他(简单理解不会把_all当
    ##成一个文件,不会存在如果当前目录存在_all这个文件,而文件不更新,系统不能编译的问题)
    ##所有这样的文件都加到了.PHONY里面。
    PHONY := _all
    ##_all是编译的默认目标,就是make指令的默认目标
    _all:
    ifeq ($(KBUILD_EXTMOD),)
    ##如果没指定编译模块
    _all: all
    else
    ##如果指定是编译模块
    _all: modules
    endif
    #如果没指定编译模块,则这里最终编译的就是vmlinux
    #(注:在体系结构相关的makefile中一般还有一个all,即zImage)
    all: vmlinux
    vmlinux: $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o $(kallsyms.o) FORCE
        ##vmlinux-modpost不存在,这里好像是忽略了??
        $(call vmlinux-modpost)
        ##如果if_changed_rule成立则执行rule_vmlinux__
        $(call if_changed_rule,vmlinux__)
        ##删除.old_version
        $(Q)rm -f .old_version
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25

    后续会分析其中的各个目标

    目标文件 vmlinux.o

    vmlinux.o是没有去除符号表的可执行文件,最终生成的vmlinux为真正的内核镜像

    -rwxr-xr-x   1 tigger tigger  61M 2014-12-22 01:21 vmlinux*
    -rw-r--r--   1 tigger tigger 109M 2014-12-22 01:21 vmlinux.o
    • 1
    • 2
    • 1
    • 2

    目标文件 vmlinux.lds

    vmlinux.lds是一个链接脚本,是给ld链接器使用的。一般来说,普通程序是不需要指定linker script的,也不需要关心各个section的具体位置。当程序执行时,kernel中的ELF Loader会根据ELF文件头解析可执行文件的各个section,并把他们映射到虚拟地址空间。然而,内核启动时,必须首先确定各个section的具体位置,这就是vmlinux.lds的作用。这个文件必然是体系结构相关的,在arm中有两个连接脚本分别位于: 
    ./arch/arm/kernel/vmlinux.lds(这个是给vmlinux编译用的连接脚本,就是这里面的vmlinux.lds
    ./arch/arm/boot/compressed/vmlinux.lds(这个是给zImage编译时候用的连接脚本)

    目标文件 kallsyms.o

    在2.6内核中,为了更好的调试内核,引入了kallsyms机制。kallsyms把内核中用到的所有函数地址和名称链接到内核文件,当内核启动后,同时加载到内存中。当发生oops时候,内核就会 
    解析eip位于哪个函数中,然后打印出backtrace信息。内核编译的最后,make会执行:nm -n vmlinux|scripts/kallsyms,其中: 
    1. nm -n vmlinux负责生成所有的内核符号并按地址排序 
    2. scripts/kallsyms负责处理这个列表,并生成需要的链接文件tmp_kallsyms%.s 
    也就是说kallsyms实际上是内核编译完了之后,vmlinux中通过nm命令生成的,所以所有符号地址都包括了,实际上是和System.map是一样的。 
    而且kallsyms中所有函数的地址,是放在一个全局数组kallsyms_addresses[]中的,如下:

    kallsyms_addresses:
            PTR     _text + 0x180
            PTR     _text + 0x180
            PTR     _text + 0x180
            PTR     _text + 0x194
            PTR     _text + 0x360
            PTR     _text + 0x374
            PTR     _text + 0x484
            PTR     _text + 0x594
            PTR     _text + 0x5f4
            PTR     _text + 0x5f8
            PTR     _text + 0x60c
            PTR     _text + 0x624
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    kallsyms_addresses中的每一个表项,都在重定位表中有记录,如果内核发生了重定位,那么kallsyms中的内容也会跟着修改,所以cat /proc/kallsyms的时候总是看到的是真正的函数地址。
    

    kallsyms的整个符号表,最终都会放在kallsyms.o文件中。

    #kallsyms也是最终依赖一个文件,这个是.tmp_kallsymsX.o
    kallsyms.o := .tmp_kallsyms$(last_kallsyms).o          
    • 1
    • 2
    • 1
    • 2

    目标 vmlinux-init

    #./Makefile
    vmlinux-init := $(head-y) $(init-y)
    • 1
    • 2
    • 1
    • 2
    • head-y
    #./Makefile
    #head-y定义在具体体系结构的makefile中,这句include会载入head-y的定义
    include $(srctree)/arch/$(SRCARCH)/Makefile
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    #./arch/arm/Makefile
    #这里的MMUEXT应该是是否开启mmu的意思,如果不开启就是head-nommu.o,开启就是head.o
    head-y := arch/arm/kernel/head$(MMUEXT).o 
            arch/arm/kernel/init_task.o
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4
    • init-y
    #./Makefile
    #一开始init-y是个目录
    init-y  := init/
    #后续把init-y变量中所有符合%/的替换为%/built-in.o,这句之后init-y 被赋值为 init/built-in.o
    init-y          := $(patsubst %/, %/built-in.o, $(init-y))
    • 1
    • 2
    • 3
    • 4
    • 5
    • 1
    • 2
    • 3
    • 4
    • 5

    所以最终vmlinux-init 实际上是: 
    1. arch/arm/kernel/head.o(这是Image/vmlinux的入口代码)。 
    2. arch/arm/kernel/init_task.o 
    3. init/built-in.o 
    三者链接而来的。

    目标 vmlinux-main

    vmlinux-main := $(core-y) $(libs-y) $(drivers-y) $(net-y)
    • 1
    • 1
    • core-y
    #./Makefile
    core-y := usr/
    ifeq ($(KBUILD_EXTMOD),)
    core-y  += kernel/ mm/ fs/ ipc/ security/ crypto/ block/
    #如果是编译内核的话,core-y最终包含: usr/ kernel/ mm/ fs/ ipc/ security/ crypto/ block/
    #目录下的built-in.o。编译模块的话,就只包含usr/build-in.o。
    core-y := $(patsubst %/, %/built-in.o, $(core-y))
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    #./arch/arm/Makefile
    #除此之外,core-y还包含体系结构相关的:arch/arm/kernel、mm、common
    #以及具体芯片相关的:mach-xxx,plat-xxx目录下built-in.o文件(如mach-goldfish)。
    #(见前面的include $(srctree)/arch/$(SRCARCH)/Makefile),几乎涵盖了所有built-in.o。
    machdirs := $(patsubst %,arch/arm/mach-%/,$(machine-y))
    platdirs := $(patsubst %,arch/arm/plat-%/,$(plat-y))
    core-y += arch/arm/kernel/ arch/arm/mm/ arch/arm/common/
    core-y += arch/arm/net/
    core-y += $(machdirs) $(platdirs)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • libs-y
    #libs-y也是包含具体体系结构相关的库,并且包括lib.a和built-in.o两个文件
    #./Makefile
    libs-y  := lib/
    #把$(libs-y)中所有的%/替换为%/lib.a -> libs-y1
    libs-y1 := $(patsubst %/, %/lib.a, $(libs-y))
    #把$(libs-y)中所有的%/替换为%/built-in.o -> libs-y2
    libs-y2 := $(patsubst %/, %/built-in.o, $(libs-y))
    ##libs-y等于二者的合并
    libs-y  := $(libs-y1) $(libs-y2)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    #./arch/arm/Makefile
    #在libs-y的目录基础上加上arch/arm/lib/这个目录,这是在libs-y1,libs-y2合并前加入的
    libs-y := arch/arm/lib/ $(libs-y)
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    • drivers-y
    #./Makefile
    drivers-y  := drivers/ sound/ firmware/
    #drivers, sound, firmware目录的所有built-in.o文件
    drivers-y  := $(patsubst %/, %/built-in.o, $(drivers-y))
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4
    #./arch/arm/Makefile
    #根据配置文件决定是否加入体系结构相关的驱动
    drivers-$(CONFIG_OPROFILE) += arch/arm/oprofile/
    • 1
    • 2
    • 3
    • 1
    • 2
    • 3
    • net-y
    #./Makefile
    net-y := net/
    #net目录的built-in.o
    net-y := $(patsubst %/, %/built-in.o, $(net-y))
    • 1
    • 2
    • 3
    • 4
    • 1
    • 2
    • 3
    • 4

    总结一下,vmlinux-main是由以下四个目标组成的:

    • core-y: 
      1. 包含体系结构无关的usr/ kernel/ mm/ fs/ ipc/ security/ crypto/ block/ 目录下的built-in.o文件(如果是编译module,则只包含usr/built-in.o)。
      2. 包含体系结构相关的文件,在arm下为arch/arm/kernel/ arch/arm/mm/ arch/arm/common/ arch/arm/net/下的built-in.o。
      3. 包含具体芯片相关的文件,如arch/arm/plat-%/ arch/arm/mach-%/下的built-in.o。
    • libs-y: 包含arch/arm/lib/和 lib/目录下的built-in.o和lib.a文件。
    • drivers-y: 包含drivers/ sound/ firmware/以及arch/arm/oprofile/(可选)目录下的built-in.o文件。
    • net-y: 包含net/built-in.o文件。

    vmlinux的生成过程规则: rule_vmlinux__

    define rule_vmlinux__
        :
        #makefile -n的时候只是打印命令不会执行,这个+号表示始终执行
        #这货生成.version文件旧的.version存到.old_version,内容为"1"
        $(if $(CONFIG_KALLSYMS),,+$(call cmd,vmlinux_version))
        #调用cmd_vmlinux__命令,这个命令实际上就是将vmlinux的一堆依赖目标,连接为vmlinux
        $(call cmd,vmlinux__)
        #如果当前$@为DIR/vmlinux,则这个命令被存储到DIR/.vmlinux.cmd中
        #@D为$@中的目录(DIR),@F为$@中的文件(vmlinux)
        $(Q)echo 'cmd_$@ := $(cmd_vmlinux__)' > $(@D)/.$(@F).cmd
    
        #quiet默认为空(有可能为silent_),为空时调用cmd_sysmap
        #cmd_sysmap = $(CONFIG_SHELL) $(srctree)/scripts/mksysmap
        #这里调用mksysmap来生成System.map,mksysmap就是个脚本,其内容为:
        #$NM -n $1 | grep -v '( [aNUw] )|(__crc_)|( $[adt])' > $2
        $(Q)$(if $($(quiet)cmd_sysmap),                                      
            ##显示命令
          echo '  $($(quiet)cmd_sysmap)  System.map' &&)                     
        $(cmd_sysmap) $@ System.map;                                         
        if [ $$? -ne 0 ]; then                                               
            rm -f $@;                                                    
            /bin/false;                                                  
        fi;
        #应该是再弄一遍符号表出来,比较符号是否正确
        $(verify_kallsyms)
    endef
    
    define verify_kallsyms
        $(Q)$(if $($(quiet)cmd_sysmap),                                      
          echo '  $($(quiet)cmd_sysmap)  .tmp_System.map' &&)                
          $(cmd_sysmap) .tmp_vmlinux$(last_kallsyms) .tmp_System.map
        $(Q)cmp -s System.map .tmp_System.map ||                             
            (echo Inconsistent kallsyms data;                            
             echo This is a bug - please report about it;                
             echo Try "make KALLSYMS_EXTRA_PASS=1" as a workaround;      
             rm .tmp_kallsyms* ; /bin/false )
    endef
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    rule_vmlinux__ -> cmd_vmlinux__

          #?=是 如果没有被复制,则等于,这里是调用LD连接vmlinux所需的各种文件
          #LDFLAGS,LDFLAGS_vmlinux这些变量会作为LD的链接参数
          #-o $@指定最终生成的文件为vmlinux
          cmd_vmlinux__ ?= $(LD) $(LDFLAGS) $(LDFLAGS_vmlinux) -o $@ 
          # -T指定链接脚本文件为vmlinux-lds 后面跟着的$(vmlinux-init)为一堆目标文件
          -T $(vmlinux-lds) $(vmlinux-init)                          
          ##如果ld载入了一个库,发现该库中,有UNDF,未被定义的变量,有了这个参数的指示后,就会在这一堆.a和.o文件里面反复搜索,直至找到为止,否则,如果在已经加载的库中,找不到,就会报错????
          --start-group $(vmlinux-main) --end-group                  
          ##从$^中去除$(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE中的字符,并返回结果,这里的结果就是.tmp_kallsyms2.o(没用kallsyms.o)
          $(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10

    rule_vmlinux生成的vmlinux.cmd:

          #在mt6582下,vmlinux.cmd如下:
          cmd_vmlinux := 
          arm-linux-androideabi-ld.bfd  #$(LD)
          -EL  -p --no-undefined -X --emit-relocs --build-id  #$LDFLAGS) $(LDFLAGS_vmlinux)
          -o vmlinux  #$-o $@
          -T arch/arm/kernel/vmlinux.lds  #-T $(vmlinux-lds)
          arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o  #$(vmlinux-init)
          --start-group  
          usr/built-in.o  arch/arm/vfp/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/net/built-in.o  mediatek/platform/mt6582/kernel/core/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  mediatek/kernel/built-in.o  mediatek/custom/out/kernel/built-in.o  mediatek/platform/mt6582/kernel/drivers/built-in.o  aliyun/security/built-in.o  net/built-in.o  #$(vmlinux-main)
          --end-group 
          .tmp_kallsyms2.o #$(filter-out $(vmlinux-lds) $(vmlinux-init) $(vmlinux-main) vmlinux.o FORCE ,$^)
    
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    也就是说vmlinux实际上是由vmlinux.lds 连接vmlinux-init, vmlinux-main和.tmp_kallsyms2.o而成的,与vmlinx.o没有链接的关系!!!

    vmlinux.o的生成

    #vmlinux.o只依赖于$(modpost-init) $(vmlinux-main)两个目标文件,
    ##其最终调用的是rule_vmlinux-modpost
    vmlinux.o: $(modpost-init) $(vmlinux-main) FORCE
        $(call if_changed_rule,vmlinux-modpost)
    
    define rule_vmlinux-modpost
        :
        #调用cmd_vmlinux-modpost,这个函数是用来链接生成vmlinux.o的
        +$(call cmd,vmlinux-modpost)
        #实际上调用的是make -f $(srctree)/scripts/Makefile.modpost vmlinux.o
        $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@
        #dot-target是在$@的前面加个.,这里就是.vmlinux.o.cmd,这里是
        #将命令存储到.vmlinux.o.cmd
        $(Q)echo 'cmd_$@ := $(cmd_vmlinux-modpost)' > $(dot-target).cmd
    endif
         #cmd_vmlinux-modpost负责生成vmlinux.o,其规则和vmlinux的规则差不多
         #其差别主要在于,vmlinux.o没有指定--no-undefined编译选项
         #没有指定链接脚本,没有指定符号表
         cmd_vmlinux-modpost = $(LD) $(LDFLAGS) -r -o $@                          
         $(vmlinux-init) --start-group $(vmlinux-main) --end-group             
         $(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21

    vmlinux.o的生成命令(.vmlinux.o.cmd)

            cmd_vmlinux.o := 
            arm-linux-androideabi-ld.bfd  #$(LD)
            -EL   #$(LDFLAGS)
            -r -o vmlinux.o # -r -o $@ ,-r指定了可重定位的输出文件
            arch/arm/kernel/head.o arch/arm/kernel/init_task.o  init/built-in.o  #$(vmlinux-init)
            --start-group  
            usr/built-in.o  arch/arm/vfp/built-in.o  arch/arm/kernel/built-in.o  arch/arm/mm/built-in.o  arch/arm/common/built-in.o  arch/arm/net/built-in.o  mediatek/platform/mt6582/kernel/core/built-in.o  kernel/built-in.o  mm/built-in.o  fs/built-in.o  ipc/built-in.o  security/built-in.o  crypto/built-in.o  block/built-in.o  arch/arm/lib/lib.a  lib/lib.a  arch/arm/lib/built-in.o  lib/built-in.o  drivers/built-in.o  sound/built-in.o  firmware/built-in.o  mediatek/kernel/built-in.o  mediatek/custom/out/kernel/built-in.o  mediatek/platform/mt6582/kernel/drivers/built-in.o  aliyun/security/built-in.o  net/built-in.o #$(vmlinux-main)
            --end-group
            #$(filter-out $(vmlinux-init) $(vmlinux-main) FORCE ,$^)为空
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9

    make -f $(srctree)/scripts/Makefile.modpost vmlinux.o

    rule_vmlinux-modpost生成vmlinux.o后会调用

    $(Q)$(MAKE) -f $(srctree)/scripts/Makefile.modpost $@,解析后就是
    • 1
    • 1

    解析后就是:

    make -f $(srctree)/scripts/Makefile.modpost vmlinux.o
    • 1
    • 1
    #./$(srctree)/scripts/Makefile.modpost
    modpost = scripts/mod/modpost                   
     $(if $(CONFIG_MODVERSIONS),-m)                  
     $(if $(CONFIG_MODULE_SRCVERSION_ALL),-a,)       
     $(if $(KBUILD_EXTMOD),-i,-o) $(kernelsymfile)   
     $(if $(KBUILD_EXTMOD),-I $(modulesymfile))      
     $(if $(KBUILD_EXTRA_SYMBOLS), $(patsubst %, -e %,$(KBUILD_EXTRA_SYMBOLS))) 
     $(if $(KBUILD_EXTMOD),-o $(modulesymfile))      
     $(if $(CONFIG_DEBUG_SECTION_MISMATCH),,-S)      
     $(if $(KBUILD_EXTMOD)$(KBUILD_MODPOST_WARN),-w) 
     $(if $(cross_build),-c)
    #根据make -f的指定,生成vmlinux.o目标
    vmlinux.o: FORCE
        $(call cmd,kernel-mod)
    #最终调用的命令如:scripts/mod/modpost -o $(srctree)/Module.symvers -S vmlinux.o
    #实际上这一句makefile是用来生成Module.symvers文件的,这个文件是对内核函数做crc签名校验的
    cmd_kernel-mod = $(modpost) $@
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17

    Module.symvers文件只有在开启CONFIG_MODVERSIONS才生效的,否则里面的crc全是0,我这里没有开,内容如下:

    <CRC>           <Symbol>                <module> <type>
    0x00000000      cfg80211_send_rx_assoc  vmlinux EXPORT_SYMBOL
    0x00000000      generic_file_splice_write       vmlinux EXPORT_SYMBOL
    0x00000000      set_anon_super  vmlinux EXPORT_SYMBOL
    0x00000000      kmem_cache_alloc        vmlinux EXPORT_SYMBOL
    0x00000000      replace_page_cache_page vmlinux EXPORT_SYMBOL_GPL
    0x00000000      __cond_resched_softirq  vmlinux EXPORT_SYMBOL
    0x00000000      mt_fh_popod_restore     vmlinux EXPORT_SYMBOL
    0x00000000      i2c_put_adapter vmlinux EXPORT_SYMBOL
    0x00000000      rtc_class_open  vmlinux EXPORT_SYMBOL_GPL
    0x00000000      scsi_sense_key_string   vmlinux EXPORT_SYMBOL
    ......
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12

    总结一下:

    • vmlinux依赖于目标文件vmlinux.o,但是二者的生成没有直接关系,即vmlinux并不链接vmlinux.o文件
    • vmlinux和vmlinux.o的区别主要在于: 
      1. vmlinux指定了–no-undefined编译选项,不可以有未决符号,而vmlinux.o可以有。
      2. vmlinux指定了链接脚本vmlinux.lds,vmlinux.o没有连接脚本。
      3. vmlinux连接了符号表*kallsyms*.o文件,vmlinux.o没有连接符号表。
      4. vmlinux没指定-r选项,vmlinux.o指定了-r选项,-r是用来生成可重定位的目标文件用的,这个选项导致了vmlinux.o虽然链接的较vmlinux少,但实际体积比vmlinux要大(在我编译出的镜像中,vmlinux大小61MB,包含56个段; vmlinx.o大小109MB,包含12119个段,其中有6061个段为重定位段,另外有5981个段为各种ksy*段,如__ksymtab_strings。

    zImage的生成

    #./arch/arm/Makefile
    #在根目录makefile中的include $(srctree)/arch/$(SRCARCH)/Makefile会include当前文件
    #这是all出现的第二个目标,all的所有目标会合并,合并后就是vmlinux和zImage
    all:    $(KBUILD_IMAGE)
    KBUILD_IMAGE := zImage
    #zImage还依赖于vmlinux
    zImage Image xipImage bootpImage uImage: vmlinux
        #此命令解析后类似: 
        #make -f scripts/Makefile.build obj=arch/arm/boot MACHINE = XXX arch/arm/boot/zImage
        #调用makefile.build作为makefile的脚本,obj和MACHINE都为变量
        #要build的目标为arch/arm/boot/zImage,这个就是target
        $(Q)$(MAKE) $(build)=$(boot) MACHINE=$(MACHINE) $(boot)/$@
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    #./scripts/makefile.build
    #初始化各个变量为空
    obj-y :=
    obj-m :=
    lib-y :=
    lib-m :=
    ##包含配置文件,没有就算了(这里没有)
    -include include/config/auto.conf
    ##导入内部函数
    include scripts/Kbuild.include
    src := $(obj)
    ##这一句是在src目录下找Makefile文件,对于arch/arm/boot来说,最终找到的是
    ##arch/arm/boot/Makefile
    kbuild-dir := $(if $(filter /%,$(src)),$(src),$(srctree)/$(src))
    kbuild-file := $(if $(wildcard $(kbuild-dir)/Kbuild),$(kbuild-dir)/Kbuild,$(kbuild-dir)/Makefile)
    ##所以对于arch/arm/boot来说,最终这里include了./arch/arm/boot/Makefile
    ##最终要编译的目标arch/arm/boot/zImage就是在./arch/arm/boot/Makefile中定义的
    include $(kbuild-file)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    #./arch/arm/boot/Makefile
    ifneq ($(MACHINE),)
    #这里一般记录 zreladdr-y,params_phys-y,initrd_phys-y 等信息
    include $(srctree)/$(MACHINE)/Makefile.boot
    endif
    ZRELADDR    := $(zreladdr-y)
    PARAMS_PHYS := $(params_phys-y)
    INITRD_PHYS := $(initrd_phys-y)
    #这个文件是被include进来的,$(obj)这里就是arch/arm/boot
    #这个$(obj)/zImage展开的话就是arch/arm/boot/zImage
    $(obj)/zImage:	$(obj)/compressed/vmlinux FORCE
        ##这里是向if_changed传入了objcopy,if_changed会给其加上cmd_开头的头,如果这里得到执行
        ##最终执行的会是cmd_objcopy
        $(call if_changed,objcopy)
        @echo '  Kernel: $@ is ready'
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    #./scripts/makefile.lib
    cmd_objcopy = $(OBJCOPY) $(OBJCOPYFLAGS) $(OBJCOPYFLAGS_$(@F)) $< $@
    • 1
    • 2
    • 1
    • 2

    所以zImage实际上是依赖于$(obj)/compressed/vmlinux的,是后者执行了一个objcopy -binary过来的($(obj)/compressed/vmlinux并不是vmlinux,前者是内核Image压缩为piggy.gz后再次生成的一个压缩后的elf镜像,而后者是内核的镜像,$(obj)/compressed/vmlinux大小约为2.XMB,后面也称其为vmlinux(小),而另一个称为vmlinux(大))其命令行如下:

    cmd_arch/arm/boot/zImage := arm-linux-androideabi-objcopy -O binary -R .comment -S  arch/arm/boot/compressed/vmlinux arch/arm/boot/zImage
    • 1
    • 1

    目标 $(obj)/compressed/vmlinux

    #./arch/arm/boot/Makefile
    #vmlinux(小)依赖于Image
    $(obj)/compressed/vmlinux: $(obj)/Image FORCE
        #这句和前面类似,还是make -f makefile.build 
        #最终include arch/arm/boot/compressed/makefile
        #目标$(obj)/compressed/vmlinux定义在arch/arm/boot/compressed/makefile中
        $(Q)$(MAKE) $(build)=$(obj)/compressed $@ 
    
    #Image又依赖于vmlinux(大)
    $(obj)/Image: vmlinux FORCE
        #Image同样是vmlinux(大)通过objcopy -binary过来的,.Image.cmd如下:
        #cmd_arch/arm/boot/Image := arm-linux-androideabi-objcopy 
        #-O binary -R .comment -S  vmlinux arch/arm/boot/Image
        $(call if_changed,objcopy)
        @echo '  Kernel: $@ is ready'
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    #./arch/arm/boot/compress/Makefile
    HEAD    = head.o
    OBJS        =
    #这个OBJS根据不同CONFIG还可能有多个.o
    OBJS        += string.o 
    lib1funcs = $(obj)/lib1funcs.o
    ashldi3 = $(obj)/ashldi3.o
    
    #这里是piggy.gzip,这个文件实际上就是压缩了Image,再加上piggy.S编译来的
    #这里的suffix_y最终执行的是cmd_gzip(./script/makefile.lib)
    $(obj)/piggy.$(suffix_y): $(obj)/../Image FORCE
        $(call if_changed,$(suffix_y))
    
    #piggy.gzip.o依赖于piggy.gzip,这只是依赖关系,默认没有编译脚本,所以
    #默认编译的是piggy.gzip.S
    $(obj)/piggy.$(suffix_y).o:  $(obj)/piggy.$(suffix_y) FORCE
    
    #vmlinux(小)也是有个自己的链接脚本vmlinux.lds的,这个和vmlinux(大)的是不同的两个脚本。
    $(obj)/vmlinux: $(obj)/vmlinux.lds $(obj)/$(HEAD) $(obj)/piggy.$(suffix_y).o 
            $(addprefix $(obj)/, $(OBJS)) $(lib1funcs) $(ashldi3) FORCE
        @$(check_for_multiple_zreladdr)
        $(call if_changed,ld)
        @$(check_for_bad_syms)
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23

    文件piggy.gzip.s

            #此文件直接将piggy.zip包含进来了
            .section .piggydata,#alloc
            .globl  input_data
    input_data:
            .incbin "arch/arm/boot/compressed/piggy.gzip"
            .globl  input_data_end
    input_data_end:
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7

    zImage的生成流程图

    从vmlinux到zImage的步骤如图: 

    *.cmd

    • 在构建内核时,各个*.o的目录下都有一个.*.cmd,这个文件是记录这个.o最终执行的编译命令的,如vmlinux.cmd和.vmlinux.o.cmd,从vmlinux到zImage的步骤总结如下:
    //1.vmlinux(61MB)->Image(5.1M) 
    //./arch/arm/boot/.Image.cmd
    cmd_arch/arm/boot/Image := arm-eabi-objcopy 
         -O binary -R .comment -S  vmlinux arch/arm/boot/Image
    //2. Image(5.1MB)->piggy.gzip(1.7MB)(gzip -9 压缩)
    //./arch/arm/boot/compressed/.piggy.gz.cmd
    cmd_arch/arm/boot/compressed/piggy.gz := gzip -f -9 
        < arch/arm/boot/compressed/../Image > 
        arch/arm/boot/compressed/piggy.gz
    //3. piggy.gzip(1.7MB)->piggy.o(1.7MB)
    //实际上是将piggy.gz通过piggy.S编译进piggy.o文件中,piggy.S文件仅有6行
    //./arch/arm/boot/compressed/.piggy.o.cmd
    
    //4. head.o 、piggy.o 、misc.o,piggy.o -> vmlinux(2.6MB) 
    //这个vmlinux是在./arch/arm/boot/compressed目录下的,且经过压缩且含有自解压代码的内核。
    //./arch/arm/boot/compressed/.vmlinux.cmd
    cmd_arch/arm/boot/compressed/vmlinux := arm-eabi-ld -EL
        --defsym _kernel_bss_size=1419856 
        --defsym zreladdr=0x00008000 
        -p --no-undefined -X -T 
        arch/arm/boot/compressed/vmlinux.lds 
        arch/arm/boot/compressed/head.o 
        arch/arm/boot/compressed/piggy.gzip.o 
        arch/arm/boot/compressed/misc.o 
        arch/arm/boot/compressed/decompress.o 
        arch/arm/boot/compressed/string.o 
        arch/arm/boot/compressed/lib1funcs.o 
        arch/arm/boot/compressed/ashldi3.o  
        -o arch/arm/boot/compressed/vmlinux
    
    //5. vmlinux(2.6MB)->zImage(2.5MB)
    //将arch/arm/boot/compressed/vmlinux去除调试信息、注释、符号表等内容,生成arch/arm/boot/zImage,这是一个可以使用的linux内核映像文件了,一个linux内核镜像,最终约2.5MB大小
    //./arch/arm/boot/.zImage.cmd
    cmd_arch/arm/boot/zImage := arm-eabi-objcopy 
        -O binary -R .comment 
        -S arch/arm/boot/compressed/vmlinux 
        arch/arm/boot/zImage
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37

    vmlinux.ld

     //vmlinux.lds.s用于对ld的输出进行排版
     //指定一个特定的输出机器架构,可以使用arm-XXX-objdump -f来查看一个文件的机器架构
    OUTPUT_ARCH(arm)
    //ENTRY用来设置程序的入口点
    ENTRY(_start)
    SECTIONS
    {
    //这个.代表默认的地址计数器,如果一个段没指定地址,就用当前地址计数
    //器的默认地址,这个地址在每次应用于一个段之后,会自加。
      . = 0;
      _text = .;
      .text : {
      //_start是程序入口地址,被放在了0x00000000的位置,从此可以反向推出,此芯片的上电启动地址为0x0000000,这个vmlinux.lds用于
      //生成那个2.6MB的vmlinux,所以这个0x00000000也是最终镜像zImage的启动地址。
        _start = .;
        *(.start)
        *(.text)
        *(.text.*)
        *(.fixup)
        *(.gnu.warning)
        *(.glue_7t)
        *(.glue_7)
      }
      .rodata : {
        *(.rodata)
        *(.rodata.*)
      }
      .piggydata : {
        *(.piggydata)
      }
      . = ALIGN(4);
      _etext = .;
      .got.plt      : { *(.got.plt) }
      _got_start = .;
      .got          : { *(.got) }
      _got_end = .;
      .pad          : { BYTE(0); . = ALIGN(8); }
      _edata = .;
      . = ALIGN(8);
      __bss_start = .;
      .bss          : { *(.bss) }
      _end = .;
      . = ALIGN(8);     
      .stack        : { *(.stack) }
      .stab 0       : { *(.stab) }
      .stabstr 0        : { *(.stabstr) }
      .stab.excl 0      : { *(.stab.excl) }
      .stab.exclstr 0   : { *(.stab.exclstr) }
      .stab.index 0     : { *(.stab.index) }
      .stab.indexstr 0  : { *(.stab.indexstr) }
      .comment 0        : { *(.comment) }
    }
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52
    • 1
    • 2
    • 3
    • 4
    • 5
    • 6
    • 7
    • 8
    • 9
    • 10
    • 11
    • 12
    • 13
    • 14
    • 15
    • 16
    • 17
    • 18
    • 19
    • 20
    • 21
    • 22
    • 23
    • 24
    • 25
    • 26
    • 27
    • 28
    • 29
    • 30
    • 31
    • 32
    • 33
    • 34
    • 35
    • 36
    • 37
    • 38
    • 39
    • 40
    • 41
    • 42
    • 43
    • 44
    • 45
    • 46
    • 47
    • 48
    • 49
    • 50
    • 51
    • 52

    参考资料

    [1].http://lli_njupt.0fees.net/ar01s07.html?ckattempt=1 
    [2].http://cache.baiducontent.com/c?m=9d78d513d99d17b8589c837e7b01d6160e54f6743da791532c94d55f92144c413171e2cb72624d4391d27d1716df4e4b9bf62173471456b28cbc8d5dabba85592e9c60742e13dc0754910eaeb85b388465d54de9d848a7e1a461cfb9d2a48e090cd705523cd3abd50d5603cd1ba34862bdedd813544817ceb06472f82d3173c83447c218aab9657900f5b18d0111853dd71545ccf366ee2915c142f940597f1af75bb67c027a66f74853a11f615d85ec29a1702e5724c213ecfb9fe1b41fd09ab977c3a797b828e122a698bbae30036d&p=c46cce10ba904ead08e2977c0908cd&newp=8570c54ad5c145c30be296645b5f88231610db2151d4d31013&user=baidu&fm=sc&query=ld++%2D%2Dstart%2Dgroup%B5%C4%D7%F7%D3%C3&qid=89430f6200031c87&p1=7

  • 相关阅读:
    阿里云ECS linux通过rinetd 端口转发来访问内网服务
    阿里云ECS linux通过iptables 配置SNAT代理网关,实现局域网上网
    适用于CentOS6.x系统的15项优化脚本
    ELK学习笔记
    MAC OSX环境下cordova+Ionic的安装配置
    Windows下 VM12虚拟机安装OS X 10.11 和VM TOOLS
    cordova 下载更新
    android adb常用命令
    ionic实现双击返回键退出功能
    ionic ngCordova插件安装
  • 原文地址:https://www.cnblogs.com/liang123/p/6325170.html
Copyright © 2011-2022 走看看