面试汇总(五):操作系统常见面试总结(一):进程与线程的相关知识点

时间:2022-07-25
本文章向大家介绍面试汇总(五):操作系统常见面试总结(一):进程与线程的相关知识点,主要内容包括其使用实例、应用技巧、基本知识点总结和需要注意事项,具有一定的参考价值,需要的朋友可以参考一下。

前言

  前面的两篇文章介绍了计算机网络在面试中的一些常见的问题。今天我们给大家介绍在面试中,操作系统常见的面试题。操作系统在计算机行业中是一门最基础的技术,无论是在开发项目还是在算法岗,我们都是基于计算机上进行的,我们对操作系统的了解体现我们从事计算机相关岗位的资深素质,因此,接下来,这篇文章给大家介绍在面试中常见的操作系统的知识点。当然,开发和算法岗对计算机网络的要求程度不同,相对而言,开发对操作系统的要求其实更高一些。但是一些基础、核心、常见的问题要求我们要掌握。

面试题及参考答案

1、请你说一下进程与线程的概念,以及为什么要有进程线程,其中有什么区别,他们各自又是怎么同步的?

  • 基本概念: 进程是对运行时程序的封装,是系统进行资源调度和分配的的基本单位,实现了操作系统的并发; 线程是进程的子任务,是CPU调度和分派的基本单位,用于保证程序的实时性,实现进程内部的并发;线程是操作系统可识别的最小执行和调度单位。每个线程都独自占用一个虚拟处理器:独自的寄存器组,指令计数器和处理器状态。每个线程完成不同的任务,但是共享同一地址空间(也就是同样的动态内存,映射文件,目标代码等等),打开的文件队列和其他内核资源。
  • 区别: 1.一个线程只能属于一个进程,而一个进程可以有多个线程,但至少有一个线程。线程依赖于进程而存在。 2.进程在执行过程中拥有独立的内存单元,而多个线程共享进程的内存。(资源分配给进程,同一进程的所有线程共享该进程的所有资源。同一进程中的多个线程共享代码段(代码和常量),数据段(全局变量和静态变量),扩展段(堆存储)。但是每个线程拥有自己的栈段,栈段又叫运行时段,用来存放所有局部变量和临时变量。) 3.进程是资源分配的最小单位,线程是CPU调度的最小单位; 4.系统开销: 由于在创建或撤消进程时,系统都要为之分配或回收资源,如内存空间、I/o设备等。因此,操作系统所付出的开销将显著地大于在创建或撤消线程时的开销。类似地,在进行进程切换时,涉及到整个当前进程CPU环境的保存以及新被调度运行的进程的CPU环境的设置。而线程切换只须保存和设置少量寄存器的内容,并不涉及存储器管理方面的操作。可见,进程切换的开销也远大于线程切换的开销。 5.通信:由于同一进程中的多个线程具有相同的地址空间,致使它们之间的同步和通信的实现,也变得比较容易。进程间通信IPC,线程间可以直接读写进程数据段(如全局变量)来进行通信——需要进程同步和互斥手段的辅助,以保证数据的一致性。在有的系统中,线程的切换、同步和通信都无须操作系统内核的干预 6.进程编程调试简单可靠性高,但是创建销毁开销大;线程正相反,开销小,切换速度快,但是编程调试相对复杂。 7.进程间不会相互影响 ;线程一个线程挂掉将导致整个进程挂掉 8.进程适应于多核、多机分布;线程适用于多核
  • 进程间通信的方式:   进程间通信主要包括管道、系统IPC(包括消息队列、信号量、信号、共享内存等)、以及套接字socket。 1.管道:   管道主要包括无名管道和命名管道:管道可用于具有亲缘关系的父子进程间的通信,有名管道除了具有管道所具有的功能外,它还允许无亲缘关系进程间的通信。 1.1 普通管道PIPE: 1)它是半双工的(即数据只能在一个方向上流动),具有固定的读端和写端 2)它只能用于具有亲缘关系的进程之间的通信(也是父子进程或者兄弟进程之间) 3)它可以看成是一种特殊的文件,对于它的读写也可以使用普通的read、write等函数。但是它不是普通的文件,并不属于其他任何文件系统,并且只存在于内存中。 1.2 命名管道FIFO: 1)FIFO可以在无关的进程之间交换数据 2)FIFO有路径名与之相关联,它以一种特殊设备文件形式存在于文件系统中。 2. 系统IPC: 2.1 消息队列   消息队列,是消息的链接表,存放在内核中。一个消息队列由一个标识符(即队列ID)来标记。 (消息队列克服了信号传递信息少,管道只能承载无格式字节流以及缓冲区大小受限等特点)具有写权限得进程可以按照一定得规则向消息队列中添加新信息;对消息队列有读权限得进程则可以从消息队列中读取信息; 特点: 1)消息队列是面向记录的,其中的消息具有特定的格式以及特定的优先级。 2)消息队列独立于发送与接收进程。进程终止时,消息队列及其内容并不会被删除。 3)消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取。 2.2 信号量semaphore   信号量(semaphore)与已经介绍过的 IPC 结构不同,它是一个计数器,可以用来控制多个进程对共享资源的访问。信号量用于实现进程间的互斥与同步,而不是用于存储进程间通信数据。 特点: 1)信号量用于进程间同步,若要在进程间传递数据需要结合共享内存。 2)信号量基于操作系统的 PV 操作,程序对信号量的操作都是原子操作。 3)每次对信号量的 PV 操作不仅限于对信号量值加 1 或减 1,而且可以加减任意正整数。 4)支持信号量组。 2.3 信号signal 信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生。 2.4 共享内存(Shared Memory)   它使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据得更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。 特点: 1)共享内存是最快的一种IPC,因为进程是直接对内存进行存取 2)因为多个进程可以同时操作,所以需要进行同步 3)信号量+共享内存通常结合在一起使用,信号量用来同步对共享内存的访问
  • 3.套接字SOCKET:   socket也是一种进程间通信机制,与其他通信机制不同的是,它可用于不同主机之间的进程通信。 线程间通信的方式: 临界区: 通过多线程的串行化来访问公共资源或一段代码,速度快,适合控制数据访问; 互斥量Synchronized/Lock: 采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问 信号量Semphare: 为控制具有有限数量的用户资源而设计的,它允许多个线程在同一时刻去访问同一个资源,但一般需要限制同一时刻访问此资源的最大线程数目。 事件(信号),Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操作

