调试

深入 kernel panic 流程【转】

Linux内核编程(08):debugfs文件系统

性能

盘一盘Linux内核中ns级别的高精度计时方法

知识点

RCU

Linux中的RCU机制

entry

entry 在 linux 链表中表示一个条目

list_entry()

#define list_entry(ptr, type, member) \
	container_of(ptr, type, member)

通过结构体(type类型)中某个成员(member),和成员地址(ptr),得到结构体的首地址(entry)。

另:ptr 是 pointer 的意思,指针

实例:

last_tid = list_entry(tid_list->prev, struct ath_atx_tid, list);

list_entry

list_first_entry()

无序链表任何一个节点都可以是头

Linux双向链表(四)——宏

大佬

知乎:

Linux嵌入式

宅学部落-王利涛

工作

Linux内核工程师是怎么步入内核殿堂的?

读书

二刷《Linux内核设计与实现》

目标:

内存管理、进程管理、文件系统。

面试官:请谈谈linux内存管理

我们有一块内存,我们如何去使用它。

程序想要运行,必须加载到内存,因为磁盘太慢了。

那要怎么把进程加载到内存呢?放在内存的什么位置呢?

如果不加以管理,比方说进程A占用了地址0-100,进程B也去占用这部分内存,就会出问题。我们也不可能提前规划好,A 使用0-100,B 使用101-200。就算能规划,你还一个平台呢?又何别的程序冲突了。

一开始,内存是按段管理的,即段式存储。

所以引入了虚拟内存,每个进程,都认为所有内存地址空间都是自己的,自己想怎么用就怎么用。A 进程使用0-10000地址,B进程照样可以使用0-1000地址。那背后是怎样实现的呢?就是虚拟地址,MMU 把虚拟地址映射到实际的物理内存地址,A 的 10000 比方说映射到物理内存的 1000,B的1000比方说映射到物理内存的2000,所以A 和B 不冲突。但是A和B 是察觉不到这件事的,都是MMU在默默的工作。

那MMU具体是怎么做的呢?比方说A访问一段虚拟地址时,MMU就会看这段虚拟地址是否在物理内存上(通过页表),如果在物理内存上,就从物理内存上访问数据。如果发现页表上没有此条记录,说明该虚拟地址没有映射到实际的物理地址,这时就发生缺页中断,去查找可用的物理内存,做映射。

现在的进程非常占内存,动不动几个G ,十几个G ,物理内存就那么大,被占完了怎么办,这时候就要考虑把部分物理内存暂存到磁盘上,捣腾出空间,磁盘上的这部分空间就是交换分区。将谁的内存置换到磁盘上呢?这就依靠内存管理算法了,没有最好,只有合适。一般是将最久没有使用的,置换到磁盘。(和kill占内存最多的进程的差别)

另外一个问题,以什么为单位管理内存呢?太小也不好,太大也不好。

内存4K,

页、页框,

置换算法,

创建算法:是从内存地址从小到大查找,找到第一块满足的内存就分配呢?还是找到一块符合条件的最小的空闲内存呢?前者快,单容易产生内存碎片;后者慢,但内存碎片较少。

不管何种算法,随着时间推移,肯定会产生内存碎片,如何回收呢?

开始看书!!!!

少讲了内核空间地址范围,用户空间地址范围,以32位系统为例。

内核把物理页作为内存管理的最小单位。MMU(Memory Management Unit,内存管理单元,管理内存并把虚拟地址转换成物理地址的硬件)通常以页为单位进行管理。这也是页表名称的由来。从虚拟内存的角度来看,页就是最小单位。

大多数 32 位体系结构支持 4KB 的页,而 64 位体系结构一般会支持 8KB 的页。

这就意味着,在支持 4KB 页大小并有 1GB 物理内存的机器上,物理内存会被划分为 262144 个页。

内核用 struct page 结构表示系统中的每个物理页。结构位于<linux/mm.h>

virtual 字段是页的虚拟地址。

_count 字段存放页的引用计数。

这个数据结构的目的在于描述物理内存本身,而不是描述包含在其中的数据。

内核用这一数据结构来管理系统中所有的页,因为内核需要知道一个页是否空闲。

系统中的每个物理页都要分配一个这样的结构体。

物理内存分为三个区:ZONE_DMA、ZONE_NORMAL、ZONE_HIGHMEM

