精华内容
下载资源
问答
  • Linux内核在arm上的启动过程

    千次阅读 2021-04-24 22:32:16
    关注v-x-公-众-号:【嵌入式基地】 后-台-回-复:【电赛】 即可获资料 ... Linux内核加载过程 通常,Linux内核都是经过gzip加载过之后的映像文件。 ...编译完成的Linux内核存放在哪里? ./vmlinux el

    关注v-x-公-众-号:【嵌入式基地
    后-台-回-复:【电赛】 即可获资料
    回复【编程】即可获取
    包括有:C、C++、C#、JAVA、Python、JavaScript、PHP、数据库、微信小程序、人工智能、嵌入式、Linux、Unix、QT、物联网、算法导论、大数据等资料

    在这里插入图片描述
    原文链接 点击查看

    Linux内核加载过程

    通常,Linux内核都是经过gzip加载过之后的映像文件。

    • bootloader复制压缩内核到内存空间。
    • 内核自解压。
    • 运行内核。

    编译完成的Linux内核存放在哪里?

    • ./vmlinux elf格式未压缩内核。
    • arch/arm/boot/compressed/vmlinux 压缩以后的elf格式内核。
    • arch/arm/boot/zImage 压缩内核。

    压缩内核(zImage)的入口

    • /arch/arm/boot/compressed/vmlinux.lds 该文件为编译器指定link顺序。

    • ENTRY(_start) 压缩内核从.start段开始执行。

    • 在/arch/arm/boot/compressed/head.S中执行以下爱操作:

        (1)检测系统空间。
      
        (2)初始化C代码空间。
      
        (3)跳转到C代码decompress_kernel,
      
                 arch/arm/boot/compressed/misc.c中。
      

    解压之前的串口输出

    • include/asm-arm/arch-s3c2410/uncompress.h 中定义了puts作为串口输出函数。
    • 解压结束之后,程序跳转到r5:解压之后内核的起始地址。

    开始真正的Linux内核

    1、入口在arch/arm/kernel/head-armv.S

    2、查找处理器类型

    __lookup_processor_type

    __lookup_architecture_type

    3、初始化页表:__creat_page_tables

    4、初始化C代码空间

    5、跳转到C代码中,start_kernel

    ARM的MMU单元

    MMU:内存管理单元

    作用:

    • 虚拟地址到物理地址的映射
    • 存储器访问权限
    • 控制Cache

    通过MMU的访存

    • MMU会先查找TLB中的虚拟地址表
    • 如果TLB中没有虚拟地址的入口,硬件从主存储器中的转换表中获取转换与访问权限。

    ARM的MMU访存原理

    在这里插入图片描述

    ARM的MMU页表格式

    MMU支持基于节或者页的存储器访问。

    • 节:1MB的存储器块
    • 大页:64KB的存储器块
    • 小页:4KB的存储器块
    • 微页:1KB的存储器块

    页表的级别

    存在主存储器内的转换页表有两个级别:

    • 第一级表:存储节转换表与指向第二级表的指针
    • 第二级表:
      (1)存储大页和小页的转换表。
      (2)存储微页的转换表。

    一级页表的地址

    第一级表占用空间16KB,必须16KB对齐
    在这里插入图片描述

    第一级描述符

    一级表每个入口描述了它所关联的1MB虚拟地址是如何映射的。
    在这里插入图片描述

    节描述符

    • Bits[1:0] 描述符类型(10b 表示节描述符)
    • Bits[3:2] 高速缓存(cache)和缓冲位(buffer)
    • Bits[4] 由具体实现定义
    • Bits[8:5] 控制的节的16 种域之一
    • Bits[9] 现在没有使用,应该为零
    • Bits[11:10] 访问控制(AP)
    • Bits[19:12] 现在没有使用,应该为零
    • Bits[31:20] 节基址,形成物理地址的高12 位

    节的转换过程

    在这里插入图片描述

    临时内核页表的创建 __create_page_tables

    __create_page_tables:
    pgtbl r4 @ page table address 0x30008000-0x4000
    mov r0, r4 @r0=0x30004000
    mov r3, #0
    add r2, r0, #0x4000
    1: str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    str r3, [r0], #4
    teq r0, r2
    bne 1b
    

    把一级页表0x30004000-0xa0080000清空

    krnladr r2, r4 @ start of kernel
    

    r4=0xa0004000,r2 = 内核起始地址所在1MB对齐空间,0x30000000

    add r3, r8, r2 @ flags + kernel base
    

    r8 为从处理器信息中得到的MMU 页表标志,r8=0xc0e, r3=0x30000c0e

    str r3, [r4, r2, lsr #18]@ identity mapping
    

    地址:0x300068000, value:0x30000c0e

    add r0, r4, #(TEXTADDR & 0xff000000) >> 18 
    @ start of kernel
    bic r2, r3, #0x00f00000
    str r2, [r0] @ PAGE_OFFSET + 0MB
    add r0, r0, #(TEXTADDR & 0x00f00000) >> 18
    str r3, [r0], #4 @ KERNEL + 0MB
    

    映射表内容

    在这里插入图片描述

    映射结果

    在这里插入图片描述

    进入C代码

    init/main.c中的start_kernel函数,进入到了Linux内核代码中。

    • printk函数
    • 重新初始化页表
    • 初始化中断,trap_init
    • 设置系统定时器、控制台…
    • 创建内核进程init
    展开全文
  • 如此海量内存给内核带来了很多挑战,其中之一就是page struct存放在哪里。 page struct的三种存放方式 在内核中,我们将物理内存按照页大小进行管理。这样每个页就对应一个page struct作为这个页的管理数据结构。 ...

    目录

    page struct的三种存放方式

    1) FLATMEM

    2) SPARSEMEM

    3) SPARSEMEM_VMEMMAP


    随着硬件能力的提升,系统内存容量变得越来越大。尤其是在服务器上,过T级别的内存容量也已经不罕见了。

    如此海量内存给内核带来了很多挑战,其中之一就是page struct存放在哪里。

    page struct的三种存放方式

    在内核中,我们将物理内存按照页大小进行管理。这样每个页就对应一个page struct作为这个页的管理数据结构。

    随着内存容量的增加,相对应的page struct也就增加。而这部分内存和其他的内存略有不同,因为这部分内存不能给到页分配器。也就是必须在系统能够正常运行起来之前就分配好。

    在内核中我们可以看到,为了应对这样的变化进化出了几个不同的版本。有幸的是,这部分内容我们现在还能在代码中直接看到,因为这个实现是通过内核配置来区分的。我们通过查找_pfn_to_page的定义就能发现一下几种memory model:

    • CONFIG_FLATMEM

    • CONFIG_SPARSEMEM

    • CONFIGSPARSEMEMVMEMMAP

    接下来让小编给各位看官一一道来。

    1) FLATMEM

    在这种情况下,宏_pfn_to_page的定义是:

    #define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))

    而这个mem_map的定义是

    struct page *mem_map;

    所以在这种情况下,page struct就是一个大数组,所有的人都按照自己的物理地址有序得挨着。

    2) SPARSEMEM

    虽然第一种方式非常简单直观,但是有几个非常大的缺点:

    • 内存如果有空洞,那么中间可能会有巨大的page struct空间浪费

    • 所有的page struct内存都在一个NUMA节点上,会耗尽某一个节点内存,甚至是分配失败

    • 且会产生夸NUMA访问导致性能下降

    所以第二种方式就是将内存按照一定粒度,如128M,划分了section,每个section中有个成员指定了对应的page struct的存储空间。

    这样就解决了上述的几个问题:

    • 如果有空洞,那么对应的 page struct就不会占用空间

    • 每个section对应的page struct是属于本地NUMA的

    怎么样,是不是觉得很完美。这一部分具体的实现可以可以看函数sparse_init()函数。

    有了这个基础知识,我们再来看这种情况下_pfn_to_page的定义:

    #define __pfn_to_page(pfn)        \
    ({  unsigned long __pfn = (pfn);      \
      struct mem_section *__sec = __pfn_to_section(__pfn);  \
      __section_mem_map_addr(__sec) + __pfn;    \
    })

    就是先找到pfn对应的section,然后在section中保存的地址上翻译出对应pfn的page struct。

    既然讲到了这里,我们就要对sparsemem中重要的组成部分mem_section多说两句。

    先来一张mem_section的整体图解:

    这是一个 NRSECTIONROOTS x SECTIONSPERROOT的二维数组。其中每一个成员就代表了我们刚才提到的128M内存。

    当然最开始它不是这个样子的。

    其实最开始这个数组是一个静态数组。很明显这么做带来的问题是这个数组定义太大太小都不合适。所以后来引进了CONFIGSPARSEMEMEXTREME编译选项,当设置为y时,这个数组就变成了动态的

    如果上面这个算作是空间上的限制的话,那么接下来就是一个时间上的限制了。

    在系统初始化时,每个mem_section都要和相应的内存空间关联。在老版本上,这个步骤通过对整个数组接待完成。原来的版本上问题不大,因为整个数组的大小还没有很大。但随着内存容量的增加,这个数值就变得对系统有影响了。如果系统上确实有这么多内存,那么确实需要初始化也就忍了。但是在内存较小的系统上,哪怕没有这么多内存,还是要挨个初始化,那就浪费了太多的时间。

    commit c4e1be9ec1130fff4d691cdc0e0f9d666009f9ae
    Author: Dave Hansen <dave.hansen@linux.intel.com>
    Date:   Thu Jul 6 15:36:44 2017 -0700
    
        mm, sparsemem: break out of loops earl

    Dave在这个提交中增加了对系统最大存在内存的跟踪,来减少不必要的初始化时间。

    瞧,内核代码一开始其实也没有这么高大上不是。

    3) SPARSEMEM_VMEMMAP

    最后要讲的,也是当前x86系统默认配置的内存模型是SPARSEMEM_VMEMMAP。那为什么要引入这么一个新的模型呢?那自然是sparsemem依然有不足。

    细心的朋友可能已经注意到了,前两种内存模型在做pfn到page struct转换是有着一些些的差异。为了看得清,我们把这两个定义再拿过来对比一下:

    先看看FLATMEM时的定义:

    #define __pfn_to_page(pfn)  (mem_map + ((pfn) - ARCH_PFN_OFFSET))

    再来看看使用SPASEMEM后的定义:

    #define __pfn_to_page(pfn)        \
    ({  unsigned long __pfn = (pfn);      \
      struct mem_section *__sec = __pfn_to_section(__pfn);  \
      __section_mem_map_addr(__sec) + __pfn;    \
    })

    更改后,需要先找到section,然后再从section->memmap的内容中换算出page的地址。

    不仅计算的内容多了,更重要的是还有一次访问内存的操作

    可以想象,访问内存和单纯计算之间的速度差异那是巨大的差距。

    既然产生了这样的问题,那有没有办法解决呢?其实说来简单,内核开发者利用了我们常见的一个内存单元来解决这个问题。

    页表

    是不是很简单粗暴?如果我们能够通过某种方式将page struct线性映射到页表,这样我们不就能又通过简单的计算来换算物理地址和page struct了么?

    内核开发者就是这么做的,我们先来看一眼最后那简洁的代码:

    #define __pfn_to_page(pfn)  (vmemmap + (pfn))

    经过内核开发这的努力,物理地址到page struct的转换又变成如此的简洁。不需要访问内存,所以速度的问题得到了解决。

    但是天下没有免费的午餐,世界哪有这么美好,鱼和熊掌可以兼得的情况或许只有在梦境之中。为了达到如此简洁的转化,我们是要付出代价的。为了实现速度上的提升,我们付出了空间的代价。

    至此引出了计算机界一个经典的话题:

    时间和空间的转换

    话不多说,也不矫情了,我们来看看内核中实现的流程。

    既然是利用了页表进行转换,那么自然是要构建页表在做这样的映射。这个步骤主要由函数vmemmap_populate()来完成,其中还区分了有没有大页的情况。我们以普通页的映射为例,看看这个实现。

    int __meminit vmemmap_populate_basepages(unsigned long start,
               unsigned long end, int node)
    {
      unsigned long addr = start;
      pgd_t *pgd;
      p4d_t *p4d;
      pud_t *pud;
      pmd_t *pmd;
      pte_t *pte;
    
      for (; addr < end; addr += PAGE_SIZE) {
        pgd = vmemmap_pgd_populate(addr, node);
        if (!pgd)
          return -ENOMEM;
        p4d = vmemmap_p4d_populate(pgd, addr, node);
        if (!p4d)
          return -ENOMEM;
        pud = vmemmap_pud_populate(p4d, addr, node);
        if (!pud)
          return -ENOMEM;
        pmd = vmemmap_pmd_populate(pud, addr, node);
        if (!pmd)
          return -ENOMEM;
        pte = vmemmap_pte_populate(pmd, addr, node);
        if (!pte)
          return -ENOMEM;
        vmemmap_verify(pte, node, addr, addr + PAGE_SIZE);
      }
    
      return 0;
    }

    内核代码的优美之处就在于,你可能不一定看懂了所有细节,但是从优美的结构上能猜到究竟做了些什么。上面这段代码的工作就是对每一个页,按照层级去填充页表内容。其中具体的细节就不在这里展开了,相信有兴趣的同学会自行去探索。

    那这么做的代价究竟是多少呢?

    以x86为例,每个section是128M,那么每个section的page struct正好是2M,也就是一个大页。

    (128M / 4K) * 64 = (128 * (1 < 20) / (1 < 12)) * 64 = 2M

    假如使用大页做页表映射,那么每64G才用掉一个4K页表做映射。

    128M * 512 = 64G

    所以在使用大页映射的情况下,这个损耗的级别在百万分之一。还是能够容忍的。

    好了,我们终于沿着内核发展的历史重走了一遍安放page struct之路。相信大家在这一路上领略了代码演进的乐趣,也会对以后自己代码的设计有了更深的思考。

     

    展开全文
  • 在内核中修改MTD分区

    千次阅读 2016-08-13 20:19:23
    1、先解释一下什么是MTD Memory Technology Device,内存技术设备,如nand flash ...3、怎么知道在哪里修改分区  (1)看u-boot启动后出现的分区信息 (2)在内核中查找分区名字,如“Boot Agen

    1、先解释一下什么是MTD

    Memory Technology Device,内存技术设备,如nand flash

    2、为什么要修改MTD分区

    用于存放不同大小的文件(如u-boot,params,uImage,root filesystem)


    3、怎么知道在哪里修改分区

     (1)看u-boot启动后出现的分区信息


    (2)在内核中查找分区名字,如“Boot Agent”,查看代码所在的地方,其中反斜杠\是转移的意思,只用双引号的本来含义,用空格的本来含义,按利用应该是mach-smdkxx.c,和下面的是并列的关系,而conmon-smdk.c是通用的意思。


    4、如何修改MTD分区

    在linux内核中的arch/arm/mach-s3c24xx/common-smdk.c中修改

    static struct mtd_partition smdk_default_nand_part[] = {}结构体数组里的内容替换成

    static struct mtd_partition smdk_default_nand_part[] = {
    [0] = {
    .name = "bootloader",
    .size = SZ_256K,
    .offset = 0,
    },

    // MTDPART_OFS_APPEND表示分区开始的偏移地址紧接着上一个分区
    [1] = {
    .name = "params",
    .offset = MTDPART_OFS_APPEND,
    .size = SZ_128K,
    },
    [2] = {
    .name = "kernel",
    .offset = MTDPART_OFS_APPEND,
    .size = SZ_4M,
    },

    //MTDPART_SIZ_FULL表示取剩余下的容量
    [3] = {
    .name = "rootfs",
    .offset = MTDPART_OFS_APPEND,
    .size = MTDPART_SIZ_FULL,
    }
    };


    展开全文
  • 内核存放在哪里? task_struct->stack 内核栈和thread_info一起存放在一个联合体thread_union中,thread_union的大小为THREAD_SIZE。 thread_info 线程描述符,和体系结构有关的信息。 /* * low level task ...

    curren、内核栈、current_thread_info

    内核栈

    为什么要有内核栈?

    内核栈用于存放一些内核的栈信息,例如临时变量,函数调用信息。

    内核栈存放在哪里?

    task_struct->stack
    内核栈和thread_info一起存放在一个联合体thread_union中,thread_union的大小为THREAD_SIZE。

    thread_info

    线程描述符,和体系结构有关的信息。

    /*
     * low level task data that entry.S needs immediate access to.
     * __switch_to() assumes cpu_context follows immediately after cpu_domain.
     */
    struct thread_info {
        unsigned long       flags;      /* low level flags */
        mm_segment_t        addr_limit; /* address limit */
        struct task_struct  *task;      /* main task structure */
        int         preempt_count;  /* 0 => preemptable, <0 => bug */
        int         cpu;        /* cpu */
    };
    
    #define THREAD_SIZE     16384
    #define THREAD_START_SP     (THREAD_SIZE - 16)
    
    union thread_union {
        struct thread_info thread_info;
        unsigned long stack[THREAD_SIZE/sizeof(long)];
    };
    

    current宏

    栈指针->thread_info
    sp的低字节部分改为0(起始地址THREAD_SIZE对齐)

    thread_info->task
    #define get_current() (current_thread_info()->task)
    #define current get_current()
    
    
    /*
     * how to get the thread information struct from C
     */
    static inline struct thread_info *current_thread_info(void) __attribute_const__;
    
    static inline struct thread_info *current_thread_info(void)
    {
        return (struct thread_info *)
            (current_stack_pointer & ~(THREAD_SIZE - 1));
    }
    

    写一个简单的内核模块来打印响应的信息。

    static void print_arg(void)
    {
        struct task_struct *task = current;
    
        printk("[%s %d] task = 0x%lx\n", __func__, __LINE__, task);
        printk("[%s %d] task->statck = 0x%lx\n", __func__, __LINE__, task->stack);
        printk("[%s %d] current_stack_pointer = 0x%lx\n", __func__, __LINE__, current_stack_pointer);
        printk("[%s %d] ~(THREAD_SIZE - 1) = 0x%lx\n", __func__, __LINE__, ~(THREAD_SIZE - 1));
        printk("[%s %d] (current_stack_pointer & ~(THREAD_SIZE - 1)) = 0x%lx\n", __func__, __LINE__, (current_stack_pointer & ~(THREAD_SIZE - 1)));
        printk("[%s %d] current_thread_info = 0x%lx\n", __func__, __LINE__, current_thread_info());
        printk("[%s %d] task->stack->task = 0x%lx\n", __func__, __LINE__, ((struct thread_info *)task->stack)->task);
        return;
    }
    
    static int __init test_module_init(void)
    {
        printk("[%s %d] init!\n", __func__, __LINE__);
        print_arg();
        return 0;
    }
    

    结果如下

    # insmod test_module.ko
    [ 5903.536202] [test_module_init 28] init!
    [ 5903.540313] [print_arg 16] task = 0xffffffc00d060000
    [ 5903.545394] [print_arg 17] task->statck = 0xffffffc00c220000
    [ 5903.551181] [print_arg 18] current_stack_pointer = 0xffffffc00c223c20
    [ 5903.557643] [print_arg 19] ~(THREAD_SIZE - 1) = 0xffffc000
    [ 5903.563207] [print_arg 20] (current_stack_pointer & ~(THREAD_SIZE - 1)) = 0xffffffc00c220000
    [ 5903.571755] [print_arg 21] current_thread_info = 0xffffffc00c220000
    [ 5903.578033] [print_arg 22] task->stack->task = 0xffffffc00d060000
    
    展开全文
  •  在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢?  对x86比较了解的话,应该知道这些函数参数和函数内部局部变量一起被分配到了函数的局部堆栈中。linux操作系统...
  • linux内核中的fastcall和asmlinkage宏

    千次阅读 2012-12-31 08:12:19
    linux内核中的fastcall和asmlinkage宏内核版本:2.6.14... 在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢?对x86比较了解的话,应该知道这些函数参数和函数内部局部变
  • Linux内核代码

    2018-03-17 14:02:00
    (2)GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定内存中某个位置之后,可以通过LG...
  • 在linux内核中我们都会经常见到FASTCALL...在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢?对x86比较了解的话,应该知道这些函数参数和函数内部局部变量一起被分配
  • 函数参数的存放位置差别

    千次阅读 2013-10-31 11:35:59
    在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢?对x86比较了解的话,应该知道这些函数参数和函数内部局部变量一起被分配到了函数的局部堆栈中。linux操作系统支持...
  • GDT可以被放在内存的任何位置,但CPU必须知道GDT的入口,也就是基地址放在哪里,Intel的设计者门提供了一个寄存器GDTR用来存放GDT的入口地址,程序员将GDT设定内存中某个位置之后,可以通过LGDT指令将GDT的入口...
  • x86体系, 32位linux内核给每一-个进程都分配4G大小的虚拟地址空间,有3G的用户态和1G的内核态,用户态主要存放我们应用程序定义的指令或者数据,局部变量存在于栈上,随着函数的运行,栈上开辟了内存,函数运行完成,...
  • 内核启动并初始化后,最终目的是像Windows一样能启动应用程序,在windows中每个应用程序都存在C盘、D盘等,而linux中每个应用程序是存放在根文件系统里面,那么挂载根文件系统在哪里,怎么实现最终目的运行应用程序?...
  • 1、中断向量表实际上就是存放在Code区(也就是STM32内部的Flash区)从0x00000000地址开始的一个数组,数组的成员为4个字节,而且这些数组在启动文件的时候已经初始化好。 2、STM32根据内核和外设中断优先级,同一...
  • yocto编译kernel

    2020-06-23 19:27:01
    1、Yocto Linux内核编译目录哪? 内核放在了哪里? 这个是放在了如下位置,我们可以用下面命令来确定: bitbake -e linux-imx | grep ^S= 2、重新配置内核配置项。 bitbake linux-yocto -c menuconfig 3、...
  • asmlinkage简要理解

    千次阅读 2019-01-22 13:56:38
     大家都知道在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢? 有一定理论基础的朋友一定会肯定地回答:这些函数参数和函数内部局部变量一起被分配到了函数的...
  • asmlinkage

    2012-07-19 22:30:40
    asmlinkage ...大家都知道在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢? 有一定理论基础的朋友一定会肯定地回答:这些函数参数和函数内部局部变量一起被分
  • FASTCALL和armlinkage

    2012-11-06 16:54:15
    大家都知道在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢? 有一定理论基础的朋友一定会肯定地回答:这些函数参数和函数内部局部变量一起被分配到了函数的局部堆栈中...
  • JAVA锁优化

    2020-09-28 22:43:41
    Synchronizd 锁升级过程 最近在整理自己的技术体系,关于并发编程,入门级知识重量级锁Synchronized,接下来试着解释这块知识。 为什么说Synchronized是重量级锁 ...锁存放在哪里 对象的内存布局如下 [外链图片
  •  大家都知道在标准C系中函数的形参在实际传入参数的时候会涉及到参数存放的问题,那么这些参数存放在哪里呢? 有一定理论基础的朋友一定会肯定地回答:这些函数参数和函数内部局部变量一起被分配到了函数的局部堆栈...
  • syslog程序

    2017-11-08 16:24:00
    作用 系统内核和许多系统程序会产生错误信息...例如,由于内核信息更重要且需要有规律地阅读以确定问题出在哪里,所以要把内核信息与其他信息分开来,单独定向到一个分离的文件中。 日志文件通常存放在/var/log目录...

空空如也

空空如也

1 2 3
收藏数 54
精华内容 21
关键字:

内核存放在哪里