建筑设计资料网站,怎样做淘宝的导购网站推广,网站访客qq抓取原理,体育新闻最新消息10条0.关注博主有更多知识
操作系统入门知识合集
目录
0.关注博主有更多知识
4.1进程概念
4.1.1进程基本概念
思考题#xff1a;
4.1.2进程状态
思考题#xff1a;
4.1.3进程控制块PCB
4.2进程控制
思考题#xff1a;
4.3线程
思考题#xff1a;
4.4临界资源与临…0.关注博主有更多知识
操作系统入门知识合集
目录
0.关注博主有更多知识
4.1进程概念
4.1.1进程基本概念
思考题
4.1.2进程状态
思考题
4.1.3进程控制块PCB
4.2进程控制
思考题
4.3线程
思考题
4.4临界资源与临界区
4.4.1临界资源与临界区
思考题
4.4.2锁机制
4.5同步和P-V操作
4.5.1同步和互斥的概念
4.5.2P-V操作概念 4.5.3P-V操作解决互斥问题
思考题 4.5.4P-V操作解决同步问题
思考题
4.5.5经典互斥与同步问题
4.1进程概念
4.1.1进程基本概念
描述和管理程序的运行过程称为进程。在Windows中可以打开任务管理器查看进程。 进程定义 进程是程序在某个数据集合上的一次运行活动。注意抠字眼进程是运行活动前提是程序在某个数据集合上这个数据集合就是软、硬件环境被多个进程共享的环境一个进程对应一次运行活动。
进程的特征 1.动态性进程是程序的一次执行过程动态的产生动态的消亡。 2.并发性进程同其他进程一起向前推进。 3.异步性每个进程按照自己各自的速度向前推进。 4.独立性进程是操作系统分配资源和调度CPU(没了解线程之前暂时这么理解)的基本单位。
进程与程序的区别 1.动态与静态进程是动态的它是程序的一次执行过程程序是静态的它是一组指令的有序集合。 2.暂存与长存进程是暂存的它在内存上短暂驻留程序是长存的它在磁盘或者其他介质上长期保存。 3.程序和进程的对应关系一个程序可能有多个进程(一个程序运行多次就会产生多个进程)。
进程的类型 1.按使用资源的权限分为系统进程和用户进程系统进程指与操作系统内核相关的进程用户进程指运行在用户态的进程。 2.按对CPU的依赖性划分为偏CPU进程和偏I/O进程偏CPU进程指的是计算密集型进程偏I/O进程指的是与用户交互频率较高的进程。 3.......
思考题
1.进程具有异步性即每个进程不考虑其他进程的运行速度按自己的逻辑往后运行。那么这个异步的特点对于进程来说是优点还是缺点
当然是进程的优点异步性能够提高CPU和I/O设备的利用率。进程在运行时根本不需要考虑其他进程运行的怎么样只需要按照自己的逻辑往后运行即可。
4.1.2进程状态
进程的状态分为 1.运行状态(Running)进程已经占有CPU并且在CPU上运行。 2.就绪状态(Ready)具有运行条件但没有CPU而暂时不能运行处于就绪态的进程只要占有CPU便立马可以运行。 3.阻塞状态(Block)进程因为等待某项服务的完成或者信号而不得不停下来例如调用系统调用等待执行结果、I/O操作操作、等待合作进程的信号......
进程状态的变迁 进程的状态可以根据一定的条件相互转换。 注意我并没有标注可以从阻塞态到运行态、就绪态到阻塞态的转换。
实际上不同的操作系统有不同的进程状态某些操作系统甚至具有新建态(new)和终止态(terminate) Linux的进程状态 1.可运行态Linux没有就绪态它把占有CPU的进程和处于就绪队列的进程的状态统称为可运行态。 2.阻塞态Linux分为浅度阻塞和深度阻塞。浅度阻塞的进程可以被其他进程的信号或者时钟唤醒反之深度阻塞的进程则不能。 3.僵尸态进程终止运行时所处的状态处于这个状态的进程会释放大部分资源。 4.挂起态当调试程序时这个进程就处于挂起态。
思考题
1.操作系统中为何没有阻塞到运行和就绪到阻塞这样的状态迁移
进程调度是操作系统控制的进程本身不具有调度的能力。当处于阻塞态的进程等待的服务完成后它就具备了运行的条件根据操作系统对各个状态的定义就必须放入就绪队列中等待操作系统的调度进程处于阻塞态的前提是请求服务或者访问I/O这本身就是一条指令它需要被CPU执行而处于就绪态的进程并没有占有CPU所以不会执行这条指令。
4.1.3进程控制块PCB
进程控制块(Process Control Block)是描述进程状态、资源和相关进程关系的数据结构PCB是进程的标志创建进程时创建PCB进程撤销时撤销PCB。
所以可以把进程的概念重新划分一下进程程序PCB。 PCB数据结构如何用C语言实现的话它就是一个struct结构体里面包含了进程状态、资源等绝大部分属性集合。也就是说操作系统在调度进程的时候不需要调度程序本身的代码和数据而是调度PCB即可。 PCB中的基本成员 Linux的进程控制块tast_struct(源码就不演示了) 和进程标识相关的成员变量 1.Linux进程的标识tast_struct有一名为PID的成员它标识当前进程的唯一标识符PPID表父进程的唯一标识符PGID表示进程组的唯一标识符。 2.Linux进程的用户标识UID成员表示用户ID(可以用来区分哪个用户创建的进程)GID表示用户组ID。
进程切换 实际上我们可以回想一下中断的处理过程保护现场、处理中断程序、恢复现场......进程切换可以从这个角度理解。 1.进程的上下文在PCB有一Context字段描述上下文上下文即表示进程的运行环境通常与CPU有关(CPU上与当前进程有关的寄存器和寄存器里面的内容)。 2.进程切换过程进程切换发生在时间片轮转结束时我们可以把它看成中断。进程换出CPU时需要把上下文压入栈要换入的进程需要把上下文从栈上放到CPU里面去。
4.2进程控制
进程控制概念在进程的生命周期期间操作系统对进程的全部控制行为。
进程会发生状态的转换而这个转换不是进程本身完成的而是操作系统对进程进行控制从而让进程发生转换。典型的控制行为有四个创建进程、阻塞进程、撤销进程、唤醒进程。
进程创建 1.功能创建一个具有唯一标识符的进程。创建进程需要的参数有唯一标识符、优先级、程序起始地址、CPU初始状态以及进程所需要的资源。 2.创建进程的过程首先操作系统还会创建一个空白的PCB然后获得并赋予进程唯一标识符ID然后为进程分配空间然后初始化PCB(例如将唯一标识符ID写入PCB)最后将该进程(PCB)插入相应的就绪队列。 进程撤销 1.功能撤销一个指定的进程收回进程所占用的资源撤销该进程的PCB。 2.进程撤销的时机进程正常结束或进程异常结束或进程受到外界干预而不得不结束。撤销进程所需要的参数仅需要进程的唯一标识符。 3.进程撤销的过程操作系统首先在PCB队列(进程队列)(存放PCB的数据结构)通过进程唯一标识符检索出指定的PCB然后读取PCB的状态如果该进程为运行态就会改变其状态并使其终止然后释放进程所占的资源最后将PCB从PCB队列移除。需要注意的是在Linux中进程可能有一个或多个子进程这些子进程也可能有一个或多个子进程......在撤销一个进程时操作系统需要递归式的检查该进程是否有子进程如果确实有则先撤销子进程。 进程阻塞 1.功能停止进程的执行使其成阻塞态。 2.阻塞的时机进程请求操作系统完成某个服务而由于某种原因操作系统不能立即满足进程的要求进程启动某种I/O操作由于I/O操作非常缓慢所以进程必须阻塞等待该操作完成新数据尚未到达例如准备接收一个信号但该信号迟迟未到无新工作可做通常是进程的自我阻塞例如程序员故意安排一个sleep()调用。 3.操作系统阻塞一个进程所需要的参数在现代操作系统中操作系统要控制进程的阻塞有必要获取进程阻塞的原因因为不同的阻塞原因会构建不同的阻塞队列不同的阻塞队列有不同的管理策略。这样能大大提高计算机的工作效率。 4.进程阻塞的过程操作系统将当前正在运行的进程终止将运行态改为阻塞态根据阻塞原因插入到相应的阻塞队列由调度程序完成进程发起的阻塞请求。
进程唤醒 1.功能唤醒处于阻塞队列当中的某个进程 2.唤醒进程的时机阻塞队列当中的队头进程得到了想要的东西(例如系统完成了请求的服务、I/O操作完成、信号到达等)操作系统将这个进程从阻塞队列拿出放入就绪队列。
原语原语是由若干指令构成的具有特定功能的函数这个函数在执行过程中不可被中断。在外部看来原语具有原子性(只有未完成和完成两态)。
进程控制原语 进程控制模块是操作系统的一部分而操作系统在完成进程控制时不希望被任何东西打断所以所有的进程控制模块都是原语创建原语、撤销原语、阻塞原语、唤醒原语......
思考题
1.为什么根据进程不同的阻塞原因构建不同的阻塞队列能提高计算机的工作效率
进程发起阻塞的原因是有多种的每种请求所消耗的时间都是不一样的。如果都使用一个统一的阻塞队列当一个进程发起一个系统服务请求时会被放入这个统一的阻塞队列当中假设改系统服务仅需2ms完成而这个进程被放入阻塞队列之前已经有一个正在等待I/O完成的进程已经在队列当中了它需要等待40ms这就会造成请求系统服务的进程多阻塞40ms这是非常耽误工作效率的行为。
4.3线程
线程概念 1.线程是CPU直接运行的实体是CPU调度的基本单位。线程的概念引出之后进程将不再是CPU调度的基本单位。 2.一个进程可以有多个执行路径这些路径叫做线程。 3.多个线程可以同时共享CPU从而实现并发。
什么是执行路径在不使用多线程技术的编程当中进程只有一条执行路径。假设我们有一个画圆函数和一个画方函数则进程只能串行的去执行这两个函数。但是当使用多线程技术时可以分配多个执行路径可以让线程1去执行画圆函数线程2去执行画方函数因为线程可以同时共享CPU所以这两个函数是一起执行的就达到了画圆的同时画方的目的。
单线程程序整个进程只有一个线程不适用多线程技术时都是单线程程序这个线程称为主线程。
多线程程序整个进程有至少两个线程多个线程当中一定存在一个主线程。
多线程的典型应用场景 1.程序的多个功能需要并发运行例如在线视频程序在线视频程序需要将视频解码、音频解码、网络接收等等这几个模块我们不希望它们是串行执行的(因为这样会造成视频播放完了才播放音频)我们希望这几个模块是并发的。 2.具有窗口互动的程序窗口是用户看到的前台用户操作实际操作的就是前台前台接收到的数据要发送给后台。这个过程我们也希望是并发执行的在后台计算的过程当中用户还能够与前台发生交互而不是后台在计算时前台的功能就丧失了。 3.多核CPU上的程序当CPU有多个核时我们希望能够高效利用CPU的计算能力就要使用多线程技术占用CPU的多个核。
多线程的缺点多线程并不是完美的它也会带来许多让人头疼的问题。例如程序调试起来是非常困难的因为是多个执行流并发执行的并发的过程难以控制因为CPU的调度是随机的我们不能预测线程安全问题这也是最严重的问题当多个线程同时访问同一份资源时就会产生数据冲突、数据不一致等等问题。
思考题
1.给定两个函数Rotate的功能是光标旋转Progress的功能是进度条。利用C线程库编写一个程序使这两个函数可以并发执行。(利用了EasyX图形库插件)
void Rotate()//旋转
{char buffer[4] { |,/,-,\\ };//注意转义字符int index 0;while (true){char puts[2] { 0 };puts[0] buffer[index];outtextxy(10, 10, puts);index % 4;Sleep(100);}
}
void Progress()//进度条
{while (true){char buffer[102] { 0 };int cnt 0;while (cnt 100){outtextxy(10, 40, buffer);fflush(stdout);buffer[cnt] #;Sleep(50);}cleardevice();}
}
可以发现这两个函数都是死循环就是说执行Rotate就不会执行Progress执行Progress就不会执行Rotate。我们的最终方案如下
void Rotate()//旋转
{char buffer[4] { |,/,-,\\ };//注意转义字符int index 0;while (true){char puts[2] { 0 };puts[0] buffer[index];outtextxy(10, 10, puts);index % 4;Sleep(100);}
}void Progress()//进度条
{while (true){char buffer[102] { 0 };int cnt 0;while (cnt 100){outtextxy(10, 40, buffer);fflush(stdout);buffer[cnt] #;Sleep(50);}cleardevice();}
}int main()
{initgraph(1024, 480);//主线程分出两个执行路径(创建两个线程现在就有三个线程了)thread t1(Rotate);//线程1执行Rotatethread t2(Progress);//线程2执行Progresst1.join();t2.join();return 0;
} 4.4临界资源与临界区
4.4.1临界资源与临界区
我们先看两段伪代码 如果说变量i是程序A和程序B的一个全局可见变量并且程序A和程序B是并发运行那么最后的输出结果可能是这样的 造成结果1的原因可能是程序A先执行完程序B再执行造成结果2的原因可能是程序A执行到3)时中断程序B执行完毕此时i的值由100被程序B修改为了200造成结果3的原因可能是程序B执行到3)时中断程序A执行完毕此时i的值由200被程序A修改为了100。
因为并发的执行过程是不可预见的所以想要保证程序A和程序B每次执行都能百分之百输出正确结果时就需要设定一个特定区域这个特定区域不能让两个程序同时进入只能先后进入。 我们让绿色部分为我们所谓的特定区域那么程序A在执行这个区域的语句时程序B必须在1)语句执行结束准备进入特定区域时阻塞等程序A执行到5)语句时程序B才能够继续向下执行。通过这样的方法就能够确保程序A和程序B每次都输出正确的结果。
注意以下所说的执行路径可能是进程也可能是线程。
临界资源(Critical Resource)一次只允许一个执行路径独占访问、使用的资源。例如上述处于特定区域的i变量。
临界区(Critical Section):执行路径访问临界资源的程序段。例如上述的绿色部分的特定区域。
临界区和临界资源的访问特点并发的执行路径不能同时进入临界区也就是排他性(互斥)。
临界区访问机制的四个原则 1.忙则等待当临界区正在被一个执行路径占有时这个临界区就处于忙状态处于忙状态的临界区不能被其他执行路径占有。 2.空闲让进当临界区没有执行路径占有时这个临界区就处于空闲状态处于空闲状态的临界区能且仅能被任意一个具有权限的执行路径占有。 3.有限等待执行路径进入临界区的请求应该在有限时间内得到满足否则就会让这个执行路径处于饥饿状态(也就是请求一个东西时迟迟得不到响应)。 4.让权等待当某一执行路径在临界区外被阻塞时它应当放弃对CPU的占有从而让其他执行路径得到CPU。
思考题
1.临界区设置的大些好还是小些好
我认为是尽量小。如果临界区设置的过大就会失去并发的意义。
4.4.2锁机制
我们要把共享的资源(能被多个执行路径同时访问)变成临界资源就需要一种保护机制这个保护机制的过程就是将访问共享资源的程序段设置为临界区设置为临界区的机制我们称为锁。
基本原理 上锁操作 1.设置一个标志假设它为S 2.这个S为1时表明临界资源可用为0时表明临界资源不可用 3.若临界资源处于不可用状态想要访问这个临界资源的执行路径应当在临界区外等待 4.若临界资源处于可用状态那么执行路径可以进入临界区访问该临界资源同时需要将S由1置0 开锁操作 执行路径离开临界区时需要将S由0置1
我们以一段伪代码来说明锁机制的原理 上锁过程当执行路径进入临界区时先进行上锁操作直接将S设为0并退出该函数此时该执行路径可以进入临界区访问临界资源同时这个临界资源处于不可用状态当另一个执行路径要进入临界区时首先做的工作也是上锁但此时S的值为0所以会执行goto语句一直跳转执行if语句从而达到阻塞效果。
开锁过程当正在处于临界区的执行路径离开临界区后应当紧接着将S的值设为1表明该临界资源可用。
我们再以流程图的方式重新对程序A和程序B加工 上面介绍的锁机制能够解决互斥问题但同时我们需要注意锁的竞争问题
#include iostream
#include thread
#include mutex
using namespace std;
#include unistd.hint tickets 1000;
mutex mtx;void start_routine(const char* name)
{while(true){mtx.lock();if(tickets 0)//如果票还够{cout name get ticket: tickets-- endl;mtx.unlock();}else{mtx.unlock();break;}}
}int main()
{thread t1(start_routine,pthread 1);thread t2(start_routine,pthread 2);t1.join();t2.join();return 0;
} 明显可以看出输出的结果并不是交替抢票。原因就在于锁机制确实能够确保对临界资源的互斥访问当线程1开锁之后会立即执行循环进入下一次上锁此时即使线程2已经在临界区外等候但线程2对于锁的竞争能力没有线程1强(因为线程1距离锁最近)。
4.5同步和P-V操作
4.5.1同步和互斥的概念
互斥慨念上述的锁机制就能解决互斥问题。互斥指的是在任意时刻一份公共资源只能被一个执行流访问。
同步关系若干执行路径为了完成一个共同的任务需要相互协调运行步伐(上面的抢票程序中的两个线程就没有相互协调运行步伐)实现同步关系的关键在于一个执行路径开始某个操作之前必须要求另一个执行路径已经完成了某个操作否则前者执行路径只能等待(也就是必须保证互斥)。
现实生活中同步关系的例子司机与售票员 1.司机要做的工作就是起步、行驶、停车在起点站和终点站之间一直重复这三个动作 2.售票员要做的工作就是关门、售票、开门在起点站和终点站之间一直重复这三个动作 3.司机和售票员之间就会产生一种微妙的同步关系司机要起步售票员就必须先关门售票员要开门司机就必须先停车。
4.5.2P-V操作概念
单纯使用锁机制不一定能够完成同步任务我们可以使用信号量来完成这个任务。
红绿灯能够控制各向车流有序通过交叉路口这也产生了一种同步关系车要行驶经过交叉路口红绿灯就必须是绿色的如果红绿灯是红色的车就必须等待。
这种红绿灯思想恰好用于操作系统中同步的基本思想执行路径在运行过程中受信号量的状态控制并能改变信号量信号量的状态不同可以使执行路径阻塞还是唤醒信号量的状态可以被执行路径修改。
信号量的P-V操作 信号量的本质就是一个资源计数器这个计数器描述了公共资源的数量。信号量其中还有一个阻塞队列。 1.P操作(荷兰语Passeren通过的意思)执行路径在进入临界区之前要先执行P操作P操作的过程会将S值减1(表示该资源被使用一份)若差值大于或等于0P操作函数退出执行路径进入临界区若差值小于0该执行路径将会被放入阻塞队列当中。 2.V操作(荷兰语Vrijgeven释放的意思)执行路径在离开临界区之后首先执行V操作V操作的过程会将S值加1(表示该资源被释放一份)若和大于0V操作函数退出执行路径继续向后执行(和大于0表明阻塞队列没有执行路径)若和小于等于0该执行路径会先从阻塞队列唤醒一个执行路径再退出V操作函数向后执行(和小于等于0表明S加1之前S是负数阻塞队列存在执行路径)。 4.5.3P-V操作解决互斥问题
前面提到过解决互斥问题的一种机制——锁机制。根据P-V操作的概念可以发现P-V操作也能解决互斥问题。
互斥问题的实质实现对临界资源的互斥访问即只允许一个执行路径进入临界区。
P-V操作解决互斥问题的过程想要进入临界区的执行流必须先执行P操作执行P操作的时候有可能会被阻塞执行路径离开临界区后要执行V操作执行V操作的时候有可能会唤醒一个执行路径。关键点在于信号量的S初值要设置合理。 以一个具体的例子来更好的理解P-V操作实现互斥 假设有3个执行路径P1、P2、P3每一个执行路径都有一个临界区CSa、CSb、CSc这些临界区都对同一份临界资源访问。因为三个执行路径看到的临界资源只有一份我们可以把信号量的S初值设为1。 可以得出结论可以通过设置合理的信号量初值配合信号量的机制就可以结局互斥问题。
思考题
1.如上述的三个执行流Pa、Pb、Pc这个三个执行流并发执行的过程中是不是一定会发生阻塞和唤醒操作如果将信号量的S初值设为0会有结果设为2会有什么结果应该如何合理设置信号量的初值
它们三个在并发执行的过程中不一定会发生阻塞和唤醒操作其原因在于前面我们讲过单道批处理系统它在宏观上实现并发但微观上是一种串行如果Pa、Pb、Pc三个执行流都是串行执行的那么就不会发生阻塞和唤醒的情况(Pa执行完再到Pb执行......)。如果将信号量的处置设为0表示没有临界资源所以任何一个执行流都不可能进入临界区如果将信号量的初值设为2则表示有两份临界资源但实际上只有一份就会导致有两个执行流同时进入临界区同时访问临界资源。所以要想合理的设置信号量的初始值应该以共享资源的数量来判断。 4.5.4P-V操作解决同步问题
同步机制的实质运行条件不满足时执行路径应当暂停执行(阻塞)运行条件满足时能让执行路径立即执行。这两个点互相配合就能实现多个执行路径互相协调的向后执行。
P-V操作解决同步问题的基本思路 1.阻塞当前执行路径在进行关键操作(通常指进入临界区)之前执行P操作必要时可以阻塞 2.继续执行在关键操作之后(通常指离开临界区之后)执行V操作必要时可以唤醒某个正在阻塞的执行路径 3.必须定义有意义的信号量并设置合适的初值
再次分析司机与售票员的例子 1.售票员关门司机才可以起步 2.司机停车售票员才可以开门 3.仔细分析之后可以发现要设置两个信号量一个表示车门的状态一个表示车辆运行的状态(起步与停车)
那么我们可以以伪代码的形式来解决这个简单的同步问题 这里我们需要注意一个点信号量的计数器不仅仅可以表示资源的数量也可以表示资源使用的状态。 思考题
1.信号量即可以表示公共资源的数量又可以表示资源使用的状态我们应该如何合理设定信号量的初值
如果多个执行流需要访问同一份资源这时我们就需要先保证互斥以资源的数量来设定初值如果多个执行流访问不同的资源但每个执行流访问自己的那份资源时需要知道其他执行流资源的状态就应该以资源使用的状态来设定信号量初始。上述司机与售票员的例子司机只负责开车和停车售票员只负责开门和关门他们没有共同访问同一份资源但是司机要想起步就必须确保售票员已经把门关好售票员想开门就必须确保司机已经停车。
4.5.5经典互斥与同步问题
1.生产者消费者问题一群生产者(Productor)向一群消费者(Consumer)提供产品(数据)他们并不是直接交互的而是通过一块特定的缓冲区。 我们明确以下几点规则 1.生产者不能向满缓冲区存产品 2.消费者不能从空缓冲区取产品 3.缓冲区在任意时刻只能被一个生产者或消费者存或取
具体分析规则可以得到生产者与生产者之间要有互斥关系(不能同时向缓冲区存产品)消费者与消费者之间要有互斥关系(不能同时从缓冲区拿产品)生产者与消费者之间既要有互斥关系又要有同步关系(只生产不消费是不合理的只消费不生产也是不合理的)。
我们以伪代码来实现这两者之间的关系 如果真正把上面的代码实现那么生产者生产一个产品消费者就会消费一个产品如果缓冲区为空消费者会阻塞(不会消费)如果缓冲区为满生产者会阻塞(不会生产)。具体的代码实现将在后期Linux环境编程中提及。
2.读者和写者问题有一本书有一个或多个读者读有一个或多个写者写。
我们明确以下几点规则 1.允许多个读者同时读 2.只允许一个写者写 3.不允许读者和写者同时读和写 我们以伪代码实现这两者的关系 像上面这样确实能够保证写者与写者之间、读者与写者之间的互斥但这样做有些一刀切的味道。我们需要注意读者与读者之间不存在互斥我们可以添加一个读者计数变量(readCount)每次读者执行流要读书时变量加1仔细思考可以发现只要有一个读者那么写者就不能写书所以我们要做到的便是只要第一个读者进来读书我们就利用P-V操作与写者互斥此时无论读者的数量为多少都能保证与写者的互斥反之当最后一个读者选择不读书时读者计数变量减1直到最后一个读者退出此时才能让写者写书。那么改进之后的设计如下 此时还存在一个问题readCount是读者的全局可见变量也就是多个读者执行流共享的变量那么它也要被当成临界资源被保护所以我们还需要做最后一步工作