内容简介:编写一个 Unix Shell - 第一部分
(译自 https://indradhanush.github.io)
我正在 RC(Recurse Center,纽约市的一个 程序员 教育特色社区,译者注)尝试一个项目,就是编写 UNIX shell。 这是将要发布的一系列帖子的第一篇。
什么是 Shell?
很多人都写了这一点,所以我不会太涉及这个定义的细节。 然而,用一句话来概括 -
Shell 是一个接口,使您可以与操作系统内核进行交互。
Shell 如何工作?
Shell 解析用户输入的命令并执行此操作。 为了能够做到这一点,shell 的工作流程是这样的:
- 启动 shell
- 等待用户输入
- 解析用户输入
- 执行命令并返回结果
- 返回到
2。
所有这一切都有一个重要的原因:进程。 Shell 是父进程。 它是我们程序的 main 线程,等待用户输入。 但是,我们不能在 main 线程本身中执行命令,原因如下:
- 错误的命令会导致整个 shell 停止工作。 我们想避免这种情况。
- 独立命令应该有自己的进程块。 我们称之为隔离,属于容错的范畴。
Fork(复刻/分叉)
为了能够避免这种情况,我们使用系统调用 fork 。 我原以为自己理解 fork ,直到用它写了四行代码。
fork 创建当前进程的副本。 该副本称为 child ,系统中的每个进程都有与之相关联的唯一进程标识(pid)。 我们来看看下面的代码:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main() {
pid_t child_pid = fork();
// The child process
if (child_pid == 0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
} else {
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
}
return 0;
}
fork 系统调用返回两次,每个进程一次。 起初听起来与直觉相反。 那就让我们来看看在底层发生的事情。
-
通过调用
fork,我们在程序中创建一个新的分支。 这与传统的if-else分支不同。fork创建一个当前进程的副本,并创建一个新的进程。 结束的系统调用返回了子进程的进程 ID。 -
在
fork调用成功之后,子进程和父进程(我们代码的主线程)同时运行。
为了让您更好地了解程序流程,请查看此图:
fork() 创建一个新的子进程,但与此同时,父进程的执行并不停止。 子进程开始并完成执行的过程,独立于父进程的执行,反之亦然。
在我们进一步进行之前快速声明一下, getpid 系统调用返回当前的进程 id。
如果编译并执行代码,您将得到类似于以下内容的输出:
### Parent ### Current PID: 85247 and Child PID: 85248 ### Child ### Current PID: 85248 and Child PID: 0
在 ### Parent ### 下的块中,当前进程 ID 为 85247 ,子进程的为 85248 。 需要注意的是子进程的 pid 比父进程大,这意味着子进程是在父进程之后创建的。
在 ### Child ### 下的块中,当前进程 ID 为 85248 ,这与前一个块中的子进程的 pid 相同。 但是,这里子进程的 pid 是 0 。
实际数字在每次执行时可能有所不同。
当在 代码第 9 行 已经明确地为 child_pid 赋值后, child_pid 在同一个执行流程中怎么能够获取到两个不同的值?有这种想法情有可原。 然而,记住,调用 fork 创建出了一个与当前进程相同的新进程。 因此,在父进程中, child_pid 是刚刚创建的子进程的实际值,子进程本身没有自己的子进程,结果 child_pid 的值为 0 。
因此,我们从第 12 行到第 16 行定义的 if-else 块是必需的,用来控制在子代与父级分别执行的代码。 当 child_pid 为 0 时,代码块将在子进程下执行,而 else 块将在父进程下执行。 块被执行的顺序无法确定,取决于操作系统的调度器。
确定性简介
让我来介绍一下 sleep 系统调用。 引用 linux 手册:
sleep – suspend execution for an interval of time
时间间隔以秒为单位。
让我们给父进程添加一个 sleep(1) 调用,在代码的 else 块:
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main() {
pid_t child_pid = fork();
// The child process
if (child_pid == 0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
} else {
sleep(1); // Sleep for one second
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
}
return 0;
}
而执行此操作时,输出将类似于:
### Child ### Current PID: 89743 and Child PID: 0
and after a span of 1 second, you would see
### Parent ### Current PID: 89742 and Child PID: 89743
每次执行代码时,都会看到相同的行为。 这是因为我们在父进程中执行了阻塞式的 sleep 调用, 于此同时操作系统调度器寻找到空闲 CPU 时间片执行子进程。
类似地,如果把 sleep(1) 调用加到子进程,即代码的 if 块,你会立即注意到这个父程序块的输出显示在控制台。 您也会注意到程序已经终止。 而子块的输出被转储到 stdout 。 类似于:
$ gcc -lreadline blog/sleep_child.c -o sleep_child && ./sleep_child ### Parent ### Current PID: 23011 and Child PID: 23012 $ ### Child ### Current PID: 23012 and Child PID: 0
这段程序的源码在sleep_child.c.
这是因为父进程在 printf 语句之后无事可做,被终止。 然而,子进程先在 sleep 调用中被阻塞一秒钟,再执行 printf 语句。
确定性的正确方式
然而,使用 sleep 来控制你的进程执行流程并不是最好的方法, 因为如果你做一次 n秒 的 sleep 调用:
- 如何保证无论你等待任何操作,都能在这
n秒内完成执行。 - 如果你所等待的完成时间比
n秒早很多,怎么办? 在这种情况下是不必要的等待。
一个更好的方法是使用 wait 系统调用(或变体之一)。 我们来使用 waitpid 系统调用。 它需要以下参数:
- 程序所等待进程的进程ID。
- 一个用于填充进程终止方式信息的变量。
- 可选标志位,定制
waitpid的行为
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
int
main() {
pid_t child_pid;
pid_t wait_result;
int stat_loc;
child_pid = fork();
// The child process
if (child_pid == 0) {
printf("### Child ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
sleep(1); // Sleep for one second
} else {
wait_result = waitpid(child_pid, &stat_loc, WUNTRACED);
printf("### Parent ###\nCurrent PID: %d and Child PID: %d\n",
getpid(), child_pid);
}
return 0;
}
当执行这个程序,你会注意到子块立即打印出来然后等待一个短暂的时刻(我们把 sleep 添加在 printf 后)。 父进程等待子进程执行完成后,它可以自由地执行自己的命令。
以上是第一部分。本文包含的所有代码都可以参看 这里 。 在下一篇文章中,我们将探讨如何从用户输入读取命令并执行。 敬请关注。
致谢
感谢 Saul Pwanson 帮助我理解 fork 的行为和 Jaseem Abid 帮助阅读草稿并提出修改建议。
参考资料
以上就是本文的全部内容,希望对大家的学习有所帮助,也希望大家多多支持 码农网
猜你喜欢:- 基于H5 canvas API 编写的扫雷游戏第一部分:资源加载
- Hive实战技能 第一部
- Go语言接口(第一部分)
- CVPR 2018摘要:第一部分
- 神经风格迁移指南(第一部分)
- WebGL基础教程:第一部分
本站部分资源来源于网络,本站转载出于传递更多信息之目的,版权归原作者或者来源机构所有,如转载稿涉及版权问题,请联系我们。
The Book of CSS3
Peter Gasston / No Starch Press / 2011-5-13 / USD 34.95
CSS3 is the technology behind most of the eye-catching visuals on the Web today, but the official documentation can be dry and hard to follow. Luckily, The Book of CSS3 distills the heady technical la......一起来看看 《The Book of CSS3》 这本书的介绍吧!