【问题标题】:write() Output Missingwrite() 输出丢失
【发布时间】:2016-11-03 05:48:22
【问题描述】:
main(int argc, char *argv[]) {

  //variables
  int status, fout, waitcall;
  pid_t pid, ppid, cpid;

  pid = getpid();

  //write to terminal
  write(fout, "Hello World - I am parent:  ",29);
  write(1, (void*)&pid, sizeof(pid));
  write(1, "  !!\n\n", 7);

  int returnchild = fork();

  // fork failed; exit
  if ( returnchild < 0 ) {
    //fail code
  }

  // child (new process)
  else if ( returnchild == 0 ) {
    //getpid(), waitpid() then write;

    if ( waitcall == -1 ) {
        perror("waitpid\n\n");
        exit( EXIT_FAILURE );
    } // end if
}

// parent goes down this path
else {
   //some code here    
}
return 0;
} // end main

我想输出到终端,这样我就可以了解程序在哪里。不输出到终端?正在使用 printf 但想使用系统调用。

【问题讨论】:

  • 您需要提供 MCVE (minimal reproducible example)。这将包括有关如何运行命令的信息。
  • fout 是如何定义的?你为什么要使用write 写信给终端? pid 的值是多少?它可能不是一个有效的 ASCII 字符串,因此将它的值写入终端不会按照您的想法进行。它需要转换为字符串。您可能还想查找strlen
  • 请注意,最后一个write() 会向终端打印一个空字节。中间的write()打印4或8字节的二进制数据;那是不可读的。第一个write() 也会向终端打印一个空字节。当使用 read()write() 等低级 I/O 函数时,您必须进行格式化,或使用 POSIX 函数 dprintf() 对文件描述符进行格式化(而不是像这样的文件流fprintf() 等)。
  • fout 未初始化。
  • 请注意,子进程 (else if (returnchild == 0)) 中的 waitpid() 调用将始终失败,因为子进程没有自己的子进程。家长可以等孩子;除非代码还创建子进程(使用fork()),否则孩子不会成功等待任何东西,您没有显示。

标签: c terminal output


【解决方案1】:

代码存在多个问题,包括您提供的不是真正的 MCVE (Minimal, Complete and Verifiable Example)。

这里是修改后的代码,与您的代码密切相关,您的代码中的一些缺陷仍然存在,但其他缺陷已修复。添加了一些额外的仪器。

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

int main(void)
{
    int status, waitcall;
    pid_t pid = getpid();

    // write to terminal
    write(STDOUT_FILENO, "Hello World - I am parent:  ", 29);
    write(STDOUT_FILENO, (void *) & pid, sizeof(pid));
    write(STDOUT_FILENO, "  !!\n\n", 7);

    int returnchild = fork();

    if (returnchild < 0)
    {
        perror("fork() failed");
        exit(EXIT_FAILURE);
    }
    else if (returnchild == 0)
    {
        // child (new process)
        printf("Child of %d (%d) at play: %d\n", (int)pid, (int)getppid(), (int)getpid());
        waitcall = waitpid(-1, &status, 0);
        if (waitcall == -1)
        {
            perror("child - waitpid() failed");
            exit(EXIT_FAILURE);
        }
        printf("Wait return PID %d (status 0x%.4X)\n", waitcall, status);
        exit(EXIT_SUCCESS);
    }
    else
    {
        // parent goes down this path
        printf("Parent %d created child %d\n", (int)getpid(), returnchild);
        while ((waitcall = waitpid(-1, &status, 0)) != -1)
            printf("Parent waited for %d (status 0x%.4X)\n", waitcall, status);
        printf("Parent: all done\n");
    }
    return 0;
}

示例输出

程序被称为wst13,它的输出是通过一个程序vis 运行的,该程序使非打印字符可见。

$ wst13 | vis
child - waitpid() failed: No child processes
Hello World - I am parent:  \000\035@\000\000  !!

