操作系统知识汇总(含面试题)

 1.epoll相比于select和poll的优缺点?

epoll相比于select和poll的优缺点如下:

优点:

(1)select/poll的增强版本
epoll是Linux内核为处理大批量文件描述符而作了改进的poll,是Linux下多路复用IO接口select/poll的增强版本,它能显著提高程序在大量并发连接中只有少量活跃的情况下的系统CPU利用率

(2)文件描述符数量无限制:

在select和poll中,由于系统对 fd 数量有限制,因此可能无法支持大量的文件描述符。而epoll没有这个限制,可以支持大量的文件描述符。

(3)基于事件通知:

epoll采用的是基于事件通知的方式,只有当某个文件描述符就绪时,才会把这个文件描述符放到就绪队列中,这样就减少了轮询的次数,提高了效率。

  (4)维护就绪队列:
epoll会维护一个就绪队列,当有文件描述符就绪时,直接将其放入就绪队列,然后用户就可以通过epoll_wait来获取就绪的描述符。这种方式相比select和poll的轮询方式,更加高效。

  (5)内存映射:
epoll还支持内存映射(mmap),即将内核中的数据结构映射到用户空间,从而避免了数据在内核空间和用户空间之间的复制。

缺点:

 (1)内存映射:
虽然内存映射可以提高效率,但也存在一些问题。例如,如果数据量很大,那么内存映射可能会导致内存占用过高。此外,内存映射中的数据结构需要用户和内核共享,因此如果用户空间的数据被修改了,那么内核中的数据也会被修改,这可能会导致一些问题。

  (2)使用难度:
epoll的使用比select和poll更复杂,需要更多的代码来实现。此外,由于epoll是基于事件通知的,因此需要更多的代码来处理事件的回调。

  (3)不能跨平台,仅限于Linux系统。

  (4)延时时间只能精确到毫秒,对于需要更精确的延时时间的应用程序可能会受到限制。

总结

        总之,epoll具有高效的IO多路复用特性,但也有其限制和缺点,需要根据具体的应用场景进行选择。

参考:

深入理解select、poll和epoll及区别_poll epoll_panamera12的博客-CSDN博客

【并发】IO多路复用select/poll/epoll介绍_哔哩哔哩_bilibili

2、介绍进程间通信的方法

进程间通信的方式有六种

示意图如下所示:包括管道、消息队列、共享内存、信号量、信号、sokect等

进程间通信的八种方式:

  1. 管道(pipe):
    管道是一种半双工的通信方式,数据只能单向流动,而且只能在具有亲缘关系的进程间使用。进程的亲缘关系通常是指父子进程关系。
  2. 命名管道(named pipe):
    命名管道也是半双工的通信方式,但是它允许无亲缘关系进程间的通信
  3. 信号量(semaphore):
    信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  4. 消息队列(message queue):
    消息队列是一种在消息的传输过程中保存消息的容器。它是一种在消息发送者和接收者之间建立连接的机制,通过这种机制,消息可以在不同的进程或计算机之间进行传递。消息队列管理器在将消息从它的源中继到它的目标时充当中间人。队列的主要目的是提供路由并保证消息的传递;如果发送消息时接收者不可用,消息队列会保留消息,直到可以成功地传递它。。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓冲区大小受限等缺点。
  5. 信号(signal):
    信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生
  6. 共享内存(shared memory):
    共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量,配合使用,来实现进程间的同步和通信。
  7. 套接字(socket):
    套接字也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同机器之间的进程间通信
  8. 信号线(signal line):
    信号线是一种简单的通信方式,用于传输单个信号。它适用于在同一计算机系统的进程间通信。

总的来说,各种进程间通信方式都有其特点和适用场景,根据具体需求选择合适的通信方式。

参考:进程间的通信方式(六种)_进程间通信的几种方法_黄瓜炒肉的博客-CSDN博客

3、堆栈的区别

堆和栈的区别主要有五大点,分别是:

1、申请方式的不同

栈由系统自动分配,而堆是人为申请开辟;

2、申请大小的不同。

栈获得的空间较小,而堆获得的空间较大

3、申请效率的不同。

栈由系统自动分配,速度较快,而堆一般速度比较慢

4、存储内容的不同

