多进程编程

一、实验题目

·设计一个 C 程序作为一个 shell 接口

它接受用户命令,然后在单独的进程中执行每个命令。这个项目可 以在任何 Linux、UNIX 或 Mac OS X 系统上完成。

shell 界面给用户一个提示,然后输入下一个命令。

示例:

osh> cat prog.c

提示符 osh> 和用户的下一个命令: 

cat prog.c

实现 shell 接口的一种技术是让父进程首先读取用户在命令行中输入 的内容(在本例中为 cat prog.c),然后创建一个单独的子进程来执行该 命令。 

UNIX shell 通常还允许子进程在后台或同时运行,我们在命令末尾 添加一个与号 (&)命令重写为 

osh>  cat  prog.c &

父进程和子进程将同时运行。

用户在 osh> 提示符下输入命令 ps -ael,存储在 args 数组中的值是:

                arg[0]=“ps”

                arg[1]=“-ael”

                arg[2]=“NULL”

这个 args 数组将被传递给 execvp() 函数,它具有以下原型:

execvp (char *command, char *params[]) ;

这里,command 表示要执行的命令,params 存储此命令的参数。对于 这个项目,execvp() 函数应该被调用为 execvp(args [0], args)。务必检查 用户是否包含 & 以确定父进程是否要等待子进程退出。

·创建历史记录

下一个任务是修改 shell 接口程序,使其提供history功能,允许用户访问最近输入的命令。通过使用该功能,用户最多可以访问 10 个命令。命令会从1开始连续编号,超过10会继续编号。例如,如果用户输入了35条命令,则最近的10条命令将编号为26到35。

        1.当用户输入 !! 执行历史记录中的最新命令。

        2.当用户输入单 ! 后跟整数 N,执行历史记录中的第 N 个命令。

·基本的错误处理

如果历史中没有命令,输入 “!!”应该会产生一条消息“No commands in history”。

如果没有与用单个 ! 输入的数字对应的命令,程序应该输出“No such command in history”。

二、相关原理与知识

·进程的概念

        多道程序系统出现后,为了刻画系统内部出现的动态情况,描述系统内部各道程序的活动规律而引进的一个概念。所有的多道程序操作系统都建立在进程的基础上。

·进程切换

从正在运行的进程中收回处理器,再调度待运行进程(ready queue)来占用处理器。从某个进程收回处理器:把进程存放在处理器的寄存器中的中间数据找个地方存起来,从而将处理器的寄存器腾出来让其他进程使用。

进程的切换实质上就是 被中止运行进程 与 待运行进程 上下文的切换。在进程未占用处理器时,进程的上下文存储在进程的私有堆栈中。

·Linux下进程的结构

在Linux操作系统中,进程在内存里有三部分的数据:“数据段”、”堆栈段”和“代码段”。

        代码段:存放了程序代码的数据,假如机器中有数个进程运行相同的一个程序,那么它们就可以使用同一个代码段。

        堆栈段:存放子程序的返回地址、子程序的参数以及程序的局部变量。

        数据段:存放程序的全局变量,常数以及动态数据分配的数据空间(比如用malloc之类的函数取得的空间)。

·fork()函数

        它执行一次却返回两个值。一方面,它使操作系统在进程管理上付出了最小的代价,另一方面,又为程序员提供了一个简洁明了的多进程方法。对于父进程,fork函数返回了子程序的进程号,而对于子程序,fork函数则返回零。

        对于程序,只要判断fork函数的返回值,就知道自己是处于父进程还是子进程中。

·如何启动另一程序的执行

在Linux中,使用exec类的函数。

exec类的函数不止一个,分别是:execl,execlp,execle,execv,execve和execvp

如果当前程序想启动另一程序的执行,但自己仍想继续运行的话,结合fork与exec的使用。

三、实验过程

·设计一个 C 程序作为一个 shell 接口

定义模块:

由要求里提到的“用户最多可以访问 10 个命令”,因此定义最大历史值为10。

定义history_count 来记录到目前为止用户所输入的所有命令个数。

定义数组history来记录具体的命令值。

另外。为了防止用户恶意以及检验命令的有效性,加入了命令最大 长度为100。

添加历史记录函数:

对主函数来说,循环一次执行一次,改变history_count 的值,使得 记录数增加。

对函数内部,分情况讨论:

  1. 当记录数小于最大记录数时,直接在当前index出存入 history 数组当中。
  2. 当记录数大于最大记录数时,先将所有记录前移,(将第10位 记录移动到第9位,以此类推),再将当前的命令存入第10位,实现总 记录数是10,当超过10会继续编号,如果用户输入了35条命令,则最 近的10条命令将编号为26到35。

主函数模块

·给出提示符号 osh>

·接受输入后,将换行符替换成结束符号\0后,传到add_to_history中,进行记录。

·定义信号变量,int run_in_background = 0;判断命令结尾是不是“&”, 如果有“&”将值改为1。再后续的程序中以此实现是否等待子进程结束, 实现父进程和子进程同时运行。

·接下来将输入,以“ ”为分割,存入arg数组当中,判断是否是”!!” 或者”!N”。

  1. 如果是”!!”,由history_count 的值判断是否有记录:

                (1)如果有记录,找出上一记录输出,并且不改变arg的值,进入下一阶段直接用上一

                        循环的arg值再次执行fork()函数。

                (2)如果没有记录,输出‘No commands in history’。

