os

233 阅读8分钟

select

  • 使用fd_set存储信息,位图,bitmap,每次都需要将信息从用户态拷贝到内核态
  • 内核每次都需要扫描整个线性表,判断哪个事件发生变化
  • 最大并发数也有一定限制

poll

  • 使用一个结构体数组进行传递,还是会存在数据在内核态与用户态之间的传递
  • 还是需要遍历才可知状态变化的事件

epoll

  • 调用 create 建立一个epoll对象
  • 调用 ctl 函数创建或者销毁需要监控的对象
  • 不需要内存的多次拷贝
  • 使用红黑树结构 检索效率高
  • 基于回调,不需要遍历整个结构,直接放到 readyList 里面,调用 wait 获取事件列表
  1. 用户态和内核态
    1. cpu指令集
      1. cpu指令集是有权限划分的ring0-3 权限依次降低
      2. ring0 为内核态 ring3为用户态 为了保护操作系统
      3. 执行内核空间的代码,具有ring 0保护级别,有对硬件的所有操作权限,可以执行所有CPU指令集,访问任意地址的内存,在内核模式下的任何异常都是灾难性的,将会导致整台机器停机
      4. 在用户模式下,具有ring 3保护级别,代码没有对硬件的直接控制权限,也不能直接访问地址的内存,程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存,在这种保护模式下,即时程序发生崩溃也是可以恢复的,在电脑上大部分程序都是在,用户模式下运行的
    2. 进程的虚拟地址 以linux32位为例
      1. 进程的虚拟地址分为两部分,内核空间和用户空间
      2. 每个进程的高位1G为内核空间且是被所有进程共享的
    3. 状态的切换
      1. 保留用户态现场(上下文、寄存器、用户栈等)
      2. 复制用户态参数,用户栈切到内核栈,进入内核态
      3. 额外的检查(因为内核代码对用户不信任)
      4. 执行内核态代码
      5. 复制内核态代码执行结果,回到用户态
      6. 恢复用户态现场(上下文、寄存器、用户栈等)
  2. 内存
    1. 虚拟地址和物理地址
      1. 虚拟地址是在进程中使用的内存地址,进程不必关心物理地址是多少
      2. 在硬件的地址称为物理地址
      3. 操作系统会提供一种机制,将不同进程的虚拟地址和物理地址进行映射关系
      4. 进程持有的内存地址会通过cpu的MMU映射到其物理地址,再去访问到他的物理内存
      5. 目的: 让进程和物理内存地址解耦 使进程之间保持相互独立和隔离
    2. 映射方式
      1. 分段
        1. 虚拟内存空间中的虚拟内存按照其逻辑划分为代码段、数据段、堆段、栈段几部分
        2. 通过段寄存器中的段表,来将虚拟地址与物理地址进行映射。段表中存储了每一个逻辑段的段号对应的物理内存的起始地址。
        3. 对于每一个在虚拟内存中存储的数据,其虚拟地址都以其所在的段号以及段内偏移组成。
        4. 产生内存碎片 内存和硬盘交换的效率低
      2. 分页机制
        1. 将内存空间分为大小相同的页 映射关系通过页表完成
        2. 页表中不仅保存了页号,物理内存地址,还保留了该物理页的访问权限,用以实现对页的访问控制
        3. 不需要将程序一次性加载进内存,需要的时候加载,如找不到则发生缺页中断,加载到内存中
        4. 页表占用空间很大,所以采用多级页表,32位使用2级页表,64位使用4级页表
        5. 快表 TLB 将最常访问的几个页表项存储到TLB中 相当于缓存 不需要四级页表的地址映射
      3. 段页式
        1. 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制
        2. 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页
        3. 虚拟地址结构由段号、段内页号和页内位移三部分组成
        4. 段号 - 段项 - 页表地址 - 段页表 - 段页项 - 物理基页地址+偏移地址 = 内存地址
        5. 虚拟地址由段号 段内页号 偏移量三部分组成

