内核空间和用户空间

内核空间与用户空间之一:基础概念

分类: Linux内核 354人阅读 评论(0) 收藏 举报

(1)Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此Linux的虚拟地址空间也为0~ 4G(32位地址最大访问极限)。Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF,供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。

                                      内核空间和用户空间_第1张图片                                          

(2)其中,内核地址空间又被划分为"物理内存区","虚拟内存分配区","高端页面映射区","专用页面映射区","系统保留映射区"几个区域。在标准配置下,物理区最大长度为896M,系统的物理内存被顺序映射在物理区中,当系统物理内存大于896M时,超过物理区的那部分内存称为高端内存。低端内存和高端内存用highmem_start_page变量来定界,内核在存取高端内存时必须将它们映射到"高端页面映射区"。

      Linux保留内核空间最顶部128K区域作为保留区,紧接保留区以下的一段区域为专用页面映射区,它的总尺寸和每一页的用途由fixed_address枚举结构在编绎时预定义,用__fix_to_virt(index)可获取专用区内预定义页面的逻辑地址。在专用页面区内为CPU预定义了一张高端内存映射页,用于在中断处理中高端页面的映射操作。

      距离内核空间顶部32M,长度为4M的一段区域为高端内存映射区,它正好占用1个页帧表所表示的物理内存总量,它可以缓冲1024个高端页面的映射。在物理区和高端映射区之间为虚存内存分配区,用于vmalloc()函数,它的前部与物理区有8M隔离带,后部与高端映射区有8K的隔离带。

      当系统物理内存超过4G时,必须使用CPU的扩展分页(PAE)模式所提供的64位页目录项才能存取到4G以上的物理内存。在PAE模式下,线性地址到物理地址的转换使用3级页表,第1级页目录由线性地址的最高2位索引,每一目录项对应1G的寻址空间,第2级页目录项以9位索引,每一目录项对应2M的寻址空间,第3级页目录项以9位索引,每一目录项对应4K的页帧。

(3) 关于内核态和用户态:当一个任务(进程)执行系统调用而陷入内核代码中执行时,我们就称进程处于内核运行态(或简称为内核态)。此时处理器处于特权级最高的(0级)内核代码中执行。当进程处于内核态时,执行的内核代码会使用当前进程的内核栈。每个进程都有自己的内核栈。当进程在执行用户自己的代码时,则称其处于用户运行态(用户态)。即此时处理器在特权级最低的(3级)用户代码中运行。当正在执行用户程序而突然被中断程序中断时,此时用户程序也可以象征性地称为处于进程的内核态。因为中断处理程序将使用当前进程的内核栈。这与处于内核态的进程的状态有些类似。

                                                                         
      关于进程上下文和中断上下文。处理器总处于以下状态中的一种:
1、内核态,运行于进程上下文,内核代表进程运行于内核空间;
2、内核态,运行于中断上下文,内核代表硬件运行于内核空间;
3、用户态,运行于用户空间。











内核空间和用户空间之二:在内核空间中系统调用用户空间的内容


         在linux内核中操作用户文件的方法:使用get_fs()和set_fs(KERNEL_DS)调用系统调用改变权限,再通过内核文件操作函数,可以访问用户空间。

(1)宏介绍
     源码在include/asm/uaccess.h中,有如下定义:

[cpp] view plain copy print ?
  1. #define MAKE_MM_SEG(s) ((mm_segment_t) { (s) })   
  2. #define KERNEL_DS MAKE_MM_SEG(0xFFFFFFFF)   
  3. #define USER_DS MAKE_MM_SEG(PAGE_OFFSET)   
  4. #define get_ds() (KERNEL_DS)   
  5. #define get_fs() (current->addr_limit)   
  6. #define set_fs(x) (current->addr_limit = (x))   


而它的注释也很清楚: /* * 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. */

