文章目录
Process Concept
操作系统需要多种多样的程序,并且计算机并不能同时运行所有的程序,所以需要分别的进程运行分别的程序。
一个进程包括以下内容:
- 文本字段(text section),即代码段
- program counter
- 栈内内存(程序生成的内存,如函数参数,本地变量,返回值等等)
- 数据段(data section),即全局变量等
- 堆内内存(动态申请的内存)
Process State Graph
指令状态含义:
new:进程正在被创建
running:指令正在被执行
waiting:进程等待某个啥时间的发生(如IO完成或收到信号)
ready:进程等待分配处理器
terminated:进程完成执行
。。。
Process Control Block(PCB)
PCB中包含着process的对应信息
- process state
- program counter
- CPU regs content
- CPU scheduling info
- 。。。
是一种overhead,有助于管理process
2. Process Scheduling
Process Schduling Queues
linux中的进程实际上以task(任务)的方法实现。其中,PCB被存储在一个名为task_struct的结构体中,并且以双向链表的结构进行储存,这个结构体字段包括:
而内核为当前正在运行的进程保存了一个current指针
- Job queue:系统中所有进程的集合,实际上就是所有task_struct串联起来的双向链表
- Ready queue(the CPU queue):已经进入ready状态的task_struct形成的队列,这样的队列实际上是链表,事实上弹出的顺序由调度程序(Scheduler)决定。被弹出的进程将会被CPU执行。从下图可以看到,当一个进程的时间片过去且
- wait queue:包括下文的Device queue(也被称为IO wait queue),当父进程创建子进程的时候,它会等待子进程接触,这个等待的时候就会进入wait queue,而等待中断也会进入wait queue
- Device queues:特定设备(磁盘,IO设备可能需要进程等待)
队列中存储着PCB
PCB中有指向nextPCB的指针,这样的链表结构构成了Job queue。
各种队列的工作方式如下图:
CPU Schedulers
CPU调度器至少100ms执行一次。
- Long-term scheduler:选出加载到内存中的
- Short-term scheduler:选出加载到CPU中的
windows/UNIX不需要Long-term scheduler。在交互式的计算机中,操作者决定了要执行的程序
- 当计算机内存不足以运行当前所有process中时,可以用交换区将进程(常常是优先级比较低的)换入磁盘:
bound process意味着进程在某些方面花费的时间比较多
- IO-bound process
- CPU-bound process
Context Switch
当CPU从用户程序因中断等因素切换到内核程序(并且常常在之后切换到其他用户程序)时,CPU需要保存当前用户进程的context,以便于下次再次执行时的恢复。这些信息大部分就出存在PCB中。而从一个process向另一个process切换就成为context switch,这个switch的时间是overhead,通常是微秒级的。
CPU在指令间切换的图例
SPARC可以提供硬件支持。它们又可以继续创建子进程,进而形成进程树。
3. Operations on Processes
进程的创建与结束
在程序执行中,一个进程可能创建若干新的进程,它们被称为父进程和子进程。
大部分操作系统通过进程id(pid)这一int值区分进程,可以被用于在内核中访问进程数据的index
linux系统典型进程图:
可以看到。名为systemd
的进程永远是所有用户进程的祖先,并且pid始终为1,它是系统的初始进程(从最初unix的System V init
转化而来)。是系统启动后最先创建的用户进程。
ssh是secure shell的缩写
之后,systemd产生若干子进程,途中的logind进程用于处理直接登入系统的客户(图中的客户使用了bash)。
命令ps -el
可以列出当前系统运行中的所有进程的信息,而pstree
可以以树形图的方法查看所有的进程。
子进程被父进程创建并执行某些任务,它可以直接从操作系统获取资源或者从父进程获取资源。
当子进程被创建的时候,父进程既可以等待子进程完成后再执行,也可以和子进程并行执行。而二者的地址空间也有2中可能:子进程与父进程共享同一份地址空间,或者子进程有自己新的地址空间。
在unix中,可以使用fork()
创建新进程,它使用和父进程相同的地址空间,他们将会在fork后并行执行,但有一点:当fork()返回0时,表示子进程;当其返回非0时,表示子进程已经返回父进程。
#include <sys/types.h>
#include <stdio.h>
#include <unistd.h>
int main(){
pid t pid;
/* fork a child process */
pid = fork();
if (pid < 0) { /* error occurred */
fprintf(stderr, "Fork Failed");
return 1;
}
else if (pid == 0) { /* child process */
execlp("/bin/ls","ls",NULL);
}
else { /* parent process */
/* parent will wait for the child to complete */
wait(NULL);
printf("Child Complete");
}
return 0;
}
上述程序在一个if-else中运行了父进程和子进程,实际上从fork开始,这个程序就分成了2个进程,字进程进入pid==0的一段,父进程进入wait(),子进程结束或调用exit()后返回到父进程wait处再执行。
所以事实上,fork函数在被调用时会有3种可能的返回值,并且必定会返回2种:
- 在父进程中,返回子进程的pid
- 在子进程中,返回0
- 复制进程错误时,返回一个负数值
此时,两个进程本应当并行执行,但是父进程执行到else内以后调用了wait,这就使得父进程停止运行,必须等待子进程显式或者隐式调用exit()后才能执行(相当于一个同步过程)
fork调用会让子进程复制父进程的pc(其实代表了程序运行的位置),其实就是fork处的pc,所以观感上是在fork后分道扬镳。
调用exec族的系统调用可以使得子进程执行新的程序,这意味着子进程将不再和父进程共享内存和其他数据,转而去执行对应调用的程序。在exec族调用执行后的代码都不会被执行,转而执行exec指定的程序,这些程序在运行完成后,会直接隐式调用exit(),返回父进程。
父进程可能出于多种原因结束子进程:
- 子进程的资源使用超出了给它分配的限额
- 分配给子进程的工作不再需要被执行
- 父进程被结束导致的级联终止
子进程已经结束,但父进程的wait()还没有调用的子进程被称为僵尸进程;而如果子进程没有结束,但父进程没有级联终止并且本身已经终止,这个子进程就被称为孤儿进程,他们会被init进程收留并由其周期性调用wait来在结束后终止。
4.Cooperating Processes
interprocess communication
IPC通过两种方式实现:
前者是共享内存(shared memory),后者是信息通信(message passing)
可以看到共享内存方法不需要内核的参与
共享内存:生产者与消费者
生产者与消费者是共享内存模型的重要概念
对应着两种buffer模式
- bounded buffer:空了不能读,满了不能写
- unbounded buffer:空了不能读,不会满可以一直写
信息传递:
使用信息传递至少要使用的源语(primitive):
send(p,message)
receive(q,message)
Indirect Communication(比较常用)
信息通过mailbox(或者是port)来进行通信:
- 每个mailbox都有一个id
- 进程只有在享有同一个mailbox时才能进行通信
- 此处通信链接(communication link)的性质:
- 见ppt
Synchronization(同步)
信息传递可以阻塞的(blocking)或非阻塞的。
其中blocking对应着synchronous,反之亦然,它们的两个进程通信操作特性见ppt。