欢迎来到尧图网

客户服务 关于我们

您的位置:首页 > 汽车 > 时评 > 操作系统面试八股文

操作系统面试八股文

2024/10/25 15:32:19 来源:https://blog.csdn.net/qq_71286244/article/details/140260411  浏览:    关键词:操作系统面试八股文

1.进程,线程和协程的区别和联系

进程,线程和协程是计算机中多任务处理的三种不同的概念。

  1. 进程:进程是操作系统中的一个概念,是系统中资源分配的基本单位。每个进程有独立的内存空间、程序和数据。进程之间需要通过进程间通信来实现数据共享和通信。

  2. 线程:线程是程序执行的最小单位,一个进程可以包含多个线程。线程之间共享同一个进程的资源,比如内存空间、文件描述符等。线程之间可以通过共享的内存空间进行数据通信。

  3. 协程:协程是一种用户态线程,是由程序员在应用层控制的。协程可以在同一个线程内实现多个并发执行的任务,通过协作而非抢占的方式来实现任务切换。协程之间可以在执行权交还给调度器之前主动控制切换。

联系:

  • 进程和线程都是操作系统的概念,而协程是由程序员在应用层实现的。
  • 线程是进程内的执行单元,而进程是资源分配的基本单位。
  • 协程是在一个线程内实现多个任务的执行单位。
  • 进程之间通信比较复杂,需要使用进程间通信机制,线程之间可以通过共享内存实现通信,协程可以通过共享状态实现通信,并且协程之间的切换开销比线程小。

总结:

  1. 进程是资源调度的基本单位,运行一个可执行程序会创建一个或多个进程,进程就是运行起来的可执行程序。
  2. 线程是程序执行的基本单位,是轻量级的进程。每个进程中都有唯一的主线程,且只能有一个主线程和进程相互依赖的关系,主线程结束进程也会结束。多提一句:协程是用户态的轻量级线程,线程内部调度的基本单位。
  • 知识点补充

主线程通常指的就是一个程序中的主线程,通常是main函数所在的线程。主线程是程序的入口点,当程序被启动时,主线程开始执行main函数中的代码。主线程结束通常意味着整个程序的生命周期结束,进程也会随之结束。因此,主线程在程序运行中扮演着非常重要的角色。

2. 线程和进程的比较

线程和进程是操作系统中实现并发的两种基本方式,它们在概念、资源共享、通信等方面有一些明显的区别和比较。

  1. 定义:
  • 进程:进程是程序执行时的一个实例,是操作系统进行资源分配和调度的基本单位。每个进程都有自己独立的内存空间、代码、数据和文件描述符等系统资源。
  • 线程:线程是进程中的一个执行单元,是程序执行的最小单位。线程共享进程的内存空间和资源,不同线程之间的切换比进程间的切换要快速。
  1. 资源共享:
  • 进程:进程拥有独立的内存空间,通信和数据共享需要使用IPC(进程间通信)机制,比如管道、信号、消息队列等。
  • 线程:线程共享相同的内存空间和资源,可以直接访问进程的全局变量和数据结构,实现数据共享比较容易。
  1. 切换效率:
  • 进程:进程间的切换会涉及到上下文切换、内存管理等操作,切换开销比较大。
  • 线程:线程之间的切换只需要保存和恢复线程私有的寄存器状态,切换开销比进程小。
  1. 创建和销毁:
  • 进程:创建一个进程需要分配独立的内存空间和系统资源,进程之间的切换开销比较大。
  • 线程:创建和销毁线程的开销小,可以在同一个进程内创建多个线程实现并发执行。

总的来说,线程比进程轻量级,因为线程共享进程的资源,切换效率高,但是线程之间的协作和同步需要注意线程安全问题。进程之间彼此独立,通信和数据共享需要使用IPC机制,进程间切换效率低,但进程间相互隔离,更加稳定。选择线程还是进程取决于具体的应用场景和需求。

3. 一个进程可以创建多少线程,和什么有关?

理论上,一个进程可用的虚拟空间是2G,默认情况下,线程的栈的大小是1MB,所以理论上最多只能创建2048个线程。如果要创建多于2048的话,必须修改编译器的设置。

