如何在C中的运行时启动进程

时间:2018-09-21 08:08:24

标签: c linux unix operating-system

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

1 个答案:

答案 0 :(得分: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()中的第一个命令行参数传递。因此,孩子只从一个论点开始-承认自己是父母。绝望地按 Ctrl C 并没有帮助–我不得不杀死xterm(在杀死系统之前)。因此,请不要忘记:argv[0]是可执行文件本身的文件路径,但是您必须在execlp()中显式提供它作为参数。

  2. 最后一个添加项是fflush()中的mainChild()。在我这样做之前,孩子在父母甚至还没有收到之前就写下了所有东西。虽然,对我来说原因很明显,但我发现值得一提。


有关execlp()的更多详细信息:

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

execlp()的签名是

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

参数:

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

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

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

如果在argc中使用argvint main(int argc, char **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