操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

操作系统简介

嘿哈布尔! 我想提请您注意一系列文章——我认为是一本有趣的文学作品的翻译——OSTEP。 本材料非常深入地讨论了类 unix 操作系统的工作,即与进程、各种调度程序、内存和构成现代操作系统的其他类似组件一起工作。 你可以在这里看到所有材料的原件 这里. 请注意,翻译是非专业的(相当随意),但我希望我保留了一般意思。

关于这个主题的实验室工作可以在这里找到:

其他部分:

您也可以查看我的频道 电报 =)

警报! 本次讲座有一个实验室! 看 github

流程API

让我们看一个在 UNIX 系统中创建进程的示例。 它通过两个系统调用发生 叉子() и exec().

调用 fork()

操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

考虑一个进行 fork() 调用的程序。 其执行结果如下。

操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

首先,我们进入 main() 函数并将字符串打印到屏幕上。 该行包含进程标识符,在原始版本中称为 PID 或进程标识符。 该标识符在 UNIX 中用于指代进程。 下一个命令将调用 fork()。 此时,该流程的几乎完全相同的副本已创建。 对于操作系统来说,看起来系统上运行着同一个程序的 2 个副本,这将依次退出 fork() 函数。 新创建的子进程(相对于创建它的父进程)将不再执行,从 main() 函数开始。 应该记住,子进程并不是父进程的精确副本;特别是,它有自己的地址空间、自己的寄存器、指向可执行指令的指针等。 因此,返回给 fork() 函数调用者的值将会不同。 特别是,父进程将收到子进程的 PID 值作为返回值,而子进程将收到等于 0 的值。使用这些返回码,您可以分离进程并强制每个进程执行自己的工作。 然而,这个程序的执行并没有严格定义。 分成 2 个进程后,操作系统开始监视它们,并计划它们的工作。 如果在单核处理器上执行,其中一个进程(在本例中为父进程)将继续工作,然后子进程将接收控制权。 重新启动时,情况可能会有所不同。

调用等待()

操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

考虑以下程序。 在这个程序中,由于存在一个调用 等待() 父进程将始终等待子进程完成。 在这种情况下,我们将在屏幕上得到严格定义的文本输出

操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

exec() 调用

操作系统:三个简单的部分。 第 3 部分:流程 API(翻译)

考虑挑战 exec()。 当我们想要运行一个完全不同的程序时,这个系统调用非常有用。 这里我们将调用 执行vp() 运行 wc 程序,这是一个字数统计程序。 调用 exec() 时会发生什么? 此调用将传递可执行文件的名称和一些参数作为参数。 之后,加载该可执行文件中的代码和静态数据,并覆盖其自己的代码段。 剩余的内存区域,例如堆栈和堆,将被重新初始化。 之后,操作系统简单地执行该程序,并向其传递一组参数。 所以我们并没有创建一个新的进程,我们只是将当前正在运行的程序转化为另一个正在运行的程序。 在后代中执行 exec() 调用后,看起来好像原始程序根本没有运行。

这种启动复杂性对于 Unix shell 来说是完全正常的,并且允许该 shell 在调用后执行代码 叉子(),但在通话之前 exec()。 此类代码的一个示例是在启动程序之前调整 shell 环境以满足正在启动的程序的需要。

- 只是一个用户程序。 她向您展示邀请行并等待您在其中写一些内容。 在大多数情况下,如果您在那里写下程序的名称,shell 将找到它的位置,调用 fork() 方法,然后调用某种类型的 exec() 来创建一个新进程并使用等待()调用。 当子进程退出时,shell将从wait()调用返回并再次打印提示符并等待输入下一个命令。

fork() 和 exec() 拆分允许 shell 执行以下操作,例如:
wc 文件 > new_file.

在此示例中,wc 程序的输出被重定向到文件。 shell 实现这一点的方式非常简单 - 通过在调用之前创建一个子进程 exec(),shell关闭标准输出并打开文件 新文件,因此,进一步运行的程序的所有输出 wc 将被重定向到文件而不是屏幕。

Unix管道 以类似的方式实现,不同之处在于它们使用 pipeline() 调用。 在这种情况下,进程的输出流将连接到位于内核中的管道队列,另一个进程的输入流将连接到该管道队列。

来源: habr.com

添加评论