操作系统相关面试问题总结
文章目录
- 操作系统相关面试问题总结
- 1. 并发和并行分别是什么,有什么区别?
- 2. 进程的几种状态是什么?
- 3. 软链接和硬链接的区别
- 4. 静态库和动态库怎么制作及如何使用,区别是什么
- 5. 什么是大端小端,如何判断大端小端?
- 6. 简述Linux系统态与用户态,什么时候会进入系统态?
- 7. 一个进程占多大内存
- 8. 一个线程占多大内存?
- 9. 操作系统最大支持多大内存
- 10. 什么是页表,为什么要有?
- 11. 简述操作系统中的缺页中断
- 12. 进程通信中的管道实现原理是什么?
- 13. 说说进程通信的方式有哪些?
- 14. 说说进程同步的方式?
- 15. 说说线程间通信的方式有哪些?
- 16. 说说线程同步方式有哪些?
- 17. 有了进程,为什么还要有线程?
- 19. 进程、线程的中断切换的过程是怎样的?
- 20. 说说什么是死锁,产生的条件,如何解决?
1. 并发和并行分别是什么,有什么区别?
并发是指一个处理器同时处理多个任务,在单核中同一时刻只有一个进程获得CPU,虽然宏观上你认为多个进程都在进行。
并行是指多个处理器或者是多核的处理器同时处理多个不同的任务。
并发是逻辑上的同时发生,而并行是物理上的同时发生
2. 进程的几种状态是什么?
-
运行
running
态:进程占有处理器正在运行的状态。进程已获得CPU
,其程序正在执行。在单处理机系统中,只有一个进程处于执行状态; 在多处理机系统中,则有多个进程处于执行状态。 -
就绪
ready
态:进程具备运行条件,等待系统分配处理器以便运行的状态。当进程已分配到除CPU
以外的所有必要资源后,只要再获得CPU
,便可立即执行,进程这时的状态称为就绪状态。在一个系统中处于就绪状态的进程可能有多个,通常将它们排成一个队列,称为就绪队列。 -
等待
wait
态:又称阻塞态或睡眠态,指进程不具备运行条件,正在等待某个时间完成的状态。也称为等待或睡眠状态,一个进程正在等待某一事件发生(例如请求I/O
而等待I/O
完成等)而暂时停止运行,这时即使把处理机分配给进程也无法运行,故称该进程处于阻塞状态。 -
新建态:对应于进程被创建时的状态,尚未进入就绪队列。创建一个进程需要通过两个步骤:
-
- 为新进程分配所需要的资源和建立必要的管理信息。
- 设置该进程为就绪态,并等待被调度执行。
-
-
终止态:指进程完成任务到达正常结束点,或出现无法克服的错误而异常终止,或被操作系统及有终止权的进程所终止时所处的状态。处于终止态的进程不再被调度执行,下一步将被系统撤销,最终从系统中消失。终止一个进程需要两个步骤:
-
- 先对操作系统或相关的进程进行善后处理(如抽取信息)
- 然后回收占用的资源并被系统删除
-
-
挂起就绪态:进程具备运行条件,但目前在外存中,只有它被对换到内存才能被调度执行。
-
挂起等待态:表明进程正在等待某一个事件发生且在外存中。
3. 软链接和硬链接的区别
参考回答
-
定义不同
软链接又叫符号链接,这个文件包含了另一个文件的路径名。可以是任意文件或目录,可以链接不同文件系统的文件。
硬链接就是一个文件的一个或多个文件名。把文件名和计算机文件系统使用的节点号链接起来。因此我们可以用多个文件名与同一个文件进行链接,这些文件名可以在同一目录或不同目录。
-
限制不同
硬链接只能对已存在的文件进行创建,不能交叉文件系统进行硬链接的创建;
软链接可对不存在的文件或目录创建软链接;可交叉文件系统;
-
创建方式不同
硬链接不能对目录进行创建,只可对文件创建;
软链接可对文件或目录创建;
-
影响不同
删除一个硬链接文件并不影响其他有相同
inode
号的文件。删除软链接并不影响被指向的文件,但若被指向的原文件被删除,则相关软连接被称为死链接(即 dangling link,若被指向路径文件被重新创建,死链接可恢复为正常的软链接)。
4. 静态库和动态库怎么制作及如何使用,区别是什么
参考回答
静态库的制作:
gcc hello.c -c //这样就生成hello.o目标文件
ar rcs libhello.a hello.o//生成libhello.a静态库
静态库的使用:
gcc main.c -lhello -o staticLibrary //main.c和hello静态库链接,生成staticLibrary执行文件
/*
* main.c:是指main主函数
* -lhello:是我们生成的.a 文件砍头去尾(lib不要 .a也不要)前面加-l
* -L:是指告诉gcc编译器先从-L指定的路径去找静态库,默认是从/usr/lib/ 或者 /usr/local/lib/去找
* ./:是指当前路径的意思
* staticLibrary:是最后想生成的文件名(这里可随意起名字)
*/
动态库的制作:
gcc -shared -fpic hello.c -o libhello.so
/*
* -shared 指定生成动态库
* -fpic :fPIC选项作用于编译阶段,在生成目标文件时就得使用该选项,以生成位置无关的代码。
*/
动态库的使用:
gcc main.c -lhello -L ./ -o dynamicDepot
/*
* main.c:是指main主函数
* -lhello:是我们生成的.so 文件砍头去尾(lib不要 .so也不要)前面加-l
* -L:是指告诉gcc编译器先从-L指定的路径去找静态库,默认是从/usr/lib/ 或者 /usr/local/lib/ 去找
* ./:是指当前路径的意思
* cdynamicDepot:是最后想生成的文件名(这里可随意起名字)
*/
区别:
- 静态库代码装载的速度快,执行速度略比动态库快。
- 动态库更加节省内存,可执行文件体积比静态库小很多。
- 静态库是在编译时加载,动态库是在运行时加载。
- 生成的静态链接库,
Windows
下以.lib
为后缀,Linux
下以.a
为后缀。生成的动态链接库,Windows
下以.dll
为后缀,Linux
下以.so
为后缀
5. 什么是大端小端,如何判断大端小端?
参考回答
小端模式:低的有效字节存储在低的存储器地址。小端一般为主机字节序;常用的X86
结构是小端模式。很多的ARM
,DSP
都为小端模式。
大端模式:高的有效字节存储在低的存储器地址。大端为网络字节序;KEIL C51
则为大端模式。有些ARM
处理器还可以由硬件来选择是大端模式还是小端模式。
如何判断:我们可以根据联合体来判断系统是大端还是小端。因为联合体变量总是从低地址存储。
int fun1(){
union test{
char c;
int i;
};
test t; t.i = 1;
//如果是大端,则t.c为0x00,则t.c != 1,反之是小端
return (t.c == 1);
}
答案解析
-
在进行网络通信时是否需要进行字节序转换?
相同字节序的平台在进行网络通信时可以不进行字节序转换,但是跨平台进行网络数据通信时必须进行字节序转换。
原因如下:网络协议规定接收到得第一个字节是高字节,存放到低地址,所以发送时会首先去低地址取数据的高字节。小端模式的多字节数据在存放时,低地址存放的是低字节,而被发送方网络协议函数发送时会首先去低地址取数据(想要取高字节,真正取得是低字节),接收方网络协议函数接收时会将接收到的第一个字节存放到低地址(想要接收高字节,真正接收的是低字节),所以最后双方都正确的收发了数据。而相同平台进行通信时,如果双方都进行转换最后虽然能够正确收发数据,但是所做的转换是没有意义的,造成资源的浪费。而不同平台进行通信时必须进行转换,不转换会造成错误的收发数据,字节序转换函数会根据当前平台的存储模式做出相应正确的转换,如果当前平台是大端,则直接返回不进行转换,如果当前平台是小端,会将接收到得网络字节序进行转换。
-
网络字节序
网络上传输的数据都是字节流,对于一个多字节数值,在进行网络传输的时候,先传递哪个字节?也就是说,当接收端收到第一个字节的时候,它将这个字节作为高位字节还是低位字节处理,是一个比较有意义的问题;
UDP/TCP/IP
协议规定:把接收到的第一个字节当作高位字节看待,这就要求发送端发送的第一个字节是高位字节;而在发送端发送数据时,发送的第一个字节是该数值在内存中的起始地址处对应的那个字节,也就是说,该数值在内存中的起始地址处对应的那个字节就是要发送的第一个高位字节(即:高位字节存放在低地址处);由此可见,多字节数值在发送之前,在内存中因该是以大端法存放的; 所以说,网络字节序是大端字节序; 比如,我们经过网络发送整型数值0x12345678
时,在80X86
平台中,它是以小端发存放的,在发送之前需要使用系统提供的字节序转换函数htonl()
将其转换成大端法存放的数值;
6. 简述Linux系统态与用户态,什么时候会进入系统态?
参考回答
- 内核态与用户态:内核态(系统态)与用户态是操作系统的两种运行级别。内核态拥有最高权限,可以访问所有系统指令;用户态则只能访问一部分指令。
- 什么时候进入内核态:共有三种方式:a、系统调用。b、异常。c、设备中断。其中,系统调用是主动的,另外两种是被动的。
- 为什么区分内核态与用户态:在CPU的所有指令中,有一些指令是非常危险的,如果错用,将导致整个系统崩溃。比如:清内存、设置时钟等。所以区分内核态与用户态主要是出于安全的考虑。
7. 一个进程占多大内存
8. 一个线程占多大内存?
参考回答
一个linux
的线程大概占8M
内存。
答案解析
linux
的栈是通过缺页来分配内存的,不是所有栈地址空间都分配了内存。因此,8M
是最大消耗,实际的内存消耗只会略大于实际需要的内存(内部损耗,每个在4k
以内)。
9. 操作系统最大支持多大内存
参考回答
32
位的Linux
操作系统最大寻址空间是4GB
,64
位系统最大支持256TB
内存大小
答案解析
32
位的Linux
操作系统最大寻址范围是2^32
换算后为4GB
64
位的Linux
操作系统只有48
位总线用于寻址,最大寻址范围是2^48
换算后为256TB
10. 什么是页表,为什么要有?
参考回答
页表是虚拟内存的概念。操作系统虚拟内存到物理内存的映射表,就被称为页表。
原因:不可能每一个虚拟内存的 Byte
都对应到物理内存的地址。这张表将大得真正的物理地址也放不下,于是操作系统引入了页(Page)的概念。进行分页,这样可以减小虚拟内存页对应物理内存页的映射表大小。
答案解析
如果将每一个虚拟内存的 Byte
都对应到物理内存的地址,每个条目最少需要 8
字节(32
位虚拟地址->32位物理地址
),在 4G
内存的情况下,就需要 32GB
的空间来存放对照表,那么这张表就大得真正的物理地址也放不下了,于是操作系统引入了页(Page)的概念。
在系统启动时,操作系统将整个物理内存以 4K
为单位,划分为各个页。之后进行内存分配时,都以页为单位,那么虚拟内存页对应物理内存页的映射表就大大减小了,4G
内存,只需要 8M
的映射表即可,一些进程没有使用到的虚拟内存,也并不需要保存映射关系,而且Linux
还为大内存设计了多级页表,可以进一页减少了内存消耗。
11. 简述操作系统中的缺页中断
参考回答
- 缺页异常:
malloc
和mmap
函数在分配内存时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常,引发缺页中断。 - 缺页中断:缺页异常后将产生一个缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。
答案解析
两者区别。
缺页中断与一般中断一样,需要经历四个步骤:保护CPU现场、分析中断原因、转入缺页中断处理程序、恢复CPU现场,继续执行。 缺页中断与一般中断区别: (1)在指令执行期间产生和处理缺页中断信号
(2)一条指令在执行期间,可能产生多次缺页中断
(3)缺页中断返回的是执行产生中断的一条指令,而一般中断返回的是执行下一条指令。
12.说说虚拟内存分布,什么时候会由用户态陷入内核态?
参考回答
-
虚拟内存分布:
-
用户空间:
(1)**代码段.text:**存放程序执行代码的一块内存区域。只读,代码段的头部还会包含一些只读的常数变量。
(2)**数据段.data:**存放程序中已初始化的全局变量和静态变量的一块内存区域。
(3)BSS 段.bss:存放程序中未初始化的全局变量和静态变量的一块内存区域。
(4)可执行程序在运行时又会多出两个区域:堆区和栈区。
**堆区:**动态申请内存用。堆从低地址向高地址增长。
**栈区:**存储局部变量、函数参数值。栈从高地址向低地址增长。是一块连续的空间。
(5)最后还有一个共享区,位于堆和栈之间。
内核空间:DMA区、常规区、高位区。
- 什么时候进入内核态:共有三种方式:a、系统调用。b、异常。c、设备中断。其中,系统调用是主动的,另外两种是被动的。
12. 进程通信中的管道实现原理是什么?
参考回答
操作系统在内核中开辟一块缓冲区(称为管道)用于通信。管道是一种两个进程间进行单向通信的机制。因为这种单向性,管道又称为半双工管道,所以其使用是有一定的局限性的。半双工是指数据只能由一个进程流向另一个进程(一个管道负责读,一个管道负责写);如果是全双工通信,需要建立两个管道。管道分为无名管道和命名管道,无名管道只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件,管道本质是一种文件;命名管道可以允许无亲缘关系进程间的通信。
管道原型如下:
#include <unistd.h> int pipe(int fd[2]);
pipe()
函数创建的管道处于一个进程中间,因此一个进程在由 pipe()
创建管道后,一般再使用fork() 建立一个子进程,然后通过管道实现父子进程间的通信。管道两端可分别用描述字fd[0]
以及fd[1]
来描述。注意管道的两端的任务是固定的,即一端只能用于读,由描述字fd[0]
表示,称其为管道读端;另 一端则只能用于写,由描述字fd[1]
来表示,称其为管道写端。如果试图从管道写端读取数据,或者向管道读端写入数据都将发生错误。一般文件的 I/O
函数都可以用于管道,如close()
、read()
、write()
等。
具体步骤如下:
- 父进程调用pipe开辟管道,得到两个文件描述符指向管道的两端。
- 父进程调用fork创建子进程,那么子进程也有两个文件描述符指向同一管道。
- 父进程关闭管道读端,子进程关闭管道写端。父进程可以往管道里写,子进程可以从管道里读,管道是用环形队列实现的,数据从写端流入从读端流出,这样就实现了进程间通信。
参考代码
#include<unistd.h>
#include<stdio.h>
#include<stdlib.h>
#include<string.h>
#define INPUT 0
#define OUTPUT 1
int main()
{
//创建管道
int fd[2];
pipe(fd);
//创建子进程
pid_t pid = fork();
if (pid < 0) {
printf("fork error!\n");
exit(-1);
} else if (pid == 0 ) {//执行子进程
printf("Child process is starting...\n");
//子进程向父进程写数据,关闭管道的读端
close(fd[INPUT]);
write(fd[OUTPUT], "hello douya!", strlen("hello douya!"));
exit(0);
} else {//执行父进程
printf ("Parent process is starting......\n");
//父进程从管道读取子进程写的数据 ,关闭管道的写端
close(fd[OUTPUT]);
char buf[255];
int output = read(fd[INPUT], buf, sizeof(buf));
printf("%d bytes of data from child process: %s\n", output, buf);
}
return 0;
}
13. 说说进程通信的方式有哪些?
参考回答
进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存)、套接字socket。
-
管道:包括无名管道和命名管道,无名管道半双工,只能用于具有亲缘关系的进程直接的通信(父子进程或者兄弟进程),可以看作一种特殊的文件;命名管道可以允许无亲缘关系进程间的通信。
-
系统IPC
消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。
信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。
信号:用于通知接收进程某个事件的发生。
内存共享:使多个进程访问同一块内存空间。
-
套接字socket:用于不同主机直接的通信。
14. 说说进程同步的方式?
参考回答
- 信号量semaphore:是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步。
P操作
(递减操作)可以用于阻塞一个进程,V操作
(增加操作)可以用于解除阻塞一个进程。 - 管道:一个进程通过调用管程的一个过程进入管程。在任何时候,只能有一个进程在管程中执行,调用管程的任何其他进程都被阻塞,以等待管程可用。
- 消息队列:消息的链接表,放在内核中。消息队列独立于发送与接收进程,进程终止时,消息队列及其内容并不会被删除;消息队列可以实现消息的随机查询,可以按照消息的类型读取。
15. 说说线程间通信的方式有哪些?
参考回答
线程间的通信方式包括临界区、互斥量、信号量、条件变量、读写锁:
- 临界区:每个线程中访问临界资源的那段代码称为临界区(Critical Section)(临界资源是一次仅允许一个线程使用的共享资源)。每次只准许一个线程进入临界区,进入后不允许其他线程进入。不论是硬件临界资源,还是软件临界资源,多个线程必须互斥地对它进行访问。
- 互斥量:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
- 信号量:计数器,允许多个线程同时访问同一个资源。
- 条件变量:通过条件变量通知操作的方式来保持多线程同步。
- 读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
16. 说说线程同步方式有哪些?
参考回答
线程间的同步方式包括互斥锁、信号量、条件变量、读写锁:
- 互斥锁:采用互斥对象机制,只有拥有互斥对象的线程才可以访问。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。
- 信号量:计数器,允许多个线程同时访问同一个资源。
- 条件变量:通过条件变量通知操作的方式来保持多线程同步。
- 读写锁:读写锁与互斥量类似。但互斥量要么是锁住状态,要么就是不加锁状态。读写锁一次只允许一个线程写,但允许一次多个线程读,这样效率就比互斥锁要高。
17. 有了进程,为什么还要有线程?
参考回答
-
原因
进程在早期的多任务操作系统中是基本的执行单元。每次进程切换,都要先保存进程资源然后再恢复,这称为上下文切换。但是进程频繁切换将引起额外开销,从而严重影响系统的性能。为了减少进程切换的开销,人们把两个任务放到一个进程中,每个任务用一个更小粒度的执行单元来实现并发执行,这就是线程。
-
线程与进程对比
(1)**进程间的信息难以共享。**由于除去只读代码段外,父子进程并未共享内存,因此必须采用一些进程间通信方式,在进程间进行信息交换。
但多个线程共享进程的内存,如代码段、数据段、扩展段,线程间进行信息交换十分方便。
(2)调用 fork() 来创建进程的代价相对较高,即便利用写时复制技术,仍然需要复制诸如内存页表和文件描述符表之类的多种进程属性,这意味着 fork() 调用在时间上的开销依然不菲。
**但创建线程比创建进程通常要快 10 倍甚至更多。**线程间是共享虚拟地址空间的,无需采用写时复制来复制内存,也无需复制页表。
参考回答
-
概念:信号量本质上是一个计数器,用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
-
原理:由于信号量只能进行两种操作等待和发送信号,即P(sv)和V(sv),具体的行为如下:
(1)P(sv)操作:如果sv的值大于零,就给它减1;如果它的值为零,就挂起该进程的执行(信号量的值为正,进程获得该资源的使用权,进程将信号量减1,表示它使用了一个资源单位)。
(2)V(sv)操作:如果有其他进程因等待sv而被挂起,就让它恢复运行,如果没有进程因等待sv而挂起,就给它加1(若此时信号量的值为0,则进程进入挂起状态,直到信号量的值大于0,若进程被唤醒则返回至第一步)。
-
作用:用于多进程对共享数据对象的读取,它主要是用来保护共享资源(信号量也属于临界资源),使得资源在一个时刻只有一个进程独享。
19. 进程、线程的中断切换的过程是怎样的?
参考回答
上下文切换指的是内核(操作系统的核心)在CPU上对进程或者线程进行切换。
-
进程上下文切换
(1)保护被中断进程的处理器现场信息
(2)修改被中断进程的进程控制块有关信息,如进程状态等
(3)把被中断进程的进程控制块加入有关队列
(4)选择下一个占有处理器运行的进程
(5)根据被选中进程设置操作系统用到的地址转换和存储保护信息
切换页目录以使用新的地址空间
切换内核栈和硬件上下文(包括分配的内存,数据段,堆栈段等)
(6)根据被选中进程恢复处理器现场
-
线程上下文切换
(1)保护被中断线程的处理器现场信息
(2)修改被中断线程的线程控制块有关信息,如线程状态等
(3)把被中断线程的线程控制块加入有关队列
(4)选择下一个占有处理器运行的线程
(5)根据被选中线程设置操作系统用到的存储保护信息
切换内核栈和硬件上下文(切换堆栈,以及各寄存器)
(6)根据被选中线程恢复处理器现场
20. 说说什么是死锁,产生的条件,如何解决?
参考回答
-
死锁: 是指多个进程在执行过程中,因争夺资源而造成了互相等待。此时系统产生了死锁。比如两只羊过独木桥,若两只羊互不相让,争着过桥,就产生死锁。
-
产生的条件:死锁发生有四个必要条件: (1)互斥条件:进程对所分配到的资源不允许其他进程访问,若其他进程访问,只能等待,直到进程使用完成后释放该资源;
(2)请求保持条件:进程获得一定资源后,又对其他资源发出请求,但该资源被其他进程占有,此时请求阻塞,而且该进程不会释放自己已经占有的资源;
(3)不可剥夺条件:进程已获得的资源,只能自己释放,不可剥夺;
(4)环路等待条件:若干进程之间形成一种头尾相接的循环等待资源关系。
-
如何解决:
(1)资源一次性分配,从而解决请求保持的问题
(2)可剥夺资源:当进程新的资源未得到满足时,释放已有的资源;
(3)资源有序分配:资源按序号递增,进程请求按递增请求,释放则相反。
答案解析
举个例子,比如:如果此时有两个线程T1和T2,它们分别占有R1和R2资源
此时,T1请求R2资源的同时,T2请求R1资源。
这个时候T2说:你把R1给我,我就给你R2
T1说:不行,你要先给我R2,我才能给你R1
那么就这样,死锁产生了。如下图: