目录:
进程和线程的区别
进程间通信方式
进程同一个主机通信和不同主机通信有什么区别?
多进程和多线程的区别
进程切换和线程切换
进程的基本状态
什么叫僵尸进程和孤儿进程?怎么处理僵尸进程?
操作系统中进程调度策略有哪几种?
什么是内存抖动
什么是页面抖动
页面置换算法
虚拟内存与物理内存
软链接和硬连接的区别
Linux 文件权限和类型
Linux 文件类型有哪些
如何理解互斥锁、条件锁、读写锁以及自旋锁
死锁
IO 模型
什么是零拷贝
1. 拥有资源
进程是资源分配的基本单位,但是线程不拥有资源,线程可以访问同一进程的资源
2. 调度
线程是独立调度的基本单位,同一进程中,线程的切换不会引起进程切换,从一个进程内的线程切换到另一个进程内的线程会引起进程切换
3. 系统开销
创建和撤销进程时,系统都要为之分配或回收资源,所付出的开销远大于创建或撤销线程时的开销。
同样的在进程切换时,也会涉及当前执行进程 CPU 环境的保存以及新调度进程 CPU 环境的设置,
而线程的切换只需保存和设置少量寄存器的内容,开销很小
4. 通信方面
进程间通信需要进程同步和互斥手段的辅助,以保证数据的一致性。
而线程间可以通过直接读/写同意进程中的数据段(如全局变量)来进行通信。
参考:
1. [凉了!张三同学没答好「进程间通信」,被面试官挂了....](https://www.cnblogs.com/xiaolincoding/p/13402297.html)
2. [远程服务调用](https://icyfenix.cn/architect-perspective/general-architecture/api-style/rpc.html)
1. 管道(Pipe)或者具名管道(Named Pipe)
管道类似于两个进程间的桥梁,可通过管道在进程间传递少量的字符流或字节流。
普通管道只用于有亲缘关系进程(由一个进程启动的另外一个进程)间的通信,
具名管道摆脱了普通管道没有名字的限制,除具有管道所有的功能外,它还允许无亲缘关系进程间的通信。
管道典型的应用就是命令行中的|操作符,譬如:
```
ps -ef | grep java
```
ps 与 grep 都有独立的进程,以上命令就通过管道操作符 | 将 ps 命令的标准输出连接到 grep 命令的标准输入上。
所谓的管道,就是内核里面的一串缓存,遵循先进先出原则,不支持 lseek 之类的文件定位操作。
管道这种通信方式效率低,不适合进程间频繁地交换数据。
当然,它的好处,自然就是简单,同时也很容易得知管道里的数据已经被另一个进程读取了。
2. 消息队列
消息队列是保存在内核中的消息链表,在发送数据时,会分成一个一个独立的数据单元,也就是消息体(数据块),
如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
消息队列生命周期随内核,如果没有释放消息队列或者没有关闭操作系统,消息队列会一直存在,
而匿名管道的生命周期,是随进程的创建而建立,随进程的结束而销毁。
不适合比较大数据的传输,因为在内核中每个消息体都有一个最大长度的限制,
同时在消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销。
3. 共享内存
共享内存的机制,就是拿出一块虚拟地址空间来,映射到相同的物理内存中。
不会发生用户态与内核态之间的消息拷贝过程,这是效率最高的进程间通信形式。
原本每个进程的内存地址空间都是相互隔离的,但操作系统提供了让进程主动创建、映射、分离、控制某一块内存的程序接口。
当一块内存被多进程共享时,各个进程往往会与其它通信机制,譬如信号量结合使用,来达到进程间同步及互斥的协调操作。
4. 信号量
信号量其实是一个整型的计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据。
P 操作会把信号量减去 -1,V 操作会把信号量加上 1,
P 操作是用在进入共享资源之前,V 操作是用在离开共享资源之后,这两个操作是必须成对出现的。
5. 信号
信号用于通知目标进程有某种事件发生,除了用于进程间通信外,进程还可以发送信号给进程自身。
信号的典型应用是 kill 命令,例如:
```
kill -9 pid
```
以上就是由 Shell 进程向指定 PID 的进程发送 SIGKILL 信号。
6. Socket
管道、消息队列、共享内存、信号量和信号都是在同一台主机上进行进程间通信,
要想跨网络与不同主机上的进程之间通信,就需要 Socket 通信。
Socket 通信不仅可以跨网络与不同主机的进程间通信,还可以在同主机上进程间通信。
同一个主机通信可以有多种方式,包括管道、消息队列、共享内存、信号量和信号和 Socket。
不同主机通信只能通过 Socket。
参考:
1. [进程切换和线程切换?](https://www.cnblogs.com/lfri/p/12597297.html)
进程切换:
1.切换页目录以使用新的地址空间
2.切换内核栈和硬件上下文。
线程切换:
切换内核栈和硬件上下文。
进程切换与线程切换的区别:
最主要的一个区别在于进程切换涉及虚拟地址空间的切换而线程不会。
因为每个进程都有自己的虚拟地址空间,而线程是共享所在进程的虚拟地址空间的,
因此同一个进程中的线程进行线程切换时不涉及虚拟地址空间的转换。
运行态,运行态指的就是进程实际占用 CPU 时间片运行时。
就绪态,就绪态指的是可运行,但因为其他进程正在运行而处于就绪状态。
阻塞态,除非某种外部事件发生,否则进程不能运行。
参考:
1. [僵尸进程和僵死进程有什么区别? - wuxinliulei的回答 - 知乎](https://www.zhihu.com/question/26432067/answer/70643183)
2. [孤儿进程与僵尸进程[总结]](https://www.cnblogs.com/anker/p/3271773.html)
孤儿进程:
一个父进程退出,而它的一个或多个子进程还在运行,那么那些子进程将成为孤儿进程。
孤儿进程将被 init 进程(进程号为 1)所收养,并由 init 进程对它们完成状态收集工作。
僵尸进程: 一个进程使用 fork 创建子进程,如果子进程退出,而父进程并没有调用 wait 或 waitpid 获取子进程的状态信息, 那么子进程的进程描述符仍然保存在系统中。这种进程称之为僵尸进程。
怎么处理僵尸进程:
1. 通过 Kill 杀掉父进程
2. fork 两次,原理是将子进程成为孤儿进程,从而其的父进程变为 init 进程,通过 init 进程可以处理僵尸进程。
Linux 把进程区分为实时进程和非实时进程,其中非实时进程进一步划分为交互式进程和批处理进程。
在 Linux 中,调度算法可以明确的确认所有实时进程的身份,但是没办法区分交互式程序和批处理程序,
Linux2.6 的调度程序实现了基于进程过去行为的启发式算法,以确定进程应该被当做交互式进程还是批处理进程。
当然与批处理进程相比,调度程序有偏爱交互式进程的倾向。
批处理中的调度:
先来先服务
最短作业优先
最短剩余时间优先
高响应比优先调度算法
交互式系统中的调度:
时间片轮询调度
优先级调度
多级队列:
每个队列时间片大小都不同,例如 1,2,4,8,..。进程在第一个队列没执行完,就会被移到下一个队列。
每个队列优先权也不同,最上面的优先权最高。因此只有上一个队列没有进程在排队,才能调度当前队列上的进程。
实时调度:
FIFO 或者 Round Robin
内存抖动是指在短时间内有大量的对象被创建或者被回收的现象,主要是循环中大量创建、回收对象。这种情况应当尽量避免。
在页面置换过程中的一种最糟糕的情形是,刚刚换出的页面马上又要换入主存,刚刚换入的页面马上就要换出主存,
这种频繁的页面调度行为称为抖动,或颠簸。
参考:
1. [页面置换算法](https://zhuanlan.zhihu.com/p/47814764)
2. [操作系统学习(10)页面置换算法](操作系统学习(10)页面置换算法)
先进先出(FIFO)
有 belady 现象(给的物理页帧越多反而缺页越频繁)。
最近最少使用(LRU)
比较优秀,但是很难实现。
时钟(CLOCK)置换算法
简单的 CLOCK 算法是给每一帧关联一个附加位,称为使用位。
又称为最近未用(Not Recently Used, NRU)算法。
改进型的 clock 算法
在使用位的基础上再增加一个修改位,则得到改进型的 CLOCK 置换算法。
LFU(least frequently used,最不常用算法)
MFU(最常使用算法)
MFU 和 LFU 置换都不常用。这些算法的实现是昂贵的,并且它们不能很好地近似 OPT(理想中的最佳置换算法) 置换。
参考:
1. [虚拟内存与物理内存的联系与区别](https://blog.csdn.net/lvyibin890/article/details/82217193)
2. [为什么 Linux 需要虚拟内存](https://draveness.me/whys-the-design-os-virtual-memory/)
Linux 操作系统中为什么需要虚拟内存:
1. 虚拟内存可以结合磁盘和物理内存的优势为进程提供看起来速度足够快并且容量足够大的存储;
2. 虚拟内存可以为进程提供独立的内存空间并引入多层的页表结构将虚拟内存翻译成物理内存,
进程之间可以共享物理内存减少开销,也能简化程序的链接、装载以及内存分配过程;
3. 虚拟内存可以控制进程对物理内存的访问,隔离不同进程的访问权限,提高系统的安全性;
进程访问一个地址的过程
每次要访问地址空间上的某一个地址,都需要把地址翻译为实际物理内存地址
所有进程共享这整一块物理内存,每个进程只把自己目前需要的虚拟地址空间映射到物理内存上
进程需要知道哪些地址空间上的数据在物理内存上,哪些不在(可能这部分存储在磁盘上),还有在物理内存上的哪里,这就需要通过页表来记录
页表的每一个表项分两部分,第一部分记录此页是否在物理内存上,第二部分记录物理内存页的地址(如果在的话)
当进程访问某个虚拟地址的时候,就会先去看页表,如果发现对应的数据不在物理内存上,就会发生缺页异常
缺页异常的处理过程,操作系统立即阻塞该进程,并将硬盘里对应的页换入内存,然后使该进程就绪,
如果内存已经满了,没有空地方了,那就找一个页覆盖,
至于具体覆盖的哪个页,取决于操作系统的页面置换算法怎么设计。
硬链接:
硬链接是指针,所有的硬链接都是指向同一个磁盘块。
删除一个指针不会真正删除文件,只有把所有的指针都删除才会真正删除文件。
软连接:
软连接是另外一种类型的文件,保存的是它指向文件的全路径,访问时会替换成绝对路径。
删除链接文件时,系统仅仅删除链接文件,而不删除源文件本身,这一点类似于 Windows 操作系统下的快捷方式。
占用存储空间的类型:文件、目录、符号链接。符号链接记录的是路径,路径不长时存在innode里面。
其他四种:套接字、块设备、字符设备、管道是伪文件,不占用磁盘空间。
参考:
1. [如何理解互斥锁、条件锁、读写锁以及自旋锁? - 邱昊宇的回答 - 知乎](https://www.zhihu.com/question/66733477/answer/246535792)
自旋锁:
只要没有锁上,就不断重试。一般应用于加锁时间很短(1ms 左右或更低)的场景。
互斥量(mutex,互斥锁):
无法获取琐时,进线程立刻放弃剩余的时间片并进入阻塞(或者说挂起)状态,
同时保存寄存器和程序计数器的内容(保存现场,上下文切换的前半部分),
当可以获取锁时,进线程激活,等待被调度进 CPU 并恢复现场(上下文切换下半部分)
上下文切换会带来数十微秒的开销,不要在性能敏感的地方用互斥锁
condition variable(条件变量,条件锁)
注意条件变量不是锁,它是一种线程间的通讯机制,并且几乎总是和互斥量一起使用的。所以互斥量和条件变量二者一般是成套出现的。
读写锁:
在执行加锁操作时需要额外表明读写意图,复数读者之间并不互斥,而写者则要求与任何人互斥。
参考:
1. [写给大忙人看的死锁全详解](https://www.cnblogs.com/cxuanBlog/p/13202898.html)
资源死锁的条件:
1. 互斥条件:每个资源都被分配给了一个进程或者资源是可用的
2. 保持和等待条件:已经获取资源的进程被认为能够获取新的资源
3. 不可抢占条件:分配给一个进程的资源不能强制的从其他进程抢占资源,它只能由占有它的进程显示释放
4. 循环等待:死锁发生时,系统中一定有两个或者两个以上的进程组成一个循环,循环中的每个进程都在等待下一个进程释放的资源。
死锁预防:
通过破坏产生死锁的必要条件之一,使系统不会产生死锁。
死锁避免:
银行家算法
死锁检测:
方法:
给每个进程、每个资源指定唯一编号;
设置一张资源分配表记录各进程与其占用资源之间的关系;
设置一张进程等待表记录各进程与要申请资源之间的关系。
从资源等待表出发,看有没有形成等待的环路。即可以利用资源分配图的思想来检测是否有死锁发生。
检测时机:
1. 当进程由于资源请求不满足而等待时检测死锁,缺点是系统开销大;
2. 定时检测;
3. 系统资源利用率下降时检测死锁。
死锁的解除:
1. 通过抢占进行恢复
2. 通过回滚进行恢复
3. 杀死进程恢复
todo
https://time.geekbang.org/course/detail/100060601-365835
https://cloud.tencent.com/developer/article/1745899
https://juejin.cn/post/6882984260672847879#heading-10
todo
https://time.geekbang.org/column/article/204696