进程可以看做是程序执行的过程, 实际上对于系统来说, 进程就是pcb
一个可执行文件执行起来,系统就是创建一个进程, 这个程序执行结束, 系统就会销毁对应的进程
进程也可以认为是操作系统中进行资源分配的最小单位
运行态(R): 正在运行或轮转到时间片就能够运行
可中断休眠态(S): 可以被中断的休眠状态(满足唤醒条件, 或者休眠被中断则进入运行态)
不可中断休眠态(D): 不可被中断的休眠状态(满足唤醒条件才能进入运行态)
停止态(T): 程序停止运行的状态(依然会被调用, 但是什么都不做)
僵尸态(Z): 进程已经退出不再调度了, 但是这个进程的资源还没有被完全释放, 等待处理的一种状态
僵尸进程: 处于僵尸态的进程, 是一种退出了, 但是资源没有完全被释放的进程
产生: 子进程先于父进程退出, 但是父进程没有关注到子进程的退出, 因此系统不会完全释放子进程的资源, 这个子进程进入僵尸态. 子进程退出之后, 在进程pcb中保存了自己的返回值, 在父进程没有关注处理的情况下, pcb资源不会被释放
危害: 资源泄露(1.pcb所占的内存资源一直无法被回收; 2.一个用户所能创建的进程数量是有限制的)
处理: 退出父进程(不合理)
避免: 进程等待
孤儿进程: 父进程先于子进程退出, 子进程就成为孤儿进程,运行在后台, 父进程成为1号进程, 也就是父进程终止了, 丧父了, 被1号进程领养,
危害: 没什么危害, 只是父进程换了, 依旧可以正常运行
每个进程的地址空间都是独立的,一般无法互相访问,但内核空间是每个进程都共享的,所以进程间通信要通过内核
所谓管道就是内核里的一串缓存,从管道的一段写入数据,实际是缓存到内核中,从另一端读取,也就是从内核中读取,遵循先进先出原则。
生命周期:随进程的创建而建立,随进程的结束而销毁。
管道又有匿名管道和命名管道
1.匿名管道
$ ps auxf | grep mysql| 竖线就是一个管道,它的功能是将前一个命令(ps auxf)的输出,作为后一个命令(grep mysql)的输入,
管道传输数据是单向的,如果想互相通信,我们需要创建两个管道
创建一个匿名管道,返回两个描述符,一个是管道的读取端描述符,一个是管道的写入端描述符,然后通过fork创建子进程,
创建的子进程会复制父进程的文件描述符,两个进程就可以通过各自的端口读取和写入同一个管道文件实现跨进程通信
管道只能一端写入,另一端读取,所以为了避免混乱,通常父进程关闭读取端,子进程关闭写入端
但是在shell中并不是这样的
在shell中执行A | B时,A,B都是shell创建出来的子进程,A,B之间不从在父子关系。
所以我们在使用shell编写时,能用一个管道解决的,就不要多用,这样就可以减少创建子进程的系统开销了
2.命名管道
通过mkfifo创建并指定管道名称
对于匿名管道,它的通信范围是存在父子关系的进程,因为管道没有实体,没有管道文件,只能通过fork来复制父进程的文件描述符,来达到通信目的。
对于命名管道,它可以在不相关的进程间也能互相通信,因为命名管道提前创建了一个类型为管道的设备文件,在进程里只要使用这个设备文件就可以相互通信
优缺点:
简单,很容易得知管道里的数据已经别另一个进程读取了
效率低,不适合进程间频繁的交换数据
管道传输的数据是无格式的字节流,且限制大小
例如:A进程给B进程发消息,A进程把数据放到对应的消息队列后就可以正常返回了,B进程需要的时候再去读取数据,B进程发消息也同理。
消息队列是保存在内核中的消息链表,在发送数据时会分成一个一个独立的数据单元(消息体 / 数据块)。
消息体时用户自定义的数据类型,每个消息体都是固定大小的存储块。如果进程从消息队列中读取了消息体,内核就会把这个消息体删除。
生命周期:随内核,如果没有释放消息队列或没有关闭操作系统,消息队列会一直存在。
优缺点:
解决了管道中效率低,不适合进程间频繁的交换数据
不适合比较大数据的传输,因为内核中每个消息体都有一个最大长度的限制,所有队列包含的全部消息体的总长度也有上限。
消息队列通信过程中,存在用户态与内核态之间的数据拷贝开销,
因为进程在写入数据到内核中的消息队列时,会发生从用户态拷贝数据到内核态的过程;
同理进程读取数据时,会发生从内核态拷贝数据到用户态的过程。
共享内存的机制就是拿出一块虚拟地址空间,映射到相同的物理内存中。
这样这个进程写入的东西,另一个进程马上就能看到,不需要拷贝,大大提高了通信速度,最快的进程间通信
如果多个进程同时修改同一个共享内存,很有可能就会冲突,例如两个进程同时写一个地址,先的进程就会被覆盖
信号量其实就是一个整型计数器,主要用于实现进程间的互斥与同步,而不是用于缓存进程间通信的数据
信号量表示资源的数量,控制信号量的方式有两种原子操作
- P操作:会把信号量减去1,相减后如果信号量<0,则表明资源已被占用,进程需阻塞等待;如果相减后>=0,则表示资源可用,进程正常运行
- V操作:会把信号量加上1,相加后如果信号量<=0,则表明当前有阻塞中的进程,会将进程唤醒运行;如果相加后>0,则表示当前没有阻塞中的进程
P操作是用于进入共享资源之前,V操作是用在离开共享资源之后,这两个操作必须是成对出现。
当信号量初始化为1,就代表互斥信号量,它保证共享内存在任何时刻只有一个进程在访问
例如:
- A执行了P操作,信号量变成了0,表示资源可用,然后就访问了共享资源
- 若此时B也想访问共享内存,进行了P操作,信号量变成了-1,表示资源已被占用,因此B被阻塞
- 直到A访问完,才会执行V操作,信号量恢复为1,才会唤醒阻塞中的B,然后B进行访问,访问完后,执行V操作,信号量恢复为默认值1。
当信号量初始化为0,就代表同步信号量,它保证进程A应在进程B之前进行
例如:进程A负责生产商品,而进程B负责检查商品,必须先进行进程A,才能进行进程B,执行有先后顺序。
- 如果B先执行,那么先执行P操作,信号量变为-1,表示A还没生产商品,B就处于阻塞等待状态
- 当A生产完商品,执行V操作,信号量变为0,就会唤醒阻塞在P操作的B,B就可以正常检查商品了
对于异常情况下的工作模式,就需要用 “信号” 的方式来通知
在Linux操作系统下,提供了几十种信号,代表不同的1意义。通过 kill -l 命令查询所有信号
例如:Ctrl+C产生SIGINT 信号,表示终止该进程
Ctrl+C产生SIGISTP信号,表示停止该进程,但并未结束
信号是进程间通信机制中唯一的异步通信机制, 因为可以在任何时候发送信号给某一进行,
一旦产生信号,我们有三种用户进程对信号的处理方式
- 执行默认操作。Linux对每种信号都规定了默认操作。
- 捕捉信号。我们可以为每一个信号定义一个信号处理函数,当信号发生时,我们就执行响应的信号处理函数。
- 忽略信号。当我们不希望处理某些信号时,就可以忽略,不做处理。
有两种信号无法捕捉也无法忽略,即SIGKILL和SEGSTOP,它们用于在任何时候中断或结束某一进程。
管道,消息队列,共享内存,信号量,信号都是在同一台主机上进行进程间通信,
socket通信,可以实现跨网络与不同主机上的进程之间通信,也可以在同主机上进程间通信
1.基于TCP协议的通信方式
2.基于UDP协议的通信方式
3.基于本地进程间通信方式