操作系统第二次实验
Topic:操作系统概念验项目2:设置shell接口的C程序。
Requirements:
- 掌握操作系统用户命令界面的原理和实现方法。具备编写简单命令解释器的能力。
- 掌握通过键盘/显示器输入/输出设备进行命令输入、命令执行和结果输出。
- 了解输入/输出设备的计算机管理能力。
Procedure:
本项目的任务是设计一个C程序,作为一个shell接口,接受用户命令,然后在单独的进程中执行每个命令。Shell接口给用户一个提示符,然后输入下一个命令。
本次实验可以分为两个部分:
(1)创建子进程并在子进程中执行命令
(2)修改shell以添加历史功能。
实验步骤:
1. 第一部分:创建子进程并执行命令
- 修改main()函数,使用fork()系统调用创建一个子进程,并执行用户指定的命令。
- 解析用户输入的命令,将命令分割成单独的标记,并将这些标记存储在字符字符串数组中。
- 使用execvp()函数执行用户命令,将命令和参数传递给execvp()函数。
- 根据用户是否在命令末尾添加了&符号来确定父进程是否等待子进程退出。
2. 第二部分:添加历史功能
- 修改shell界面程序,添加一个历史功能,允许用户访问最近输入的命令。
- 使用一个命令历史缓冲区来存储用户输入的命令。
- 实现两种从命令历史中检索命令的技术:
- 当用户输入!!时,执行最近的命令。
- 当用户输入单个!后跟一个整数N时,执行历史记录中第N个命令。
- 在命令执行后,将该命令添加到历史缓冲区中,并显示在用户的屏幕上。
3. 实验设计与实现
设计思路:
- 在第一部分中,我们需要使用fork()系统调用创建一个子进程,并使用execvp()函数执行用户命令。
- 在第二部分中,我们需要使用一个命令历史缓冲区来存储用户输入的命令,并实现两种从历史记录中检索命令的技术。
4. 代码实现
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#define MAX_LINE 80 /* The maximum length command */
#define MAX_HISTORY 10 /* The maximum number of commands in history */
char* history[MAX_HISTORY]; /* Array to store command history */
int history_count = 0; /* Counter for command history */
void add_to_history(char* command) {
if (history_count < MAX_HISTORY) {
history[history_count] = strdup(command); // 将命令复制到历史记录数组中
history_count++;
} else {
free(history[0]);
for (int i = 0; i < MAX_HISTORY - 1; i++) {
history[i] = history[i + 1];
}
history[MAX_HISTORY - 1] = strdup(command);
}
}
void print_history() {
for (int i = history_count - 1; i >= 0; i--) {
printf("%d %s\n", history_count - i, history[i]); // 打印历史记录
}
}
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);
char command[MAX_LINE];
fgets(command, sizeof(command), stdin);
command[strcspn(command, "\n")] = '\0'; /* Remove trailing newline character */
if (strcmp(command, "exit") == 0) {
should_run = 0;
continue;
}
if (strcmp(command, "history") == 0) {
print_history();
continue;
}
if (strcmp(command, "!!") == 0) {
if (history_count == 0) {
printf("No commands in history.\n");
continue;
}
strcpy(command, history[history_count - 1]); // 执行最近的命令
}
if (command[0] == '!') {
int index = atoi(&command[1]);
if (index > 0 && index <= history_count) {
strcpy(command, history[history_count - index]); // 执行历史记录中的第N个命令
} else {
printf("No such command in history.\n");
continue;
}
}
add_to_history(command); // 将命令添加到历史记录中
int background = 0;
if (command[strlen(command) - 1] == '&') {
background = 1;
command[strlen(command) - 1] = '\0';
}
char *token = strtok(command, " ");
int i = 0;
while (token != NULL) {
args[i] = token;
token = strtok(NULL, " ");
i++;
}
args[i] = NULL;
pid_t pid = fork();
if (pid < 0) {
fprintf(stderr, "Fork failed.\n");
return 1;
} else if (pid == 0) {
execvp(args[0], args); // 在子进程中执行命令
fprintf(stderr, "Command execution failed.\n");
return 1;
} else {
if (!background) {
wait(NULL); // 等待子进程结束
}
}
}
return 0;
}
5. 实验结果
本次实验环境是Ubuntu Linux和termius(SSH远程控制客户端)。
首先我们把关键代码写入我们的simple.c的文件当中,可以用一下命令:
touch simple.c(创建simple.c的文件)
vim simple.c(编辑simple.c的内容,我们可以把代码复制到文件当中)
最后退出保存(:wq)
用 gcc simple.c来编译文件,如果没有警告或报错我们的关键代码没有语法错误。
我们用gcc命令编译后会生成一个shell可执行文件:
完成任务要求后要进行测试,我们以shell编程模式运行这个可执行文件,在终端中输入一下命令:
./a.out
在实验运行过程中,我们可以通过以下步骤测试实验结果:
- 在shell界面中输入命令,并观察命令是否被正确执行。
-
使用"history"命令查看命令历史记录,并验证最近命令和指定命令的执行结果。
-
输入"!!"命令,验证是否执行最近的命令。
-
输入"!N"命令,其中N是一个整数,验证是否执行历史记录中第N个命令。
实验结论:
通过本实验,掌握了操作系统用户命令界面的原理和实现方法,并成功设计了一个简单的shell界面,实现了命令的执行和历史记录功能。