Linux memory management
? i386保护模式的分段与分页
? Linux分页
? 线性地址空间分布
? 用户地址空间
? 内核地址空间
? 空闲物理内存管理
? 内核物理内存分配接口
? 共享存储
Intel x86保护模式的地址映射
段选择子 16bits 段内偏移 32bits
页内偏移 12bits
物理地址 32bits
页目录索引 10bits 页表索引 10bits
逻辑地址
线性地址
物理地址
GDT,LDT
pgd,pte
……
Intel x86的分段
描述符索引 13bits GDT/LDT 1bit
段选择子
访问特权级 2bits
段内偏移 32bits
段基地址 32bits
段描述符 8bytes
+
线性地址 32bits
段界限 20bits
GDT
…………
Linux在 i386上的分页
线性地址
物理地址 32bitspmd_t * dir
+
页帧首地址
20bits << PAGE_SHIFT
页内偏移 12bits页目录索引 10bits 页表索引 10bits
#define PMD_SHIFT 22
#define PTRS_PER_PMD 1
#define pmd_index(address) (((address) >>PMD_SHIFT) & (PTRS_PER_PMD-1))
pgd pte
存在位
读写位
用户态 /核心
态访问位
表示是否采
用写透方式
表示是否启
用高速缓存
访问位
已写标
志位
页大小,4K(0)或
4M(1),只适用于页
目录表项
页目录表项和页表项
Linux的线性地址空间分配
? 4G线性地址空间分为用户空间和内核空间,内
核空间又分为物理内存区、虚拟内存分配区、
高端页面映射区、专用页面映射区和系统保留
映射区
? 线性地址从 0x00000000到 0xBFFFFFFF的 3G
为用户和内核共同访问,0xC0000000以上的
1G由内核独享,用户态无法访问
? 因此所有进程的页目录的后四分之一均指向内
核页目录的相应目录项。为减小同步的开销,
内核只在处理 page_fault时同步用户进程的页
目录项,因此页目录项不一致的情况是有的
Linux的线性地址空间分配
Linux的线性地址空间分布
Memory mapped region:
File-mapping
Shared libraries
kernel code/data/stack
Memory mapped region
for shared libraries
runtime heap (via malloc)
program text (.text)
initialized data (.data)
uninitialized data (.bss)
stack
forbidden0
%esp process
VM
brk
0xc0
physical memorysame
for each
process
process-specific data
structures
(page tables,
task and mm structs)
kernel
VM
.data
.text
p
demand-zero
demand-zero
libc.so
.data
.text
Linux的用户地址空间分布
例:
#include <stdio.h>
int z = 0; /*进程的数据段中 */
int main()
{
int *a = 0; /*进程的用户栈中 */
int pid = getpid();
if (pid = fork()) { /*父进程执行这里的代码 */
a = (int *) malloc(100*sizeof(int));/*父进程的堆空间,runtime data*/
z = pid;
printf(“z1 = %d\n”,z);
} else { /*子进程执行的代码 */
a = &z;
*a = pid;
printf(“z2 = %d\n”,z);
}
printf(“all done,pid = %d\n”,pid);
return 1;
}
利用 /proc查看进程的用户地址空间
文件映射举例
#include <unistd.h>
#include <sys/mman.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
/* mmap.c - a program that uses mmap to copy itself to stdout */
int main() {
struct stat stat;
int i,fd,size;
char *bufp;
/* open the file & get its size*/
fd = open("./mmap.c",O_RDONLY);
fstat(fd,&stat);
size = stat.st_size;
/* map the file to a new VM area */
bufp = mmap(0,size,PROT_READ,MAP_PRIVATE,fd,0);
/* write the VM area to stdout */
write(1,bufp,size);
}
用户地址空间管理-虚存段 VMA
Linux的用户地址空间分布
? 进程描述符中的 struct mm_struct *mm用来管
理已分配给该进程的线性地址空间
? 每一个连续的线性地址区间由一个
vm_area_struct管理,简称 vma
? mm中的 vma按地址排序由线性链表连接起来,
当 vma的数量相当大的时候启用 avl树,与线性
链表同时管理 vma以提高访问效率
? 随着 vma的动态改变,vma之间存在归并和拆
分等操作
? 分配 vma并不立即分配页帧
? 物理映射区最大为 896M,如果实际物理内存
不足 896M,则该区与实际物理内存大小相等
? 两个隔离区的作用是防止越界造成的致命损害
Linux的内核地址空间分布
物理区 虚存区8~ 16M隔离区 8K隔离区 高端映射区 固定映射区 保留区
线性地址 0xC0000000
Linux的物理内存映射
? 一般情况下,对物理内存的访问可通过预映射
机制方便的实现,Linux保留了 128M的虚拟内
存映射区,因此最多可以有 1G-128M=896M的
物理内存直接映射到 0xC0000000开始的一段
内核空间中
? 当物理内存大于 896M时,超过物理区的那部
分内存称为高端内存,内核在存取高端内存时
必须将它们映射到高端页面映射区,长度为
4M的高端内存映射区正好占用 1帧页表所表示
的物理内存总量,它可以缓冲 1024个高端页面
的映射
0X001000(4K) swapper_pg_dir 核心态访问空间的页目录
0X002000(8K) pg0
0X003000(12K) bad_pages
0X004000(16K) bad_pg_table
0X005000(20K) floppy_track_buffer
0X006000(24K) kernel_code+text
FREE
0X0A0000(640K RESERVED
0X100000(1M) pg_tables(4K)
swap_cache_mem
mem_map
bitmap
FREE
0X000000(0K) Empty_Zero_Page 由 mem_init初始化
初始化后的物理内存分布
物理页管理 (mem_map表 )
typedef struct page { /* 物理页描述字 */
struct page *next,*prev; /* 由于搜索算法的约定,这两项必须首先定义 */
struct inode *inode; /* 若该页帧的内容是文件,则 inode和 offset
unsigned long offset; /* 指出文件的 inode和偏移位置 */
struct page *next_hash;
atomic_t count; /* 访问此页帧的进程记数,大于 1表示由多个进程共享 */
unsigned flags; /* atomic flags,some possibly updated asynchronously */
unsigned dirty:16,/* 页帧修改标志 */
age:8; /* 页帧的年龄,越小越先换出 */
struct wait_queue *wait;
struct page *prev_hash;
struct buffer_head * buffers; /* 若该页帧作为缓冲区,则指示地址 */
unsigned long swap_unlock_entry;
unsigned long map_nr; /* 页帧在 mem_map表中的下标,
page->map_nr == page - mem_map */
} mem_map_t;
mem_map_t * mem_map = NULL; /* 页帧描述表的首地址 */
mem_map_t大小,64字节,故 1M的内存需要 1M/64=16K=4pages的空间存放描述字
空闲物理页管理
?bitmap表
在物理内存低端,紧跟 mem_map表的 bitmap表以位
示图方式记录了所有物理内存的空闲状况。与
mem_map一样,bitmap在系统初始化时由
free_area_init()函数创建 (mm/page_alloc.c)。
与一般性位图不同,bitmap表分割成
NR_MEM_LISTS组。
?buddy算法
将空闲内存管理为一对对的伙伴;( 2^k)页面
大小
伙伴算法与空闲物理页位图
bitmaps:位图, 标识 2^k大
小的内存块对 ( 一对伙伴 )
的状态:
0:都空闲或都忙;如果都
空闲, 在它们作为一块更大
的 空 闲 块 出 现 在 下 一 个
free_area的 bitmaps中 。
1:其中一块忙,一块空闲

内存分配的三种内核接口
? __get_free_pages:从 buddy system中申请大
小为 2^k个 page的连续的物理页帧,分配内核
空间的物理映射区中的一段连续的线性地址
? kmalloc(kfree):从 slab cache中申请连续的物
理内存,通常用于专用对象
? vmalloc(vfree):申请线性地址连续的内存空间,
分配内核空间的虚拟内存映射区中的一段连续
的线性地址,而实际对应的物理地址可以是不
连续的,为检测越界,vmalloc的区域之间是
有空洞的,且为保证效率,页面不会被 swap
out
共享存储
linux的共享存储沿用了 SYSV IPC机制。进程利用共享存储进
行通讯时,先向内核申请一个共享存储段。成功后把它添加
( attach)到自己的地址空间,然后象使用普通存储器一样
使用它。
共享存储限制:
一个共享存储段的大小限制,SHMMAX:4M,SHMMIN:1byte
( 实际是一个 page大 ), 系统中允许的共享存储段的个数限
制,SHMMNI,4096。 系统中允许的共享存储段占总页面数:
SHMALL,2M个页面
这些参数可以由超级用户修改, 如:
# echo 2048 >/proc/sys/kernel/shmmni
共享存储管理系统调用,shmget,shmat,shmdt,shmctl
shm_server.c
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHMSZ 27
main(){
char c; int shmid; key_t key; char *shm,*s;
key = 5678;
if ((shmid = shmget(key,SHMSZ,IPC_CREAT|0666))< 0)
{ perror("shmget"); exit(1); }
if ((shm = shmat(shmid,NULL,0))==(char *)-1)
{ perror("shmat"); exit(1); }
s = shm;
for (c = 'a'; c <= 'z'; c++)
*s++ = c;
*s = NULL;
while (*shm != '*')
sleep(1);
exit(0);
}
Example,IPC using shared memory
shm_client.c
/* shm-client - client program to demonstrate shared memory,*/
#include <sys/types.h>
#include <sys/ipc.h>
#include <sys/shm.h>
#include <stdio.h>
#define SHMSZ 27
main(){
int shmid;
key_t key;
char *shm,*s;
key = 5678;
if ((shmid = shmget(key,SHMSZ,0666)) < 0)
{ perror("shmget"); exit(1); }
if ((shm = shmat(shmid,NULL,0)) == (char *) -1)
{ perror("shmat"); exit(1); }
for (s = shm; *s != NULL; s++)
putchar(*s);
putchar('\n');
*shm = '*';
exit(0);
}