2011-03-21

简介: 进程地址空间 Linux采用虚拟内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存。更重要的是,即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。

进程地址空间

Linux采用虚拟内存技术,系统中的所有进程之间以虚拟方式共享内存。对每个进程来说,它们好像都可以访问整个系统的所有物理内存。更重要的是,即使单独一个进程,它拥有的地址空间也可以远远大于系统物理内存。

       进程地址空间由每个进程中的线性地址区组成,每个进程都有一个32位或64位的平坦(flat)空间,空间的具体大小取决于体系结构。“平坦”指地址空间范围是一个独立的连续区间。通常情况下,每个进程都有唯一的这种平坦空间,而且每个进程的地址空间之间彼此互不相干。两个不同的进程可以在它们各自地址空间的相同地址内存存放不同的数据。但是进程之间也可以选择共享地址空间,我们称这样的进程为线程

       在地址空间中,我们更为关心的是进程有权访问的虚拟内存地址区间,比如08048000~0804c000。这些可被访问的合法地址区间被成为内存区域memory area),通过内核,进程可以给自己的地址空间动态地添加或减少内存区域。

       进程只能访问有效范围内的内存地址。每个内存区域也具有相应进程必须遵循的特定访问属性,如只读、只写、可执行等属性。如果一个进程访问了不在有效范围中的地址,或以不正确的方式访问有效地址,那么内核就会终止该进程,并返回“段错误”信息。

?        内存区域可以包含各种内存对象,如下:

?        可执行文件代码的内存映射,成为代码段text section)。

?        可执行文件的已初始化全局变量的内存映射,成为数据段data section)。

?        包含未初始化全局变量的零页(也就是bss)的内存映射。零页是指页面中的数据全部为0

?        用于进程用户空间栈的零页的内存映射。

?        每一个诸如C库或动态链接程序等共享库的代码段、数据段和bss也会被载入进程的地址空间。

?        任何内存映射文件

?        任何共享内存段

?        任何匿名的内存映射,比如由malloc()分配的内存。

       进程地址空间的任何有效地址都只能位于唯一的区域,这些内存区域不能相互覆盖。可以看到,在执行的进程中,每个不同的内存片断都对应一个独立的内存区域:栈、对象代码、全局变量、被映射的文件等等。

       内核使用内存描述符表示进程的地址空间。内存描述符mm_struct结构体表示,定义在文件中,该结构包含了和进程地址空间有关的全部信息。

 

VMA

       内存区域由vm_area_struct结构体描述,定义在文件中,内存区域在内核中也经常被称作虚拟内存区域或者VMA

       VMA标志是一种位标志,它定义在vm_area_struct结构中(该结构中的vm_flags子域)。和物理页的访问权限不同,VMA标志反映了内核处理页面索需要遵守的行为准则,而不是硬件要求。VM_IO标志内存区域中包含对设备I/O空间的映射。该标志通常在设备驱动程序执行mmap()函数进行I/O空间映射时才被设置,同时该标志也表示该内存区域不能被包含在任何进程的存放转存(core dump)中。VM_RESERVED标志内存区域不能被换出,它也是在设备驱动程序进行映射时被设置。

       vm_area_struct结构体中的vm_ops域指向与指定内存区域相关的操作函数表,内核使用表中的方法操作VMA

 

mmap()do_mmap():创建地址区间

       内核使用do_mmap()函数创建一个新的线性地址区间。但是说给函数创建一个新VMA并不非常准确,因为如果创建的地址区间和一个已经存在的地址区间相邻,并且它们具有相同的访问权限的话,那么两个区间将合并为一个。如果不能合并,那么就确实需要创建一个新的VMA了。但无论哪种情况,do_mmap()函数都会将一个地址区间加入到进程的地址空间中——无论是扩展已经存在的内存区域还是创建一个新的区域。

       do_mmap()函数声明在文件中,原型如下:

unsigned long do_mmap(struct file *file, unsigned long addr,

                  unsigned long len, unsigned long prot,

                  unsigned long flag, unsigned long offset)

       在用户空间可以通过mmap()函数调用获取内核函数do_mmap()的功能。mmap()系统调用原型如下:

       void *mmap2(void *start, size_t length,

                            int prot, int flags,

                            int fd, off_t pgoff)

       do_munmap()函数从特定的进程地址空间中删除指定地址区间,该函数在文件中声明:

       int do_munmap(struct mm_struct *mm, unsigned long start, size_t len)

       系统调用munmap()给用户空间程序提供了一种从自身地址空间中删除指定地址区间的方法,它和系统调用mmap()的作用相反:

       int munmap(void *start, size_t length)

 

mmap设备操作

对于驱动程序来说,内存映射可以提供给用户程序直接访问设备内存的能力。映射一个设备,意味着使用户空间的一段地址关联到设备内存上。无论何时,只要程序在分配的地址范围内进行读取或者写入,实际上就是对设备的访问。

