c - 如何在运行时在 c 中启动一个进程

标签 c linux unix operating-system

我有一个 C 问题,其中一个进程在运行时启动其他进程,并且该进程必须与其他进程进行进程间通信。现在我知道了 fork() 和 execl() 的基础知识,但除此之外,我对进程的了解还很初级(尤其是如何在运行时启动进程),所以任何帮助将不胜感激。

最佳答案

一个进程与另一个进程之间最简单的通信形式是通过命令行参数传递信息。除此之外,IPC还有很多方式,包括发送方打开写入,接收方读取的公共(public)文件。

这是我已经在评论中写的内容。我忍不住做了一个小演示。

testSimpleIPC.c:

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

/* This is done in one program which is started
 * - without command line arguments to behave as parent
 * - with command line argument to behave as child.
 */
int mainParent(void);
int mainChild(int argc, char **argv);

int main(int argc, char **argv)
{
  if (argc == 1) { /* becomes parent */
    return mainParent();
  } else {
    return mainChild(argc, argv);
  }
}

int mainParent(void)
{
  const char *ipcFileName = "testSimpleIPC.txt";
  /* open file for IPC */
  FILE *fIn = fopen(ipcFileName, "a+");
  if (!fIn) {
    perror("Failed to fopen() for reading");
    return -1;
  }
  /* fork and start child */
  int pid;
  switch (pid = fork()) {
    case -1: /* failed */
      perror("Failed to fork()");
      return -1;
    case 0: /* returned in child process */
      execlp("./testSimpleIPC",
        "./testSimpleIPC", ipcFileName, NULL);
      /* If this point is reached execlp failed! */
      perror("Failed to execlp()");
      return -1;
    default:
      printf("testSimpleIPC spawned child with PID %d\n", pid);
  }
  /* read messages from child */
  char buffer[80];
  for (;;) {
    if (fgets(buffer, sizeof buffer, fIn) != NULL) {
      /* clip line-ending from buffer end */
      for (size_t len = strlen(buffer); len--;) {
        if (isspace(buffer[len])) buffer[len] = '\0';
        else break;
      }
      /* report */
      printf("Parent received :'%s'\n", buffer);
      /* bail out in case */
      if (strcmp(buffer, "quit") == 0) break;
    }
  }
  fclose(fIn);
  /* done */
  return 0;
}

int mainChild(int argc, char **argv)
{
  assert(argc == 2);
  const char *const ipcFileName = argv[1];
  /* write messages to parent */
  FILE *fOut = fopen(ipcFileName, "a");
  if (!fOut) {
    perror("Failed to fopen() for writing");
    return -1;
  }
  for (int i = 1; i < 10; ++i) {
    printf("Sending 'message %d'...\n", i);
    if (fprintf(fOut, "message %d\n", i) < 0
      || fflush(fOut)) {
      perror("Failed to fprintf()");
      return -1;
    }
  }
  if (fprintf(fOut, "quit\n") < 0 || fclose(fOut)) {
    perror("Failed to fprintf()");
    return -1;
  }
  /* done */
  return 0;
}

cygwin64 中编译和测试在 Windows 10 上:

$ gcc -std=c11 -o testSimpleIPC testSimpleIPC.c 

$ ./testSimpleIPC
testSimpleIPC spawned child with PID 27320
Sending 'message 1'...
Sending 'message 2'...
Parent received :'message 1'
Sending 'message 3'...
Parent received :'message 2'
Sending 'message 4'...
Parent received :'message 3'
Sending 'message 5'...
Parent received :'message 4'
Sending 'message 6'...
Parent received :'message 5'
Sending 'message 7'...
Parent received :'message 6'
Sending 'message 8'...
Parent received :'message 7'
Sending 'message 9'...
Parent received :'message 8'
Parent received :'message 9'
Parent received :'quit'

$

注意事项:

  1. 起初,我忘记将可执行文件的文件路径作为第一个命令行参数传递给execlp()。因此, child 一开始只有一个论点——承认自己是 parent 。绝望地按 CtrlC 没有帮助——我不得不杀死我的 xterm(在它杀死我的系统之前)。所以,不要忘记:argv[0] 按照惯例是可执行文件本身的文件路径,但您必须明确地将其作为 execlp() 中的参数提供。

  2. 最后添加的是 mainChild() 中的 fflush()。在我这样做之前, child 在 parent 开始接收之前就已经写好了一切。虽然,原因对我来说很明显,但我发现这个值得一提。


关于 execlp() 的更多细节:

我使用了 execlp(3) - Linux man page记忆细节。

execlp()的签名是

int execlp(const char *file, const char *arg, ...);

参数:

  • file 提供与正在执行的文件关联的文件名。
  • arg 是传递给可执行文件的第一个参数。
  • ... 可以是传递给可执行文件的任意数量的附加参数。

请注意,最后传递的参数必须为 NULL 才能终止列表。 (否则,调用 execlp() 可能导致 Undefined Behavior )。

一个令人讨厌的陷阱是第一个参数(在 execlp() 中,第二个nd,名为 arg)。

如果在 int main(int argc, char **argv) 中使用 argcargv,我们将使用 argc 始终至少为 1 并且 argv[0] 提供调用可执行文件本身的文件路径。

坏消息:这是一个约定(到处都考虑)。因此,某处没有内置自动机制——execlp() 的调用者必须考虑这一点:

在提供 0 ... n 附加参数之前重复可执行文件的文件名作为第一个参数,并且不要忘记最后的 NULL

在上面的例子中:

      execlp("./testSimpleIPC",
        "./testSimpleIPC", ipcFileName, NULL);

回想一下我的 1. 注意(上文),当我忘记了第一个参数 "./testSimpleIPC" 时,ipcFileName 的内容成为第一个并被传递给子进程作为 argv[0]。由于代码不使用 argv[0],我没有注意到这一点。然而,argv[1] 丢失的事实导致子进程再次将自己标识为父进程。因此,我遇到了大量难以停止的已启动进程。

实际上,我没有忘记 assert(argc == 2); 来检查 argc 的期望值。然而,这仅在调试代码中有效,并且在我编译时没有使用 -g 时并没有拯救我,因此 assert 变得无效。

就在今天,我发现 1251 个进程(从我最近的测试中剩下)占用了 40% 的 CPU 负载,直到我设法停止它们

for I in $( ps | grep testSimpleIPC | awk '{ print $1 }' ); do kill $I ; done

关于c - 如何在运行时在 c 中启动一个进程,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/52439332/

相关文章:

c - 如何找出Linux中进程(C程序)中特定函数的内存使用情况?

bash - 当我通过 bash 函数进行管道传输时,为什么不显示命令退出日志?

c - C 中的 while 循环出现问题 - 拒绝输入,但随后第二次接受相同的输入

c - c中的多分支树

php - php 扩展崩溃 - 将内存地址转换为行号

unix - 'find -delete' 和 'rm -rf' 之间的区别?

c - fork() 和 wait() 调用

c++ - 多语言字符串转数字

linux - 使用 Navicat 通过 SSH 登录 Postgresql - 正确的设置是什么?

linux - 使用 tac 反向读取文件