操作系统详解(3)——进程、并发和并行

系列文章:
操作系统详解(1)——操作系统的作用
操作系统详解(2)——异常处理(Exception)

文章目录

  • 进程的定义
    • 例子
  • 上下文
    • 上下文内容
    • 调度
    • context switch
  • 并发
    • 逻辑控制流
    • 并发流
  • 系统调用错误处理
  • 总结

进程的定义

进程是计算机科学中 最深刻、最成功的概念之一。

进程的经典定义就是一个执行中程序的实例。系统中的每个程序都运行在某个进程的 上下文(context) 中。

例子

以shell为例, 当用户输入一个可执行文件时,shell会创建一个新的进程, 然后在这个新进程的上下文中运行这个可执行目标文件。
在可视化图形操作系统中,当我们双击软件的图标时,发生的是同样的事.

上下文

上下文内容

上下文是由程序正确运行所需的状态组成的。
是内核重新启动一个被抢占的进程所需的状态。
包括:

  • 该程序的代码以及数据
  • PC(程序计数器), 通用目的寄存器, 文件描述符
  • 栈(user and kernel)
  • 环境变量
  • kernel的数据(进程表, 页表, 文件表)

调度

内核可以抢占当前的进程, 并重新开始一个先前被抢占的进程, 这种决策叫做调度, 是由内核中成为调度器的代码处理.

进程之间的切换通过 context switch 实现.
context switch 是较高形式的异常控制流, 以异常处理机制为基础

context switch

上下文切换何时会发生?

  • 内核代表用户执行系统调用
    • read, sleep, etc. 通常导致call的进程被阻塞
    • 即使没有阻塞, 内核也可以执行context switch, ,而不是将控制返回给调用进程
  • interrupt
    • 比如说 Timer interrupt

以下是一个上下文切换的例子:
操作系统详解(3)——进程、并发和并行_第1张图片

磁盘取数据要用一段相对较长的时间(数量级为几十毫秒),所以内核执行从进程A到进程B的上下文切换,而不是在这个间歇时间内等待,什么都不做。
在切换之前,内核正代表进程A在用户模式下执行指令.在切换的第一部分中, 内核代表进程A在内核模式下执行指令。然后在某一时刻,它开始代表进程B(仍然是内核模式下)执行指令。在切换之后,内核代表进程B在用户模式下执行指令。

随后,进程B在用户模式下运行一会儿,直到磁盘发出一个中断信号,表示数据已经从磁盘传送到了内存。内核判定进程B已经运行了足够长的时间,就执行一个从进程B到进程A的上下文切换,将控制返回给进程A中.
紧随在系统调用read之后的那条指令。进程A继续运行,直到下一次异常发生,依此类推。

并发

逻辑控制流

PC存储着CPU将要执行的指令, 多个进程分别有各自的PC. CPU只是按照顺序执行这些指令, 而这个指令序列就是control flow.
操作系统详解(3)——进程、并发和并行_第2张图片

并发流

并发: Concurrency
一个逻辑流的执行在时间上与另一个流重叠,称为并发流(concurrent flow)

流X和Y互相并发,当且仅当X在Y开始之后和Y 结束之前开始,或者Y在X开始之后和X结束之前开始
否则,则称为顺序执行.

例子:
操作系统详解(3)——进程、并发和并行_第3张图片
并发: A&B, A&C
顺序: B&C

Tips: 物理意义上, 并发的进程之间并不重叠(执行上是错位的), 但是我们可以看做多个进程之间是平行地在执行.

一个进程和其他进程轮流运行的概念称为多任务(multitasking)。
一个进程执行它的控制流的一部分的每一时间段叫做时间片(time slice) 因此,多任务也叫做时间分片(time slicing).

Attention: 并发流的思想与流运行的处理器核数或者计算机数无关。如果两个流在时间上重叠,那么它们就是并发的,即使它们是运行在同一个处理器上。
作为对比, 阐述并行的定义:
如果两个流并发地运行在不同的处理器核或者计算机上,那么我们称它们为并行流(parallel flow).

系统调用错误处理

当 Unix 系统级函数遇到错误时,
它们通常会返回-1,并
设置全局整数变量 error 来表示什么出错了。

检查错误对程序员是个好习惯

例子:

// 调用Unix fork函数
if ((pid = fork()) < 0) {
    fprintf(stderr, "fork error: %s\n", 
        strerror(errno));
    // strerror函数返回一个一个文本串,描述了和某个errno值相关联的错误
 	exit(0);
}
// 定义错误报告函数, 可以简化代码:
void unix_error(char *msg) /* unix-style error */
{
    fprintf(stderr, "%s: %s\n", msg, strerror(errno));
 	exit(0);
}
// 进一步包装:
pid_t Fork(void)
{
 	pid_t pid;
 	if ((pid = fork()) < 0)
 		unix_error("Fork error");
 	return pid;
}

总结

介绍了进程的概念以及并发的内涵,并区分了并发与并行的区别。

下一章将讲解进程的控制,包括子进程的创建(fork), 子进程回收(waitpid), 加载并运行程序(execve)

你可能感兴趣的:(《深入了解计算机系统》,linux,笔记,学习)