Linux内核分析

一、预备知识
1.Linux内核发展
1)单内核和微内核:
1>引出Linux和Unix,Windows之间的关联
2>Linux的优势:集两家之长

2.CPU的寻址方式
1)CPU的位数决定因素、地址总线、数据总线
1>CPU的位数由“算数逻辑单元”决定;
2>地址总线和数据总线尽量保持一致。

2)寻址方式:实地址模式和保护模式
1>实地址:可以理解为分段方式的由来。对于8086结构的CPU而言,内存的不够,产生段寄存器,将内部16位地址和某个段寄存器相加,形成20位的实际物理地址,这样的问题就是无保护机制,可以修改段寄存器实现对任意地址的访问;
2>保护地址:运用于80286和80386中,增加一个数据结构来对地址访问增加权限。

3)段式内存管理机制的实现(也可理解为保护模式):
1>全局性的段描述表寄存器GDTR---保存内核进程和用户进程相关属性;
2>局部性的段描述表寄存器LDTR---保存各个用户进程相关属性。

3.页式内存管理机制
1)页式内存管理机制的由来:
1>段式内存管理机制:1.段的可变长度,不利于盘区交换;2.如果改小段的长度,势必会频繁改变段寄存器的内容
2>由于页式存储方式是在80386的段式存储方式上发展而来的,故其绕不开段式存储,相当于再加一层地址空间

2)页式内存管理机制的实现
1>在页式存储管理中,有页面目录PGD,中间目录PMD和页面表PT,采用三层可以节省页表所占用的空间
2>采用三层甚至多层的原因:
a.在一定程度上通过代码的实现可以节省内存空间;
b.并于后期扩展到64位甚至更高位的CPU架构上去。

二、内存管理
1.Linux主要采用页式内存管理机制
1)当为80386架构时,必须包含段式存储,虽有没有任何实质作用;
2)页式存储一般采用三层表来实现:页面目录PGD,中间目录PMD和页面表PT

2.内核映射的物理地址空间页面划分:
1)ZONE_DMA:用于DMA使用;
2)ZONE_NORMAL:用于内核空间的常驻内存的正常映射
3)ZONE_HIGHMEM:用于超过1G内存的使用。

3.内存的越界访问
1)产生缺页中断的情形:
1>页目录或者页表不存在,即线性地址和物理地址映射尚未建立,或者已经撤销;
2>相应的物理页面不在内存中;
3>访问权限不符。

2)缺页中断情形的判别:
1>当访问地址在3G以上,进入了内核地址,其就访问越界了
2>当访问区域范围没越界,说明该页面在地址中尚未建立或者已经撤销;


3)访问内存失败处理--进入缺页中断程序

4.用户堆栈的扩展
1)定义:开始创建进程时建立的堆栈区间已经用完了,此时必须重新扩展堆栈区间;
2)实现:
1>首先判别是正常的堆栈扩展还是非法越界操作;
2>通过expand_stack()改变堆栈区vm_area_struct的结构;
3>建立映射关系,包括页面目录,中间目录和页面表;
4>最后是CPU从一个页面错误异常中返回用户空间时,将会先重新执行因映射失败而中途夭折的那条指令,然后再继续执行下去,这是异常处理的特殊性。

5.物理页面的使用和周转
1)内存交换的由来
1>虽然每个进程需要内存空间不大,但进程量多时,必定会引起内存的不够;
2>早期的磁盘交换技术是建立在段式存储管理基础之上的,后来才发展成页式存储管理;


2)存储空间的介绍
1>对磁盘存储空间的管理:通过swap_info_struct结构体来管理
2>对内存存储空间的管理:系统初始化时,内核先根据检测到的物理内存大小,为每个页面都建立一个page结构,形成一个page结构的数组,并使用全局量mem_map指向这个数组;然后将这些页面合并成“块”;再根据块的大小简历起若干“管理区”,同时区中存在一个空闲队列,这样并于内存的分配使用