2、请你回答一下fork和vfork的区别

  • fork的基础知识: fork:创建一个和当前进程映像一样的进程可以通过fork( )系统调用:
#include <sys/types.h>
#include <unistd.h>
pid_t fork(void);

  成功调用fork( )会创建一个新的进程,它几乎与调用fork( )的进程一模一样,这两个进程都会继续运行。在子进程中,成功的fork( )调用会返回0。在父进程中fork( )返回子进程的pid。如果出现错误,fork( )返回一个负值。   最常见的fork( )用法是创建一个新的进程,然后使用exec( )载入二进制映像,替换当前进程的映像。这种情况下,派生(fork)了新的进程,而这个子进程会执行一个新的二进制可执行文件的映像。这种“派生加执行”的方式是很常见的。   在早期的Unix系统中,创建进程比较原始。当调用fork时,内核会把所有的内部数据结构复制一份,复制进程的页表项,然后把父进程的地址空间中的内容逐页的复制到子进程的地址空间中。但从内核角度来说,逐页的复制方式是十分耗时的。现代的Unix系统采取了更多的优化,例如Linux,采用了写时复制的方法,而不是对父进程空间进程整体复制。

  • vfork的基础知识:    在实现写时复制之前,Unix的设计者们就一直很关注在fork后立刻执行exec所造成的地址空间的浪费。BSD的开发者们在3.0的BSD系统中引入了vfork( )系统调用。