线程和进程 分段分页

  1. linux 线程模型 www.360doc.com/content/19/…
    1. 多对一 很多个用户线程对应一个内核调度实体 多线程操作时 不需要内核态和用户态频繁切换 一个线程调用内核 则其他线程阻塞
    2. 一对一 一个用户线程对应一个内核调度实体 线程的每次操作会在用户态和内核态切换 如果系统出现大量线程,会对系统性能有影响
    3. m对n 一个用户线程阻塞时,其他用户线程仍然可以执行 可以实现较完整的调度、优先级等
    4. 线程是操作系统调度和执行的基本单位,linux中被称为轻量级进程,其实linux中是没有线程概念的,线程只是一个和其他进程共享某些资源的进程,线程只是进程间共享资源的一种方式,有一个task_struct结构
    5. 早期通过一个管理线程来满足posix协议提出的要求,NPTL(Native POSIX Threading Library),在linux2.6解决了之前存在的一些问题,有了线程组的概念,task_struct多了一个tgid(thread group id)的字段,主线程的tgid=pid,非主线程的tgid=主线程的pid。

进程调度

进程通信的方式

  1. 管道 一个固定大小的缓存区 由内核管理 使用了锁 等待队列和信号来实现并发访问
    1. 无名管道 半双工 只能在父子和兄弟进程间使用
    2. 命名管道 全双工 可以双向传输 可以在无关的进程间使用 多了一个文件标识 存在于文件系统中
  2. 信号量 只能同时被一个进程访问
    1. 是一个计数器 用于进程间同步 如果要传递数据要结合共享内存来使用
    2. 对信号量的操作是一次原子操作
  3. 消息队列
    1. 保存在内核的一个消息链表
    2. 具有特定的格式和特定的优先级
  4. 共享内存
    1. 可以直接读写内存,效率高 无需复制 需要考虑同步问题
    2. 实现方式 内存映射 共享内存机制
  5. socket通信
  • linux 进程 cloud.tencent.com/developer/a…

    • R 运行状态 正在运行或者在运行队列中等待。
    • S 中断状态 处于休眠状态,当形成某个条件或者接收到信号之后脱离该状态
    • D 不可中断状态 进程不响应系统异步信号,即使kill也杀不掉
    • Z 僵死状态 进程已经终止,但是进程描述符存在,直到父进程调用wait4系统函数后讲进程释放
    • T 停止状态 进程收到停止信号后停止运行
  • 进程状态的方式

    • ps process status
    • -aux 显示所有包含其他使用者的行程
    • top 动态展示进程的实时状态

codegen

  1. 语言
    1. 项目主体使用ts编写 使用go编写的exe文件对go的语法树进行分析 调用go的exe来实现对go代码的部分生成
  2. 使用技术
    1. 命令行工具使用commander npm link
    2. ejs模板渲染技术 文本替换 列表批量插入 类似前端写的js语法
    3. ast抽象语法树
  3. 实现功能
    1. init初始化项目
    2. 生成dao层
    3. 生成api层
    4. 生成ams前端框架
  4. 生成dao层 只有js
    1. 读取路径 判断路径 读取module名
    2. 读取config.toml文件 toml解析
    3. 根据配置文件 选择数据库和表名(或者该库所有表)生成对应的dao和model对象
    4. 通过数据库名从 information_schema.tables 来获取表名
    5. 从information_schema.columns中获取对应表每一列的 名字类型注释等信息
    6. column_type data_type 列类型这个类型比data_type列所指定的更加详细,如data_type 是int 而column_type 就有可以能是int(11)
    7. 根据获取的表信息来生成对应的文件
    8. 渲染需要的数据 appName tableName的各种格式 表的字段属性
    9. 数据库类型转为go的类型 名字 类型 tag 注释
    10. 渲染ejs模板,fs-extra 写入文件 readfile读取模板
    11. ejs.render渲染模板文件
  5. 生成api层
    1. api层和service层使用ejs模板渲染生成文件 替换modelName等即可
    2. model层通过调用go的可执行文件
    3. 获取dao层生成的model os.OpenFile ioutil.ReadAll parser.ParseFile()

ast.Print 查看结构 根据结构一步步学习 ast.Inspect 深度优先遍历语法树 router的填充也是通过调用go的可执行性文件 传入参数来进行生成

服务注册和发现与领导人选举

  1. 使用consul进行服务注册与发现 实现简单的负载均衡
  2. leader选举 分布式锁
  3. 注册器 注册 注销 获取锁 重置锁 监控
  4. 周期性的刷新
  5. consul提供了监听某个kv变化的接口 已经变化后的函数接口
  6. 监听器 开始监听 结束监听
  7. 获取成功后执行的操作以及放弃