当前位置:  开发笔记 > 编程语言 > 正文

操作系统通常如何管理内核内存和页面处理?

如何解决《操作系统通常如何管理内核内存和页面处理?》经验,为你挑选了2个好方法。

我正在研究内核设计,我有一些关于分页的问题.

到目前为止我的基本想法是:每个程序都有自己的(或者它认为)4G内存,减去我保留的程序可以调用的内核函数的某个部分.因此,操作系统需要找出一些方法来加载程序在操作期间需要使用的内存中的页面.

现在,假设我们拥有无限的内存和处理器时间,我可以加载/分配程序写入或读取的任何页面,因为它使用页面错误来查找不存在(或被换出)的页面,因此操作系统可以快速分配它们或交换它们.但在现实世界中,我需要优化这个过程,这样我们就不会有一个程序不断消耗它所触及的所有内存.

所以我想我的问题是,操作系统通常如何解决这个问题?我最初的想法是创建一个程序调用set/free页面的函数,然后它可以自己进行内存管理,但程序通常是这样做的,还是编译器认为它有自由的统治?此外,编译器如何处理需要分配相当大的内存段的情况?我是否需要提供一个试图按顺序为其提供X页的功能?

这显然不是一个特定于语言的问题,但我偏爱标准C并且对C++很好,所以我希望任何代码示例都在那个或汇编中.(程序集不应该是必需的,我完全打算使用尽可能多的C代码,并作为最后一步进行优化.)

另一件事应该更容易回答:一般如何处理程序需要调用的内核函数?只有拥有一组内存区域(我正在考虑虚拟空间的末尾),它包含程序可以调用的大多数基本功能/进程特定内存吗?我的想法就是让内核函数做一些非常花哨的东西并将页面交换出来(这样程序在自己的空间中看不到敏感的内核函数)当程序需要做任何重要的事情,但我不是真的在这一点上关注安全性.

所以我想我比一般的设计理念更担心.我想让内核与GCC完全兼容(不知何故),我需要确保它提供了普通程序所需的一切.

谢谢你的建议.



1> CesarB..:

所有这些问题的一个很好的起点是看看Unix是如何做到的.正如一句名言所说的那样,"那些不了解UNIX的人注定要重新发明它,不好."

首先,关于调用内核函数.仅仅具有程序可以调用的函数是不够的,因为程序很可能以"用户模式"运行(IA-32上的响铃3)并且内核必须以"内核模式"运行(通常是0在IA-32上进行其特权行动.你必须以某种方式在两种模式之间进行转换,这是非常特定于体系结构的.

在IA-32上,传统的方法是使用IDT中的门和软件中断(Linux使用int 0x80).较新的处理器有其他(更快)的方法来实现,哪些可用取决于CPU是来自AMD还是Intel,以及特定的CPU型号.为了适应这种变化,最近的Linux内核在每个进程的地址空间顶部使用由内核映射的代码页.因此,在最近的Linux上,要进行系统调用,您可以在此页面上调用一个函数,该函数将执行切换到内核模式所需的任何操作(内核具有该页面的多个副本,并选择要使用的副本)在启动时取决于处理器的功能).

现在,内存管理.这是一个很大的主题; 你可以写一本关于它的大书而不是夸大这个主题.

请务必记住,至少有两个内存视图:物理视图(页面的实际顺序,硬件内存子系统和外部外围设备可见)和逻辑视图(页面顺序)在CPU上运行的程序看到).两者都很容易混淆.您将分配物理页面并将它们分配给程序或内核地址空间上的逻辑地址.单个物理页面可以具有多个逻辑地址,并且可以映射到不同进程中的不同逻辑地址.

内核内存(为内核保留)通常映射在每个进程的地址空间的顶部.但是,它被设置为只能在内核模式下进行访问.没有必要使用花哨的技巧来隐藏那部分内存; 硬件完成阻止访问的所有工作(在IA-32上,它通过页面标志或段限制完成).

程序以几种方式在地址空间的其余部分上分配内存:

部分内存由内核的程序加载器分配.这包括程序代码(或"文本"),程序初始化数据("数据"),程序未初始化数据("bss",零填充),堆栈以及若干赔率和结束.从要加载的可执行文件的标题中读取要分配的内容,在哪里,应该是什么初始内容,要使用哪些保护标志以及其他一些内容.

传统上在Unix上,存在可以增长和缩小的内存区域(其上限可以通过brk()系统调用来改变).这通常由堆使用(C库上的内存分配器,其中malloc()一个接口,负责堆).

您经常可以要求内核将文件映射到地址空间区域.对该区域的读取和写入(通过分页魔术)指向后备文件.这通常被称为mmap().使用匿名mmap,您可以分配地址空间的新区域,这些区域不受任何文件的支持,但是以相同的方式操作.内核的程序加载器通常用于mmap分配程序代码的一部分(例如,程序代码可以由可执行程序本身支持).

未以任何方式分配(或为内核保留)的地址空间的访问区域被认为是错误,并且在Unix上将导致信号被发送到程序.

编译器静态地分配内存(通过在可执行文件头上指定它;内核的程序加载器将在加载程序时分配内存)或动态地(通过调用语言的标准库上的函数,通常然后调用该函数) C语言标准库,然后调用内核来分配内存并在必要时对其进行细分.

学习所有这些基础知识的最佳方法是阅读有关操作系统的几本书中的一本,特别是那些使用Unix变体的书.它将比StackOverflow上的答案更详细.



2> Adam Rosenfi..:

这个问题的答案是高度依赖于架构的.我假设你在谈论x86.对于x86,内核通常提供一组系统调用,这些调用是进入内核的预定入口点.用户代码只能在这些特定点进入内核,因此内核可以仔细控制它与用户代码的交互方式.

在x86中,有两种方法可以实现系统调用:使用中断,以及使用sysenter/sysexit指令.通过中断,内核设置了一个中断描述符表(IDT),它定义了进入内核的可能入口点.然后,用户代码可以使用该int指令生成一个软中断来调用内核.中断也可以由硬件产生(所谓的硬中断); 这些中断通常应该与软中断不同,但它们并非必须如此.

sysenter和sysexit指令是执行系统调用的更快方法,因为处理中断很慢; 我对使用它们并不熟悉,所以我无法评论它们是否是适合您情况的更好选择.

无论您使用哪种方式,都必须定义系统调用接口.您可能希望在寄存器中而不是在堆栈中传递系统调用参数,因为生成中断将导致您将堆栈切换到内核堆栈.这意味着您几乎肯定必须在用户模式端编写一些汇编语言存根以进行系统调用,并再次在内核端收集系统调用参数并保存寄存器.

完成所有这些后,您就可以开始考虑处理页面错误了.页面错误实际上只是另一种类型的中断 - 当用户代码尝试访问没有页表项的虚拟地址时,它将生成中断14,并且您还将错误地址作为错误代码.内核可以获取此信息,然后决定从磁盘读取丢失的页面,添加页表映射,然后跳回用户代码.

我强烈建议您查看MIT操作系统课程中的一些材料.查看参考部分,它有很多好东西.

推荐阅读
手机用户2502852037
这个屌丝很懒,什么也没留下!
DevBox开发工具箱 | 专业的在线开发工具网站    京公网安备 11010802040832号  |  京ICP备19059560号-6
Copyright © 1998 - 2020 DevBox.CN. All Rights Reserved devBox.cn 开发工具箱 版权所有