#include <sys/types.h>
#include <unistd.h>
pid_t vfork(void);

   除了子进程必须要立刻执行一次对exec的系统调用,或者调用_exit( )退出,对vfork( )的成功调用所产生的结果和fork( )是一样的。vfork( )会挂起父进程直到子进程终止或者运行了一个新的可执行文件的映像。通过这样的方式,vfork( )避免了地址空间的按页复制。在这个过程中,父进程和子进程共享相同的地址空间和页表项。实际上vfork( )只完成了一件事:复制内部的内核数据结构。因此,子进程也就不能修改地址空间中的任何内存。    vfork( )是一个历史遗留产物,Linux本不应该实现它。需要注意的是,即使增加了写时复制,vfork( )也要比fork( )快,因为它没有进行页表项的复制。然而,写时复制的出现减少了对于替换fork( )争论。实际上,直到2.2.0内核,vfork( )只是一个封装过的fork( )。因为对vfork( )的需求要小于fork( ),所以vfork( )的这种实现方式是可行的。

  • 写时复制   Linux采用了写时复制的方法,以减少fork时对父进程空间进程整体复制带来的开销。   写时复制是一种采取了惰性优化方法来避免复制时的系统开销。它的前提很简单:如果有多个进程要读取它们自己的那部门资源的副本,那么复制是不必要的。每个进程只要保存一个指向这个资源的指针就可以了。只要没有进程要去修改自己的“副本”,就存在着这样的幻觉:每个进程好像独占那个资源。从而就避免了复制带来的负担。如果一个进程要修改自己的那份资源“副本”,那么就会复制那份资源,并把复制的那份提供给进程。不过其中的复制对进程来说是透明的。这个进程就可以修改复制后的资源了,同时其他的进程仍然共享那份没有修改过的资源。所以这就是名称的由来:在写入时进行复制。   写时复制的主要好处在于:如果进程从来就不需要修改资源,则不需要进行复制。惰性算法的好处就在于它们尽量推迟代价高昂的操作,直到必要的时刻才会去执行。   在使用虚拟内存的情况下,写时复制(Copy-On-Write)是以页为基础进行的。所以,只要进程不修改它全部的地址空间,那么就不必复制整个地址空间。在fork( )调用结束后,父进程和子进程都相信它们有一个自己的地址空间,但实际上它们共享父进程的原始页,接下来这些页又可以被其他的父进程或子进程共享。   写时复制在内核中的实现非常简单。与内核页相关的数据结构可以被标记为只读和写时复制。如果有进程试图修改一个页,就会产生一个缺页中断。内核处理缺页中断的方式就是对该页进行一次透明复制。这时会清除页面的COW属性,表示着它不再被共享。   现代的计算机系统结构中都在内存管理单元(MMU)提供了硬件级别的写时复制支持,所以实现是很容易的。   在调用fork( )时,写时复制是有很大优势的。因为大量的fork之后都会跟着执行exec,那么复制整个父进程地址空间中的内容到子进程的地址空间完全是在浪费时间:如果子进程立刻执行一个新的二进制可执行文件的映像,它先前的地址空间就会被交换出去。写时复制可以对这种情况进行优化。
  • fork和vfork的区别:
  • fork( )的子进程拷贝父进程的数据段和代码段;vfork( )的子进程与父进程共享数据段
  • fork( )的父子进程的执行次序不确定;vfork( )保证子进程先运行,在调用exec或exit之前与父进程数据是共享的,在它调用exec或exit之后父进程才可能被调度运行。
  • vfork( )保证子进程先运行,在它调用exec或exit之后父进程才可能被调度运行。如果在调用这两个函数之前子进程依赖于父进程的进一步动作,则会导致死锁。 4.当需要改变共享数据段中变量的值,则拷贝父进程。

3、请你说一说并发(concurrency)和并行(parallelism)

并发(concurrency): 指宏观上看起来两个程序在同时运行,比如说在单核cpu上的多任务。但是从微观上看两个程序的指令是交织着运行的,你的指令之间穿插着我的指令,我的指令之间穿插着你的,在单个周期内只运行了一个指令。这种并发并不能提高计算机的性能,只能提高效率。 并行(parallelism): 指严格物理意义上的同时运行,比如多核cpu,两个程序分别运行在两个核上,两者之间互不影响,单个周期内每个程序都运行了自己的指令,也就是运行了两条指令。这样说来并行的确提高了计算机的效率。所以现在的cpu都是往多核方面发展。

