linux: fork/socketpair/close и несколько дочерних процессов
Прямо сейчас я пытаюсь понять разветвление / повторное связывание stdin / out / err дочерних процессов и правильно управлять ресурсами (файловыми дескрипторами, сокетами) без утечки каких-либо ресурсов.
Осталось несколько вопросов: После того, как я создаю сокет-пару и разветвление, у меня в родительских 5 файловых дескрипторах и в дочернем (stdin / out / err / socket1 / socket2). В дочернем процессе мне нужно закрыть "родительскую" сторону пары сокетов. Я закрываю () stdin / out / err после fork и dup() "клиентский конец" сокета три раза. После dup() нужно ли закрывать "источник" dup? Я думаю, да... но я прав?
Когда я создаю таким образом (см. Ниже) второго потомка, правильно ли обрабатывается ресурс? Я старался сильно полагаться на RAII, чтобы не пропускать fds, но так ли это? Я скучаю по большой вещи?
Пока и спасибо заранее!
Georg
РЕДАКТИРОВАТЬ: я исправил ошибку в rebind_and_exec_child.
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <memory>
#include <cassert>
// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
explicit fdhandle(int fd) {
mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
close(*pfd);
delete pfd;
});
assert(mp_fd);
*mp_fd = fd;
}
operator int() {
assert(mp_fd);
return *mp_fd;
}
private:
std::shared_ptr<int> mp_fd;
};
void rebind_and_exec_child(fdhandle fd, std::string exe) {
// now close the std fds and connect them to the given fd
close(0); close(1); close(2);
// dup the fd three times and recreate stdin/stdout/stderr with fd as the target
if (dup(fd) != 0 || dup(fd) != 1 || dup(fd) != 2) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(EXIT_FAILURE);
}
// now we can exec the new sub process and talk to it through
// stdin/stdout/stderr
char *arguments[4] = { exe.c_str(), exe.c_str(), "/usr/bin", NULL };
execv(exe.c_str(), arguments);
// this could should never be reached
perror("error: executing the binary");
exit(EXIT_FAILURE);
}
fdhandle fork_connected_child(std::string exe) {
// create the socketpair
int fd[2];
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
perror("error, could not create socket pair");
exit(EXIT_FAILURE);
}
fdhandle fdparent(fd[0]); fdhandle fdchild(fd[1]);
// now create the child
pid_t pid = fork();
switch (pid) {
case -1: // could not fork
perror("error forking the child");
exit(EXIT_FAILURE);
break;
case 0: // child
rebind_and_exec_child(fdchild);
break;
default: // parent
return fdparent;
break;
}
}
int main(int argc, const char** argv) {
// create 2 childs
fdhandle fdparent1 = fork_connected_child("/bin/ls");
fdhandle fdparent2 = fork_connected_child("/bin/ls");
}
1 ответ
Я думаю, я нашел решение. Для каждого созданного сокета на socketpair()
позвони, я поставил FD_CLOEXEC
, Таким образом, я могу быть уверен, что ядро закрывает все файловые дескрипторы. Все остальные сокеты, которые обрабатываются моим кодом, будут закрыты вызовом класса fdhandle close()
, При переподключении stdin / stdout / stderr я заменил dup()
за dup2()
потому что это близко и дублировать атомно.
Подсказка была на этой странице: http://pubs.opengroup.org/onlinepubs/9699919799/functions/exec.html
Файловые дескрипторы, открытые в образе вызывающего процесса, должны оставаться открытыми в новом образе процесса, за исключением тех, чей флаг close-on-exec
FD_CLOEXEC
установлено. Для тех файловых дескрипторов, которые остаются открытыми, все атрибуты описания открытого файла остаются неизменными. Для любого файлового дескриптора, который закрыт по этой причине, блокировки файлов удаляются в результате закрытия, как описано вclose()
, Блокировки, которые не удаляются закрытием файловых дескрипторов, остаются без изменений.
Теперь это мой скорректированный код:
РЕДАКТИРОВАТЬ: скорректированная структура
#include <stdio.h>
#include <unistd.h>
#include <string.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <sys/types.h>
#include <fcntl.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/wait.h>
#include <memory>
#include <cassert>
#include <iostream>
// this handle takes a fd, behaves like an int and makes sure the fd is closed again
class fdhandle {
public:
fdhandle() {}
explicit fdhandle(int fd) {
mp_fd = std::shared_ptr<int>(new int, [=](int* pfd) {
close(*pfd);
delete pfd;
});
assert(mp_fd);
*mp_fd = fd;
// set FD_CLOEXEC on fd
int flags;
flags = fcntl(fd, F_GETFD);
if (-1 == flags) {
perror("error, could not get flags from filedescriptor");
exit(EXIT_FAILURE);
}
flags |= FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
perror("error, could not set FD_CLOEXEC");
exit(EXIT_FAILURE);
}
}
operator int() {
assert(mp_fd);
return *mp_fd;
}
void show_fd_status() {
if (!mp_fd)
return;
int fd = *mp_fd;
using namespace std;
char buf[256];
int fd_flags = fcntl(fd, F_GETFD);
if (fd_flags == -1)
return;
int fl_flags = fcntl(fd, F_GETFL);
if (fl_flags == -1)
return;
char path[256];
sprintf(path, "/proc/self/fd/%d", fd);
memset(&buf[0], 0, 256);
ssize_t s = readlink(path, &buf[0], 256);
if (s == -1) {
cerr << " (" << path << "): " << "not available";
return;
}
cerr << fd << " (" << buf << "): ";
// file status
if (fd_flags & FD_CLOEXEC) cerr << "cloexec ";
if (fl_flags & O_APPEND) cerr << "append ";
if (fl_flags & O_NONBLOCK) cerr << "nonblock ";
// acc mode
if (fl_flags & O_RDONLY) cerr << "read-only ";
if (fl_flags & O_RDWR) cerr << "read-write ";
if (fl_flags & O_WRONLY) cerr << "write-only ";
if (fl_flags & O_DSYNC) cerr << "dsync ";
if (fl_flags & O_RSYNC) cerr << "rsync ";
if (fl_flags & O_SYNC) cerr << "sync ";
struct flock fl;
fl.l_type = F_WRLCK;
fl.l_whence = 0;
fl.l_start = 0;
fl.l_len = 0;
fcntl(fd, F_GETLK, &fl);
if (fl.l_type != F_UNLCK)
{
if (fl.l_type == F_WRLCK)
cerr << "write-locked";
else
cerr << "read-locked";
cerr << "(pid:" << fl.l_pid << ") ";
}
}
private:
std::shared_ptr<int> mp_fd;
};
struct child
{
pid_t pid;
fdhandle fd;
};
void rebind_and_exec_child(fdhandle fd, std::string exe) {
// unset FD_CLOEXEC
int flags, oflags;
flags = oflags = fcntl(fd, F_GETFD);
if (-1 == flags) {
perror("error, could not get flags from filedescriptor");
exit(EXIT_FAILURE);
}
flags &= ~FD_CLOEXEC;
if (fcntl(fd, F_SETFD, flags) == -1) {
perror("error, could not unset FD_CLOEXEC");
exit(EXIT_FAILURE);
}
// close and rebind the stdin/stdout/stderr
// dup the fd three times and recreate stdin/stdout/stderr with fd as the target
if (dup2(fd, STDIN_FILENO) != 0 || dup2(fd, STDOUT_FILENO) != 1 || dup2(fd, STDERR_FILENO) != 2) {
perror("error duplicating socket for stdin/stdout/stderr");
exit(EXIT_FAILURE);
}
// restore the old flags
if (fcntl(fd, F_SETFD, oflags) == -1) {
perror("error, could not set FD_CLOEXEC");
exit(EXIT_FAILURE);
}
// now we can exec the new sub process and talk to it through
// stdin/stdout/stderr
char path[256];
char argv[256];
sprintf(path,"%s",exe.c_str());
sprintf(argv,"%d",30);
execlp(path, path, argv, 0);
// this should never be reached
perror("error: executing the binary");
exit(EXIT_FAILURE);
}
child fork_connected_child(std::string exe) {
// create the socketpair
int fd[2];
if (-1 == socketpair(PF_LOCAL, SOCK_STREAM, 0, fd)) {
perror("error, could not create socket pair");
exit(EXIT_FAILURE);
}
fdhandle fdparent(fd[0]); fdhandle fdchild(fd[1]);
// now create the child
pid_t pid = fork();
switch (pid) {
case -1: // could not fork
perror("error forking the child");
exit(EXIT_FAILURE);
break;
case 0: // child
rebind_and_exec_child(fdchild, exe);
break;
default: // parent
std::cout << "forked " << exe << std::endl;
return child { pid, fdparent };
break;
}
}
int main(int argc, const char** argv) {
// setup the signal handler prior to forking
sleep(20);
// create 2 childs
{
child child1 = fork_connected_child("/usr/bin/sleep");
child child2 = fork_connected_child("/usr/bin/sleep");
int status;
waitpid(child1.pid, &status, 0);
waitpid(child2.pid, &status, 0);
}
sleep(20);
}