前言
一、操作系统概览
what:
- 操作系统是管理计算机硬件和软件资源的计算机程序
- 负责管理内存、决定资源供需顺序、控制输入输出设备等;
- 操作系统提供让用户和系统交互的操作界面;
why:
- 我们无法直接操作计算机资源;
- 设备种类繁多复杂,需要统一界面;
- 操作系统的简易性使得更多人使用计算机
二、操作系统基本功能
主要有三种功能:管理资源、对资源抽象、提供接口;
三、操作系统相关概念
主要有四个概念:并发性、共享性、虚拟性、异步性;
四、进程管理
1、进程实体
why
- 没有OS之前,资源属于当前运行的程序;
- 配置OS后,引入多道程序的概念;
- 合理的隔离资源、运行环境,提升资源利用率;
- 进程是系统进行资源分配和调度的基本单位;
- 进程作为程序运行的载体保障程序正常执行;
- 进程的存在使得操作系统资源的利用率大幅提升;
2、主存中的进程形态
3、进程与线程 Process Thread
一个进程可以有一个或多个线程;
- 线程是操作系统进行运行调度的最小单位;(操作系统对进程的调度,实际上是对进程内线程的调度);
- 包含在进程之中,是进程中实际运行工作的单位;
- 一个进程可以并发多个线程,每个线程执行不同的任务;
- 进程的线程共享进程资源;但是也拥有自己一部分数据
- 线程独有:栈 , 寄存器(上下文信息),调度优先级,信号屏蔽字,标识符
- 共享:堆空间、全局变量、静态变量(bss、data段中)、虚拟地址空间(代码段、数据段),文件描述符,信号处理方式,工作路径,用户组ID/组ID
4、进程管理之无状态模型
就绪状态:
- 当进程被分配到除CPU以外所有必要的资源后;(PCB、内存、栈、堆空间)
- 只要再获得CPU的使用权,就可以立即运行;
- 其他资源都准备好、只差CPU资源的状态就是就绪状态;
- 一个系统中多个处于就绪状态的进程通常拍成一个队列: 就绪队列;
执行状态:
- 进程获得CPU,其程序正在执行成为执行状态;
- 单处理器中,某一时刻,只能有一个进程是处于执行状态;
阻塞状态:
- 进程因为某种原因,从而放弃CPU的状态成为阻塞状态;(其他设备位就绪而无法继续执行)
- 同样有阻塞队列存在;
创建状态:
分为两步:
- 分配PCB;
- 插入就绪队列;
- 创建进程时,拥有PCB,但是其他资源还尚未就绪的状态称为创建状态;
- 操作系统提供fork函数接口,创建进程;
创建状态:
分为两步:
- 系统的清理;
- 归还PCB ;
- 进程结束,由系统清理或者归还PCB的状态称为终止状态;
状态转移
5、进程管理之进程同步
为什么需要进程间同步
-
进程堆缓存 (cache 上) 进行操作有三个步骤:读出、操作、写回;
-
著名的生产者消费者模型:并发的轮流操作一块区域,导致缓存区不一致;
-
其中操作的缓存区就是临界资源;
*哲学家进餐模型: 五个人,五只筷子,只有拿起左边筷子再拿到右边筷子才能吃;
- 几种情况:其中筷子就是临界资源,哲学家就是进程;
- 拿起左边筷子,发现右边筷子被拿了、等右边的释放、拿起右边筷子,开始吃饭;只是等待;
- 五个人同时拿起左边筷子,都发现右边筷子被拿了、都在等右边筷子释放,一直到饿死;
根源问题:彼此之间没有通信;
进程同步要解决的问题:
- 对竞争资源在多进程间进行使用次序的协调;
- 使得并发执行的多个进程之间可以有效使用资源和相互合作;
#include<thread>
#include <windows.h>
int nums = 0;
void preducer()
{
cout << "preducer() begin" << endl;
int times = 1000000;
Sleep(1000);
while (times--)
nums += 1;
}
void comsumer()
{
cout << "comsumer() begin" << endl;
int times = 1000000;
while (times--)
nums -= 1;
}
void main()
{
cout << " start " << endl;
thread thread1(preducer), thread2(comsumer);
thread1.join();
thread2.join();
cout << nums << endl;
cout << " end " << endl;
}
start
preducer() begin
comsumer() begin
-311334
end
start
preducer() begin
comsumer() begin
164717
end
1、进程间同步的原则
- 空闲让进:资源五占用,允许使用
- 忙则等待:资源有占用,请求进程等待
- 有限等待:保证有限等待时间能够使用资源
- 让权等待:等待时,进程需要让出CPU;进程进入阻塞状态;使得cpu可以高效运行
临界资源:作为共享资源,却无法同时被多个线程共同访问的共享资源。当有进程在使用临界资源时,其他进程必须依据操作系统的同步机制,等待占用进程是发放该资源才可重新竞争使用共享资源。
2、进程间同步与通信
- 为了解决进程间竞争关系(间接制约关系)而引入进程互斥;
- 为了解决进程间松散的协作关系( 直接制约关系)而引入进程同步;
- 为了解决进程间紧密的协作关系而引入进程通信。
- 进程互斥关系是一种特殊的进程同步关系,即逐次使用互斥共享资源,也是对进程使用资源次序上的一种协调。
进程同步:
-
消息队列、共享存储、信号量;
-
- 进程互斥、同步都可以看做进程的通信;
-
- 同步是让两个进程一前一后顺序执行;互斥代表一个时刻只有一个进程访问某个资源;
-
- 并发进程之间的交互必须满足两个基本要求:同步和通信。
- 同步主要是临界区、互斥、信号量、事件
- 进程间通信是管道、内存共享、消息队列、信号量、socket
- 共通之处是,信号量和消息(事件
进程间通信:
- 系统IPC(包括信号、管道、消息队列、共享内存、套接字socket);信号量用于互斥与同步
通信方式 | 简介 | 特点 |
---|---|---|
信号 | 异常工作模式可以使用;包括硬、软件来源;异步通信机制,是一种软件中断;软件中断通知事件处理 | 只能传送少量信号用来通知,并不适用于数据交换;(异步打断) |
管道 | 简单、半双工、效率低;可以得知有没有被别的进程读取;内核中的fifo缓存实现;生命周期随进程; | 一般限制于父子进程 |
消息队列 | 通信不及时、大小受限、用户态与内核态切换开销;内核中消息链表实现;生命周期随内核; | 相对于管道,能传输有结构的数据流; |
共享内存 | 通过虚拟内存映射到同一块物理内存中;使用信号量等配合同步和通信 | 大量数据交互,需要保证数据同步; |
套接字 | 可跨主机通信;域套接字用本地socket文件; | 可跨主机进程通信 |
信号量 | 计数器实现进程间同步、互斥;原子操作来实现信号量加减,以表示资源占用与否 |
- 管道(pipe):半双工、数据单向流动、只能在具有亲缘关系的进程间使用;不存在管道文件,只能通过文件描述符fd通信;
- 有名管道(named pipe):创建了 管道 设备文件;进程使用这个文件即可通信;
- 消息队列(message queue):消息队列是消息的链表,存放在内核中并由消息队列表示符标示。消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限制等缺点。消息队列通信过程中,存在⽤户态与内核态之间的数据拷⻉开销
- 共享内存(shared memory):共享内存就是映射一段内被其它进程所访问的内存,共享内存由一个进程创建,但是多个进程都可以访问。共享内存是最快的IPC,它是针对其它进程通信方式运行效率低的而专门设计的。它往往与其它通信机制。如信号量,配合使用,来实现进程间的同步和通信。
- 套接字(socket):套接字也是进程间的通信机制,与其它通信机制不同的是,它可以用于不同机器间的进程通信。
- 信号(signal):信号是一种比较复杂的通信方式,用于通知接受进程进程某个事件已经发生;包括了中断与中断处理函数(handle);(注册中断函数、产生中断(进入内核态)(产生中断后,修改用户进程的堆栈,使其进入handle 后返回原断点),进入中断处理函数)
- 信号量(semaphore):信号量是一个计数器,原子操作来实现信号量加减,可以用来控制多个进程对共享资源的访问。它常作为一种锁的机制,防止某进程正在访问共享资源时,其它进程也访问该资源。因此它主要作为不同进程或者同一进程之间不同线程之间同步的手段。
- 初始化为0为同步信号量;初始化为1 为互斥信号量
- 互斥使用:访问共享内存;P V 成对出现;访问前P,访问后V
- 同步使用:生产者消费者模型;生产完一个用V ,消费者消费之前P;
进程的同步与通信,进程与线程同步的区别,进程与线程通信的区别
同步、通信机制如下:
Linux 下:
-
Linux 下常见的进程同步方法有:SysVIPC 的 sem(信号量)、file locking / record locking(通过 fcntl 设定的文件锁、记录锁)、futex(基于共享内存的快速用户态互斥锁)。针对线程(pthread)的还有 pthread_mutex 和 pthread_cond(条件变量)。
-
Linux 下常见的进程通信的方法有 :pipe(管道),FIFO(命名管道),socket(套接字),SysVIPC 的 shm(共享内存)、msg queue(消息队列),mmap(文件映射)。以前还有 STREAM,不过现在比较少见了(好像)。
Windows下:
-
在Windwos中,进程同步主要有以下几种:互斥量、信号量、事件、可等计时器等几种技术。
-
在Windows下,进程通信主要有以下几种:内存映射、管道、消息等,但是内存映射是最基础的,因为,其他的进程通信手段在内部都是考内存映射来完成的。
3、线程同步、互斥
- 进程内多线程也需要同步,也存在上面两种模型问题;
- 线程间共享进程资源:代码段、堆空间、数据段、打开的文件等资源;
- 每个线程有独立的栈空间;
- 存在临界区:多线程执行操作共享变量的这段代码;
- 互斥:临界区代码只能有一个线程在内;(生产者消费者问题)
- 同步:并发线程/进程,在关键节点上,需要互相等待与互通消息;(哲学家就餐问题)
- 协作关系的处理:锁/信号量;信号量还可用作进程通信;
锁 | 特点 |
---|---|
忙等待锁:自旋锁 | 获取不到锁时,一直循环等待;单核需要抢占调度器,否则无法轮转;优势在于减少线程切换; |
无等待锁:互斥锁、读写锁 | 没获取到锁,线程进队列,然后调度; |
- 互斥:
- 互斥是指某一资源同时只允许一个访问者对其进行访问,具有唯一性和排它性。但互斥无法限制访问者对资源的访问顺序,即访问是无序的。
- 互斥是一种特殊的同步。
- 同步:(更复杂的互斥!)
- 同步是指在互斥的基础上(大多数情况),通过其它机制实现访问者对资源的有序访问。
- 同步其实已经实现了互斥,所以同步是一种更为复杂的互斥。
同步、互斥 | 使用 |
---|---|
互斥 | synchronized (同步)保证了互斥;(临界区、互斥锁、事件、信号量) |
同步 | wait和notify可以在互斥的基础上实现同步;(事件、信号量、条件变量) |
- 临界区(Critical Section):适合一个进程内的多线程访问公共区域或代码段时使用
- 互斥量 (Mutex):适合不同进程内多线程访问公共区域或代码段时使用,与临界区相似。
- 事件(Event):通过线程间触发事件实现同步互斥
- 信号量(Semaphore):与临界区和互斥量不同,可以实现多个线程同时访问公共区域数据,原理与操作系统中PV操作类似,先设置一个访问公共区域的线程最大连接数,每有一个线程访问共享区资源数就减一,直到资源数小于等于零。(信号量
-
文件等公用资源 这个是共享的;使用这些公共资源的线程必须同步。
-
Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
-
互斥量:保证线程互斥的使用临界资源的锁;
-
读写锁:应对多读少写、多写少读的情况而来的锁;
-
自旋锁:
-
条件变量:通过wait 被notify 后的操作;通常配合互斥锁使用;(锁住、判断变量,wait(解锁)、蹬到notify)
4、进程通信与线程通信
- 同一进程中的线程因属同一地址空间,可直接通信;只需同步即可;
- 线程也被称为轻权进程,同一进程的线程共享全局变量和内存,使得线程之间共享数据很容易也很方便,但会带来某些共享数据的互斥问题。
- 父子进程的派生是非常昂贵的,而且父子进程的通讯需要ipc或者其他方法来实现,比较麻烦。而线程的创建就花费少得多,并且同一进程内的线程共享全局存储区,所以通讯方便。
- 线程的缺点也是由它的优点造成的,主要是同步,异步和互斥的问题,值得在使用的时候小心设计。
- 线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制。
6、linux 进程管理
1、进程的类型:
- 前台进程:
终端shell,具有终端,可以和用户交互的进程;
- 后台进程:
- 与前台进程相对,没有占用终端的就是后台进程;
- 基本不和用户交互,优先级比前台进程低;
- 用& 符号结束,实现后台进程使用(打印还会继续,但是不占用终端,用户可以继续使用终端)(打印可以通过重定向,不打印到终端来, 2>&1>/dev/null );ctrl^c无法杀死,需要 kill -9%1 杀死进程;
- 守护进程:
- 特殊的后台进程;一直运行到系统关闭;
- linux有很多典型的守护进程: (进程名以 d 结尾的一般都是守护进程)
- crond:定时任务的守护进程;httpd :http服务的守护进程;sshd:ssh登录的守护进程;mysqld:mysql的守护进程;
2、进程的标记:
进程ID :pid
- 进程ID 时进程的唯一标记,每个进程拥有不同的ID ;
- 进程ID 表现为一个非负整数,最大值由操作系统限定(top命令,查看linux 内的所有进程);
- 父进程fork 创建子进程;进程的父子关系可以通过pstree 来查看
特殊进程:
- ID :0; 为idle 进程,是系统创建的第一个进程;
- ID :1; 为 init 进程,是进程0 的子进程,完成系统初始化; 同时也是所有用户的祖先进程;
3、进程的状态标记:
- man ps 命令来了解各种进程的标记;
跑两个后台进程;用命令 (ps -aux | grep 进程id)查看进程状态;
4、相关命令
-
top (linux下所有进程):PR代表优先级、VIRT虚拟内存、COMMAND 进程命令
-
kill(给进程发送信号,杀死进程):(kill -9 进程号)杀死对应进程;(kill -l)查看list 对应使用的数字;
-
+& (后台进程);
-
ps(当前状态)、pstree(进程树结构)、man ps(看进程标记文档)、
-
(ps -aux | grep 进程id)查看对应id进程状态;(ps -aux | grep ‘python3’)查看python3进程状态;
-
(ps -ef --forest) 查看进程树结构; -aux (显示详细信息);(ps - u 用户)查看用户进程;
-
(ps -aux --sort=-pcpu) 按照cpu使用来查看进程;(ps -aux --sort=-pmem) 按照内存使用来查看进程;
7、作业管理之进程调度
1、进程调度概述
- 进程调度:计算机通过决策决定哪个就绪进程可以获得CPU使用权;
- 保留旧进程运行信息,请出旧进程(收拾包袱)
- 选择新进程,准备换镜并分配CPU (新进驻)
要了解:
- 就绪队列的排队机制:一定方式排队列,便于调度程序最快找到就绪进程;
- 选择运行进程的委派机制:一定的策略选择就绪进程,将CPU资源分配给他;
- 新老进程的上下文切换机制:保存当前进程的上下文信息,装入被委派执行进程的运行上下文
进程调度分类: 按照老进程有没有执行完
- 非抢占式的调度;
CPU分配给某个进程,就要一直使用下去;
调度程序不得抢占被使用的处理器;
直到进程完成工作或因为IO阻塞才让出处理器;
- 抢占式的调度;
允许调度程序以一定的策略暂停当前运行的进程;
保存好旧进程的上下文信息,分配处理器给新进程;
2、进程调度算法
- 先来先服务调度算法:FIFO作为队列;
- 短进程优先调度算法:不利于长作业进程的执行;
- 高优先权优先调度算法:前台进程优先级高于后台进程;
- 时间片轮转调度算法
按照先来先服务原则排就绪队列
每次从队列头部取出待执行进程,分配一个时间片执行
相对公平的调度,但不能保证及时相应用户
7、作业管理之死锁
1、 死锁的产生:
- 死锁:
- 两个或以上进程执行过程中,由于竞争资源或者彼此通信而造成的阻塞现象;
- 若无外力作用,他们都将无法推进下去;
- 此时称系统处于死锁状态或系统产生了死锁;
- 永远在互相等待的进程称为死锁进程;
和之前的哲学家问题一致;
死锁产生原因:
- 竞争资源:(共享资源数量不满足各个进程需求;各个进程间发生资源竞争导致死锁)
等待请求的资源被释放;
自身占用资源不释放;
- 进程调度顺序不当
死锁产生的四个必要条件: 如果是死锁必会满足这四个条件
- 互斥条件 :进程对资源的使用是排他性的使用(某资源只能由一个进程使用,其他进程需等待)
- 请求保持条件:
- 进程至少保持一个资源,又提出新的资源请求
- 新资源被占用,请求被阻塞
- 被阻塞的进程不释放自己保持的资源
- 不可剥夺条件:
- 进程获得的资源在未完成只用前不能被剥夺
- 获得的资源只能由进程自身释放
- 环路等待条件:发生死锁时,必然存在 进程-资源 环型链
2、死锁的处理:
预防死锁的方法: (只需要破坏一个必要条件即可破坏死锁)
- 互斥条件 :进程对资源的使用是排他性的使用(某资源只能由一个进程使用,其他进程需等待)
- 破坏请求保持条件:
- 系统规定进程运行之前,一次性申请所有需要的资源
- 进程运行器件不会提出资源请求
- 不可剥夺条件:
- 当一个进程请求新的资源得不到满足,必须释放占有的资源
- 进程运行时占有的资源可以被释放,意味着可以被剥夺
- 环路等待条件:
- 可用资源线性排序,申请必须按照需要递增申请
- 线性申请不再形成环路,从而摒弃了环路等待条件
银行家算法:
是个可操作的著名的避免死锁的算法;
以银行借贷系统分配策略为基础的算法;
- 客户申请的贷款时有限 的,每次申请需声明最大资金量
- 银行家在能够满足贷款时,都应该给用户贷款
- 客户在使用贷款后,能够及时归还贷款
五、存储管理
- 确保计算机有足够的内存处理数据;
- 确保程序可以从可用内存中获取一部分内存直接使用;
- 确保程序可以归还使用后的内存以供其他程序使用;
1、内存分配
内存分配过程:
- 单一连续分配:
- 单一来纳许分配是最简单的内存分配方式;
- 只能在单用户、单进程的操作系统中使用;
- 固定分区分配:
- 固定分区分配是支持多道程序的最简单的存储分配方式;
- 内存空间被划分为若干固定大小的区域;
- 每个分区提供给一个程序使用,互不干扰;
- 动态分区分配:
- 根据实际需要,动态分配内存空间
- 动态分区空闲表(池策略)、动态分区空闲链表(可以合并大的空间)(堆策略);
动态分区分配算法:
- 首次适应算法(FF算法):
- 分配时从开始,顺序查找适合内存区,没有合适区则分配失败;
- 分配时从头部开始,使得头部地址空间不断被划分;太多碎片;
- 对首次适应算法改进:循环适应算法(从上次检索结束为止继续检索)
- 最佳适应算法(BF算法)
- 要求空闲区链表按照容量大小排序(从小到大);
- 遍历链表找到最佳合适的空闲区;
- 快速适应算法(QF算法)
- 要求有多个空闲区链表;
- 每个空闲区链表存储一种容量的空闲区;可以最快找到;
2、内存回收
内存回收过程:
- 回收区位于空闲区后:
- 不需要新建空闲链表;直接把回收区空间扩充进空闲区;
- 回收区位于空闲区前:
- 将回收区与空闲区合并;新的空闲区使用回收区的地址;
- 回收区位于两个空闲区之间:
- 将回收区与俩空闲区合并;新的空闲区使用空闲区1的地址;
- 回收区单独存在:
- 将回收区创建新的空闲节点;并将其插入相应空闲区链表中;
3、段页式存储管理
进程角度理解进程空间管理;操作系统如何管理进程空间;
1、页式存储管理
- 字块是相对物理设备的定义;
- 页面是相对逻辑空间的定义;
- 将进程逻辑空间等分成若干大小的页面;
- 相应的把物理内存空间分成与页面大小一致的物理块;
- 以页面为单位,把进程空间装进屋里内存中分散的物理块中;
如果有一段连续逻辑分布在多个页面中,将大大降低执行效率:所以提出段式管理
页面太小造成太多内存碎片(页内碎片);页面太大造成难以分配(连续内存空间中不一定放得下);
页式存储管理的地址也分为:页号 +页内偏移
多级页表: 一次将一个页表导入内存,两级页表的话,就可以少拷贝很多项;相应也会慢;
2、段式存储管理
- 将进程逻辑空间等划分成若干段(非等分);
- 段长度由进程逻辑长度决定;
3、页式、段式存储管理 - 优缺点
- 段式、页式都离散地管理了进程的逻辑空间;
- 页是物理单位;段是逻辑单位;
- 分页为了合理利用空间;分段为了满足用户要求;
- 页大小由硬件固定;段大小可动态变化;
- 页表信息是一维的;段表信息是二维的;
- 分页可以有效利用内存利用率;(虽然存在页内碎片)
- 分段可以更好满足用户需求;(存在段外碎片)
4、段页式存储管理
先分段,段内分页;
- 先把逻辑空间按段式管理分成若干段;
- 再把段内空间按页式管理等分成若干页;
4、虚拟内存
一个游戏有十几个G,内存只有4G,怎么运行这个游戏呢;
1、虚拟内存概述
虚拟内存是针对进程而言的;
为什么引入虚拟内存:
- 有些进程实际需要的内存很大,远超物理内存的容量;
- 多道程序设计,使得每个进程可用物理内存更加稀缺;
- 不可能无限增加物理内存,物理内存总有不够的时候;
- 有些进程实际需要的内存很大,远超物理内存的容量;多道程序设计,使得每个进程可用物理内存更加稀缺;不可能无限增加物理内存,物理内存总有不够的时候;程序可以使用一系列虚拟地址来访问大于可用物理内存的内存缓冲区。
- 程序可以使用一系列相邻的虚拟地址来访问物理内存中不相邻的大内存缓冲区。
- 不同进程使用的虚拟地址彼此隔离。
实现:
- 虚拟内存式操作系统内存管理的关键技术;
- 使得多道程序运行和大程序运行成为现实;
- 把程序使用内存划分,将部分暂时不适用的内存放置在辅存;
2、程序局部性原理
局部性原理包括了时间局部性原理、空间局部性原理;及(访问过的可能还要去访问、访问的内存附近的内存可能也要被访问)
局部性原理是实现虚拟内存技术的原因;
局部性原理: cpu在访问存储器时,无论存取指令、存取数据,所访问的存储单元趋于聚集在一个较小的连续区域中;
- 程序运行时,无需全部装入内存,装部分程序到内存中即可;
- 如果访问页不在内存,则发出缺页中断,发起页面置换;
- 从用户层面看,程序拥有很大空间,即 虚拟内存;
3、虚拟内存置换算法
- 先进先出算法 FIFO ;先进缓存的先被替换
- 最不经常使用算法 LFU ;淘汰最不常使用的字块;额外空间记录字块使用频率;
- 最近最少使用算法 LRU ; 优先淘汰一段时间内没使用的字块;一般使用双链表实现;把当前访问的节点放在表头(淘汰链表尾部);
机组中讲的内存和cache缓存置换也是这个;
- 高速缓存的替换时机:CPU获取缓存时,缓存中无对应数据;开始高速缓存替换;
- 主存页面的替换时机:主存缺页时,从辅存中加载数据;
区别:
-
替换策略发生在:cache-主存层次、主存-辅存层次;
-
cache-主存层次:主要为了解决速度问题;
-
主存-辅存层次:主要为了解决容量问题;
5、linux 存储管理
1、buddy内存管理算法
- buddy算法时经典的内存管理算法;
- 算法基于计算机处理二进制的优势,具有极高效率
- 为了解决外碎片;努力让内存与相邻内存合并能快速进行;
- 将内存外碎片问题转移成为内存内碎片(向上取2的幂使得内存分配大于需要);
buddy内存分配:
buddy内存回收:
2、linux交换空间
-
交换空间(swap)是磁盘的一个分区;
-
linux物理内存满时,会把一些内存交换到 swap空间;
-
swap空间 是初始化系统时配置的;
应用: -
冷启动依赖;(很多程序在启动过程使用大量内存;只是在启动时候使用的内存;)
-
系统睡眠以来;(睡眠时,内存数据保存至swap)
-
大进程空间依赖;
六、文件管理
1、文件的逻辑结构
- 逻辑结构的文件类型:
- 顺序文件、索引文件
1、辅存的存储空间分配
辅存分配方式
- 连续分配(存储要求高)->
- 隐式链接(随机访问效率差、可靠性差)->
- 显示连接(不支持高效的直接存储、检索时占用大空间)->
- 索引分配 (可随机访问、每个文件单独拥有一个索引块、文件较大时,索引分配方式优势大)
辅存存储空间管理:
- 和内存存储类似
目录管理
2、linux 文件基本操作
linux 目录
相对路径、绝对路径
- pwd(绝对路径)
- cd … (为上一级目录)(访问相对路径)
- ls -l(详细信息)、ls -al(包含了隐藏文件的详细信息)
linux 文件常用操作
文件的创建、删除、读取、写入;
- touch vim (都能创建)
- cat (可以查看 )
- rm (删除文件)、rm -r dev/(递归删除目录和里面文件)
- mv (移动、改名)
- mkdir (创建目录)
linux 文件类型
3、linux 文件系统
文件系统概览
EXT文件系统
- df -T (查看系统挂载磁盘信息)
- stat 文件 (查看文件具体信息)
七、设备管理
广义IO 设备
- 对CPU而言,凡是对CPU进行数据输入的都是输入设备
- 对CPU而言,凡是对CPU进行数据输出的都是输出设备(堆内存的读写也是io操作)
分类:
- 按使用特性分类、 按信息交换的单位分类(数据块 b - block、字符 c - char)、 按设备的共享属性分类、按传输速率分类;
IO设备的缓冲区
- IO设备的缓冲区为了解决CPU与IO设备速率的不匹配;
- 减少处理IO请求频率、提高CPU与IO设备间的并行性;
- 专用缓冲区只适用于特定IO进程,这样的IO进程较多时,对内存消耗较大;
- 操作系统划出可供多个进程使用的公共缓冲区,称其缓冲池;
SPOOLing 技术
虚拟设备技术
- 也是为了解决CPU与IO设备速率的不匹配;
- 是关于慢速字符设备如何与计算机主机交换信息的一种技术;
- 利用高速共享设备,将低速的独享设备模拟为高速的共享设备;
- 逻辑上,系统为每一个用户都分配了一台独立的高速独享设备;
即将高速共享设备(例如磁盘)存储进程需要打印的东西,通过spooling打印出去;低速独享设备(打印机)并没有给任何进程独享。
- 在输入、输出之间增加了排队转储环节(输入井、输出井)
- SPOOLing负责输入输出井与低俗设备之间的调度;
- 逻辑上,进程直接与高速设备交互,减少了进程的等待时间;
八、保护临界资源、通信
1、线程同步
实例代码如下:
互斥量 互斥锁(mutex)
- 互斥量保证先后执行;
- 互斥量保证了关键指令的原子性; (一系列操作不可被中断的特性;要么全部执行,要么全部没执行)
- 互斥量是线程同步的最简单的方法;
- 互斥量、互斥锁,他是处于两态之一的变量:解锁、加锁;
- 两个状态可以保证资源访问的串行性;
自旋锁 (spin_lock)
- 原理和互斥锁一致,使用前加锁;
- 使用自旋锁的线程会反复检查锁变量是否可用;
- 自旋锁不会让出CPU ,是一种忙等待的状态;相当于死循环等锁开;
优缺点:
- 自旋锁避免了进程、线程上下文切换的开销;
- 操作系统内部很多使用的是自旋锁;
- 自旋锁不适合单核CPU;因为占用CPU;
读写锁 (rwlock_t、rwlock_rdlock、rwlock_wrlock)
- 针对临界资源多读少写;
- 读取的时候不会改变临界资源的值;
- 读写锁是一种特殊自旋锁;
- 允许多个读者同时访问资源,以提高读性能;仅对读操作互斥;
- 读的时候读锁;写的时候写锁;
优缺点:
- 自旋锁避免了进程、线程上下文切换的开销;
- 操作系统内部很多使用的是自旋锁;
- 自旋锁不适合单核CPU;因为占用CPU;
- 比互斥锁快的多,针对的环境不同;
条件变量 cond_t ;cond_wait;cond_signal;
- 条件变量允许线程睡眠,直到满足某种条件;
- 当满足条件时,可以向该线程发出信号,通知唤醒;
- 需要配合互斥量使用;
这其实是因为前面的锁没有提及的问题:
- 缓冲器小于0,不允许消费者消费,消费者必须等待;
- 缓冲区满时,不允许生产者往缓冲区生产,生产者必须等待;
当生产者生产了一个产品的时候,唤醒可能等待的消费者;
当消费者消费了一个产品的时候,唤醒可能等待的生产者;
- wait 等待条件满足
- signal 发送唤醒
临界区linux 没有,windows有;更轻量级;在用户态实现;
2、fork 创建进程
fork 是系统调用
- 新创建的进程初始化状态与父进程一致
- 系统为fork的进程分配新的资源
- fork 返回两次,返回子进程id的是父进程,返回0的是子进程;
3、进程同步
- 进程间数据共享: 管道,消息队列,共享内存,unix 域套接字
对比:
- 易用性:消息队列 > unix 域套接字 > 管道 > 共享内存(配合信号量使用)
- 效率:共享内存 > unix 域套接字 > 管道 > 消息队列
- 常用:共享内存,unix 域套接字
-
异步通讯:信号
-
同步和互斥: 信号量
1、共享内存
- 进程空间是通过页表,通过段页式存储管理,与实际物理内存建立的映射;
- 所以进程间也是公用物理内存的;
- 操作系统的进程管理,使得进程间内存空间独立;进程默认是不能访问进程空间之外的内存空间的;
共享内存:
- 共享内存,允许不相关的进程访问同一片物理内存;
- 共享内存是两个进程之间共享和传递数据最快的方式;
- 共享内存未提供同步机制,需要借助其他机制管理访问;
使用共享内存:
- 申请共享内存 : shmget((key_t)1111,sizeof(struct ShmEntry),0666|IPC_CREAT);
- 连接到进程空间 : (ShmEntry*)shmat(shmid,0,0);连接到
- 使用共享内存
- 脱离进程空间&& 删除 : shmdt(entry); && shmctl(shmid,IPC_RMID,0);
2、Unix 域套接字
- 套接字本是网络通信;
- Unix系统提供的域套接字提供了网络套接字类似功能;
- 他提供了单机、简单、可靠的进程通信同步服务;
- 只能单机使用,不能跨机器使用;跨机器需要网络套接字;
- 相比共享内存,提供的是可靠的信息传递,不需要维护同步信息;
- Nginx 、 uWSGI 、 都会使用到;
server使用:
- 创建套接字
- 绑定套接字
- 监听套接字
- 接收&处理信息
client使用:
- 创建套接字
- 连接套接字
- 发送信息
3、共享内存 与 Unix 域套接字 实现代码
【C++】linux 下进程同步例子;实现共享内存、unix 域套接字;
用户态与内核态
上下文切换
协程
编写良好程序指南
九、项目实战
实现支持异步任务的线程池
- 了解语言的同步原理
- 实现线程安全的队列,实现基本任务对象;
- 实现基本任务对象
- 了解线程池
- 实现任务处理线程
- 实现任务处理线程池
- 实现异步任务处理对象;
实现线程安全的队列:
- 要获取当前队列元素数量
- 往队列中放元素
- 从队列中取元素
- 队列可能多个线程同时操作,要保证线程安全
多个线程同时访问:加锁
队列为空要获取:阻塞,条件变量
实现基本任务对象 task 用于存储任务的处理逻辑的
- 定义任务基本参数
- 定义任务唯一标记 uuid
- 存放具体的执行逻辑
1、线程池
what:
- 线程池是存放多个线程的容器;
- 当CPU调度线程执行后,不会销毁线程,而是将线程放回线程池重复利用;
why:
- 线程是稀缺资源,不应该被频繁创建和销毁;
- 为了架构解耦,线程创建与业务处理解耦 ,更加优雅;
- 线程池是使用线程的最佳实践;
使用线程池:减少创建和销毁花的时间和系统资源开销;
如果不适用线程池:可能造成系统创建大量同类线程而导致消耗完内存或者过度切换;
2、任务处理线程
- 任务处理线程需要不断从任务队列里取任务执行;
- 任务处理线程需要有一个标记,标记线程什么时候该停止;
实现任务处理线程:
- 基本属性:任务队列、标记
- 线程执行的逻辑
- 线程停止
3、实现任务处理线程池
- 存放多个任务处理线程
- 负责多个线程的启停
- 管理向线程池的提交任务,下发给线程去执行
- 实现基本属性
- 实现提交任务 put、batch_put 方法
- 实现线程启停 start 、join 方法
- 实现线程池大小 size 方法
init:
构造线程池;构造任务队列;
生成需要的线程数在线程队列中,传入任务处理线程;(任务处理线程用于从任务队列中取出任务去执行;
join:
每个线程取出来,stop他们;并清空线程池:池内所有线程pop、join;
put:
将任务put 进任务队列中;
size:
直接返回线程池size,线程安全队列已经实现了加锁。所以这里不用加锁;(线程安全队列中,用的是互斥锁)
4、测试、实现 :异步任务处理 AsyncTask
- 初始化一个线程池
- 生成一系列任务
- 往线程池提交任务执行(简单的打印任务即可)
实现异步任务处理AsyncTask
- 因为不知道任务什么 时候被执行、什么时候执行完
- 需要添加有一个标记,任务完成后标记完成;任务未完成时,获取任务结果,会阻塞获取线程;(条件变量)
- 以实现:将任务交给异步线程,主线程把要做的处理完了,再回去看异步线程的结果;
实现AsyncTask
- 设置运行结果 set_result
- 获取运行结果 get_result