4、请你说一说有了进程,为什么还要有线程?

  • 线程产生的原因:   进程可以使多个程序能并发执行,以提高资源的利用率和系统的吞吐量;但是其具有一些缺点: 进程在同一时间只能干一件事 进程在执行的过程中如果阻塞,整个进程就会挂起,即使进程中有些工作不依赖于等待的资源,仍然不会执行。   因此,操作系统引入了比进程粒度更小的线程,作为并发执行的基本单位,从而减少程序在并发执行时所付出的时空开销,提高并发性。和进程相比,线程的优势如下: 从资源上来讲, 线程是一种非常"节俭"的多任务操作方式。在linux系统下,启动一个新的进程必须分配给它独立的地址空间,建立众多的数据表来维护它的代码段、堆栈段和数据段,这是一种"昂贵"的多任务工作方式。 从切换效率上来讲, 运行于一个进程中的多个线程,它们之间使用相同的地址空间,而且线程间彼此切换所需时间也远远小于进程间切换所需要的时间。据统计,一个进程的开销大约是一个线程开销的30倍左右。 从通信机制上来讲, 线程间方便的通信机制。对不同进程来说,它们具有独立的数据空间,要进行数据的传递只能通过进程间通信的方式进行,这种方式不仅费时,而且很不方便。线程则不然,由于同一进城下的线程之间贡献数据空间,所以一个线程的数据可以直接为其他线程所用,这不仅快捷,而且方便。   除以上优点外,多线程程序作为一种多任务、并发的工作方式,还有如下优点: 1、使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时,不同的线程运行于不同的CPU上。 2、改善程序结构。一个既长又复杂的进程可以考虑分为多个线程,成为几个独立或半独立的运行部分,这样的程序才会利于理解和修改。

5、请问单核机器上写多线程程序,是否需要考虑加锁,为什么?

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

6、 请问线程需要保存哪些上下文,SP、PC、EAX这些寄存器是干嘛用的?

  线程在切换的过程中需要保存当前线程Id、线程状态、堆栈、寄存器状态等信息。其中寄存器主要包括SP PC EAX等寄存器,其主要功能如下: SP: 堆栈指针,指向当前栈的栈顶地址 PC: 程序计数器,存储下一条将要执行的指令 EAX: 累加寄存器,用于加法乘法的缺省寄存器

7、请你说一说线程间的同步方式,最好说出具体的系统调用

  • 信号量:   信号量是一种特殊的变量,可用于线程同步。它只取自然数值,并且只支持两种操作: P(SV): 如果信号量SV大于0,将它减一;如果SV值为0,则挂起该线程。 V(SV): 如果有其他进程因为等待SV而挂起,则唤醒,然后将SV+1;否则直接将SV+1。 其系统调用为:   sem_wait(sem_t *sem):以原子操作的方式将信号量减1,如果信号量值为0,则sem_wait将被阻塞,直到这个信号量具有非0值。   sem_post(sem_t *sem):以原子操作将信号量值+1。当信号量大于0时,其他正在调用sem_wait等待信号量的线程将被唤醒。
  • 互斥量:   互斥量又称互斥锁,主要用于线程互斥,不能保证按序访问,可以和条件锁一起实现同步。当进入临界区 时,需要获得互斥锁并且加锁;当离开临界区时,需要对互斥锁解锁,以唤醒其他等待该互斥锁的线程。其主要的系统调用如下: pthread_mutex_init: 初始化互斥锁 pthread_mutex_destroy: 销毁互斥锁 pthread_mutex_lock: 以原子操作的方式给一个互斥锁加锁,如果目标互斥锁已经被上锁,pthread_mutex_lock调用将阻塞,直到该互斥锁的占有者将其解锁。 pthread_mutex_unlock: 以一个原子操作的方式给一个互斥锁解锁。
  • 条件变量:   条件变量,又称条件锁,用于在线程之间同步共享数据的值。条件变量提供一种线程间通信机制:当某个共享数据达到某个值时,唤醒等待这个共享数据的一个/多个线程。即,当某个共享变量等于某个值时,调用 signal/broadcast。此时操作共享变量时需要加锁。其主要的系统调用如下: pthread_cond_init: 初始化条件变量 pthread_cond_destroy: 销毁条件变量 pthread_cond_signal: 唤醒一个等待目标条件变量的线程。哪个线程被唤醒取决于调度策略和优先级。 pthread_cond_wait: 等待目标条件变量。需要一个加锁的互斥锁确保操作的原子性。该函数中在进入wait状态前首先进行解锁,然后接收到信号后会再加锁,保证该线程对共享资源正确访问。

