1.谈谈你对僵尸进程的理解,如果僵尸进程太多,会出现什么问题,如何处理?
答:僵尸进程(子进程才会出现)是指那些子进程已经终止但是其父进程尚未回收其退出状态信息的进程。在linux系统中,当子进程终止时,其状态不会立刻从系统中消失。而是转变为僵尸进程。留下一个很小的内核结构,包含子进程的退出状态。资源使用信息等,这个结构存在父进程中,直到父进程通过wait()、waitpid()来收集这些信息后,僵尸进程才会被清理。
当僵尸进程太多的时候,会消耗进程表项资源,每个僵尸进程虽然不占用其他实质的资源,但是会占用进程表项,系统的进程表资源有限,如果僵尸进程过多,会影响新进程的创建。
处理方法:
1.使用wait()或waitpid():在父进程中适当调用函数,主动回收子进程的退出状态,对于不确定何时终止的子进程,使用非阻塞的waitpid()配合适当的循环检查。
2.忽略SIGCHLD信号:如果父亲不关心儿子,通过SIG_IGN信号处理器来忽略SIGCHLD信号,这样子进程终止后会自动被内核清理。但是这种方式不适合用于需要收集子进程退出状态的情况。
3.守护进程重生:守护进程设计,采用双进程模型,父进程fork出一个子进程后立即退出,让孙子进程(子进程fork得到)成为实际工作的进程。这样,当孙进程意外终止后,其父进程(原子进程)可以立即重启一个新的工作进程,同时回收僵尸进程。
4.编写健壮的逻辑程序:没啥好说的,确保程序中实现了子进程退出后的处理过程。
2.谈谈你对僵孤儿进程的理解,如果孤儿进程太多,会出现什么问题,如何处理?
答:孤儿进程是和僵尸进程相对应的状态,都是进程管理中的概念。当父进程已经终止,而子进程仍然在运行的子进程,不会变成僵尸进程,因为当父进程终止后,init(pid1)自动成为孤儿进程的新父进程(后爸),init进程会定期调用wait(),因此孤儿进程在终止后会被立刻回收。
孤儿进程过多时:孤儿进程本身不会直接造成严重后果,因为它们会被init系统管理并结束。但是大量的孤儿进程的产生会导致init进程的负担加大。
孤儿进程不需要特殊的处理,因为会被init进程处理掉。
3.一个进程可以创建多少线程?
答:理论上一个进程可以创建的线程没有具体的数目,只能说很多,他受到操作系统、可用内存,系统资源和应用程序等的影响,需要具体情况具体分析,线程栈空间从进程虚拟空间得到(不一定是堆上)。以4G内存大小的系统为例,linux中每个线程的栈大约是8M空间(windows是1M),因此理论上可以创建4096/8=512(4096/1=4096)个线程,但是这里是把内存全部用来创建线程了,实际上还有其他系统的内存占用。
4.进程的调度方法有哪些,如何终止进程?
答:进程的调度指的是操作系统管理进程执行顺序和处理事件的进制方法。
1.先来先服务(FCFS):按照进程到达顺序线性执行,缺点是短进程执行时间会长进程延迟。
2.短作业优先(SJF):优先执行最短时间执行进程,但是逻辑复杂,且无法评估进程运行时间。
3.优先级调度(PS):设置进程优先级,按优先级进行。
4.时间片轮转(PR):每个进程执行被分配一个固定时间片来执行,时间片结束后。进程返回就绪队列尾部等待下一个调度,这样能保证每个进程都有cpu时间。
5.最高响应优先(HRRN):综合等待时间和要求服务时间,响应比=(等待时间+要求服务时间)/要求服务时间,根据响应比大小来分配优先级,综合了1和2,当等待时间变长时,进程优先级会变高。
6.多级队列调度:进程按需划分队列,就绪队列分成多个优先级不同的队列,每个队列可能会采用不同的调度算法,高优先级队列的进程优先执行,不同需求分别使用不同调度,以平衡时间。
7.多级反馈队列调度:结合优先级和时间片轮转的方法,根据其行为在多个优先级队列间动态移动,新进程一般以最高优先级开始,未能完成则降级到下一个队列。
终止进程的方法,不同系统具有不同的方法,对于大部分操作系统而言,可以通过图形界面图像化操作终止进程。对于终端界面,可以使用命令行终止进程。windows通过任务管理器终止,linux通过查找任务PID,然后kill进程。
5.什么是锁,乐观锁和悲观锁是什么?
答:锁是用于同步多个线程或进程访问共享资源的一种机制,他确保同一时间只有一个线程可以执行受保护的资源区域的代码段,从而防止出现竞争状态和数据紊乱。
乐观锁:基于数据在大部分时间不会发生并发冲突的前提下,因而在操作数据时不是直接加锁,而是在数据提交更新时检查数据是否被其他事务(原子层面的数据库操作)修改过。通过版本号和时间戳控制。他相对于悲观锁减少了加解开销,提高了系统的并发性。但是在高并发时,频繁的检查数据又会降低效率。
悲观锁:基于数据在大部分时间会发生冲突的前提下,因而在操作数据时直接加锁,防止其他事务修改。通过任务启动数据加锁和结束数据解锁控制,可以完全隔离数据但是会阻塞其他需要访问的线程,资源瓶颈。
6.操作系统如何实现原子操作?
答:原子操作(原子性)指的是一个操作在执行过程中不会被中断,要么全部执行完成,要门完全不执行,不存在部分执行部分不执行的情况。硬件层面包括总线锁定和比较与变换。软件层面包括互斥锁和信号量、原子变量操作和屏障以及事务内存
总线锁定:多处理系统中通过总线锁定在访问共享内存时锁定系统或内存总线,阻止其他处理器在同一时间访问内存,从而保证了当前操作系统的原子性,如X86通过LOCK前缀实现。
比较与变换(CSA):无锁同步原语,允许同时检查内存位置的值与一个期望值是否相等,如果是则以原子方式更行该位置的值,常用于实现自旋锁和无锁数据结构。
互斥锁和信号量:本身不是原子操作,但是可以用来保护代码和资源,确保同一时间只有一个线程可以执行,间接实现了原子性。
原子变量操作:依靠编程语言实习(C++的std::atomic)提供了原子变量类型和操作,在编译层面实现了原子性。
屏障:多核系统中通过屏障用来确保指令的执行顺序,以及堆内存的访问在多处理器可见,有利于实现原子性和一致性。
事务内存:高级抽象概念,允许一系列操作看起来像是原子操作,是模拟原子操作。
7.什么是死锁?发生死锁怎么解决?进程锁有哪些,线程锁有哪些?
答:死锁是多线程编程中的常见问题,它发生两个及以上的进程(线程)中相互等待对方持有的资源而无法继续执行的情况,每个进程(线程)都占用至少一个资源并等待另一个进程占有的资源,从而形成了循环等待的局面,没有进程能够执行。
死锁的条件包含以下四个:互斥、请求与保持、不剥夺和循环等待,只有这些共同发生时才会发生死锁现象。
互斥条件:至少有一个资源处于非共享模式,即一次只能被一个进程(线程)占用,其他需要该资源的实体必须等待。
请求与保持条件:进程已经有了一个资源又提出新的资源请求,而该资源被其他进程占有,于是当前进程被阻塞,但对已获得的资源不释放,导致其他进程需要这部分的资源无法获得。
不剥夺条件:已经分配给某个进程的资源在未使用完之前,不能被其他进程强行剥夺占有,即资源只能被占有他的进程自愿释放。
循环等待条件:存在一个进程自愿等待的循环链,即进程1、2、3、4、5、6中的1正在等待2占有的资源释放,2在等待3的,而6在等待1的,形成循环。
避免死锁主要有两种方法,第一个是破坏死锁发生条件之一,另一个是通过资源分配图等方式动态检测系统状态,确保系统进入死锁,譬如银行家算法。
当发生死锁后,通过算法定期检测系统是否进入死锁状态,如果发生,则撤回或回滚进程,释放资源,或者在尝试获取锁时设置超时时间,当超时后释放已获得的锁的资源。
进程锁:
互斥锁:保证同一时间只有一个进程可以访问共享资源。
读写锁:允许多个读者可以同时访问资源,但是写入时独占资源。
信号量:形成互斥,同时控制资源的最大访问数。
条件变量:配合互斥锁,用于线程间的同步,通过条件允许访问资源。
线程锁:
自旋锁:尝试获取锁的线程如果失败,不立刻放弃cpu,,而是通过循环等待锁释放。
互斥锁:与进程中的互斥锁相似。
读写锁:与进程中的读写锁相似。
递归锁:允许一个线程对此获取同一把锁,不会产生死锁。
8. 请你谈谈什么事物理地址, 什么是逻辑地址?
答:物理地址指的是计算机中真实存在的地址位置,是硬件层面的地址位置,由内存控制器来定位和访问主存中的数据,CPU通过地址总线将物理地址发送个内存,以读取或者写入数据,物理地址与存储单元一一对应,是未经转换到额原始地址。逻辑地址是程序代码中的使用的地址,是程序角度下的地址空间,在由地址变换的功能的系统中,逻辑地址是相对于某个基准地址的偏移量,他不是用来直接访问物理地址空间的,逻辑地址便于程序员编写程序,同时也使得程序能够在不同的额内存配置中运行。逻辑地址到物理地址的转换通过内存管理单元(MMU)负责完成。