首页 > 解决方案 > 使用 memcpy 或 strcpy 在子进程中将 c 字符串从 1 个缓冲区复制到另一个缓冲区似乎不起作用

问题描述

我正在尝试编写一个模拟 shell 来保存命令行历史记录并覆盖信号操作SIGINT以触发打印用户输入的前 10 个命令。据我所知,从处理所有信号到更新光标和运行命令的一切都可以execvp正常工作。

但是,我遇到了 2 个问题,我很难解决这些问题。

  1. 尝试将输入缓冲区的内容实际复制到我自己的 c 字符串向量(即histv. 在调用read并存储用户输入后buf,我尝试将 的内容复制buf到位置histv[cursor % MAX_HISTORY](我使用模数的原因是因为使用一种循环数组而不是处理光标上升到更大数字的情况似乎更容易比MAX_HISTORY

  2. 当我尝试将该进程作为后台进程运行时,我总是会收到 execvp 错误,即使命令有效也是如此。我知道在父进程收到SIGUSR2信号并创建一个新的子进程来运行命令之后它正在发生。argv所以我假设它与子进程杀死自己并引发之后发生的事情有关SIGUSR2

下面是整个程序的代码。有些地方有点乱,但总体来说还是比较简单的。我已经使用了所有的memcpy, strcpystrncpy但无济于事。它们都编译并运行没有错误,但它们似乎都没有做任何事情。

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

// limits
#define MAX_LINE 80
#define MAX_HISTORY 10

// function headers
void print_history(unsigned int const, char**);
void handler_func(int);
void read_func(char*[], char[], char**, unsigned int const);
int parse_args(char*, char**, size_t);

// globals
volatile sig_atomic_t sig_caught = 0;

void print_history (const unsigned int cursor, char **histv) {
    int temp = (cursor > MAX_HISTORY) ? (cursor - MAX_HISTORY) : 0;

    puts("\n\nprinting the previous ten commands...");
    printf("cursor %d", cursor);

    for (int i = 1; temp < cursor; temp++) {
        printf("%d%s\n", i++, histv[temp % MAX_HISTORY]);
    }
}

void handler_func(int sig)
{   
    /* update loop control variable */
    sig_caught = 1;
}

int main(void)
{
    // declare sigaction struct
    struct sigaction sigactor;

    // initialize sigaction struct
    sigactor.sa_handler = handler_func;
    sigemptyset(&sigactor.sa_mask);
    sigactor.sa_flags = 0;

    // set up sigaction for SIGINT
    if (sigaction(SIGINT, &sigactor, NULL) == -1) {
        perror("siagction() failed");
        _exit(EXIT_FAILURE);
    }


    // set the buffer to no buffering
    setvbuf(stdout, NULL, _IONBF, 0);

    unsigned int cursor = 0;

    /* initlialize history vector */
    char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
    for (int i = 0; i < MAX_HISTORY; i++) 
        histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);

    // enter shell loop
    while (1) {

        /* fork process and get child pid */
        int cpid;

        char *argsv[MAX_LINE/2+1];
        char buf[MAX_LINE];


        while(!sig_caught) {

            cpid = fork();

            /* child */
            if (cpid == 0) {
                read_func(argsv, buf, histv, cursor);
            }

            /* fork error */
            else if (cpid < 0) {
                perror("Error forking process");
                _exit(EXIT_FAILURE);
            }

            /* parent process begins here */
            else {

                /* variable to store status returned from child*/
                int cstatus;

                /* suspend parent until child exits *
                 * store return status in cstatus   */
                waitpid(cpid, &cstatus, 0);

                /* get status from child process and check for SIGTERM *
                 * SIGTERM is raised by child when someone enters '!q' */
                switch(WTERMSIG(cstatus))
                {
                /* user wants to quit */
                case SIGTERM:
                    puts("User issued quit command");
                    for (int i = 0; i < MAX_HISTORY; i++) 
                        free((void *)histv[i]);
                    free((void *)histv);
                    _exit(EXIT_SUCCESS);

                /* invalid string length */
                case SIGUSR1: 
                    puts("Please enter a valid string");
                    break;

                /* background process */
                case SIGUSR2:
                    cpid = fork();
                    if (cpid < 0) perror("Error forking process...");
                    else if (cpid == 0) {
                        if (execvp(argsv[0], argsv) < 0) {
                            --cursor;
                            perror("execvp");
                            kill(getpid(), SIGUSR1);
                        }
                    }
                }

                if (!sig_caught) cursor++;
            }

        }// signal loop

        kill (cpid, SIGTERM);
        print_history(cursor, histv);
        fflush(stdout);
        sig_caught = 0;

    }
}

void read_func(char *argsv[], char buf[], char *histv[], unsigned int const cursor)
{
    printf("\nCMD > ");

    int background = 0;

    size_t length = read(STDIN_FILENO, buf, MAX_LINE);

    if (length > 80 || length <= 0) kill(getpid(), SIGUSR1);

    /* copy buffer into history and update cursor */
    memcpy(histv[cursor % MAX_HISTORY], buf, length);

    printf("cursor %d", cursor);
    /* parse arguments and return number of arguments */
    background = parse_args(buf, argsv, length);

    /* user entered quit command or string is invalid */
    if (background == -1) kill(getpid(), SIGTERM);

    /* signal parent to run process in the background */
    if (background == 1) kill(getpid(), SIGUSR2);

    /* run command */
    if (execvp(argsv[0], argsv) < 0) {
        perror("execvp");
        _exit(EXIT_FAILURE);
    }

}

int parse_args(char buf[], char *argsv[], size_t length)
{

    int i,      /* loop index for accessing buf array */
        start,  /* index where beginning of next command parameter is */
        ct,     /* index of where to place the next parameter into args[] */
        bckg;   /* background flag */


    /* read what the user enters on the command line */
    ct = 0;
    start = -1;
    bckg = 0;

    if (buf[0] == '!' && buf[1] == 'q') return -1;

    /* examine every character in the buf */
    for (i = 0; i < length; i++) {
        switch (buf[i]){
            case ' ':
            case '\t':       /* argument separators */
                if(start != -1){
                    argsv[ct] = &buf[start];    /* set up pointer */
                    ct++;
                }
                buf[i] = '\0'; /* add a null char; make a C string */
                start = -1;
                break;

            case '\n':                 /* should be the final char examined */  
                if (start != -1){
                    argsv[ct] = &buf[start];
                    ct++;
                }
                buf[i] = '\0';
                argsv[ct] = NULL; /* no more arguments to this command */
                break;

            case '&':
                bckg = 1;
                buf[i] = '\0';
                break;

            default:             /* some other character */
                if (start == -1)
                    start = i;
        }
    }
    argsv[ct] = NULL; /* just in case the input line was > 80 */

    return bckg;
}

旁注,该parse_args函数最初是给我们的,我对其进行了一些更改以与程序的其余部分一起使用,但在大多数情况下,我并没有编写该函数。

请放轻松,我已经很久没有使用 C 做任何事情了,我在这里做的很多事情都花了很多精力才开始理解这个程序的行为方式。(〜:

标签: c

解决方案


因此,正如o11c 所描述的那样,这两个问题都在于内存没有被共享。我通过使用mmap而不是解决了这个问题,malloc这反过来又简化了我的程序,因为我不再需要处理内存管理。更改如下所述。

char **histv = (char**)malloc(sizeof(char*) * MAX_HISTORY);
for (int i = 0; i < MAX_HISTORY; i++) 
    histv[i] = (char*)malloc(sizeof(char) * MAX_LINE);

改为

char **histv = (char**)mmap(NULL, (sizeof(char*) * MAX_HISTORY), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);
for (int i = 0; i < MAX_HISTORY; i++) histv[i] = (char*)mmap(NULL,  (sizeof(char) * MAX_LINE), (PROT_READ | PROT_WRITE), (MAP_SHARED | MAP_ANONYMOUS), -1, 0);

虽然我不知道这是否是最好的方法,但它解决了我的问题。

另请注意,munmap即使从技术上讲,它应该在退出时自动处理,但我确实明确地取消了内存映射。


推荐阅读