我正在尝试用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;
}
答案 0 :(得分: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]开始添加程序参数。 e.g。
input_arguments[0] = "ls"
input_arguments[1] = "/var/log/"
这是您的代码补丁:
27c27
< int i = 0;
---
> int i = 1;
38a39
> input_arguments[0]=prog;
106a108
>
答案 1 :(得分:1)
这里有几件事:
char* tmp = malloc(strlen(token)+1);
memset(tmp, '\0', strlen(token)+1);
strncpy(tmp, token, strlen(token));
主要称为strdup。
顺便说一下,如果你实际使用strlen(token)并且返回,那么token [strlen(token)]已经是'\ 0',因为strlen知道如何因此,上面的memset是不必要的(如果你用那个长度做strncpy)。
您的主要问题是您没有正确理解execvp man page。 它陈述如下:
当作为此调用的结果执行C语言程序时,它应作为C语言函数调用输入,如下所示:
int main(int argc,char * argv []);
后来,它继续说:
arg0,...表示的参数是指向以null结尾的字符串的指针。这些字符串应构成新过程映像可用的参数列表。该列表由空指针终止。 参数arg0应该指向一个exec函数与正在启动的进程相关联的文件名。
这显然是你错过的一点。 argv参数的第一个参数必须与路径参数传递的值相同(或允许的变体)。
更多关于这一点,通常shell可以探索PATH变量本身并通过execve调用替换过程映像(最后一个因为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
,但不能作为another_test运行。这是有道理的,因为程序可能需要知道它们是如何被调用的。例如,busybox需要发生这种情况才能知道用户想要什么。
但你发布的代码不仅仅是添加一些input_arguments[0] = program
并在1中启动“int i”。我上面提到的那些东西也是如此:不要重新发明轮子,strdup已经存在了你应该使用而不是malloc + memset + strncpy。它减少了三个函数调用,代码所做的概念更加浓缩。
每次循环结束时它会产生一个孩子,并且它不介意程序是否没有产生(可能程序不存在)。 检查run_exec
后为什么不分叉?
为什么不等待你生成的那个特定线程(fork()在父级上返回的pid_t)?
如果无法生成图像,为什么不杀死子进程? 目前,如果子进程在execvp调用中失败,那么它将要求更多命令,并且一旦被杀死,父进程将打印其统计信息并继续该作业。这可能是无意的,而且肯定是违反直觉的。
此外,我不知道你是否注意到了,但你没有检查stdin中的EOF,如果你是从文件中提取而不是交互式(或者如果用户输入EOF [Ctrl] 。+ d])。您可能也应该将退出操作绑定到EOF,否则您将无法完成“使用”脚本文件。