ARTS #38

Algorithm

11. 盛最多水的容器

// 暴力解法
func maxArea1(height []int) (result int) {
    i := 0
    result = 0
    for i < len(height) {
        k := i + 1
        for k < len(height) {
            current := (k - i) * min(height[i], height[k])
            if current > result {
                result = current
            }
            k++
        }
        i++
    }
    return
}

func min(a int, b int) (result int) {
    if a < b {
        return a
    }
    return b
}

// 官方双指针解法
func maxArea2(height []int) (result int) {
    left := 0
    right := len(height) - 1
    result = 0
    for left < right {
        current := (right - left) * min(height[left], height[right])
        if current > result {
            result = current
        }
        if height[left] < height[right] {
            left += 1
            continue
        }
        if height[left] >= height[right] {
            right -= 1
            continue
        }
    }
    return
}

Review

10 Common Software Architectural Patterns in a nutshell
文章介绍了一些常用的设计模式的优缺点和使用场景。

TIP

这周开发过程涉及到状态机相关需求,所以了解了fsm相关概念。并使用Go开源fsm库完成了业务需求。

Share

继续学习“操作系统32讲”

L23 段页结合的实际内存管理

虚拟内存

用户角度只能看到分段的虚拟内存,硬件角度虚拟地址映射的是物理内存的页号。操作系统负责虚拟地址到物理地址的转换( 重定位也就是地址翻译)。

  1. 首先根据段号+偏移(CS:IP)去段表)找到对应的虚拟地址。CS对应段号,IP对应偏移量。比如CS:IP为1:30,段号找到基址为0x4800,再加上偏移量得到虚拟地址。
段号  基址  长度  保护
0  0x4000 0x0800 R
1  0x4800 0x1400 R/W
2  0xF000 0x1000 R/W
3  0x0000 0x3000 R
  1. 根据步骤1得到的虚拟地址,然后去页表里面查询得到物理地址。步骤1、2实际都是由MMU自动完成。

操作系统在fork一个进程过程如何实现内存分配和虚实地址映射

  1. 从虚拟内存中分配一段内存段给新的进程,然后分配对应段表。这里讲的是比较简单的内存分割算法(根据进程ID递增分配内存块)
// 这个函数讲的就是fork进程的时候,把父进程的内存信息copy一份到子进程
int copy_mem(int nr,task_struct *p){ // p就是被fork的进程的pcb
    unsigned long new_data_base;
    new_data_base = nr * 0x4000000; // 64M*nr nr代表第几个进程,32位操作系统每个进程的内存空间是64M nr*64就是偏移量
    set_base(p->ldt[1],new_data_base); // 设置基地址,这里可能是数据段。并且建立段表
    set_base(p->ldt[2],new_data_base); // 设置基地址,这里可能是代码段。并且建立段表
}
  1. 分配物理内存,建立页表。这里因为和父进程共用内存,所以不需要分配新的物理内存,直接建立页表就可以。

32位操作系统的多级页表:

10bits 10bits 12bits
页目录号 页号 offset(页内偏移)

分配物理内存

int copy_mem(int nr,task_struct *p){
    unsigned long old_data_base;
    old_data_base = get_base(current->ldt[2]);
    copy_page_tables(old_data_base, new_data_base, data_limit);
}

// 建立子进程的页表
int copy_page_tables(unsigned long from, unsigned long to, long size){
    from_dir = (unsigned long *)((from >> 20) & 0xffc); // 页内偏移为12bits 页号为10bits,先右移22位得到页目录号 每个页目录号对应的页号为4字节 所以需要向左偏移2位 最终就是向右偏移20位
    to_dir = (unsigned long *)((to >> 20) & 0xffc);
    size = (unsigned long *)(size + 0x3ffffff) >> 22; // 有多个页目录章节
    for(; size-->0;from_dir++, to_dir++){ // 建立新的页目录章节的具体页号内容映射
        from_page_table = (0xffffff000 & *from_dir); 
        to_page_table = get_free_page();  // 去物理内存中申请新的内存分配给新的页号
        *to_dir = ((unsigned long) to_page_table) | 7;
    }
    for(;nr-->0;from_page_table++,to_page_table++){ // 拷贝父进程的页号内容到子进程
        this_page = *from_page_table; // from_page_table是父进程的内存指针,*from_page_table就是指针指向的内容
        this_page &= ~2; // 父子进程共用内存,所以需要设置为只读
        *to_page_table = this_page; // 父进程内容给子进程
        *from_page_table = this_page; // 内存块修改成只读,所以需要给父进程也重新赋值一下
        this_page -= LOW_MEM;
        this_page >>= 12;
        mem_map[this_page]++;
    }
}

