原文: http://hi.baidu.com/zjlinus/blog/item/b0f831dd13ac50375882ddac.html
在 linux 中判断语句经常会看到 likely 和 unlikely ,例如:
if(likely(value)){
}
else{
}
简单从表面上看 if(likely(value)) == if(value) , if(unlikely(value)) == if(value) 。也就是 likely 和 unlikely 是一样的,但是实际上执行是不同的,加 likely 的意识是 value 的值为真的可能性更大一 些,那么执行 if 的机会大,而 unlikely 表示 value 的值为假的可能性大一些,执行 else 机会大一些。加上这种修饰,编译成二进制代码时 likely 使得 if 后面的执行语句紧跟着前面的程序, unlikely 使得 else 后面的语句紧跟着前面的程序,这样就会被 cache 预读取,增加程序 的执行速度, likely 和 unlikely 的实现在
include/linux/compiler.h 中:
9 #if __GNUC__ == 2 && __GNUC_MINOR__ < 96
10 #define __builtin_expect(x, expected_value) (x)
11 #endif
12
13 #define likely(x) __builtin_expect((x),1)
14 #define unlikely(x) __builtin_expect((x),0)
__builtin_expect 是 gcc 的一个预处理命令,其解释如下:
long __builtin_expect (long exp, long c)
You may use __builtin_expect to provide the compiler with branch prediction information. In general, you should prefer to use actual profile feedback for this
(‘-fprofile-arcs’), as programmers are notoriously bad at predicting how theirprograms actually perform. However, there are applications in which this data is hard to collect.
The return value is the value of exp, which should be an integral expression. The value of c must be a compile-time constant. The semantics of the built-in are that it is expected that exp == c. For example:
if (__builtin_expect (x, 0))
foo ();
would indicate that we do not expect to call foo, since we expect x to be zero. Since you are limited to integral expressions for exp, you should use constructions such as
if (__builtin_expect (ptr != NULL, 1))
error ();
when testing pointer or floating-point values.
<script> function StorePage(){d=document;t=d.selection?(d.selection.type!='None'?d.selection.createRange().text:''):(d.getSelection?d.getSelection():'');void(keyit=window.open('http://www.365key.com/storeit.aspx?t='+escape(d.title)+'&u='+escape(d.location.href)+'&c='+escape(t),'keyit','scrollbars=no,width=475,height=575,left=75,top=20,status=no,resizable=yes'));keyit.focus();} </script> 玩过windows 内核的都应该知道windows 为了节省4k 的内存,实现了自映射,将页目录本身当作了一个页表来寻址4M 的虚拟空间(将4M 空间的虚 拟内存映射到物理内存),而这4m 的虚拟地址空间恰恰就是从0xc0000000 到0xc03FFFFF 的空间,这一段空间正好是页表页目录的映射区域。 下面我来说一下这怎么变成现实。
首先有几点必须遵守的规则:1. 一个页表映射的4m 虚拟地址肯定是连续的;2. 一个页表映射到的1024 个4k 的物理页不一定是连续的;3. 实现自映射必 须是页目录和页表结构一样。有了以上的规则,我们要想理解(或者自己实现一个)windows 的自映射就必须理解实际上windows 的页表虚拟地址都是 连续的,映射到0xc0000000 到0xc03FFFFF ,表现为一个页表数组,一共1024 项,每项4096 字节,一共正好4m ,页目录作为这4m 的 页表。我们反着想,如果页目录成为了这4m 的页表,那么肯定映射0xc0000000 到0xc03FFFFF 的地址,我们将端点的两个地址分 解:11000000000000000000000000000000B 和11000000001111111111111111111111B ,仔细 观察发现高10 位是相等的,而这高10 位正好是页目录项的索引,我们知道他是768 ,好了,页目录要作为第768 页目录项对应的页表,现在这个页目录可以 作为一个页表插入到这个页表数组的第768 项的位置了,它开始身兼两职了,CR3 寄存器里面写的是它的物理地址,它作为页目录,此页面偏移768 处也写入 了这个物理地址,它作为页表。
现在考虑把这个页目录(页表)的虚拟映射地址换一下会怎样,别的页表不动。比如这个页目录映射到了0xdc000000 这个虚拟地址(仅仅据个例子)。然 后分解地址1101110000 0000000000 000000000000 ,通过计算发现其对应于页目录的880 项,那好吧,我把此页面880 偏移改成此页面的物理地址,别的偏移处仍然指向 0xc0000000 到0xc03FFFFF 的虚拟地址对应的物理地址。看看会发生什么事情,如果把这个从0xc0000000 到0xc03FFFFF 游 离出来的页面当作页表的话,那么它所映射的虚拟地址应该连续,但是它自己是游离出来的,它的虚拟地址和别的页表的虚拟地址根本不连续,所以这样做是不可以 的,也就是说对映射地址的连续性要求是一个必要的条件。windows 之所以这么简单的实现了自映射,完全归功于它的设计思想,实际上一个设计并没有多么 复杂,只要有了完备性,剩下的就是一个自然结果了,比如X 系统是复杂的,而且它在unix 出现很久以后才出现,但是unix 却完美地支持了x 系统,这就说 明unix 很完备。windows 的模块化作的很好,一致性作的也很好,它几乎将任何东西都标准化,每个进程的页表映射到同一个虚拟地址,在 windows 看来,为每个进程提供一个一致的视图要比巧妙的杂糅式进程管理重要的多,所以它将虚拟地址空间作了很复杂的划分和规定。
linux 正好走了相反的路,它对于虚拟内存空间什么也没有规定,只是说了一下映射规则,提供了一套完备的机制,策略问题根本没有提及,内核虚存空间各个 进程完全共享,各个数据结构散落在各处,内核空间仅仅提供了直接映射和高端映射的若干种映射规则和分配规则,至于说每个空间范围干什么用,linux 只字 未提,所以说linux 的页表可以散落在物理内存的任何地方,因为物理内存和虚拟内存在内核的很大一部分是一一映射的,所以上面的虚拟地址连续的要求就很 难满足,比如这可能要求物理内存连续,在linux 连续地址空间如此宝贵,浪费当作页表linux 觉得这不值得,再说,页表可以随时分配,为什么要连续 呢?连续的话就要先预留4m 页面,然后页表没有完全分配前会造成很多空洞,有几个进程能完全用完4m 的页表呢?linux 保守的回答了这个问题。我们看一 下代码就明白页表的分配了,代码来自最新的2.6.27 内核:
int handle_mm_fault(struct mm_struct *mm, struct vm_area_struct *vma,
unsigned long address, int write_access)
{
pgd_t *pgd;
pud_t *pud;
pmd_t *pmd;
pte_t *pte;
__set_current_state(TASK_RUNNING);
count_vm_event(PGFAULT);
if (unlikely(is_vm_hugetlb_page(vma)))
return hugetlb_fault(mm, vma, address, write_access);
pgd = pgd_offset(mm, address);
pud = pud_alloc(mm, pgd, address);
if (!pud)
return VM_FAULT_OOM;
pmd = pmd_alloc(mm, pud, address);
if (!pmd)
return VM_FAULT_OOM;
pte = pte_alloc_map(mm, pmd, address);// 此处即分配页表
if (!pte)
return VM_FAULT_OOM;
return handle_pte_fault(mm, vma, address, pte, pmd, write_access);
}
#define pte_alloc_map(mm, pmd, address) /
((unlikely(!pmd_present(*(pmd))) && __pte_alloc(mm, pmd, address))? /
NULL: pte_offset_map(pmd, address))
分配页表的策略是alloc_pages (GFP_KERNEL |__GFP_HIGHMEM |__GFP_REPEAT |__GFP_ZERO , 0 )好家伙,这么猛,你能保证这分配的页面和前一个页面是连续的吗?你不能,我也不能,甚至linus 也不能