因此,一个进程可以创建的线程数是由可用的虚拟空间和线程的栈的大小共同决定的。只要虚拟空间足够,那么新线程的建立就会成功。如果需要创建超过2k以上的线程,减少你线程的大小就可以实现,虽然再一般情况下,你不需要那么多的线程。过多的线程将会导致大量的时间浪费再线程的切换上,给程序运行效率带来负面的影响。

4. 外中断和异常由什么区别?

外中断和异常都是操作系统中的两种事件,但它们有一些区别:

  1. 定义:
  • 外中断(Interrupt):外中断是由外部设备或其他处理器引起的一种信号,用于向 CPU 发出异步的事件通知,例如时钟中断、I/O 中断等。外中断是由硬件触发的,可以打断 CPU 的执行流程。
  • 异常(Exception):异常是由 CPU 内部的某些事件引起的一种信号,例如除零错误、内存访问异常等。异常是由指令执行期间发生的,通常情况下会导致程序的异常终止。
  1. 触发条件:
  • 外中断:外中断是由外部设备或其他处理器产生,例如硬件设备的状态发生变化或计时器触发。外中断是异步事件,可以在任何时候发生,并且随机性比较强。
  • 异常:异常是由指令执行期间发生的特殊情况,例如算术运算溢出、非法指令等。异常通常是同步事件,与指令执行有关,具有一定的可预测性。
  1. 处理方式:
  • 外中断:外中断通常需要操作系统提供中断处理程序来响应中断事件,以保证系统能够正确地处理外部设备的请求或通知。
  • 异常:异常可以由操作系统或程序自身进行处理,根据不同的异常类型进行相应的异常处理操作,例如恢复现场、中断指令执行等。

总的来说,外中断是由外部设备或其他处理器触发的异步事件,而异常是由 CPU 内部的指令执行触发的同步事件。处理外中断通常需要操作系统介入,而异常的处理方式可以是由操作系统或程序自身处理。

5. 进程状态的切换你知道多少?

进程在操作系统中通常会经历多个状态,并且在这些状态之间进行切换。以下是常见的进程状态和状态切换:

  1. 就绪状态(Ready):
  • 进程已经准备好运行,但还需要等待处理器的分配。处于就绪状态的进程通常被插入到就绪队列中等待调度。
  1. 运行状态(Running):
  • 处于运行状态的进程正在执行,占用 CPU 资源。在多道程序环境下,多个进程可以轮流被调度执行。
  1. 阻塞状态(Blocked):
  • 进程因为某些原因(如等待 I/O 完成、等待资源释放等)而处于等待状态,无法继续执行。这时进程将会被阻塞,直到满足条件才能切换至就绪状态。
  1. 创建状态(New):
  • 进程刚被创建,但还未进入就绪状态或运行状态。在这个阶段,操作系统会为进程分配资源,并初始化相关状态信息。
  1. 终止状态(Terminated):
  • 进程执行完成或被操作系统终止后,进入终止状态。在终止状态下,操作系统会释放进程占用的资源,并进行善后工作。

进程在不同状态之间的转换通常会受到内部或外部事件的触发,导致进程状态的改变。常见的状态切换包括:

  • 从就绪状态切换到运行状态:当 CPU 被分配给该进程时,进程从就绪状态切换到运行状态。
  • 从运行状态切换到阻塞状态:当进程需要等待某些事件完成时,会从运行状态切换到阻塞状态。
  • 从阻塞状态切换到就绪状态:当被等待的事件完成时,进程从阻塞状态切换到就绪状态,等待 CPU 调度执行。

进程状态的切换同样会受到操作系统的调度策略和算法的影响,以确保系统能够有效地管理和调度进程,提高系统的并发性能。

