0

我正在尝试用 C 编写 shell,但遇到了问题。shell应该循环运行,每次都提示用户,每次从stdin读取和解析文本。然后将参数划分为标记,并将每个标记放入参数向量中。然后代码派生一个孩子,然后使用参数向量作为参数运行命令。然后代码等待子进程终止,并打印有关子进程的统计信息(运行时等)。问题是,每当我们运行程序时,当我们输入 ls /home(例如)时,它并没有列出主目录,而是列出了我们当前所在的目录。另外,如果我们尝试添加一个新的代码的变量,代码停止工作。知道如何解决这个问题吗?

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

#define TRUE 1
#define BUFFERSIZE 129

int main (int argc, char* argv[]){

    int status;
    int who = RUSAGE_CHILDREN;
    struct rusage usage;
    struct rusage before_usage;
    struct timeval start, end;
    while(TRUE){
    printf("==>:  ");
    char input_string[BUFFERSIZE];
    memset(input_string, '\0', BUFFERSIZE);
    fgets(input_string, BUFFERSIZE-1, stdin);

        int i = 0;
        char* input_arguments[32]; //Pointers to what the commands will be
        char* token; //The specific piece of the input "ls" or "/home"
    char* program;
    int run_exec = 1; //To say whether execvp will run


    token = strtok(input_string, " ");
    char* prog = malloc(strlen(token)+1);
    memset(prog, '\0', strlen(token));
    strncpy(prog, token, strlen(token));
    program = prog;
    printf("PROGRAM: %s\n", prog);
    token = strtok(NULL, " ");


    if(strncmp(program, "exit", 4) ==0)
        exit(0);

        while(token != NULL && i < 32){

        printf("TOKEN: %s \n", token);
        char* tmp = malloc(strlen(token)+1);
        memset(tmp, '\0', strlen(token)+1);
        strncpy(tmp, token, strlen(token));
        input_arguments[i]=tmp;
        printf("INPUT: %s \n", input_arguments[i]);

        if(strcmp(program, "cd") == 0){
            chdir(input_arguments[0]);
        run_exec = 0;
        printf("EXEC: %d\n", run_exec);
    }

    printf("%d\n", i);
    i++;
    size_t tmp2 = 50;
    printf("%s\n", getcwd(tmp, tmp2));

    token = strtok(NULL, " ");

    printf("IN LOOP\n");
}
input_arguments[i] = NULL;
printf("AFTER LOOP\n");

if(fork() != 0){
    int start_time = gettimeofday(&start, NULL);
    if(run_exec == 1){
        getrusage(who, &before_usage);
            waitpid(-1, &status, 0);
            int end_time = gettimeofday(&end, NULL);
            double wall_time_passed = (end.tv_sec -start.tv_sec)*1000
                + (end.tv_usec - start.tv_usec)/1000;
            getrusage(who, &usage);
            double user_time = (usage.ru_utime.tv_sec*1000 +
                usage.ru_utime.tv_usec/1000);
            double system_time = (usage.ru_stime.tv_sec*1000 
                + usage.ru_stime.tv_usec/1000);

//long page_faults = 0;
//long soft_faults = 0;
//long invol = 0;
//long vol = 0;
            printf("Number of Page Faults: %ld \n", usage.ru_majflt -before_usage.ru_majflt);
        //printf("soft_faults: %lu\n", soft_faults);
        printf("Number of Page Reclaims: %ld \n",                                usage.ru_minflt - before_usage.ru_minflt);
        //printf("soft_faults: %lu\n", soft_faults);
        printf("Number of times preempted involuntarily: %ld \n",                usage.ru_nivcsw - before_usage.ru_nivcsw);
        printf("Number of times preempted Voluntarily: %ld \n",                     usage.ru_nvcsw - before_usage.ru_nivcsw);
            printf("User Time: %f \n", user_time);
            printf("System Time: %f \n", system_time);
            printf("Wall-Time: %f \n", (wall_time_passed));
    }
}

else{
    if(run_exec == 1){
    printf("RUNNING EXEC: %s, %s \n", program, *input_arguments); 
    printf("EXEC: \n", execvp(program, input_arguments));
    }

}
}

return 0;

}

4

