赞
踩
进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发;
线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的 实时性,实现进程内部的并发;
一个程序至少有一个进程,一个进程至少有一个线程,线程依赖于进程而存在;
进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。
1)管道
管道是半双工的,数据只能向一个方向流动;如果需要双方通信时,需要建立起两个管道。
管道只能用于父子进程或者兄弟进程之间或者说具有亲缘关系的进程;
管道对于管道两端的进程而言,就是一个文件,但它不是普通的文件,它不属于某种文件系统,只存在与内存中。
管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据,管道一端的进程顺序的将数据写入缓冲区,另一端的进程则顺序的读出数据。该缓冲区可以看做是一个循环队列,读和写的位置都是自动增长的,不能随意改变,一个数据只能被读一次,读出来以后在缓冲区就不复存在了。当缓冲区读空或者写满时,有一定的规则控制相应的读进程或者写进程进入等待队列,当空的缓冲区有新数据写入或者满的缓冲区有数据读出来时,就唤醒等待队列中的进程继续读写。
管道的主要局限性正体现在它的特点上,比如只支持单向数据流,只能用于具有亲缘关系的进程之间,没有名字,管道的缓冲区是有限的等等。
2)命名管道
这种管道也叫FIFO。命名管道不同于管道的地方,在于它提供了一个路径名与之关联,以命名管道的文件形式存在于文件系统中,这样,即使与命名管道的创建进程不存在亲缘关系的进程,只要可以访问文件系统中的这个路径,就能够彼此通过命名管道相互通信。命名管道严格遵循先进先出原则的,不支持诸如数据随机定位。命名管道的名字存在于文件系统中,但内容存放在内存中。
3)消息队列
消息队列是消息的链表,具有特定的格式,它是存放在内存里面的,并且每个消息队列都有唯一的标识。消息队列允许一个或多个进程向它写入与读取消息,所以,利用消息队列,一个进程可以将一个数据块发送到另一个进程,每个数据块都有一个类型,接收进程可以独立地接收含有不同类型的数据结构,这个过程是异步的,我们可以通过发送消息来避免命名管道的同步和阻塞问题。但消息队列的数据块有一个最大长度的大小限制。
4) 共享内存
共享内存是针对其他通信机制运行效率较低而设计的,它可以让多个进程可以可以直接读写同一块内存空间,是最快的IPC形式。
为了在多个进程间交换信息,内核专门留出了一块内存区,可以由需要访问的进程将其映射到自己的私有地址空间。进程就可以直接读写这一块内存而不需要进行数据的拷贝,从而大大提高效率。
由于多个进程共享一段内存,因此需要依靠某种同步机制来达到进程间的同步和互斥。
5)信号量
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它是一种类似于锁的机制,就是防止某进程正在访问共享资源时,其他进程也访问该资源。
6)Socket
Socket就是套接字,套接字也是一种通信机制,凭借这种机制,可以让不在同一台主机上的两个进程,通过网络进行通信,一般可以用在客户端和服务器之间的通信。
实际上,Socket 是在应用层和传输层之间的一个抽象层,它把 TCP/IP 协议的传输层里面复杂的操作,抽象为几个简单的接口,供应用层调用实现进程在网络中的通信。
延伸问题:Socket通信流程是怎样的?
概括地说,就是通信的两端都建立了一个 Socket ,然后通过 Socket 对数据进行传输。通常服务器处于一个无限循环,等待客户端的连接。
对于客户端,它的的过程比较简单,首先创建 Socket,通过TCP连接服务器,将 Socket 与远程主机的某个进程连接,然后就发送数据,或者读取响应数据,直到数据交换完毕,关闭连接,结束 TCP 对话。
对于服务端,先初始化 Socket,建立流式套接字,与本机地址及端口进行绑定,然后通知 TCP,准备好接收连接,调用 accept() 阻塞,等待来自客户端的连接。如果这时客户端与服务器建立了连接,客户端发送数据请求,服务器接收请求并处理请求,然后把响应数据发送给客户端,客户端读取数据,直到数据交换完毕。最后关闭连接,交互结束。
延伸问题:从TCP连接的角度说说Socket通信流程。
首先是三次握手的Socket交互流程。
1.服务器调用 socket()、bind()、listen() 完成初始化后,调用 accept() 阻塞等待;
2.客户端 Socket 对象调用 connect() 向服务器发送了一个 SYN 并阻塞;
3.服务器完成了第一次握手,即发送 SYN 和 ACK 应答;
4.客户端收到服务端发送的应答之后,从 connect() 返回,再发送一个 ACK 给服务器;
5.服务器 Socket 对象接收客户端第三次握手 ACK 确认,此时服务端从 accept() 返回,建立连接。
接下来就是两个端的连接对象互相收发数据。
然后是四次挥手的Socket交互流程。
1.某个应用进程调用 close() 主动关闭,发送一个 FIN;
2.另一端接收到 FIN 后被动执行关闭,并发送 ACK 确认;
3.之后被动执行关闭的应用进程调用 close() 关闭 Socket,并也发送一个 FIN;
4.接收到这个 FIN 的一端向另一端 ACK 确认。
互斥量 Synchronized/Lock:采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问
信号量 Semphare:它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作
1). 死锁的概念
在两个或者多个并发进程中,如果每个进程持有某种资源而又等待其它进程释放它或它们现在保持着的资源,在未改变这种状态之前都不能向前推进,称这一组进程产生了死锁。通俗的讲,就是两个或多个进程无限期的阻塞、相互等待的一种状态。
2). 死锁产生的四个必要条件
互斥:至少有一个资源必须属于非共享模式,即一次只能被一个进程使用;若其他申请使用该资源,那么申请进程必须等到该资源被释放为止;
占有并等待:一个进程必须占有至少一个资源,并等待另一个资源,而该资源为其他进程所占有;
非抢占:进程不能被抢占,即资源只能被进程在完成任务后自愿释放
循环等待:若干进程之间形成一种头尾相接的环形等待资源关系
3). 死锁的处理基本策略和常用方法
解决死锁的基本方法主要有 预防死锁、避免死锁、检测死锁、解除死锁 、鸵鸟策略 等。
(1). 死锁预防
死锁预防的基本思想是 只要确保死锁发生的四个必要条件中至少有一个不成立,就能预防死锁的发生,具体方法包括:
打破互斥条件:允许进程同时访问某些资源。但是,有些资源是不能被多个进程所共享的,这是由资源本身属性所决定的,因此,这种办法通常并无实用价值。
打破占有并等待条件:可以实行资源预先分配策略(进程在运行前一次性向系统申请它所需要的全部资源,若所需全部资源得不到满足,则不分配任何资源,此进程暂不运行;只有当系统能满足当前进程所需的全部资源时,才一次性将所申请资源全部分配给该线程)或者只允许进程在没有占用资源时才可以申请资源(一个进程可申请一些资源并使用它们,但是在当前进程申请更多资源之前,它必须全部释放当前所占有的资源)。但是这种策略也存在一些缺点:在很多情况下,无法预知一个进程执行前所需的全部资源,因为进程是动态执行的,不可预知的;同时,会降低资源利用率,导致降低了进程的并发性。
打破非抢占条件:允许进程强行从占有者哪里夺取某些资源。也就是说,但一个进程占有了一部分资源,在其申请新的资源且得不到满足时,它必须释放所有占有的资源以便让其它线程使用。这种预防死锁的方式实现起来困难,会降低系统性能。
打破循环等待条件:实行资源有序分配策略。对所有资源排序编号,所有进程对资源的请求必须严格按资源序号递增的顺序提出,即只有占用了小号资源才能申请大号资源,这样就不回产生环路,预防死锁的发生。
(2). 死锁避免的基本思想
死锁避免的基本思想是动态地检测资源分配状态,以确保循环等待条件不成立,从而确保系统处于安全状态。所谓安全状态是指:如果系统能按某个顺序为每个进程分配资源(不超过其最大值),那么系统状态是安全的,换句话说就是,如果存在一个安全序列,那么系统处于安全状态。资源分配图算法和银行家算法是两种经典的死锁避免的算法,其可以确保系统始终处于安全状态。其中,资源分配图算法应用场景为每种资源类型只有一个实例(申请边,分配边,需求边,不形成环才允许分配),而银行家算法应用于每种资源类型可以有多个实例的场景。
(3). 死锁解除
死锁解除的常用两种方法为进程终止和资源抢占。所谓进程终止是指简单地终止一个或多个进程以打破循环等待,包括两种方式:终止所有死锁进程和一次只终止一个进程直到取消死锁循环为止;所谓资源抢占是指从一个或多个死锁进程那里抢占一个或多个资源,此时必须考虑三个问题:
(I). 选择一个牺牲品
(II). 回滚:回滚到安全状态
(III). 饥饿(在代价因素中加上回滚次数,回滚的越多则越不可能继续被作为牺牲品,避免一个进程总是被回滚)
创建、就绪、运行、终止、阻塞
运行状态就是进程正在CPU上运行。在单处理机环境下,每一时刻最多只有一个进程处于运行状态。
就绪状态就是说进程已处于准备运行的状态,即进程获得了除CPU之外的一切所需资源,一旦得到CPU即可运行。
阻塞状态就是进程正在等待某一事件而暂停运行,比如等待某资源为可用或等待I/O完成。即使CPU空闲,该进程也不能运行。
运行态→阻塞态:往往是由于等待外设,等待主存等资源分配或等待人工干预而引起的。
阻塞态→就绪态:则是等待的条件已满足,只需分配到处理器后就能运行。
运行态→就绪态:不是由于自身原因,而是由外界原因使运行状态的进程让出处理器,这时候就变成就绪态。例如时间片用完,或有更高优先级的进程来抢占处理器等。
就绪态→运行态:系统按某种策略选中就绪队列中的一个进程占用处理器,此时就变成了运行态。
并发就是在一段时间内,多个任务都会被处理;但在某一时刻,只有一个任务在执行。单核处理器可以做到并发。比如有两个进程A和B,A运行一个时间片之后,切换到B,B运行一个时间片之后又切换到A。因为切换速度足够快,所以宏观上表现为在一段时间内能同时运行多个程序。
并行就是在同一时刻,有多个任务在执行。这个需要多核处理器才能完成,在微观上就能同时执行多条指令,不同的程序被放到不同的处理器上运行,这个是物理上的多个进程同时进行。
分页就是说,将磁盘或者硬盘分为大小固定的数据块,叫做页,然后内存也分为同样大小的块,叫做页框。当进程执行的时候,会将磁盘的页载入内存的某些页框中,并且正在执行的进程如果发生缺页中断也会发生这个过程。页和页框都是由两个部分组成的,一个是页号或者页框号,一个是偏移量。分页一般是有硬件来完成的,每个页都对应一个页框,它们的对应关系存放在一个叫做页表的数据结构中,页号作为这个页表的索引,页框号作为页表的值。操作系统负责维护这个页表。
分页和分段有什区别?
分页对程序员是透明的,但是分段需要程序员显式划分每个段。
分页的地址空间是一维地址空间,分段是二维的。
页的大小不可变,段的大小可以动态改变。
分页主要用于实现虚拟内存,从而获得更大的地址空间;分段主要是为了使程序和数据可以被划分为逻辑上独立的地址空间并且有助于共享和保护。
段式存储管理是一种符合用户视角的内存分配管理方案。在段式存储管理中,将程序的地址空间划分为若干段(segment),如代码段,数据段,堆栈段;这样每个进程有一个二维地址空间,相互独立,互不干扰。段式管理的优点是:没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)
页式存储管理方案是一种用户视角内存与物理内存相分离的内存分配管理方案。在页式存储管理中,将程序的逻辑地址划分为固定大小的页(page),而物理内存划分为同样大小的帧,程序加载时,可以将任意一页放入内存中任意一个帧,这些帧不必连续,从而实现了离散分离。页式存储管理的优点是:没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满)。
两者的不同点:
目的不同:分页是由于系统管理的需要而不是用户的需要,它是信息的物理单位;分段的目的是为了能更好地满足用户的需要,它是信息的逻辑单位,它含有一组其意义相对完整的信息;
大小不同:页的大小固定且由系统决定,而段的长度却不固定,由其所完成的功能决定;
地址空间不同: 段向用户提供二维地址空间;页向用户提供的是一维地址空间;
信息共享:段是信息的逻辑单位,便于存储保护和信息的共享,页的保护和共享受到限制;
内存碎片:页式存储管理的优点是没有外碎片(因为页的大小固定),但会产生内碎片(一个页可能填充不满);而段式管理的优点是没有内碎片(因为段大小可变,改变段大小来消除内碎片)。但段换入换出时,会产生外碎片(比如4k的段换5k的段,会产生1k的外碎片)。
FCFS(先来先服务,队列实现,非抢占的):先请求CPU的进程先分配到CPU
SJF(最短作业优先调度算法):平均等待时间最短,但难以知道下一个CPU区间长度
优先级调度算法(可以是抢占的,也可以是非抢占的):优先级越高越先分配到CPU,相同优先级先到先服务,存在的主要问题是:低优先级进程无穷等待CPU,会导致无穷阻塞或饥饿;解决方案:老化
时间片轮转调度算法(可抢占的):队列中没有进程被分配超过一个时间片的CPU时间,除非它是唯一可运行的进程。如果进程的CPU区间超过了一个时间片,那么该进程就被抢占并放回就绪队列。
多级队列调度算法:将就绪队列分成多个独立的队列,每个队列都有自己的调度算法,队列之间采用固定优先级抢占调度。其中,一个进程根据自身属性被永久地分配到一个队列中。
多级反馈队列调度算法:与多级队列调度算法相比,其允许进程在队列之间移动:若进程使用过多CPU时间,那么它会被转移到更低的优先级队列;在较低优先级队列等待时间过长的进程会被转移到更高优先级队列,以防止饥饿发生。
进程同步是指协调多个进程之间的执行顺序,以避免产生竞态条件或其他不确定的结果。以下是几种常见的进程同步机制:
互斥锁(Mutex): 互斥锁是一种最基本的同步机制,它确保在同一时刻只有一个进程可以访问共享资源。当一个进程获取了互斥锁,其他试图获取锁的进程将被阻塞。
信号量(Semaphore): 信号量是一种更为灵活的同步机制,它允许指定多个进程同时访问一个共享资源。信号量有一个计数器,当资源被占用时,计数器减一;当资源被释放时,计数器加一。进程在访问资源前需要等待信号量为正。
条件变量(Condition Variable): 条件变量用于在进程之间建立先行顺序。它通常与互斥锁一起使用。一个进程可以等待条件变量满足,而另一个进程可以通过改变条件变量的状态来通知等待的进程。
屏障(Barrier): 屏障用于同步多个进程,使它们在程序的不同点上汇合。当所有参与的进程都到达屏障点时,它们将一起继续执行。
读写锁(Read-Write Lock): 读写锁允许多个进程同时读取共享资源,但只有一个进程能够写入。这样可以提高读取性能,但限制了写入的并发性。
事件(Event): 事件是一种通信机制,用于通知一个或多个进程发生了某个事件。进程可以等待事件的发生,或者触发事件。
消息传递: 进程通过消息传递进行通信,可以通过消息的发送和接收来同步进程的执行。这种机制通常涉及到阻塞和非阻塞的消息传递方式。
虚拟内存是计算机操作系统中的一种技术,它通过将部分存储器(通常是硬盘空间)虚拟化,使得程序能够访问比物理内存更大的地址空间。这个虚拟的地址空间被称为虚拟内存,而物理内存则是真正存在于计算机硬件中的内存。
以下是虚拟内存的一些关键概念和工作原理:
虚拟地址空间: 每个运行的程序都有自己的虚拟地址空间,这是一个连续的、从零开始的地址范围。这个地址空间通常被划分为多个部分,包括代码段、数据段、堆、栈等。
物理内存: 物理内存是计算机实际存在的内存,是RAM(随机存储器)的一部分。操作系统负责将虚拟地址空间映射到物理内存上。
页表: 为了实现虚拟内存,操作系统维护了一个页表,用于将程序的虚拟地址映射到物理内存的实际地址。当程序访问虚拟内存中的某个地址时,操作系统通过页表查找相应的物理地址。
页面: 虚拟内存和物理内存都被划分为固定大小的块,称为页面。当程序访问虚拟内存时,可能会发生缺页中断,需要将虚拟内存中的某个页面加载到物理内存。
页面置换: 如果物理内存不足,操作系统需要选择一个页面进行置换。通常采用的策略有最近最少使用(LRU)等,选择最久未被访问的页面进行替换。
交换空间: 当物理内存不足以容纳所有活跃的页面时,操作系统可以使用硬盘上的一部分空间作为交换空间,用于存放不常用的页面。
内存保护: 虚拟内存允许操作系统实现内存保护机制,防止程序访问未分配的内存或越界访问。当程序访问非法内存时,操作系统会产生一个异常。
内存共享: 虚拟内存支持不同程序之间的内存共享,这对于进程间通信和动态链接等方面非常有用。
总体而言,虚拟内存为程序提供了一个比实际物理内存更大的地址空间,使得操作系统能够更灵活地管理内存,并为多任务操作系统提供了更好的性能和资源隔离。虚拟内存的引入使得程序员能够开发更大、更复杂的程序,而无需过分担心物理内存的限制
在操作系统中,“颠簸”(Thrashing)是指系统过度使用虚拟内存导致频繁的页面置换,而大部分时间都在进行页面置换而非执行实际的计算工作,从而导致系统性能急剧下降的现象。颠簸通常发生在系统中同时运行了过多的进程,而物理内存不足以容纳这些进程所需的页面的情况下。
以下是导致颠簸的主要原因和相关概念:
页面置换: 当系统中运行的进程需要访问的页面不在物理内存中时,就会发生页面置换。操作系统会将不再需要的页面置换到磁盘上,然后将需要的页面调入物理内存。这个过程是常见的,但如果置换过于频繁,系统性能就会受到严重影响。
工作集: 每个进程都有一个工作集,表示其当前运行所需的页面集合。操作系统通过监控每个进程的工作集来进行页面置换。
局部性原理: 颠簸的发生通常与局部性原理相违背。局部性原理包括时间局部性和空间局部性,它们指出程序在一段时间内很可能访问相邻的地址或者刚刚访问过的地址。颠簸时,系统频繁地将页面置换到磁盘上,破坏了局部性原理。
过多的进程: 当系统中运行了过多的进程,而物理内存无法满足它们的需求时,系统就会频繁进行页面置换,造成颠簸。
颠簸会导致系统性能极差,因为大部分时间都花费在磁盘和内存之间的页面置换上,而非实际的计算工作。为了避免颠簸,系统管理员可以考虑以下几个策略:
增加物理内存: 提供更多的物理内存可以减少对虚拟内存的过度依赖,降低颠簸的风险。
优化进程调度: 使用合适的进程调度算法,确保运行在内存中的进程能够更充分地利用物理内存。
调整页面置换算法: 选择合适的页面置换算法,以最小化对性能的影响。
减少运行的进程数: 限制系统中同时运行的进程数量,以降低对物理内存的竞争。
综上所述,颠簸是一个重要的性能问题,需要系统管理员综合考虑物理内存、进程调度、页面置换等因素来进行优化。
操作系统的局部性原理是指在程序执行时,对数据和指令的访问存在一定的局部性规律,即程序在某个时间段内更有可能访问到已经访问过的数据或指令。这个原理主要包括两种局部性:
时间局部性(Temporal Locality): 时间局部性指的是在一段时间内刚刚访问过的存储位置很可能在短时间内再次被访问。这表现为程序往往在短时间内集中访问某个地址,而不是随机地访问内存中的地址。
空间局部性(Spatial Locality): 空间局部性指的是在一段时间内访问的存储位置的附近位置也很可能在短时间内被访问。这表现为程序往往按照一定的步长或者连续地访问内存中的地址,而不是跳跃式地访问。
这两种局部性原理对计算机系统的性能有着重要的影响,尤其是对于缓存系统和虚拟内存的设计:
缓存系统: 缓存是一种速度较快但容量较小的存储设备,用于暂时存放常被访问的数据。由于时间局部性和空间局部性,缓存能够有效地提高程序的性能。常用的缓存替换算法如LRU(Least Recently Used,最近最少使用)也是基于时间局部性的思想。
虚拟内存: 虚拟内存的分页和分段机制也是基于局部性原理的。程序在执行时,只有部分页面或段被载入到物理内存中,而不是整个程序全部载入。这样,如果程序具有良好的局部性,只有部分页面或段需要被载入,减少了物理内存的需求,提高了内存的利用率。
指令预取: 现代处理器在执行指令时也会利用空间局部性原理,通过指令预取(Instruction Prefetching)提前将可能用到的指令加载到指令缓存中,以提高指令的获取速度。
总体而言,局部性原理在计算机系统的设计和优化中起到了重要的作用,有助于提高程序的执行效率和系统的整体性能。
大内核,就是将操作系统的全部功能都放进内核里面,包括调度、文件系统、网络、设备驱动器、存储管理等等,组成一个紧密连接整体。大内核的优点就是效率高,但是很难定位bug,拓展性比较差,每次需要增加新的功能,都要将新的代码和原来的内核代码重新编译。
微内核与单体内核不同,微内核只是将操作中最核心的功能加入内核,包括IPC、地址空间分配和基本的调度,这些东西都在内核态运行,其他功能作为模块被内核调用,并且是在用户空间运行。微内核比较好维护和拓展,但是效率可能不高,因为需要频繁地在内核态和用户态之间切换。
分时系统(Sharing time system)就是系统把CPU时间分成很短的时间片,轮流地分配给多个作业。它的优点就是对多个用户的多个作业都能保证足够快的响应时间,并且有效提高了资源的利用率。
实时系统(Real-time system)是系统对外部输入的信息,能够在规定的时间内(截止期限)处理完毕并做出反应。它的优点是能够集中地及时地处理并作出反应,高可靠性,安全性。
通常计算机采用的是分时,就是多个进程/用户之间共享CPU,从形势上实现多任务。各个用户/进程之间的调度并非精准度特别高,如果一个进程被锁住,可以给它分配更多的时间。而实时操作系统则不同,软件和硬件必须遵从严格的时间限制,超过时限的进程可能直接被终止。在这样的操作系统中,每次加锁都需要仔细考虑。
静态链接就是在编译期间,由编译器和连接器将静态库集成到应用程序内,并制作成目标文件以及可以独立运作的可执行文件。静态库一般是一些外部函数与变量的集合。
静态库很方便,但是如果我们只是想用库中的某一个函数,却仍然得把所有的内容都链接进去。一个更现代的方法是使用共享库,避免了在文件中静态库的大量重复。
动态链接可以在首次载入的时候执行,也可以在程序开始执行的时候完成。这个是由动态链接器完成,比方标准 C 库(libc.so) 通常就是动态链接的,这样所有的程序可以共享同一个库,而不用分别进行封装。
预处理阶段:处理以 # 开头的预处理命令;
编译阶段:翻译成汇编文件;
汇编阶段:将汇编文件翻译成可重定位目标文件;
链接阶段:将可重定位目标文件和 printf.o 等单独预编译好的目标文件进行合并,得到最终的可执行目标文件。
非抢占式的调度算法,按照请求的顺序进行调度。
有利于长作业,但不利于短作业,因为短作业必须一直等待前面的长作业执行完毕才能执行,而长作业又需要执行很长时间,造成了短作业等待时间过长。另外,对I/O密集型进程也不利,因为这种进程每次进行I/O操作之后又得重新排队。
非抢占式的调度算法,按估计运行时间最短的顺序进行调度。
长作业有可能会饿死,处于一直等待短作业执行完毕的状态。因为如果一直有短作业到来,那么长作业永远得不到调度。
最短作业优先的抢占式版本,按剩余运行时间的顺序进行调度。 当一个新的作业到达时,其整个运行时间与当前进程的剩余时间作比较。如果新的进程需要的时间更少,则挂起当前进程,运行新的进程。否则新的进程等待。
将所有就绪进程按 FCFS 的原则排成一个队列,每次调度时,把 CPU 时间分配给队首进程,该进程可以执行一个时间片。当时间片用完时,由计时器发出时钟中断,调度程序便停止该进程的执行,并将它送往就绪队列的末尾,同时继续把 CPU 时间分配给队首的进程。
时间片轮转算法的效率和时间片的大小有很大关系:
因为进程切换都要保存进程的信息并且载入新进程的信息,如果时间片太小,会导致进程切换得太频繁,在进程切换上就会花过多时间。
而如果时间片过长,那么实时性就不能得到保证。
为每个进程分配一个优先级,按优先级进行调度。
为了防止低优先级的进程永远等不到调度,可以随着时间的推移增加等待进程的优先级。
延伸问题:什么是抢占式调度?什么是非抢占式调度?
抢占式就是说操作系统将正在运行的进程强行暂停,由调度器将CPU分配给其他就绪进程。
非抢占式是调度器一旦把处理机分配给某进程后便让它一直运行下去,直到进程完成或发生进程调度进程调度某事件而阻塞时,才把处理机分配给另一个进程。
对于单核单线程CPU而言,在某一时刻只能执行一条CPU指令。上下文切换(Context Switch)是一种将CPU资源从一个进程分配给另一个进程的机制。从用户角度看,计算机能够并行运行多个进程,这恰恰是操作系统通过快速上下文切换造成的结果。在切换的过程中,操作系统需要先存储当前进程的状态(包括内存空间的指针,当前执行完的指令等等),再读入下一个进程的状态,然后执行此进程。
上下文切换是操作系统进行多任务处理时的一项重要操作,它涉及将 CPU 从一个任务切换到另一个任务。这个过程包括保存当前任务的上下文(Context)信息,加载下一个任务的上下文信息,以确保任务之间的平滑切换。下面详细解释操作系统的上下文切换:
上下文(Context): 上下文是指一个任务在执行时的状态和相关数据,包括寄存器的内容、程序计数器的值、内存映像、打开文件等。上下文切换就是在不丢失任务状态的前提下,将一个任务的上下文保存起来,然后加载另一个任务的上下文。
触发条件: 上下文切换可以由多种原因触发,主要包括:
时间片耗尽: 操作系统采用时间片轮转调度算法,一个任务的时间片用完后会触发上下文切换。
任务等待: 当一个任务等待某些事件(如 I/O 操作完成)时,操作系统会切换到另一个任务。
中断: 外部中断(硬件中断)或软中断(系统调用)也会导致上下文切换。
上下文切换流程: 上下文切换的基本流程如下:
保存当前任务的上下文: 将当前任务的寄存器状态、程序计数器等关键信息保存到任务的内存空间或内核数据结构中。
选择下一个任务: 从就绪队列中选择下一个要执行的任务。
加载下一个任务的上下文: 将下一个任务的上下文从内存或内核数据结构加载到寄存器、程序计数器等硬件环境中。
跳转到下一个任务: 将控制权转移到下一个任务,使其开始执行。
代价与开销: 上下文切换的操作涉及大量的寄存器状态的保存和加载,这是一个相对昂贵的操作。因此,减少上下文切换的次数有助于提高系统性能。
多任务调度: 上下文切换是实现多任务调度的关键步骤。通过合理的调度算法和高效的上下文切换机制,操作系统可以在多个任务之间实现快速而有效的切换,实现任务的并发执行。
内核态和用户态切换: 上下文切换通常涉及从用户态切换到内核态,因为保存和加载整个上下文需要特权指令,而这些指令只能在内核态执行。
总体而言,上下文切换是操作系统实现多任务的基础,对系统性能和响应时间有重要影响。因此,在设计操作系统时,需要考虑合适的调度策略和优化上下文切换的开销。
系统调用是应用程序向系统内核请求服务的方式。可以包括硬件相关的服务(例如,访问硬盘等),或者创建新进程,调度其他进程等。系统调用是程序和操作系统之间的重要接口。
库函数就是说把一些常用的函数编写完放到一个文件里,编写应用程序时调用,这是由第三方提供的,发生在用户地址空间。
在移植性方面,不同操作系统的系统调用一般是不同的,移植性差;库函数会相对好一些。比如说在所有的ANSI C编译器版本中,C库函数是相同的。
在调用开销方面,系统调用需要在用户空间和内核环境间切换,开销较大;而库函数调用开销较小。
按照磁盘请求的顺序进行调度。
优点是公平和简单。缺点也很明显,因为未对寻道做任何优化,使平均寻道时间可能较长。
优先调度与当前磁头所在磁道距离最近的磁道。
虽然平均寻道时间比较低,但是不够公平。如果新到达的磁道请求总是比一个在等待的磁道请求近,那么在等待的磁道请求会一直等待下去,也就是出现饥饿现象。一般来说,两端的磁道请求更容易出现饥饿现象。
也叫SCAN扫描算法。电梯算法就是说读写磁头总是保持一个方向运行,直到该方向没有请求为止,然后改变运行方向。
因为考虑了移动方向,因此所有的磁盘请求都会被满足,解决了最短寻道时间优先的饥饿问题。
在程序运行过程中,如果要访问的页面不在内存中,就发生缺页中断从而将该页调入内存中。此时如果内存已无空闲空间,系统必须从内存中调出一个页面到磁盘对换区中来腾出空间。
所选择的被换出的页面将是最长时间内不再被访问,通常可以保证获得最低的缺页率。这是一种理论上的算法,因为无法知道一个页面多长时间不再被访问。
先进先出
选择换出的页面是最先进入的页面。该算***将那些经常被访问的页面也被换出,从而使缺页率升高。
虽然无法知道将来要使用的页面情况,但是可以知道过去使用页面的情况。LRU 将最近最久未使用的页面换出。为了实现 LRU,需要在内存中维护一个所有页面的链表。当一个页面被访问时,将这个页面移到链表表头。这样就能保证链表表尾的页面是最近最久未访问的。因为每次访问都需要更新链表,因此这种方式实现的 LRU 代价很高。
时钟算法使用环形链表将页面连接起来,再使用一个指针指向最老的页面。它将整个环形链表的每一个页面做一个标记,如果标记是0,那么暂时就不会被替换,然后时钟算法遍历整个环,遇到标记为1的就替换,否则将标记为0的标记为1。
Linux文件系统里面有文件和目录,组成一个树状的结构,树的每一个叶子节点表示文件或者空目录。每个文件基本上都由两部分组成:
inode:一个文件占用一个 inode,记录文件的属性,同时记录此文件的内容所在的 block 编号;
block:记录文件的内容,文件太大时,会占用多个 block。
除此之外还包括:
superblock:记录文件系统的整体信息,包括 inode 和 block 的总量、使用量、剩余量,以及文件系统的格式与相关信息等;
block bitmap:记录 block 是否被使用的位图。
当要读取一个文件的内容时,先在 inode 中查找文件内容所在的所有 block,然后把所有 block 的内容读出来。
硬链接就是在目录下创建一个条目,记录着文件名与 inode 编号,这个 inode 就是源文件的 inode。删除任意一个条目,文件还是存在,只要引用数量不为 0。但是硬链接有限制,它不能跨越文件系统,也不能对目录进行链接。
符号链接文件保存着源文件所在的绝对路径,在读取时会定位到源文件上,可以理解为 Windows 的快捷方式。当源文件被删除了,链接文件就打不开了。因为记录的是路径,所以可以为目录建立符号链接。
Windows 提供了 3 种方法来进行内存管理:虚拟内存,最适合用来管理大型对象或者 结构数组;内存映射文件,最适合用来管理大型数据流(通常来自文件)以及在单个计算机 上运行多个进程之间共享数据;内存堆栈,最适合用来管理大量的小对象。
Window 操纵内存可以分两个层面:物理内存和虚拟内存。
其中物理内存由系统管理,不允许应用程序直接访问,应用程序可见的只有一个 2G 地 址空间,而内存分配是通过堆进行的,对于每个进程都有自己的默认堆,当一个堆创建后, 就通过虚拟内存操作保留了相应大小的地址块(不占有实际的内存,系统消耗很小),当在 堆上分配一块内存时,系统在堆的地址表里找到一个空闲块(如果找不到,且堆创建属性是 可扩充的,则扩充堆大小)为这个空闲块所包含的所有内存页提交物理对象(物理内存上或 硬盘上的交换文件上)。这时可以就访问这部分地址了。提交时,系统将对所有进程的内存 统一调配,如果物理内存不够,系统试图把一部分进程暂时不访问的页放入交换文件,以腾 出部分物理内存。释放内存时,只在堆中将所在的页解除提交(相应的物理对象被解除), 继续保留地址空间。
如果要知道某个地址是否被占用/可不可以访问,只要查询此地址的虚拟内存状 VirtualQuery),如果是提交,则可以访问。如果仅仅保留,或没保留,则产生一个软件异常。 此外有些内存页可以设置各种属性。如果是只读,向内写也会产生软件异常。
A. 指令队列;B.指令堆栈;C.消息队列;D.消息堆栈
答案:C
处理消息队列的顺序。首先 windows 绝对不是按队列先进先出的次序来处理的,而是有 一定优先级的。优先级通过消息队列的状态标志来实现的。首先最高优先级的是别的线程发 过来的消息(通过 sendmessage),其次是处理登记消息队列消息,再次处理 QS_QUIT 标志,
再处理虚拟输入队列,再处理 wm_paint 最后是 wm_timer
在特定时间内完成特定的任务,实时性与可靠性。
所谓“实时操作系统”,实际上是指操作系统工作时,其各种资源可以根据需要随时进 行动态分配。由于各种资源可以进行动态分配,因此其处理事务的能力较强、速度较快。
对 I/O 设备的程序轮询的方式,是早期的计算机系统对 I/O 设备的一种管理方式。它定 时对各种设备轮流询问一遍有无处理要求。轮流询问之后,有要求的,则加以处理。在处理 I/O 设备的要求之后,处理机返回继续工作。尽管轮询需要时间,但轮询要比 I/O 设备的速度要快得多,所以一般不会发生不能及时处理的问题。当然,再快的处理机,能处理的输入 输出设备的数量也是有一定限度的。而且,程序轮询毕竟占据了 CPU 相当一部分处理时间, 因此程序轮询是一种效率较低的方式,在现代计算机系统中已很少应用。
程序中断通常简称中断,是指 CPU 在正常运行程序的过程中,由于预选安排或发生了 各种随机的内部或外部事件,使 CPU 中断正在运行的程序,而转到为响应的服务程序去处 理。
轮询——效率低,等待时间很长,CPU 利用率不高
中断——容易遗漏一些问题,CPU 利用率高
每个进程中访问临界资源的那段程序称为临界区,每次只准许一个进程进入临界区,进 入后不允许其他进程进入。
① 如果有若干进程要求进入空闲的临界区,一次仅允许一个进程进入。
② 任何时候,处于临界区内的进程不可多于一个。如已有进程进入自己的临界区,则 其它所有试图进入临界区的进程必须等待。
③ 进入临界区的进程要在有限时间内退出,以便其它进程能及时进入自己的临界区。
④ 如果进程不能进入自己的临界区,则应让出 CPU,避免进程出现“忙等”现象。
操作系统通过系统调用(System Call)的方式访问硬件和执行底层操作。系统调用是操作系统提供给应用程序的接口,允许应用程序请求操作系统提供的服务,包括对硬件的访问。下面是操作系统访问软硬件的方式:
系统调用: 应用程序通过系统调用来请求操作系统提供的服务。系统调用是一种特殊的过程调用,应用程序通过在代码中插入系统调用的指令来触发操作系统执行相应的操作。例如,读写文件、分配内存、创建进程等操作都是通过系统调用完成的。
中断: 中断是一种异步的事件机制,它允许硬件或其他软件组件打断当前正在执行的程序,转而执行操作系统内核中相应的中断处理程序。硬件中断(如时钟中断、I/O 中断)和软中断(由软件触发的中断,通常用于系统调用)都是操作系统访问硬件的方式。
异常: 异常是一种与中断类似的事件,但通常是由程序执行过程中产生的错误或异常条件引发的。例如,除零错误、页面错误等都可以引发异常。操作系统可以通过异常处理程序来响应和处理这些异常情况。
I/O 指令: 操作系统可以通过直接执行输入/输出(I/O)指令来与硬件进行通信。这种方式通常用于底层设备驱动程序的开发,涉及对硬件寄存器的直接访问。
总体而言,操作系统通过系统调用、中断、异常等机制与硬件进行交互。这种交互使得应用程序能够访问硬件资源,同时操作系统负责管理和保护这些资源,提供一个抽象层,使得应用程序能够在相对简单的接口上开发。这种分层的结构有助于提高系统的可维护性、可移植性和安全性
一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile 定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重 新编译,甚至于进行更复杂的功能操作,因为 makefile 就像一个 Shell 脚本一样,其中也 可以执行操作系统的命令。makefile 带来的好处就是——“自动化编译”,一旦写好,只需 要一个 make 命令,整个工程完全自动编译,极大的提高了软件开发的效率。make 是一个 命令工具,是一个解释 makefile 中指令的命令工具,一般来说,大多数的 IDE 都有这个命 令,比如:Delphi 的 make,Visual C++的 nmake,Linux 下 GNU 的 make。可见,makefile 都成为了一种在工程方面的编译方法。
加电后,会触发CPU的reset信号,导致CPU复位,然后CPU会跳到(arm下0x00000000,x86 下 0xfffffff0)执行指令。主要是做 CPU 初始化,确定 CPU 的工作模式,mmu 初始化。建立页 表段表,初始化中孤单控制器和中断向量表,初始化输入和输出,初始化 nandflash,把 OS 的 TEXT 区加载到 sdram,然后跳转到 sdram 的 main()
中断是指在计算机执行期间,系统内发生任何非寻常的或非预期的急需处理事件,使得 CPU 暂时中断当前正在执行的程序而转去执行相应的事件处理程序。待处理完毕后又返回原 来被中断处继续执行或调度新的进程执行的过程。
存储过程(Stored Procedure)是一组为了完成特定功能的 SQL 语句集,经编译后存 储在数据库中。用户通过指定存储过程的名字并给出参数(如果该存储过程带有参数)来执 行它。存储过程是 SQL 语句和可选控制流语句的预编译集合,以一个名称存储并作为一个 单元处理。存储过程存储在数据库内,可由应用程序通过一个调用执行,而且允许用户声明 变量、有条件执行以及其它强大的编程功能。存储过程在创建时即在服务器上进行编译,所 以执行起来比单个 SQL 语句快。
存储过程的优点:
(1)存储过程只在创造时进行编译,以后每次执行存储过程都不需 再重新编译,而一般 SQL 语句每执行一次就编译一次,所以使用存储过程可提高数据库执行 速度;
(2)当对数据库进行复杂操作时(如对多个表进行 Update, Insert, Query, Delete 时), 可将此复杂操作用存储过程封装起来与数据库提供的事务处理结合一起使用;
(3)存储过 程可以重复使用,可减少数据库开发人员的工作量;
(4)安全性高,可设定只有某此用户才具有对指定存储过程的使用权。
存储过程的缺点:
(1)如果更改范围大到需要对输入存储过程的参数进行更改,或者 要更改由其返回的数据,则您仍需要更新程序集中的代码以添加参数、更新 GetValue() 调 用,等等,这时候估计比较繁琐了。
(2)可移植性差。由于存储过程将应用程序绑定到 SQL Server,因此使用存储过程封装业务逻辑将限制应用程序的可移植性。
硬中断(Hardware Interrupt)和软中断(Software Interrupt)是计算机系统中用于处理异步事件的两种不同机制。它们都是中断机制的不同类型,用于在 CPU 执行任务的过程中处理外部事件。
硬中断(Hardware Interrupt):
定义: 硬中断是由计算机硬件生成的中断信号,通常是与外部设备(如键盘、鼠标、磁盘控制器等)交互时产生的。硬中断是由硬件设备发出的异步信号,要求 CPU 立即切换到中断处理程序执行相应的操作。
来源: 硬中断源头包括外部设备、时钟等硬件模块,它们可以向 CPU 发送中断请求。
处理: 当硬中断发生时,CPU 会立即中断当前执行的任务,保存当前任务的上下文,转而执行与中断相关的中断服务程序(Interrupt Service Routine,ISR)。硬中断处理完成后,CPU 恢复先前被中断的任务。
例子: 外部设备产生的中断,如磁盘驱动器完成了数据读取,会发出硬中断请求,引发中断处理。
软中断(Software Interrupt):
定义: 软中断是由软件生成的中断信号,是通过执行一条特殊的中断指令而产生的。软中断是由程序或操作系统内核主动触发的中断,而不是由外部硬件设备引起的。
来源: 软中断通常由操作系统或应用程序内的某些特殊事件或系统调用触发,例如系统调用、软中断指令等。
处理: 软中断的处理方式类似硬中断,CPU 在执行当前任务时,可以通过触发软中断立即切换到相应的中断服务程序。软中断处理完成后,CPU 恢复原任务的执行。
例子: 系统调用是一种常见的软中断形式,应用程序可以通过系统调用请求操作系统提供某种服务,例如文件 I/O、内存分配等。
总结:
异步性质: 硬中断是由硬件异步触发的,而软中断是由软件主动触发的。
触发方式: 硬中断是由外部事件触发的,而软中断是通过特殊指令或软件事件触发的。
处理方式: 在处理方式上,硬中断和软中断的机制是相似的,都涉及保存和恢复上下文,并执行中断服务程序。
硬中断和软中断在操作系统中的设计和实现中都起到了重要作用,使得计算机系统能够响应外部事件、进行多任务处理,并提供系统调用等功能。
在操作系统中,物理地址到逻辑地址的转换通常是通过页表来实现的。这个过程涉及到虚拟内存管理。下面是一个简单的步骤说明:
虚拟地址(逻辑地址): 进程中运行的程序使用虚拟地址,这是一个由进程生成的地址,它不直接对应物理内存中的位置。
页表: 操作系统维护了一个称为页表的数据结构,其中包含了虚拟地址到物理地址的映射。这个表通常是一个二级或多级的结构,以便有效地管理大量的地址映射。
页表项: 页表中的每个条目被称为页表项(Page Table Entry,PTE),其中包含虚拟页号和对应的物理页框号。
页(Page): 内存被划分为固定大小的块,称为页。通常,一个页的大小是2的幂次方,如4KB或8KB。
地址翻译过程: 当程序使用虚拟地址时,操作系统通过查找页表找到对应的页表项。该页表项提供了虚拟页号到物理页框号的映射。通过将虚拟页号的偏移加到物理页框号的基地址上,就得到了物理地址。
TLB(Translation Lookaside Buffer): 为了提高地址转换的速度,现代处理器通常使用 TLB,它是一个高速缓存,存储了最近使用的一些页表项。如果虚拟地址的映射在 TLB 中找到,就可以避免访问主存中的页表。
这个过程的主要目的是实现虚拟内存,允许程序使用比物理内存更大的地址空间。它还提供了一些其他的好处,如内存保护、共享内存和内存的动态分配。
总体而言,物理地址到逻辑地址的转换是通过页表实现的,这是操作系统虚拟内存管理的关键组成部分。不同的操作系统和硬件架构可能会有不同的实现细节,但基本思想是相似的。
堆和栈是计算机内存中两种不同的分配方式,它们用于存储程序运行时的不同类型的数据。
分配方式:
堆: 在堆上分配内存是动态的,需要程序员手动申请并负责释放。堆上的内存由程序员分配和释放,不会在程序的作用域结束时自动释放。
栈: 栈上的内存是自动分配的,由编译器在程序运行时自动管理。栈上的内存在定义变量时分配,在变量的作用域结束时自动释放。
空间大小:
堆: 堆的空间通常较大,取决于系统的虚拟内存大小。它提供了较大的灵活性,但也需要手动管理。
栈: 栈的空间相对较小,通常受限于系统的栈大小。栈的大小在程序运行时就被确定,无法动态改变。
分配速度:
堆: 堆上的内存分配比较灵活,但由于需要手动管理,可能会导致内存泄漏或者产生不必要的垃圾。堆上的分配和释放速度可能较慢。
栈: 栈上的内存分配速度较快,因为它只涉及到指针的移动,不需要进行复杂的内存管理操作。但栈上的内存只在局部变量的作用域内有效。
数据类型:
堆: 通常用于存储动态分配的数据,如对象、数组等。
栈: 主要用于存储局部变量、函数调用和控制流信息。
生命周期:
堆: 数据在堆上分配后,会一直存在,直到程序员显式释放。因此,堆上的数据的生命周期可以是动态的,与程序的整个运行周期相关。
栈: 栈上的数据的生命周期受限于函数的调用和返回,局部变量的作用域结束时就会被自动释放。
在实际编程中,栈和堆各有其适用的场景。栈适合于存储局部变量和函数调用信息,而堆适合于存储动态分配的数据。程序员需要根据具体的需求和数据的生命周期来选择合适的内存分配方式。
每个线程有自己的堆栈。
DLL 中有没有独立的堆栈,这个问题不好回答,或者说这个问题本身是否有问题。因为 DLL 中的代码是被某些线程所执行,只有线程拥有堆栈,如果 DLL 中的代码是 EXE 中的线程 所调用,那么这个时候是不是说这个 DLL 没有自己独立的堆栈?如果 DLL 中的代码是由 DLL
自己创建的线程所执行,那么是不是说 DLL 有独立的堆栈?
以上讲的是堆栈,如果对于堆来说,每个 DLL 有自己的堆,所以如果是从 DLL 中动态分 配的内存,最好是从 DLL 中删除,如果你从 DLL中分配内存,然后在 EXE 中,或者另外一个 DLL 中删除,很有可能导致程序崩溃。
线程:相对与进程而言,线程是一个更加接近与执行体的概念,它可以与同进程的其他
线程共享数据,但拥有自己的栈空间,拥有独立的执行序列。
两者都可以提高程序的并发度,提高程序运行效率和响应时间。
线程和进程在使用上各有优缺点:线程执行开销小,但不利于资源管理和保护;而进
程正相反。同时,线程适合于在 SMP 机器上运行,而进程则可以跨机器迁移。
为了避免操作系统和关键数据被用户程序破坏,将处理器的执行状态分为内核态和用户态。
内核态是操作系统管理程序执行时所处的状态,能够执行包含特权指令在内的一切指令,能够访问系统内所有的存储空间。
用户态是用户程序执行时处理器所处的状态,不能执行特权指令,只能访问用户地址空间。
用户程序运行在用户态,操作系统内核运行在内核态。
如何实现内核态和用户态的切换?
处理器从用户态切换到内核态的方法有三种:系统调用、异常和外部中断。
系统调用是操作系统的最小功能单位,是操作系统提供的用户接口,系统调用本身是一种软中断。
异常,也叫做内中断,是由错误引起的,如文件损坏、缺页故障等。
外部中断,是通过两根信号线来通知处理器外设的状态变化,是硬中断。
缓冲区溢出是指当计算机向缓冲区内填充数据位数时超过了缓冲区本身的容量溢的数
据覆盖在合法数据上,
危害:在当前网络与分布式系统安全中,被广泛利用的 50%以上都是缓冲区溢出,其中
最著名的例子是 1988 年利用 fingerd 漏洞的蠕虫。而缓冲区溢出中,最为危险的是堆栈溢出, 因为入侵者可以利用堆栈溢出,在函数返回时改变返回程序的地址,让其跳转到任意地址, 带来的危害一种是程序崩溃导致拒绝服务,另外一种就是跳转并且执行一段恶意代码,比如 得到 shell,然后为所欲为。通过往程序的缓冲区写超出其长度的内容,造成缓冲区的溢出, 从而破坏程序的堆栈,使程序转而执行其它指令,以达到攻击的目的。
造成缓冲区溢出的原因是程序中没有仔细检查用户输入的参数
Copyright © 2003-2013 www.wpsshop.cn 版权所有,并保留所有权利。