栈在函数调用时,函数调用语句的下一条可执行语句的地址第一个进栈,然后函数的各个参数进栈,其中静态变量是不入栈的。而堆一般是在头部用一个字节存放堆的大小,堆中的具体内容是人为安排;

5、底层不同

栈是连续的空间,而堆是不连续的空间

扩展:为什么堆开辟的内存不是连续的空间

堆(Heap)是一种动态分配的内存,它的主要特性是能够在运行时动态地申请和释放内存。然而,堆的内存并不是连续的。这是因为堆的内存分配策略是基于分治思想的,它将内存分为多个大小不同的块(chunk),每个块包含固定数量的内存单元。

当程序需要申请内存时,堆管理器会根据请求的大小,从堆中分配一个或多个合适的块。这些块可以是连续的,也可以是不连续的。如果当前堆中没有足够大的连续块来满足请求,那么堆管理器会从操作系统申请更多的内存,并将这些内存分割成更小的块。

同样地,当程序释放内存时,堆管理器并不会立即将释放的内存归还给操作系统。相反,它会将这些内存块合并,以便在下次请求时可以重复利用。这种策略可以减少频繁的内存申请和释放操作,从而提高程序的性能。

因此,堆的内存分配策略导致了其内存不是连续的。这种非连续的内存分配方式在某些情况下可能会影响程序的性能,但它在总体上提供了更好的灵活性和效率。

4、pthread_create、pthread_join()、join()方法的作用

1、pthread_create()

pthread_create()函数是POSIX线程(Pthreads)库中的一个核心函数,用于在程序中创建新的线程。这个函数的原型如下:


	int pthread_create(pthread_t *thread, 
const pthread_attr_t *attr, 
void *(*start_routine) (void *), 
void *arg);

参数详解:

  • thread:一个指向pthread_t类型变量的指针,用于存储新创建的线程的标识符。
  • attr:线程的属性(如堆栈大小、优先级等)。通常可以设为NULL,表示使用默认属性。
  • start_routine:新创建的线程应该开始执行的函数的指针。这个函数应该具有void*的返回类型,第一个参数是void*类型,表示线程函数的参数。
  • arg:传递给start_routine的参数。

函数返回0表示成功,返回一个错误码表示失败。

使用pthread_create()可以创建并发执行的线程,增加程序的并行性,提高程序的性能。需要注意的是,在多线程程序中,需要正确地处理线程间的同步和互斥问题,避免出现数据竞争、死锁等问题。

下面是一个简单的例子:

#include <stdio.h> 
#include <stdlib.h> 
#include <pthread.h> 


void *thread_function(void *arg) { 
int *id_ptr = (int*) arg; 
int id = *id_ptr; 
printf("Thread %d: Hello, World!\n", id); 
return NULL; 
} 


int main() { 
pthread_t threads[5]; 
int thread_ids[5]; 
int i; 
for (i = 0; i < 5; i++) { 
thread_ids[i] = i; 
if (pthread_create(&threads[i], NULL, thread_function, &thread_ids[i])) { 
fprintf(stderr, "Error creating thread\n"); 
return 1; 
} 
} 
pthread_exit(NULL); 
return 0; 
}

这个程序创建了5个线程,每个线程打印一句"Hello, World!"。注意这个例子中的线程函数thread_function的参数是一个指向整数数组的指针,这个数组存储了每个线程的ID。

2、pthread_join()

pthread_join()函数是POSIX线程(Pthreads)库中的一个函数,用于将一个线程加入到当前的进程中。具体来说,pthread_join()会阻塞调用线程(也就是当前线程),直到指定的线程结束。

函数的原型如下:


	int pthread_join(pthread_t thread, void **retval);

其中,thread参数是一个pthread_t类型的线程标识符,这个标识符是通过pthread_create()函数返回的。retval是一个可选的指针参数,用于存储被加入的线程的返回值。

函数会返回0,如果成功,或者返回一个错误码(比如EINVAL,如果提供的线程标识符无效)。

使用pthread_join()函数有几个重要的原因:

  1. 资源回收:当一个线程结束时,它仍然会保留一些系统资源(如内存和文件描述符)。通过调用pthread_join(),可以确保这些资源被正确地回收。
  2. 获取线程的返回值:如果指定了retval参数,那么当被加入的线程结束时,它的返回值会被存储在retval指向的地址中。
  3. 同步:通过阻塞调用线程,直到另一个线程结束,可以实现线程间的同步。这对于确保某些操作在所有需要的线程都完成后才进行是非常有用的。

然而,需要注意的是,过度使用pthread_join()可能会导致性能问题。例如,如果你在一个循环中不断地创建和加入线程,那么由于每次循环都会调用pthread_join(),导致每次循环都需要等待上一个线程结束,这可能会严重降低程序的执行效率。此外,如果一个线程无法正常结束(例如由于未捕获的异常),那么调用pthread_join()的线程可能会永远被阻塞。因此,在使用pthread_join()时,需要谨慎处理这些潜在的问题。

在C++中,std::thread类中的join()函数用于等待线程结束。这与Java中的join()方法的功能类似。

具体来说,当调用一个线程的join()函数时,当前线程会停止执行,并等待该线程执行完毕后继续执行。这可以用于实现线程间的同步,以及等待其他线程完成工作。

3、join()

join()函数有两种形式:

  1. void join(std::chrono::milliseconds rel_time) throws std::system_error:等待指定的时间达到后,当前线程继续执行。如果等待超时,则抛出std::system_error异常。
  2. void join():等待该线程执行完毕,当前线程会阻塞,直到该线程执行完毕后继续执行。

需要注意的是,如果一个线程是守护线程(即其属性被设置为守护属性),那么它不会被join()函数阻塞,而是随时可以被操作系统回收。因此,在使用join()函数时需要确保当前线程不是守护线程,或者在调用join()函数之前先将其设置为非守护线程。

5、线程池是什么,应用在什么场合?

线程池是一种预先创建线程的技术,它可以在需要时重复使用已有的线程,减少创建和销毁线程的开销,从而提高系统的性能。线程池一般应用在对并发处理要求较高的场合,例如:

  1. 高并发访问Web应用:通过使用线程池,可以处理大量的并发请求,提高系统的吞吐量。
  2. 大规模数据处理:在大数据处理场景下,使用线程池可以高效地利用系统资源,快速处理数据。
  3. 并行计算:在科学计算、机器学习等领域,线程池可以用于并行计算,提高计算效率。

总之,线程池在需要处理大量并发任务的情况下非常有用,它可以提高系统的性能和响应速度。

6、操作系统中的线性内存?

线性内存是操作系统中的一个概念,指的是程序在运行时在内存中以线性方式组织的地址空间。在物理内存中,存储单元是按照物理地址排列的,但线性地址空间提供了一种抽象,使得程序可以按照一维线性地址空间来访问内存。

线性内存模型的主要特点包括:

  1. 连续性:线性地址空间是连续的,这意味着程序可以访问从地址0开始的连续地址空间。
  2. 单一性:线性地址空间是唯一的,每个程序都有自己的地址空间,它们之间相互隔离。
  3. 对称性:线性地址空间对于程序来说是透明的,程序无法直接访问物理地址,只能通过操作系统提供的内存访问机制来访问。

线性内存模型的实现通常依赖于硬件和操作系统的内存管理功能。例如,在现代计算机中,通过页表机制将线性地址映射到物理地址。操作系统负责维护页表,并提供诸如虚拟内存等高级内存管理功能。

扩展内容:

操作系统中有哪些内存?

计算机的操作系统中涉及到的内存主要有以下几种:

  1. 物理内存:物理内存是系统的物理硬件,即安装在实际电路板上的内存芯片。物理内存被划分成许多相同大小的内存页,其大小取决于CPU的架构和操作系统的配置,一般为4KB。物理内存的使用主要分为以下几方面:内核使用、进程使用、页缓存以及磁盘I/O操作使用的内存。
  2. 虚拟内存:虚拟内存是为了满足物理内存不足而设置的存储管理装置,它将硬盘空间作为内存使用,当程序需要使用内存时,会被虚拟内存管理系统映射到硬盘上的虚拟内存区域,物理内存的不足为程序的运行提供了有效的缓冲。
  3. 高速缓存:高速缓存是一种特殊的存储区域,它由硬件自动管理,用于临时存放CPU近来经常访问的数据和指令。
  4. 只读存储器(ROM):这是一种只能读取而不能写入数据的存储器,是在生产厂家制造时用特殊方法把数据烧入其中的。
  5. 可读写存储器(RAM):这是一种可以读出和写入数据的存储器,其保存的数据在断电后不会消失。其中随机访问存储器(RAM)分为动态(DRAM)和静态(SRAM)两大类。

