【问题标题】:execvp() function always returns a negative valueexecvp() 函数总是返回一个负值
【发布时间】:2017-05-16 21:47:12
【问题描述】:

我正在做一个模拟 shell 程序来练习 C 和 Linux 命令功能,execvp()。我知道,每当返回值小于 0 时,执行在execvp 中传递的命令时就会出错。我对我的stringTok() 函数处理句子数组字符正确传入是肯定的,因为我打印了它们的长度和字符串值进行双重检查。对于句子的最后一个标记,本质上是将参数列表分成单独的标记,我将'\0' 字符分配给该标记的最后一个字符。
最终,每个单独的令牌都被分配给char*cmdargv[MAX_LINE]in stringTok()execvp(cmdargv[0], cmdargv) 是我传递命令参数的方式。例如,“ls -al”是输入,fgets() 函数将该字符串读入args[MAX_LINE]。子进程将运行execvp,但返回负值并打印出错误字符串。这甚至不起作用,因为我尝试按照代码中的注释手动为 cmd 和 argv 分配值。我想我一定是在 execvp() 中传递了错误的值,或者做错了什么。
理想情况下,如果我输入“ls -al”,它应该与在 linux shell 中的操作完全相同,列出当前目录中的所有文件或目录(预期输出)。

输入:
osh>ls -al
输出:
参数: 镜头 2
内容:ls
最后一个元素 115
镜头 3
内容:-al
最后一个元素 108
最后一个成员:(null)
osh>错误:: 错误地址
最后一个成员:(null)
osh>

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

#define MAX_LINE 80 


int  stringTok(char* sentence, char** tokens) {

    const char* delim = " \r\n"; 
    int count = 0; 

    // make copy of sentence for strtok 
    size_t len = strlen(sentence);
    char *copy = malloc(len+1); 
    if(!copy) return 0; // if copy null, return 0 
    strncpy(copy, sentence, len); 

    copy[len] = '\0'; 

    // Allocate and copy tokens 
    for(char* word = strtok(copy, delim); 
        word; 
        word = strtok(NULL, delim)) 
    {   
        size_t len = strlen(word); 
        tokens[count] = malloc(len+1);
        if(!tokens[count]) break; 
            strncpy(tokens[count], word, len); 
            tokens[count][len] = '\0'; 
            count++; 
    } 
        size_t strlenth = strlen(tokens[count - 1]);
    //if(count > 1) {
    //   tokens[count - 1][strlenth -1] = '\0'; 
    //}
    tokens[count] = NULL; 
    return count;   
}

int main(void) {
    char args[MAX_LINE]; // command to be executed
    char *cmdargv[MAX_LINE]; // command tokens

    int should_run = 1; 

    do {
        printf("osh>");
        fflush(stdout); 
        fgets(args, MAX_LINE, stdin);
        //fflush(stdout); 

        printf("args:<%s>\n", args);

        int count = stringTok(args, cmdargv);
        for(int i = 0; i < count; i++) {
            printf("len %ld\n", strlen(cmdargv[i]));
            printf("content: %s\n", cmdargv[i]);
            printf("last elememnt %d\n", cmdargv[i][strlen(cmdargv[i]) - 1]);
        }
        cmdargv[count] = NULL;      
            printf("last member:%s", cmdargv[count]);   
        //printf("cmdargv:%s\n", cmdargv[0]);
        //printf("cmdargc:%d\n", cmdargv[0][strlen(cmdargv[0]) - 1]);
        int pid = fork(); 

        char *cmd = "ls";
        char *argv[3];
        argv[0] = "ls";
        argv[1] = "-la";    

        if(pid == 0) {
            execvp(cmd, argv); 
            if(execvp(cmd, argv) < 0) {
              //printf("Error Executing Command\n");
              perror("Error:");
            }
        } else if (pid > 0) { 
             // if '&' {
             // wait(&pid)
             //}    
        } else {
            printf("Fork failed\n");
            exit(1); 
        } 
        printf("\n"); 
    } while(should_run); 
    return 0;
}

