Linux 缺页中断发展历史

详解linux内核-缺页中断处理 而vmalloc出现异常比较好处理,只需要页表同步就可以了(因为伴随着进程的切换可能用户进程的页表不是最新的,需要将内核的页表更新到用户进程的页表),2.内核引用用户空间地址发生的异常,比如用户态的地址非法,或者页面已经被交换到了磁盘。6.一旦页框干净后,操作系统查找所需页面在磁盘上的地址,通过磁盘操作将其装入,当页面被装入后,产生缺页中断的进程仍然被挂起,并且如果有其他可运行的用户进程,则选择另一用户进程运行。首先是find_vma,查找缺页地址所处的vma,vma在用户进程创建的时候分配好的。 阅读详情

慢慢来吧~~


Linux V0.11




缺页中断响应代码:

.globl _page_fault

_page_fault:
	xchgl %eax,(%esp)
	pushl %ecx
	pushl %edx
	push %ds
	push %es
	push %fs
	movl $0x10,%edx
	mov %dx,%ds
	mov %dx,%es
	mov %dx,%fs
	movl %cr2,%edx
	pushl %edx
	pushl %eax
	testl $1,%eax         // 检测当前页的共享位
	jne 1f                   
	call _do_no_page      // 没有共享就缺页
	jmp 2f
1:	call _do_wp_page      // 有共享就是写时复制
2:	addl $8,%esp
	pop %fs
	pop %es
	pop %ds
	popl %edx
	popl %ecx
	popl %eax
	iret

核心接口:

1. 缺页处理接口  do_no_page

void do_no_page(unsigned long error_code,unsigned long address)
{
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	int block,i;

	address &= 0xfffff000;
	tmp = address - current->start_code;
	if (!current->executable || tmp >= current->end_data) {
		get_empty_page(address);
		return;
	}
	if (share_page(tmp))
		return;
	if (!(page = get_free_page()))
		oom();
/* remember that 1 block is used for header */
	block = 1 + tmp/BLOCK_SIZE;
	for (i=0 ; i<4 ; block++,i++)
		nr[i] = bmap(current->executable,block);
	bread_page(page,current->executable->i_dev,nr);
	i = tmp + 4096 - current->end_data;
	tmp = page + 4096;
	while (i-- > 0) {
		tmp--;
		*(char *)tmp = 0;
	}
	if (put_page(page,address))
		return;
	free_page(page);
	oom();
}

流程图



2.写时复制的复制函数: do_wp_page

void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page,new_page;

	old_page = 0xfffff000 & *table_entry;
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
		*table_entry |= 2;
		invalidate();
		return;
	}
	if (!(new_page=get_free_page()))
		oom();
	if (old_page >= LOW_MEM)
		mem_map[MAP_NR(old_page)]--;
	*table_entry = new_page | 7;
	invalidate();
	copy_page(old_page,new_page);
}	

/*
 * This routine handles present pages, when users try to write
 * to a shared page. It is done by copying the page to a new address
 * and decrementing the shared-page counter for the old page.
 *
 * If it's in code space we exit with a segment error.
 */
void do_wp_page(unsigned long error_code,unsigned long address)
{
#if 0
/* we cannot do this yet: the estdio library writes to code space */
/* stupid, stupid. I really want the libc.a from GNU */
	if (CODE_SPACE(address))
		do_exit(SIGSEGV);
#endif
	un_wp_page((unsigned long *)
		(((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc)))));

}

3. 写时复制函数

void write_verify(unsigned long address)
{
	unsigned long page;

	if (!( (page = *((unsigned long *) ((address>>20) & 0xffc)) )&1)) //页表有效
		return;
	page &= 0xfffff000;
	page += ((address>>10) & 0xffc); // 页表偏移
	if ((3 & *(unsigned long *) page) == 1)  /* non-writeable, present */
		un_wp_page((unsigned long *) page);
	return;
}



Linux V0.12

1. 对do_wp_page 添加了参数有效性检测:


void do_wp_page(unsigned long error_code,unsigned long address)
{
	if (address < TASK_SIZE)
		printk("\n\rBAD! KERNEL MEMORY WP-ERR!\n\r");
	if (address - current->start_code > TASK_SIZE) {
		printk("Bad things happen: page error in do_wp_page\n\r");
		do_exit(SIGSEGV);
	}

	un_wp_page((unsigned long *)
		(((address>>10) & 0xffc) + (0xfffff000 &
		*((unsigned long *) ((address>>20) &0xffc)))));

}

2. do_no_page 加入了地址有效性和对虚拟内存的支持:

源码:

void do_no_page(unsigned long error_code,unsigned long address)
{
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	int block,i;
	struct m_inode * inode;

	if (address < TASK_SIZE)
		printk("\n\rBAD!! KERNEL PAGE MISSING\n\r");
	if (address - current->start_code > TASK_SIZE) {
		printk("Bad things happen: nonexistent page error in do_no_page\n\r");
		do_exit(SIGSEGV);
	}
	page = *(unsigned long *) ((address >> 20) & 0xffc);
	if (page & 1) {
		page &= 0xfffff000;
		page += (address >> 10) & 0xffc;
		tmp = *(unsigned long *) page;
		if (tmp && !(1 & tmp)) {
			swap_in((unsigned long *) page);
			return;
		}
	}
	address &= 0xfffff000;
	tmp = address - current->start_code;
	if (tmp >= LIBRARY_OFFSET ) {
		inode = current->library;
		block = 1 + (tmp-LIBRARY_OFFSET) / BLOCK_SIZE;
	} else if (tmp < current->end_data) {
		inode = current->executable;
		block = 1 + tmp / BLOCK_SIZE;
	} else {
		inode = NULL;
		block = 0;
	}
	if (!inode) {
		get_empty_page(address);
		return;
	}
	if (share_page(inode,tmp))
		return;
	if (!(page = get_free_page()))
		oom();
/* remember that 1 block is used for header */
	for (i=0 ; i<4 ; block++,i++)
		nr[i] = bmap(inode,block);
	bread_page(page,inode->i_dev,nr);
	i = tmp + 4096 - current->end_data;
	if (i>4095)
		i = 0;
	tmp = page + 4096;
	while (i-- > 0) {
		tmp--;
		*(char *)tmp = 0;
	}
	if (put_page(page,address))
		return;
	free_page(page);
	oom();
}

流程图:



Linux V0.95

1 . 首先, 缺页中段的响应代码从page.s移除( page.s 不存在了) , 加入到了 kernek/asm.s.  且直接调用do_page_fault

_page_fault:
	pushl $_do_page_fault
	jmp error_code

2. 对与引起缺页中断的原有的判断在 do_page_fault内

/* This routine handles page faults.  It determines the address,
   and the problem then passes it off to one of the appropriate
   routines. */
void do_page_fault (unsigned long *esp, unsigned long error_code)
{
	unsigned long address;
	/* get the address */

	__asm__ ("movl %%cr2,%0":"=r" (address));
	if (!(error_code & 1)) {
		do_no_page(error_code, address, current);
		return;
	} else {
		do_wp_page(error_code, address);
		return;
	}
}

3. 写时复制调用的的接口 un_wp_page 对于内存耗尽的情况做了循环和更多的保护, 不再直接操作page数组的count , 该用free_page 来释放一个引用.

void un_wp_page(unsigned long * table_entry)
{
	unsigned long old_page;
	unsigned long new_page = 0;
	unsigned long dirty;

repeat:
	old_page = *table_entry;
	dirty = old_page & PAGE_DIRTY;
	if (!(old_page & 1)) {
		if (new_page)
			free_page(new_page);
		return;
	}
	old_page &= 0xfffff000;
	if (old_page >= HIGH_MEMORY) {
		if (new_page)
			free_page(new_page);
		printk("bad page address\n\r");
		do_exit(SIGSEGV);
	}
	if (old_page >= LOW_MEM && mem_map[MAP_NR(old_page)]==1) {
		*table_entry |= 2;
		invalidate();
		if (new_page)
			free_page(new_page);
		return;
	}
	if (!new_page) {
		if (!(new_page=get_free_page()))
			oom();                           // 防止 oom 没有推出程序
		goto repeat;                             // 再来一遍
	}
	copy_page(old_page,new_page);
	*table_entry = new_page | dirty | 7 ;
	free_page(old_page);                             // 释放一个引用
	invalidate();
}	

4. do_no_page 加入更多检测合法性的代码.


Linux V0.95a

加入更多的合法性检测

if (*page_table) {
	printk("put_dirty_page: page already exists\n");
	*page_table = 0;
	invalidate();
}

Linux V0.95c

加入一点对进程的操作

例如在do_wp_page中

	++current->min_flt;

Linux V0.96b

将获取空内存页的接口进行了一层包装, 处理了内存不足的问题. 从而像接口  do_no_page 之类的可以不再考虑内存不足.

/*
 * fill in an empty page or directory if none exists
 */