2. 如果是“!N”,判断对应的N是否存在,当N小于10的时候 直接在history中进行查找,取出命令。如果大于10,先对index进 行处理,使其对应到history的1-10的记录值上,取出命令。

                (1) 如果有记录,取出命令后,更新arg的值,进入下一阶段, 再次执行fork()函

                        数。

                (2) 如果没有记录,输出‘No such command in history’。

· 下一步进入创建进程模块,使用fork()函数创建进程,在子进程中执行execvp(),实现用户在命令行输入的命令。此时判断run_in_background 的值,决定是否要等待子进程结束。

四、实验结果与分析

·shell 界面给用户一个提示,然后输入下一个命令

1.输入cat 1.txt,实验现象如图所示

        2.输入ls,实验现象如图 

 ·父进程和子进程同时运行

        输入cat 1.txt &,实验现象如图所示:

·创建历史记录 

        1、当用户输入 !! 执行历史记录中的最新命令,实验现象如图所示。

         2、当用户输入单 ! 后跟整数 N,执行历史记录中的第 N 个命令,实验现象如图所示。(该例子中的输入是!11,读取第11条记录)

·基本的错误处理 

        1、如果历史中没有命令,输入 “!!”应该会产生一条消息“No commands in history”,实验现象如图所示。

        2、如果没有与用单个 ! 输入的数字对应的命令,程序应该输出“No such command in history”。

        (1)当输入值大于最大记录数,实验现象如图所示:

        (2)当记录数很多,此时输入值不在最新的10条记录内,实验现象如图所示: 

五、问题总结 

1、挑战:如何实现history中只保存最新的10条数据。

     方案:在小于10条记录时直接存入,当大于10条记录时,采用队列的   数据结构,将1位出列,所有位数前移,将最新的命令存在最大   位。

2、问题:在实现‘!!’和‘!N’时,发现没有输出,继续进行下一指令可 执行。

     方案:分析代码后,发现是因为在每次循环,都直接将history_count加 1,将‘!!’和‘!N’也当做命令记录了,导致后面在history数 组 中寻找命令时,取出的命令时‘!!’和‘!N’,导致无法执 行,在函数内添加语句‘history_count--’解决。

3、问题:在实现‘!N’时,发现当N超过10,判断就会报错。

     方案:分析代码后,发现是在记录历史记录时,将计数的history_count 只放在了当命令数小于记录数的条件下,造成只有在小于最大记 录数时,history_count才会计数。将history_count++移动到函数 初始端解决

4、问题:在实现‘!N’时,发现重复执行上一次的代码片段,虽然判断了 第N条命令是否存在,但是,执行的却是上一条指令。

     方案:分析代码后,原因是没有更新arg数组,导致每次执行的语句中 arg的值是上一循环中的值。在‘!N’条件模块内,添加更新arg 数组的语句解决。

六、源代码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/wait.h>

#define MAX_COMMAND_LENGTH 100
#define MAX_HISTORY_SIZE 10

char history[MAX_HISTORY_SIZE][MAX_COMMAND_LENGTH];
int history_count = 0;

void add_to_history(char* command) {
    history_count++;
    if (history_count < MAX_HISTORY_SIZE) {
        strcpy(history[history_count], command);

    } else {
        for (int i = 0; i < MAX_HISTORY_SIZE - 1; i++) {
            strcpy(history[i], history[i + 1]);
        }
        strcpy(history[MAX_HISTORY_SIZE - 1], command);
    }
}

void display_history() {
    for (int i = 0; i < history_count; i++) {
        printf("%d. %s\n", i + 1, history[i]);
    }
}

int main() {
    char input[MAX_COMMAND_LENGTH];
    char* args[MAX_COMMAND_LENGTH];
    char* token;

    while (1) {
        printf("osh> ");
        fgets(input, MAX_COMMAND_LENGTH, stdin);
        input[strcspn(input, "\n")] = 0; // Remove newline character

        add_to_history(input);

        // Check if command should run in the background
        int run_in_background = 0;
        if (input[strlen(input) - 1] == '&') {
            run_in_background = 1;
            input[strlen(input) - 1] = '\0'; // Remove the '&'
        }
        // Parse the input command
        int i = 0;
        token = strtok(input, " ");
        while (token != NULL) {
            args[i] = token;
            token = strtok(NULL, " ");
            i++;
        }
        args[i] = NULL;
        f(strcmp(args[0], "#") == 0) {
            break;
        }
        if (strcmp(args[0], "!!") == 0) {
            history_count--;
            if (history_count == 0) {
                printf("No commands in history\n");
                continue;
            }
            strcpy(input, history[history_count - 1]);
            printf("Executing: %s\n", input);
        } else if (args[0][0] == '!') {
            history_count--;
            int index = atoi(args[0] + 1);
            if (index > 0 && index <= history_count && index > history_count - 10) {
                if (history_count > 10)index = index - history_count + 10;
                strcpy(input, history[index - 1]);
                printf("Executing: %s\n", input);

                token = strtok(input, " ");
                while (token != NULL) {
                    args[i] = token;
                    token = strtok(NULL, " ");
                    i++;
                }
                args[i] = NULL;
            } else {
                printf("No such command in history\n");
                continue;
            }
        }

        pid_t pid = fork();
        if (pid == 0) {
            execvp(args[0], args);
            exit(0);
        } else {
            if (!run_in_background) wait(NULL);
        }
    }

    return 0;
}

  • 12
    点赞
  • 6
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值