【问题标题】:How to start off a process at run time in c如何在运行时在c中启动一个进程
【发布时间】:2018-09-21 08:08:24
【问题描述】:

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

【问题讨论】:

  • 一个minimal reproducible example 会很好。一个进程与另一个进程之间由自身启动的最简单的通信形式是通过命令行参数传递信息。除此之外,IPC还有很多方式,包括发送方打开用于写入的通用文件,接收方用于读取。
  • 您能否详细说明 execlp() 在这里如何工作的过程?我的目标是用我自己编译的可执行文件替换孩子的地址空间,但没能做到。我现在正在尝试这样的事情,请纠正我的错误。 execlp("C:\C 代码\sampleSolution\bin\Release", "sampleSolution", NULL);
  • 我在下面扩展了我的答案并对此进行了详细说明。您似乎在 Windows 上工作。 (在 Linux 和 cygwin 中,\ 不是目录分隔符。)请不要忘记,如果在 C 字符串文字中使用,您必须复制 \s。此外,它必须包含可执行文件的文件名,然后再次与我的答案底部所述相同。 :-) 所以,应该是execlp("C:\\C code\\sampleSolution\\bin\\Release\\sampleSolution", "C:\\C code\\sampleSolution\\bin\\Release\\sampleSolution", NULL);。

标签: c linux unix operating-system


【解决方案1】:

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

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

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;
}

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

$ 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() 中的第一个命令行参数传递。因此,孩子一开始只有一个论据——承认自己是父母。绝望地按下 CtrlC 并没有帮助——我不得不杀死我的xterm(在它杀死我的系统之前)。所以,不要忘记:argv[0] 按照惯例是可执行文件本身的文件路径,但您必须明确将其作为参数提供给 execlp()。

  2. 最后添加的是 mainChild() 中的 fflush()。在我这样做之前,孩子甚至在父母开始接收之前就写下了所有内容。虽然原因对我来说很明显,但我觉得这值得一提。


关于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) 中使用argc 和argv,我们习惯于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

【讨论】:

    猜你喜欢
    • 2015-09-14
    • 1970-01-01
    • 1970-01-01
    • 2019-09-24
    • 2021-01-26
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    • 1970-01-01
    相关资源
    最近更新 更多