static unsigned long get_empty(unsigned long * p)
{
	unsigned long page = 0;

repeat:
	if (1 & *p) {
		free_page(page);
		return *p;
	}
	if (*p) {
		printk("get_empty: bad page entry \n");
		*p = 0;
	}
	if (page) {
		*p = page | 7;
		return *p;
	}
	if (!(page = get_free_page()))
		oom();
	goto repeat;
}

接口do_no_page

void do_no_page(unsigned long error_code, unsigned long address,
	struct task_struct *tsk, unsigned long user_esp)
{
	static unsigned int last_checked = 0;
	int nr[4];
	unsigned long tmp;
	unsigned long page;
	unsigned int block,i;
	struct inode * inode;

	/* Thrashing ? Make it interruptible, but don't penalize otherwise */
	for (i = 0; i < CHECK_LAST_NR; i++)
		if ((address & 0xfffff000) == last_pages[i]) {
			current->counter = 0;
			schedule();
		}
	last_checked++;
	if (last_checked >= CHECK_LAST_NR)
		last_checked = 0;
	last_pages[last_checked] = address & 0xfffff000;
	if (address < TASK_SIZE) {
		printk("\n\rBAD!! KERNEL PAGE MISSING\n\r");
		do_exit(SIGSEGV);
	}
	if (address - tsk->start_code >= TASK_SIZE) {
		printk("Bad things happen: nonexistent page error in do_no_page\n\r");
		do_exit(SIGSEGV);
	}
	page = get_empty((unsigned long *) ((address >> 20) & 0xffc));                // 这里直接获取新内存页, 不再考虑内存不足
	page &= 0xfffff000;
	page += (address >> 10) & 0xffc;
	tmp = *(unsigned long *) page;
	if (tmp & 1) {
		printk("bogus do_no_page\n");
		return;
	}
	++tsk->rss;
	if (tmp) {
		++tsk->maj_flt;
		swap_in((unsigned long *) page);
		return;
	}
	address &= 0xfffff000;
	tmp = address - tsk->start_code;
	inode = NULL;
	block = 0;
	if (tmp < tsk->end_data) {
		inode = tsk->executable;
		block = 1 + tmp / BLOCK_SIZE;
	} else {
		i = tsk->numlibraries;
		while (i-- > 0) {
			if (tmp < tsk->libraries[i].start)
				continue;
			block = tmp - tsk->libraries[i].start;
			if (block >= tsk->libraries[i].length)
				continue;
			inode = tsk->libraries[i].library;
			block = 1 + block / BLOCK_SIZE;
			break;
		}
	}
	if (!inode) {
		++tsk->min_flt;
		get_empty_page(address);
		if (tsk != current)
			return;
		if (tmp >= LIBRARY_OFFSET || tmp < tsk->brk)
			return;
		if (tmp+8192 >= (user_esp & 0xfffff000))
			return;
		send_sig(SIGSEGV,tsk,1);
		return;
	}
	if (tsk == current)
		if (share_page(inode,tmp)) {
			++tsk->min_flt;
			return;
		}
	++tsk->maj_flt;
	if (!(page = get_free_page()))
		oom();
	for (i=0 ; i<4 ; block++,i++)
		nr[i] = bmap(inode,block);
	bread_page(page,inode->i_dev,nr);
	i = tmp + 4096 - tsk->end_data;
	if (i>4095)
		i = 0;
	tmp = page + 4096;
	while (i--) {
		tmp--;
		*(char *)tmp = 0;
	}
	if (put_page(page,address))
		return;
	free_page(page);
	oom();
}

之后的版本不再默认一级页表总是在地址0

越来越复杂了

简单说说Python中的pip如何换源 【安装包】:pip install 【升级包】:pip install --upgrade 【卸载包】:pip uninstall 【查看已安装的包】:pip list【搜索包】:pip search (注意:pip search功能在pip 10.0.0之后被弃用,建议使用其他方式搜索包,如访问PyPI官网或使用其他搜索引擎) 阅读详情

相关推荐

Plot3d 网格数据格式

PLOT3D是一个计算机图形程序,旨在可视化计算流体力学的网格和解决方案。PLOT3D文件可以是ASCII, 也可是Fortran unformatted 或 C binary形式。PLOT3D文件分为网格文件(XYZ 文件), 空气动力学结果文件 (Q 文件)和通用结果文件(函数文件 + 函数名称文件)。网格文件中可加入所谓的IBlank参数。

weixin_62592231的博客 3677

linux内核研究笔记6(do_wp_page概述)

