搜尋此網誌

2011年7月31日 星期日

study kernel(3) -- memory,vfs

一、 内存及内存管理
1. 内存地址
三种不同的地址:
1. 逻辑地址:每个逻辑地址都有一个段和偏移量组成,偏移量指明从段开始的地方到实际地址之间的距离。Linux中逻辑地址总是与线性地址一致。
2. 线性地址:虚拟地址。32位系统有4G的线性地址,64位系统有8G线性地址。
3. 物理地址:物理内存的实际地址。
地址转换关系:
逻辑地址通过分段单元转换为线性地址,之后再通过分页单元转换为物理地址。
2. 页
页:线性地址被分成固定长度为单位的组。页内部连续的线性地址被映射到连续的物理地址中。页既指一组线性地址,又指这组地址中的数据。Linux每一页有4KB容量。
页框:或物理页。一个页框包含一个页,因此页框长度与页长度一致。页可存放在任何一个物理页中。页框就是物理内存。
页表:保存线性地址和物理地址之间映射的页。
内核中用页述符struct page描述每一个页框的状态信息,所有的页描述符都保存在mem_map数组中。
struct page {
unsigned long flags; //描述页框状态
atomic_t _count; //页框的引用计数
atomic_t _mapcount; //页框中页表数目
void *virtual;; //页框的虚拟地址
……
}
转换关系如图:


3. 内存管理区

内核物理内存划分为不同的区,并用struct zone描述每个区。每个管理区的分配器可以在他的物理内存区域添加删除页框。

4. slab分配器
slab层把不同对象划分为高速缓存组,每个高速缓存被划分为多个slab,每个slabe由一个或多个连续的页框组成,页框中包含对象的数据结构。
如struct inode由inode_cachep高速缓存进行分配。这个高速缓存有一个或多个slab组成。每个slab包含尽可能多的struct indoe对象。当内核请求分配新的inode结构时,内核从部分满或空的slab返回一个指向已分配但未使用的结构的指针。当内核用完后,slab分配器把该对象标记为空闲。
Slab的使用:(struct task_struct)
a. 创建高速缓存
static struct kmem_cache *task_struct_cachep; //全局变量
void __init fork_init(unsigned long mempages) //内核初始化调用
{
    task_struct_cachep =
    kmem_cache_create("task_struct", sizeof(struct task_struct),
    ARCH_MIN_TASKALIGN,SLAB_PANIC|SLAB_NOTRACK, NULL);
}
b. 申请进程描述符
当创建子进程会有如下过程
fork()->sys_fork()->do_fork()->copy_process()->dup_task_struct()
static struct task_struct *dup_task_struct(struct task_struct *orig)
{
    struct task_struct *tsk;
    tsk = kmem_cache_alloc (task_struct_cachep, GFP_KERNEL);
                                            //从高速缓存中返回一个进程描述符的指针
    ……
}
c. 释放进程描述符
当进程被销毁时会有如下过程:
free_task()->free_task_struct()
即执行kmem_cache_free(task_struct_cachep, (tsk)),将tsk所指的进程描述符标记为空闲。
二、 进程地址空间
1. 进程虚拟内存的使用
Linux操作系统采用虚拟内存管理技术,使得每个进程都有各自互不干涉的进程地址空间。进程的可用32 位地址系统支持的全部4G 线性空间。进程的线性地址空间分成两部分:1. 从0x00000000 到 0xbfffffff 的线性地址,无论用户态还是内核态的进程都可以寻址。2. 从0xc0000000 到 0xffffffff 的线性地址,只有内核态的进程才能寻址。
对普通进程来讲,它都会涉及到5种不同的数据段。
1.代码段:代码段是用来存放可执行文件的操作指令,也就是说是它是可执行程序在内存种的镜像。代码段需要防止在运行时被非法修改,所以只准许读取操作,而不允许写入操作。
2.数据段:数据段用来存放可执行文件中已初始化全局变量,就是存放程序静态分配的变量和全局变量。可读可写。
3.BSS段:包含了程序中未初始化全局变量,在内存中 bss段全部置零。
4.堆:堆是用于存放进程运行中被动态分配的内存段,它大小并不固定,可动态扩张或缩减。当进程调用malloc等函数分配内存时,新分配的内存就被动态添加到堆上;当利用free等函数释放内存时,被释放的内存从堆中被剔除。
5. 栈:栈是用户存放程序临时创建的局部变量。除此以外在函数被调用时,其参数也会被压入发起调用的进程栈中,并且待到调用结束后,函数的返回值也回被存放回栈中。由于栈的先进先出特点,所以栈特别方便用来保存/恢复调用现场。从这个意义上将我们可以把堆栈看成一个临时数据寄存、交换的内存区。
2. 创建进程的地址空间
内核使用内存描述符struct mm_struct表示进程的地址空间。当创建新的进程时内核调用copy_mm()函数建立新进程的所有页表和内存描述符来创建进程的地址空间:
static int copy_mm(unsigned long clone_flags, struct task_struct * tsk)
{
    struct mm_struct * mm, *oldmm;
    oldmm = current->mm;

    if (clone_flags & CLONE_VM) { //线程使用父进程地址空间
        atomic_inc(&oldmm->mm_users);
        mm = oldmm;
        goto good_mm;
    }

    mm = dup_mm(tsk); //创建子进程地址空间
}

struct mm_struct *dup_mm(struct task_struct *tsk)
{
    struct mm_struct *mm, *oldmm = current->mm;

    mm = allocate_mm(); //从slab中分配一个mm_struct
    memcpy(mm, oldmm, sizeof(*mm));

    mm_init(mm, tsk); //初始化结构体并为进程创建pgd和pte

    init_new_context(tsk, mm);

    dup_mm_exe_file(oldmm, mm);

    dup_mmap(mm, oldmm); //创建VMA
}
VMA指定地址空间内连续区间上的一个独立内存范围来代表多种类型的内存区域。VMA用struct vm_area_struct描述。
static int dup_mmap(struct mm_struct *mm, struct mm_struct *oldmm)
{
    struct vm_area_struct *mpnt, tmp;
    struct rb_node **rb_link, rb_parent;

    //扫描父进程的VMA链表
    for (mpnt = oldmm->mmap; mpnt; mpnt = mpnt->vm_next) {
        tmp = kmem_cache_alloc(vm_area_cachep, GFP_KERNEL);
        *tmp = *mpnt; //复制父进程VMA

        //将复制的VMA插入到子进程的VMA链表和红黑树中
        _vma_link_rb(mm, tmp, rb_link, rb_parent);
        rb_link = &tmp->vm_rb.rb_right;
        rb_parent = &tmp->vm_rb;
    }

        /*拷贝父进程所有vm_area_struct对应的页目录、页表。将父子进程中私
         有的、可写的页标记为只读的,以便这种页面能用写时复制机制进行处理
        */
        copy_page_range(mm, oldmm, mpnt);
}
虚拟地址、虚拟地址区域、内存描述符之间关系如图:
3. 缺页异常处理
内核中的函数直接获得动态内存,内核是操作系统中优先级最高的成分,可采用kmalloc/vmalloc分配连续和非连续线性区得到内存。
用户态进程调用malloc分配内存时,请求被认为是不紧迫的,因此,当用户态进程请求动态内存时,并没有立即获得实际的物理页框,而仅仅获得对一个新的VMA。这样,当用户进程真正向这些线性区写的时候,就会产生缺页异常,在缺页异常处理程序do_page_fault()中获得真正的物理内存。

三、 虚拟文件系统
虚拟文件系统(VFS)是一个内核软件层,用来处理与Unix标准文件系统相关的所有系统调用。它与驱动程序联系的更加紧密。其分层结构如图:
1. 主要数据结构
a. 超级块对象结构struct super_block
该结构保存了一个被安装在linux系统上的文件系统的信息。对于基于磁盘的文件系统,该结构一般和保存在磁盘上的"文件系统控制块"对应。也就是说如果是磁盘文件系统,该结构保存的磁盘文件系统的控制信息。
b. 索引节点对象结构struct inode
该结构中存储的是一个特定文件的一般信息,对于一个基于磁盘的文件系统,该结构对应磁盘上的文件数据控制块。每一个inode结构都对应一个inode节点号,这个节点号是唯一的,它也唯一标识一个文件系统中的文件。
c. 文件对象结构struct file
该结构中存储的是一个打开的文件和打开这个文件的进程间的交互信息。该结构保存在内核的内存区,在打开文件时被创建,关闭文件时被释放。
d. 目录项对象结构struct dentry
该结构存储的是目录实体和对应的文件的关联信息。
2. 各数据结构之间的关系
内核创建文件对象并返回文件描述符索引号给用户空间。文件对象引用目录项对象,后者引用索引节点对象。这两个对象都引用超级块对象。多个文件对象可以引用同一个目录项对象。一个目录项对象还可能引用另一个目录项对象。如图:

沒有留言:

張貼留言