Linux内核用户空间和内核空间数据交换

       前些日子一直在纠结copy_from_user函数是怎么实现用户态空间复制到内核态空间的。下面先附上copy_from_user的代码相关函数的代码,函数的调用关系是copy_to_user->__generic_copy_from_user->__copy_user_zeroing。那么我们就来看__copy_user_zeroing的代码。

//copy_to_user->__generic_copy_from_user->__copy_user_zeroing
#define __copy_user_zeroing(to,from,size)               \                                                                                                                                                                                                                                                                                                                                 
 do {                                    \                                                                                                                                                                                                                                                                                                                                                 
     int __d0, __d1;                         \                                                                                                                                                                                                                                                                                                                                             
     __asm__ __volatile__(                       \                                                                                                                                                                                                                                                                                                                                         
         "0: rep; movsl\n"                   \                                                                                                                                                                                                                                                                                                                                             
         "   movl %3,%0\n"                   \                                                                                                                                                                                                                                                                                                                                             
         "1: rep; movsb\n"                   \                                                                                                                                                                                                                                                                                                                                             
         "2:\n"                          \                                                                                                                                                                                                                                                                                                                                                                                                    
    ".section .fixup,\"ax\"\n"              \                                                                                                                                                                                                                                                                                                    
    "3: lea 0(%3,%0,4),%0\n"                \                                                                                                                                                                                                                                                                                                                                                   
    "4: pushl %0\n"                 \                                                                                                                                                                                                                                                                                                              
    " pushl %%eax\n"                  \                                                                                                                                                                                                                                                                                                                                                       
    " xorl %%eax,%%eax\n"             \                                                                                                                                                                                                                                                                                                                                                       
    " rep; stosb\n"                   \                                                                                                                                                                                                                                                                                                                                                       
    " popl %%eax\n"                   \                                                                                                                                                                                                                                                                                                                                                       
    " popl %0\n"                  \                                                                                                                                                                                                                                                                                                                                                           
    " jmp 2b\n"                   \                                                                                                                                                                                                                                                                                                                                                           
    ".previous\n"                       \                                                                                                                                                                                                                                                                                                                                                       
    ".section __ex_table,\"a\"\n"               \                                                                                                                                                                                                                                                                                                                                     
          "   .align 4\n"                 \                                                                                                                                                                                                                                                                                                                                                 
          "   .long 0b,3b\n"                  \                                                                                                                                                                                                                                                                                                                                             
          "   .long 1b,4b\n"                  \                                                                                                                                                                                                                                                                                                                                             
          ".previous"                     \                                                                                                                                                                                                                                                                                                                                                 
          : "=&c"(size), "=&D" (__d0), "=&S" (__d1)       \                                                                                                                                                                                                                                                                                                                                 
          : "r"(size & 3), "0"(size / 4), "1"(to), "2"(from)  \                                                                                                                                                                                                                                                                                                                             
          : "memory");                        \                                                                                                                                                                                                                                                                                                                                             
  } while (0)  

由上面的代码可以看出,实际上在用户空间复制到地址空间的时候,并未做地址转换工作。该函数是在内核空间中,内核空间又是怎么访问用户空间的呢。

我们知道i386 MMU进行寻址的时候实际上是虚拟地址经过映射才可以到达物理地址,无论是在内核空间还是在用户空间,这个规则是不会变的。实际上在用户态进程调用系统调用陷入内核后,内核是没有切换CR3寄存器的,也就是还是使用原来进程的CR3寄存器的值,所以MMU进行地址映射时还是寻找的原来的页面目录项(PGD),所以内核可以对用户态进程调用系统调用传入的指针进行直接访问。这里面还有一个问题,就是使用用户态进程PGD是如何访问内核的地址的。

如果了解过Linux内核知识的小伙伴应该知道,i386 Linux用户态虚拟内存分为两部分,3GB的用户空间和1GB的内核空间,并且所有的进程使用的是同一个内核空间,如经典的下图所示:

 这里就有一个疑惑,每个进程是使用独立的页面目录项(PGD)又是如何做到映射一块内存地址呢。实际上可以从创建进程的代码开始看(fork系统调用),找到内核分配pgd的代码就可以了。

调用顺序为sys_fork->do_fork->copy_mm->mm_init->pgd_alloc->get_pgd_fast->get_pgd_slow

  extern __inline__ pgd_t *get_pgd_slow(void)                                                                                                                                                                                                                                                                                                                                                  
  {                                                                                                                                                                                                                                                                                                                                                                                            
      pgd_t *ret = (pgd_t *)__get_free_page(GFP_KERNEL);                                                                                                                                                                                                                                                                                                                                       
                                                                                                                                                                                                                                                                                                                                                                                               
      if (ret) {                                                                                                                                                                                                                                                                                                                                                                               
  #if CONFIG_X86_PAE                                                                                                                                                                                                                                                                                                                                                                           
          int i;                                                                                                                                                                                                                                                                                                                                                                               
          for (i = 0; i < USER_PTRS_PER_PGD; i++)                                                                                                                                                                                                                                                                                                                                              
              __pgd_clear(ret + i);                                                                                                                                                                                                                                                                                                                                                            
  #else                                                                                                                                                                                                                                                                                                                                                                                        
          memset(ret, 0, USER_PTRS_PER_PGD * sizeof(pgd_t));                                                                                                                                                                                                                                                                                                                                   
  #endif                                                                                                                                                                                                                                                                                                                                                                                       
          memcpy(ret + USER_PTRS_PER_PGD, swapper_pg_dir + USER_PTRS_PER_PGD, (PTRS_PER_PGD - USER_PTRS_PER_PGD) * sizeof(pgd_t));   //这行是关键                                                                                                                                                                                                                                                        
      }                                                                                                                                                                                                                                                                                                                                                                                        
      return ret;                                                                                                                                                                                                                                                                                                                                                                              
  } 

swapper_pg_dir在上一节讲到了,是内核转入保护模式页面映射设置的页面目录项(PGD),从这行可以看出,当每创建一个进程时,是将swapper_gd_dir高地址空间(内核空间)的页面目录项复制到新创建进程的页面目录项中。所以Linux用户态所有的进程,共享一个内核空间。

ps:这个问题纠结了我好一段时间,现在终于解决了。记录一下

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值