do_wp_page函数由页保护异常过程调用,用来处理页保护异常。该函数里面直接调用un_wp_page函数。 void un_wp_page(unsigned long * table_entry) { unsigned long old_page,new_page; old_page = 0xfffff000 & *table_entry; if (old_page &gt...

sunxiaohusunke的专栏 1533

《自适应控制与预测控制》_预测控制_zip压缩_

自适应控制与预测控制这本书相关课件,对学习自动控制理论有很大的帮助

linux内存管理~缺页中断

linux内存管理之缺页中断 学习总结

m0_56922987的博客 1615

linux中的get_page函数,linux 0.11内核代码 do_no_page函数

linux 0.11内核代码 do_no_page函数发布时间:2010-07-20 12:51:21来源:红联作者:suowenairdo_no_page (unsigned long error_code, unsigned long address){int nr[4];unsigned long tmp;unsigned long page;int block, i;address &am...

weixin_33542015的博客 575

11.4 写时复制

do_wp_page()函数处理那些用户试图修改pte页表没有可写属性的页面,它新分配一个页面并且复制旧页面内容到新的页面中。 /* * This routine handles present pages, when users try to write * to a shared page. It is done by copying the page to a new address * and decrementing the shared-page counter for the old

Do one thing at a time, and do well. 528

Linux 0.11-缺页中断-37

所以顺利地映射到了物理地址,也就是 /bin/sh 的代码部分(从硬盘加载过来的),那接下来就终于可以执行 /bin/sh 程序,也就是 shell 程序了。可是,128M 这个线性地址并没有页表映射它,也就是因为上面我们说的,我们除了 /bin/sh 文件的头部加载到了内存外,其他部分并没有进行加载操作。但有个问题是,我们仅仅将 /bin/sh 文件的头部加载到了内存,其他部分并没有进行加载,那我们是怎么执行到的 /bin/sh 的程序指令呢?那这个 shell 程序到底是啥呢?

m0_53157173的博客 934

linux内核中断发展史,Linux 缺页中断发展历史

慢慢来吧~~Linux V0.11缺页中断响应代码:.globl _page_fault_page_fault:xchgl %eax,(%esp)pushl %ecxpushl %edxpush %dspush %espush %fsmovl $0x10,%edxmov %dx,%dsmov %dx,%esmov %dx,%fsmovl %cr2,%edxpushl %edxpushl %eaxt...

weixin_39908462的博客 385

零页面机制在缺页中断中的作用

在2.6的早期内核以及更早的2.4,2.2以及1.X内核中有一个empty_zero_page的数组,它是一个全局的页面数组,它的作用很大,要比现在2.6.2X/3X内核中empty_zero_page的重要性大,empty_zero_page的主要作用就是只要用户引用一个只读的匿名页面并没有进行写操作,缺页中断处理中内核就不会给用户进程分配新的页面。零...

weixin_34319374的博客 705

Linux 线程

1.进程是一个执行起来的程序。进程=内核数据结构+代码和数据2.线程:执行流,执行粒度比进程更细。是进程的一个执行分支。进程:承担资源的。——>内存线程:线程是OS调度的。——>内存如果Linux要支持线程,那么线程就必须被OS管理!那么就要描述tcb结构——>可不可以使用PCB 代替tcb。

m0_66802392的博客 949

Linux系统编程:进程地址空间

所谓进程地址空间,本质上就是一个描述进程可视化范围的地址空间内存在各种区域划分,对线性地址进行start、end即可。本质上其实就是一个内核数据结构,和PCB一样,地址空间也是需要被操作系统管理的:先描述再组织。 而每一个进程都有自己的进程地址空间,PCB内部有一个指针指向这块空间!

weixin_51142926的博客 4225

Linux基础进程篇】初谈进程地址空间——初窥进程地址空间的雏形

今天分享的是关于地址空间的理解,此为第一篇,目的是打牢基础篇。一:历史核心问题回顾二:语言层面的地址空间2.1:了解2.2:验证三:系统层面的地址空间3.1:引入新概念,初步理解这种现象——引入地址空间的概念3.2:加深粒度再来理解上面父子进程同意变量的现象四:谈细节4.1:地址空间究竟是什么4.2:为什么要有进程地址空间4.3:页表4.3.1:CR3寄存器4.3.2:页表项4.3.3:缺页中断五:结语。

2302_78684687的博客 1325

Linux取经路】初探进程地址空间

本篇文章初步的介绍了进程地址空间的相关概念,其中包括如何理解进程地址空间,为什么要有进程地址空间、以及也标的一些相关概念。

春人.的博客 6354

Linux 进程地址空间

本文深入探讨了Linux进程地址空间机制。通过全局变量gval的写时拷贝问题,阐述了进程地址空间的本质:每个进程拥有独立的虚拟地址空间(32位4GB,64位更大),通过页表映射到物理内存。重点分析了写时拷贝技术如何实现进程独立性,以及mm_struct数据结构如何管理虚拟地址空间的区域划分。文章还解释了虚拟地址空间的五大优势:使内存访问有序化、提供权限保护、防止野指针、支持动态加载、实现进程管理解耦。最后介绍了vm_area_struct结构体如何管理离散的堆空间,以及进程独立性实现的根本原理。整个机制体现

2301_79976271的博客 727

linux history 分页,Linux内存管理解析(一) : 分段与分页机制

背景 : 在此文章里会从分页分段机制去解析Linux内存管理系统如何工作的,由于Linux内存管理过于复杂而本人能力有限。会尽量将自己总结归纳的部分写清晰。从实模式到保护模式的寻址方式的不同 :16位CPU的寻址方式 : 在 8086 CPU 中,提供了两类寄存器来进行寻址,分别为段寄存器(例如 CS,DS,SS)和段偏移寄存器(例如 SI,DI,SP)。而这几种寄存器的长度都为16bit,寻址方...

weixin_39835117的博客 263

linux下内存 历史遗留,内存分配的原理

内存分配的原理__Linux虚拟内存管理(glibc)_Linux的虚拟内存管理有几个关键概念_Linux虚拟地址空间如何分布_malloc和free是如何分配和释放内存_如何查看堆内内存的碎片情况_既然堆内内存brk和sbrk不能直接释放,为什么不全部使用 mmap 来分配,munmap直接释放呢Linux虚拟内存管理(glibc)在使用mysql作为DB开发的兑换券系统中,随着分区表的不断创建...

weixin_39588206的博客 195

linux内核研究笔记4(do_wp_page参数)

内核版本:linux 0.11 函数定义:memory.c第247行 void do_wp_page(unsigned long error_code,unsigned long address) { #if 0 /* we cannot do this yet: the estdio library writes to code space */ /* stupid, stupid. I ...

sunxiaohusunke的专栏 1432

Linux0.11 缺页处理(三)

页异常中断处理程序(中断 14),主要分两种情况处理。一是由于缺页引起的页异常中断,通过调用 do_no_page(error_code, address) 来处理;二是由页写保护引起的页异常,此时调用页写保护处理函数 do_wp_page(error_code, address) 进行处理。其中的出错码(error_code)是由 CPU 自动产生并压入堆栈的,出现异常时访问的线性地址是从控制寄存器 CR2 中取得的。CR2 是专门用来存放页出错时的线性地址。

Liuqz2009的专栏 1211

linux进程地址空间(2) 缺页异常详解(3)写时复制COW详解

接上一篇 现在分析写时复制COW,对于写时复制,首先把握一点就是只有写操作时才有可能触发写时复制,所以首先总要判断异常flag是否含有标志FAULT_FLAG_WRITE,然后判断二级页表条目值是否含有L_PTE_WRITE标志,这是意味着这个物理页是否可写,如果不可写则说明应该进入写时复制流程,调用处理函数do_wp_page; 可见,COW的应用场合就是访问映射的页不可写,它包括两种情况,

u010246947的专栏 3477

简单的页异常流程

本文是基于Linux0.11源码来叙述该功能 取之于互联网 用之于互联网 页异常中断处理程序(中断 14),主要分两种情况处理。 是由于缺页引起的页异常中断, 这需要通过调用 do_no_page(error_code, address)来处理; 是由于页面写保护引起的页异常,此时调用页写保护处理函数 do_wp_page(error_code, address)进行处理。 函数参数中的出错码(error_code)是由CPU 自动产生并被压入堆栈的,出现异常时访问的线性地址是从控制寄存器 CR2

老鹰不捉小鸡 930

linux内核研究笔记2(关于asm.s中call函数的说明)

asm.s程序第39行中(以下片段第21行处),有个call函数调用代码,如下: _divide_error: pushl $_do_divide_error no_error_code: xchgl %eax,(%esp) pushl %ebx pushl %ecx pushl %edx pushl %edi pushl %esi pushl %ebp push %ds ...

sunxiaohusunke的专栏 504
上一篇: Linux V0.11 源码解析 读书笔记
下一篇: Linux 虚拟内存发展历史 个人笔记
_RTFSC
博客等级 码龄15年 29粉丝 92原创
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值