以上就是操作系统中主要的内存种类,希望能够帮助到您。

7、中断的分类有哪些?

中断可以根据不同的角度进行分类,以下是一些可能的分类方法:

  1. 同步中断和异步中断:同步中断是指只有在指令执行完毕后 CPU 才会发出的中断,也被称为异常(exception)。异步中断则是由其他硬件设备依照 CPU 时钟信号随机产生的中断,例如键盘中断。
  2. 可屏蔽中断和不可屏蔽中断:可屏蔽中断可以被 CPU 屏蔽,即可以被禁止发生。不可屏蔽中断则无法被 CPU 屏蔽。
  3. 中断和异常:根据 Intel 官方资料,同步中断称为异常(exception),异步中断被称为中断(interrupt)。
  4. 软件中断和硬件中断:软件中断是由程序运行时发生的异常事件产生的,例如除以零、非法指令等。硬件中断则是来自硬件设备的中断信号,例如键盘中断
  5. 外中断和内中断:外中断是指由 CPU 外部设备或通道操作正常结束或发生某种错误时发生的中断。内中断则是由 CPU 内部异常事件产生的中断,例如溢出、除法错误等。
  6. 前置中断和后置中断:前置中断是在执行指令前产生的中断,后置中断则是在指令执行结束后产生。
  7. 中断源分类法:根据中断源的不同进行分类,例如输入输出中断、外中断、机器故障中断、程序性中断、访管中断等。

以上分类方法不是全面的,只是部分可能的分类方式。根据具体的场景和需求,还可以进行其他的分类。

8、进程上下文切换的过程是什么?

进程上下文切换的过程如下:

  1. 保存当前进程的硬件上下文(包括PC/PSW/SP和通用寄存器等)。
  2. 修改当前进程的PCB,将其状态改为就绪或者阻塞,并加入相关队列。
  3. 调度下一个进程,修改其PCB,将其状态改为运行。
  4. 切换进程的存储管理数据,将“当前进程”的存储管理数据改为被调度进程的存储管理信息(如页表、TLB)。
  5. 恢复新进程的硬件上下文,让PC指向新进程代码。

以上就是进程上下文切换的过程,这个过程保证了操作系统能够根据优先级等条件对进程进行合理的调度,并且在一个进程出现异常的时候能够及时地恢复到之前的状态。

9、线程的调度策略有哪些?

线程的调度策略有以下几种:

  1. 时间片轮转法:每个线程被分配一个时间片,在该时间片内执行,然后让出CPU,等待下一轮调度。
  2. 优先级调度法:根据线程的优先级高低进行调度。优先级高的线程先于优先级低的线程执行。
  3. 抢占式调度法:在这种策略下,一个正在运行的线程可以被其他线程抢占(preempt)。抢占的依据可以是优先级或者其他调度策略。
  4. 协同程序(Coroutine)调度法:在这种策略下,线程通过协同程序显式地让出CPU,以便其他线程可以执行。

以上就是线程的几种调度策略。需要注意的是,具体的调度策略可能会因操作系统和具体应用场景而异。

10、哪些部分是线程进程共有的,哪些是独有的?

线程和进程在某些方面是共享的,在某些方面是独享的。以下是一些关键的共享和独享方面:

共享的方面:

  1. 进程代码段:进程中的所有线程共享相同的代码段。
  2. 进程的公有数据:线程可以访问和修改进程中的公有数据,这使得线程间可以方便地进行通信。
  3. 进程打开的文件描述符:所有线程共享相同的文件描述符,因此它们可以访问同一个文件或其他资源。
  4. 信号的处理器:所有线程都共享相同的信号处理器,所以它们可以以同样的方式响应信号。
  5. 进程的当前目录:所有线程共享同一个当前目录。
  6. 进程用户ID与进程组ID:所有线程共享相同的用户ID和进程组ID。

