推荐给好友 上一篇 | 下一篇

重读2.4内核(2)

这是很早的时候看的两个文件,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: 内核 重读
 

评分:0

我来说两句

seccode