L24 内存换入-请求调页

为什么需要内存换入

32位操作系统在用户角度来看,4G的虚拟内存都是可以随意使用的。但是实际的物理内存可能就只有1G或者3G,为了较小的物理内存能够支撑4G的虚拟内存,就引入了内存换入。
用户程序、数据在没有使用的时候存储在磁盘上,在需要用到的时候将需要的数据写入到内存上,这就是内存换入。

如何实现内存换入

  1. CS:IP查找段表之后,得到虚拟地址。
  2. MMU拿虚拟地址去查找页表的时候,如果页表上找不到对应的页框号这时候会触发一个缺页中断(page fault)。
  3. 中断处理程序处理缺页中断的时候,先去磁盘找到对应数据,然后会去内存中get_free_page找到一个空白的内存段并将磁盘中的数据写入到free_page中。
  4. 中断处理完成之后,继续完成剩下程序逻辑。整个内存换入完成。

L25 内存换出

内存换出常用算法

  1. FIFO:先入先出算法 会产生较多的缺页次数
  2. MIN算法:选最远将使用的页淘汰。这个是最优算法,但是无法预测未来哪些页会被访问。
  3. LRU(least recent used):最近最长一段时间没有使用的页进行淘汰。最近不常用的页表,一般来说未来也不会被经常使用所以可以优先进行淘汰。这个也符合计算机的局部性现象。

LFU算法的实现

  1. 使用页码栈来实现,最准确。但是每次地址访问都需要修改栈,实现代价太大。
  2. 将页组织成循环队列,给每个页加一个引用位(reference bit)。时间轮(clock)算法
  • 每次访问一页时,硬件MMU自动设置引用位为1
  • 选择淘汰页:触发缺页中断的时候遍历循环队列并扫描引用位,如果引用位是1的时候清0并继续扫描;如果引用位是0的话,就选择该页进行淘汰。 SCR算法(second chance replacement)。
  1. clock算法的改进
  • 缺页中断触发几率很小,同时MMU每次访问页表的时候都会自动把引用位置为1。这样就会导致很大概率下缺页中断处理过程,时间轮上的所有引用位都为1,这时候就会导致每次淘汰的都是最先来的页。最终退化成FIFO。
  • 解决方案:引入一个扫描指针。扫描指针不断地定时轮训循环队列并把引用位置为0。扫描指针移动速度比淘汰指针快,这样子实现定时清除引用位。从而防止clock算法退化成FIFO。
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
【社区内容提示】社区部分内容疑似由AI辅助生成,浏览时请结合常识与多方信息审慎甄别。
平台声明:文章内容(如有图片或视频亦包括在内)由作者上传并发布,文章内容仅代表作者本人观点,简书系信息发布平台,仅提供信息存储服务。

相关阅读更多精彩内容

  • 文末领取大图。 这不是一篇教你如何创建一个操作系统的文章,相反,这是一篇指导性文章,教你从几个方面来理解操作系统。...
    cuixiaoyan阅读 4,481评论 0 0
  • 1.1 课程概述 基本概念及原理 操作系统介绍 中断及系统调用 内存管理 进程及线程 调度 同步 文件系统 I/O...
    liuzhangjie阅读 5,245评论 0 0
  • 第一章.计算机系统概述1.基本构成2.指令的执行3.中断3.1 目的3.2 类型3.3 中断控制流3.4 中断处理...
    某WAP阅读 4,351评论 0 0
  • 操作系统基本概念 操作系统是计算机科学研究基石之一。 功能 管理硬件(如设备驱动:实现用户提出的I/O操作请求,完...
    Hengtao24阅读 10,043评论 2 14
  • 1、导论 与用户交互的程序: 基于文本的shell 基于图标的图形化用户界面(GUI) 操作系统所处的位置: 多数...
    曹元_阅读 5,370评论 0 4

友情链接更多精彩内容