2 回答 2

1

尝试根据以下示例重新格式化您的变量:

....
if(run_exec == 1){
   //printf("RUNNING EXEC: %s, %s \n", program, *input_arguments); 
   //printf("EXEC: \n", execvp(program, input_arguments));
   char* args[] = { "ls", "/home" };
   execvp(args[0], args);
}
.....

您的代码已被注释,并替换为简单的数组“ls”、“/home”

或者,更具体地说 - 您必须将 input_arguments[0] 设置为程序名称,并开始从 input_arguments[0] 添加程序参数。例如

input_arguments[0] = "ls"
input_arguments[1] = "/var/log/"

这是您的代码的补丁:

27c27
<         int i = 0;
---
>         int i = 1;
38a39
>     input_arguments[0]=prog;
106a108
> 
于 2013-09-10T22:08:21.497 回答
1

这里有几件事:

  1. 每次您在程序中输入时,它都会跨越一个最终等待等待的孩子。这是可取的吗?
  2. char* tmp = malloc(strlen(token)+1);
    memset(tmp, '\0', strlen(token)+1);
    strncpy(tmp, token, strlen(token));
    

    主要称为strdup

  3. 顺便说一句,如果你实际使用 strlen(token) 并且它返回,那么 token[strlen(token)] 已经是 '\0',因为 strlen 知道如何停止,因此,上面的 memset 是不必要的(如果你用那个长度做strncpy)。

  4. 您的主要问题是您没有正确理解execvp 手册页。它声明如下:

    当一个 C 语言程序作为这个调用的结果被执行时,它应该作为一个 C 语言函数调用输入如下:

    int main (int argc, char *argv[]);

    后来,它继续说:

    由 arg0,... 表示的参数是指向以空字符结尾的字符串的指针。这些字符串应构成可用于新过程映像的参数列表。该列表由一个空指针终止。参数 arg0 应该指向一个文件名,该文件名与由 exec 函数之一启动的进程相关联。

    这显然是你错过的重点。argv 参数的第一个参数必须与作为路径参数传递的值相同(或允许的变体)值。

更重要的是,通常 shell 可以自己探索 PATH 变量并通过 execve 调用替换进程映像(最后一个 e 是因为 shell 通常允许您修改环境变量),并且它们作为 *path 传递文件的实际路径在 PATH 中找到,以及用户输入的 argv[0] 参数。

关于这一点,POSIX 标准说

对严格符合 POSIX 应用程序的要求还规定,作为第一个参数传递的值是与正在启动的进程关联的文件名。[...] 在某些情况下,传递的文件名不是文件的实际文件名 [...]

因此,如果您有这样的可执行文件:

#include <stdio.h>
int main(int argc, char *argv[]) {
  printf("I was called as %s\n", argv[0]); return 0;
}

您可以将其编译为test,但将其符号链接为another_test.

当运行为时test,它应该说I was called as test,但不是当作为另一个测试运行时。这是有道理的,因为程序可能需要知道它们是如何被调用的。例如,busybox 需要这样做才能知道用户想要什么。

但是您发布的代码不仅仅是添加一些input_arguments[0] = program并在 1 中启动“int i”。我上面放的那些东西也是正确的:不要重新发明轮子,strdup 已经存在,你应该使用而不是 malloc+memset +strncpy。它减少了三个函数调用,并且代码所做的概念更加简洁。

每次循环结束时,它都会产生一个孩子,并且它不介意程序是否没有产生(也许程序不存在)。为什么不检查run_exec分叉?为什么不等待您生成特定线程(fork() 在父级上返回的 pid_t)?

如果无法生成图像,为什么不杀死子进程?目前,如果子进程在 execvp 调用中失败,它将是请求更多命令的那个,一旦被杀死,父进程将打印其统计信息并继续工作。这可能是无意的,而且肯定是违反直觉的。

此外,我不知道您是否注意到,但是您没有在标准输入中检查 EOF,如果您从文件中获取而不是交互方式(或者如果用户输入 EOF [Ctrl.+D),这可能会杀死您])。您可能也应该将退出操作绑定到 EOF,否则您将永远无法完成“使用”脚本文件。

于 2013-09-10T22:41:44.103 回答