【问题讨论】:

  • @NateLee:您认为execvp() 怎么知道您传递给它的参数数量?如果您阅读手册页,您会看到传递给execvp() 的参数列表必须NULL 指针终止。
  • @NateLee,不要忽略我之前评论的 first 部分,关于将您的代码简化为 MCVE。或者您甚至可以从另一个方向着手,从头开始构建 MCVE。一般来说,尽一切合理努力提出我们容易回答的问题。这样你会得到更好的答案。
  • 您不需要if — 只需使用execvp(cmdargv[0], cmdargv);,因为如果execvp() 成功,则此进程(可能是子进程)在execvp() 之后不会返回。如果execvp() 完全返回,它就失败了。当您成功使用execvp() 时,您指定的命令将完全替换当前进程。父进程将发现失败,因为退出状态将非零。请注意,您应该将错误消息打印到标准 error,而不是标准输出。我还观察到,您通常应该在失败时退出 - 使用 exit()_exit() 或类似名称。
  • fgets(args, MAX_LINE, stdin); fflush(stdout); 很奇怪。我希望fflush(stdout); fgets(args, MAX_LINE, stdin);
  • @NateLee:您仍然没有尝试找出 为什么 execvp() 失败。据我们所知,它可能无法找到文件,或者权限问题,或者您可能已经达到了您的进程限制,而与您的代码无关。失败后拨打perror()查询原因。

标签: c system-calls


【解决方案1】:

execv()execvp()execvpe() 函数提供了一个数组 指向表示参数列表的以 null 结尾的字符串的指针 可用于新程序。第一个论点,按照惯例, 应该指向与正在执行的文件关联的文件名。 指针数组必须以空指针终止

由于数组在 C 中只是作为指针传递的,并且被调用的函数没有隐藏的方法来知道数组的长度,因此必须明确给出。或者在单独的参数中(例如,fgets()size 参数),或者以已知值结尾的数组,如 NULL(如这里的 execv*)。您的代码未将最终参数设置为 NULL,这可能会导致 execlp 返回错误地址错误或类似错误,因为您设置的值后面的未初始化值将被视为指针并且可能指向无效内存地址。

解决这个问题后,exec 就可以在这里为我工作了。

此外,您应该使用perror() 之类的东西而不是不断的错误消息来获取有关发生了什么 错误的一些信息。如果exec 失败,子进程将继续运行,所以用_exit() 终止它。

【讨论】:

  • 你说的是真的,你推荐的是好的,但我不太理解你所说的与问题的联系。
  • 我确实尝试在stringTok() 函数中的return count 之前添加一行token[count] = NULL。为什么 execvp() 仍然返回负值?
  • @ilkkachu 在使用 perror("error") 后,它返回 "osh>Error:: Bad address" 并且最后一个带有 printf("last member of cmdargv:%s" cmdargv[count]) 的成员确实返回了一个空指针 (null)。并且 execvp() 仍然返回一个负值。
【解决方案2】:

这是对我有用的代码版本。它在内存分配上更干净一些——你在父 shell 中疯狂地泄漏(命令行的副本和每个参数的单独副本)。它们很容易清理。此代码正确处理 EOF,而不是陷入无限循环。它打印命令信息而不会崩溃。

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

#define MAX_LINE 80

static int stringTok(char *sentence, char **tokens)
{
    const char *delim = " ";
    int count = 0;

    size_t len = strlen(sentence);
    char *copy = malloc(len + 1);
    if (!copy)
        return 0;
    strncpy(copy, sentence, len);

    copy[len] = '\0';

    for (char *word = strtok(copy, delim);
         word;
         word = strtok(NULL, delim))
    {
        size_t len = strlen(word);
        tokens[count] = malloc(len + 1);
        if (!tokens[count])
            break;
        strncpy(tokens[count], word, len);
        tokens[count][len] = '\0';
        count++;
    }
    size_t length = strlen(tokens[count - 1]);
    tokens[count - 1][length - 1] = '\0';
    tokens[count] = 0;
    free(copy);
    return count;
}

