7天快速学习计算机基础必考八股文day02:操作系统
day02操作系统目录一览图
- 请简述对进程的理解——操作系统的进程详解
- 请简述同步与异步的区别——进程状态模型详解
- 请简述进程和线程的区别——操作系统线程详解
- 请简述什么是操作系统的内核态——用户态与内核态详解
- IO密集型任务部署需要注意什么——程序运行类型分析
- 协程是什么?为什么需要协程——协程基础详解
- 请简述系统设计中缓存的作用——存储器的层次结构详解
- 请简述操作系统的虚拟内存——虚拟内存详解
- 请简述操作系统中的缺页中断——操作系统内存管理详解
- 请编程实现LRU算法——页面置换算法详解
- 软链接与硬链接有什么区别——Linux文件系统详解
- 请简述服务器部署的RAID——磁盘冗余阵列详解
- 请谈谈对死锁必要条件的理解——死锁的基本理论
- 线程-进程同步问题三大经典案例详解
- 请简述乐观锁、悲观锁、可重入锁——锁的分类详解
- 请简述互斥锁与自旋锁的区别——线程间通信详解
- 进程间有哪些同步的方式——进程间通信详解
- 请简述无锁数据结构的原理——CAS原理与无锁技术详解
- 请简述项目中分布式锁的实现方式——分布式锁实现详解
事情的起因是偶然看见了这个: 7天快速学习计算机基础必考八股文于是打算根据这个目录对相关知识的复习一波,本篇文章仅为个人学习研究,部分内容来自网络收集以及个人见解整理,如有不对,欢迎评论指出不胜感激,会尽快修改。本文目录以
问题--知识点
为标题
请简述对进程的理解——操作系统的进程详解
存储在硬盘中的代码文件通过编译后生成的二进制可执行文件运行时被装载到内存中由CPU执行程序中的指令,这个运行中的程序被称为**「进程」(Process)**。
请简述同步与异步的区别——进程状态模型详解
同步和异步主要是描述进程间通信关系的两种模式。
同步指一个进程完成其工作前需要等待另一个进程的消息或者信号,异步则恰恰相反,一个进程不需要等待另一个进程的消息或信号,它们可以独立执行。
阻塞和非阻塞是描述进程在等待消息通知时的状态。
阻塞是指进程在等待某个事件(如I/O操作)完成期间,不能执行其他操作,而非阻塞则是即使事件尚未完成,进程也可以执行其他操作。
同步和异步关注的是消息通信机制即发送方和接收方如何处理消息的传递,强调两个程序的运行彼此有逻辑、时间上的先后关系;
阻塞和非阻塞关注的是等待某个事件(如I/O操作)完成期间的行为状态,关注的是接口调用(发出请求)后等待数据返回时的状态;
同步和异步以及阻塞和非阻塞可以相互组合使用,例如同步阻塞、同步非阻塞、异步阻塞和异步非阻塞。
请简述进程和线程的区别——操作系统线程详解
1、进程是操作系统进行资源分配的最小单位,线程是CPU调度的最小单位,一个进程可以有多个线程;
2、基本上各进程之间是独立的,而同一进程中的线程极有可能相互影响;
3、线程执行开销小,但不利于资源的管理和保护;而进程正相反。
为什么需要线程?
1、同一进程内的线程共享内存和文件,因此它们之间相互通信无须调用内核
2、多个线程可以并发处理不同的任务,更有效地利用了多处理器和多核计算机。而进程只能在一个时间干一件事,如果在执行过程中遇到阻塞问题比如 IO 阻塞就会挂起直到结果返回
3、线程更加轻量级,一个进程可以创建多个线程
请简述什么是操作系统的内核态——用户态与内核态详解
操作系统的内核态与用户态是两种运行级别,具有不同的权限和功能。
-
内核态(Kernel Mode):
- 内核态是操作系统中的一种特权级别或运行模式。在内核态下,操作系统拥有最高的权限和访问系统资源的能力,可以执行特权指令和直接访问硬件设备。
- 当一个进程通过系统调用、中断或异常陷入执行异常代码时,我们就称进程处于内核态。
- 内核态是运行在内核模式的进程可以执行指令集中的任何指令,并且可以访问系统中的任何存储位置。
-
用户态(User Mode):
- 用户态是操作系统的最低特权级,也称为普通的用户进程运行的特权级。大部分用户直接面对的程序都是运行在用户态。
- 应用程序只能执行受限指令,不能直接访问硬件设备。
- 用户态提供应用程序运行的空间,为了使应用程序访问到内核管理的资源例如CPU、内存、I/O,内核必须提供一组通用的访问接口,这些接口就叫系统调用。
在操作系统中,进程在用户态下执行时,当需要访问硬件设备或其他特权资源时,会通过系统调用切换到内核态。内核态执行相应的操作后,再切换回用户态。这样的设计有助于保护系统的安全和稳定,防止恶意程序对系统资源的滥用。
IO密集型任务部署需要注意什么——程序运行类型分析
-
优化策略:我们可以通过批处理、缓存和多线程这三种方式来优化IO密集型系统的性能。批处理可以显著减少网络传输的延迟时间,从而提升系统性能,通过减少网络IO次数实现。而缓存技术能减轻IO压力,将常用的数据或结果暂存在内存中,从而降低对底层存储系统的访问频率。多线程则能够提高系统的并发性,使得更多的请求能够得到及时响应。
-
线程池的配置:针对IO密集型的任务,我们可以针对原本的线程池做一些改造以提高任务的处理效率。例如,阿里巴巴泰山版java开发手册建议线程池不应使用Executors去创建,而是通过ThreadPoolExecutor进行配置和管理。
-
硬件选择:由于IO密集型任务会频繁进行IO操作,因此选择高性能的硬件设备(如SSD硬盘)可以提高读写速度,从而提高系统性能。
-
软件调优:可以通过调整操作系统、文件系统以及应用程序等软件设置来优化性能。例如,调整操作系统的文件缓存大小、TCP参数等可以有效减少IO操作的时间消耗。
协程是什么?为什么需要协程——协程基础详解
为什么出现协程?
协程,又称微线程,是一种用户态的轻量级线程,主要用于解决高效率并发编程问题。在没有协程的时代,进行并发编程主要有两种方式:同步编程和异步多线程/进程。同步编程中,应用程序会等待IO操作的结果,阻塞当前线程;而异步多线程/进程则是将频繁的IO操作或单纯的IO操作独立到一/多个线程中,业务线程与IO线程间靠通信/全局变量来共享数据。
协程的应用场景
协程在阻塞操作上表现出色,例如磁盘读写、网络读写和硬件读写等。这些操作在阻塞的时候都需要阻塞线程,而在这种情况下使用协程可以提高程序的运行效率。协程的一个显著优势是它可以在单个线程内实现多个任务的切换,从而最大限度地利用CPU资源。然而,一个线程内的多个协程是串行执行的,因此协程并不适合计算密集型的场景。
协程主要适用于I/O阻塞型的场景,例如网络请求、文件读写等。因为I/O本身就是阻塞型的(相较于CPU的时间世界而言),使用协程可以提高程序的运行效率。协程在执行中不能有阻塞操作,否则整个线程会被阻塞,因此不适合处理CPU密集型的问题。
协程的另一个适用场景是高性能计算,通过牺牲公平性来换取吞吐率的提升。此外,协程也可以应用于IO密集型的流式计算。
需要注意的是,协程和性能没有直接关系,其性能可能并不比纯异步编程或反应器服务器更优秀。因此,选择是否使用协程应基于具体的应用场景和需求进行考虑。
请简述系统设计中缓存的作用——存储器的层次结构详解
在系统设计中,缓存的作用主要体现在提高数据访问速度和减少数据库压力等方面。缓存是一种存储技术,将热点数据存储在高速存储器中,当需要使用这些数据时,首先在缓存中查找,如果找到了,则直接从缓存中读取数据,而不必再从低速的存储器中读取,从而提高了数据的访问速度。
缓存技术和框架对于互联网的高并发、高性能的项目和系统至关重要。然而,仅仅利用缓存进行key-value的简单存取是远远不够的。在具体的业务场景中,缓存的应用可能会变得非常复杂,需要有强大的架构设计能力来应对。
需要注意的是,引入缓存会增加系统的复杂性,因此在实际应用中应根据实际业务需求来决定是否使用缓存以及如何使用缓存(例如,选择分布式缓存还是本地缓存)。同时,也需要设置合理的缓存策略以优化系统性能,并规避可能由于引入缓存而带来的风险。
随着分布式系统在软件工程中的广泛应用,像Redis和Memcached这样的分布式缓存解决方案也逐渐流行起来,它们能够实现数据在多个服务器和数据中心之间的缓存,从而进一步提升系统的性能。
请简述操作系统的虚拟内存——虚拟内存详解
虚拟内存是操作系统中的内存管理技术,它的核心方法是分离内存空间。具体来说,当一个程序需要使用的内存超过了物理内存的大小,操作系统会借助磁盘等额外的存储空间来扩展主存,从而形成一个比物理内存更大的逻辑内存空间。
在这个逻辑内存空间中,每个进程都有自己独享的虚拟地址空间,这个地址空间是一个连续的地址空间,每个进程都认为自己在使用一整个完整的、连续的地址空间。但实际上,不同进程的虚拟地址空间可能是相互独立的,彼此之间不会互相干扰。
为了实现这种虚拟内存的管理方式,操作系统和CPU需要共同配合。操作系统会把虚拟内存划分为若干个页,同时把物理内存也划分成若干个与虚拟内存页大小相同的页。然后,操作系统只需要记录虚拟内存页和物理内存页之间的映射关系即可。
虚拟内存技术带来了许多优势。首先,它可以实现进程之间的安全隔离,每个进程访问的都是自身的私有内存片;其次,它可以实现代码库的有效共享,多个进程可以共享同一份代码;此外,虚拟内存还可以更有效地利用碎片空间,为每个进程创建更多的主存空间。
请简述操作系统中的缺页中断——操作系统内存管理详解
缺页中断是中央处理器的内存管理单元在软件试图访问已映射在虚拟地址空间中,但是并未被加载在物理内存中的一个分页时所发出的中断。这是由于进程的地址空间是虚拟的,可能对应的真实映射地址在外存中,因此当真实访问到这个地址的时候,就会产生缺页中断。操作系统处理完缺页中断后,程序会恢复执行,并重新访问之前发生缺页中断的地址。
值得注意的是,缺页中断并非在一条指令执行完成后产生的,而是在指令执行过程中发生的。此外,页表中可能存放的不止是物理地址,还有一些控制位,比如读写bit位和缺页bit位等。
请编程实现LRU算法——页面置换算法详解
LRU(Least Recently Used)算法是一种常用的页面置换算法,用于内存管理。当内存空间不足时,LRU算法会将最近最少使用的页面替换出去,以释放内存空间。
以下是C++实现的LRU算法:
#include <iostream>
#include <list>
#include <unordered_map>
using namespace std;
class LRUCache {
public:
LRUCache(int capacity) : _capacity(capacity) {}
int get(int key) {
auto it = _cache.find(key);
if (it == _cache.end()) {
return -1;
}
_lru.splice(_lru.begin(), _lru, it->second);
return it->second->second;
}
void put(int key, int value) {
auto it = _cache.find(key);
if (it != _cache.end()) {
it->second->second = value;
_lru.splice(_lru.begin(), _lru, it->second);
return;
}
if (_cache.size() == _capacity) {
int lastKey = _lru.back().first;
_lru.pop_back();
_cache.erase(lastKey);
}
_lru.emplace_front(key, value);
_cache[key] = _lru.begin();
}
private:
int _capacity;
list<pair<int, int>> _lru;
unordered_map<int, list<pair<int, int>>::iterator> _cache;
};
int main() {
LRUCache cache(2);
cache.put(1, 1);
cache.put(2, 2);
cout << cache.get(1) << endl; // 输出 1
cache.put(3, 3); // 淘汰 key 2
cout << cache.get(2) << endl; // 输出 -1 (未找到)
cache.put(4, 4); // 淘汰 key 1
cout << cache.get(1) << endl; // 输出 -1 (未找到)
cout << cache.get(3) << endl; // 输出 3
cout << cache.get(4) << endl; // 输出 4
return 0;
}
在这个实现中,我们使用了一个双向链表_lru
来记录页面的使用顺序,以及一个哈希表_cache
来记录页面的键值对。当需要获取一个页面时,我们首先在哈希表中查找该页面,如果找到了,就将其移动到链表的头部;如果没有找到,就返回-1。当需要插入一个页面时,我们首先检查缓存是否已满,如果满了,就淘汰链表尾部的页面,并从哈希表中删除对应的键值对;然后,将新的页面插入到链表的头部,并在哈希表中添加对应的键值对。
软链接与硬链接有什么区别——Linux文件系统详解
在Linux系统中,软链接和硬链接都是文件链接的类型,它们都能实现让一个文件与另一个文件关联起来,使得对其中一个文件的修改能够影响到另一个文件。
硬链接是同一个文件在不同位置上存在多个有效路径名的情况,这些路径名都指向同一个索引节点(inode)。因此,硬链接其实是同一个文件的不同名字,它们都共享着同一个数据块(data block),并且都不占用额外的存储空间。但是硬链接不能跨过文件系统,只能链接同一文件系统的文件。此外,硬链接也不能用于目录。
软链接则是一个独立的文件,它包含了另一个文件的路径名,通过索引节点来链接。不同于硬链接,软链接可以链接不同文件系统的文件。软链接本身有自己的 inode 和数据块,因此拥有自己的文件属性和权限。同时,软链接还支持对目录的创建。
总的来说,硬链接和软链接的本质区别在于他们链接的是同一个文件还是不同的文件,以及他们是否能够跨过文件系统进行链接。
请简述服务器部署的RAID——磁盘冗余阵列详解
RAID,全称为Redundant Array of Independent Disks,中文可译为独立冗余磁盘阵列。它是通过将多块独立的物理硬盘以不同的方式组合起来形成一个硬盘组(逻辑硬盘),从而提供比单个硬盘更高的存储性能和数据备份技术。
RAID的核心思想是把多个硬盘通过一定组合方式组合起来,成为一个新的硬盘阵列组,从而达到高性能硬盘的要求。它主要有两个关键技术:镜像和条带化。镜像技术可以提供数据的安全性,而条带化(又称块级并行)则可以提高数据的读写速度。
常见的RAID组合方式有:RAID0、RAID1、RAID5、RAID6、RAID01、RAID10等。其中,RAID0又被称为“条带”,它将两个或多个硬盘组成一个逻辑硬盘,以提高读写速度;RAID1则是通过数据镜像实现数据冗余,以增强数据的安全性;RAID5和RAID6则在保证数据安全性的同时,提供了较高的读写性能。
总的来说,服务器部署RAID的主要目的是为了提高数据的安全性和可用性,同时也能提升存储系统的性能。
请谈谈对死锁必要条件的理解——死锁的基本理论
死锁是进程运行时可能遇到的一种状态,当多个进程因竞争资源而造成一种僵局,若无外力干涉那它们都将无法再向前推进。这种情况的发生必然遵循某些规律,为此,我们定义了死锁的四个必要条件:
-
互斥条件:一次只有一个进程可以使用资源,即资源在某一时间段内只能被一个进程占有。此时若有其他进程请求该资源,则请求进程只能等待。
-
请求与保持条件:当进程已经保持了至少一个资源,但又提出了新的资源请求时,而该资源已被其他进程占有,此时请求进程会被阻塞,但它会对自己已获得的资源保持不放。
-
不可剥夺条件:进程已获得的资源,在未使用完之前,不能被其他进程强行夺走,即只能由获得该资源的进程自己来释放。
-
循环等待条件:若干进程之间形成一种头尾相接的循环等待资源关系,即存在一个进程集合{P0, P1, P2, …, Pn}中的P0正在等待一个P1占用的资源;P1正在等待P2占用的资源,……,Pn正在等待已被P0占用的资源。
这四个条件是死锁的必要条件,只要系统发生死锁,这些条件必然成立。然而,只要上述条件之一不满足,就不会发生死锁。因此,理解了死锁的原因,尤其是产生死锁的四个必要条件,就可以最大可能地避免、预防和解除死锁。
线程-进程同步问题三大经典案例详解
线程-进程同步问题涉及到多个经典案例,其中最为知名的是生产者消费者问题、读者写者问题和哲学家就餐问题。
-
生产者消费者问题:在这个场景中,存在一个有限的缓冲区,生产者进程负责生产产品放入该缓冲区,消费者进程从缓冲区取出产品进行消费。这个过程会面临一个问题,即如果生产者生产速度过快,而消费者消费速度跟不上,那么缓冲区可能会被填满,导致生产者无法继续生产;反之,如果消费者消费速度过快,缓冲区为空,生产者也无法继续生产。因此,需要确保生产者与消费者的协调运作。
-
读者写者问题:这是一个关于对同一资源进行访问的问题。读者进程负责读取数据,而写者进程负责修改数据。为了避免读写冲突,我们需要确保同一时间只有一个读者或写者在操作资源。
-
哲学家就餐问题:五个哲学家围坐在一个圆桌周围,每个哲学家面前都有一只叉子,每两个哲学家之间放有一只碗。他们的生活目标是吃到美味的食物,但唯有同时拿起左右两只叉子才能开始进食。这个问题旨在探讨如何避免死锁情况的发生。
解决上述问题的关键是实现进程间的同步和互斥,这通常可以通过信号量、互斥锁、条件变量等同步机制来实现。例如,在生产者消费者问题中,可以设置一个计数器作为信号量来控制生产者和消费者的执行顺序;在读者写者问题中,可以使用互斥锁来保护共享资源;而在哲学家就餐问题中,同样可以使用互斥锁来避免死锁的发生。
请简述乐观锁、悲观锁、可重入锁——锁的分类详解
锁是实现线程同步的一种机制,主要分类包括悲观锁、乐观锁和可重入锁。
-
悲观锁:也被称为悲天悯人锁,在并发情况下,悲观锁总是假设最坏的情况,认为别人会同时修改数据。因此,每次在获取数据的时候都会上锁,防止其他线程同时修改数据。如果其他线程想要获取数据,只有等待悲观锁被释放之后才能进行。这样可以避免数据竞争问题,保证数据的一致性,但同时也带来了较大的性能开销。
-
乐观锁:与悲观锁相反,乐观锁在并发情况下总是假设最好的情况,认为别人不会同时修改数据。因此,在获取数据的时候并不会上锁。只有在更新数据的时候,才会检查在读取至更新这段时间内别人有没有修改过这个数据。如果别人已经修改了数据,那么就会返回错误或者失败,让用户决定如何操作。乐观锁适用于读多写少的场景,可以提升系统的并发性能。
-
可重入锁:是一种支持同一线程多次获取同一把锁的机制。也就是说,在线程执行过程中,该线程可以再次获取已经获取过的锁。可重入锁可以避免因为线程多次加锁释放锁产生的性能开销,提高程序的运行效率。
请简述互斥锁与自旋锁的区别——线程间通信详解
互斥锁和自旋锁都是用于实现线程同步的机制,但它们在处理加锁失败的情况时有着不同的行为。
-
互斥锁:当一个线程尝试获取已被其他线程持有的互斥锁时,该线程会进入阻塞状态,释放CPU资源,让其他线程有机会执行。只有当持有锁的线程执行完毕并释放该锁后,等待的线程才能再次尝试获取锁。这种方式也被称为睡眠等待型锁。
-
自旋锁:与互斥锁不同,当一个线程试图获取自旋锁但失败时,该线程并不会进入阻塞状态,而是保持运行并持续检测锁是否可用。换言之,这个线程会忙等待,直到它成功获取到锁为止。自旋锁通常使用一个循环来不断地检查锁是否已经被释放。
进程间有哪些同步的方式——进程间通信详解
进程间同步是为了协调竞争资源的使用次序,以及使得并发执行的多个进程之间可以有效使用资源和相互合作。常见的进程间同步方式包括以下几种:
-
信号量:信号量是一个整数值,用于进程间传递信号。在信号量上主要有三种操作:初始化,P操作(也称为递减操作)和V操作(也称为增加操作)。其中,P操作和V操作都是原子操作。P操作可以用于阻塞一个进程,而V操作则能释放一个被阻塞的进程。
-
管程:管程是一种数据结构和一组操作,用于控制对共享资源的访问。管程内的代码只能由管程内的进程调用,保证了共享资源的安全使用。
-
临界区:临界区是一段只能被一个进程访问的代码或数据。通过对多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问。
-
事件:事件是一种同步机制,用于在进程间传递消息。一个进程可以等待一个事件的发生,而另一个进程可以触发这个事件。
-
互斥量:互斥量是一种用于保护共享资源的同步原语。它只允许一个线程在同一时间内访问共享资源。
请简述无锁数据结构的原理——CAS原理与无锁技术详解
无锁数据结构是一种多线程编程的算法,它不依赖锁来实现线程间的同步,从而避免了因阻塞而导致的性能瓶颈。这种技术的主要实现方式就是利用CAS操作——Compare and Swap。
CAS操作是原子操作的一种,其原理是通过内存中的值与指定数据进行比较,如果数值一样则将内存中的数据替换为新的值。这个操作有三个操作数:内存中的值(V)、预期原始值(A)、修改后的新值。当内存中的值和预期原始值相等时,就将修改后的新值保存到内存中;否则,说明共享数据已经被修改,此时会放弃已经所做的操作,然后重新执行刚才的操作,直到重试成功。
无锁数据结构主要采用这种方式来访问共享数据,常见的无锁数据结构有:无锁队列、无锁容器等。由于无锁数据结构在高并发场景下表现出优秀的性能,因此在大规模分布式系统中得到了广泛的应用。
请简述项目中分布式锁的实现方式——分布式锁实现详解
分布式锁,顾名思义,是在分布式系统中对共享资源进行操作的一种控制手段,主要目的是保证在多台服务器执行某段代码时,该代码段能被仅有一台服务器执行,从而实现操作的一致性。实现分布式锁的方式有多种,主要包括以下三种:
-
基于数据库实现分布式锁:利用数据库本身的排他性实现锁的控制。悲观锁策略是一种常见的实现方式,它通过select … for update语句来获取锁,在查询期间其他事务不能修改数据,从而保证了数据一致性。
-
基于缓存(例如Redis)实现分布式锁:这种方法利用了缓存系统的原子性操作。最典型的实现方式是使用SETNX命令,当且仅当键不存在时设置值,从而保证同一时刻只有一个客户端能够获得锁。
-
基于Zookeeper实现分布式锁:Zookeeper是一个开源的分布式协调服务,它提供了一种简单的、高性能、可靠的分布式锁实现方式。Zookeeper的数据模型类似于一个文件系统,每个节点被称为znode,通过创建临时顺序节点来实现分布式锁的控制。
以上三种实现方式各有优缺点,需要根据具体的业务场景和需求来选择最合适的实现方式。
最后,八股文真的不必买课,网上资料很多的呀,或者看看大佬整理的文章资料即可,实在不行也可以问AI啊!