操作系统简介
嘿哈布尔! 我想提请您注意一系列文章——我认为是一本有趣的文学作品的翻译——OSTEP。 本材料非常深入地讨论了类 unix 操作系统的工作,即与进程、各种调度程序、内存和构成现代操作系统的其他类似组件一起工作。 你可以在这里看到所有材料的原件
关于这个主题的实验室工作可以在这里找到:
其他部分:
您也可以查看我的频道
警报! 本次讲座有一个实验室! 看
流程API
让我们看一个在 UNIX 系统中创建进程的示例。 它通过两个系统调用发生 叉子() и exec().
调用 fork()
考虑一个进行 fork() 调用的程序。 其执行结果如下。
首先,我们进入 main() 函数并将字符串打印到屏幕上。 该行包含进程标识符,在原始版本中称为 PID 或进程标识符。 该标识符在 UNIX 中用于指代进程。 下一个命令将调用 fork()。 此时,该流程的几乎完全相同的副本已创建。 对于操作系统来说,看起来系统上运行着同一个程序的 2 个副本,这将依次退出 fork() 函数。 新创建的子进程(相对于创建它的父进程)将不再执行,从 main() 函数开始。 应该记住,子进程并不是父进程的精确副本;特别是,它有自己的地址空间、自己的寄存器、指向可执行指令的指针等。 因此,返回给 fork() 函数调用者的值将会不同。 特别是,父进程将收到子进程的 PID 值作为返回值,而子进程将收到等于 0 的值。使用这些返回码,您可以分离进程并强制每个进程执行自己的工作。 然而,这个程序的执行并没有严格定义。 分成 2 个进程后,操作系统开始监视它们,并计划它们的工作。 如果在单核处理器上执行,其中一个进程(在本例中为父进程)将继续工作,然后子进程将接收控制权。 重新启动时,情况可能会有所不同。
调用等待()
考虑以下程序。 在这个程序中,由于存在一个调用 等待() 父进程将始终等待子进程完成。 在这种情况下,我们将在屏幕上得到严格定义的文本输出
exec() 调用
考虑挑战 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