6.一个程序从开始到结束的完整过程?

  1. 预处理(Preprocessing):
  • 预处理是编译过程中的第一步,主要负责对代码进行预处理,包括展开宏定义、包含头文件、条件编译等处理。
  • 预处理器读取源代码文件,根据预处理指令(以#开头的指令),对源代码进行修改或处理,生成预处理后的代码。
  • 比较常见的预处理指令包括#define用于定义宏、#include用于包含头文件、#ifdef、#endif等用于条件编译等。
  1. 编译(Compilation):
  • 编译是将预处理后的代码转换为汇编代码的过程,主要负责词法分析、语法分析、语义分析、中间代码生成等步骤。
  • 编译器将源代码翻译成机器可以理解的指令,生成与具体硬件平台相关的汇编代码(Assembly Code)。
  • 编译器通常会将源代码分析转换为抽象语法树(Abstract Syntax Tree,AST),然后将其转换为目标代码。
  1. 汇编(Assembly):
  • 汇编是将汇编代码翻译成机器码的过程,将汇编指令翻译成相应的二进制指令。
  • 汇编器(Assembler)负责将汇编代码转换为目标机器的机器语言指令,生成目标文件。
  • 目标文件包含了机器指令、数据、符号表等信息,但尚未与其他模块链接。
  1. 链接(Linking):
  • 链接是将多个目标文件和库文件链接为一个可执行程序的过程,负责解析符号引用、符号重定位、符号解析等工作。
  • 链接器(Linker)将各个目标文件中引用的库函数或变量解析为实际地址,并生成最终的可执行文件。
  • 在链接过程中,可能会进行符号冲突解析、优化、生成符号表等工作,最终生成可执行程序(Executable File)。
  • 链接的过程包括静态链接(在编译时进行链接)和动态链接(在程序运行时进行链接)两种方式。

综合以上四个过程,预处理负责对源代码进行处理,编译将源代码转换为汇编代码,汇编将汇编代码转换为机器码,链接将目标文件和库文件链接为最终可执行程序。整个过程完成后,程序才能被正常运行。

7. 进程同步的四种方法?

进程同步是指多个进程之间在共享资源的访问、通信和协作中保持一致性或避免冲突的一种机制。常见的进程同步方法包括:

  1. 信号量(Semaphore):
  • 信号量是一种用于进程同步的经典方法,通过操作系统提供的信号量机制来控制进程的访问权限。
  • 信号量可以作为一个计数器,用于记录共享资源的数量,进程在访问前通过操作信号量来获取资源并修改计数器,访问后释放资源。
  • 信号量包括二进制信号量(只有0和1两个值)和计数信号量(可以是任意整数值)两种类型,可用于实现互斥、同步等操作。
  1. 互斥锁(Mutex):
  • 互斥锁是一种用于确保在任意时刻只有一个进程可以访问共享资源的同步机制,通常用于解决临界区(Critical Section)的同步问题。
  • 进程在进入临界区前尝试获取互斥锁,如果锁已被其他进程占用,则进程会被阻塞直到锁可用;进程访问完临界区后释放锁。
  1. 条件变量(Condition Variable):
  • 条件变量是一种用于实现进程间等待和通知的同步机制,在等待某个条件成立时进程挂起,直到条件满足时被唤醒。
  • 进程可以通过条件变量来阻塞自己等待某个条件的发生,另一个进程在满足条件时通过条件变量唤醒等待的进程继续执行。
  1. 读写锁(Read-Write Lock):
  • 读写锁是一种特殊的同步机制,用于控制对共享资源的并发读写操作,允许多个线程同时读取共享资源,但只允许一个线程进行写操作。
  • 当有线程要对共享资源进行写操作时,其他线程无法同时进行读或写操作,保证了共享资源的一致性。

这些方法可以根据具体的情况选择使用,用于确保并发操作的正确性和一致性,避免产生死锁、竞态条件等问题。

8. 一个由c/c++编译的程序占用的内存分为哪几个部分?

一个由C/C++编译的程序在内存中通常会被划分为以下几个主要部分:

  1. 代码段(Code Segment):
  • 代码段存储程序的执行代码,即程序的机器指令。
  • 代码段通常是只读的,因为程序的代码在运行时无需修改。
  • 代码段的大小在程序加载时被确定,通常是固定大小。
  1. 数据段(Data Segment):
  • 数据段包含程序中的全局变量、静态变量和常量。
  • 数据段分为初始化数据段(Initialized Data Segment)和未初始化数据段(Uninitialized Data Segment)。
  • 初始化数据段存储已经初始化的全局和静态变量,而未初始化数据段存储未初始化的全局和静态变量。
  1. 堆(Heap):
  • 堆是程序运行时动态分配内存的区域,用于存储程序在运行时动态创建的数据。
  • 堆的大小不固定,在程序运行时可以动态增长或缩小。
  • 程序通过对应的内存管理函数(如malloc、free或new、delete)来进行对堆内存的分配和释放。
  1. 栈(Stack):
  • 栈存储程序运行过程中的局部变量、函数参数、返回地址等数据。
  • 栈是一种后进先出(LIFO)结构,每次函数调用时会在栈上创建一个新的帧用于存储方法的参数和局部变量。
  • 栈的大小通常较小且固定,当递归层级过深或者栈溢出时会导致程序崩溃。
  1. 全局/静态变量区域:
  • 有些编译器会单独划分出一个全局/静态变量区域存储全局变量和静态变量,这些变量在程序整个运行过程中都有效。
  • 这个区域包括已初始化的全局变量、未初始化的全局变量等。

以上是一个典型的C/C++程序在内存中的主要分段,不同的编译器和操作系统可能存在细微差异。在程序的运行过程中,这些内存区域会随着程序的运行状态动态变化,需要由操作系统进行调度和管理。

9.常见内存分配方式有哪些?

常见的内存分配方式包括以下几种:

  1. 静态内存分配(Static Memory Allocation):
  • 在编译时分配内存空间,在程序运行期间一直存在。
  • 静态内存分配适用于全局变量、常量、静态变量等需要在整个程序生命周期内存在的数据。
  1. 动态内存分配(Dynamic Memory Allocation):
  • 在程序运行时根据需要动态地分配内存空间,使用时申请,不需要时释放。
  • 动态内存分配适用于需要动态创建、释放内存的数据结构,例如链表、树等。
  1. 栈内存分配(Stack Memory Allocation):
  • 栈内存分配是一种自动分配和释放的内存分配方式,由编译器自动管理。
  • 局部变量、函数参数、函数调用等数据通常都存储在栈上。
  • 栈内存分配速度快,但大小有限,嵌套层级过深或者申请过大的局部变量可能导致栈溢出。
  1. 堆内存分配(Heap Memory Allocation):
  • 堆内存分配是一种手动分配和释放的内存分配方式,需要程序员手动调用对应的内存管理函数来申请和释放内存。
  • 动态分配的内存通常存储在堆上,例如使用malloc和free函数或new和delete运算符动态分配释放内存。
  • 堆内存分配相比栈内存分配灵活性更高,但需要程序员显式管理内存,防止内存泄漏和野指针等问题。

这些不同的内存分配方式各有优势和适用场景,程序员需要根据实际需求和情况选择合适的内存分配方式,以确保程序的高效性和稳定性。

10.从堆和栈上建立对象哪个快?

在堆(Heap)上建立对象通常比在栈(Stack)上建立对象慢,这是因为堆上的内存分配是动态的,需要系统在运行时查找合适的内存空间并进行分配,而且需要额外的内存管理开销。相对而言,在栈上建立对象是一种静态内存分配,编译器在编译时就可以确定内存分配的位置和大小,因此更加高效。

在堆上建立对象时,程序需要调用new或malloc等动态内存分配函数,再将返回的指针赋给对象指针。而在栈上建立对象时,只需要在声明对象时指定对象类型,编译器会自动分配内存并调用对象的构造函数。

需要注意的是,虽然在堆上建立对象可能比较慢,但堆上的对象具有更灵活的生命周期管理,可以动态地创建和销毁对象,适用于对象生命周期不确定或需要动态分配的情况。因此,在选择从堆还是栈上建立对象时,需要根据实际需求和情况进行权衡。

版权声明:

本网仅为发布的内容提供存储空间,不对发表、转载的内容提供任何形式的保证。凡本网注明“来源:XXX网络”的作品,均转载自其它媒体,著作权归作者所有,商业转载请联系作者获得授权,非商业转载请注明出处。

我们尊重并感谢每一位作者,均已注明文章来源和作者。如因作品内容、版权或其它问题,请及时与我们联系,联系邮箱:809451989@qq.com,投稿邮箱:809451989@qq.com