zoukankan      html  css  js  c++  java
  • 24小时学通Linux内核之构建Linux内核

    24小时学通Linux内核之构建Linux内核

      今天是腊八节,说好的女票要给我做的腊八粥就这样泡汤了,好伤心,好心酸呀,看来代码写久了真的是惹人烦滴,所以告诫各位技术男敲醒警钟,不要想我看齐,不然就只能和代码为伴了的~~话说没了腊八粥但还是有代码,还有各位读者的支持呀,所以得继续写下去,静下心来,完成Linux内核的学习,坚持,加油~

      到目前为止,我们已经认识了Linux内核子系统,也探究了系统的初始化过程,并且深入探索了start_kernel()函数,同样,了解内核映像的创建也是非常重要的,接下来将讨论一下内核映像的编译和链接过程,那么这些当然需要工具链了,工具链包含编译程序、汇编程序、链接程序,是创建Linux内核映像的一组程序集合,下图说明了工具链的链式关系:

    ELF二进制目标文件

    可执行ELF目标文件包括:ELF头,程序头表(用于加载的节),第1节,第2节。。。。节头表(可选)

    ELF头文件

    typedef struct elf32_hdr{
      unsigned char e_ident[EI_NIDENT]; //标识该文件是否为ELF文件
      Elf32_Half    e_type;  //指定目标文件类型,例如可执行文件,重定位文件,共享的目标文件
      Elf32_Half    e_machine;   //被编译文件所在系统的体系结构
      Elf32_Word    e_version; //目标文件的版本
      Elf32_Addr    e_entry;  /* Entry point */  //程序的起始地址
      Elf32_Off     e_phoff;   //保存程序头表在文件中的偏移量
      Elf32_Off     e_shoff;   //保存节头表在文件中的偏移量
      Elf32_Word    e_flags;   //保存于特定与处理器的标志
      Elf32_Half    e_ehsize;  //字段保存ELF头的大小
      Elf32_Half    e_phentsize;  //保存程序头表中的每一项的大小
      Elf32_Half    e_phnum;  //程序头中表项的个数
      Elf32_Half    e_shentsize;  //节头表中每一项的大小
      Elf32_Half    e_shnum;   //保存节头中项的数量,表明该文件中有多少节
      Elf32_Half    e_shstrndx;  //保存节头中节字符串的索引
    } Elf32_Ehdr;

    节头表

    typedef struct elf32_shdr {
      Elf32_Word    sh_name;   //包含节名
      Elf32_Word    sh_type;   //包含节的内容
      Elf32_Word    sh_flags;  //各种属性的内容
      Elf32_Addr    sh_addr;   //节在内存映像中的地址 
      Elf32_Off     sh_offset; //保存ELF文件中这一节中初始字节的偏移量
      Elf32_Word    sh_size;   //包含节的大小
      Elf32_Word    sh_link;   //表链接的索引
      Elf32_Word    sh_info;   //包含附加信息
      Elf32_Word    sh_addralign;  //包含地址对其的约束
      Elf32_Word    sh_entsize;  //节中每项的大小
    } Elf32_Shdr;

    非可执行ELF文件节

    节点 说明
    .data 已初始化的数据
    bss 为初始化的数据
    .hash 符号散列表
    .init 初始化代码
    .symtab  符号表
    .text   可执行的指令
    .plt   过程链接表
    .rodata  只读数据
    dynamic 动态链接信息

    程序头表

    typedef struct elf64_phdr {
      Elf64_Word p_type;    //描述该段的类型
      Elf64_Word p_flags;   //以p_type而定
      Elf64_Off p_offset;   //<span style="font-family: Arial, Helvetica, sans-serif;">该段的开始相对于文件开始的偏移量</span>
      Elf64_Addr p_vaddr;   //段虚拟地址
      Elf64_Addr p_paddr;   //段的虚拟地址  
      Elf64_Xword p_filesz; //文件映像中该段的字节数
      Elf64_Xword p_memsz;  //内存映像中该段的字节数
      Elf64_Xword p_align;  //描述要对齐的段在内存中如何对齐,该值是2的整数次幂    
    } Elf64_Phdr; 

    通过这些信息,系统函数exec()和链接程序合作,为可执行程序在内存中创建进程映像,该过程如下:

    • 将可执行文件的段加入内存
    • 加载所有需要的共享库
    • 需要时重定向可执行文件及其共享对象
    • 将控制权交给程序

      

      那么内核是如何被编译成二进制文件的呢,又是如何在执行前装入内存。下面将开始介绍编译内核源代码。内存启动始于执行arch/x86/boot/目录中的实模式汇编代码。查看arch/x86/kernel/setup_32.c文件可以看出保护模式的内核怎样获取实模式内核收集的信息。第一条信息来自于init/main.c中的代码,深入挖掘init/calibrate.c可以对BogoMIPS校准理解得更清楚,而include/asm-your-arch/bugs.h则包含体系架构相关的检查。

      内核中的时间服务由驻留于arch/your-arch/kernel/中的体系架构相关的部分和实现于kernel/timer.c中的通用部分组成。从include/linux/time*.h头文件中可以获取相关的定义。

      jiffies定义于linux/jiffies.h文件中。HZ的值与处理器相关,可以从include/asm-your-arch/ param.h找到,内存管理源代码存放在顶层mm/目录中。

    Linux的官方源代码发布网址是www.kernel.org。其源代码目录结构示意图如下:

      利用内核配置工具自动生成.config的内核配置文件,这是编译的第一步,.config文件位于源代码目录下,其选项的位置根据它们在内核配置工具中的位置进行排序,我们来看看一个.config文件的节选:

     1 #
     2 # Automatically generated make config: don't edit
     3 #
     4 CONFIG_X86=y
     5 CONFIG_MMU=y
     6 CONFIG_UID16=y
     7 CONFIG_GENERIC_ISA_DMA=y    //这4行位于顶层菜单中
     8 
     9 #
    10  # Code maturity level options
    11  #
    12  CONFIG_EXPERIMENTAL=y
    13  CONFIG_CLEAN_COMPILE=
    14  CONFIG_STANDALONE=y
    15  CONFIG_BROKEN_ON_SMP=y  //这4行位于代码成熟度选项菜单中
    16 
    17  #
    18  # General setup
    19  #
    20  CONFIG_SWAP=y
    21  CONFIG_SYSVIPC=y
    22  #CONFIG_POSIX_MQUEUE is not set
    23  CONFIG_BSD_PROCESS_ACCT=y  //这4行位于通用设置选项菜单中

      

      最后来粗略的介绍一下Linux内核的Makefile文件,也只能简单的介绍一下啦,这个可是重难点,这里我稍微说一下,以后会具体去学习。Linux内核是一种单体内核,但是通过动态加载模块的方式,使它的开发非常灵活 方便。那么,它是如何编译内核的呢?我们可以通过分析它的Makefile入手。以下是 一个简单的hello内核模块的Makefile. 

    ifneq ($(KERNELRELEASE),)
    obj-m:=hello.o
    else
    KERNELDIR:=/lib/modules/$(shell uname -r)/build
    PWD:=$(shell pwd)
    default:
            $(MAKE) -C $(KERNELDIR)  M=$(PWD) modules
    clean:
            rm -rf *.o *.mod.c *.mod.o *.ko
    endif

      首先,由于make 后面没有目标,所以make会在Makefile中的第一个不是以.开头的目标作为默认的目标执行。于是default成为make的目标。make会执行 $(MAKE) -C $(KERNELDIR) M=$(PWD) modules shell是make内部的函数,make执行了两次。

    第一次执行时是读hello模块的源代码所在目录/home/s tudy/prog/mod/hello/下的Makefile。

    第二次执行时是执行/usr/src/linux/下的Makefile时. 

    这其中很复杂,我也不知道怎么讲了。关于make modules的更详细的过程可以在scripts/Makefile.modpost文件的注释 中找到。不过我找到了一个大牛写的跟我一下学Makefile的博客,我把博客地址附在下面,供大家参考一下:http://blog.csdn.net/haoel/article/details/2886/

      小结

      本章探究了目标文件的编译,链接过程,以及目标文件的结构,以便理解可执行代码的最终形式,构建Linux内核涵盖了内核编译所需要的工具,最后还简单的描述了Makefile,,这些都是难点,,得多加缩习啦,,尽管今天没吃到腊八粥,但是转转锅还是很给力的,吃到现在还不饿,是一个难忘的一天  ~~

      版权所有,转载请注明转载地址:http://www.cnblogs.com/lihuidashen/p/4253752.html

  • 相关阅读:
    topcoder srm 445 div1
    topcoder srm 440 div1
    topcoder srm 435 div1
    topcoder srm 430 div1
    topcoder srm 400 div1
    topcoder srm 380 div1
    topcoder srm 370 div1
    topcoder srm 425 div1
    WKWebView强大的新特性
    Runtime那些事
  • 原文地址:https://www.cnblogs.com/lihuidashen/p/4253752.html
Copyright © 2011-2022 走看看