1、地址
物理地址:出现在CPU地址总线上的寻址物理内存的地址信号,是地址变换的最终结果
线性地址(虚拟地址):
逻辑地址: 程序代码经过编译后,出现在汇编程序中的地址
地址转换:
逻辑地址转换物理地址:CPU利用段式内存管理单元,将逻辑地址转换成线性地址,在利用页式内存管理单元,把线性地址转换成物理地址
段式内存(X86):
16位CPU:内部有20位的地址线,寻址范围是1M的内存空间,但是用于存放地址的寄存器(IP,SP)只有16位,因此只能访问65536个单元存储,64K
为了管理1M的内存空间,在CPU内部增加了段寄存器。16位CPU把1M内存空间分为若干个逻辑段,每个逻辑段的要求如下:
1)逻辑段的其实地址(段地址)必须是16的倍数,即最后的4个二进制位必须为0
2)逻辑段的最大容量是64K
段寄存器是为了对内存进行分段管理而增加的16位CPU有4个段寄存器,程序同时可以访问4个含义不同的段:
1)CS+IP:用于代码段的访问,CS指向存放程序的段基址,IP指向下条要执行的指令在CS段的偏移量,通过这两个寄存器就可以得到一个内存物理地址(存放下条指令)
2)SS+SP:用于堆栈段的访问,SS指向堆栈段的及地址,SP指向栈顶,可以通过SS+SP两个寄存器直接访问栈顶单元的内存物理位置
3)DS+BX:用于数据段的访问,DS中的值左移四位得到数据段起始地址,再加上BS中的偏移量,得到一个存储单元的物理地址
4)ES+BX:用于附加段的访问,ED中的值左移四位得到附加段起始地址,再加上BX中的偏移量,得到一个存储单元的物理地址
两个寄存器:段基地址寄存器和段偏移寄存器
逻辑地址=段基地址+段内偏移量
由逻辑地址得到物理地址的公式:
PA=段寄存器的值*16+逻辑地址
32位CPU:同样采用分段的管理模式,与16位CPU的不同之处在于32位采用两种不同的工作方式:实模式和保护模式
实模式 :内存管理和16位CPU一致
保护模式:段基地址长32位,每个段的最大容量是4G,段寄存器的值是段地址的“选择器”,用该选择器从内存中得到一个32位的段地址,存储单元的物理地址就是该短地址加上段内偏移值
页式内存:线性地址被分为固定长度的组,称为页(虚拟地址)
物理页:分页单元把所有的物理内存划分成固定长度的管理单元,称为物理页,或者是页框、页帧(物理地址)
Linux内存中所有段的及地址均为0,因此逻辑地址与线性地址保持一致,在Linux中(逻辑地址,线性地址和虚拟地址)可以认为是一致的
2、虚拟内存(Linux进程地址空间)
Linux操作系统采用虚拟内存管理技术,使得每个进程都有独立的进程地址空间。利用虚拟地址不但能起到保护操作系统的作用,而且更重要的是用户程序可使用比实际物理内存更大的地址空间。
用户空间:每个进程独立拥有3G的用户空间,0~0xbfffffff,每当进程切换时,用户空间就会跟着变化
可以通过 /proc/pid/maps 命令查看进程使用的线性地址
实际的物理内存只有当进程真的访问新获取的虚拟地址时,才会由“请页机制”产生“缺页”异常,从而进入分配实际页框的程序。该异常是虚拟内存机制赖以存在的基本保证--它会告诉内核去位进程分配物理页,并建立对应的页表,之后虚拟内存才试试再在的映射到物理地址上。
申请内存:
按页分配:
get_zaroed_page(unsigned int flags)//返回指向新页面的指针,并将页面清零
__get_free_page(unsigned int flags)//返回指向新页面的指针,但不页面清零 --void free_page(unsigned long addr)
__get_free_pages(unsigned int flags,unsigned int order)//,分配若干个连续的页面,返回指向该内存区域的指针,不页面清零--void free_pages(unsigned long addr,unsigned long order)
3、Linux内核地址空间
高端内存:物理内存896MB以上的部分称为高端内存
内核空间分布:
直接映射区 8M 动态映射区 8K KMAP区 固定映射区 4K
| 896M(MB) | 120M(min) | 4M | 4M |
直接内存映射区:从3G开始,最大896M的线性地址区间,该区域的线性地址和物理地址之间存在线性转换关系:
线性地址 = 3G + 物理地址
动态映射区:由vmalloc来进行分配,特点是线性空间连续,但是对应的物理空间不一定连续(物理页可能处于地端内存,也可能处于高端内存)
永久内存映射区(PKMAP):可以访问896M以上的所有内存,访问方法:
1)使用alloc_page(__GFP_HIGHMEM)分配高端内存页
2)使用kmap函数将分配到的高端内存映射到该区域
固定映射区:和4G顶端只有4K的隔离带,固定映射区中每个地址项都服务于特定的用途
4、内核链表
链表的开销主要是访问的顺序性和组织链的空间损失,
内核链表具备双链表功能,,通常都组织成双向循环链表
内核链表操作:
初始化链表头
INIT_LIST_HEAD(list_head *head);
插入节点:
list_add(struct list_head *new,struct list_head *head) //链表头之后插入
list_add_tail(struct list_head *new,struct list_head *head)//链表末尾加入
list_del(struct list_head *entry); //删除节点
提取数据结构
list_entry(ptr,tye,member)//,已知数据结构中的节点指针ptr,找出数据结构
遍历:
list_for_each(struct list_head *pos,struct list_head *head);
5、Linux内核定时器
时间中断由系统的定时硬件以周期性的时间间隔产生,这个时间间隔(频率)由内核根据HZ来确定
每当时钟中断发生时,全局变量jiffies(unsigned long)加1,jiffies记录了自linux启动后时钟中断发生的次数,驱动程序常利用jiffies来计算不同事件间的时间间隔
Linux内核定时器被组织成双向链表,并使用struct timer_list结构描述:
struct timer_list {
struct list_head entry;//内核使用
unsigned long expires;//超时的jiffies值,timer.expires= jiffies + (x * HZ);
void (*function)(unsigned long);//超时处理函数,function(int para) { // para 等于 超时时间的值}
unsigned long data;//超时处理函数参数
struct tvec_base *base;//内核使用
操作定时器:
初始化定时器队列结构
void init_timer(struct timer_list *timer);
启动定时器
void add_timer(struct timer_list *timer);
删除定时器
int del_timer(struct timer_list *timer); //在定时器超时前将它删除,当定时器超时后,系统会自动将它删除
};