Самая простая форма связи между одним процессом, запущенным отдельно, - это передача информации через аргументы командной строки.Помимо этого, есть много способов 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;
}
Скомпилировано и протестировано в 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'
$
Примечания:
Сначала я забыл передать путь к файлу исполняемого файла в качестве первого аргумента командной строки в execlp()
.Таким образом, ребенок начинался только с одного аргумента - признавая себя родителем.Отчаянное нажатие Ctrl C не помогло - мне пришлось убить xterm
(до того, как это убило мою систему).Итак, не забывайте: argv[0]
является условным обозначением пути к файлу самого исполняемого файла, но вы должны явно указать его в качестве аргумента в execlp()
.
Последнее добавление былоfflush()
в mainChild()
.До того, как я это сделал, ребенок написал все, прежде чем родитель даже начал получать.Хотя причина была для меня очевидна, я обнаружил, что стоит упомянуть.
Немного подробнее о execlp()
:
Я использовал execlp (3) - справочная страница Linux для вызова подробностей.
Подпись execlp()
равна
int execlp(const char *file, const char *arg, ...);
Параметры:
file
предоставляет имя файла, связанного с исполняемым файлом. arg
- это первый аргумент, передаваемый исполняемому файлу. ...
может быть произвольным числом дополнительных аргументов, передаваемых исполняемому файлу.
Обратите внимание, что последний переданный аргумент должен быть NULL
, чтобы завершить список.(В противном случае вызов execlp()
может привести к неопределенному поведению ).
Первым аргументом является неприятная ошибка (в execlp()
2 nd с именемarg
).
При использовании argc
и argv
в int 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% загрузки процессора, пока мне не удалось остановить их с помощью
for I in $( ps | grep testSimpleIPC | awk '{ print $1 }' ); do kill $I ; done