简历-性能优化:缓存一致性、使用 kmalloc() 代替 vmalloc() [第11章 vmalloc()]、内存池(类似slab机制)

kmalloc() –> 空闲链表 –> slab 分配器

看视频: 链接

重定位(非重定向)

保护

共享

代码中的地址都是虚拟(逻辑)地址,编译好的汇编或二进制也是虚拟地址,讨论的堆、栈、静态数据区、代码区也都是虚拟地址

重定向,就是程序地址使用偏移量,代替直接使用地址。

在执行对内存的访问之前,必须把它转换成物理地址。

程序的链接和加载

程序的链接和加载

特权模式

内存管理:

固定数量分区(等大小)。问题:支持的进程并发数量有限。有的进程很大,放不下;有的进程很小,占一个分区浪费。

👇

固定数量分区(大小不等的分区)

👇

**动态分区。**进程需要多少内存,就分配多少内存,没有内部碎片。缺点:程序释放,产生外部碎片。解决办法:压缩。

放置算法:最佳适配、首次适配、下次适配

👇

伙伴系统。类似 2048 游戏。优点,分区数量不固定,灵活。分区释放后很容易合并,减少碎片。

利用了二叉树。

👇

非连续分配。允许一个进程使用非连续的物理地址空间,允许共享代码与数据,支持动态加载和动态链接。

如何实现虚拟地址和物理地址的转换。

  • 软件实现(灵活,开销大):每一次内存的访问,还要额外地增加地址转换工作。
  • 硬件实现(够用,开销小)

👇

分页。

目的:更好地利用内存,不考虑数据的实际意义,单纯一页一页划分。

👇

分段。代码段、堆、栈、静态数据区等。

目的:更细粒度和灵活的分离与共享。

段错误 (核心已转储)

👇

基于段寄存器的段式内存管理。

缺点:需要很多段寄存器

👇

基于段表的段式内存管理

段表和页表的区别:段比页大,段表里面的记录也就比页表少。

👇

交换分区。

目的:满足人们对内存无休止的增长。进程需要的内存比你物理内存还要大。

做法:覆盖(overlay)、交换(swapping)

覆盖是比较老的技术,只能发生在单个进程内部,程序员自己决定哪些部分覆盖。

交换的实现:

可将暂时不能运行的进程放到外存。

换入换出的基本单位:整个进程的地址空间。(PS :现在也允许将进程空间的一部分换出)

换出(swap out):把一个进程的整个地址空间保存到外存

换出(swap in):把外存中某个进程的地址空间读入到内存。

交换的时机:仅当内存空间不够或有不够的可能时换出。

交换分区的大小:存放所有用户进程的所有内存映像的拷贝。

程序换入时的重定位:换出后再换入时需要放在原处吗?采用动态地址映射的方法。

缺页异常、缺段异常:访问的地址没有映射到内存地址空间

付出的代价:缺页中断,操作系统将进程置于阻塞状态(需要去磁盘读数据到内存)。

当磁盘I/O发生时,另一个进程被分派去运行;

当磁盘I/O完成时,会发出一个中断,使操作系统将受影响的进程置于就绪状态。

使用交换技术,实现超过物理内存大小的进程运行;

使用非连续内存分配机制,实现只分配目前所使用到的地址空间,实现将一个程序的部分换出到磁盘。

进程空间(虚拟内存)被分成大小相等的块。页

物理内存被分成大小相等的块。帧(页框?)

不必要求连续的页必须放在连续的帧中。只要记录页和帧的对应关系就行。访问的时候去查映射关系就行了。

页和帧的大小相等,所以只要记录页和帧的对应关系就行了,内部偏移不用记录,两者是一致的。

页表:操作系统为每个进程,维护一个页和帧的对应关系。

a.0 -> 0

a.1->1

b.0->2

b.1->3

页表工作过程:

进程访问一个地址时,这个地址时虚拟地址,也就是页(虚拟地址的前半部分为页地址,后半部分为字节偏移量),

操作系统根据进程号和页地址,去页表查找,该进程的这个页对应的物理内存中的帧号。

在加上字节偏移,就得到实际的物理地址,

然后用这个物理地址去内存中拿数据,

然后返回给进程。

额外的开销:一次访问,需要访问两次内存。因为查页表也要访问内存。

连续分配的话,就没有查表这个开销,只要知道起始基地址就行了,后面都是连续的,直接基地址加偏移就行了。

对于页表,操作系统需要做什么?

至少要知道页表在哪里。