8、请你说一下多线程和多进程的不同,以及使用场景

   进程是资源分配的最小单位,而线程时CPU调度的最小单位。多线程之间共享同一个进程的地址空间,线程间通信简单,同步复杂,线程创建、销毁和切换简单,速度快,占用内存少,适用于多核分布式系统,但是线程间会相互影响,一个线程意外终止会导致同一个进程的其他线程也终止,程序可靠性弱。而多进程间拥有各自独立的运行地址空间,进程间不会相互影响,程序可靠性强,但是进程创建、销毁和切换复杂,速度慢,占用内存多,进程间通信复杂,但是同步简单,适用于多核、多机分布。    多进程模型的优势是CPU:多线程模型主要优势为线程间切换代价较小,因此适用于I/O密集型的工作场景,因此I/O密集型的工作场景经常会由于I/O阻塞导致频繁的切换线程。同时,多线程模型也适用于单机多核分布式场景。    多进程模型,适用于CPU密集型。同时,多进程模型也适用于多机分布式场景中,易于多机扩展。

9、游戏服务器应该为每个用户开辟一个线程还是一个进程,为什么?

   游戏服务器应该为每个用户开辟一个进程。因为同一进程间的线程会相互影响,一个线程死掉会影响其他线程,从而导致进程崩溃。因此为了保证不同用户之间不会相互影响,应该为每个用户开辟一个进程

10、请你说一说死锁发生的条件以及如何解决死锁

   死锁是指两个或两个以上进程在执行过程中,因争夺资源而造成的下相互等待的现象。死锁发生的四个必要条件如下: 互斥条件: 进程对所分配到的资源不允许其他进程访问,若其他进程访问该资源,只能等待,直至占有该资源的进程使用完成后释放该资源; 请求和保持条件: 进程获得一定的资源后,又对其他资源发出请求,但是该资源可能被其他进程占有,此时请求阻塞,但该进程不会释放自己已经占有的资源 不可剥夺条件:进程已获得的资源,在未完成使用之前,不可被剥夺,只能在使用后自己释放 环路等待条件: 进程发生死锁后,必然存在一个进程-资源之间的环形链    解决死锁的方法即破坏上述四个条件之一,主要方法如下:    资源一次性分配,从而剥夺请求和保持条件 可剥夺资源:即当进程新的资源未得到满足时,释放已占有的资源,从而破坏不可剥夺的条件 资源有序分配法: 系统给每类资源赋予一个序号,每个进程按编号递增的请求资源,释放则相反,从而破坏环路等待的条件

11、请你说一说进程状态转换图,动态就绪,静态就绪,动态阻塞,静态阻塞

  • 1、进程的五种基本状态:

1)创建状态:进程正在被创建

2)就绪状态:进程被加入到就绪队列中等待CPU调度运行

3)执行状态:进程正在被运行

4)等待阻塞状态:进程因为某种原因,比如等待I/O,等待设备,而暂时不能运行。

5)终止状态:进程运行完毕

  • 2、交换技术   当多个进程竞争内存资源时,会造成内存资源紧张,并且,如果此时没有就绪进程,处理机会空闲,I/0速度比处理机速度慢得多,可能出现全部进程阻塞等待I/O。   针对以上问题,提出了两种解决方法: 1)交换技术:换出一部分进程到外存,腾出内存空间。 2)虚拟存储技术:每个进程只能装入一部分程序和数据。   在交换技术上,将内存暂时不能运行的进程,或者暂时不用的数据和程序,换出到外存,来腾出足够的内存空间,把已经具备运行条件的进程,或进程所需的数据和程序换入到内存。   从而出现了进程的挂起状态:进程被交换到外存,进程状态就成为了挂起状态。
  • 3、活动阻塞,静止阻塞,活动就绪,静止就绪 1)活动阻塞:进程在内存,但是由于某种原因被阻塞了。 2)静止阻塞:进程在外存,同时被某种原因阻塞了。 3)活动就绪:进程在内存,处于就绪状态,只要给CPU和调度就可以直接运行。 4)静止就绪:进程在外存,处于就绪状态,只要调度到内存,给CPU和调度就可以运行。 从而出现了:
  • 活动就绪 —— 静止就绪 (内存不够,调到外存)
  • 活动阻塞 —— 静止阻塞 (内存不够,调到外存)
  • 执行 —— 静止就绪 (时间片用完)

