|
| Livid's Paranoid - shuffle |
|
| life is random |
|
| Modern Memory Management |
|
http://www.onlamp.com/pub/a/onlamp/2005/10/27/memory-management.html
Computer memory is perhaps the most rapidly changing component in computer systems. Back in the early '80s, 1K memory chips were quite common in the first home microcomputers. Now, 25 years later, the average home computer may contain memory modules of 1GB, representing growth by a factor of 1 million times, compared with only a 1,000-fold growth in CPU speed.
Despite this enormous increase in memory capacity, many of the problems that exist on today's machines are the same as those of their early predecessors--namely, running out of memory. The more RAM that is available to a software developer, the more the developer tends to use. Certainly techniques of memory conservation have changed with the times. In the early days of computing, programmers used clever tactics to tighten code loops or reuse blocks of memory at the expense of code readability, in order to cram impressive amounts of code into tiny amounts of RAM. Today, code size is rarely a concern anymore, and attention has shifted to storing large amounts of data--images, video, audio, and tables--but little else has changed. The articles in this series deal with memory management issues on modern Unix-like systems. Most of the topics apply to all computers and operating systems.
This article, the first in the series, discusses the Unix dynamic memory allocation system along with the concept of memory segmentation. It also reviews the utilities top and ulimit, giving special attention to their role in memory management.
Memory management is an important concept to grasp regardless of which programming language you use. You must be most careful with C, where you control all memory allocation and freeing. Languages such as C++, Java, Perl, and PHP take care of a lot of the housekeeping automatically. Nevertheless, all of these languages and others can allocate memory dynamically, and thus the following discussion applies to them all.
malloc
The majority of programming languages ultimately end up using a single system call to allocate memory, malloc. malloc is part of the main C library, but most other languages internally end up calling malloc to reserve blocks of memory as well. The process works as follows:
- The program requests a block of memory from malloc of size n bytes.
- If a contiguous block of memory of n bytes is not available, malloc returns a NULL pointer.
- Otherwise, malloc marks the block as occupied in its allocation tables and returns a pointer to the memory.
- The calling program uses the memory as needed.
- The calling program calls free, with the pointer returned by malloc as its argument.
- malloc looks up the pointer in its allocation tables and, if it finds it, removes it from the tables, effectively marking it as freed.
This brings up two important points. First is the concept of memory fragmentation. That is to say, malloc always returns memory in linear blocks. This may sound odd at first--why not use all the memory available, regardless of the address? Remember, though, it returns only a single pointer to the memory block. If it were not one big contiguous block, how would you know when to stop reading or writing sequentially and jump to the next block? The CPU would have to do a lot of extra work to give the illusion that such a returned block were still contiguous if it in fact were not.
For example, consider a machine with 1,000MB of RAM. You request three blocks of 300MB each and get back pointers starting at 0MB, 350MB, and 700MB. This leaves free a block from 300MB to 350MB, and 650MB to 700MB, for a total of 100MB free, as you would expect. However, any further request for a block larger than 50MB will fail, as this is the largest contiguous piece available. Only after freeing one of the earlier 300MB blocks could you request something bigger than 50MB. Thus it is not enough that memory be available; it must be in a contiguous block.
To avoid problems with memory fragmentation, try to avoid making many small memory allocations and instead replace them with one or two large allocations where possible.
Second, note that freeing a block of memory does little more than mark it as unallocated in malloc's internal allocation tables. The contents of the memory generally remain intact, although with C programs compiled in debug mode, for example, you can have memory zeroed automatically after freeing to aid in debugging. Often freed pointers will contain a value of 0xDEADBEEF or some such clever mnemonic to help the programmer identify such pointers easily when debugging. When building release code, however, these cleanup tasks are not present--mainly because they slow down the program significantly. This leaves it up to the programmer to clean up after himself.
It is impossible to tell whether a block of memory has been freed just by looking at it. You must keep track of that yourself. An additional point is that malloc does not normally return the freed memory to the operating system; it remains owned by the process until it terminates. The process can reuse it the next time it requests more memory, but other programs will not have access to it, even if no other memory is available. As a corollary, then, the memory footprint of a program is the size of the largest allocation(s) made at any one time. Thus it is always wise to free objects you do not need, especially large ones as soon as possible, to minimize this footprint.
There are also alternatives to malloc. For example, a program linked to the mapmalloc library will return dynamically allocated memory to the operating system when it calls free. While no change in the code is necessary, the price is that memory allocation takes about five times longer.
Memory Segmentation
As far back as the days of 8-bit computing, CPUs have always contained a few critical registers. These include the accumulator, where mathematical operations actually occur; the program counter (PC), which points to the command being executed; some index registers or data counters (DCs), which refer to data tables or variables in memory; and the stack pointer (SP), which always points to the top of the stack, a last-in, first-out (LIFO) data structure used for temporary storage.
The 8088 (IBM XT) CPU contained four special 16-bit registers called segment registers: code segment (CS), data segment (DS), stack segment (SS), and extra segment (ES). The name segment refers to memory access in real mode. A 20-bit address, allowing access to up to 1MB of RAM, came from a 16-bit segment and a 16-bit index to give the final address, by multiplying the segment by 16 and adding the index to the result. Thus each segment address corresponded to a single (overlapping) 64K chunk of memory (plenty for that time). The idea was that CS would always point to the 64K block that contained the machine code program being executed, DS to the 64K block where the program stored variables and temporary data, and SS to the program stack. ES was usable for other purposes, such as pointing to a second data area. The segment registers could of course change during the execution of the program if needed, but until programs using more than 256K became common, this was largely unnecessary.
While modern CPUs have far more registers, most of which are 32-bit or more, the concept of memory segmentation has not really changed all that much. Compiled programs still separate code from data from the stack, though the programmer may never see this. Nevertheless, understanding this separation and exactly how it works can be critical in debugging code, as well as writing portable applications.
Consider for a moment a program to be written on a dedicated system; for example, one to control all the automatic doors in an airport. Such programs generally read inputs from a number of sensors--in this case, they activate when someone approaches a door. The program must then respond to the input by activating some external device, a motor perhaps, to open the door. The program will normally run from ROM or an EPROM chip--these are cheap, and robust because the program stays in memory even when power is shut off. However, the program must store temporary data, including readings from the sensors, in writable memory--RAM. And so must the stack, as it is a writable object. In this situation, there is a true physical separation between program code and data. Any attempt to write data to the code segment, stored in a read-only medium, will fail. This was at least one early motivation for enforcing a strict separation between code and data.
The only real nomenclature change for contemporary systems is that the data segment, which is often the largest block of memory required, is called the heap. When malloc divvies up memory and returns it to your program, this memory comes off the heap. The compiled code still resides in its own section of memory, separate from the heap. The function of the stack has not changed in 30 years, either--its function is to pass arguments to subroutines, store the return address for subroutine calls, and store temporary values, such as the registers just before a CPU interrupt occurs. Often the stack and heap share the same memory space but, starting at opposite ends, grow toward each other. The stack has a preset size limit (sometimes customizable). It is possible to overflow the stack, which is almost always fatal to the program. In fact, buffer overflow security vulnerabilities caused by destroying the stack are the subject of many other articles.
How can writing to the code segment become a problem? Consider the following short C program:
int main(void)
{
char *buf;
/* warning: buggy code: do not copy and paste */
buf = "hello";
printf(buf);
strcpy(buf,"world");
printf(buf);
return 0;
}
It is simple enough to follow, and the expected output might be helloworld. However, run it in the debugger (in this case, on a Solaris machine) and examine the variables a bit more closely. Upon initialization, buf = 0xffbffa60 points to some random initial memory location--&buf = 0xffbffa58. That is, the actual character pointer variable is stored at this location in memory, which must be somewhere in the heap. The initial value of buf is not so random after all, but being in debug mode, this is to be expected.
Watch what happens after assigning buf the value hello, however. Now printing the contents of buf reveals that buf = 0x00020898--not in the heap, but way on the other end of memory in the code segment! This makes sense, because, as the next article in the series will show, the code segment actually stores constants such as hello. The code here made buf to point to it. So far so good; nothing bad has happened ... yet. The code then prints out buf, and again there is no trouble here.
The code then copies the next word to print into buf. Did you catch it? Granted, this certainly is not the recommended way to code, but the second word is the same length as the first, so there are no troubles in terms of writing past the end of the buffer--just remember the address buf contains. The strcpy command attempts to write 5 bytes to 0x00020898, an address in the code segment--which is potentially ROM! This is a big no-no. If this code were really stored in a ROM chip what would happen? The strcpy would effectively be a nonoperation, because writes to read-only memory make no sense, and the succeeding printf would print hello a second time.
Of course, you should always allocate string buffers in C as character arrays of a fixed size (if known) or else dynamically with malloc, never as shown in the previous example. That code is merely to illustrate how a seemingly correct program can produce unexpected results. A corrected program might be:
int main(void)
{
char buf[6];
strcpy(buf,"hello");
printf(buf);
strcpy(buf,"world");
printf(buf);
return 0;
}
top and ulimit
The most universal method for checking memory usage in Unix is probably top. This useful utility is available on most operating systems, and displays several important statistics about each process running on the machine. The memory-related ones are specifically RSS/RES and SIZE (though these names may vary slightly). The RSS column stands for resident set size, and indicates the amount of physical RAM being used by the program at a given time. SIZE, on the other hand, gives the total RAM usage, including code, heap, and stack. When the numbers differ, the missing data is actually stored in swap memory, on disk. This is usually memory that has gone unaccessed for a while and that will not consume physical memory. Normally the OS decides automatically which objects to swap in and out of virtual memory. As mentioned earlier, freed memory does not return to the heap until a program terminates, and as a result, memory use indicated by SIZE will never decrease throughout the extent of a program. It only ever goes one way--up.
A handy method for controlling memory use by processes under Unix is the ulimit command. With it you can set the maximum size of a process's data segment, RSS, open files, stack size, and more. You can specify hard limits (which you cannot change once set) and soft limits (adjustable up to a maximum value determined by the hard limit). Limiting memory usage can help avoid runaway processes from eating up memory, and it is often a good safeguard to have while debugging a program.
It may be useful to increase the stack size if running a highly recursive algorithm, because each recursive call pushes more data onto the stack. Another good time to use ulimit is when running a web server, where a CGI process may run hundreds of times, once for each connection allowed by the server. In this case, it is wise to limit per-process memory usage to avoid accidentally exhausting the memory of the server. Set the limit to the amount of RAM in the server divided by maximum number of connections permitted by the server, leaving some RAM for other processes too. If this limit is less than the peak RSS of an active process, reduce the number of connections permitted to the web server accordingly.
A useful way to reduce the memory footprint of a CGI is to use a technique such as streaming or chunking. The exact implementation may vary depending on the task, but essentially this entails breaking the problem into smaller pieces, working on one piece at a time, and freeing each piece before the next is started. In this way, you only ever need enough memory to hold a single piece. A simple example would be a CGI that needs to read a 100MB file from disk and return it to the user. Instead of reading it all in at once, then writing it to stdout using a full 100MB of RAM, the program can read 1MB at a time, write this to stdout, and free it before reading the next chunk. This limits the memory usage to only 1MB. The size of the chunks is usually adjustable, giving complete and flexible control over how much memory each process will require, while not limiting the size of data that the program can manage.
Summary
This article has discussed the fundamentals of memory management in PCs, and how they have changed over the past 30 years since the inception of the microcomputer. While memory capacity has increased drastically, the need to manage its use efficiently has not changed. We have simply started applying computers to much bigger problems to take advantage of all the available RAM.
To understand how to detect and remedy memory management issues, it is necessary to understand the storage of code and data in memory. The next article in the series will cover the storage and representation of variables in memory and will show how to use this knowledge to identify certain errors that are extremely difficult to debug without such knowledge. It will also cover swap memory, shared memory, and memory leaks in detail. |
| [ more ] - [ nerd's substance ] - 2005-11-09 15:18:58 - 3612+5189 |
|
| 我们为什么要做爱? |
|
| Let's have a talk. |
| [ more ] - [ 2005 ] - 2005-08-22 11:20:19 - 37116+40 |
|
| 关于健康的工作方式 |
|
最近发现了一个很有趣的小软件, Time Out, 设置为 Open at Login 之后, 每隔 45 分钟就会提醒你休息 10 分钟. 很不错. 不过我不是很喜欢这个软件的一些设计细节, 所以就自己小小地修改了一下, 换了图标和一些字体的设计(kudos to Interface Builder), 你可以从下面这个链接下载我修改过的版本:
http://www.livid.cn/goods/Time%20Out.zip
如果你用的是 Windows, 你可以试试 Workrave, 功能还要更强一些. |
| [ more ] - [ 2008 ] - 2008-05-05 17:21:05 - 6991+14456 |
|
| WeiPhone |
|
如果你是 iPhone / iPod touch 的新机主,那么向你推荐一个很不错的中文论坛:
http://www.weiphone.com/
最近我在玩 iPhone 的时候从上面学习到了很多东西,感谢他们。 |
| [ more ] - [ 2007 ] - 2007-11-13 07:20:40 - 46342+4681 |
|
| Google AdSense 的新产品 |
|
这是来自 Google AdSense 的最新产品,如果你希望给你的网站增加更多收入的话,就去看看吧。
|
| [ more ] - [ 2005 ] - 2005-11-06 01:13:39 - 4299+4887 |
|
| Mac Office 2008 Beta |
|
从 Demonoid 上拉下来的,版本号 Version 12.0.0 (070919)。

启动速度非常快。Entourage 很稳定,我打算接下来当作主力邮件客户端用。新增的叫做 My Day 的小工具是一个很不错的 GTD 辅助。
捆绑的 Microsoft Messenger 没有更新,还是 6.0.3。 |
| [ more ] - [ nerd's substance ] - 2007-10-16 02:59:25 - 4821+3965 |
|
| Extensions |
|
http://filext.org/
When to deal with various uploads, it's a handy database. |
| [ more ] - [ 2005 ] - 2005-12-09 21:59:37 - 4397+3583 |
|
| 双重思想 |
|
| 知与不知,知道全部真实情况而却扯一些滴水不漏的谎话,同时持两种互相抵消的观点,明知它们互相矛盾而仍都相信,用逻辑来反逻辑,一边表示拥护道德一边又否定道德,一边相信民主是办不到的一边又相信党是民主的捍卫者,忘掉一切必须忘掉的东西,而又在需要的时候想起它来,然后又马上忘掉它,而尤其是,把这样的做法应用到做法本身上面——这可谓绝妙透顶了:有意识地进入无意识,而后又并不意识到你刚才完成的催眠。即使要了解“双重思想”的含义你也得使用双重思想。
[tags]newspeak, doublethink[/tags] |
| [ more ] - [ 2007 ] - 2007-06-26 11:55:08 - 4329+5924 |
|
| On Mobile Web |
|
关于 Mobile Web 的一些思考结论。
- 界面应该尽可能不要依赖图片和 CSS,一些 plaintext HTML 就足够了。出于下载速度及资费原因,大部分 nerd 使用 portable device 访问 Web 的时候都会禁止这些功能。
- 彻底不能使用任何 JavaScript,即使在 Desktop PC 上,JavaScript 也足够折磨人了,而到了 portable device 上,就彻底是地狱。
- 单个页面的大小控制在 10K 以内,8~9K 是最佳尺寸,从心理学及技术角度而言都是如此。
- 没有太大的必要在 Mobile Web 上提供登录功能,大部分内容 read-only 就足够好了。大部分人没有耐心在 Mobile Web 上登录的。
- 应该维护一个足够详尽的能够支持或者测试过的 portable device 的 HCL,这样你就可以知道事情究竟进展到哪一步了。这部分的工作可以使用 Google Spreadsheet 来完成,其协作功能在这种场合尤其有用。
- 针对设备的优化的重点是所有的 Nokia 的设备及 Windows Mobile 系统的设备,这些设备是目前访问 Mobile Web 的主力,另外需要注意的是,记得经常在 Opera 的 SSR (Small Screen Rendering)模式下进行尽可能多的测试。
- UTF-8,GB18030 及 GB2312 等编码的支持是必要的。
- WAP、WML 等协议已经过时了,现在大部分设备都能够支持 XHTML 及 CSS 的一个子集,所以没有必要花费时间在那些古老的协议上了,尤其是在中国这样的一个手机更新换代非常频繁的国家。
So,为了实践这些理论,我做了 V2EX Mobile。 |
| [ more ] - [ nerd's substance ] - 2006-09-05 04:18:41 - 6928+8344 |
|
| 更新我的 Blog 的一些设计 |
|
是时候进行一些修改和加强了。
- 修改了顶部的 banner 的设计
- 去掉了插入在列表项目中的广告
- 加强了每篇文章的显示页面
我想,什么时候我应该完成一个全新的版本来庆祝 2005 年的结束吧。 |
| [ more ] - [ 2005 ] - 2005-11-14 03:11:15 - 4717+5464 |
|
| Zend Studio 4 Beta 已出 |
|
Zend Studio 是用于开发 PHP 程序的最理想的工具。(理论上而言,但是具体到我个人的话……)
新的 4.0 Beta 最大的改进就是在数据库支持和语法高亮方面。另外就是,在新版本中还加入了大量的开发者辅助方面的信息。
官方网站地址∶http://www.zend.com/store/products/zend-studio/beta.php |
| [ more ] - [ nerd's substance ] - 2005-01-12 07:47:28 - 6587+25 |
|
| Monsters |
|
 |
| [ more ] - [ 2006 ] - 2006-02-17 11:05:02 - 4103+2605 |
|
| Lividict,我做的可以在线查询的网上词典 |
|
服务器位于我家客厅,你可以现在就试验试验它的功能,比如用它来查我的名字∶
http://livid.3322.org/lividict/livid.html |
| [ more ] - [ nerd's substance ] - 2005-04-06 01:58:04 - 15325+33 |
|
| 今年的冬天似乎已经结束 |
|
| 悠闲的春节假日的下午,在家听 Pink Floyd 的 Wish you were here,发现 OpenQ 还在继续被维护,觉得挺高兴。我一定是有病,觉得如果要用 QQ 的话,在 Linux 上用是最爽的,因为速度快。 |
| [ more ] - [ 2005 ] - 2005-02-11 15:13:44 - 3312+32 |
|
| Sputnik 1 |
|
| Sputnik 1 是人类发射到宇宙的第一颗卫星,俄语中“旅伴”的意思,这颗非返回式卫星在电池耗尽之后,便不再向地球发射任何信号,而消失在了星辰大海中。 |
| [ more ] - [ 2006 ] - 2006-12-24 03:45:43 - 5283+7367 |
|
| uncertainness is part of the plan |
|
|
|
|
|
|
|