linux缺页中断源码分析(基于linux0.11)

2024-03-27 21:38

本文主要是介绍linux缺页中断源码分析(基于linux0.11),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

缺页中断发生在系统对虚拟地址转换成物理地址的过程中。如果对应的页目录或者页表项没有对应有效的物理内存,则会发生缺页中断。

系统在初始化的时候注册了缺页中断的处理函数。中断号是14。

// 缺页和写保护异常处理函数set_trap_gate(14,&page_fault);

page_fault是汇编实现的。

_page_fault:// 交换两个寄存器的值,esp指向的位置保存了错误码xchgl %eax,(%esp)// 压栈寄存器pushl %ecxpushl %edxpush %dspush %espush %fs// 内核数据段描述符movl $0x10,%edxmov %dx,%dsmov %dx,%esmov %dx,%fs// 如果是缺页异常,cr2保存了引起缺页的线性地址movl %cr2,%edx// 线性地址(有的话)和错误码入参pushl %edxpushl %eax// 1和eax与,结果放到ZF中testl $1,%eax// zf=0则跳转,即0是写异常,1是缺页异常jne 1fcall _do_no_page// 跳到标签2jmp 2f
1:	call _do_wp_page
// 出栈,返回中断,会重新执行异常指令
2:	addl $8,%esppop %fspop %espop %dspopl %edxpopl %ecxpopl %eaxiret

缺页中断的具体处理函数是。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;// 取得线性地址对应页的页首地址,与0xfffff000即减去页偏移 address &= 0xfffff000;// 算出离代码段首地址的偏移tmp = address - current->start_code;// tmp大于等于end_data说明是访问堆或者栈的空间时发生的缺页,直接申请一页if (!current->executable || tmp >= current->end_data) {get_empty_page(address);return;}// 是否有进程已经使用了if (share_page(tmp))return;// 获取一页,4kbif (!(page = get_free_page()))oom();
/* remember that 1 block is used for header *//*算出要读的硬盘块号,但是最多读四块。tmp/BLOCK_SIZE算出线性地址对应页的页首地址离代码块距离了多少块,然后读取页首地址对应的块号,所以需要加一。比如距离2块的距离,则需要读取的块是第三块*/block = 1 + tmp/BLOCK_SIZE;// 查找文件前4块对应的硬盘号for (i=0 ; i<4 ; block++,i++)// bmap算出逻辑块号对应的物理块号nr[i] = bmap(current->executable,block);// 从硬盘读四块数据进来,并且复制到物理页中bread_page(page,current->executable->i_dev,nr);/*tmp是小于end_data的,因为从tmp开始加载了4kb的数据,所以tmp+4kb(4096)后大于end_data,所以大于的部分需要清0,i即超出的字节数*/i = tmp + 4096 - current->end_data;// page是物理页首地址,加上4kb,从后往前清0tmp = page + 4096;while (i-- > 0) {tmp--;*(char *)tmp = 0;}// 建立线性地址和物理地址的映射if (put_page(page,address))return;// 失败则是否刚才申请的物理页free_page(page);oom();
}

1 如果缺页的是堆、栈的空间,则直接分配一页新的物理地址。

// 给address分配一个新的页,并且把页对应的物理地址存储在页面项中
void get_empty_page(unsigned long address)
{unsigned long tmp;if (!(tmp=get_free_page()) || !put_page(tmp,address)) {free_page(tmp);		/* 0 is ok - ignored */oom();}
}
/** This function puts a page in memory at the wanted address.* It returns the physical address of the page gotten, 0 if* out of memory (either when trying to access page-table or* page.)*/
// page是物理地址,address是线性地址。建立物理地址和线性地址的关联,即给页表和页目录项赋值
unsigned long put_page(unsigned long page,unsigned long address)
{unsigned long tmp, *page_table;/* NOTE !!! This uses the fact that _pg_dir=0 */if (page < LOW_MEM || page >= HIGH_MEMORY)printk("Trying to put page %p at %p\n",page,address);// page对应的物理页面没有被分配则说明有问题if (mem_map[(page-LOW_MEM)>>12] != 1)printk("mem_map disagrees with %p at %p\n",page,address);// 计算页目录项的偏移地址,页目录首地址再物理地址0处。这里算出偏移地址后,就是绝对地址,与0xffc即四字节对齐page_table = (unsigned long *) ((address>>20) & 0xffc);// 页目录项已经指向了一个有效的页表if ((*page_table)&1)// 算出页表首地址,*page_table的高20位是有效地址page_table = (unsigned long *) (0xfffff000 & *page_table);else {// 页目录项还没有指向有效的页表,分配一个新的物理页if (!(tmp=get_free_page()))return 0;// 把页表地址写到页目录项,tmp为页表的物理地址,或7代表页面是用户级、可读、写、执行、有效*page_table = tmp|7;// 页目录项指向页表的物理地址page_table = (unsigned long *) tmp;}/* address是32位,右移12为变成20位,再与3ff就是取得低10位,即address在页表中的索引,或7代表该页面是用户级、可读、写、执行、有效*/page_table[(address>>12) & 0x3ff] = page | 7;
/* no need for invalidate */// 返回线性地址return page;
}

2 否则先判断是否有另一个进程和当前进程使用了同一个执行文件。是的话,则判断是否可以共享。

/** share_page() tries to find a process that could share a page with* the current one. Address is the address of the wanted page relative* to the current data space.** We first check if it is at all feasible by checking executable->i_count.* It should be >1 if there are other tasks sharing this inode.*/
// 判断有没有多个进程执行了同一个可执行文件
static int share_page(unsigned long address)
{struct task_struct ** p;if (!current->executable)return 0;// 只有当前进程使用这个可执行文件则返回if (current->executable->i_count < 2)return 0;for (p = &LAST_TASK ; p > &FIRST_TASK ; --p) {if (!*p)continue;if (current == *p)continue;if ((*p)->executable != current->executable)continue;// 找到一个不是当前进程,但都执行了同一个可执行文件的进程if (try_to_share(address,*p))return 1;}return 0;
}
/** try_to_share() checks the page at address "address" in the task "p",* to see if it exists, and if it is clean. If so, share it with the current* task.** NOTE! This assumes we have checked that p != current, and that they* share the same executable.*/
// 使得另一个进程的页目录和页表项指向另一个进程的正在使用的物理地址
static int try_to_share(unsigned long address, struct task_struct * p)
{unsigned long from;unsigned long to;unsigned long from_page;unsigned long to_page;unsigned long phys_addr;/*address是距离start_code的偏移。这里算出这个距离跨了多少个页目录项,然后加上start_code的页目录偏移就得到address在页目录里的绝对偏移*/from_page = to_page = ((address>>20) & 0xffc);// p进程的代码开始地址(线性地址),取得p进程的页目录项地址,再加上address算出的偏移from_page += ((p->start_code>>20) & 0xffc);// 取得当前进程的页目录项地址,页目录物理地址是0,所以这里就是该地址对应的页目录项的物理地址to_page += ((current->start_code>>20) & 0xffc);
/* is there a page-directory at from? */// from是页表的物理地址和标记位from = *(unsigned long *) from_page;// 没有指向有效的页表则返回if (!(from & 1))return 0;// 取出页表地址from &= 0xfffff000;// 算出address对应的页表项地址,((address>>10) & 0xffc)算出页表项偏移,0xffc说明是4字节对齐from_page = from + ((address>>10) & 0xffc);// 页表项的内容,包括物理地址和标记位信息phys_addr = *(unsigned long *) from_page;
/* is the page clean and present? */// 是否有效和是否是脏的,如果不是有效并且干净的则返回if ((phys_addr & 0x41) != 0x01)return 0;// 取出物理地址的页首地址phys_addr &= 0xfffff000;if (phys_addr >= HIGH_MEMORY || phys_addr < LOW_MEM)return 0;// 目的页目录项内容to = *(unsigned long *) to_page;// 目的页目录项是否指向有效的页表if (!(to & 1))// 没有则新分配一页,并初始化标记位if (to = get_free_page())*(unsigned long *) to_page = to | 7;elseoom();// 取得页表地址to &= 0xfffff000;// 取得address对应的页表项地址to_page = to + ((address>>10) & 0xffc);// 是否指向了有效的物理页,是的话说明不需要再建立线性地址到物理地址的映射了if (1 & *(unsigned long *) to_page)panic("try_to_share: to_page already exists");
/* share them: write-protect */// 标记位不可写*(unsigned long *) from_page &= ~2;// 把address对应的源页表项内容复制到目的页表项中*(unsigned long *) to_page = *(unsigned long *) from_page;// 使tlb失效invalidate();// 算出页数,物理页引用数加一phys_addr -= LOW_MEM;phys_addr >>= 12;mem_map[phys_addr]++;return 1;
}

3 1,2都不满足,则到硬盘中把一页内容加载到内存中,并且修改页表项内容。
最后重新执行触发缺页中断的地址。这时候可以找到对应的内容了。

这篇关于linux缺页中断源码分析(基于linux0.11)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/853468

相关文章

Linux之systemV共享内存方式

《Linux之systemV共享内存方式》:本文主要介绍Linux之systemV共享内存方式,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录一、工作原理二、系统调用接口1、申请共享内存(一)key的获取(二)共享内存的申请2、将共享内存段连接到进程地址空间3、将

Python中的Walrus运算符分析示例详解

《Python中的Walrus运算符分析示例详解》Python中的Walrus运算符(:=)是Python3.8引入的一个新特性,允许在表达式中同时赋值和返回值,它的核心作用是减少重复计算,提升代码简... 目录1. 在循环中避免重复计算2. 在条件判断中同时赋值变量3. 在列表推导式或字典推导式中简化逻辑

快速修复一个Panic的Linux内核的技巧

《快速修复一个Panic的Linux内核的技巧》Linux系统中运行了不当的mkinitcpio操作导致内核文件不能正常工作,重启的时候,内核启动中止于Panic状态,该怎么解决这个问题呢?下面我们就... 感谢China编程(www.chinasem.cn)网友 鸢一雨音 的投稿写这篇文章是有原因的。为了配置完

Linux命令之firewalld的用法

《Linux命令之firewalld的用法》:本文主要介绍Linux命令之firewalld的用法,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux命令之firewalld1、程序包2、启动firewalld3、配置文件4、firewalld规则定义的九大

Java程序进程起来了但是不打印日志的原因分析

《Java程序进程起来了但是不打印日志的原因分析》:本文主要介绍Java程序进程起来了但是不打印日志的原因分析,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录Java程序进程起来了但是不打印日志的原因1、日志配置问题2、日志文件权限问题3、日志文件路径问题4、程序

Linux之计划任务和调度命令at/cron详解

《Linux之计划任务和调度命令at/cron详解》:本文主要介绍Linux之计划任务和调度命令at/cron的使用,具有很好的参考价值,希望对大家有所帮助,如有错误或未考虑完全的地方,望不吝赐教... 目录linux计划任务和调度命令at/cron一、计划任务二、命令{at}介绍三、命令语法及功能 :at

Linux下如何使用C++获取硬件信息

《Linux下如何使用C++获取硬件信息》这篇文章主要为大家详细介绍了如何使用C++实现获取CPU,主板,磁盘,BIOS信息等硬件信息,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录方法获取CPU信息:读取"/proc/cpuinfo"文件获取磁盘信息:读取"/proc/diskstats"文

Java 正则表达式URL 匹配与源码全解析

《Java正则表达式URL匹配与源码全解析》在Web应用开发中,我们经常需要对URL进行格式验证,今天我们结合Java的Pattern和Matcher类,深入理解正则表达式在实际应用中... 目录1.正则表达式分解:2. 添加域名匹配 (2)3. 添加路径和查询参数匹配 (3) 4. 最终优化版本5.设计思

Java字符串操作技巧之语法、示例与应用场景分析

《Java字符串操作技巧之语法、示例与应用场景分析》在Java算法题和日常开发中,字符串处理是必备的核心技能,本文全面梳理Java中字符串的常用操作语法,结合代码示例、应用场景和避坑指南,可快速掌握字... 目录引言1. 基础操作1.1 创建字符串1.2 获取长度1.3 访问字符2. 字符串处理2.1 子字

Linux内核参数配置与验证详细指南

《Linux内核参数配置与验证详细指南》在Linux系统运维和性能优化中,内核参数(sysctl)的配置至关重要,本文主要来聊聊如何配置与验证这些Linux内核参数,希望对大家有一定的帮助... 目录1. 引言2. 内核参数的作用3. 如何设置内核参数3.1 临时设置(重启失效)3.2 永久设置(重启仍生效