3)物理页面周转的实现
1>释放磁盘页面的函数_swap_free(),其实质不涉及磁盘的操作,而只是在聂存中“账面”上的操作,表示磁盘上那个页面的内容已经作废
2>内存页面周转的理解:
a.页面的分配,使用和回收;
b.盘区交换,其最终目的也是页面的回收。只有用户空间的内存才可以进行交换,而内核是不能进行交换的.
3>用户空间页面分类(用户进程分配使用的内存空间,但都是由内核进行分配,其中b就是用户进程自己要求的)
a.普通的用户空间,包括进程的代码段,数据段,堆栈段以及动态分分配的“存储堆”;
    b.通过系统调用mmap()映射到用户控件的已打开文件的内容,这是用户进程参与内存管理的的操作;
    c.进程间的共享内存区。
4>内核空间页面分类(内核进程分配使用的内存空间)
a.内核代码和内核中的全局变量,其不会被释放,是静态存储的;
    b.一旦使用完毕便于保存的价值;
    1>内核中通过kmalloc()或者vmalloc()分配释放
    2>内核通过alloc_page()分配
    c.使用完毕,但其还有保存价值  
       1>缓冲存储一些文件的目录结构;
       2>缓冲存储一些inode结构的空间
       3>系统读写操作的缓冲区
5>页面交换存在的问题
a.页面交换必定会影响系统性能,故在系统空闲时,可以考虑换出一些内存;
b.但新的问题是,不能准确预知系统的使用,可能刚换出又要使用;
c.采用页面换出和内存页面的释放分开进行;
d.然后通过建立几个队列,对这些状态进行管理。

6.物理页面的分配
1)通过alloc_pages()来完成物理页面的分配
2)物理页面分配失败的原因
1>可分配页面的总量太少
2>总量不少,但不能满足所要求的页面块的大小,解决方法如下:
a.通过page_launder()把“脏页”面“洗净”,然后回收释放干净页面;
b.唤醒kswapd进程,而要求分配页面的进程进入睡眠等待,然后再重新申请分配;;

7.页面的定期换出
1)页面定期换出的由来:避免在进程需要内存时才处理,提前换出内存空间;
2)实现函数:kswapd()
1>相当于一个进程,受内核调度,其没有独立的地址空间,使用内核的地址空间
2>它的代码是静态链接在内核中的,可以直接调用内核中的各种子程序,不想其他进程,需要系统调用
3)函数具体实现:
1>发现物理页面短缺的情况下进行,目的是预先找出若干页面
a.检查内存中可供分配或者周转的物理页面是否短缺;
1>当前尚存的空闲页面
2>现有的不活跃的"干净"页面
3>现有的不活跃的“脏”页面,这个需要写入交换设备变成干净页面才可以重新分配
b.发现可供分配页面短缺,设法释放和换出若干页面
1>首先调用page_launder(),把已经转入不活跃状态的“脏”页面“洗净”,使其变成立即可以分配的页面,这个可以进行两次扫描;
2>文件系统使用过程中,会缓存很哌多的目录项dentry数据结构和一些文件索引节点inode数据结构,占用物理页面,可以使用shrink_dcache_memory()和shrink_icache_memory()函数加以回收;
3>“slab”管理机制,也会占用很多的空闲页面,可以通过kmem_cache_reap()来实现回收;
c.当通过以上三种方式还是没法达到内存分配所需时,等待到缺省中断时或许有了:
1>通过refill_inactive_scan()扫描活跃页面队列,试图找到可以转换成不活跃状态的页面
2>通过swap_out()找出一个进程(这个进程也是通过查询找到的相对合适的进程),然后扫描其映射表,从中找出可以转入不活跃状态的页面

8.物理页面的换入
1)缺页中断产生的原因:
1>页目录或者页表不存在,即线性地址和物理地址映射尚未建立,或者已经撤销;
2>相应的物理页面不在内存中;
3>访问权限不符。
2)处理缺页异常:
1>首先看相应的内存页面是否还留在swapper_space的换入换出队列中尚未释放,调用lookup_swap_cache()实现;
2>如果释放了,通过read_swap_cache()分配一个内存页面,并且从磁盘中将其内容读进来,而且每次读的时候会多读几个页面,构成一个集群,并且暂存在活跃页面或者swapper_space的换入换出页面中;

9.内存缓冲区的管理
1)内存缓冲池的演变:
1>动态内存分配,不适合为每个数据结构分配一个“缓冲池”,这个不合理且浪费;
2>像用户空间中的malloc()那样动态内存分配:
a.长期使用后,会使存储堆“碎片化”,可以采用2^n的方式缓解;
b.每次分配的内存进行初始化,可以保证在下次分配时“重用”而无需初始化,提高使用效率;
c.缓冲区的组织和管理是密切相关的;
d.不适合多处理器公用内存的情况。
3>“slab”内存管理方法----见附录1

2)内存缓冲区的具体实现
1>专用缓冲区队列的建立
2>缓冲区的分配和释放

10.外部设备存储空间的地址映射
1)对外设访问的两种不同方式
1>内存映射:可以直接用访问内存的指令对外部设备的存储单元进行访问
2>I/O映射:外部设备的存储单元和内存分属两个不同的体系,必须要专门的指令来对外设进行访问
3>由于外部设备的增多,且I/O映射方式是不能满足要求的,必须将外设上的存储器映射到内存空间,实际上是虚拟空间,在内核中,其通过ioremap实现

2)内存页面管理
1>对于普通的内存分配:通常是先在空间分配一个虚拟区间,然后为此区间分配相应的物理内存页面并建立其映射,但这种映射并不是一次完成,可以在访问这些虚拟页面引起页面异常是逐步建立
2>ioremap()内存管理:先有物理存储空间,出现在总线上,然后再映射到内存空间

11.系统调用brk()
1)用户空间内存映射
1>用户通过编译,链接形成映像可执行文件,该文件已经划分了区域,便于程序的装载;
2>代码区和数据区(静态分配的数据空间,包括全局变量和static的局部变量),内核在建立进程时就分配好了内存空间,包括虚拟地址和物理地址
3>堆栈的使用空间也属于基本要求,所以也是在建立进程时就分配好的,只是在进行分配扩展时调用malloc().

2)brk()函数的具体实现

12.系统调用mmap()
1)通过系统调用mmap(),将一个已打开文件的内容映射到它的用户空间,用户界面为:mmap(void *start, size_t length, int prot, int flags, int fd, off_t offset)
2)这种映射方式区别于对常规文件的操作,如read(),write(),将文件映射到用户空间后像访问内存一样访问文件显然要方便很多
3)mmap()函数的具体实现


附录1:
1.内存分配的问题
1)内存分配每次至少分配一个页面。但当请求分配的内存大小为几十个字节或几百个字节时应该如何处理?如何在一个页面中分配小的内存区,小内存区的分配所产生的内碎片又如何解决?
2)内核经常反复使用某一内存区。例如,只要内核创建一个新的进程,就要为该进程相关的数据结构(task_struct、打开文件对象等)分配内存区。当进程结束时,收回这些内存区。因为进程的创建和撤销非常频繁,因此,Linux的早期版本把大量的时间花费在反复分配或回收这些内存区上;

