学习《操作系统导论》01

抽象:进程(原书第四章)

主要介绍的是“进程”这一概念,分别从进程的概念、进程的相关API、进程的状态以及操作系统中针对进程的一些必要的数据结构设计进行了介绍,也仅仅只是初步介绍,并没有过于深入,深入的内容在后续章节中会详细展开。

关键问题:如何提供有许多CPU的假象?
操作系统通过虚拟化CPU来提供这种假象,说白了就是:让一个进程只运行一个时间片,然后切换到其他进程,由于切换速度很快,所以看上去好像每个程序都在同时运行着,这就是所谓的“时分共享CPU技术”,缺点就是损失性能,因为CPU是共享的,所以每个进程都会执行的相对慢一些(涉及到一些进程切换工作的损耗)。

要实现CPU的虚拟化,那么操作系统就需要一些低级机制和高级智能,低级机制一般都是一些方法和协议,是具体的实现,而高级智能更像是策略,比如经常听到的上下文切换,这里面的如何切换就依赖于策略的制定,而具体在切换时切换到哪个就依赖具体的底层机制去实现了。

时分共享、空分共享
前面说到的对于一些共享的资源,按照时间段进行划分,每个时间段内由某个实体进行使用,这种就是所谓的时分共享技术,比如计算机中的CPU。与之对应的就是空分共享,这个的典型就是计算机的磁盘,此时资源在空间上被划分给希望使用它的人,但是一旦分配之后,在拥有者释放之前,不可以再将其分配个其他实体使用。


程序的抽象:进程

开发人员编写的程序源代码,如果静静躺在磁盘中,那么它只是一种静态的程序;此时如果有操作系统对该程序调度运行了,那么这种运行起来的动态的程序,在操作系统中就被抽象为:进程。

换句话说:进程实际上就是操作系统针对这些运行着的程序的一种抽象。

对于一个OS来说,如果想要调用程序运行,有两个必须的硬件是必定要依赖的:

  • 内存:这个是存放指令的地方,程序在计算机中执行的时候,实际上就是一条条指令的集和,这些指令一般都会放到内存中便于CPU去取,这里就需要进程能够访问内存,以便于向内存中存放指令
  • 寄存器:寄存器有不同的种类,每一种都有各自的用途,比如PC寄存器,它就可以记录当前进程执行到哪一步了,这个对于进程切换时会非常有用,那对应的就需要进程能够访问它,在必要的时候 ,将自身的一些状态数据存入到对应的寄存器当中。

分离 “策略”和“机制”
在现代许多操作系统中,一个通用的设计范式是:把高级的“策略”和低级的“机制”区分开来进行设计:
可以将这里的“策略”看成是【为系统的“how”提供答案】,比如在操作系统中如何进行上下文的切换等等这类问题。
而“机制”则可以看成是【为系统的which“提供答案】,比如操作系统中此时应该调度哪一个进程。
分开设计的好处就是:如果有需要,我们可以轻松的改变策略,而不用调整机制,这是一种模块化的设计,是一种通用的软件设计原则。底层的机制一般不会轻易变动,上层的策略可以灵活选择。


进程API

简单起见,只是介绍了进程常见的一些API接口,未做具体深入细节讲解:

  • 创建create
  • 销毁destroy
  • 等待wait
  • 其他控制miscellaneous control:比如让进程暂停一段时间后,再恢复运行
  • 状态status

创建进程

  1. 首先代码和相关静态数据存放在磁盘,操作系统准备调用程序。
  2. 将这些代码和静态数据装入到内存中(早期的操作系统是一次性全部加载进去,现在的操作系统实行的是惰性加载)。
  3. 将静态数据和程序装入内存中后,还需要做一些其他的分配工作,比如:栈内存和堆内存的分配
  4. 再进行一些必要的输入输出(I/O)相关的初始化操作
  5. 上面都准备完成后,操作系统才会进入程序的入口处:main;此时OS将CPU的控制权转移到新创建的进程中,程序开始执行

image


进程状态

这里列了主要的三个状态,实际状态肯定不止三个:

  • running
  • ready:进程已经准备好,但是由于某些原因操作系统并为执行它
  • blocked:常见的比如:IO读取出现的阻塞状态
    image

进程的数据结构

这里说的主要是进程在操作系统层面管理时的一种数据结构,因为操作系统需要追踪进程的一些重要指标信息,操作系统在切换进程时,对于停下来的进程需要用寄存器保存一些必要的上下文信息,这些信息实际上也反映了操作系统对进程的一些抽象,书本中给出了一个xv6的进程数据结构的示例代码:

// xv6寄存器在进程停止以及随后的重启过程中
// 会保存并重新加载下面这些上下文信息
struct context {
	int eip;
	int esp;
	int ebx;
	int ecx;
	int edx;
	int esi;
	int edi;
	int ebp;
};
// 进程可能处于的一些状态
enum proc_state { UNUSED, EMBRYO, SLEEPING,
RUNNABLE, RUNNING, ZOMBIE }; //可以看到状态有六个
// xv6追踪每个进程的相关信息:
// 包括进程的寄存器上下文以及进程状态
struct proc {
	char *mem; // 进程的内存开始地址
	uint sz; // 进程的内存大小
	char *kstack; // 内核堆栈的底部
	// for this process
	enum proc_state state; // 进程状态
	int pid; // 进程id
	struct proc *parent; // 父进程
	void *chan; // If non-zero, sleeping on chan
	int killed; // If non-zero, have been killed
	struct file *ofile[NOFILE]; // Open files
	struct inode *cwd; // Current directory
	struct context context; // Switch here to run process
	struct trapframe *tf; // Trap frame for the current interrupt
};

有时候系统会有一个初始(initial)状态,表示进程在创建时处于的状态。另外,一个进程可以处于已退出但尚未清理的最终(final)状态(在基于 UNIX 的系统中,这称为僵尸状态)。这个最终状态非常有用,因为它允许其他进程(通常是创建进程的父进程)检查进程的返回代码,并查看刚刚完成的进程是否成功执行(通常,在基于 UNIX 的系统中,程序成功完成任务时返回零,否则返回非零)。完成后,父进程将进行最后一次调用(例如,wait()),以等待子进程的完成,并告诉操作系统它可以清理这个正在结束的进程的所有相关数据结构。

补充:数据结构-进程列表
它主要就是用来跟踪系统中正在运行的程序,有时它也被称为进程控制快(PCB)。

posted @ 2023-04-20 20:01  StillLoving  阅读(59)  评论(0)    收藏  举报