操作系统基础
什么是操作系统
- 操作系统(Operating System,简称 OS)是管理计算机硬件与软件资源的程序,是计算机的基石。
- 操作系统本质上是一个运行在计算机上的软件程序 ,用于管理计算机硬件和软件资源。 举例:运行在你电脑上的所有应用程序都通过操作系统来调用系统内存以及磁盘等等硬件。
- 操作系统存在屏蔽了硬件层的复杂性。 操作系统就像是硬件使用的负责人,统筹着各种相关事项。
- 操作系统的内核(Kernel)是操作系统的核心部分,它负责系统的内存管理,硬件设备的管理,文件系统的管理以及应用程序的管理。 内核是连接应用程序和硬件的桥梁,决定着系统的性能和稳定性
什么是系统调用
根据进程访问资源的特点,我们可以把进程在系统上的运行分为两个级别:
- 用户态(user mode) : 用户态运行的进程可以直接读取用户程序的数据。
- 系统态(kernel mode):可以简单的理解系统态运行的进程或程序几乎可以访问计算机的任何资源,不受限制。
我们运行的程序基本都是运行在用户态,如果我们调用操作系统提供的系统态级别的子功能咋办呢?那就需要系统调用了!
也就是说在我们运行的用户程序中,凡是与系统态级别的资源有关的操作(如文件管理、进程控制、内存管理等),都必须通过系统调用方式向操作系统提出服务请求,并由操作系统代为完成
这些系统调用按功能大致可分为如下几类:
- 设备管理。完成设备的请求或释放,以及设备启动等功能。
- 文件管理。完成文件的读、写、创建及删除等功能。
- 进程控制。完成进程的创建、撤销、阻塞及唤醒等功能。
- 进程通信。完成进程之间的消息传递或信号传递等功能。
进程和线程
什么是进程?
进程是操作系统中最重要的抽象概念之一,是资源分配的基本单位,是独立运行的基本单位。
进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的上下文(context)中。操作系统为了对资源统一管理,将同一个代码的运行称为一个进程。
程序:本质是一段静态的代码。为了解决某个问题,包含了所有功能的代码。执行程序为用户提供服务。
上下文是由程序正确运行所需的状态组成的。这个状态包括存放在内存中的程序的代码和数据,它的栈、通用目的寄存器的内容、程序计数器、环境变量以及打开文件描述符的集合。
进程一般由以下的部分组成:
-
进程控制块PCB,是进程存在的唯一标志,包含进程标识符PID,进程当前状态,程序和数据地址,进程优先级、CPU现场保护区(用于进程切换),占有的资源清单等。
-
程序段
-
数据段
进程特点
动态性:进程是动态产生,动态消亡的
并发性:任何进程都可以同其他进程一起并发执行
并发:一个CPU(利用时间片)同时执行多个任务
并行:多CPU同时执行多个任务
独立性:进程是一个能独立运行的基本单位,彼此不干扰。同时也是系统分配资源和调度的独立单位;
什么是线程?
-
是进程内部的执行单元,是CPU调度的基本单位,每个线程执行的任务可以不同,也可以相同。一个线程可以创建和撤消另一个线程,同一进程中的多个线程之间可以并发执行。
-
线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。
-
每个线程完成不同的任务,但是属于同一个进程的不同线程之间共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
为什么需要线程?
线程产生的原因:进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点:
-
进程在同一时刻只能做一个任务,很多时候不能充分利用CPU资源。
-
进程在执行的过程中如果发生阻塞,整个进程就会挂起,即使进程中其它任务不依赖于等待的资源,进程仍会被阻塞。
引入线程就是为了解决以上进程的不足,线程具有以下的优点:
-
从资源上来讲,开辟一个线程所需要的资源要远小于一个进程。
-
从切换效率上来讲,运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间(这种时间的差异主要由于缓存的大量未命中导致)。
-
从通信机制上来讲,线程间方便的通信机制。对不同进程来说,它们具有独立的地址空间,要进行数据的传递只能通过进程间通信的方式进行。线程则不然,属于同一个进程的不同线程之间共享同一地址空间,所以一个线程的数据可以被其它线程感知,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步措施)。
简述进程间通信方法
大概有 7 种常见的进程间的通信方式。
- 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
- 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
- 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
- 消息队列(Message Queuing) :是一个消息的链表。可以把消息看作一个记录,具有特定的格式以及特定的优先级。有写权限的进程可以向消息队列中添加新消息;有读权限的进程则可以从消息队列中读走消息。消息本质上是一种数据结构
- 消息队列存放在内核中,消息队列克服了信号承载信息量少,管道只能承载无格式字节流以及缓冲区大小受限等缺点。
- 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
- 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
- 套接字(Sockets) : 此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,
- 简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
每个进程各自有不同的用户地址空间,任何一个进程的全局变量在另一个进程中都看不到,所以进程之间要交换数据必须通过内核,在内核中开辟一块缓冲区,进程A把数据从用户空间拷到内核缓冲区,进程B再从内核缓冲区把数据读走,内核提供的这种机制称为进程间通信。
不同进程间的通信本质:进程之间可以看到一份公共资源;而提供这份资源的形式或者提供者不同,造成了通信方式不同。
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。
管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
共享内存是运行在同一台机器上的进程间通信最快的方式,因为数据不需要在不同的进程间复制。通常由一个进程创建一块共享内存区,其余进程对这块内存区进行 读写。
使用共享存储来实现进程间通信的注意点是对数据存取的同步,必须确保当一个进程去读取数据时,它所想要的数据已经写好了。通常,信号量被要来实现对共享存 储数据存取的同步
信号量 它是用来协调不同进程间的数据对象的,而最主要的应用是前一节的共享内存方式的进程间通信。本质上,信号量是一个计数器,它用来记录对某个资源(如共享内存)的存取状况。
简述进程组
进程组即多个进程的集合,进程组有一个组长,组长进程的PID等于进程组的PGID。
多线程模型
-
多对一模型。将多个用户级线程映射到一个内核级线程上。该模型下,线程在用户空间进行管理,效率较高。缺点就是一个线程阻塞,整个进程内的所有线程都会阻塞。几乎没有系统继续使用这个模型。
-
一对一模型。将内核线程与用户线程一一对应。优点是一个线程阻塞时,不会影响到其它线程的执行。该模型具有更好的并发性。缺点是内核线程数量一般有上限,会限制用户线程的数量。更多的内核线程数目也给线程切换带来额外的负担。linux和Windows操作系统家族都是使用一对一模型。
-
多对多模型。将多个用户级线程映射到多个内核级线程上。结合了多对一模型和一对一模型的特点。
进程调度策略的基本设计指标
-
CPU利用率
-
系统吞吐率,即单位时间内CPU完成的作业的数量。
-
响应时间。
-
周转时间。是指作业从提交到完成的时间间隔。从每个作业的角度看,完成每个作业的时间也是很关键
-
平均周转时间
-
带权周转时间
-
平均带权周转时间
-
进程调度算法 5
为了确定首先执行哪个进程以及最后执行哪个进程以实现最大 CPU 利用率,计算机科学家已经定义了一些算法,它们是:
- 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 时间片轮转调度算法 : 时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- 如果在时间片结束时进程还在运行,则CPU将被剥夺并分配给另一个进程。如果进程在时间片结束前阻塞或结束,则CPU当即进行切换
- 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。
- 多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
- 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级
先来先服务调度算法:创建一个任务队列,一旦有新任务就加入这个队列,CPU完成一个任务后就从队列取任务。
短作业(进程)优先调度算法:针对较短的作业,优先调给CPU工作。
时间片轮转算法:每个时间片依次执行一个任务,时间片结束后将该任务放回任务队列。
多级反馈队列调度算法: 是一种根据先来先服务原则给就绪队列排序,为就绪队列赋予不同的优先级数,不同的时间片,按照优先级抢占CPU的调度算法。算法的实施过程如下:
按照先来先服务原则排序,设置N个就绪队列为Q1,Q2…QN,每个队列中都可以放很多作业;
为这N个就绪队列赋予不同的优先级,第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低;
设置每个就绪队列的时间片,优先权越高,算法赋予队列的时间片越小。时间片大小的设定按照实际作业(进程)的需要调整;
进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
***对于同一个队列中的各个进程,按照 ( 时间片轮转法调度 )***。比如Q1队列的时间片为N,那么Q1中的作业在经历了时间片为N的时间后,若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业即抢占式调度CPU。
进程状态 5
进程同步的方法
操作系统中,进程是具有不同的地址空间的,两个进程是不能感知到对方的存在的。有时候,需要多个进程来协同完成一些任务。 当多个进程需要对同一个内核资源进行操作时,这些进程便是竞争的关系,操作系统必须协调各个进程对资源的占用,进程的互斥是解决进程间竞争关系的方法。 进程互斥指若干个进程要使用同一共享资源时,任何时刻最多允许一个进程去使用,其他要使用该资源的进程必须等待,直到占有资源的进程释放该资源。 当多个进程协同完成一些任务时,不同进程的执行进度不一致,这便产生了进程的同步问题。需要操作系统干预,在特定的同步点对所有进程进行同步,这种协作进程之间相互等待对方消息或信号的协调关系称为进程同步。进程互斥本质上也是一种进程同步。 进程的同步方法
一,进程同步的几种方式
1、信号量
用于进程间传递信号的一个整数值。在信号量上只有三种操作可以进行:初始化,P操作和V操作,这三种操作都是原子操作。
P操作(递减操作)可以用于阻塞一个进程,V操作(增加操作)可以用于解除阻塞一个进程。
基本原理是两个或多个进程可以通过简单的信号进行合作,一个进程可以被迫在某一位置停止,直到它接收到一个特定的信号。该信号即为信号量s。
为通过信号量s传送信号,进程可执行原语semSignal(s);为通过信号量s接收信号,进程可执行原语semWait(s);如果相应的信号仍然没有发送,则进程会被阻塞,直到发送完为止。
可把信号量视为一个具有整数值的变量,在它之上定义三个操作:
- 一个信号量可以初始化为非负数
- semWait操作使信号量s减1.若值为负数,则执行semWait的进程被阻塞。否则进程继续执行。
- semSignal操作使信号量加1,若值大于或等于零,则被semWait操作阻塞的进程被解除阻塞。
2、管程
管程是由一个或多个过程、一个初始化序列和局部数据组成的软件模块,其主要特点如下:
- 局部数据变量只能被管程的过程访问,任何外部过程都不能访问。
- 一个进程通过调用管程的一个过程进入管程。
- 在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
管程通过使用条件变量提供对同步的支持,这些条件变量包含在管程中,并且只有在管程中才能被访问。有两个函数可以操作条件变量:
- cwait(c):调用进程的执行在条件c上阻塞,管程现在可被另一个进程使用。
- csignal(c):恢复执行在cwait之后因为某些条件而阻塞的进程。如果有多个这样的进程,选择其中一个;如果没有这样的进程,什么以不做。
3、消息传递
消息传递的实际功能以一对原语的形式提供:
- send(destination,message)
- receive(source,message)
这是进程间进程消息传递所需要的最小操作集。
一个进程以消息的形式给另一个指定的目标进程发送消息;
进程通过执行receive原语接收消息,receive原语中指明发送消息的源进程和消息
线程同步的方法
线程同步是两个或多个共享关键资源的线程的并发执行。应该同步线程以避免关键的资源使用冲突。操作系统一般有下面4种线程同步的方式:
- 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
- 信号量(Semphares) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。
- 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
- 临界区: 对多线程的串行化实现线程同步,保证同一时刻只有一个线程访问数据
线程间的通信方式
# 锁机制:包括互斥锁、条件变量、读写锁
*互斥锁提供了以排他方式防止数据结构被并发修改的方法。
*读写锁允许多个线程同时读共享数据,而对写操作是互斥的。
*条件变量可以以原子的方式阻塞进程,直到某个特定条件为真为止。对条件的测试是在互斥锁的保护下进行的。条件变量始终与互斥锁一起使用。
# 信号量机制(Semaphore):包括无名线程信号量和命名线程信号量
# 信号机制(Signal):类似进程间的信号处理
线程间的通信目的主要是用于线程同步,所以线程没有像进程通信中的用于数据交换的通信机制
进程的基本操作
以Unix系统举例:
-
进程的创建:fork()。新创建的子进程几乎但不完全与父进程相同。子进程得到与父进程用户级虚拟地址空间相同的(但是独立的)一份副本,包括代码和数据段、堆、共享库以及用户栈。子进程还获得与父进程任何打开文件描述符相同的副本,这就意味着当父进程调用 fork 时,子进程可以读写父进程中打开的任何文件。父进程和新创建的子进程之间最大的区别在于它们有不同的 PID。fork函数是有趣的(也常常令人迷惑), 因为它只被调用一次,却会返回两次:一次是在调用进程(父进程)中,一次是在新创建的子进程中。在父进程中,fork 返回子进程的 PID。在子进程中,fork 返回 0。因为子进程的 PID 总是为非零,返回值就提供一个明 确的方法来分辨程序是在父进程还是在子进程中执行。
pid_t fork(void);
-
回收子进程:当一个进程由于某种原因终止时,内核并不是立即把它从系统中清除。相反,进程被保持在一种已终止的状态中,直到被它的父进程回收(reaped)。当父进程回收已终止的子进程时,内核将子进程的退出状态传递给父进程,然后抛弃已终止的进程。一个进程可以通过调用 waitpid 函数来等待它的子进程终止或者停止。
pid_t waitpid(pid_t pid, int *statusp, int options);
-
加载并运行程序:execve 函数在当前进程的上下文中加载并运行一个新程序。
int execve(const char *filename, const char *argv[], const char *envp[]);
-
进程终止:
void exit(int status);
什么是孤儿进程?僵尸进程?
- 孤儿进程: 父进程退出,子进程还在运行的这些子进程都是孤儿进程,孤儿进程将被init进程(1号进程)所收养,并由init进程对他们完成状态收集工作。孤儿进程是无害的,不需要进行回收
2。 僵尸进程:子进程运行结束了而父进程还没有,而且父进程未对子进程进行回收,就会产生僵尸进程
-
原因:
- 子进程在完成工作后,会给父进程发送SIGCHILD信号,等待父进程进行处理
- 如果父进程没有妥善处理,就会产生僵尸进程
- 解决:两次fork()来避免僵尸进程
通过两次调用fork。父进程首先调用fork创建一个子进程然后waitpid等待子进程退出,子进程再fork一个孙进程后退出。这样子进程退出后会被父进程等待回收,而对于孙子进程其父进程已经退出所以孙进程成为一个孤儿进程,孤儿进程由init进程接管,孙进程结束后,init会等待回收。- 通过信号机制来避免僵尸进程
简述线程和进程的区别和联系
-
一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。
-
进程在执行过程中拥有独立的地址空间,而多个线程共享进程的地址空间。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。)
-
进程是操作系统对程序资源进行统一管理的基本单元。线程是进程进行服务提供的基本单元。CPU每时每刻执行的是线程。
-
通信:由于同一进程中的多个线程具有相同的地址空间,使它们之间的同步和通信的实现,也变得比较容易。进程间通信
IPC
,线程间可以直接读写进程数据段(如全局变量)来进行通信(需要一些同步方法,以保证数据的一致性)。 -
进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。
-
进程间不会相互影响;一个进程内某个线程挂掉将导致整个进程挂掉。我们创建的变量是可以被任何一个线程访问并修改的
-
一个进程内某个线程挂掉将导致整个进程挂掉
- 这要看怎么去理解死掉这个词,如果是正常的线程中止或者死循环这样的死掉,对进程的其他线程是没有影响的。如果是段错误除数为零等这类意外的死掉,默认情况就会中止整个进程,如果安装了对应的信号处理函数,就会触发信号处理调用。
-
进程适应于多核、多机分布;线程适用于多核。
进程和线程的基本API
单核cpu同一时刻能处理多少个进程
- 单核CPU在同一时间只能处理一个进程
- 多核CPU可以同时执行多个进程,进程个数不高于核数
首先应该明白两个概念:并发和并行
并发:当有多个线程在操作时,如果系统只有一个CPU,则它根本不可能真正同时进行一个以上的线程,它只能把CPU运行时间划分成若干个时间段,再将时间段分配给各个线程执行,在一个时间段的线程代码运行时,其它线程处于挂起状。.这种方式我们称之为并发(Concurrent)。
并行:当系统有一个以上CPU时,则线程的操作有可能非并发。当一个CPU执行一个线程时,另一个CPU可以执行另一个线程,两个线程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
协程
https://zhuanlan.zhihu.com/p/172471249
多线程有2个问题
1 系统线程占用很多的内存空间
2 多线程切换占用大量的系统时间
协程解决这2个问题
协程运行在线程之上,当一个协程执行完成后,可以选择主动让出,让另一个协程运行在当前线程之上。
协程并没有增加线程数量,只是在线程的基础之上通过分时复用的方式运行多个协程,而且协程的切换在用户态完成,切换的代价比线程从用户态到内核态的代价小很多
死锁
https://blog.csdn.net/Javascript_tsj/article/details/124148567
死锁是怎样产生的?
一旦出现死锁,整个程序既不会发生任何异常,也不会给出任何提示,只是所有线程都处于阻塞状态,无法继续。
死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。 产生死锁需要满足下面四个条件:
-
互斥条件 :(一个资源每次只能被一个进程使用)只有对必须互斥使用的资源的争抢才会导致死锁(如哲学家的筷子、打印机设备)。像内存、扬声器这样可以同时让多个进程使用的资源是不会导致死锁的(因为进程不用阻塞等待这种资源)。
不剥夺条件 :进程所获得的资源在未使用完之前,不能由其他进程强行夺走,只能主动释放。
请求和保持条件 :进程已经保持了至少一个资源,但又提出了新的资源请求,而该资源又被其他进程占有,此时请求进程被阻塞,但又对自己已有的资源保持不放。
循环等待条件 :存在一种进程资源的循环等待链,链中的每一个进程已获得的资源同时被下一个进程所请求
————————————————
只有四个条件同时成立时,死锁才会出现
如何预防死锁问题?
- 破坏请求与保持条件 :一次性申请所有的资源。
- 破坏不剥夺条件 :占用部分资源的线程进一步申请其他资源时,如果申请不到,可以主动释放它占有的资源。
- 破坏循环等待条件 :靠按序申请资源来预防。按某一顺序申请资源,释放资源则反序释放。破坏循环等待条件。
如何避免死锁问题?
避免死锁就是在资源分配时,借助于算法(比如银行家算法)对资源分配进行计算评估,使其进入安全状态
安全状态 指的是系统能够按照某种进程推进顺序(P1、P2、P3.....Pn)来为每个进程分配所需资源,直到满足每个进程对资源的最大需求,使每个进程都可顺利完成。称<P1、P2、P3.....Pn>序列为安全序列。若系统存在这种安全序列,说明系统处于安全状态
饥饿
在一个动态系统中,对于每类系统资源,操作系统需要一个分配策略,当多个进程同时申请 某类资源时,由分配策略确定资源分配给进程的次序。
有时资源分配策略是不公平的,即不能保证等待时间上限的存在。在这种情况下,即使系统没有发生死锁,某些进程也可能会长时间等待。当等待时间给进程推进和响应带来明显影响时,称发生了进程“饥饿”。
当“饥饿”到一定程度的进程所赋予的任务即使完成也不再具有实际意义时称该进程被“饿死”。
例如,当有多个进程需要打印文件时,如果系统分配打印机的策略是最短文件优先,那么长文件的打印任务将由于短文件的源源不断到来而被无限期推迟,导致最终的“饥饿”甚至饿死。
“饥饿”并不表示系统一定死锁,但至少有一个进程的执行被无限期推迟。饥饿与死锁的主要差别有:
1)进入“饥饿”状态的进程可以只有一个,而由于循环等待条件而进入死锁状态的进程却必须大于或等于两个。
2)处于饥饿状态的进程可以是一个就绪进程,如静态优先权调度算法时的低优先权进程,而处于死锁状态的进程则必定是阻塞进程。
页表
简述页表
页表用于存储虚拟地址中的虚拟页面号和物理页面号的映射关系。 除此之外,有些页的读写有限制,页表也通过其他存储位,标记该页访问位,是否在内存中(可能被页面置换出去了)等等。
简述多级页表
多级页表用于减少内存的占用。以二级页表为例,虚拟地址被分为DIR,PAGE和offset三部分,通过顶级页表和DIR,寻找到该二级页表的起始位置,再通过二级页表的起始位置和PAGE,找到页物理地址,最后加上页偏移,即可得到最终的物理地址。
1114. 按序打印
给你一个类:
public class Foo { public void first() { print("first"); } public void second() { print("second"); } public void third() { print("third"); } }
三个不同的线程 A、B、C 将会共用一个 Foo
实例。
- 线程 A 将会调用
first()
方法 - 线程 B 将会调用
second()
方法 - 线程 C 将会调用
third()
方法
请设计修改程序,以确保 second()
方法在 first()
方法之后被执行,third()
方法在 second()
方法之后被执行。
class Foo { private AtomicInteger firstJobDone = new AtomicInteger(0); private AtomicInteger secondJobDone = new AtomicInteger(0); public Foo() {} public void first(Runnable printFirst) throws InterruptedException { // printFirst.run() outputs "first". printFirst.run(); // mark the first job as done, by increasing its count. firstJobDone.incrementAndGet(); } public void second(Runnable printSecond) throws InterruptedException { while (firstJobDone.get() != 1) { // waiting for the first job to be done. } // printSecond.run() outputs "second". printSecond.run(); // mark the second as done, by increasing its count. secondJobDone.incrementAndGet(); } public void third(Runnable printThird) throws InterruptedException { while (secondJobDone.get() != 1) { // waiting for the second job to be done. } // printThird.run() outputs "third". printThird.run(); } }
class Foo { private final CountDownLatch firstDone; private final CountDownLatch secondDone; public Foo() { firstDone = new CountDownLatch(1); secondDone = new CountDownLatch(1); } public void first(Runnable printFirst) throws InterruptedException { // printFirst.run() outputs "first". Do not change or remove this line. printFirst.run(); firstDone.countDown(); } public void second(Runnable printSecond) throws InterruptedException { firstDone.await(); // printSecond.run() outputs "second". Do not change or remove this line. printSecond.run(); secondDone.countDown(); } public void third(Runnable printThird) throws InterruptedException { secondDone.await(); // printThird.run() outputs "third". Do not change or remove this line. printThird.run(); } }
class Foo { private Semaphore s2; private Semaphore s3; public Foo() { s2 = new Semaphore(0); s3 = new Semaphore(0); } public void first(Runnable printFirst) throws InterruptedException { printFirst.run(); s2.release(); } public void second(Runnable printSecond) throws InterruptedException { s2.acquire(); printSecond.run(); s3.release(); } public void third(Runnable printThird) throws InterruptedException { s3.acquire(); printThird.run(); } }
什么是内核态和用户态?
为了避免操作系统和关键数据被用户程序破坏,将处理器的执行状态分为内核态和用户态。
内核态是操作系统管理程序执行时所处的状态,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。
用户程序运行在用户态,操作系统内核运行在内核态。
如何实现内核态和用户态的切换?
处理器从用户态切换到内核态的方法有三种:系统调用、异常和外部中断。
操作系统上下文切换怎么进行的
上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。上下文切换过程中的信息被保存在进程控制块(PCB-Process Control Block)中。PCB又被称作切换帧(SwitchFrame)。上下文切换的信息会一直被保存在CPU的内存中,直到被再次使用
每个进程维护了对应的进程控制块( PCB) 内核将相同状态的进程的PCB放置在同一队列
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 阿里最新开源QwQ-32B,效果媲美deepseek-r1满血版,部署成本又又又降低了!
· 单线程的Redis速度为什么快?
· SQL Server 2025 AI相关能力初探
· AI编程工具终极对决:字节Trae VS Cursor,谁才是开发者新宠?
· 展开说说关于C#中ORM框架的用法!