面试题11

211 阅读9分钟

本文已参与「新人创作礼」活动,一起开启掘金创作之路。

1. 进程通信中的管道实现原理是什么?

操作系统在内核中开辟一块缓冲区(称为管道)用于通信。管道是一种两个进程间进行单向通信的机制。因为这种单向性,管道又被称为半双工管道,所以它的使用是有一定局限性的。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。管道分为无名管道和命名管道,无名管道只能用于具有亲缘关系进程间直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件,管道本质上是一种文件;命名管道可以允许无亲缘关系进程间的通信。

管道原型如下:
#include <unistd.h>

int pipe(int fd[2);

pipe()函数创建的管道处于一个进程的中间,因此一个进程在由pipe()创建管道后,一般再使用fork()建立一个子进程,然后通过管道实现父子进程间的通信。管道两端可分别用于描述字fd[0]以及fd[1]来描述。注意管道的两端的任务是固定的,即一端只能用于读,由描述字fd[0]来表示,称其为管道读端。另一端则只能用于写,由描述字fd[1]来表示,称其为管道写端。如果试图从管道写端读取数据或者向管道读端写入数据都会发生错误。一般文件的I/O函数都可以用于管道,如close()、read()、write()等。

具体步骤如下: (1)父进程调用pipe()开辟管道,得到两个文件描述符指向管道的两端;

(2)父进程调用fork()创建子进程,那么子进程也有两个文件描述符指向同一管道;

(3)父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道使用环形队列实现,数据从写端流入,从读端流出,从而实现进程间的通信。

2. 简述mmap的原理和使用场景

mmap是一种内存映射文件的方法,即将一个文件或者它对象映射到进程的地址空间,实现文件磁盘地址和进程虚拟地址空间中一段虚拟地址的一一对应关系。实现这样的映射关系后,进程就可以采用指针的方式读写操作这一段内存,而系统会自动回写脏页面到对应的文件磁盘上,即完成了对文件的操控而不必再调用read,write等系统调用函数。相反,内核空间对这段区域的修改也直接反应用户空间,从而可以实现不同进程间的文件共享。 在这里插入图片描述 使用场景: (1)对同一块区域频繁进行读写操作; (2)可用于实现用户空间和内核空间的高效交互; (3)可提供进程间共享内存以及相互通信; (4)可实现搞笑的大规模数据传输。

3. 互斥量能不能再进程中使用?

能。在不同的进程间,存在着资源竞争或并发使用的问题,所以需要互斥量。

进程中也需要互斥量,因为一个进程中可以包含多个线程,线程与线程之间需要通过互斥的手段进行同步,避免导致共享数据修改引起冲突。可以使用互斥锁,属于互斥量的一种。

4. 协程是轻量级线程,轻量级表现在哪里?

(1)协程调用与切换比x线程效率高:协程执行效率极高。写成不需要多线程的锁机制,可以不加锁的访问全局变量,所以上下文切换非常快。

(2)协程占用内存少:执行协程只需要极少的栈内存(大概4~5KB),而默认情况下,线程栈的内存大小为1MB。

(3)切换开销更少:协程直接操作栈,基本没有内核切换的开销,所以切换开销比线程少。

5. 常见信号有哪几种,含义是什么?

在这里插入图片描述

6. 说说线程间通信的方式有哪些?

线程的通信方式包括临界区,互斥量、信号量、条件变读、读写锁。 (1)临界区:每个线程中访问临界资源的那段代码称为临界区。(临界资源是一次仅允许一个线程使用的共享资源)。每次只允许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,索格线程必须互斥地对它仅从访问。

(2)互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以被访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。

(3)信号量:计数器,允许多个线程同时访问同一个资源。

(4)条件变量:通过条件变量通知操作的方式来保持多线程同步。

(5)读写锁:读写锁与互斥量相似。但互斥量要么就是加锁状态,要么就是不加锁状态。而读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率要比互斥锁要高。

7. 说说线程同步的方式有哪些?

线程间的同步方式包括互斥锁、信号量、条件变量、读写锁。

(1)互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以被访问。又因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。

(2)信号量:计数器,允许多个线程同时访问同一个资源。

(3)条件变量:通过条件变量通知操作的方式来保持多线程同步。

(4)读写锁:读写锁和互斥量相似,但是互斥量要么是加锁状态,要么是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。

8. 什么是死锁,产生的条件,如何解决?

(1)死锁

是指多个进程在执行过程中,因争夺资源而造成了互相等待,此时系统产生了死锁。比如两只羊过独木桥,如果两只羊互不相让,争着过桥,就会产生死锁。

(2)产生的条件,死锁发生有四个必要条件:

1.互斥条件:进程间所分配的资源不允许其他进程访问,若其他进程访问,只能等待,知道进程使用完后释放该资源;

2.请求保持条件:进程获得一定资源后,又对其他资源发出请求,但该资源被其他进程占有,此时请求阻塞,而且该进程不会释放自己已经占有的资源;

3.不可剥夺条件:进程已经获得的资源,只能自己释放,不可剥夺;

4.环路等待条件:若干进程之间形成了一种头尾相接的循环等待资源关系。

(3)如何解决:

1.资源一次性分配,从而解决请求保持的问题

2.可剥夺资源:当进程新的资源未得到满足时,释放已有的资源;

3.资源有序分配:资源按序号递增,进程请求按递增请求,释放则按递减请求。

(4)举个例子

举个例子,比如:如果此时有两个线程T1和T2,它们分别占有R1和R2资源

此时,T1请求R2资源的同时,T2请求R1资源。

这个时候T2说:你把R1给我,我就给你R2

T1说:不行,你要先给我R2,我才能给你R1

那么就这样,死锁产生了。如下图: 在这里插入图片描述

9. 有了进程,为什么还要有线程?

1.原因

进程在早期的多任务操作系统中时基本的执行单元。每次进程切换,都要先保存进程资源然后再回复,这称为上下文切换。但是进程频繁切换会引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务重用粒度更小的执行单元来实现并发执行,这就是线程。

2.线程与进程对比

(1)进程间的信息难以共享。由于除去只读代码外,父子进程并未共享内存,因此必须采用一些进程间的通信方式(管道、消息队列、信号量、信号、共享内存、socket套接字),在进程间进行信息交换。

但多个线程共享进程的内存,如代码段、数据段、扩展段,线程间的信息交换十分方便。

(2)调用fork()来创建进程的代价相对要求较高,即便利用写时复制技术,仍然需要赋值诸如内存页表和文件描述符表之类的多种进程属性,这意味着fork()调用在时间上的开销依然不菲。

但创建线程比创建进程通常要快十倍甚至更多。线程间是共享虚拟地址空间的,无需采用写时复制技术来复制内存,也不用复制页表。

10. 在单核机器上写多线程程序,是否要考虑加锁,为什么?

在单核机器上写多线程程序,仍然需要线程锁。

原因:因为线程锁通常用来实现线程的同步和通信。在单核机器上的多线程程序,仍然存在线程同步的问题。因为抢占式操作系统中,通常为每个线程分配一个时间片,当某个县城时间片耗尽时,操作系统会将其挂起,然后运行另一个线程。如果这两个线程共享某些数据,在不使用线程锁的前提下,可能会导致共享数据修改引起冲突。