因此可以看到,fs的值是作为是否进行参数检查的标志。当系统调用的参数要求来自用户空间,在内核中使用系统调用的时候,set_fs(get_ds())改变了用户空间的限制,即扩大了用户空间范围,因此即可使用在内核中的参数了。

(2)函数实例,在一个模块中新建一个文本,并往文本中写入内容。

[cpp] view plain copy print ?
  1. #include <linux/kernel.h>  
  2. #include <linux/module.h>  
  3. #include <linux/init.h>  
  4. #include <linux/fs.h>  
  5. #include <linux/string.h>  
  6. #include <linux/mm.h>  
  7. #include <linux/syscalls.h>  
  8. #include <asm/unistd.h>  
  9. #include <asm/uaccess.h>  
  10.   
  11.   
  12. #define MY_FILE "LogFile"  
  13. //#define MY_FILE "/root/LogFile"  
  14.   
  15. asmlinkage long sys_open(const char __user *filename,int flags, int mode);  
  16.   
  17. char buf[128];  
  18. struct file *file = NULL;  
  19.   
  20.    
  21.   
  22. static int __init init(void)  
  23. {  
  24.         mm_segment_t old_fs;  
  25.         int ret = 0;  
  26.         printk("<0>""Hello, I'm the module that intends to write messages to file./n");  
  27.   
  28.   
  29.         if(file == NULL)  
  30.                 file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);  
  31.         //                 file = sys_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);  
  32.         if (IS_ERR(file)) {  
  33.                 printk("<0>""error occured while opening file %s, exiting.../n", MY_FILE);  
  34.                 return 0;  
  35.         }  
  36.         memset(buf,0x61,128);  
  37.   
  38.         sprintf(buf,"%s""The Messages is xjkdljflk.");  
  39.   
  40.         old_fs = get_fs();  
  41.         printk("<0>""old_fs is %lx/n",old_fs.seg);  
  42.         set_fs(KERNEL_DS);  
  43.         old_fs = get_fs();  
  44.         printk("<0>""old_fs is %lx/n",old_fs.seg);  
  45.         printk("<0>""KERNEL_DS is %lx/n",KERNEL_DS);  
  46.         ret = file->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos);  
  47.         if(ret < 0)  
  48.                 printk("<0>""file write failed/n");  
  49.         set_fs(old_fs);  
  50.   
  51.   
  52.         return 0;  
  53. }  
  54.   
  55. static void __exit fini(void)  
  56. {  
  57.         if(file != NULL)  
  58.                 filp_close(file, NULL);  
  59. }  
  60.   
  61. module_init(init);  
  62. module_exit(fini);  
  63. MODULE_LICENSE("GPL");  


     使用的Makefile如下:

[cpp] view plain copy print ?
  1. ifeq ($(KERNELRELEASE),)  
  2.   
  3. KERNELDIR := /usr/src/linux-headers-2.6.32-28-generic  
  4. PWD := $(shell pwd)  
  5.   
  6.   
  7. modules:  
  8.  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules  
  9.   
  10. modules_install:  
  11.  $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install  
  12.   
  13. clean:  
  14.  rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c   
  15. .PHONY:modules modules_install clean  
  16. else  
  17. obj-m := call.o  
  18. endif  


       编译完后,执行insmod和rmmod即可看到想要的结果。

(3)过程说明

         用flip_open函数打开文件,得到struct file *的指针fp。使用指针fp进行相应操作,如读文件可以用fp->f_ops->read,最后用filp_close()函数关闭文件。

        总之:要在内核空间使用系统调用,此时传递给->write()的参数地址就是内核空间的地址了,在USER_DS之上(USER_DS ~ KERNEL_DS),如果不做任何其它处理,在write()函数中,会认为该地址超过了USER_DS范围,所以会认为是用户空间的“蓄意破坏”,从而不允许进一步的执行; 为了解决这个问题; set_fs(KERNEL_DS);将其能访问的空间限制扩大到KERNEL_DS,这样就可以在内核顺利使用系统调用了!

你可能感兴趣的:(linux内核)