int main(void)
{
    char args[MAX_LINE]; // command to be executed
    char *cmdargv[MAX_LINE]; // command tokens

    int should_run = 1;

    do
    {
        printf("osh>");
        fflush(stdout);
        if (fgets(args, MAX_LINE, stdin) == 0)
        {
            putchar('\n');
            break;
        }

        printf("args:%s\n", args);

        int count = stringTok(args, cmdargv);
        for (int i = 0; i < count; i++)
        {
            printf("len %ld\n", strlen(cmdargv[i]));
            printf("content: %s\n", cmdargv[i]);
            printf("last character %d\n", cmdargv[i][strlen(cmdargv[i]) - 1]);
        }

        int pid = fork();

        if (pid == 0)
        {
            for (int i = 0; cmdargv[i] != 0; i++)
                fprintf(stderr, "%d: [%s]\n", i, cmdargv[i]);
            execvp(cmdargv[0], cmdargv);
            int errnum = errno;
            fprintf(stderr, "Error executing command %s (%d: %s)\n",
                    cmdargv[0], errnum, strerror(errnum));
            exit(1);
        }
        else if (pid < 0)
        {
            fprintf(stderr, "Fork failed\n");
            exit(1);
        }
        else
        {
            for (int i = 0; cmdargv[i] != 0; i++)
                free(cmdargv[i]);
            int corpse;
            int status;
            while ((corpse = waitpid(0, &status, 0)) > 0)
                fprintf(stderr, "PID %d exited with status 0x%.4X\n", corpse, status);
            printf("\n");
        }
    } while (should_run);
    return 0;
}

样品运行

我的程序在可执行文件xv97(来源xv97.c)中:

$ make xv97
gcc -O3 -g -std=c11 -Wall -Wextra -Werror -Wmissing-prototypes -Wstrict-prototypes -Wold-style-definition xv97.c -o xv97
$ ./xv97
osh> extraordinary gallantry
args: extraordinary gallantry

len 13
content: extraordinary
last character 121
len 9
content: gallantry
last character 121
0: [extraordinary]
1: [gallantry]
Error executing command extraordinary (2: No such file or directory)
PID 17445 exited with status 0x0100

osh>    ls    -l    .       
args:    ls    -l    .

len 2
content: ls
last character 115
len 2
content: -l
last character 108
len 1
content: .
last character 46
0: [ls]
1: [-l]
2: [.]
total 8488
-rw-r--r--    1 jleffler  staff     2223 Feb 14 13:40 README.md
drwxr-xr-x   19 jleffler  staff      646 May 12 10:15 bin
-rwxr-xr-x    1 jleffler  staff     8712 May 16 16:02 classifier
-rw-r--r--    1 jleffler  staff      637 May  8 08:21 classifier.c
drwxr-xr-x    3 jleffler  staff      102 May  1 10:23 classifier.dSYM
drwxr-xr-x   10 jleffler  staff      340 May  1 13:35 doc
drwxr-xr-x    7 jleffler  staff      238 Feb 23 15:11 etc
drwxr-xr-x   18 jleffler  staff      612 Apr  4 12:14 inc
-rwxr-xr-x    1 jleffler  staff     8992 May 16 16:02 iq89
-rw-r--r--    1 jleffler  staff     2464 May  1 20:43 iq89.c
drwxr-xr-x    3 jleffler  staff      102 Apr 20 11:56 iq89.dSYM
drwxr-xr-x    5 jleffler  staff      170 Feb 14 13:40 lib
-rw-r--r--    1 jleffler  staff      390 May  1 13:35 makefile
drwxr-xr-x  168 jleffler  staff     5712 May 12 11:56 src
-rwxr-xr-x    1 jleffler  staff     9540 May 16 16:43 xv97
-rw-r--r--    1 jleffler  staff     2432 May 16 16:43 xv97.c
drwxr-xr-x    3 jleffler  staff      102 May 16 15:55 xv97.dSYM
PID 17452 exited with status 0x0000

osh>^D
$

(我稍微编辑了文件列表——这就是为什么文件的大小与显示的文件大小不匹配的原因。)

我们可以讨论为什么一天最多只有 80 个字符。

我在运行 macOS Sierra 10.12.5 的 Mac 上进行测试,使用 GCC 7.1.0 作为编译器。

【讨论】:

  • 感谢您的详细解释,它确实有助于您的代码以及像我这样的新手的输出和描述。事实上,我自己稍微调整了代码,去掉了 main 方法中额外的 cmdargv[count] = NULL,因为我已经在 stringTok() 中拥有了 tokens[count] = NULL。事实证明它也有效。我很困惑的一件事是为什么你有tokens[count] = 0. 而且,你能解释一下while ((corpse = waitpid(0, &amp;status, 0)) &gt; 0) 的作用吗?我猜这会告诉你为什么 fork() 无法创建子进程,对吧?
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2020-07-20
  • 1970-01-01
  • 2013-06-27
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多