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

  • 相关阅读:
    手机端适配rem代码片段
    location记录<18.7.21>
    本地储存(localStorage)记录
    为什么size_t重要?(Why size_t matters)(转)
    Mac OS X 完全卸载MySQL
    ECMAScript6兼容性
    mac截屏
    javascript中array的map方法的使用
    Jquery对话框基本配置
    angular中封装fancyBox(图片预览)
  • 原文地址:https://www.cnblogs.com/lihuidashen/p/4253752.html
Copyright © 2011-2022 走看看