本文涉及的内核代码版本为2.4.0。(其余文章如不作特殊说明,也使用该版本代码。)
1. get_fs()/set_fs()
这两个函数乍一看似乎用来控制fs寄存器的值。如果不细看其内部实现,很容易被误导。比如fs/namei.c中有如下代码,segment都用来描述了,很容易被误导。
1 2
| if (!segment_eq(get_fs(), KERNEL_DS)) return -EFAULT;
|
其实这两个函数在内核平台无关代码中有调用,因此绝不可能用来表示一个平台相关的概念。(fs寄存器是intel特有的)
还好,内核代码中有说明为什么要这么命名。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17
| * The fs value determines whether argument validity checking should be * performed or not. If get_fs() == USER_DS, checking is performed, with * get_fs() == KERNEL_DS, checking is bypassed. * * For historical reasons, these macros are grossly misnamed. */ #define MAKE_MM_SEG(s) ((mm_segment_t) { (s) }) #define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF) #define USER_DS MAKE_MM_SEG(PAGE_OFFSET) #define get_ds() (KERNEL_DS) #define get_fs() (current->addr_limit) #define set_fs(x) (current->addr_limit = (x))
|
注释中说的很清楚,这个fs仅用来判断是否要验参数。而且由于历史原因,这个晕死人不偿命的名称就一直沿用下来。
举个例子,
1 2 3 4 5 6 7 8 9 10 11 12
| * Like copy_strings, but get argv and its values from kernel memory. */ int copy_strings_kernel(int argc,char ** argv, struct linux_binprm *bprm) { int r; mm_segment_t oldfs = get_fs(); set_fs(KERNEL_DS); r = copy_strings(argc, argv, bprm); set_fs(oldfs); return r; }
|
函数copy_strings_kernel()在do_execve()中会被调用,用来拷贝内核中的字符串到内核空间。这边复用了从用户空间拷贝字符串到内核空间的函数copy_strings()。但是调用之前需要把fs设置为KERNEL_DS。因为copy_strings()当中会有检查字符串地址是否小于current进程的addr_limit,而内核空间地址在用户空间之上,如果不设置fs为KERNEL_DS,则检查必然失败。(为什么要检查?如果内核代码对用户空间地址随意放行,用户程序就可以钻空子任意妄为了。)
例如,copy_strings()中有如下检查,
1 2
| #define __addr_ok(addr) ((unsigned long)(addr) < (current->addr_limit.seg))
|
2. 内核代码中的物理地址和虚拟地址
3. get_pte_fast()
4. 内核地址空间页表如何映射到用户进程