select
- 使用fd_set存储信息,位图,bitmap,每次都需要将信息从用户态拷贝到内核态
- 内核每次都需要扫描整个线性表,判断哪个事件发生变化
- 最大并发数也有一定限制
poll
- 使用一个结构体数组进行传递,还是会存在数据在内核态与用户态之间的传递
- 还是需要遍历才可知状态变化的事件
epoll
- 调用 create 建立一个epoll对象
- 调用 ctl 函数创建或者销毁需要监控的对象
- 不需要内存的多次拷贝
- 使用红黑树结构 检索效率高
- 基于回调,不需要遍历整个结构,直接放到 readyList 里面,调用 wait 获取事件列表
- 用户态和内核态
- cpu指令集
- cpu指令集是有权限划分的ring0-3 权限依次降低
- ring0 为内核态 ring3为用户态 为了保护操作系统
- 执行内核空间的代码,具有ring 0保护级别,有对硬件的所有操作权限,可以执行所有CPU指令集,访问任意地址的内存,在内核模式下的任何异常都是灾难性的,将会导致整台机器停机
- 在用户模式下,具有ring 3保护级别,代码没有对硬件的直接控制权限,也不能直接访问地址的内存,程序是通过调用系统接口(System Call APIs)来达到访问硬件和内存,在这种保护模式下,即时程序发生崩溃也是可以恢复的,在电脑上大部分程序都是在,用户模式下运行的
- 进程的虚拟地址 以linux32位为例
- 进程的虚拟地址分为两部分,内核空间和用户空间
- 每个进程的高位1G为内核空间且是被所有进程共享的
- 状态的切换
- 保留用户态现场(上下文、寄存器、用户栈等)
- 复制用户态参数,用户栈切到内核栈,进入内核态
- 额外的检查(因为内核代码对用户不信任)
- 执行内核态代码
- 复制内核态代码执行结果,回到用户态
- 恢复用户态现场(上下文、寄存器、用户栈等)
- cpu指令集
- 内存
- 虚拟地址和物理地址
- 虚拟地址是在进程中使用的内存地址,进程不必关心物理地址是多少
- 在硬件的地址称为物理地址
- 操作系统会提供一种机制,将不同进程的虚拟地址和物理地址进行映射关系
- 进程持有的内存地址会通过cpu的MMU映射到其物理地址,再去访问到他的物理内存
- 目的: 让进程和物理内存地址解耦 使进程之间保持相互独立和隔离
- 映射方式
- 分段
- 虚拟内存空间中的虚拟内存按照其逻辑划分为代码段、数据段、堆段、栈段几部分
- 通过段寄存器中的段表,来将虚拟地址与物理地址进行映射。段表中存储了每一个逻辑段的段号对应的物理内存的起始地址。
- 对于每一个在虚拟内存中存储的数据,其虚拟地址都以其所在的段号以及段内偏移组成。
- 产生内存碎片 内存和硬盘交换的效率低
- 分页机制
- 将内存空间分为大小相同的页 映射关系通过页表完成
- 页表中不仅保存了页号,物理内存地址,还保留了该物理页的访问权限,用以实现对页的访问控制
- 不需要将程序一次性加载进内存,需要的时候加载,如找不到则发生缺页中断,加载到内存中
- 页表占用空间很大,所以采用多级页表,32位使用2级页表,64位使用4级页表
- 快表 TLB 将最常访问的几个页表项存储到TLB中 相当于缓存 不需要四级页表的地址映射
- 段页式
- 先将程序划分为多个有逻辑意义的段,也就是前面提到的分段机制
- 接着再把每个段划分为多个页,也就是对分段划分出来的连续空间,再划分固定大小的页
- 虚拟地址结构由段号、段内页号和页内位移三部分组成
- 段号 - 段项 - 页表地址 - 段页表 - 段页项 - 物理基页地址+偏移地址 = 内存地址
- 虚拟地址由段号 段内页号 偏移量三部分组成
- 分段
- 虚拟地址和物理地址
- linux 线程模型 www.360doc.com/content/19/…
- 多对一 很多个用户线程对应一个内核调度实体 多线程操作时 不需要内核态和用户态频繁切换 一个线程调用内核 则其他线程阻塞
- 一对一 一个用户线程对应一个内核调度实体 线程的每次操作会在用户态和内核态切换 如果系统出现大量线程,会对系统性能有影响
- m对n 一个用户线程阻塞时,其他用户线程仍然可以执行 可以实现较完整的调度、优先级等
- 线程是操作系统调度和执行的基本单位,linux中被称为轻量级进程,其实linux中是没有线程概念的,线程只是一个和其他进程共享某些资源的进程,线程只是进程间共享资源的一种方式,有一个task_struct结构
- 早期通过一个管理线程来满足posix协议提出的要求,NPTL(Native POSIX Threading Library),在linux2.6解决了之前存在的一些问题,有了线程组的概念,task_struct多了一个tgid(thread group id)的字段,主线程的tgid=pid,非主线程的tgid=主线程的pid。
进程调度
进程通信的方式
- 管道 一个固定大小的缓存区 由内核管理 使用了锁 等待队列和信号来实现并发访问
- 无名管道 半双工 只能在父子和兄弟进程间使用
- 命名管道 全双工 可以双向传输 可以在无关的进程间使用 多了一个文件标识 存在于文件系统中
- 信号量 只能同时被一个进程访问
- 是一个计数器 用于进程间同步 如果要传递数据要结合共享内存来使用
- 对信号量的操作是一次原子操作
- 消息队列
- 保存在内核的一个消息链表
- 具有特定的格式和特定的优先级
- 共享内存
- 可以直接读写内存,效率高 无需复制 需要考虑同步问题
- 实现方式 内存映射 共享内存机制
- socket通信
-
linux 进程 cloud.tencent.com/developer/a…
- R 运行状态 正在运行或者在运行队列中等待。
- S 中断状态 处于休眠状态,当形成某个条件或者接收到信号之后脱离该状态
- D 不可中断状态 进程不响应系统异步信号,即使kill也杀不掉
- Z 僵死状态 进程已经终止,但是进程描述符存在,直到父进程调用wait4系统函数后讲进程释放
- T 停止状态 进程收到停止信号后停止运行
-
进程状态的方式
- ps process status
- -aux 显示所有包含其他使用者的行程
- top 动态展示进程的实时状态
codegen
- 语言
- 项目主体使用ts编写 使用go编写的exe文件对go的语法树进行分析 调用go的exe来实现对go代码的部分生成
- 使用技术
- 命令行工具使用commander npm link
- ejs模板渲染技术 文本替换 列表批量插入 类似前端写的js语法
- ast抽象语法树
- 实现功能
- init初始化项目
- 生成dao层
- 生成api层
- 生成ams前端框架
- 生成dao层 只有js
- 读取路径 判断路径 读取module名
- 读取config.toml文件 toml解析
- 根据配置文件 选择数据库和表名(或者该库所有表)生成对应的dao和model对象
- 通过数据库名从 information_schema.tables 来获取表名
- 从information_schema.columns中获取对应表每一列的 名字类型注释等信息
- column_type data_type 列类型这个类型比data_type列所指定的更加详细,如data_type 是int 而column_type 就有可以能是int(11)
- 根据获取的表信息来生成对应的文件
- 渲染需要的数据 appName tableName的各种格式 表的字段属性
- 数据库类型转为go的类型 名字 类型 tag 注释
- 渲染ejs模板,fs-extra 写入文件 readfile读取模板
- ejs.render渲染模板文件
- 生成api层
- api层和service层使用ejs模板渲染生成文件 替换modelName等即可
- model层通过调用go的可执行文件
- 获取dao层生成的model os.OpenFile ioutil.ReadAll parser.ParseFile()
ast.Print 查看结构 根据结构一步步学习 ast.Inspect 深度优先遍历语法树 router的填充也是通过调用go的可执行性文件 传入参数来进行生成
服务注册和发现与领导人选举
- 使用consul进行服务注册与发现 实现简单的负载均衡
- leader选举 分布式锁
- 注册器 注册 注销 获取锁 重置锁 监控
- 周期性的刷新
- consul提供了监听某个kv变化的接口 已经变化后的函数接口
- 监听器 开始监听 结束监听
- 获取成功后执行的操作以及放弃