2.Buddy算法(伙伴系统算法)
1)使用场景:内核中很多时候要求分配连续页,为快速检测内存中的连续区域
2)原理:系统中的空闲内存总是两两分组,每组中的两个内存块称作伙伴。伙伴的分配可以是彼此独立的。但如果两个小伙伴都是空闲的,内核将其合并为一个更大的内存块,作为下一层次上某个内存块的伙伴。
3)Buddy算法具体实现
1>所有的空闲页框分组为11个块链表,每个块链表分别包含大小为1,2,4,8,16,32,64,128,256,512和1024个连续页框的页框块;
2>每个页块组用双向循环链表进行管理,分别挂到free_area[]数组上;
3>位图数组来标记内存页面的使用情况;
4>内存分配:内存分配时搜索,系统按照Buddy算法,根据请求的页面数在free_area[]对应的空闲页块组中搜索。 若请求页面数不是2的整数次幂,则按照稍大于请求数的2的整数次幂的值搜索相应的页面块组。当相应页块组中没有可使用的空闲页面块时就查询更大一些的页块组,在找到可用的页块后分配所需要的页面。当某一空闲页面被分配后,若仍有剩余的空闲页面,则根据剩余页面的大小把他们加入到相应页面组中
5>内存释放,系统将其作为空闲页面看待,检查是否存在与这些页面相邻的其他空闲页块,若存在,则合为一个连续的空闲区按Buddy算法重新分组;
4)Buddy算法的缺点:
1>伙伴系统分配内存时最小也是一个内存页面,对于内存大小更小的需求,就会产生很多内存碎片;
2>频繁与系统交互,影响系统性能。

3.slab算法
1)适用场景:内核本身经常需要比完整页帧小的多的内存块,如进程描述符,特点是占用内存小;
2)原理:
1>在伙伴系统基础上自行定义额外的内存管理层,将伙伴系统提供的页划分为更小的部分;
2>slab来自一种很简单的思想,即事先准备好一些会频繁分配,释放的数据结构
3>减少初始化次数,对于释放回的内存保持为针对特定目的而初始化的状态。
3)slab算法解决的问题:
1>内核对内存区的分配取决于所存放数据的类型,一定程度上减少了内存碎片;
2>Slab的主要目的是为了减少对伙伴算法的调用次数。
4)slab算法具体实现
1>Slab分配模式把对象分组放进缓冲区(尽管英文中使用了Cache这个词,但实际上指的是内存中的区域,而不是指硬件高速缓存)
5)slab算法的优点:
1>内核通常依赖于对小对象的分配,它们会在系统生命周期内进行无数次分配
2>slab 缓存分配器通过对类似大小的对象进行缓存而提供这种功能,从而避免了常见的碎片问题
3>slab 分配器还支持通用对象的初始化,从而避免了为同一目而对一个对象重复进行初始化
4>slab 分配器还可以支持硬件缓存对齐和着色,这允许不同缓存中的对象占用相同的缓存行,从而提高缓存的利用率并获得更好的性能


注:以上均是学习《Linux内核情景分析》的笔记,望大家指正

  • 1
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Linux内核分析与编程是指对Linux操作系统的内核进行深入研究并进行相关开发工作的过程。Linux内核是操作系统的核心部分,负责管理计算机的硬件资源和软件的运行。 首先,Linux内核分析是通过研究内核源代码来理解其工作原理和设计理念。分析时我们可以关注如中断处理、进程调度、内存管理、文件系统等关键模块,深入了解它们的实现原理和交互方式。通过分析内核代码,我们能够了解Linux内核中各个组件的工作方式,更好地了解操作系统的工作流程和机制。 其次,Linux内核编程是指基于已有内核的代码基础上进行开发工作。通过在内核中添加新的功能模块或优化现有功能,我们可以对操作系统进行定制化的开发,以满足特定需求。内核编程需要具备深厚的C语言编程基础和操作系统相关知识,熟悉内核代码的组织结构和编码规范。 Linux内核分析与编程的价值在于提升我们对操作系统的理解和能力。通过深入研究内核源代码,我们可以更好地掌握操作系统的原理和机制,从而优化系统性能和安全性。同时,通过内核编程,我们能够进行操作系统的个性化定制,满足特定业务需求。这对于从事系统开发、嵌入式开发等方向的工程师来说,具有重要的意义。 总结来说,Linux内核分析与编程是对Linux操作系统内核进行研究和开发的过程。通过分析内核源代码来了解内核的工作原理和设计思想,并通过内核编程进行系统定制和优化,从而提升对操作系统的理解和能力。这对于开发人员来说具有重要的价值和意义。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值