并不是所有的设备都能进行mmap抽象。例如,串口设备和其他面向流的设备就无法实现这种抽象。mmap的另一个限制是映射都是以PAGE_SIZE为单位的。内核只能在页表一级处理虚拟地址;因此,被映射的区域必须是PAGE_SIZE的整数倍,而且必须位于起始于PAGE_SIZE整数倍地址的物理内存内。如果区域的大小不是页大小的整数倍,内核就通过生成一个稍微大一些的区域来容纳它。

mmap方法是file_operations结构中的一员,并且在执行mmap系统调用时就会调用该方法。在调用实际方法之前,内核会完成很多工作,而且该方法的原型与系统调用的原型由很大区别。

文件操作声明如下:

int (*mmap) (struct file * filp, struct vm_area_struct *vma);

其中vma参数包含了用于访问设备的虚拟地址区间的信息。大部分工作已经由内核完成了,要实现mmap,驱动程序只要为这一地址范围构造合适的页表即可,如果需要的话,就用一个新的操作集替换vma->vm_ops

有两种建立页表的方法:使用remap_page_range函数可一次建立所有的页表,或者通过nopage VMA方法每次建立一个页表。

构造用于映射一段物理地址的新页表的工作是由remap_page_range完成的,它的原型如下:

int remap_page_range(unsigned long virt_add, unsigned long phys_add,

                                   unsigned long size, pgprot_t prot);

nopage方法具有如下原型:

struct page (*nopage) (struct vm_area_struct *vma, unsigned long address,  int write_access);

       当用户进程访问当前不在内存中的VMA页面时,就会调用关联的VMA操作集中的nopage函数。参数address包含导致失效的虚拟地址,该地址向下取整到所在页的起始地址。函数nopage必须定位并返回指向用户所期望的页的struct page指针。这个函数还要调用get_page宏,以增加它所返回的页面的使用计数:get_page(struct page *pageptr)

       nopage方法在mremap系统调用中非常有用。应用程序使用mremap来改变应该映射区域的边界地址。mremap系统调用的原型如下:

        void  * mremap(void  *old_address,  size_t old_size , size_t new_size, unsigned long flags);

       Linuxmremap实现没有通知驱动程序被映射区域的变化。事实上,当区域减小时,它会通过unmap方法通知驱动程序;但区域变大时,却没有相应的回调函数可以利用。

       换句话说,在映射区域增长时驱动程序不会得到通知,因为nopage方法会在将来完成相应的工作,从而不必在真正需要之前使用内存。因此,如果我们需要支持mremap系统调用,就必须实现nopage方法。

       nopage方法必要的实现步骤如下:

       首先计算想得到的物理地址,然后用__va()将它转换为逻辑地址,最后用virt_to_page将逻辑地址转成一个struct page 返回指向这个struct page的指针。一般来说,直接从物理地址获得struct page是可能的,但是这样的代码很难在不同的体系结构之间移植。

       Remap_page_range的一个有意思的限制是,它只能对保留页和物理内存之外的物理地址给予访问。也就是说,remap_page_range不会允许你重映射常规地址(常规物理内存对应的地址),相反,它会改为映射到零页。因此,如果需要映射RAM的话,我们只能使用nopage方法。


下面是对simple.c(ldd2的样例代码)得一个简单的分析:

/*
 * Simple - REALLY simple memory mapping demonstration.
 *
 * Copyright (C) 2001 Alessandro Rubini and Jonathan Corbet
 * Copyright (C) 2001 O'Reilly & Associates
 *
 * The source code in this file can be freely used, adapted,
 * and redistributed in source or binary form, so long as an
 * acknowledgment appears in derived source files.  The citation
 * should list that the code comes from the book "Linux Device
 * Drivers" by Alessandro Rubini and Jonathan Corbet, published
 * by O'Reilly & Associates.   No warranty is attached;
 * we cannot take responsibility for errors or fitness for use.
 *
 * $Id: simple.c,v 1.8 2001/07/18 22:28:18 rubini Exp $
 */

#ifndef __KERNEL__
#  define __KERNEL__
#endif
#ifndef MODULE
#  define MODULE
#endif

#include
#include

#include    /* printk() */
#include    /* kmalloc() */
#include        /* everything... */
#include     /* error codes */
#include     /* size_t */
#include

#include "sysdep.h"

#ifdef LINUX_20
#  error "This module can't run with Linux-2.0"
#endif

static int simple_major = 0;
MODULE_PARM(simple_major, "i");
MODULE_AUTHOR("Jonathan Corbet");

/*
 * Forwards for our methods.
 */
int simple_open (struct inode *inode, struct file *filp);
int simple_release(struct inode *inode, struct file *filp);
int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma);
int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma);


