面试题-网络汇总

Posted by lichao modified on June 22, 2022

什么是僵尸进程?

一个子进程结束后,它的父进程并没有等待它(调用wait或者waitpid),那么这个子进程将成为一个僵尸进程。僵尸进程是一个已经死亡的进程,但是并没有真正被销毁。它已经放弃了几乎所有内存空间,没有任何可执行代码,也不能被调度,仅仅在进程表中保留一个位置,记载该进程的进程ID、终止状态以及资源利用信息(CPU时间,内存使用量等等)供父进程收集,除此之外,僵尸进程不再占有任何内存空间。这个僵尸进程可能会一直留在系统中直到系统重启。 危害:占用进程号,而系统所能使用的进程号是有限的;占用内存。

以下情况不会产生僵尸进程:

该进程的父进程先结束了。每个进程结束的时候,系统都会扫描是否存在子进程,如果有则用Init进程接管,成为该进程的父进程,并且会调用wait等待其结束。 父进程调用wait或者waitpid等待子进程结束(需要每隔一段时间查询子进程是否结束)。wait系统调用会使父进程暂停执行,直到它的一个子进程结束为止。waitpid则可以加入WNOHANG(wait-no-hang)选项,如果没有发现结束的子进程,就会立即返回,不会将调用waitpid的进程阻塞。同时,waitpid还可以选择是等待任一子进程(同wait),还是等待指定pid的子进程,还是等待同一进程组下的任一子进程,还是等待组ID等于pid的任一子进程; 子进程结束时,系统会产生SIGCHLD(signal-child)信号,可以注册一个信号处理函数,在该函数中调用waitpid,等待所有结束的子进程(注意:一般都需要循环调用waitpid,因为在信号处理函数开始执行之前,可能已经有多个子进程结束了,而信号处理函数只执行一次,所以要循环调用将所有结束的子进程回收); 也可以用signal(SIGCLD, SIG_IGN)(signal-ignore)通知内核,表示忽略SIGCHLD信号,那么子进程结束后,内核会进行回收。

内存分配方式

  1. 从静态存储区域分配。内存在程序编译的时候就已经分配好,这块内存在程序的整个运行期间都存在。例如全局变量,static变量。
  2. 在栈上创建。在执行函数时,函数内局部变量的存储单元都可以在栈上创建,函数执行结束时这些存储单元自动被释放。栈内存分配运算内置于处理器的指令集中,效率很高,但是分配的内存容量有限。
  3. 从堆上分配,亦称动态内存分配。程序在运行的时候用malloc或new申请任意多少的内存,程序员自己负责在何时用free或delete释放内存。动态内存的生存期由我们决定,使用非常灵活,但问题也最多。

线程同步有哪些方式?

1
为什么需要线程同步:线程有时候会和其他线程共享一些资源,比如内存、数据库等。当多个线程同时读写同一份共享资源的时候,可能会发生冲突。因此需要线程的同步,多个线程按顺序访问资源。
  • 互斥量 Mutex:互斥量是内核对象,只有拥有互斥对象的线程才有访问互斥资源的权限。因为互斥对象只有一个,所以可以保证互斥资源不会被多个线程同时访问;当前拥有互斥对象的线程处理完任务后必须将互斥对象交出,以便其他线程访问该资源;

  • 信号量 Semaphore:信号量是内核对象,它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量。信号量对象保存了最大资源计数和当前可用资源计数,每增加一个线程对共享资源的访问,当前可用资源计数就减1,只要当前可用资源计数大于0,就可以发出信号量信号,如果为0,则将线程放入一个队列中等待。线程处理完共享资源后,应在离开的同时通过ReleaseSemaphore函数将当前可用资源数加1。如果信号量的取值只能为0或1,那么信号量就成为了互斥量;

  • 事件 Event:允许一个线程在处理完一个任务后,主动唤醒另外一个线程执行任务。事件分为手动重置事件和自动重置事件。手动重置事件被设置为激发状态后,会唤醒所有等待的线程,而且一直保持为激发状态,直到程序重新把它设置为未激发状态。自动重置事件被设置为激发状态后,会唤醒一个等待中的线程,然后自动恢复为未激发状态。

  • 临界区 Critical Section:任意时刻只允许一个线程对临界资源进行访问。拥有临界区对象的线程可以访问该临界资源,其它试图访问该资源的线程将被挂起,直到临界区对象被释放。

进程和线程的联系和区分

  • 区别和联系
    • 线程是CPU调度的基本单位,同一个进程中可以有多个线程,但至少有一个线程
    • 进程是操作系统分配资源的最小单位
  • 一个进程中有哪些数据段
    • 代码段(Text Segment):保存代码
    • 静态数据段(Data Segment):静态数据
    • 未初始化数据段(BSS)
    • 栈(Stack):向下增长
    • 堆(Heap):向上增长
    • .rodata
  • 同一个进程下的不同线程间有哪些数据段是共享的?
    • 除Stack外均共享

协程与线程进行比较?

  • 一个线程可以拥有多个协程,一个进程也可以单独拥有多个协程,这样 python 中则能使用多核CPU。
  • 线程进程都是同步机制,而协程则是异步
  • 协程能保留上一次调用时的状态,每次过程重入时,就相当于进入上一次调用的状态;

进程间通信有哪些方式?

  • 管道(Pipe):
    • 管道是半双工的,数据只能向一个方向流动;需要双方通信时,需要建立起两个管道;
    • 一个进程向管道中写的内容被管道另一端的进程读出。写入的内容每次都添加在管道缓冲区的末尾,并且每次都是从缓冲区的头部读出数据;
    • 只能用于父子进程或者兄弟进程之间(具有亲缘关系的进程)
  • 命名管道(FIFO):命名管道也是半双工的通信方式,但是它允许无亲缘关系的进程间的通信。
  • 消息队列:消息队列是由消息的链表,存放在内核中并由消息队列标识符标识。消息队列克服了信号传递信息少、管道只能承载无格式字节流以及缓存区大小受限等缺点。
  • 信号(Signal):信号是一种比较复杂的通信方式,用于通知接受进程某个事件已经发生。
  • 共享内存:共享内存就是映射一段能被其他进程所访问的内存,这段共享内存由一个进程创建,但多个进程都可以访问。共享内存是最快的 IPC 方式,它是针对其他进程间通信方式运行效率低而专门设计的。它往往与其他通信机制,如信号量配合使用,来实现进程间的同步和通信。
  • 信号量(semophore):信号量是一个计数器,可以用来控制多个进程对共享资源的访问。它常作为一种锁机制,防止某进程正在访问共享资源时,其他进程也访问该资源。因此,主要作为进程间以及同一进程内不同线程之间的同步手段。
  • 套接字(Socket):套接字也是一种进程间通信的机制,与其他通信机制不同的是,它可用于不同及其间的进程通信。

了解更多……

线程独占哪些资源?

  • 线程ID
  • 一组寄存器的值
  • 线程自身的栈(堆是共享的)
  • 错误返回码:线程可能会产生不同的错误返回码,一个线程的错误返回码不应该被其它线程修改;
  • 信号掩码/信号屏蔽字(Signal mask):表示是否屏蔽/阻塞相应的信号(SIGKILL,SIGSTOP除外)

进程有哪几种状态?

  • 就绪状态:进程已获得除处理机以外的所需资源,等待分配处理机资源
  • 运行状态:占用处理机资源运行,处于此状态的进程数小于等于CPU数
  • 阻塞状态: 进程等待某种条件,在条件满足之前无法执行

什么是用户态和内核态?

为什么要分用户态和内核态?

  • 安全性:防止用户程序恶意或者不小心破坏系统/内存/硬件资源;
  • 封装性:用户程序不需要实现更加底层的代码;
  • 利于调度:如果多个用户程序都在等待键盘输入,这时就需要进行调度;统一交给操作系统调度更加方便。

为了限制不同程序的访问能力,防止一些程序访问其它程序的内存数据,CPU 划分了用户态和内核态两个权限等级:

  • 用户态只能受限地访问内存,且不允许访问外围设备,没有占用 CPU 的能力,CPU 资源可以被其它程序获取;
  • 内核态可以访问内存所有数据以及外围设备,也可以进行程序的切换。

所有用户程序都运行在用户态,但有时需要进行一些内核态的操作,比如从硬盘或者键盘读数据,这时就需要进行系统调用,使用陷阱指令,CPU切换到内核态,执行相应的服务,再切换为用户态并返回系统调用的结果。