内核空间与用户空间之一:基础概念
分类: Linux内核
2010-01-27 19:26
354人阅读
收藏
举报
(1)Linux简化了分段机制,使得虚拟地址与线性地址总是一致,因此Linux的虚拟地址空间也为0~ 4G(32位地址最大访问极限)。Linux内核将这4G字节的空间分为两部分,将最高的1G字节(从虚拟地址0xC0000000到0xFFFFFFFF),供内核使用,称为“内核空间”。而将较低的3G字节(从虚拟地址0x00000000到0xBFFFFFFF,供各个进程使用,称为“用户空间)。因为每个进程可以通过系统调用进入内核,因此,Linux内核由系统内的所有进程共享。于是,从具体进程的角度来看,每个进程可以拥有4G字节的虚拟空间。
(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 ?
- #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))
#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))
而它的注释也很清楚: /* * 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 ?
- #include <linux/kernel.h>
- #include <linux/module.h>
- #include <linux/init.h>
- #include <linux/fs.h>
- #include <linux/string.h>
- #include <linux/mm.h>
- #include <linux/syscalls.h>
- #include <asm/unistd.h>
- #include <asm/uaccess.h>
-
-
- #define MY_FILE "LogFile"
-
-
- asmlinkage long sys_open(const char __user *filename,int flags, int mode);
-
- char buf[128];
- struct file *file = NULL;
-
-
-
- static int __init init(void)
- {
- mm_segment_t old_fs;
- int ret = 0;
- printk("<0>""Hello, I'm the module that intends to write messages to file./n");
-
-
- if(file == NULL)
- file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);
-
- if (IS_ERR(file)) {
- printk("<0>""error occured while opening file %s, exiting.../n", MY_FILE);
- return 0;
- }
- memset(buf,0x61,128);
-
- sprintf(buf,"%s", "The Messages is xjkdljflk.");
-
- old_fs = get_fs();
- printk("<0>""old_fs is %lx/n",old_fs.seg);
- set_fs(KERNEL_DS);
- old_fs = get_fs();
- printk("<0>""old_fs is %lx/n",old_fs.seg);
- printk("<0>""KERNEL_DS is %lx/n",KERNEL_DS);
- ret = file->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos);
- if(ret < 0)
- printk("<0>""file write failed/n");
- set_fs(old_fs);
-
-
- return 0;
- }
-
- static void __exit fini(void)
- {
- if(file != NULL)
- filp_close(file, NULL);
- }
-
- module_init(init);
- module_exit(fini);
- MODULE_LICENSE("GPL");
#include <linux/kernel.h>
#include <linux/module.h>
#include <linux/init.h>
#include <linux/fs.h>
#include <linux/string.h>
#include <linux/mm.h>
#include <linux/syscalls.h>
#include <asm/unistd.h>
#include <asm/uaccess.h>
#define MY_FILE "LogFile"
//#define MY_FILE "/root/LogFile"
asmlinkage long sys_open(const char __user *filename,int flags, int mode);
char buf[128];
struct file *file = NULL;
static int __init init(void)
{
mm_segment_t old_fs;
int ret = 0;
printk("<0>""Hello, I'm the module that intends to write messages to file./n");
if(file == NULL)
file = filp_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);
// file = sys_open(MY_FILE, O_RDWR | O_APPEND | O_CREAT, 0644);
if (IS_ERR(file)) {
printk("<0>""error occured while opening file %s, exiting.../n", MY_FILE);
return 0;
}
memset(buf,0x61,128);
sprintf(buf,"%s", "The Messages is xjkdljflk.");
old_fs = get_fs();
printk("<0>""old_fs is %lx/n",old_fs.seg);
set_fs(KERNEL_DS);
old_fs = get_fs();
printk("<0>""old_fs is %lx/n",old_fs.seg);
printk("<0>""KERNEL_DS is %lx/n",KERNEL_DS);
ret = file->f_op->write(file, (char *)buf, sizeof(buf), &file->f_pos);
if(ret < 0)
printk("<0>""file write failed/n");
set_fs(old_fs);
return 0;
}
static void __exit fini(void)
{
if(file != NULL)
filp_close(file, NULL);
}
module_init(init);
module_exit(fini);
MODULE_LICENSE("GPL");
使用的Makefile如下:
[cpp] view plain copy print ?
- ifeq ($(KERNELRELEASE),)
-
- KERNELDIR := /usr/src/linux-headers-2.6.32-28-generic
- PWD := $(shell pwd)
-
-
- modules:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules
-
- modules_install:
- $(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
-
- clean:
- rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c
- .PHONY:modules modules_install clean
- else
- obj-m := call.o
- endif
ifeq ($(KERNELRELEASE),)
KERNELDIR := /usr/src/linux-headers-2.6.32-28-generic
PWD := $(shell pwd)
modules:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules
modules_install:
$(MAKE) -C $(KERNELDIR) M=$(PWD) modules_install
clean:
rm -rf *.o *~ core .depend .*.cmd *.ko *.mod.c
.PHONY:modules modules_install clean
else
obj-m := call.o
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,这样就可以在内核顺利使用系统调用了!