`

linux内存管理学习笔记

阅读更多



1 linux内存管理
地址类型
物理地址
出现在cpu地址总线上的寻址物理内存的地址信号,是地址变换的最终结果
线性地址(虚拟地址)
在32位cpu架构下,可以表示4g的地址空间,用16进制表示就是


0x00000000到0xffffffff
逻辑地址
程序代码编译后,出现在汇编程序中的地址


地址转换
cpu将一个逻辑地址转换为物理地址:
利用段式内存管理单元,将逻辑地址转换成线性地址
再利用页式内存管理单元,将线性地址转换成物理地址
段式管理


(16位cpu)
有20位的地址线,1m的内存空间,由于寄存器只有16位,只能访问65536
个存储单元,64k
因此cpu采用了内存分段的管理模式,cpu内部加入了段寄存器,把1M的
空间分为若干个逻辑段,要求如下:
逻辑段的起始地址必须是16的倍数(最后四个二进制位必须为0)
逻辑段的最大容量最大为64k

物理地址的形成方式:
段地址是16的倍数,形式为xxxx0h,即前16位是变化的,可以只保存前
16位二进制位来保存整个段基地址,所以使用时需要用段寄存器左移补
4个0来得到实际的段地址


确定内存单元在存储器中的具体位置: 段地址+偏移量
逻辑地址=段基地址+段内偏移量
物理地址=段寄存器值*16+逻辑地址

16位cpu有四个段寄存器,程序可同时访问四个不同含义的段
cs+ip 用于代码段的访问
cs 存放程序的段基址 用这两个寄存器就可以得到一个内存物理地址
ip 指向下条要执行的指令在cs段的偏移量

ss+sp 用于堆栈段的访问 直接访问栈顶单元的内存地址
ss 指向堆栈段的基地址
sp 指向栈顶
ds+bx 用于数据段的访问
ds 值左移四位得到数据段的起始地址
bx 偏移量
es+bx 用于附加段的访问
es 值左移四位得到附加段起始地址
bx 偏移量


(32位cpu)
有两种不同的工作方式:

实模式 与16位cpu一致


保护模式 段基地址长达32位 段寄存器存放的是一个地址


(段选择器), 选择器从内存中得到一个32位的段地址

页式管理
线性地址分为固定长度的组,称为页


linux中所有段的基地址均为0,所以线性地址=逻辑地址
linux页式管理
linux2.3.29内核采用了四级页管理架构
页全局目录
页上级目录
页中间目录
页表


2 linux进程地址空间
linux采用虚拟内存管理技术,每个进程都有独立的进程地址空间,约3G


linux将4G的虚拟地址空间划分为
用户空间 从0到0xbfffffff 随进程切换发生变化
内核空间 从3G到4G
用户进程只能访问用户空间,可以通过系统调用访问内核空间



查看线性地址
px aux
cat /proc/<pid>/maps


内核内存分配
应用程序中 malloc
linux内核中 kmalloc

函数原型:
#include<linux/slab.h>
void *kmalloc(size_t size,int flags)
size 要分配的内存大小
flags 分配标志,控制kmalloc的行为
GFP_AUTOMIC 在进程上下文之外的代码(包括中断处理)中分配内存,不睡眠
GFP_KERNEL 进程上下文中的分配,可能睡眠(16M-896M)
__GFP_DMA 分配能够DMA的内存区(物理地址在16m以下的页帧)
__GFP_HIGHMEM 分配的内存位于高端内存(896以上)



按页分配
get_zeroed_page(unsigned int flags)//返回指向新页面的指针,页面清零
__get_free_page(unsigned int flags)//同上,但不清零
__get_free_pages(unsigned int flags,unsigned int order)
//分配若干个连续的页面,返回指向该内存区域的指什,不清零




释放内存
void free_page(unsigned long addr)
void free_pages(unsigned long addr,unsigned long order)














3 linux内核地址空间
内核空间是由内核负责映射,不会随进程改变发生变化
物理内存为896M以上的为高端内 存

内核空间分布:
(3G开始)
直接映射区(direct memory region) 896M
8M
动态映射区(vmalloc region)
8k
KMAP区4M
固定映射区4M
4k
(4G结束)
-----------------------------------------
直接映射区 从3G开始,最大896M的线性地址区间
线性地址=3G+物理地址
动态映射区 地址由内核函数vmalloc来进行划分,线线空间连续,物理空间不


一定
永久内存映射区(PKMap region)对于896m以上的高端内存
访问方法:
1 alloc_page(__GFP_HIGHMEM)分配高端内存页
2 kmap函数将分配到的高端内存映射到该区域
固定映射区(fixing mapping region)


4 linux内核链表
链表的数据结构: 数据域和指针域
定义如下:
sturct list_head{
struct list_head *next,*prev;
};

内核链表为双向循环链表


链表的操作主要有:
/*初始化链表头*/
static inline void INIT_LIST_HEAD(struct list_head *list)
{
list->next = list;
list->prev = list;
}

/*插入节点*/
/**
* 节点前插入
* list_add - add a new entry
* @new: new entry to be added
* @head: list head to add it after
*
* Insert a new entry after the specified head.
* This is good for implementing stacks.
*/
static inline void list_add(struct list_head *new, struct list_head


*head)
{
__list_add(new, head, head->next);
}




/**
*节点尾插入
* list_add_tail - add a new entry
* @new: new entry to be added
* @head: list head to add it before
*
* Insert a new entry before the specified head.
* This is useful for implementing queues.
*/
static inline void list_add_tail(struct list_head *new, struct


list_head *head)
{
__list_add(new, head->prev, head);
}


/*删除节点*/
/**
* list_del - deletes entry from list.
* @entry: the element to delete from the list.
* Note: list_empty() on entry does not return true after this, the


entry is
* in an undefined state.
*/
static inline void list_del(struct list_head *entry)
{
__list_del(entry->prev, entry->next);
entry->next = LIST_POISON1;
entry->prev = LIST_POISON2;
}


/*提取数据结构*/
/**
* list_entry - get the struct for this entry
* @ptr:the &struct list_head pointer.
* @type:the type of the struct this is embedded in.
* @member:the name of the list_struct within the struct.
*/
#define list_entry(ptr, type, member) \
container_of(ptr, type, member)


/* 遍历 */


/**
* list_for_each-iterate over a list
* @pos:the &struct list_head to use as a loop cursor.
* @head:the head for your list.
*/
#define list_for_each(pos, head) \
for (pos = (head)->next; prefetch(pos->next), pos != (head); \
pos = pos->next)






5 linux内核定时器


度量时间差
时钟中断 由系统的定时硬件以周期性的时间间隔产生,这个间隔由内核跟据hz来


确定,hz与体系结构无关,可配置(50-1200),x86平台默认为1000
时钟中断发生后,全局变量jiffies(unsigned long)会加1,驱动程序常


利用其来计算不同事件的时间间隔
内核定时器 (双向链表)
用于控制某个函数(定时器处理函数)在未来的某个特定时间执行(只执行一次)


struct timer_list{
struct list_head entry //内核使用
unsigned long expires //超时的jiffies的值
void (*function)(unsigned long) 超时处理函数
unsigned long data //超时处理函数参数
struct tvec_base *base //内核使用
};






/*初始化定时器队列*/
void init_timer(struct timer_list *timer);
/*启动定时器*/
void add_timer(struct timer_list *timer);
/*定时器超时前将其删除*/
void del_timer(struct timer_list *timer);


示例代码如下:
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/timer.h>
#include <asm/uaccass.h>


MODULE_LICENSE("GPL");
MODULE_AUTHOR("Retacn Yue");
MODULE_DESCRIPTION("timer module");
MODULE_ALIAS("timer module");


sturct timer_list timer;


void timer_function(int para){
prink("timer expired and para is %d\n",para);
}


int timer_init(){
/*初始化定时器*/
init_timer(&timer);


timer.data=5; /*超时处理函数参数*/
timer.expires=jiffies+(20*HZ); /*超时jiffies的值*/
timer.function=timer_function; /*超时处理函数*/
/*启动定时器*/
add_timer(&timer);

return 0;
}


void timer_exit(){
del_timer(&timer);
}


module_init(timer_init);
module_exit(timer_exit);

分享到:
评论

相关推荐

Global site tag (gtag.js) - Google Analytics