12、死循环+来连接时新建线程的方法效率有点低,怎么改进?

  提前创建好一个线程池,用生产者消费者模型,创建一个任务队列,队列作为临界资源,有了新连接,就挂在到任务队列上,队列为空所有线程睡眠。改进死循环:使用select epoll这样的技术

13、怎么唤醒被阻塞的socket线程?怎样确定当前线程是繁忙还是阻塞?请问就绪状态的进程在等待什么?两个进程访问临界区资源,会不会出现都获得自旋锁的情况?

给阻塞时候缺少的资源; 使用ps命令查看 被调度使用cpu的运行权 单核cpu,并且开了抢占可以造成这种情况。

14、请你来说一说协程

  • 1、概念   协程,又称微线程,纤程,英文名Coroutine。协程看上去也是子程序,但执行过程中,在子程序内部可中断,然后转而执行别的子程序,在适当的时候再返回来接着执行。   由协程运行结果可能是12x3yz。在执行A的过程中,可以随时中断,去执行B,B也可能在执行过程中中断再去执行A。但协程的特点在于是一个线程执行。
  • 2、协程和线程区别   那和多线程比,协程最大的优势就是协程极高的执行效率。因为子程序切换不是线程切换,而是由程序自身控制,因此,没有线程切换的开销,和多线程比,线程数量越多,协程的性能优势就越明显。   第二大优势就是不需要多线程的锁机制,因为只有一个线程,也不存在同时写变量冲突,在协程中控制共享资源不加锁,只需要判断状态就好了,所以执行效率比多线程高很多。   在协程上利用多核CPU呢——多进程+协程,既充分利用多核,又充分发挥协程的高效率,可获得极高的性能。   Python对协程的支持还非常有限,用在generator中的yield可以一定程度上实现协程。虽然支持不完全,但已经可以发挥相当大的威力了。

15、请你说一下僵尸进程

  • 1)正常进程   正常情况下,子进程是通过父进程创建的,子进程再创建新的进程。子进程的结束和父进程的运行是一个异步过程,即父进程永远无法预测子进程到底什么时候结束。 当一个进程完成它的工作终止之后,它的父进程需要调用wait()或者waitpid()系统调用取得子进程的终止状态。   unix提供了一种机制可以保证只要父进程想知道子进程结束时的状态信息, 就可以得到:在每个进程退出的时候,内核释放该进程所有的资源,包括打开的文件,占用的内存等。 但是仍然为其保留一定的信息,直到父进程通过wait / waitpid来取时才释放。保存信息包括: 1进程号the process ID 2退出状态the termination status of the process 3运行时间the amount of CPU time taken by the process等
  • 2)孤儿进程   一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。孤儿进程将被init进程(进程号为1)所收养,并由init进程对它们完成状态收集工作。
  • 3)僵尸进程   一个进程使用fork创建子进程,如果子进程退出,而父进程并没有调用wait或waitpid获取子进程的状态信息,那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。   僵尸进程是一个进程必然会经过的过程:这是每个子进程在结束时都要经过的阶段。   如果子进程在exit()之后,父进程没有来得及处理,这时用ps命令就能看到子进程的状态是“Z”。如果父进程能及时 处理,可能用ps命令就来不及看到子进程的僵尸状态,但这并不等于子进程不经过僵尸状态。   如果父进程在子进程结束之前退出,则子进程将由init接管。init将会以父进程的身份对僵尸状态的子进程进行处理。 危害:   如果进程不调用wait / waitpid的话, 那么保留的那段信息就不会释放,其进程号就会一直被占用,但是系统所能使用的进程号是有限的,如果大量的产生僵死进程,将因为没有可用的进程号而导致系统不能产生新的进程。 外部消灭:   通过kill发送SIGTERM或者SIGKILL信号消灭产生僵尸进程的进程,它产生的僵死进程就变成了孤儿进程,这些孤儿进程会被init进程接管,init进程会wait()这些孤儿进程,释放它们占用的系统进程表中的资源 内部解决: 1、子进程退出时向父进程发送SIGCHILD信号,父进程处理SIGCHILD信号。在信号处理函数中调用wait进行处理僵尸进程。 2、fork两次,原理是将子进程成为孤儿进程,从而其的父进程变为init进程,通过init进程可以处理僵尸进程。

