进程有那些状态?
答:
- 创建状态(new) :进程正在被创建,尚未到就绪状态。
- 就绪状态(ready) :进程已处于准备运行状态,即进程获得了除了处理器之外的一切所需资源,一旦得到处理器资源(处理器分配的时间片)即可运行。
- 运行状态(running) :进程正在处理器上上运行(单核 CPU 下任意时刻只有一个进程处于运行状态)。
- 阻塞状态(waiting) :又称为等待状态,进程正在等待某一事件而暂停运行如等待某资源为可用或等待 IO 操作完成。即使处理器空闲,该进程也不能运行。
- 结束状态(terminated) :进程正在从系统中消失。可能是进程正常结束或其他原因中断退出运行。
进程间的通信方式
1. 管道/匿名管道(Pipes) :用于具有亲缘关系的父子进程间或者兄弟进程之间的通信。
2. 有名管道(Names Pipes) : 匿名管道由于没有名字,只能用于亲缘关系的进程间通信。为了克服这个缺点,提出了有名管道。有名管道严格遵循先进先出(first in first out)。有名管道以磁盘文件的方式存在,可以实现本机任意两个进程通信。
3. 信号(Signal) :信号是一种比较复杂的通信方式,用于通知接收进程某个事件已经发生;
4. 消息队列(Message Queuing) :消息队列是消息的链表,具有特定的格式,存放在内存中并由消息队列标识符标识。管道和消息队列的通信数据都是先进先出的原则。与管道(无名管道:只存在于内存中的文件;命名管道:存在于实际的磁盘介质或者文件系统)不同的是消息队列存放在内核中,只有在内核重启(即,操作系统重启)或者显示地删除一个消息队列时,该消息队列才会被真正的删除。消息队列可以实现消息的随机查询,消息不一定要以先进先出的次序读取,也可以按消息的类型读取.比 FIFO 更有优势。消息队列克服了信号承载信息量少,管道只能承载无格式字 节流以及缓冲区大小受限等缺。
5. 信号量(Semaphores) :信号量是一个计数器,用于多进程对共享数据的访问,信号量的意图在于进程间同步。这种通信方式主要用于解决与同步相关的问题并避免竞争条件。
6. 共享内存(Shared memory) :使得多个进程可以访问同一块内存空间,不同进程可以及时看到对方进程中对共享内存中数据的更新。这种方式需要依靠某种同步操作,如互斥锁和信号量等。可以说这是最有用的进程间通信方式。
7. 套接字(Sockets) :此方法主要用于在客户端和服务器之间通过网络进行通信。套接字是支持 TCP/IP 的网络通信的基本操作单元,可以看做是不同主机之间的进程进行双向通信的端点,简单的说就是通信的两方的一种约定,用套接字中的相关函数来完成通信过程。
线程之间的通信方式?
答:
1. 共享内存:线程之间共享程序的公共状态,线程之间通过读-写内存中的公共状态来隐式通信。volatile共享内存
2. 消息传递:线程之间没有公共的状态,线程之间必须通过明确的发送信息来显示的进行通信。wait/notify等待通知方式、join方式
3. 使用阻塞队列控制线程通信:管道输入/输出流的形式
4.使用管道流进行线程通信(已被上面的阻塞队列代替)
通过输入/输出在线程间进行通信通常很有用。Java中对应的实现就是PipedWriter类和PipedReader类。这种使用管道来通信的模型可以看成是"生产者-消费者"问题的变种,这里的管道就是一个封装好的解决方案。管道基本上就是一个阻塞队列,它存在于引入阻塞队列之前的java版本中。在实际开发中,很少会使用到管道流。
用户级线程和内核级线程?
线程的同步方式?
1. 互斥量(Mutex):采用互斥对象机制,只有拥有互斥对象的线程才有访问公共资源的权限。因为互斥对象只有一个,所以可以保证公共资源不会被多个线程同时访问。比如 Java 中的 synchronized 关键词和各种 Lock 都是这种机制。
2. 信号量(Semphares) :它允许同一时刻多个线程访问同一资源,但是需要控制同一时刻访问此资源的最大线程数量
3. 事件(Event) :Wait/Notify:通过通知操作的方式来保持多线程同步,还可以方便的实现多线程优先级的比较操
操作系统中进程的调度算法有哪些吗?
- 先到先服务(FCFS)调度算法 : 从就绪队列中选择一个最先进入该队列的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 短作业优先(SJF)的调度算法 : 从就绪队列中选出一个估计运行时间最短的进程为之分配资源,使它立即执行并一直执行到完成或发生某事件而被阻塞放弃占用 CPU 时再重新调度。
- 时间片轮转调度算法 :时间片轮转调度是一种最古老,最简单,最公平且使用最广的算法,又称 RR(Round robin)调度。每个进程被分配一个时间段,称作它的时间片,即该进程允许运行的时间。
- 优先级调度 : 为每个流程分配优先级,首先执行具有最高优先级的进程,依此类推。具有相同优先级的进程以 FCFS 方式执行。可以根据内存要求,时间要求或任何其他资源要求来确定优先级。
- 多级反馈队列调度算法 :前面介绍的几种进程调度的算法都有一定的局限性。如短进程优先的调度算法,仅照顾了短进程而忽略了长进程 。多级反馈队列调度算法既能使高优先级的作业得到响应又能使短作业(进程)迅速完成。,因而它是目前被公认的一种较好的进程调度算法,UNIX 操作系统采取的便是这种调度算法。
过程:
1、按照先来先服务原则排序,设置N个就绪队列为Q1,Q2...QN,每个队列中都可以放很多作业;
2、为这N个就绪队列赋予不同的优先级,第一个队列的优先级最高,第二个队列次之,其余各队列的优先权逐个降低;
3、设置每个就绪队列的时间片,优先权越高,算法赋予队列的时间片越小。时间片大小的设定按照实际作业(进程)的需要调整;
4、进程在进入待调度的队列等待时,首先进入优先级最高的Q1等待。
5、首先调度优先级高的队列中的进程。若高优先级中队列中已没有调度的进程,则调度次优先级队列中的进程。例如:Q1,Q2,Q3三个队列,只有在Q1中没有进程等待时才去调度Q2,同理,只有Q1,Q2都为空时才会去调度Q3。
6、对于同一个队列中的各个进程,按照时间片轮转法调度。比如Q1队列的时间片为N,那么Q1中的作业在经历了时间片为N的时间后,若还没有完成,则进入Q2队列等待,若Q2的时间片用完后作业还不能完成,一直进入下一级队列,直至完成。
7、在低优先级的队列中的进程在运行时,又有新到达的作业,那么在运行完这个时间片后,CPU马上分配给新到达的作业即抢占式调度CPU。
多线程和多进程区别?
答:
线程(CPU调度和分派的基本单位)是进程(操作系统进行资源分配和调度的基本单位)划分成的更小的运行单位,一个进程在其执行的过程中可以产生多个线程。线程和进程最大的不同在于基本上各进程是独立的,而各线程则不一定,因为同一进程中的线程极有可能会相互影响。线程执行开销小,但不利于资源的管理和保护;而进程正相反,并且线程中内存地址空间是相互共享的,而进程中不是。因此,进程和线程其实都是内核调度实体(KSE),只是对⼀些系统资源,例如虚拟内存、打开⽂件描述符、对信号的处置、进程 ID 等资源的共享程度不同⽽已。所以我们可以推断出以下结论:
- 线程间的通信更容易,因为拥有着同⼀块数据段,因⽽可以使⽤全局变量进⾏通信,相当于继承,大家公用一部分资源。
- 线程的创建速度要高于进程,因为“数据重合度”更⾼,⼦线程只需要拥有⾃⼰的程序计数器,一组寄存器和栈即可(1、程序计数器为什么是私有的?程序计数器私有主要是为了线程切换后能恢复到正确的执行位置。2、栈为什么是私有的?保证线程中的局部变量不被别的线程访问到,虚拟机栈和本地方法栈是线程私有的。)
- 线程之间的上下⽂切换也要⽐进程间的上下⽂切换更快,因为线程更加轻量,自然在切换的过程中消耗的资源也更少(什么是上下文切换?线程切换意味着需要保存当前线程的上下文,留待线程下次占用 CPU 的时候恢复现场。并加载下一个将要占用 CPU 的线程上下文。这就是所谓的 上下文切换。)。
- 线程同步的时候需要同步机制来支持,因为资源共享,我们在共享资源操作的时候,需要加额外的操作,但是进程不用,进程的资源是独立的,同步的时候更加容易
协程和线程区别?
答:
- 概念:协程是一种比线程更加轻量级的存在,一个线程可以拥有多个协程。协程不是被操作系统内核所管理,而是完全由程序所控制(也就是在用户态执行),好处是性能得到了很大提升,不会像线程切换那样消耗资源。协程拥有自己的寄存器上下文和栈。协程调度切换时,将寄存器上下文和栈保存到其他地方,在切回来的时候,恢复先前保存的寄存器上下文和栈。因此:协程能保留上一次调用时的状态(即所有局部状态的一个特定组合),每次过程重入时,就相当于进入上一次调用的状态,换种说法:进入上一次离开时所处逻辑流的位置。
-
协程的好处:
无需线程上下文切换的开销(无需切换内核态与用户态)
无需原子操作锁定及同步的开销
方便切换控制流,简化编程模型
线程进程都是同步机制,而协程则是异步。 -
应用场景:可以在线程IO阻塞的时候,去运行协程,提高CPU利用率
什么是协程?(参考链接)
并发和并行的区别
-
并发:
并发(Concurrent),在操作系统中,是指一个时间段中有几个程序都处于已启动运行到运行完毕之间,且这几个程序都是在同一个处理机上运行。
就想前面提到的操作系统的时间片分时调度。打游戏和听音乐两件事情在同一个时间段内都是在同一台电脑上完成了从开始到结束的动作。那么,就可以说听音乐和打游戏是并发的。
-
并行:
并行(Parallel),当系统有一个以上CPU时,当一个CPU执行一个进程时,另一个CPU可以执行另一个进程,两个进程互不抢占CPU资源,可以同时进行,这种方式我们称之为并行(Parallel)。
这里面有一个很重要的点,那就是系统要有多个CPU才会出现并行。在有多个CPU的情况下,才会出现真正意义上的『同时进行』。
什么是僵尸进程?
答:僵尸进程是当子进程比父进程先结束,而父进程又没有回收子进程,释放子进程占用的资源,此时子进程将成为一个僵尸进程。如果父进程先退出,子进程被init接管,子进程推出后init会回收其占用的相关资源。如果太多僵尸进程的话,会导致系统的进程号不够用,不再产生进程。解决方法: (1) 让僵尸进程成为孤儿进程,由init进程回收;(手动杀死父进程)。(2) 父进程用wait或waitpid去回收资源(方案不好)父进程通过wait或waitpid等函数去等待子进程结束,但是不好,会导致父进程一直等待被挂起,相当于一个进程在干活,没有起到多进程的作用。
线程死锁是如何产生的,如何避免 ?
答:
- 线程死锁的条件:
- 互斥条件:一个资源在同一时刻只由一个线程占用。
- 请求与保持条件:一个线程在请求被占资源时发生阻塞,并对已获得的资源保持不放。
- 循环等待条件:发生死锁时,所有的线程会形成一个死循环,一直阻塞。
- 不剥夺条件:线程已获得的资源在未使用完不能被其他线程剥夺,只能由自己使用完释放资源。
- 避免死锁的方法:只有上面四个条件达成才能形成死锁,所以我们只需要破坏上面任意一个条件即可
- 破坏互斥条件:使资源同时访问而非互斥使用,就没有进程会阻塞在资源上,从而不发生死锁。但是这种场景很少,因为你资源共享的话,如果是只读就可以,但是设计到修改很可能会导致异常。而且我们加锁就是为了互斥。
- 破坏占有和等待条件:采用静态分配的方式,静态分配的方式是指进程必须在执行之前就申请需要的全部资源,且直至所要的资源全部得到满足后才开始执行。
- 占有部分资源的线程进一步申请其他资源时,如果申请不到,主动释放它占有的资源,破坏 "不可抢占" 条件
- 按序申请资源,破坏 "循环等待" 条件
io多路复用,介绍select,poll,epoll原理,他们的优缺点及不同?
手写死锁?
Lock lock1 = new ReentrantLock();
Lock lock2 = new ReentrantLock();
static Runnable a = () -> {
try {
while (true) {
HashMap<Object, Object> objectObjectHashMap = new HashMap<>();
// lock1.lock();
synchronized (test.obj1) {
System.out.println("Thread 1 lock 1");
Thread.sleep(5000);
// lock2.lock();
synchronized (test.obj2) {
System.out.println("Thread 1 lock 2");
Thread.sleep(5000);
}
// lock2.unlock();
System.out.println("Thread 1 unlock 2");
// lock1.unlock();
}
System.out.println("Thread 1 unlock 1");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
};
static Thread thread1 = new Thread(a);
static Thread thread2 = new Thread(() -> {
try {
while (true) {
// lock2.lock();
synchronized (test.obj2) {
System.out.println("Thread 2 lock 2");
Thread.sleep(5000);
// lock1.lock();
synchronized (test.obj1) {
System.out.println("Thread 2 lock 1");
Thread.sleep(5000);
}
// lock1.unlock();
System.out.println("Thread 2 unlock 1");
// lock2.unlock();
}
System.out.println("Thread 2 unlock 2");
}
} catch (InterruptedException e) {
e.printStackTrace();
}
});
public static String obj1 = "obj1";
public static String obj2 = "obj2";
public static void main(String[] args) {
thread1.start();
thread2.start();
}
简述操作系统如何进行内存管理?
答:
-
为什么要内存管理?
答:早期,程序直接运行在物理内存上,直接操作物理内存,这种方式存在几个问题:
1、地址空间不隔离:程序操作相同地址空间会造成互相影响甚至崩溃,而且安全性也得不到保证;2、使用效率低:没有特别好的策略保证多个进程对超过物理内存大小的内存需求的满足;3、程序运行地址不确定:程序运行时,都需要分配空闲区域,而空闲位置不确定,会带来一些重定位问题; -
虚拟内存
计算机系统里任何问题都可以靠引入一个中间层来解决,内存管理就在程序和物理内存之间引入了虚拟内存的概念;对进程地址和物理地址进行隔离;
简述操作系统中的缺页中断?
答:
- 概念: malloc()和mmap()等内存分配函数,在分配时只是建立了进程虚拟地址空间,并没有分配虚拟内存对应的物理内存。当进程访问这些没有建立映射关系的虚拟内存时,处理器自动触发一个缺页异常。
- 缺页中断:在请求分页系统中,可以通过查询页表中的状态位来确定所要访问的页面是否存在于内存中。每当所要访问的页面不在内存是,会产生一次缺页中断,此时操作系统会根据页表中的外存地址在外存中找到所缺的一页,将其调入内存。
什么时候会由用户态陷入内核态?
答:
-
为何要区分用户态和内核态?
最简单的运行程序的方式是“直接执行”,即直接在 CPU 上执行任意程序。直接执行的问题是:1、如何限制代码行为?比如禁止:设置特殊寄存器的值、访问存储器的任意位置、I/O 请求、申请更多系统资源等。2、在运行这个程序的时候,如何切换到另一个程序?进程调度应该是 OS 才有的权限。因此引入用户态和内核态和两种模式。用户态无法执行受限操作,如 I/O 请求,执行这些操作会引发异常。核心态只能由操作系统运行,可以执行特权操作。用户程序通过系统调用 system call 执行这些特权操作。OS 执行前会判断进程是否有权限执行相应的指令。区分用户态和核心态的执行机制称为“受限直接执行”(Limited Direct Execution)。 - 什么时候会陷入内核态?
系统调用(trap)、中断(interrupt)和异常(exception)。
系统调用是用户进程主动发起的操作。发起系统调用,陷入内核,由操作系统执行系统调用,然后再返回到进程。
中断和异常是被动的,无法预测发生时机。中断包括 I/O 中断、外部信号中断、各种定时器引起的时钟中断等。异常包括程序运算引起的各种错误如除 0、缓冲区溢出、缺页等。
在系统的处理上,中断和异常类似,都是通过中断向量表来找到相应的处理程序进行处理。区别在于,中断来自处理器外部,不是由任何一条专门的指令造成,而异常是执行当前指令的结果。
操作系统中,虚拟地址与物理地址之间如何映射?
Linux 下如何查看端口被哪个进程占用?
答:lsof -i:端口号
进程通信中的管道实现原理是什么?
答:
管道是如何通信的:
管道是由内核管理的一个缓冲区,相当于我们放入内存中的一个纸条。管道的一端连接一个进程的输出。这个进程会向管道中放入信息。管道的另一端连接一个进程的输入,这个进程取出被放入管道的信息。一个缓冲区不需要很大,它被设计成为环形的数据结构,以便管道可以被循环利用。当管道中没有信息的话,从管道中读取的进程会等待,直到另一端的进程放入信息。当管道被放满信息的时候,尝试放入信息的进程会等待,直到另一端的进程取出信息。当两个进程都终结的时候,管道也自动消失。管道是如何创建的:
从原理上,管道利用fork机制建立,从而让两个进程可以连接到同一个PIPE上。最开始的时候,上面的两个箭头都连接在同一个进程Process 1上(连接在Process 1上的两个箭头)。当fork复制进程的时候,会将这两个连接也复制到新的进程(Process 2)。随后,每个进程关闭自己不需要的一个连接 (两个黑色的箭头被关闭; Process 1关闭从PIPE来的输入连接,Process 2关闭输出到PIPE的连接),这样,剩下的红色连接就构成了如上图的PIPE。-
管道通信的实现细节:
在 Linux 中,管道的实现并没有使用专门的数据结构,而是借助了文件系统的file结构和VFS的索引节点inode。通过将两个 file 结构指向同一个临时的 VFS 索引节点,而这个 VFS 索引节点又指向一个物理页面而实现的。如下图
有两个 file 数据结构,但它们定义文件操作例程地址是不同的,其中一个是向管道中写入数据的例程地址,而另一个是从管道中读出数据的例程地址。这样,用户程序的系统调用仍然是通常的文件操作,而内核却利用这种抽象机制实现了管道这一特殊操作。
Linux 下如何排查 CPU 以及 内存占用过多?
答:CPU的话使用TOP命令去查一下哪些线程
简述 Linux 虚拟内存的页面置换算法?
简述 Linux 零拷贝的原理?
答:
参考文章