zoukankan      html  css  js  c++  java
  • 操作系统笔记(持续更新中)

    综述

    1.知识体系

    操作系统就像一家外包公司:

    为了实现系统的运作,其实是由几个子系统支撑完成的:

    2. 系统调用

    2.1 立项与进程管理

    • 父进程进行fork操作,得到子进程,先从父进程拷贝数据结构,再修该
    • 子进程调用execve执行另一个程序
      -有个系统调用叫 waitpid,父进程能知道子进程是否运行完毕

    2.2 会议室与内存管理

    • 进程空间内,存放代码的部分叫做代码段(Code Segment)
    • 进程内,存放数据的部分叫做数据段(Data Segment
      - 局部变量在当前函数有效,离开函数则释放
      - 动态分类的,指明才销毁的,称为堆
    • 进程不用的部分就不管,进程需要使用内存的时候,会调用内存管理系统,但是也不代表对应到了真正的物理内存,只有要写入且发现没有物理内存,才会触发一个中断,现分配物理内存
    • 两个系统调用
      - brk:内存数据量小时,和原来的数据连在一起
      - mmap: 数据量大时重新划分一个区域

    档案库管理与文件管理

    最重要的6个文件操作:

    • 对已有文件的打开关闭
    • 创建
    • 打开文件以后使用lseek跳到文件某个位置
    • readwrite

    每个文件,Linux都会分配一个文件描述符(File Descriptor),这是一个整数。有了这个文件描述符,我们就可以使用系统调用,查看或者干预进程运行的方方面面。

    2.3 项目异常与信号处理

    每种信号都定义了默认动作,也可提供处理函数,可以通过sigaction系统调用,注册一个信号处理函数。提供了信号处理服务,进程执行中一旦有变动,就可以及时处理了。

    项目组间沟通与进程间通信

    • 消息队列
      - msgsnd发送消息到消息队列
      - msgget创建一个消息队列
      - msgrcv从队列获取消息
    • 共享内存
      - shmget创建共享内存块
      - shmat将共享内存映射到自己的内存空间
    • 如何解决同时访问数据的问——举例:Semaphore信号量

    2.4公司间沟通与网络通信

    网络服务通过套接字Socket完成

    2.5 中介与Glibc

    系统调用不是直接使用的,而是使用Glibc。它是Linux下使用的标准C库,为程序员提供丰富的API,封装了操作系统的系统服务。

    2.6 小结

    3.x86架构

    • CPU 包括: 运算单元, 数据单元, 控制单元
      • 运算单元 不知道算哪些数据, 结果放哪
      • 数据单元 包括 CPU 内部缓存和寄存器, 暂时存放数据和结果
      • 控制单元 获取下一条指令, 指导运算单元取数据, 计算, 存放结果
    • 进程包含代码段, 数据段等, 以下为 CPU 执行过程:
      • 控制单元 通过指令指针寄存器(IP), 取下一条指令, 放入指令寄存器中
        • 指令包括操作和目标数据
      • 数据单元 根据控制单元的指令, 从数据段读数据到数据寄存器中
      • 运算单元 开始计算, 结果暂时存放到数据寄存器
    • 两个寄存器, 存当前进程代码段和数据段起始地址, 在进程间切换
    • 总线包含两类数据: 地址总线和数据总线

    • x86 开放, 统一, 兼容
    • 数据单元 包含 8个 16位通用寄存器, 可分为 2个 8位使用
    • 控制单元 包含 IP(指令指针寄存器) 以及 4个段寄存器 CS DS SS ES
      • IP 存放指令偏移量
      • 数据偏移量存放在通用寄存器中
      • 段地址<<4 + 偏移量 得到地址

    • 32 位处理器
    • 通用寄存器 从 8个 16位拓展为 8个 32位, 保留 16位和 8位使用方式
    • IP 从 16位扩展为 32位, 保持兼容
    • 段寄存器仍为 16位, 由段描述符(表格, 缓存到 CPU 中)存储段的起始地址, 由段寄存器选择其中一项
      • 保证段地址灵活性与兼容性

    • 16位为实模式, 32位为保护模式
    • 刚开机为实模式, 需要更多内存切换到保护模式

    4. 从BIOS到BootLoader

    5. 内核初始化

    5.1 初始化步骤

    • 进程初始化:系统启动首先启动0号进程,
    • 初始化中断门
    • 初始化内存管理
    • 创建1号进程

    5.2 从用户态到内核态

    用户态-系统调用-保存寄存器-内核态执行系统调用-恢复寄存器-返回用户态

    6 系统调用

    • gclib对系统调用的封装

    进程管理

    7. 进程

    7.1 进程如何从代码到运行

    • 文件编译生成so文件和可执行文件
    • 用户态的进程A执行fork,创建进程B
    • B会执行exec系列系统调用
    • 系统调用通过load_elf_binary方法,将可执行文件加载到B的内存中

    8.线程

    9.进程的数据结构

    进程用task_struct表示
    包含内容

    • ID
    • 信号处理
      pid 是 process id,tgid 是 thread group ID。
      任何一个进程,如果只有主线程,那 pid 是自己,tgid 是自己,group_leader 指向的还是自己。
      但是,如果一个进程创建了其他线程,那就会有所变化了。线程有自己的 pid,tgid 就是进程的主线程的 pid,group_leader 指向的就是进程的主线程。
    • 任务状态
    • 进程调度
    • 运行统计信息
    • 进程亲缘关系
      是一个树形结构,保存了parent,children,sibling的指针
    • 进程权限
      - uid和gid时进程的真实id,意思是谁启动了进程
      - euid和egid,“起作用”的。当进程要操作消息队列,共享内存,信号量等对象时,比较这个id
      - fsuid和fsgid, filesystem,对文件操作会审核权限
      - 使用chmod u+x可以改变文件的set-useri-id标识位,这样文件的euid和fsuid都改成了当前用户
      • 新加入的capabilities机制用位图表示权限
    • 内存管理
    • 文件与文件系统
    • 用户态函数栈
      - 高地址到低地址,往下增长,入栈出栈都从下面的栈顶开始
    • 内核态函数栈
      - 通过一个pt_regs的struct保存寄存器状态
      - 可以通过task_struct找到内核栈和内核寄存器

    10. 进程的调度

    • task_struct解决了能”看到“哪些问题,还需要解决如何”做到

    10.1 调度策略与调度类

    • 实时进程

    • 普通进程

    • 调度策略
      #define SCHED_NORMAL 0
      #define SCHED_FIFO 1
      #define SCHED_RR 2
      #define SCHED_BATCH 3
      #define SCHED_IDLE 5
      #define SCHED_DEADLINE 6

    • 调度优先级
      int prio, static_prio, normal_prio;
      unsigned int rt_priority;

    • 实时调度策略
      - FIFO
      先来先服务
      - RR
      时间片轮流服务
      - DEADLINE
      选择离当前deadline距离最近的任务

    • 普通调度策略
      - NORMAL
      和名字一样普通
      - BATCH
      后台进程
      - IDLE
      空闲时进行的进程

    • 调度策略的封装

    • 上述变量只是定义了名字,实际上的实现则是由task_struct把调度策略封装在了sched_class中

    • 完全公平调度算法
      CFS:completely fair scheduling

      • 记录下进程运行时间,称为一个tick
      • cfs会为进程安排一个虚拟运行时间vruntime
      • 随着进程运行,tick增加,vruntime也增加
      • vruntime大则运行时间多,小则少,需要给小的进程分配更多的运行时间
      • 不同进程有权重,权重高的vruntime高

    10.2 调度队列与调度实体

    • cfs需要一个数据结构对vruntime进行排序——红黑树
      - 红黑树:一种自平衡查找树
      Linux的的进程调度完全公平调度程序,用红黑树管理进程控制块,进程的虚拟内存区域都存储在一颗红黑树上,每个虚拟地址区域都对应红黑树的一个节点,左指针指向相邻的地址虚拟存储区域,右指针指向相邻的高地址虚拟地址空间
      题外话:红黑树的应用
    • cpu运行时的数据结构

      可以看到,一个 CPU 上有一个队列,CFS 的队列是一棵红黑树,树的每一个节点都是一个 sched_entity,每个 sched_entity 都属于一个 task_struct,task_struct 里面有指针指向这个进程属于哪个调度类。

    内存管理

    概述

    内存管理要做到三件事:

      1. 虚拟内存空间的管理,每个进程看到的地址空间是独立的,互不干扰的
      1. 物理内存的管理,只有内存管理模块能够使用
      1. 内存映射,需要将虚拟内存和物理内存管理起来

    虚拟内存的布局

    • 我们从最低位开始排起,先是 Text Segment、Data Segment 和 BSS Segment。Text Segment 是存放二进制可执行代码的位置,Data Segment 存放静态常量,BSS Segment 存放未初始化的静态变量。是不是觉得这几个名字很熟悉?没错,咱们前面讲 ELF 格式的时候提到过,在二进制执行文件里面,就有这三个部分。这里就是把二进制执行文件的三个部分加载到内存里面。
    • 接下来是堆(Heap)段。堆是往高地址增长的,是用来动态分配内存的区域,malloc 就是在这里面分配的。
      接下来的区域是 Memory Mapping Segment。这块地址可以用来把文件映射进内存用的,如果二进制的执行文件依赖于某个动态链接库,就是在这个区域里面将 so 文件映射到了内存中。
    • 再下面就是栈(Stack)地址段。主线程的函数调用的函数栈就是用这里的。
    • 如果普通进程还想进一步访问内核空间,是没办法的,只能眼巴巴地看着。如果需要进行更高权限的工作,就需要调用系统调用,进入内核。
      一旦进入了内核,就换了一种视角。刚才是普通进程的视角,觉着整个空间是它独占的,没有其他进程存在。当然另一个进程也这样认为,因为它们互相看不到对方。这也就是说,不同进程的 0 号到 29 号会议室放的东西都不一样。
    • 但是到了内核里面,无论是从哪个进程进来的,看到的都是同一个内核空间,看到的都是同一个进程列表。虽然内核栈是各用各的,但是如果想知道的话,还是能够知道每个进程的内核栈在哪里的。所以,如果要访问一些公共的数据结构,需要进行锁保护。也就是说,不同的进程进入到内核后,进入的 30 号到 39 号会议室是同一批会议室。
  • 相关阅读:
    Maven pom.xml文件获取当前时间戳
    maven依赖jdk的tools.jar包坐标怎么依赖
    解决Git问题:git登录账号密码错误remote: Incorrect username or password、如何快速关联/修改Git远程仓库地址、git修改用户名邮箱密码
    浅析Java里的内存分析及常量池加强对Java里字符串的理解
    浅析Java数据结构:稀疏数组的介绍和使用场景
    UmiJS简单介绍及使用UmiJS开发结构浅析
    React基础知识笔记:如何渲染html代码、条件渲染与循环渲染、如何获取动态路由传参、动态设置背景图、umi+dva中全局使用dispatch
    git commit提交时报错husky > pre-commit (node v10.16.3)
    浅析npm报错ENOTFOUND npm ERR! network request to https://npm.***.com/*** failed 及 .npmrc 文件的作用、npm --verbose命令
    浅析为什么说Java中只有值传递
  • 原文地址:https://www.cnblogs.com/yuuken/p/13515572.html
Copyright © 2011-2022 走看看