\000Child of 16413 (16413) at play: 16415
Parent 16413 created child 16415
Parent waited for 16415 (status 0x0100)
Parent: all done
$

发生了什么变化?

变量fout was not setmelpomene 所述,因此在第一个write() 调用中使用它是不安全的。我将它替换为标准输出文件描述符的“官方”POSIX 名称STDOUT_FILENO,尽管使用1 来代替它很诱人。变量ppidcpid 没有被使用,所以它们也被删除了。

else if (returnchild == 0) 的操作已写入。它会报告一些 PID 值,然后调用 waitpid(),因为您的 cmets 说它这样做了 - 而 waitpid() 失败,因为孩子没有创建任何自己的孩子。

else(父)进程的操作已写入。它报告一些 PID 值,然后在循环中调用waitpid(),报告任何死子的 PID 和状态,并在没有更多子等待时退出循环。然后它会在所有完成时报告。

cited 的问题之一是第一个和第三个write() 操作写入尾随空值,因为长度太长,因此包含终端空值。您可以在输出中看到这些:vis 程序将它们报告为 \000。同样,中间的write()pid 的4 个字节打印为原始数据,导致字节序列\035@\000\000。当作为 little-endian 整数的字节处理时,等于 256 * 64 + 29 = 16413,也就是 pid 中记录的值。

在多核机器(本例中为 MacBook Pro 内的 Intel Core i7)上,输出的顺序是不确定的。但是,在vis 报告任何标准输出之前报告孩子的错误(请参阅printf() anomaly after fork()。不使用| vis,示例输出如下所示:

$ wst13
Hello World - I am parent:  %@  !!

Parent 16421 created child 16422
Child of 16421 (16421) at play: 16422
child - waitpid() failed: No child processes
Parent waited for 16422 (status 0x0100)
Parent: all done
$

还有什么问题?

commented

请注意,最后一个write() 会向终端打印一个空字节。中间的write()打印4或8字节的二进制数据;那是不可读的。第一个write() 也会向终端打印一个空字节。当使用 read()write() 等低级 I/O 函数时,您必须进行格式化,或者使用 POSIX 函数 dprintf() 对文件描述符进行格式化(而不是像fprintf() 等)。

解决此问题的众多方法之一是:

    // write to terminal
    const char hw[] = "Hello World - I am parent:  ";
    const char nn[] = "  !!\n\n";
    if (write(STDOUT_FILENO, hw, sizeof(hw) - 1) != sizeof(hw) - 1 ||
        write(STDOUT_FILENO, (void *) & pid, sizeof(pid)) != sizeof(pid) ||
        write(STDOUT_FILENO, nn, sizeof(nn) - 1) != sizeof(nn) - 1)
    {
        perror("short write to standard output");
        exit(EXIT_FAILURE);
    }

这仍然会为 PID 打印乱码。另一种选择是:

    // write to terminal
    const char hw[] = "Hello World - I am parent:  ";
    const char nn[] = "  !!\n\n";
    dprintf(STDOUT_FILENO, "%s%d%s", hw, (int)pid, nn);

还有许多其他方法可以从中获得相同的输出。您可以将中间调用修复为write()

    // write to terminal
    const char hw[] = "Hello World - I am parent:  ";
    const char nn[] = "  !!\n\n";
    char pidstr[16];
    snprintf(pidstr, sizeof(pidstr), "%d", (int)pid);
    int pidlen = strlen(pidstr);
    if (write(STDOUT_FILENO, hw, sizeof(hw) - 1) != sizeof(hw) - 1 ||
        write(STDOUT_FILENO, pidstr, pidlen) != pidlen ||
        write(STDOUT_FILENO, nn, sizeof(nn) - 1) != sizeof(nn) - 1)
    {
        perror("short write to standard output");
        exit(EXIT_FAILURE);
    }

因此可能的补救措施仍在继续。你还能找到多少种方法呢?

【讨论】: