✅1.什么是冯诺依曼体系结构?
冯诺依曼体系结构即现代计算机的硬件体系结构:计算机应该包含五大硬件单元
1.输入设备:采集数据
- 比如键盘,鼠标,扫描仪,网卡接收网络中的数据
2.输出设备:进行数据输出
- 比如显示器,打印机,网卡向网络中发送数据
3.存储器:进行数据中间数据缓冲
4.运算器:进行数据运算
5.控制器:进行设备管理
运算器+控制器就是CPU中央处理器
✅2.图解
✅3.所有的设备都是围绕存储器工作的
- cpu不会直接从输入设备获取数据进行处理,而是先把数据放到存储器中,cpu从存储器中获取数据处理
- cpu不会直接将数据交给输出设备进行输出,而是先把数据放到存储器中,控制输出设备从存储器中获取数据输出
既然所有的设备都是围绕着存储器工作的,那存储器是什么呢?
- 存储器就是我们常说的内存
我们熟知电脑还有个叫硬盘的东西,为什么存储器是内存而不是硬盘呢?
- 因为硬盘的吞吐量太低了,正常的机械硬盘是200MB/s
- 内存的吞吐量是己写硬盘的数十倍
那内存的速度那么快,为什么内存只用于缓冲,不使用内存存储数据,而用硬盘存储呢?
- 主要是因为硬盘与内存的存储介质是不同的
- 内存是易失介质,数据在断电后就会丢失,而硬盘断电后数据不会丢失
✅1.什么是操作系统
操作系统一个“搞管理”的,(安装在计算机上的一个程序)任何一个计算机系统都包括操作系统(
os
),用来管理计算机上的软硬件资源
✅2.操作系统包括啥
内核
用来完成进程管理,内存管理,文件管理,驱动管理等
外部应用
函数库,shell程序等,是为了让系统更加好用,作为系统和用户之间的桥梁
✅3.为什么需要操作系统来管理
- 用户是无法直接访问内核的,只能通过系统调用接口来访问,用户直接访问内核的
危险性
太高了- 为了控制风险,“听系统的话”,操作系统会向外提供访问内核的接口,这个接口就称为
系统调用接口
✅4.操作系统怎么管理
操作系统给管理方法叫:
先描述
在组织
为了更好的理解,我们引入一个例子
学校管理体系大致分为三部分:管理者,被管理者,执行者
学生:自然是
被管理者
啦导员,老师,宿管阿姨:这些都是管学生的人,但是他们不是管理者,他们只是制度的
执行者
,真正的管理者是制度制定者校长:制定制度,
管理者
假如有一个学生非常不听话,要被开除,怎么样才叫被开除呢?
- NO
- NO
- YES(好可怕,不要被退学~)
这样我们可以大致了解校长是通过什么管理学生的----学籍档案
校长并不需要知道你是谁,但是他手里有你的学籍档案,你就要被”管“啦
学籍档案是什么
是你从上学以来的学籍信息,是一个学生的描述信息
这就是你被描述起来了
光有学籍档案就可以管理了嘛,全国有那么多学生,我要从中多的档案中找到你,得花不少时间吧
这就要谈到组织了
虽然学生有那么多,但是可以组织起来,分到不同的省,不同的市,不同的学校,不同的学院,不同的班级,是不是就很好找了
现在知道是怎么管理的嘛
从你开始上学,将你的信息收集起来,描述起来,然后放到学籍管理系统中组织起来进行管理
其实操作系统就是差不多的管理过程啦
- 描述起来,例如将键盘,鼠标等用struct 结构体描述起来
- 组织起来,利用链表或其他高效的数据结构
- 从开发角度看,操作系统把自己包成了一个球,但是会暴露
部分接口
,供上层开发使用,这个由操作系统提供的接口,就叫系统调用接口
- 但是这个系统调用在使用上很难,他对于使用者的
要求太高
了(因为功能非常基础,用起来很费劲),所以有些开发者对部分系统调用进行了封装
,形成了库
,有了库以后,就极大方便了
用户或是开发者的使用
- 先把进程描述起来
- 再把进程组织起来
我们可以看一下自己的任务管理器,它显示了我们有哪些正在运行的进程
- 我们课本上的概念:进程是一个程序的执行实例,正在执行的程序等
- 内核:是担当系统分配资源的实体
没听懂,有必要深入了解一下
这个不难理解,就像我们的手机可以同时看小说、听音乐
但是,我们假设只有一个cpu,是如何做到多个程序同时运行的呢?
CPU的
分时机制
CPU只负责执行指令,处理数据,至于处理哪一个程序,CPU并不关心
把CPU比喻成流水线上的员工,他只负责有一个包裹过来了,就接收它,处理它,至于什么是包裹~完全不看
那负责决定放什么包裹,什么时候放放包裹的,是谁呢?
是
操作系统
,操作系统对程序的运行进行调度管理所以CPU进行程序处理的时候不会一次性的将一个程序运行完毕才会运行下一个,而是每个程序都运行一个很短的时间,这叫
分配时间片
,时间片运行完毕,由操作系统进行调度,让另一个程序的代码数据在cpu上进行处理
- 操作系统将程序的信息放到cpu寄存器中,让cpu知道执行哪一个程序的哪一个指令,处理什么数据
- 如果此时有多个程序运行,cpu分时机制会实现程序之间的切换
- 如何切换,就是调度
cpu分时机制实现cpu轮询处理每一个运行中的程序,而程序运行调度由操作系统进行管理
回到第一个问题,操作系统是怎么管理的呢
操作系统将每一个程序的运行信息保存下来,进行调度管理,所以下一次程序被分到时间片的时候才能知道这个程序上一次运行到哪里
如图,操作系统就是调度程序在1,2,3,4,5,6中来回分配时间片,时间很短,反正我们是感受不到的
- 程序的指令或数据不动,在内存中就是死的,称不上叫进程
- 操作系统通过对一个程序进行描述,让一个程序运行起来
- 对于操作系统来说,进程就是PCB(如上图)是一个程序运行的动态描述,通过PCB,才能实现程序运行的调度管理
进程
–PCB
进程信息被放在一个叫做进程控制模块的数据结构中,我们称为PCB(process control block)
task_struct
task_struct是Linux下的描述进程的
task_struct是Linux内核的一种数据结构,它会被装载到内存中,并且包含进程的信息
task_struct
内容分类
- 内存指针—>包括程序代码和进程相关数据的指针,还有其他进程共享的内存块的指针
- 上下文数据—>进程执行时处理器的寄存器中的数据
- 程序计数器—>程序中即将被执行的下一条指令的地址
- 标示符—>描述本进程的唯一标识符,用来区分其他进程
- IO状态信息—>显示的IO请求,分配给进程的IO设备,被进程使用的文件列表
- 优先级—>相对于其他进程的优先级
- 记账信息—>处理器时间总和,使用的时钟数总和,时间限制,记账等
并行:cpu资源不够的情况下,采用cpu分时机制,任务轮询处理
并发:多核cpu,多个进程同时占据cpu进行数据处理
为了弄懂正在运行的进程是什么意思,我们需要在知道进程的不同状态
1.运行状态R:
running
包含正在运行以及就绪,运行态就是一个进程拿到时间片就能进行数据处理的状态
2.可中段休眠状态S:
interruptible sleep
指的是休眠可以唤醒,条件自然满足后唤醒,也可中断,被中断后置为运行态
3.不可中断休眠状态D:
uninterruptible sleep
必须等到唤醒条件自然满足后才置为运行态
4.停止状态T:
stopped
停止与休眠不一样,停止只能手动唤醒
5.僵尸状态Z:
zombie
描述一个进程退出了,但是进程资源没有完全释放,呈等待处理的一种状态
6.死亡状态X:
dead
这个状态只是一个返回状态,不会在任何链表里看到这个状态
休眠
- 暂时不需要cpu调度运行的程序,会让出cpu资源
- 休眠也有唤醒条件,会查看状态,如果是休眠,就会看唤醒条件是否满足
- 如果满足,则置为运行状态,进行处理,如果不满足则切换下一个进程
口说无凭,让我们真实的看一下
ls
/proc
命令查看系统文件夹的进程信息fork
利用fork创建新进程,运行的代码与调用fork的进程一样,而且运行位置也相同,调用进程称为父子进程,子进程是通过拷贝父进程的上下文数据,程序计数器,内存指针来创建的
- 在父进程中返回子进程的pid大于0
- 在子进程中返回0
- 子进程创建失败,返回-1
我们怎么能知道谁是父进程谁是子进程呢,光从返回值判断就行了?
让结果告诉我们
尽然父子进程做的事情都差不多,那为什么需要创建子进程?
- 创建子进程大多数情况下并不是为了让子进程做与父进程一样的事,而是让子进程去调度另一个程序的运行,如果这个任务有风险,交给子进程去做,哪怕数据处理出错崩溃了,也不会影响主线程,保证了主线程的安全。
ps
aux
- 一般是指在操作系统中用来指定操作系统运行环境的一些
参数
- 例如我们在编写c/c++的时候,在链接的时候,我们不知道链接的是静态库还是动态库,依旧可以链接成功,原因是有相关的环境变量帮助编译器进行查找
- 简单来说,环境变量就是
一个变量
,用于存储系统运行的环境参数
- 通过修改环境变量的值,灵活配置下系统运行环境参数(使系统环境配置更加灵活)
- 子进程的继承性(子进程会默认拥有父进程相同的环境变量,可以通过环境变量进行进程之间的数据传递)
- PATH:指定命令的搜索路径
- HOME:指定用户的主要工作目录
- SHELL:当前的shell,它的值通常是/bin/bash
为什么有时候一些指令可以直接执行,不需要带路径,而我们的二进制程序需要带路径才能执行?
PATH是典型的环境变量,用来存储程序默认的搜索运行路径
- 运行一个程序的时候,如果没有指定程序的名称,则shell会去PATH环境变量中保存的路径中去找这个程序,如果没有找到,就报错没有这个命令。
但是我们可以认为的将程序所在路径,加入到PATH中,例如
将当前路径加入到PATH中
PATH:$PATH:.
- env:显示所有环境变量
- set:显示本地定义的shell变量和环境变量
- echo:显示某个环境变量值
- export:设置一个新的环境变量
- unset:清除环境变量
- 使用extern char **environ
- char *getenv(char *key)
- setenv(char *key, char val, int override)//如果这个环境变量已经有了,是否要覆盖它
- putenv(char *key = val)
✅1.图解
✅ 2.什么是程序
✔2.1 定义
- 程序是一堆
死代码
,保存在程序文件中,编译器在编译程序生成可执行文件时,就会对每一条指令,每一个数据,进行一个地址编号✔2.2过程
- 当程序运行的时候,就会将指令以及数据放到指定位置的内存地址位置
- cpu就会根据地址偏移逐步去执行指令,以及找到对用的数据进行处理
- 程序运行之后才会占据内存,因此程序地址空间通常被称为进程地址空间
✅3.什么是地址
- 内存地址,队内存以字节为存储单位的一个编号, 通过地址就能找到具体对应的内存单元
✅4.什么是程序地址空间
- 程序地址空间,就是操作系统为进程使用mm_struct结构体描述的一个
虚拟
的地址空间
程序地址空间通常被称为进程地址空间
进程地址空间是什么
✅1.问题
- 如果真的将物理内存分成了不同的区域,代码段存放的是代码,权限是只读的,不能修改的
- 如果物理内存有一段不可修改,那数据是怎么放进去的?
✅2.分析
- 我们知道一个内存地址只能指向一个唯一的内存单元,一个内存单元只能存储一个数据
- 其实进程中所访问的地址都是虚拟地址,一个假地址,并非物理内存的地址
- 我们所说的程序地址空间,实际上也是一个虚拟的地址空间
- 是操作系统为进程通过一个mm_struct结构体所描述的一个假的地址空间
mm_struct (task_size, start_code, end_code)
,通过大小以及区域的编号描述
✅3.结论
- 原来我们所说的地址空间是一个虚拟的地址空间,只是一堆地址编号的描述(
并非物理内存地址
)
✅4.图解
为什么需要虚拟内存地址
✅1.提出问题
- 为什么操作系统不让进程直接访问物理内存,而是弄了一个虚拟地址空间,让进程访问虚拟地址呢?进程直接访问物理内存,有什么不好?
✅2.分析
✔ 没有虚拟地址有什么坏处
1.程序在编译时,编译器就会给指令和数据进行地址编号,但是如果某个地址内存已经被占用,则程序就运行不起来了
2.编译器的地址管理麻烦,无法动态获知拿快内存是否被使用,也无法进行代码以及数据的地址赋值
3.如果让进程直接访问物理内存,有一个野指针的话,在操作的时候可能就把其他进程的数据改变了,即无法进行内存访问控制
4.程序加载通常需要使用一块连续的内存空间,内存利用率很低✔有虚拟内存有什么好处
1.通过虚拟地址空间映射到物理内存上进行数据存储,可实现数据在物理内存上的离散式存储,提高内存的利用率
2.每个进程都有自己的虚拟内存空间,因此对于每个进程来说,都会拥有自己的一块连续的空间使用
进程中访问的都是虚拟地址,如何通过虚拟地址找到物理内存呢?
操作系统中内存管理方式
✅1.分段式
✔方法
- 将虚拟地址的组成分为
段号
+段内偏移量
(比如全局数据段有很多变量,他们的段号都是一样的,也就意味着段的起始位置一样,但是每个变量的偏移量不同)
- 通过段号对应的物理内存段起始地址以及虚拟地址中的偏移量组成一个完整的物理内存,找到对应的物理内存单元
✔图解
✔优点
- 对编译器的地址管理比较友好
✔缺点
- 没有解决数据连续存储内存利用率低的问题,因为一个段管理了很多变量数据,这些变量就都从同一个起始位置进行偏移,也就在物理地址中使用了连续的地址空间
- 分段号式管理中,同一个段内数据都使用了连续的地址空间,每个段之间不用连续,但是段内是连续的
✅2.分页式
✔方法
因为通常物理块比较小,并且不要求同一个进程的多个数据必须在同一块内,因此分页式腾空出世
页表会在进行内存访问的时候进行内存访问控制,判断是否有权限
页表放的是页表项,每个页表项保存的都是一个虚拟内存页与对应物理内存块的映射关系
✔图解
✔分页式管理的优点:
- 实现数据离散式存储,提高内存利用率,并且通过页表进行内存访问控制
- 实现了数据在物理内存中的离散式存储
✔计算
- 在32为系统中,若页表大小是4096,则页表项有1024*1024个,也就是2^20
- 虚拟地址中页号占据虚拟地址的高20 位
分页式与分段式各有优缺点,所以我们用段页式
✅3.段页式
- 综合了 分段式 和分页式,将内存进行分段,在每个段内采用分页管理
✔图解