数据管理(一)

在前面的几章中,我们涉及到了资源限制的主题。在这一章,我们将会讨论管理我们资源分配的方法,然后讨论多个用户连续处理文件的方法,最后我们来讨论Linux系统所提供的工具如何处理将普通文件的限制用作一个数据存储介质的问题。

我们可以数据管理总结为如下三个方面:

动态内存管理:要做些什么而Linux不允许我们做什么
文件锁:协作锁,共享文件锁区域,以及避免死锁
dbm数据:一个基本的,大多数Linux系统中所提供的非基于SQL数据库的函数库

管理内存

在所有的计算机系统中内存是稀有资源。不论有多少内存可用,看起来都会显得不足。再也不是以前那种情况了:可以寻址1M内存被认为对于所有人来说都是足够的,然而现在512M的内存已成了最低配置了。

由操作系统的最早版本开始,Unix风格的操作系统就具有一个非常清晰的方法来管理内存,Linux也是如此,因为他实现了X/Open规范。Linux程序,除了一些特殊的嵌入式程序除外,绝不允许直接访问物理内存。程序所看到的只是一个被小心管理的场景。

Linux使用一个清晰的直接寻址的内存空间来提供程序。另外,他提供了保护,所以不同的程序之间彼此会被保护,而且在机器内部允许程序透明的访问比实际的物理内存大得多的内存空间,只要机器很好的进行了配置而且有足够的交换空间。

简单的内存分配

我们在标准的C库中使用malloc调用来分配内存。

#include <stdlib.h>
void *malloc(size_t size);

注意,Linux(遵循X/Open规范)不同于某些Unix实现,因为他并不需要一个特定的malloc.h包含文件。另外要注意就是,指定要分配的字节数的size参数并不是int,尽管他通常是一个无符号整形。

在绝大多数的Linux系统上,我们可以分配大量的内存。让我们由一个非常简单的程序开始,但是这并不适用于老的基于MS-DOS的程序,因为他们并不允许访问超出PC的640K的内存。

试验--简单的内存分配

输入下面的程序,memory1.c:

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define A_MEGABYTE (1024 * 1024)
int main()
{
char *some_memory;
int megabyte = A_MEGABYTE;
int exit_code = EXIT_FAILURE;
some_memory = (char *)malloc(megabyte);
if (some_memory != NULL) {
sprintf(some_memory, “Hello World\n”);
printf(“%s”, some_memory);
exit_code = EXIT_SUCCESS;
}
exit(exit_code);
}

当我们运行这个程序时,其输出结果如下:

$ ./memory1
Hello World

工作原理

这个程序要求malloc库为其指定一个到1M内存的指针。我们要进行检测以确保malloc函数调用成功,然后使用其中的一部分内存来演示其存在。当我们运行这个程序时,我们应可以看到打印出Hello World字符串,从而表示malloc确实返回了可用的兆字节空间。我们并没有检测所分配的全部字节空间,我们需要信任malloc代码!

注意,因为malloc返回一个void *指针,我们将其转换成我们所需要的char *类型。malloc函数确保返回对齐的内存空间,从而可以将其转换为任何类型的指针。

简单的原因在于绝大多数的Linux系统使用32位整数以及使用32位指针来指向内存,这允许我们最多可以指定4GB的内存空间。使用32位指针,而不需要段寄存器或是其他技巧来直接寻址的技术就被称之为普通32位内存模式。这种模式也用于Windows XP以及Windows 9x/Me的32程序。我们不应依赖于整数是32位的,因为也存在正在使用的Linux的64位版本。

分配大内存

现在我们已经看到Linux已经超出了MS-DOS内存模式的限制,下面我们会提出一个更为困难的问题。在下面的程序中,我们会要求分配大于机器中实际物理内存大小的内存空间,所以我们会期望malloc函数会由于实际的内存数量而失败,因为内核以及其他运行的进程都会占用一定的内存。

试验--要求所有的物理内存

在memory2.c中,我们会要求分配大于机器实际内存数量的内存。也许我们需要依据我们实际的物理内存数量来调整PHY_MEM_MEGS值。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define A_MEGABYTE (1024 * 1024)
#define PHY_MEM_MEGS 256 /* Adjust this number as required */
int main()
{
char *some_memory;
size_t size_to_allocate = A_MEGABYTE;
int megs_obtained = 0;
while (megs_obtained < (PHY_MEM_MEGS * 2)) {
some_memory = (char *)malloc(size_to_allocate);
if (some_memory != NULL) {
megs_obtained++;
sprintf(some_memory, “Hello World”);
printf(“%s - now allocated %d Megabytes\n”, some_memory,
megs_obtained);
}
else {
exit(EXIT_FAILURE);
}
}
exit(EXIT_SUCCESS);
}

程序会产生类似于下面的输出:

$ ./memory2
Hello World - now allocated 1 Megabytes
Hello World - now allocated 2 Megabytes
...
Hello World - now allocated 511 Megabytes
Hello World - now allocated 512 Megabytes

工作原理

这个程序非常类似于前面的例子。他只是进行简单的循环,要求分配越来越多的内存。令人惊奇的是他仍然可以正常工作,因为我们创建了一个会使用所有物理的程序。注意,在这里我们对于malloc调用使用了size_t类型。

另一个有趣的特点在于,至少在我们试验的这个机器上是这样,运行这个程序时屏幕会闪动。所以我们不仅是使用了全部的内存,而且我们可以很快的完成这些所要求的工作。

下面我们来探讨这个特性,并且我们会看一下在memory3.c中我们可以分配多少内存。现在我们很清楚的是Linux可以很聪明的处理内存请求的事情,现在我们每次只分配1K内存,并且写入我们所获得的每块内存。

试验--可用内存

下面是memory3.c的内容。很自然的,对于系统而言,这个程序是极其不友好的,而且会严重影响一个多用户的机器。如果我们没有意识到这个风险,那么最好是不要运行这个程序;如果我们不运行也并不会影响我们对于这个程序的理解。

#include <unistd.h>
#include <stdlib.h>
#include <stdio.h>
#define ONE_K (1024)
int main()
{
char *some_memory;
int size_to_allocate = ONE_K;
int megs_obtained = 0;
int ks_obtained = 0;
while (1) {
for (ks_obtained = 0; ks_obtained < 1024; ks_obtained++) {
some_memory = (char *)malloc(size_to_allocate);
if (some_memory == NULL) exit(EXIT_FAILURE);
sprintf(some_memory, “Hello World”);
}
megs_obtained++;
printf(“Now allocated %d Megabytes\n”, megs_obtained);
}
exit(EXIT_SUCCESS);
}

这一次程序的输出结果如下所示:

$ ./memory3
Now allocated 1 Megabytes
...
Now allocated 377 Megabytes
Now allocated 378 Megabytes
Out of Memory: Killed process 2365
Killed

而这一次程序结束了。程序的运行也只是花费了几秒,而且在接近机器实际物理内存的时候会慢下来,而且很明显的使用硬盘。然而,程序已经分配了大于机器中实际物理内存的内存数量。最后系统阻止这个有侵害性的程序并且将其杀掉。在一些系统上,程序只会在malloc调用失败时简单的退出。

工作原理

程序所分配的内存是由Linux内核所管理。每次程序要求分配内存并且试着对所分配的内存进行读写操作,Linux内核会接过管理权并且决定如何处理这些请求。

最初,内核只是简单的使用空闲的物理内存来满足程序的内存分配请求,但是一旦内存用光,他就会使用所谓的交换空间。在Linux系统上,这个是一个系统安装时所分配的独立的磁盘空间。如果我们熟悉Windows,Linux交换空间的作用有点类似于隐藏的Windows交换文件。然而,与Windows不同的是,在代码中我们并不需要担心局部堆,全局堆,或是可丢弃的内存段--Linux内核会替我们管理这些。

内核会在物理内存和交换空间中移动数据与程序代码,从而每次我们读写内存时,数据看起来都像是在物理内存中一样,而实际上,这些数据是在我们试图访问他们之前进行定位的。

更为深入的来说,Linux实现了一个虚拟页面内存系统。所有由用户程序员所看到的内存都是虚拟的;也就是说,他并不实际存在于用户所用的物理地址中。Linux将所有的内存划分为页,通常一页为4096字节。当一个程序试图访问内存时,就会进行一个虚拟内存到物理内存的转换,尽管其实现以及其所需要的时间依赖于我们所使用的实际硬件。当所访问的内存并不是实际的物理内存时,就会产生页面失效,并且将控制权传递给内核。

Linux内核会检测正在访问的地址,如果他是一个合法的程序地址,内核就会确定哪一页物理内存是可以访问的。然后内核会分配内存,如果以前并没有向此页内存写入任何数据,或者,此页内存存在于交换空间的磁盘中,就会将包含相应数据的内存页面读入到物理内存中(也许需要将一页移出磁盘)。然后,在映射虚拟内存地址来匹配物理地址之后,内核允许用户程序继续运行。Linux程序并不需要担心这些活动,因为这些实现完全隐藏在内核中。

事实上,当程序用尽物理内存与交换空间,或进超出最大的堆栈尺寸时,内核最后就会拒绝额外的内存申请,并且也许会预先结束这个程序。

所以这对于程序的编写者来说意味着什么呢?最基本的,所有的都是好消息。Linux十分善于管理内存,并且允许程序使用大容量的内存甚至是大块的单独内存空间。然而,我们需要清楚的就是分配两块内存并不会得到一块连续的内存地址。我们所得到的就是我们所请求的:两块单独的内存空间。

那么这样是否显得对于内存的支持有限,而杀死进程是否意味着没有必要检测malloc的返回结果呢?当然不是。在C程序中使用动态分配内存最经常遇到的一个问题是在超出所分配内存的空间写入数据。当出现这种情况时,程序并不会立即退出,但是我们很可能已经覆盖了malloc库例程内部所使用的某些数据。

通常,malloc的调用结果会失败,原因并不在于没有内存可供分配,而是因为内存结构已经被破坏。这些问题很难被追踪,而且在程序,越早检测到错误,就会有更好的机会来追踪原因。在第10章,调试与优化,我们会讨论一些工具可以帮助我们追踪内存问题。

滥用内存

假设我们试图使用内存做些坏事。在下面的程序memory4.c中,我们分配一块内存,然后试图在超出内存结束位置的地方写一些数据。

试验--滥用内存

#include <stdlib.h>
#define ONE_K (1024)
int main()
{
char *some_memory;
char *scan_ptr;
some_memory = (char *)malloc(ONE_K);
if (some_memory == NULL) exit(EXIT_FAILURE);
scan_ptr = some_memory;
while(1) {
*scan_ptr = ‘\0’;
scan_ptr++;
}
exit(EXIT_SUCCESS);
}

其输出结果如下:

$ /memory4
Segmentation fault (core dumped)

工作原理


Linux内存管理系统会保护系统的其余部分不受内存滥用的影响。为了保证一个行为奇怪的程序不会损害其他程序,Linux会结束这个程序。

在Linux系统上运行的第一个程序只会看到其自己的内存映射,这与其他程序的内存映射是不同的。只有操作系统知道物理内存是如何安排,并且不仅为用户程序管理内存,而且会在用户程序之间起到保护的作用。

你可能感兴趣的:(数据结构,linux,虚拟机,windows,配置管理)