16、server端监听端口,但还没有客户端连接进来,此时进程处于什么状态?请问如何设计server,使得能够接收多个客户端的请求?

   这个需要看服务端的编程模型,如果如上一个问题的回答描述的这样,则处于阻塞状态,如果使用了epoll,select等这样的io复用情况下,处于运行状态。   多线程,线程池,io复用

17、请问怎么实现线程池

1.设置一个生产者消费者队列,作为临界资源 2.初始化n个线程,并让其运行起来,加锁去队列取任务运行 3.当任务队列为空的时候,所有线程阻塞 4.当生产者队列来了一个任务后,先对队列加锁,把任务挂在到队列上,然后使用条件变量去通知阻塞中的一个线程

18、请解释一下,LINUX下的线程,GDI类

   LINUX实现的就是基于核心轻量级进程的”一对一”线程模型,一个线程实体对应一个核心轻量级进程,而线程之间的管理在核外函数库中实现。    GDI类为图像设备编程接口类库。

19、系统线程数量上限是多少?

  Linux 系统中单个进程的最大线程数有其最大的限制 PTHREAD_THREADS_MAX。   这个限制可以在/usr/include/bits/local_lim.h中查看 ,对 linuxthreads这个值一般是 1024,对于 nptl 则没有硬性的限制,仅仅受限于系统的资源。   这个系统的资源主要就是线程的stack 所占用的内存,用 ulimit -s 可以查看默认的线程栈大小,一般情况下,这个值是8M=8192KB。

20、线程的那些资源共享,那些资源不共享?

  • 共享的资源有 a. 堆 由于堆是在进程空间中开辟出来的,所以它是理所当然地被共享的;因此new出来的都是共享的(16位平台上分全局堆和局部堆,局部堆是独享的) b. 全局变量 它是与具体某一函数无关的,所以也与特定线程无关;因此也是共享的 c. 静态变量虽然对于局部变量来说,它在代码中是“放”在某一函数中的,但是其存放位置和全局变量一样,存于堆中开辟的.bss和.data段,是共享的 d. 文件等公用资源 这个是共享的,使用这些公共资源的线程必须同步。Win32 提供了几种同步资源的方式,包括信号、临界区、事件和互斥体。
  • 独享的资源有 a. 栈 栈是独享的 b. 寄存器 这个可能会误解,因为电脑的寄存器是物理的,每个线程去取值难道不一样吗?其实线程里存放的是副本,包括程序计数器PC

21、请你说一下线程之间通信的手段

  • 使用全局变量   主要由于多个线程可能更改全局变量,因此全局变量最好声明为volatile
  • 使用消息实现通信   在Windows程序设计中,每一个线程都可以拥有自己的消息队列(UI线程默认自带消息队列和消息循环,工作线程需要手动实现消息循环),因此可以采用消息进行线程间通信sendMessage,postMessage。
  • 使用事件CEvent类实现线程间通信   Event对象有两种状态:有信号和无信号,线程可以监视处于有信号状态的事件,以便在适当的时候执行对事件的操作。

总结

  由于操作系统面试的内容较多,因此本文以及接下来的两篇文章的对面试中常见的操作系统问题进行了简单的总结,一方面是为了方便自己以后面试的复习,另外也是给大家再次面试相关岗位的时候提供复习方向以及思路解答。这里就需要我们对操作系统有一个较为深层次的理解。于是,我们在准备的时候,首先就应该夯实基础,只有这样才能在众多的面试者中脱颖而出。另外,作为在计算机行业工作的从事者,掌握一些基础的操作系统的知识是很有必要的,也是我们的基本素养。最后希望大家不断进步,都能尽早拿到自己比较满意的offer!!!!继续加油,未来可期!!!!