操作系统
目录
- 操作系统
- (1) 进程与线程的区别和联系(重点)
- (2)并行和并发的区别
- (3) 进程之间的通信方法有哪几种 (重点)
- (4) 进程调度方法详细介绍
- (5) 进程的执行过程是什么样的,执行一个进程需要做哪些工作?
- (6) 操作系统的内存管理说一下
- (7) 死锁产生的必要条件(怎么检测死锁,解决死锁问题)
- (8) 死锁的恢复
- (9)什么是饥饿
- (10) 如果要你实现一个mutex互斥锁你要怎么实现
- (11)线程之间的通信方式有哪些? 进程与线程同步?
- (12) 什么时候用多进程,什么时候用多线程
- (13) 孤儿进程和僵尸进程分别是什么,怎么形成的?
- (14) 说一下PCB / 说一下进程地址空间
- (15) 内核空间和用户空间是怎样区分的
- (16) 多线程是如何同步的(尤其是如果项目中用到了多线程,很大可能会结合讨论)
- (17) 同一个进程内的线程会共享什么资源?
- (18) 异常和中断的区别
- (19) 一般情况下在Linux/windows平台下栈空间的大小
- (20)虚拟内存的了解/逻辑地址如何映射到物理地址/页面置换算法
- (21)进程的状态以及转换图
- (22)在执行malloc申请内存的时候,操作系统是怎么做的?/内存分配的原理说一下/malloc函数底层是怎么实现的?/进程是怎么分配内存的?
- (23)什么是字节序?怎么判断是大端还是小端?有什么用?
- (24)介绍一下几种典型的锁?
- (25)内核态和用户态(为什么要区分,相互转换)
- (26)进程与线程的切换流程
- (27)页面置换算法有哪些
- (28)线程的分类
- (29)硬和软链接的区别
- (30)什么是分页,什么是分段
- (31)什么是缓冲区溢出,危害是什么
- (32)fork的时候父/子进程的关系
- 什么是进程?
通俗解释:内存中的每一个正在执行的文件,都是一个进程。操作系统会给每一个进程分配很多资源,比如说内存空间,文件描述符,IO的端口号等等
- 什么是线程?
通俗解释:一个程序里面不同的执行路径,就叫做一个线程。
(1) 进程与线程的区别和联系(重点)
区别
- 进程是对运行时程序的封装,是系统进行资源分配和调度的基本单元,而线程是进程的子任务,是CPU分配和调度的基本单元。
- 一个进程可以有多个线程,但是一个线程只能属于一个进程。
- 进程的创建需要系统分配内存和CPU,文件句柄等资源,销毁时也要进行相应的回收,所以进程的管理开销很大;但是线程的管理开销则很小。
- 进程之间不会相互影响;而一个线程崩溃会导致进程崩溃,从而影响同个进程里面的其他线程。
联系
- 线程是存在进程的内部,一个进程中可以有多个线程,一个线程只能存在一个进程中。
1.进程的创建
pid_t fork(void); // 创建一个进程
pid_t getpid(void); // 获取当前进程号(ID)
pid_t getppid(void); // 获取父进程号(ID)
(2)并行和并发的区别
- 并发是指多个线程在一个CPU处理器上执行,使的同一时刻cpu只能执行一个线程,将cpu的运行时间划分为不同的时片分配给不同的线程,每个时间片执行一个线程,其他线程挂起。(同一时刻只能有一条指令执行)
- 并行是指多线程在多个CPU中执行,各个线程互不枪战CPU资源 ,它可以真正意义上实现多个进程同时执行。(多条指令同时进行)
(3) 进程之间的通信方法有哪几种 (重点)
进程之间的通信方式主要有六种,包括管道,信号量,消息队列,信号,共享内存,套接字。
管道:
- 管道是半双工的,双方通信时,需要建立两个管道。
- 管道的实质是一个内核缓冲区,进程以先进先出的方式从缓冲区存取数据:管道一端的进程按序将数据写入缓冲区,另一端的进程则按序读取,
- 该缓冲区可以看做一个循环队列,读和写的位置都是自动增加的,一个数据只能被读一次,读出以后在缓冲区就不复存在了。
- 当缓冲区读空或者写满时,有一定的规则控制读写进程是否进入等待队列,当空的缓冲区有新数据写入或满的缓冲区有数据读出时,就唤醒等待队列中的进程继续读写。管道是最容易实现的
匿名管道pipe和命名管道除了建立,打开,删除的方式不同外,其余都是一样的。匿名管道只允许有亲缘关系的进程之间通信,也就是父子进程之间的通信,命名管道允许具有非亲缘关系的进程间通信。
信号量:
信号量是一个计数器,可以用来控制多个进程对共享资源的访问。信号量只有等待和发送两种操作。等待(P(sv))就是将其值减一或者挂起进程,发送(V(sv))就是将其值加一或者将进程恢复运行。
信号:
信号是Linux系统中用于进程之间通信或操作的一种机制,信号可以在任何时候发送给某一进程,而无须知道该进程的状态。如果该进程并未处于执行状态,则该信号就由内核保存起来,知道该进程恢复执行并传递给他为止。如果一个信号被进程设置为阻塞,则该信号的传递被延迟,直到其阻塞被取消时才被传递给进程。 信号是开销最小的
共享内存:
共享内存允许两个或多个进程共享一个给定的存储区,这一段存储区可以被两个或两个以上的进程映射至自身的地址空间中,就像由malloc()分配的内存一样使用。一个进程写入共享内存的信息,可以被其他使用这个共享内存的进程,通过一个简单的内存读取读出,从而实现了进程间的通信。共享内存的效率最高,缺点是没有提供同步机制,需要使用锁等其他机制进行同步。
消息队列:
消息队列就是一个保存在内核中的消息的链表,用户进程可以向消息队列添加或读取消息。与管道通信相比,其优势是对每个消息指定特定的消息类型,接收的时候不需要按照队列次序,而是可以根据自定义条件接收特定类型的消息。
可以把消息看做一个记录,具有特定的格式以及特定的优先级。对消息队列有写权限的进程可以向消息队列中按照一定的规则添加新消息,有读权限的进程可以从消息队列中读取消息。
套接字:
套接口也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同设备及其间的进程通信。
(4) 进程调度方法详细介绍
先来先服务 (FCFS):按照作业到达任务队列的顺序调度
- FCFS是非抢占式的,易于实现,效率不高,性能不好,有利于长作业(CPU繁忙性)而不利于短作业(I/O繁忙性)
短作业优先 (SJF):每次从队列里选择预计时间最短的作业运行。
- SJF是非抢占式的,优先照顾短作业,具有很好的性能,降低平均等待时间,提高吞吐量。但是不利于长作业,长作业可能一直处于等待状态,出现饥饿现象;完全未考虑作业的优先紧迫程度,不能用于实时系统。
最短剩余时间优先(SRTF):
- 该算法首先按照作业的服务时间挑选最短的作业运行,在该作业运行期间,一旦有新作业到达系统,并且该新作业的服务时间比当前运行作业的剩余服务时间短,则发生抢占;否则,当前作业继续运行。该算法确保一旦新的短作业或短进程进入系统,能够很快得到处理。
时间片轮转
- 用于分时系统的进程调度。基本思想:系统将CPU处理时间划分为若干个时间片(q),进程按照到达先后排列。每次调度选择队首的进程,执行完1个时间片后,计时器发出时钟中断请求,该进程移至队尾。循环操作,该算法能在给定的时间内响应所有用户的请求,达到分时系统的目的。
(5) 进程的执行过程是什么样的,执行一个进程需要做哪些工作?
进程的执行需要经过三大步骤:编译,链接和装入。
编译:
将源代码编译成若干模块
链接:
将编译后的模块和所需要的库函数进行链接。链接包括三种形式:静态链接,装入时动态链接(将编译后的模块在链接时一边链接一边装入),运行时动态链接(在执行时才把需要的模块进行链接)
装入:
将模块装入内存运行
(6) 操作系统的内存管理说一下
操作系统的内存管理包括物理内存管理和虚拟内存管理
- 物理内存管理包括交换与覆盖,分页管理,分段管理和段页式管理等;
- 虚拟内存管理包括虚拟内存的概念,页面置换算法,页面分配策略等;
(面试官这样问的时候,其实是希望你能讲讲虚拟内存)
页面置换算法:最佳置换算法OPT,先进先出置换算法FIFO,时钟Clock置换算法,最近最久未使用算法
(7) 死锁产生的必要条件(怎么检测死锁,解决死锁问题)
(1) 互斥:一个资源每次只能被一个进程使用。
(2) 占有并请求:一个进程因请求资源而阻塞时,对已获得的资源保持不放。
(3) 不可剥夺:进程已获得的资源,在末使用完之前,不能强行剥夺。
(4) 循环等待:若干进程之间形成一种头尾相接的循环等待资源关系。
产生死锁的原因主要是:
(1) 因为系统资源不足。
(2) 进程运行推进的顺序不合适。
(3) 资源分配不当等。
(8) 死锁的恢复
1.重新启动:是最简单、最常用的死锁消除方法,但代价很大,之前所有进程已经完成的工作都将付之东流,不仅包括死锁的全部进程,也包括未参与死锁的全部进程。
2.终止进程:终止参与死锁的进程并回收它们所占资源。
(1) 一次性全部终止;(2) 逐步终止(优先级,代价函数)
3.剥夺资源(resource preemption):
剥夺死锁进程所占有的全部或者部分资源。
(1) 逐步剥夺 (2) 一次剥夺:一次性地剥夺死锁进程所占有的全部资源。
4.进程回退(rollback):
让参与死锁的进程回退到以前没有发生死锁的某个点处,并由此点开始继续执行,希望进程交叉执行时不再发生死锁。但是系统开销很大:
(9)什么是饥饿
饥饿是由于资源分配策略不公引起的,当进程或线程无法访问它所需要的资源而不能继续执行时,就会发生饥饿现象。
(10) 如果要你实现一个mutex互斥锁你要怎么实现
实现mutex最重要的就是实现它的lock()方法和unlock()方法。我们保存一个全局变量flag,flag=1表明该锁已经锁住,flag=0表明锁没有锁住。
实现lock()时,使用一个while循环不断检测flag是否等于1,如果等于1就一直循环。然后将flag设置为1;unlock()方法就将flag置为0;
static int flag=0;
void lock(){
while(TestAndSet(&flag,1)==1);
//flag=1;
}
void unlock(){
flag=0;
}
因为while有可能被重入,所以可以用TestandSet()方法。
int TestAndSet(int *ptr, int new) {
int old = *ptr;
*ptr = new;
return old;
}
(11)线程之间的通信方式有哪些? 进程与线程同步?
线程之间通信:
- 1.锁机制: 互斥锁、读写锁、条件变量
- 2.信号量机制:匿名线程信号和命名线程信号
- 3.信号机制
进程与线程的同步
进程:无名管道、有名管道、信号、共享内存、消息队列、信号量
线程:互斥量、读写锁、线程信号、条件变量
比喻:如果说进程是工厂,那么线程就是工厂里的工人,而工人是要干活的。工厂(进程)给工人(线程)提供了内存空间,让工人(线程)干活。
(12) 什么时候用多进程,什么时候用多线程
优先使用多线程:
- 需要频繁创建和销毁的
- 需要大量计算的
- 任务间相关性比较强
优先使用多进程:
- 任务间相关性弱
- 可能要扩展到多机分布的。
但是实际中更常见的是进程+线程的结合方式
(13) 孤儿进程和僵尸进程分别是什么,怎么形成的?
僵尸进程定义
一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或者waitpid获取子进程的状态信息,那么子进程的进程描述符等一系列信息还会保存在系统中。这种进程称之为僵死进程。
危害:
僵尸进程虽然不占有任何内存空间,但如果父进程不调用 wait() / waitpid() 的话,那么保留的信息就不会释放,其进程号就会一直被占用,而系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。
处理方法:
kill父进程:罪魁祸首是产生出大量僵尸进程的那个父进程。通过 kill 发送 SIGKILL 信号把产生大量僵死进程的那个元凶枪毙掉。元凶死了后,它产生的僵死进程就变成了孤儿进 程,这些孤儿进程会被 init 进程接管,init 进程会 wait() 这些孤儿进程,释放它们在系统进程表中占用的资源。
孤儿进程
- 如果一个进程在回收它的所有子进程之前就被终止了,它的子进程就称为孤儿进程。
- 孤儿进程最终会被init进程(进程号为1)所收养,init进程会wait()这些孤儿进程,释放它们在系统进程表中占用的资源
(14) 说一下PCB / 说一下进程地址空间
PCB就是进程控制块,是操作系统中的一种数据结构,用于表示进程状态,操作系统通过PCB对进程进行管理。
PCB中包含有:进程标识符,处理器状态,进程调度信息,进程控制信息
进程地址空间内有:
- 代码段text:存放程序的二进制代码
- 初始化的数据Data:已经初始化的变量和数据
- 未初始化的数据BSS:还没有初始化的数据
- 栈
- 堆
(15) 内核空间和用户空间是怎样区分的
-
在Linux中虚拟地址空间范围为0到4G,最高的1G地址(0xC0000000到0xFFFFFFFF)供内核使用,称为内核空间
低的3G空间(0x00000000到0xBFFFFFFF)供各个进程使用,就是用户空间。 -
内核空间中存放的是内核代码和数据,而进程的用户空间中存放的是用户程序的代码和数据。
操作系统需要两种CPU的状态:
之所以要进行区分是为了进行权限保护,防止用户的程序乱搞操作系统。如果人人都能随意读写任意地址,那软件的管理就会乱套。
内核态:运行的是操作系统的程序,操作硬件
用户态:运行的是用户的程序
- 用户态——>内核态:唯一的途径是通过中断,异常或主动转换的系统调用
- 内核态——>用户态:通过设置状态字PSW
(16) 多线程是如何同步的(尤其是如果项目中用到了多线程,很大可能会结合讨论)
- 临界区
- 信号量
- 事件
- 互斥量
(17) 同一个进程内的线程会共享什么资源?
- 该进程的地址空间
- 全局变量
- 堆空间
线程的栈空间是自己独有的
(18) 异常和中断的区别
中断和异常都是CPU对系统发生的某个事情做出的一种反应。
区别:中断由外因引起,异常由CPU本身原因引起
(19) 一般情况下在Linux/windows平台下栈空间的大小
在Linux下栈空间通常是8M,Windows下是1M
(20)虚拟内存的了解/逻辑地址如何映射到物理地址/页面置换算法
在运行一个进程的时候,它所需要的内存空间可能大于系统的物理内存容量。通常一个进程会有4G的空间,但是物理内存并没有这么大,所以这些空间都是虚拟内存,它的地址都是逻辑地址,每次在访问的时候都需要映射成物理地址。
当进程访问某个逻辑地址的时候,会去查看页表,如果页表中没有相应的物理地址,说明内存中没有这页的数据,发生缺页异常,这时候进程需要把数据从磁盘拷贝到物理内存中。如果物理内存已经满了,就需要覆盖已有的页,如果这个页曾经被修改过,那么还要把它写回磁盘。
(21)进程的状态以及转换图
五态模型
- 新建态:进程刚刚创建。
- 就绪态:进程已经准备就绪,只等分配到CPU时间片,就可以进行执行
- 执行态:已经分配到CPU时间片,随时可以执行
- 阻塞态:出现等待事件
- 终止态:进程结束
(22)在执行malloc申请内存的时候,操作系统是怎么做的?/内存分配的原理说一下/malloc函数底层是怎么实现的?/进程是怎么分配内存的?
从操作系统层面上看,malloc是通过两个系统调用来实现的: brk和mmap
brk是将进程数据段(.data)的最高地址指针向高处移动,这一步可以扩大进程在运行时的堆大小
mmap是在进程的虚拟地址空间中寻找一块空闲的虚拟内存,这一步可以获得一块可以操作的堆内存。
通常,分配的内存小于128k时,使用brk调用来获得虚拟内存,大于128k时就使用mmap来获得虚拟内存。
进程先通过这两个系统调用获取或者扩大进程的虚拟内存,获得相应的虚拟地址,在访问这些虚拟地址的时候,通过缺页中断,让内核分配相应的物理内存,这样内存分配才算完成。
(23)什么是字节序?怎么判断是大端还是小端?有什么用?
字节序是对象在内存中存储的方式,大端即为最高有效位在前面,小端即为最低有效位在前面。
判断大小端的方法:使用一个union数据结构
union{
short s;
char c[2]; // sizeof(short)=2;
}un;
un.s=0x0102;
if(un.c[0]==1 and un.c[1]==2) cout<<"大端";
if(un.c[0]==2 and un.c[1]==1) cout<<"小端";
(24)介绍一下几种典型的锁?
读写锁
- 多个读者可以同时进行读
- 写者必须互斥(只允许一个写者写,也不能读者写者同时进行)
- 写者优先于读者(一旦有写者,则后续读者必须等待)
互斥锁
互斥锁是一种简单的加锁方法控制对共享资源的访问,一次只能一个线程拥有互斥锁,其他线程只有等待。互斥锁只有两个状态:上锁lock,解锁unlock。
这个过程有点类似于,把打印机放在一个房间里,给这个房间安把锁,这个锁默认是打开的。当 A 需要打印时,他先过来检查这把锁有没有锁着,没有的话就进去,同时上锁在房间里打印。而在这时,刚好 B 也需要打印,B 同样先检查锁,发现锁是锁住的,他就在门外等着。而当 A 打印结束后,他会开锁出来,这时候 B 才进去上锁打印。
自旋锁
如果进线程无法取得锁,进线程不会立刻放弃CPU时间片,而是一直循环尝试获取锁,直到获取为止。如果别的线程长时期占有锁,那么自旋就是在浪费CPU做无用功,但是自旋锁一般应用于加锁时间很短的场景,这个时候效率比较高。
曾经有个经典的例子来比喻自旋锁:A,B两个人合租一套房子,共用一个厕所,那么这个厕所就是共享资源,且在任一时刻最多只能有一个人在使用。当厕所闲置时,谁来了都可以使用,当A使用时,就会关上厕所门,而B也要使用,但是急啊,就得在门外焦急地等待,急得团团转,是为“自旋”,
自旋锁和互斥锁的区别
- 自旋锁不会引起调用者睡眠,所以自旋锁的效率远高于互斥锁。
- 但如果别的线程一直占用着锁,自旋就是在占用CPU做无用功。
- 在用自旋锁时有可能造成死锁,如递归调用时
- 自旋锁适用于使用者保持锁时间较短的情况。
(25)内核态和用户态(为什么要区分,相互转换)
操作系统需要两种CPU的状态:内核态,用户态
区分内核态和用户态的目的: 这样做是为了进行权限保护,限定用户的程序不能乱搞操作系统,如果人人都可以随意读写任意的地址空间,那么软件管理将会乱套。
内核态:运行的是操作系统的程序,操作硬件
用户态:运行的是用户的程序。
用户态——内核态:唯一的方式是通过中断或异常
内核态——用户态:通过设置程序状态字PSW
(26)进程与线程的切换流程
每一个进程都一个PCB,有一个进程a和一个进程b,进程a先去cpu执行,这个运行不是无节制的,当CPU时间片耗尽后,就会通过PCB中的程序计数器找到下一个即将执行的指令b的地址,此时a的执行结果与上下文信息保存在寄存器中。线程切换与其相似
(27)页面置换算法有哪些
1.最佳置换算法(OPT)
2. 先进先出置换算法(FIFO)
3.最近最久未使用(LRU)算法
4. 时钟(CLOCK)置换算法
(28)线程的分类
1.用户线程 2.守护线程 3.主线程
(29)硬和软链接的区别
软链接:
以路径形式存在,类似于快捷方式
可以跨文件系统,对一个不存在的文件名进行链接,可以对目录进行链接
硬链接:
以文件副本形式存在,但不占用实际空间
不允许给目录创建硬链接,只能在同一个文件系统中才能创建
(30)什么是分页,什么是分段
首先它们都是把虚拟内存空间映射到物理地址空间的机制,分页和分段的区别在于粒度,
分页按照固定大小分割,分段按照逻辑功能分割大小不固定,分页地址是一维的,分段地址空间是二维的
(31)什么是缓冲区溢出,危害是什么
缓冲区溢出是指当计算机向缓冲区填充数据时超出了缓冲区本身的容量,溢出的数据覆盖在合法数据上。
原因:造成缓冲区溢出的主要原因是程序中没有仔细检查用户输入。
危害有以下两点:
1、程序崩溃,导致拒绝服务
2、跳转并且执行一段恶意代码
(32)fork的时候父/子进程的关系
除了进程ID不一样外,子进程获得父进程的数据空间、堆和栈的复制,变量的地址也都一样。