/*
 * Our various sub-devices.
 */
/* Device 0 uses remap_page_range */
struct file_operations simple_remap_ops = {
    open:    simple_open,
    release: simple_release,
    mmap:    simple_remap_mmap,
};

/* Device 1 uses nopage */
struct file_operations simple_nopage_ops = {
    open:    simple_open,
    release: simple_release,
    mmap:    simple_nopage_mmap,
};

#define MAX_SIMPLE_DEV 2

struct file_operations *simple_fops[MAX_SIMPLE_DEV] = {
    &simple_remap_ops,
    &simple_nopage_ops,
};

/*
 * Open the device; all we have to do here is to up the usage count and
 * set the right fops.
 */
int simple_open (struct inode *inode, struct file *filp)
{
    unsigned int dev = MINOR(inode->i_rdev);

    if (dev >= MAX_SIMPLE_DEV)
        return -ENODEV;
    filp->f_op = simple_fops[dev];
    MOD_INC_USE_COUNT;
    return 0;
}


/*
 * Closing is even simpler.
 */
int simple_release(struct inode *inode, struct file *filp)
{
    MOD_DEC_USE_COUNT;
    return 0;
}



/*
 * Common VMA ops.
 */

void simple_vma_open(struct vm_area_struct *vma)
{ MOD_INC_USE_COUNT; }

void simple_vma_close(struct vm_area_struct *vma)
{ MOD_DEC_USE_COUNT; }


/*
 * The remap_page_range version of mmap.
 */

static struct vm_operations_struct simple_remap_vm_ops = {
    open:  simple_vma_open,
    close: simple_vma_close,
};

int simple_remap_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long offset = VMA_OFFSET(vma);  //设备文件的偏移量,以页为单位,(实际就是设备文件的物理地址)

    if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))    //如果偏移量超过实际物理内存的地址范围,或者文件标记为同步时
        vma->vm_flags |= VM_IO;                        //设置vm_flags标记为VM_IO,将VMA标记为一个内存映射的I/O区域
    vma->vm_flags |= VM_RESERVED;                    //VM_RESERVED标记内存区域不能被换出

    if (remap_page_range(vma->vm_start, offset, vma->vm_end-vma->vm_start,
                vma->vm_page_prot))                    //调用remap_page_range()构建必需的页表,该函数返回的值通常是0或者
        return -EAGAIN;                            //一个负的错误码;参数vma->vm_start指向需要映射的用户虚拟地址的起始
                                    //地址,offset指向虚拟地址所映射到的物理地址
                                    //vma->vm_end-vma->vm_start是映射的区域的大小
                                    //vma->vm_page_prot是新的VMA所需要的访问控制权限

    vma->vm_ops = &simple_remap_vm_ops;                    //更新VMA的操作集
    simple_vma_open(vma);                        //调用VMA操作集中的open函数,在打开VMA时增加模块的使用计数
    return 0;
}



/*
 * The nopage version.
 */
struct page *simple_vma_nopage(struct vm_area_struct *vma,
                unsigned long address, int write_access)
{
    struct page *pageptr;
    unsigned long physaddr = address - vma->vm_start + VMA_OFFSET(vma);    //获取虚拟地址address所对应的实际物理地址,VMA_OFFSET(vma)是
                                       //被映射的设备文件的偏移量,是VMA映射的起始地址。
    pageptr = virt_to_page(__va(physaddr));                //首先将physaddr通过__va转换为逻辑地址,然后用virt_to_page将逻辑
                                        //地址转换为一个struct page
    get_page(pageptr);                            //增加该页面的使用计数
    return pageptr;
}


#ifdef LINUX_22 /* wrapper for 2.2, which had a different nopage retval */
unsigned long simple_vma_nopage_22(struct vm_area_struct * area,
                unsigned long address, int write_access)
{
    return (unsigned long) simple_vma_nopage(area, address, write_access);
}
#define simple_vma_nopage simple_vma_nopage_22
#endif  /* LINUX_22 */
       
static struct vm_operations_struct simple_nopage_vm_ops = {
    open:    simple_vma_open,
    close:   simple_vma_close,
    nopage:  simple_vma_nopage,
};


int simple_nopage_mmap(struct file *filp, struct vm_area_struct *vma)
{
    unsigned long offset = VMA_OFFSET(vma);

    if (offset >= __pa(high_memory) || (filp->f_flags & O_SYNC))
        vma->vm_flags |= VM_IO;
    vma->vm_flags |= VM_RESERVED;

    vma->vm_ops = &simple_nopage_vm_ops;        //改变VMA的操作集(实现了nopage方法)。
    simple_vma_open(vma);
    return 0;
}



/*
 * Module housekeeping.
 */
static int simple_init(void)
{
    int result;

    SET_MODULE_OWNER(&simple_remap_ops);
    SET_MODULE_OWNER(&simple_nopage_ops);

    result = register_chrdev(simple_major, "simple", &simple_remap_ops);
    if (result     {
        printk(KERN_WARNING "simple: unable to get major %d\n", simple_major);
        return result;
    }
    if (simple_major == 0)
        simple_major = result;
    return 0;
}


static void simple_cleanup(void)
{
    unregister_chrdev(simple_major, "simple");
}


module_init(simple_init);
module_exit(simple_cleanup);

相关文章
CTF---Web入门第七题 猫抓老鼠
猫抓老鼠分值:10 来源: 实验吧 难度:难 参与人数:8697人 Get Flag:3740人 答题人数:3944人 解题通过率:95% catch!catch!catch!嘿嘿,不多说了,再说剧透了 解题链接: http://ctf5.
1448 0
|
3天前
|
SQL 人工智能 安全
【灵码助力安全1】——利用通义灵码辅助快速代码审计的最佳实践
本文介绍了作者在数据安全比赛中遇到的一个开源框架的代码审计过程。作者使用了多种工具,特别是“通义灵码”,帮助发现了多个高危漏洞,包括路径遍历、文件上传、目录删除、SQL注入和XSS漏洞。文章详细描述了如何利用这些工具进行漏洞定位和验证,并分享了使用“通义灵码”的心得和体验。最后,作者总结了AI在代码审计中的优势和不足,并展望了未来的发展方向。
|
11天前
|
编解码 Java 程序员
写代码还有专业的编程显示器?
写代码已经十个年头了, 一直都是习惯直接用一台Mac电脑写代码 偶尔接一个显示器, 但是可能因为公司配的显示器不怎么样, 还要接转接头 搞得桌面杂乱无章,分辨率也低,感觉屏幕还是Mac自带的看着舒服
|
18天前
|
存储 人工智能 缓存
AI助理直击要害,从繁复中提炼精华——使用CDN加速访问OSS存储的图片
本案例介绍如何利用AI助理快速实现OSS存储的图片接入CDN,以加速图片访问。通过AI助理提炼关键操作步骤,避免在复杂文档中寻找解决方案。主要步骤包括开通CDN、添加加速域名、配置CNAME等。实测显示,接入CDN后图片加载时间显著缩短,验证了加速效果。此方法大幅提高了操作效率,降低了学习成本。
2780 8
|
13天前
|
存储 缓存 关系型数据库
MySQL事务日志-Redo Log工作原理分析
事务的隔离性和原子性分别通过锁和事务日志实现,而持久性则依赖于事务日志中的`Redo Log`。在MySQL中,`Redo Log`确保已提交事务的数据能持久保存,即使系统崩溃也能通过重做日志恢复数据。其工作原理是记录数据在内存中的更改,待事务提交时写入磁盘。此外,`Redo Log`采用简单的物理日志格式和高效的顺序IO,确保快速提交。通过不同的落盘策略,可在性能和安全性之间做出权衡。
1576 12
|
5天前
|
人工智能 关系型数据库 Serverless
1024,致开发者们——希望和你一起用技术人独有的方式,庆祝你的主场
阿里云开发者社区推出“1024·云上见”程序员节专题活动,包括云上实操、开发者测评和征文三个分会场,提供14个实操活动、3个解决方案、3 个产品方案的测评及征文比赛,旨在帮助开发者提升技能、分享经验,共筑技术梦想。
715 95
|
1月前
|
弹性计算 人工智能 架构师
阿里云携手Altair共拓云上工业仿真新机遇
2024年9月12日,「2024 Altair 技术大会杭州站」成功召开,阿里云弹性计算产品运营与生态负责人何川,与Altair中国技术总监赵阳在会上联合发布了最新的“云上CAE一体机”。
阿里云携手Altair共拓云上工业仿真新机遇
|
18天前
|
人工智能 Serverless API
AI助理精准匹配,为您推荐方案——如何快速在网站上增加一个AI助手
通过向AI助理提问的方式,生成一个技术方案:在网站上增加一个AI助手,提供7*24的全天候服务,即时回答用户的问题和解决他们可能遇到的问题,无需等待人工客服上班,显著提升用户体验。
1468 9
|
6天前
|
SQL 存储 人工智能
【产品升级】Dataphin V4.3重大升级:AI“弄潮儿”,数据资产智能化
DataAgent如何助理业务和研发成为业务参谋?如何快速低成本的创建行业数据分类标准?如何管控数据源表的访问权限?如何满足企业安全审计需求?
355 0
【产品升级】Dataphin V4.3重大升级:AI“弄潮儿”,数据资产智能化
|
2天前
|
人工智能 自然语言处理 程序员
提交通义灵码创新实践文章,重磅好礼只等你来!
通义灵码创新实践征集赛正式开启,发布征文有机会获得重磅好礼+流量福利,快来参加吧!
196 7