目前我在学校当助教,这是操作系统课程的第一个项目。
此项目包括设计一个 C 程序作为 shell 接口,该接口接受用户命令,然后在单独的进程中执行每个命令。您的实现将支持输入和输出重定向,以及管道作为一对命令之间的 IPC 形式。完成此项目将涉及使用 UNIX fork()、exec()、wait()、dup2() 和 pipe() 系统调用,并且可以在 Linux 系统上完成。
一、概述
shell 界面会向用户提示,之后输入下一个命令。下面的示例说明了提示符 osh>和用户的下一个命令:cat prog.c。(此命令使用 UNIX cat 命令在终端上显示文件 prog.c。
osh>cat prog.c
实现 shell 接口的一种技术是让父进程首先读取用户在命令行上输入的内容(在本例中为 cat prog.c),然后创建一个单独的子进程来执行该命令。除非另有指定,否则父进程将等待子进程退出,然后再继续。但是,UNIX shell 通常还允许子进程在后台运行或并发运行。为此,我们在命令末尾添加一个 & 符号 (&)。因此,如果我们将上述命令重写为
osh>cat prog.c &
父进程和子进程将同时运行 使用 fork() 系统调用创建单独的子进程,并使用 exec() 系列中的一个系统调用执行用户的命令。提供命令行 shell 的一般操作的 C 程序如下所列代码所示:
#include <stdio.h>
#include <unistd.h>
#define MAX LINE 80 /* The maximum length command */
int main(void)
{
char *args[MAX LINE/2 + 1]; /* command line arguments */
int should run = 1; /* flag to determine when to exit program */
while (should run) {
printf("osh>");
fflush(stdout);
/**
* After reading user input, the steps are:
* (1) fork a child process using fork()
* (2) the child process will invoke execvp()
* (3) parent will invoke wait() unless command included &
*/
}
return 0;
}
main() 函数显示提示 osh->,并概述了在读取用户输入后要执行的步骤。main() 函数连续循环,只要应该运行等于 1;当用户在提示符下进入退出时,您的程序将设置为应运行为0并终止。本项目分为几个部分:
- 创建子进程并在子进程中执行命令
- 提供历史记录功能
- 添加对输入和输出重定向的支持
- 允许父进程和子进程通过管道进行通信
二、在子进程中执行命令
第一个任务是修改 main() 函数,以便分叉子进程并执行用户指定的命令。这将需要将用户输入的内容解析为单独的令牌,并将令牌存储在字符串数组中。例如,如果用户在 osh> 提示符下输入命令 ps -ael,则存储在 args 数组中的值为:
args[0] = "ps"
args[1] = "-ael"
args[2] = NULL
此 args 数组将传递给 execvp() 函数,该函数具有以下原型:
execvp(char *command, char *params[])
此处,命令表示要执行的命令,参数存储此命令的参数。对于此项目,execvp() 函数应作为 execvp(args[0], args) 调用。请务必检查用户是否包含 &以确定父进程是否要等待子进程退出。
三、重定向输入和输出
应修改 shell 以支持">"和"<"重定向运算符,其中">"将命令的输出重定向到文件,"<"将输入重定向到文件中的命令。例如,如果用户输入
osh>ls > out.txt
ls 命令的输出将重定向到文件 out.txt。同样,输入也可以重定向。例如,如果用户输入
osh>sort < in.txt
文件中的.txt将用作排序命令的输入。管理输入和输出的重定向将涉及使用 dup2() 函数,该函数将现有文件描述符复制到另一个文件描述符。例如,如果 fd 是文件输出的文件描述符.txt,则调用
dup2(fd, STDOUT FILENO);
将 fd 复制到标准输出(终端)。这意味着对标准输出的任何写入实际上都将发送到out.txt文件。您可以假定命令将包含一个输入或一个输出重定向,并且不包含两者。换句话说,您不必关心命令序列,例如排序<入.txt >出.txt。
四、通过管道进行通信
对 shell 的最后一个修改是允许一个命令的输出作为使用管道的另一个命令的输入。例如,以下命令序列
osh>ls -l | less
将命令 ls -l 的输出用作 less 命令的输入。ls 和 less 命令将作为单独的进程运行,并将使用 UNIX pipe() 函数进行通信。也许创建这些单独进程的最简单方法是让父进程创建子进程(这将执行 ls -l)。此子进程还将创建另一个子进程(执行较少),并将在自身和它创建的子进程之间建立管道。实现管道功能还需要使用 dup2() 函数,如上一节所述。最后,尽管可以使用多个管道将多个命令链接在一起,但您可以假定命令将仅包含一个管道字符,并且不会与任何重定向运算符组合在一起。