进程
1. 进程概述
1.1 程序与进程
通俗来讲,进程是系统中运行的程序,但是进程不仅包括程序代码, 还包括程序状态如程序计数器和寄存器的的内容等。程序在内存中主要包括代码段,数据段和堆栈段。
程序是可执行文件,当可执行文件载入到内存后,这个程序就变成进程。加载可执行文件的方式:1)双击,2)在命令行输入可执行文件的名称
1.2 程序的状态
- 创建
- 就绪:等待处理器调度
- 运行:正在运行
- 等待:等待某个事件
- 终止:完成执行
1.3 进程的调度
进程进入系统后,会被添加到队列中。队列用链表的方式实现,头节点包括指向第一个进程的头指针和指向最后一个进程的尾指针。
队列包括
- 就绪队列:包括所有就绪的进程
- 设备队列:等待I/O资源或等待其他硬件资源的进程
调度进程
当一个进程运行时,可能发生的事件:
- 发出I/O请求,进入I/O队列
- 创建子进程,等待子进程完成
- 响应中断,释放CPU放回就绪队列
上下文切换
当切换进程运行时,系统需要保存当前运行进程的上下文。上下文保存在PCB(Process Control Block)中, 包括CPU寄存器的值,进程状态等。
2. 进程运行与多进程
2.1 进程的创建与终止
2.1.1 进程创建
操作系统中以进程标识符(pid)来唯一标识进程。
每个进程都可以创建其他进程,创建的进程称为子进程,原进程称为父进程,同一个进程创建的子进程互称兄弟进程,从而形成进程树。
子进程的资源分配
子进程需要被分配资源,包括CPU时间,内存,文件,I/O设备。
- 子进程的资源由操作系统分配
- 子进程的资源从父进程那里获得
父进程与子进程的执行方式
- 父进程与子进程并发执行
- 父进程等待子进程执行完(父进程调用
wait()
函数,把自己移出就绪队列,等到子进程终止)
子进程的地址空间
- 子进程可以完全拷贝父进程的内存空间
- 子进程加载另一个新程序
UNIX系统实现
父进程通过调用fork(
)函数创建子进程,子进程复制父进程全部内存空间,与父进程有相同的代码端和数据段,允许父进程与子进程之间的通信。两个进程都继续执行后续代码。父进程的fork()
返回子进程的id,子进程的fork()
返回0。
当进程调用exec()
函数时,标识进程加载其他可执行文件,运行新程序,取代原地址空间的内容。两个进程仍可以进行通信并运行不同的任务
2.1.2 进程终止
进程通过系统调用exit()
函数请求终止。进程结束后,返回状态值到父进程,同时所有进程资源会被操作系统释放(在Windows中,父进程可以通过调用TerminateProcess()
函数来终止子进程)。
一个进程终止,所有的子进程也应终止,由操作系统完成
2.2 进程间通信
进程间通信主要包括两种基本模式:共享内存与消息传递的方式。共享内存是一块供进程间共享的内存区域,进程通过对共享内存的写入和读出交换数据信息。消息传递是进程间通过传递消息的方式来实现通信。
一般来说,共享内存快于消息队列,因为消息的传输需要系统调用,会消耗时间因为涉及到内核。而共享内存只在创建的时候需要系统调用,此后进程都可以直接对共享内存进行正常访问。而对于分布式系统来说,消息传递的方式比共享内存更易于实现。
-
共享内存
为了允许进程的并发访问,共享内存设置了缓冲区,一个进程写入,一个进程读出并清除。 -
消息传递
send()函数与receive()函数
-
直接与间接通信
- 直接
- 每个进程指明发送端或接收端id, 如果P发送消息给Q进程, 那么P与Q进程分别调用
- send(Q, message) 向进程Q发送消息
- receive(P, message) 从进程P接受消息
- 每个进程指明发送端或接收端id, 如果P发送消息给Q进程, 那么P与Q进程分别调用
- 间接
- 通过邮箱来发送和接收消息. 进程可以向邮箱写入或者读出并清除消息, 如果邮箱用A表示, 那么函数实现为
- send(A, message)
- receive(A, message)
- 多进程同时读消息时, 系统处理的方式包括
- 一个通信链路只能与两个进程相关联
- 一次最多一个进程调用receive()
- 系统选择进程来读取消息. 可以是随机, 也可以由发送端指定接收端
- 通过邮箱来发送和接收消息. 进程可以向邮箱写入或者读出并清除消息, 如果邮箱用A表示, 那么函数实现为
- 直接
-
阻塞与非阻塞传递
- 阻塞
- 阻塞发送: 发送端阻塞直到接收端接收到消息
- 阻塞接收: 接收端阻塞直到接收到消息
- 非阻塞
- 非阻塞发送: 发送端发送完消息后直接返回
- 非阻塞接收: 没有接收到消息也返回
- 阻塞
-
消息临时队列缓存
队列缓存实现方式与阻塞相关- 零容量: 不允许消息停留在网络中, 发送端必须阻塞
- 有限容量: 在队列满之前, 发送端可以非阻塞发送. 但是等队列容量满之后, 发送端需要阻塞发送
- 无限容量: 发送端可以一直非阻塞发送
- 管道
- 普通管道
- 普通管道只能单向通信, 如果需要双向通信, 需要创建两个管道
- 进程包括两个指针, 一个指向管道写入端, 一个指向管道读出端
- 因为管道是匿名的, 所以只用于父子进程之间的通信, 子进程复制父进程的打开文件, 所以也继承了父进程的管道
- 命名管道(FIFO)
- 通信可以是双向的, 但是是半双工通信的, 所以通常使用两个FIFO管道