Процесс, запущенный из системной команды в C, наследует родительский fd - PullRequest
11 голосов
/ 04 августа 2011

У меня есть пример приложения сервера SIP, прослушивающего порты tcp и udp 5060. В какой-то момент кода я делаю систему ("pppd file / etc / ppp / myoptions &");

После этого, если я выполняю netstat -apn, он показывает, что порты 5060 также открыты для pppd! Есть ли способ избежать этого? Это стандартное поведение системной функции в Linux?

Спасибо, Elison

Ответы [ 5 ]

14 голосов
/ 04 августа 2011

Да, по умолчанию всякий раз, когда вы запускаете процесс (что делает system), дочерний объект наследует все файловые дескрипторы родителя. Если ребенку не нужны эти дескрипторы, он ДОЛЖЕН их закрыть. Способ сделать это с помощью system (или любого другого метода, который выполняет fork + exec) - установить флаг FD_CLOEXEC для всех файловых дескрипторов, которые не должны использоваться дочерними элементами вашего процесса. Это заставит их автоматически закрываться всякий раз, когда какой-либо дочерний элемент исполняет какую-либо другую программу.

Как правило, в ЛЮБОЕ ВРЕМЯ ваша программа открывает ЛЮБОЙ ВИД дескриптора файла, который будет существовать в течение длительного периода времени (например, сокета прослушивания в вашем примере), и который не должен передаваться детям, вы должны сделать

fcntl(fd, F_SETFD, fcntl(fd, F_GETFD) | FD_CLOEXEC);

в дескрипторе файла.


По состоянию на 2016 год? В версии POSIX.1 вы можете использовать флаг SOCK_CLOEXEC или тип сокета, чтобы автоматически получить это поведение при создании сокета:

listenfd = socket(AF_INET, SOCK_STREAM|SOCK_CLOEXEC, 0);
bind(listenfd, ...
listen(listemfd, ...

который гарантирует, что он будет закрыт должным образом, даже если какой-то другой одновременно запущенный поток выполняет вызов system или fork + exec. К счастью, этот флаг уже некоторое время поддерживается в Unix-системах Linux и BSD (но, к сожалению, не в OSX).

3 голосов
/ 04 августа 2011

Вам, вероятно, следует вообще избегать функции system(). Это опасно по своей природе, так как вызывает оболочку, которая может быть изменена и довольно непереносима даже между Unicies.

То, что вы должны сделать, это fork()/exec() танец. Это выглядит примерно так

if(!fork()){
     //close file descriptors
     ...

    execlp("pppd", "pppd", "file", "/etc/ppp/myoptions", NULL);
    perror("exec");
    exit(-1);
}
1 голос
/ 04 августа 2011

Как уже говорили другие, это стандартное поведение, от которого зависят программы.

Когда дело доходит до предотвращения этого, у вас есть несколько вариантов. Во-первых, закрывает все файловые дескрипторы после fork(), как предлагает Дейв. Во-вторых, есть поддержка POSIX для использования fcntl с FD_CLOEXEC для установки бита 'close on exec' для каждого fd.

И наконец, поскольку вы упоминаете, что работаете в Linux, существует ряд изменений, предназначенных для того, чтобы вы могли установить бит прямо в момент открытия. Естественно, это зависит от платформы. Обзор можно найти на http://udrepper.livejournal.com/20407.html

Это означает, что вы можете использовать побитовое или с 'type' в вашем вызове создания сокета, чтобы установить флаг SOCK_CLOEXEC. При условии, что вы используете ядро ​​2.6.27 или новее, то есть.

1 голос
/ 04 августа 2011

Да, это стандартное поведение fork() в Linux, из которого реализовано system().

Идентификатор, возвращаемый из вызова socket(), является допустимым дескриптором файла. Это значение можно использовать с файловыми функциями, такими как read(), write(), ioctl() и close().

Обратное утверждение о том, что каждый файловый дескриптор является сокетом, неверно. Нельзя открыть обычный файл с помощью open() и передать этот дескриптор, например, bind() или listen().

Когда вы вызываете system(), дочерний процесс наследует те же файловые дескрипторы, что и родительский. Вот как stdout (0), stdin (1) и stderr (2) наследуются дочерними процессами. Если вы решите открыть сокет с дескриптором файла 0, 1 или 2, дочерний процесс унаследует этот сокет как один из стандартных файловых дескрипторов ввода / вывода.

Ваш дочерний процесс наследует каждый открытый дескриптор файла от родительского, включая открытый вами сокет.

0 голосов
/ 04 августа 2011

system() копирует текущий процесс, а затем запускает дочерний процесс поверх него. (текущего процесса больше нет. Возможно, поэтому pppd использует 5060. Вы можете попробовать fork()/exec() создать дочерний процесс и сохранить родительский процесс.

...