• linux0.11-虚拟内存


    参考 :5.3 Linux对内存的管理与使用

    linuxkernelsrc-Linux文档类资源-CSDN下载

    分页与分段

    逻辑地址

            Linux0.11 中,给每个程序(进程)都划分了总容量为64MB的虚拟内存空间。因此程序的逻辑地址范围:0x00-0x4000000.现在32位系统是4G.

    fs\exec.c 在change_idt中设置
        data_limit = 0x4000000;//64M

    1. static unsigned long change_ldt(unsigned long text_size,unsigned long * page)
    2. {
    3. unsigned long code_limit,data_limit,code_base,data_base;
    4. int i;
    5. code_limit = text_size+PAGE_SIZE -1;
    6. code_limit &= 0xFFFFF000;
    7. data_limit = 0x4000000;//64M
    8. code_base = get_base(current->ldt[1]);
    9. data_base = code_base;
    10. set_base(current->ldt[1],code_base);
    11. set_limit(current->ldt[1],code_limit);
    12. set_base(current->ldt[2],data_base);
    13. set_limit(current->ldt[2],data_limit);
    14. /* make sure fs points to the NEW data segment */
    15. __asm__("pushl $0x17\n\tpop %%fs"::);
    16. data_base += data_limit;
    17. for (i=MAX_ARG_PAGES-1 ; i>=0 ; i--) {
    18. data_base -= PAGE_SIZE;
    19. if (page[i])
    20. put_page(page[i],data_base);
    21. }
    22. return data_limit;
    23. }

    线程地址:(分段机制)

            逻辑地址(或者说是段中的偏移地址),加上相应段的基地址就生成一个线性地址。Linux 0.11 中 每个任务的在线性地址空间的起始位置是(任务号)*64M(逻辑地址)。最大任务是64.因此全部任务使用的线性地址空间范围是64*64M = 4G.

    物理地址:(分页机制)

    这也是为什么线性地址的计算方式是:任务号*64M 保证不同的任务的线性地址不同,这样使用同一个页目录表的使用,计算出来的物理地址空间不同

            页目录表的创建在head.s中

    boot\head.s

    1. //1. 加载内核运行时的个数据段寄存器,重新设置中断描述符表
    2. //2. 开启内核正常运行时的协处理器等资源
    3. //3. 设置内存管理的分页机制
    4. //4. 跳转到main.c开始运行
    5. /*
    6. * linux/boot/head.s
    7. *
    8. * (C) 1991 Linus Torvalds
    9. */
    10. /*
    11. * head.s contains the 32-bit startup code.
    12. *
    13. * NOTE!!! Startup happens at absolute address 0x00000000, which is also where
    14. * the page directory will exist. The startup code will be overwritten by
    15. * the page directory.
    16. */
    17. .text
    18. .globl _idt,_gdt,_pg_dir,_tmp_floppy_area
    19. _pg_dir:
    20. startup_32:
    21. movl $0x10,%eax
    22. mov %ax,%ds
    23. mov %ax,%es
    24. mov %ax,%fs
    25. mov %ax,%gs
    26. lss _stack_start,%esp
    27. call setup_idt
    28. call setup_gdt
    29. movl $0x10,%eax # reload all the segment registers
    30. mov %ax,%ds # after changing gdt. CS was already
    31. mov %ax,%es # reloaded in 'setup_gdt'
    32. mov %ax,%fs
    33. mov %ax,%gs
    34. lss _stack_start,%esp
    35. xorl %eax,%eax
    36. 1: incl %eax # check that A20 really IS enabled
    37. movl %eax,0x000000 # loop forever if it isn't
    38. cmpl %eax,0x100000
    39. je 1b

     中调用:    

    call setup_idt
    call setup_gdt

    缺页中断:p98 4.4 分页

           页表一共需要  1K * 1K * 4(一个表项占四字节) = 4MB.在使用过程中,通过访问页目录表中第0位p确认 二级页表是否存在,不存在则创建,因此不需要一开始就创建4MB的页表。通过缺页中断的方式,动态加载页表,同时还可以将不使用的页表放入磁盘,缺页时加载到内存。

          缺页中断的方式只能用于主内存,不能用于内核内存区

          

    写时复制:13章内存管理

    fork.c

    1. *
    2. * Ok, this is the main fork-routine. It copies the system process
    3. * information (task[nr]) and sets up the necessary registers. It
    4. * also copies the data segment in it's entirety.
    5. */
    6. // 所谓进程创建就是对0号进程或者当前进程的复制
    7. // 就是结构体的复制 把task[0]对应的task_struct 复制一份
    8. //除此之外还要对栈堆拷贝 当进程做创建的时候要复制原有的栈堆
    9. // nr就是刚刚找到的空槽的pid
    10. int copy_process(int nr,long ebp,long edi,long esi,long gs,long none,
    11. long ebx,long ecx,long edx,
    12. long fs,long es,long ds,
    13. long eip,long cs,long eflags,long esp,long ss)
    14. {
    15. struct task_struct *p;
    16. int i;
    17. struct file *f;
    18. //其实就是malloc分配内存
    19. p = (struct task_struct *) get_free_page();//在内存分配一个空白页,让指针指向它
    20. if (!p)
    21. return -EAGAIN;//如果分配失败就是返回错误
    22. task[nr] = p;//把这个指针放入进程的链表当中
    23. *p = *current;//把当前进程赋给p,也就是拷贝一份 /* NOTE! this doesn't copy the supervisor stack */
    24. //后面全是对这个结构体进行赋值相当于初始化赋值
    25. p->state = TASK_UNINTERRUPTIBLE;
    26. p->pid = last_pid;
    27. p->father = current->pid;
    28. p->counter = p->priority;
    29. p->signal = 0;
    30. p->alarm = 0;
    31. p->leader = 0; /* process leadership doesn't inherit */
    32. p->utime = p->stime = 0;
    33. p->cutime = p->cstime = 0;
    34. p->start_time = jiffies;//当前的时间
    35. p->tss.back_link = 0;
    36. p->tss.esp0 = PAGE_SIZE + (long) p;
    37. p->tss.ss0 = 0x10;
    38. p->tss.eip = eip;
    39. p->tss.eflags = eflags;
    40. p->tss.eax = 0;//把寄存器的参数添加进来
    41. p->tss.ecx = ecx;
    42. p->tss.edx = edx;
    43. p->tss.ebx = ebx;
    44. p->tss.esp = esp;
    45. p->tss.ebp = ebp;
    46. p->tss.esi = esi;
    47. p->tss.edi = edi;
    48. p->tss.es = es & 0xffff;
    49. p->tss.cs = cs & 0xffff;
    50. p->tss.ss = ss & 0xffff;
    51. p->tss.ds = ds & 0xffff;
    52. p->tss.fs = fs & 0xffff;
    53. p->tss.gs = gs & 0xffff;
    54. p->tss.ldt = _LDT(nr);
    55. p->tss.trace_bitmap = 0x80000000;
    56. if (last_task_used_math == current)//如果使用了就设置协处理器
    57. __asm__("clts ; fnsave %0"::"m" (p->tss.i387));
    58. if (copy_mem(nr,p)) {//老进程向新进程代码段和数据段进行拷贝
    59. task[nr] = NULL;//如果失败了
    60. free_page((long) p);//就释放当前页
    61. return -EAGAIN;
    62. }
    63. for (i=0; i<NR_OPEN;i++)//
    64. if (f=p->filp[i])//父进程打开过文件
    65. f->f_count++;//就会打开文件的计数+1,说明会继承这个属性
    66. if (current->pwd)//跟上面一样
    67. current->pwd->i_count++;
    68. if (current->root)
    69. current->root->i_count++;
    70. if (current->executable)
    71. current->executable->i_count++;
    72. set_tss_desc(gdt+(nr<<1)+FIRST_TSS_ENTRY,&(p->tss));
    73. set_ldt_desc(gdt+(nr<<1)+FIRST_LDT_ENTRY,&(p->ldt));
    74. p->state = TASK_RUNNING;//把状态设定为运行状态 /* do this last, just in case */
    75. return last_pid;//返回新创建进程的id号
    76. }

    在    if (copy_mem(nr,p)) {//老进程向新进程代码段和数据段进行拷贝中:

    1. // 对内存拷贝
    2. // 主要作用就是把代码段数据段等栈上的数据拷贝一份
    3. int copy_mem(int nr,struct task_struct * p)
    4. {
    5. unsigned long old_data_base,new_data_base,data_limit;
    6. unsigned long old_code_base,new_code_base,code_limit;
    7. code_limit=get_limit(0x0f);
    8. data_limit=get_limit(0x17);
    9. old_code_base = get_base(current->ldt[1]);
    10. old_data_base = get_base(current->ldt[2]);
    11. if (old_data_base != old_code_base)
    12. panic("We don't support separate I&D");
    13. if (data_limit < code_limit)
    14. panic("Bad data_limit");
    15. new_data_base = new_code_base = nr * 0x4000000;
    16. p->start_code = new_code_base;
    17. set_base(p->ldt[1],new_code_base);
    18. set_base(p->ldt[2],new_data_base);
    19. if (copy_page_tables(old_data_base,new_data_base,data_limit)) {
    20. free_page_tables(new_data_base,data_limit);
    21. return -ENOMEM;
    22. }
    23. return 0;
    24. }

    在父进程创建子进程后,此时只是将A进程的页目录项拷贝给B进程, 并不会拷贝页表项(4M 太大了),此时父子进程共用一段物理内存。同时将表项中的第一位  RW

    至0.仅仅允许读不允许写操作。 一旦有父进程或子进程对这块内存进行写操作,就会引发页面出错:page_fault(14号)

            在该异常的中断服务函数会给写进程复制一个新的物理页面,此时父子进程就各自有一块要写的内存,然后设置该内存为可读写状态,然后返回重新进行刚刚异常的写操作

            写操作=》页面异常中断=》处理写保护异常=》重新分配物理内存页面=》重新执行写操作

    代码位置:mm/memory.c

  • 相关阅读:
    测试计划包括哪些主要步骤和信息?
    阿里云上如何过等保,收费标准怎么样?
    深度解析服务发布策略之金丝雀发布
    自动驾驶——基础架构
    让世界更精彩,拓世科技新品亮相世界舞台!虚实融合 智兴百业,2023世界VR产业大会正式开幕!
    应用配置文件 student-system.yaml 生成 Deployment报错,有没有厉害的解决一下的
    Dubbo-聊聊通信模块设计
    OA项目之会议通知(查询&是否参会&反馈详情)
    高新技术企业领域划分
    2021 年的 Flutter 状态管理:如何选择?
  • 原文地址:https://blog.csdn.net/LIJIWEI0611/article/details/126455356