这是很早的时候看的两个文件,extable.c ioremap.c. 如果是单看这里的说明许感觉过于简单,其实在论坛里充斥了对 .fixup section的讨论,注意去寻找下。
一./arch/i386/extable.c
内核总要和用户打交道,比如从用户提供的buffer中拷贝数据等等。为了保证kernel的安
全,这种操作需要格外小心。 有可能用户提供的buffer没有初始化,或者是空指针,无论如何内
核都应该能应对这种事情。
看一个例子,arch/i386/lib/usercopy.c
#define __do_strncpy_from_user \
do { \
int __d0, __d1, __d2; \
__asm__ __volatile__( \
" testl %1,%1\n" \
" jz 2f\n" \
"0: lodsb\n" \
" stosb\n" \
" testb %%al,%%al\n" \
" jz 1f\n" \
" decl %1\n" \
" jnz 0b\n" \
"1: subl %1,%0\n" \
"2:\n" \
".section .fixup,\"ax\"\n" \
"3: movl %5,%0\n" \
" jmp 2b\n" \
".previous\n" \
".section __ex_table,\"a\"\n" \
" .align 4\n" \
" .long 0b,3b\n" \
".previous" \
: "=d"(res), "=c"(count), "=&a" (__d0), "=&S" (__d1), \
"=&D" (__d2) \
: "i"(-EFAULT), "0"(count), "1"(count), "3"(src), "4"(dst) \
: "memory"); \
} while (0)
首先注意到.section .fixup 链接指令将修复内核的代码放入代码断.fixup, 将一个对应
关系 0b,3b(这是内嵌汇编的label)放入section __ex_table, 意思是说,如果0b出现page fault,
GP错误等就跳转到3b执行.
而/arch/i386/extable.c正是实现在__ex_table中查找0b返回3b的功能.代码极为简单不再
列举代码. 只是注意,内核模块也是内核的一部分,也会有这种事情需要处理,这个文件中也有对模
块进行搜索查找.
文件提供的函数search_exception_table在do_page_fault,do_trap,do_general_protection
都有使用,正是这一机制的具体实现.
二.arch/i386/ioremap.c
参考LDD2, ch8, Software-Mapped I/O Memory。
比如isa设备和pci设备,或者是fb,硬件的跳线或者是物理连接方式决定了硬件上
的内存影射到的cpu物理地址。
在内核访问这些地址必须分配给这段内存以虚拟地址,这正是__ioremap的意义所在
,需要注意的是,物理内存已经"存在"了,无需alloc page给这段地址了.
文件中的注释也是比较详尽的,并且只暴露了__ioremap,iounmap两个函数供其他模
块调用,函数remap_area_pte,remap_area_pmd,remap_area_pages只为__ioremap所用.
/*
* 映射指定的物理地址的一段内存到内核的虚拟地址.
* 内核如果需要直接访问high address的内存则也需要这个函数先映射一下.
*
* NOTE! We need to allow non-page-aligned mappings too: we will obviously
* have to convert them into an offset in a page-aligned mapping, but the
* caller shouldn't need to know that small detail.
*/
void * __ioremap(unsigned long phys_addr, unsigned long size, unsigned long flags)
{
void * addr;
struct vm_struct * area;
unsigned long offset, last_addr;
/*先做各种检查*/
/* Don't allow wraparound or zero size */
last_addr = phys_addr + size - 1;
if (!size || last_addr < phys_addr)
return NULL;
/*
* Don't remap the low PCI/ISA area, it's always mapped..
*/
if (phys_addr >= 0xA0000 && last_addr < 0x100000)
return phys_to_virt(phys_addr);
/*
* Don't allow anybody to remap normal RAM that we're using..
*/
if (phys_addr < virt_to_phys(high_memory)) {
//by the way :virt_to_phys 只能用于HIGH MEM以下
//如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram
char *t_addr, *t_end;
struct page *page;
t_addr = __va(phys_addr);
t_end = t_addr + (size - 1);
for(page = virt_to_page(t_addr); page <= virt_to_page(t_end); page++)
if(!PageReserved(page)) //只容许映射reserved的页面
return NULL;
}
/*
* 然后对映射地址页对齐
*/
offset = phys_addr & ~PAGE_MASK;
phys_addr &= PAGE_MASK;
size = PAGE_ALIGN(last_addr) - phys_addr;
/*
* 接下来,分配虚拟地址给这段物理内存
*/
area = get_vm_area(size, VM_IOREMAP);
if (!area)
return NULL;
addr = area->addr;
/* 最后,遍历设置这段空间所涉及的pgd, pmd, pte
* 则大功告成.
*/
if (remap_area_pages(VMALLOC_VMADDR(addr), phys_addr, size, flags)) {
vfree(addr);
return NULL;
}
return (void *) (offset + (char *)addr);
}
就不深入进去细说remap_area_pte,remap_area_pmd,remap_area_pages了。
顺便说说内存的划分和管理. linux 内核在3G(PAGE_OFFSET)以上, 如果全部映射
为普通ram也只能管理1G的物理内存. 所以就保留了vmalloc的空间,最多容许映射
MAX_MEM的物理内存. 参见 arch/i386/kernel/setup.c 的定义:
#define MAXMEM (unsigned long)(-PAGE_OFFSET-VMALLOC_RESERVE)
#define MAXMEM_PFN PFN_DOWN(MAXMEM)
内核使用的缓冲区可以使用vmallc分配,如果需要直接访问某个MAX_MEM地址以上
的物理地址,也可以映射到这段空间,如这里的ioremap.
virt_to_phys只能应用在high_memory以下的虚拟地址上. 变量high_memory是在系统启动过程中初始化的. 见arch/i386/kernel/setup.c 函数setup_arch 679行
max_low_pfn = max_pfn;
if (max_low_pfn > MAXMEM_PFN) {
max_low_pfn = MAXMEM_PFN; //最多映射MAXMEM_PFN个物理页面
................
(注意max_low_pfn这里是局部变量)
如果系统拥有超过MAXMEM的物理内存,内核则无法直接映射(即virt_to_phys失效).
max_low_pfn如果大于MAXMEM_PFN(即物理地址>MAXMEM,映射不了了)就只映射MAXMEM_PFN
个页面,如果系统中还没到这么多内存,则有多少映射多少.
max_low_pfn 在setup_arch 712行:传递给init_bootmem
...............
bootmap_size = init_bootmem(start_pfn, max_low_pfn);
...............
init_bootmem随后赋值给变量max_low_pfn(bootmem.c的全局变量). 最后,
arch/i386/mm/init.c 函数mem_init, 568行,
................
high_memory = (void *) __va(max_low_pfn * PAGE_SIZE);
................
将max_low_pfn 转换成虚拟地址赋予high_memory.
回到__ioremap 中来看看这段检查.
...................
/*
*Don't allow anybody to remap normal RAM that we're using..
*/
if (phys_addr < virt_to_phys(high_memory)) {
//by the way :virt_to_phys 只能用于HIGH MEM以下
//如果要映射小于HIMEM的物理地址,绝对要禁止映射普通的ram
...................
原作者注释Don't allow anybody to remap normal RAM that we're using..
社么是RAM that we're using..?
根据以上分析,如果phys_addr 小于virt_to_phys(high_memory)意为着正在映射内核已经映射了的物理页面,除非页面是Reserved属性(保留页,如bios数据区).
已经映射过了, 内核"随时使用",当然不容许再次映射. 如果phys_addr>
virt_to_phys(high_memory),内核通过vmalloc使用,ioremap也容许映射这些ram页面,不过有点怪怪的,应该禁止.
以上通过对HIGHMME的简单分析解释virt_to_phys的使用范围,并解释ioremap所作检查的意义. 下面回到linux内存的话题上,来注意一个问题:
虽然virt_to_phys不能处理high_memory以上的虚拟地址,但是那些不能映射的物理页面的管理结构page,依然在mem_map的数组里. 那些虚拟地址只有通过pte来获取真正的物理地址.而page的虚拟地址也不适用phys_to_virt,只能通过page->virtual获得。
void iounmap(void *addr)
{
if (addr > high_memory) //get_vm_area 获取的虚拟地址必然大于 high_memory
return vfree((void *) (PAGE_MASK & (unsigned long) addr));
}
其实就是vfree啦.代码参见mm/vmalloc.c,这里不再罗列. 和
remap_area_pages类似,遍历涉及到的pgd,pmd,pte释放相关页面.只需要看看这个
static inline void free_area_pte(pmd_t * pmd, unsigned long address, unsigned long size)
{
pte_t * pte;
unsigned long end;
if (pmd_none(*pmd))
return;
if (pmd_bad(*pmd)) {
pmd_ERROR(*pmd);
pmd_clear(pmd);
return;
}
pte = pte_offset(pmd, address);
address &= ~PMD_MASK;
end = address + size;
if (end > PMD_SIZE)
end = PMD_SIZE;
do {
pte_t page;
page = ptep_get_and_clear(pte);
address += PAGE_SIZE;
pte++;
if (pte_none(page))
continue;
if (pte_present(page)) {
struct page *ptpage = pte_page(page);
if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
//VALID_PAGE 检查此区域是否分配了ram页面,
//ioremap可以影射vm_area为io内存
//如果是VALID_PAGE(pagenr<max_mapnr)
__free_page(ptpage);
continue;
}
printk(KERN_CRIT "Whee.. Swapped out page in kernel page table\n");
} while (address < end);
}
只来关心一下其中加了注释的一小段,
.........
if (VALID_PAGE(ptpage) && (!PageReserved(ptpage)))
//VALID_PAGE 检查此区域是否分配了ram页面,ioremap可以影射
//vm_area为io内存
//如果是VALID_PAGE(pagenr<max_mapnr)
__free_page(ptpage);
........
如果对应ioremap的操作可以知道,vmalloc的空间被ioremap"借用",所以这段虚拟地址上可能出现reseved的页面,也可能出现not valid之页面,也可能出现ram页面(vmalloc). 所以vfree释放页面的时候需要检查,只释放"真正的RAM"页面即那些HIGHMEM页面.VALID_PAGE确保页面的mapnr在mem_map数组空间范围之内,亦即,页面是在所有RAM页面构成的地址范围内.这个范围中可能存在reseved的页面. 除去这些保留页,就可以放心的free了.
再注意一下,虽然地址大于virt_to_phy(high_memory)的物理页面无法使用,
phy_to_virt,virt_to_phy互相转换,但是依然在mem_map的管理之下.
小提一下pmd_bad,个人认为此宏是一个例行check,理由如下:
1.没有任何内核代码将一个pmd写成非法等着pmd_bad 去检查
2.出现pmd错误则产生一个log信息,足以证明此猜想.
TAG: 内核 重读