独享的方面:

  1. 线程ID:每个线程都有自己的线程ID,这个ID在本进程中是唯一的。
  2. 寄存器组的值:每个线程有自己的寄存器组值,当线程切换时,必须保存和恢复这些值。
  3. 线程的堆栈:每个线程有自己的函数堆栈,这是线程独立执行的关键。
  4. 错误返回码:每个线程有自己的错误返回码,即使多个线程共享相同的函数调用。
  5. 线程的信号屏蔽码:每个线程有自己的信号屏蔽码,以便能够控制哪些信号会被接收。
  6. 线程的优先级:每个线程有自己的优先级,这决定了它在竞争相同资源时获得的执行时间。

总的来说,线程之间共享进程的资源,但拥有各自独立的执行状态和运行线索。

11、利用线程池怎么调度线程

线程池的调度线程主要遵循以下流程:

  1. 当线程池中的核心线程都处于运行状态时,新接收的任务将会被放入等待队列中,直到有核心线程空闲。
  2. 如果等待队列中已经满了,但核心线程都还在运行中,这时新任务将会创建新的线程来处理(非核心线程)。
  3. 当核心线程都已满、等待队列也已满,且还有新任务到来时,新任务将会创建新的线程来处理(非核心线程),直到线程池中的线程总数超过最大线程数。

请注意,具体的调度策略可能会因不同的实现方式而有所不同。

12、页表是什么?

页表是一种特殊的数据结构,用于管理虚拟地址和物理地址之间的映射关系。它是计算机系统内存管理的重要组成部分。在多任务操作系统中,每个进程都拥有自己的虚拟地址空间,而页表则用于将虚拟地址映射到实际的物理地址。

页表通常存放在系统空间的页表区,每个进程都拥有一个自己的页表,页表中的每一项对应着一个物理页帧。当进程需要访问内存中的某个数据时,它会根据页表的映射关系找到对应的物理页帧。

页表的主要作用是实现虚拟内存,使得进程可以使用相同的虚拟地址来访问内存中的不同数据。通过页表的映射关系,操作系统可以将虚拟地址转换为实际的物理地址,从而实现内存的共享和保护。

在页表的设计中,通常包括以下信息:

  1. 有效位(Valid Bit):标识该页是否被加载到内存中。
  2. 修改位(Dirty Bit):标识该页是否被修改过。
  3. 访问位(Access Bit):标识该页是否被访问过。
  4. 保护位(Protection Bit):标识该页的访问权限和保护机制。
  5. 页号(Page Number):标识该页在物理内存中的位置。

通过页表,操作系统可以实现对内存的统一管理和分配,提高内存的利用率和系统的性能。同时,页表也为实现多任务并发执行和保护系统安全提供了基础支持。

13、静态链接和动态链接的区别是什么?

动态链接和静态链接是两种不同的链接方式,主要在链接的执行、代码的加载和占用的资源等方面存在差别。

  1. 链接的执行:
    静态链接在编译程序时就完成了链接
    ,因此生成的可执行文件包含了所有的依赖库。而动态链接则在程序运行时才将所需的库文件加载到内存中
  2. 代码的加载:
    静态链接将所有的库文件都包含在生成的可执行文件中,因此,在加载可执行文件时,操作系统需要将这些代码全部加载到内存中。而动态链接则只在需要时才将库文件加载到内存中。
  3. 占用的资源:
    静态链接由于将所有的库文件都包含在生成的可执行文件中,因此可执行文件的大小会增大,并且需要加载所有的依赖库到内存中,可能会占用更多的内存和磁盘空间。而动态链接则可以共享多个程序使用的库文件,从而减小了可执行文件的大小,节省了内存和磁盘空间。
  4. 更新和重编译:
    在动态链接中,如果库文件有更新,只需要更新库文件本身,而不需要重新编译可执行文件。而在静态链接中,如果库文件有更新,则需要重新编译可执行文件。

总的来说,静态链接和动态链接各有优缺点,需要根据实际情况选择合适的链接方式。

14、动态链接库什么时候进行加载?

动态链接库的加载时间可以在程序运行时进行,具体取决于程序对动态库的调用方式和操作系统对动态库的处理方式。以下是一些可能的情况:

  1. 当程序调用动态库中的函数时,操作系统会将该函数的相关信息从动态库中读取出来,并加载到内存中。
  2. 当程序需要使用动态库中的变量时,操作系统会将该变量的相关数据从动态库中读取出来,并加载到内存中。
  3. 当程序启动时,操作系统可以自动加载动态库,以便程序能够使用其中的函数和变量。
  4. 如果动态库被多个程序同时使用,操作系统可以将该动态库加载到内存中的一个共享区域,以便多个程序可以共享该动态库。

总的来说,动态链接库的加载时间可以在程序运行时的任何时候,具体取决于程序对动态库的调用方式和操作系统对动态库的处理方式。

15、gcc编译各个阶段的产物是什么?

GCC(GNU Compiler Collection)编译一个程序会经历多个阶段,包括预处理(Preprocessing)、编译(Compilation)、汇编(Assembly)和链接(Linking)。各个阶段的产物如下:

  1. 预处理阶段:
    在这个阶段,预处理器(cpp)将源文件中的预处理指令(如#include,#define等)展开,合并文件,生成预处理后的文件。产物通常以.i或.ii为文件扩展名。
  2. 编译阶段:
    编译器(如gcc的cc1)将预处理后的源文件翻译成汇编语言。产物通常以.s为文件扩展名。
  3. 汇编阶段:
    汇编器(如as)将汇编语言代码转换为机器语言代码,生成目标文件(object file)。产物通常以.o或.obj为文件扩展名。
  4. 链接阶段:
    链接器(如ld)将多个目标文件以及必要的库文件链接在一起,生成可执行文件。产物通常以可执行文件的形式存在。

需要注意的是,GCC还可以进行优化、编译调试信息等其他操作,这些操作可能会生成额外的中间产物或最终产物。

16、I/O多路复用的原理是什么?有哪些?

I/O多路复用的原理是,通过一种机制(系统内核缓冲I/O数据),让单个进程可以监视多个文件描述符,一旦某个描述符就绪(一般是读就绪或写就绪),能够通知程序进行相应的读写操作。这种机制可以减少系统开销,提高系统的效率。

I/O多路复用的实现方式主要有三种:select、poll和epoll。

  1. Select函数需要手动遍历fdset,找到就绪的文件描述符。其缺点是当文件描述符较多时,效率较低
  2. Poll函数不需要手动遍历fdset,但是它要求用户提供待监视的文件描述符的集合,并且要求内核对每个文件描述符进行轮询
  3. Epoll函数是I/O多路复用的最佳实现方式,它结合了select和poll的优点,能够在用户空间和内核空间之间实现一个事件驱动的接口使得系统开销更小,效率更高

总的来说,I/O多路复用技术通过单个进程就可以处理多个I/O请求,从而提高了系统的效率和性能。

17、操作系统中锁有哪些  作用是什么?

操作系统中常见的锁有以下几种:

  1. 互斥锁(Mutex Lock):互斥锁是一种常见的线程同步机制,用于保护共享资源在多线程环境下的互斥访问。它提供了两个基本操作:加锁和解锁。互斥锁的原理和实现方式可以有多种,常见的实现包括使用原子操作、互斥变量、硬件指令等。互斥锁的目标是保护共享资源的访问,使得在同一时刻只有一个线程能够访问共享资源,其他线程需要等待。
  2. 读写锁(Read-Write Lock):读写锁是一种更高级的锁,用于区分读操作和写操作。读写锁通常分为共享锁和排他锁,共享锁可以被多个线程同时读取共享资源,而排他锁只能被一个线程独占写操作。读写锁适用于读操作比写操作更频繁的情况,可以提高并发性能。
  3. 自旋锁(Spin Lock):自旋锁是一种特殊的锁,它在等待锁被释放时不会进入阻塞状态,而是让线程在原地等待反复执行忙等待循环。自旋锁适用于短时间内的等待,如果等待时间较长,可能会导致CPU资源的浪费。
  4. 条件变量(Condition Variable):条件变量是一种可以用来等待特定条件的锁,当条件满足时,线程会被唤醒继续执行。条件变量常常和互斥锁或读写锁一起使用,用于实现更复杂的同步操作。
  5. 信号量(Semaphore):信号量是一种用于控制多个线程对共享资源的访问的计数器,它可以有多个等待的线程。信号量的原理是通过加减计数器来控制资源的访问,当计数器为0时,表示没有资源可用,线程需要等待;当计数器大于0时,表示有资源可用,线程可以继续执行。
  6. 递归锁(Recursive Lock):递归锁是一种可以重复获取的锁,同一个线程可以多次获取同一个锁而不会造成死锁。递归锁适用于需要多次获取同一个锁的情况
  7. 屏障(Barrier):屏障是一种用于同步多个线程的机制,它会将所有参与的线程阻塞在一个特定的点,直到所有线程都到达该点后才会释放。屏障常用于需要多个线程协同完成的任务,如并行计算等。

这些锁的主要作用是实现线程间的同步和互斥访问,以避免数据不一致和其他并发问题。在使用时需要根据具体情况选择合适的锁类型。

三次握手的过程可以减少某些情况下的网络通信量,因此它是必要的。如果没有三次握手,那么在客户端与服务器之间就没有办法建立可靠的连接。

18、多线程如何实现同步?

多线程同步是一种协调多个线程访问共享资源的方式,以避免出现数据不一致或其他并发问题。以下是一些实现多线程同步的常见方法:

  1. 互斥锁(Mutex):这是最基本的多线程同步机制。一个线程在获得互斥锁后,其他尝试获取该锁的线程将会被阻塞直到原线程释放该锁。这样可以确保同一时刻只有一个线程可以访问被保护的代码块。
  2. 条件变量(Condition Variables)条件变量允许一个或多个线程等待某个条件的发生当条件满足时,等待的线程将被唤醒。条件变量常与互斥锁配合使用,用于实现更复杂的同步操作。
  3. 读写锁(Read-Write Locks):对于读操作频繁,写操作较少的情况,读写锁是一种有效的同步机制。它允许多个线程同时读取共享资源,但在写入时只允许一个线程进行。这可以提高并发性能,特别是当读操作远多于写操作时。
  4. 信号量(Semaphores):信号量是一种更高级的同步工具,可以控制多个线程对共享资源的访问。它有两种主要类型:二进制信号量和计数信号量。二进制信号量只能取0或1,而计数信号量可以取任意非负整数。
  5. 屏障(Barriers)屏障用于同步多个线程,直到所有线程都到达某个特定点。它常用于并行计算中的同步操作,确保所有线程都完成其任务的一部分后,才能继续执行后续代码。
  6. 原子操作(Atomic Operations):原子操作是一种保证操作原子性的机制,即该操作是不可中断的。原子操作可以用于实现简单的同步操作,例如对共享变量的递增或递减。

以上是一些常用的多线程同步方法,具体选择哪种方法取决于你的应用场景和需求。在设计和实现多线程同步时,要充分考虑性能、可扩展性和可维护性等因素。

19、预处理、编译、汇编、链接

01程序编译过程分析,预处理,编译,汇编,链接 (baidu.com)

1、预处理:

1、删除“#define”并展开所定义的宏

2、处理所有条件预编译指令,如“#if”,“#ifdef”, “#endif”等

3、插入头文件到“#include”处,可以递归方式进行处理

4、删除所有的注释“//”和“/* */”

5、添加行号和文件名标识,以便编译时编译器产生调试用的行号信息

6、保留所有#pragma编译指令(编译器需要用)

经过预编译处理后,得到的是预处理文件(如,hello.i,.i是预处理 C 源代码后产生的中间文件(Intermediate file)) ,它还是一个可读的文本文件 ,但不包含任何宏定义,上面所说的第3条插入头文件到“include”处,可以理解为将头文件里面的内容进行展开。

2、编译

编译过程就是将预处理后得到的预处理文件(如 hello.i)进行词法分析、语法分析、语义分析、优化后,生成汇编代码文件。用来进行编译处理的程序称为编译程序(编译器,Compiler)

经过编译后,得到的汇编代码文件(如 hello.s,即汇编代码文件(Assembly file))还是可读的文本文件,CPU无法理解和执行它,不要着急,我们接下来看下一步汇编命令的执行过程。

3、汇编

首先我们先了解下汇编代码文件(由汇编指令构成)称为汇编语言源程序,其实就是上面编译过程结束之后生成的.s文件,这个文件就是汇编代码文件,改文件是有一条条汇编指令构成,汇编的作用就是讲这一条条汇编指令转换成对应的机器执行。

汇编结果是一个可重定位目标文件(如,hello.o,即目标文件objecit file),其中包含的是不可读的二进制代码,必须用相应的工具软件来查看其内容

4、链接

预处理、编译和汇编三个阶段针对一个模块(一个*.c文件)进行处理,得到对应的一个可重定位目标文件(一个*.o文件),但是在程序的编写过程中,我们都是多个.c文件的,这样经过上面的预处理,编译汇编的过程之后我们得到的也是多个.o(可重定位目标文件),但是我们在最终执行的时候是只有一个可执行文件的,这个过程就是连接的目的了。

链接过程将多个可重定位目标文件合并以生成可执行目标文件,大家可以看下最初时候的图,在最后一步连接的过程中,我们不只是要hell.o而且还需要printf.o(代码中含有printf函数),链接就是将这连个.o合并为一个生成可执行目标文件

 参考:

01程序编译过程分析,预处理,编译,汇编,链接 (baidu.com)

20、描述DMA的作用和内存处理机制

DMA(Direct Memory Access)是一种数据处理机制,它允许设备在不经过CPU的情况下直接与内存进行数据交换。这对于大数据量的传输和避免CPU的瓶颈非常有用,因为这样可以显著提高数据传输的速度,同时使CPU能够执行其他任务。

在内存处理机制中,DMA通常与系统总线相关联。DMA控制器可以连接多个设备,这些设备可以通过DMA控制器直接访问内存。当一个设备需要从外部设备读取数据到内存时,DMA控制器可以提供必要的控制信号,使得外部设备和内存之间可以直接进行数据传输,而不需要CPU的介入。类似地,当内存中的数据需要传输到外部设备时,也可以使用DMA控制器。

这种直接数据传输的方式可以大大提高数据传输的速度,因为DMA控制器可以控制数据的传输速率,而且可以在后台进行,不需要CPU的实时干预。这使得CPU可以专注于执行其他任务,从而提高系统的整体效率。

需要注意的是,虽然DMA可以直接访问内存,但它不能直接访问CPU的寄存器或缓存。因此,在DMA传输开始之前,需要将数据的地址设置到DMA控制器中,并且在传输完成之后进行通知。这也是为什么说DMA是在后台进行的,因为它可以在不需要CPU干预的情况下完成数据传输。

21、什么是安全序列

安全序列(Safe Sequence)是指在系统中的所有进程能够按照某一种次序分配资源,并且依次地运行完毕,这种进程序列{P1,P2,...,Pn}就是安全序列。如果存在这样一个安全序列,则系统是安全的,不会进入死锁状态;否则系统将进入不安全状态。

22、死锁的必要条件有哪些?

死锁的必要条件有四个,分别是:

  1. 互斥条件:
    一个资源每次只能被一个进程使用。
  2. 请求和保持条件:
    一个进程因请求资源而阻塞时,对已获得的资源保持不放。
  3. 不可剥夺条件:
    已经分配的资源,未使用完之前不能强行剥夺。
  4. 环路等待条件:
    若干进程之间形成一种头尾相接的环路,每个进程都在等待下一个进程所占有的资源。

题目:

1、碎片是指()

A.不能被使用的存储区

B.存储分配完后所剩的空闲区

C.未被使用,而又暂时不能使用的存储区

D.没有被使用的存储区

正确答案是C.未被使用,而又暂时不能使用的存储区。碎片是指未被使用,而又暂时不能使用的存储区。

2、在操作系统中引入缓冲的主要原因包括

在操作系统中引入缓冲的主要原因包括以下几个方面:

  1. 改善CPU和IO设备之间的速度匹配,减少双方的不必要等待时间。
  2. 改善CPU和IO设备之间的数据传输速率匹配,实现数据流的平滑传输。
  3. 缓和CPU和IO设备之间的数据传输速率匹配,避免数据丢失或重复。
  4. 起到数据中转站的作用,使数据能够安全、可靠地在CPU和IO设备之间传输。

这些都是缓冲在操作系统中发挥的重要作用。

3、现有一信号量,设定初始值为6,当前值为-3,此类情况表示有()进程在等待。

A4 B6 C5 D3

正确答案是D.3。
信号量是一个表示资源的数量的变量,其值可以在正整数、零和负整数之间变化。当信号量为零时,表示没有进程可以继续执行;当信号量为正整数时,表示有相应数量的资源可用,可以满足当前等待的进程;当信号量为负整数时,表示有相应数量的进程正在等待资源。
在本题中,初始值为6,表示有6个资源可用;当前值为-3,表示有3个进程正在等待资源。因此,答案为D,即有3个进程在等待。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值