Создает ли новый дубликат дескрипторов файлов и дескрипторы Socket в Linux?

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

Я читал, что разница между процессом и потоком в Linux заключается в том, что потоки разделяют одну и ту же память. В этом случае я предполагаю, что появление нового потока для обработки нового соединения также дублирует файловые дескрипторы, а также потребует, чтобы "родительский" поток закрыл его копию сокета?

Ответ 1

Нет. Темы имеют одинаковую память, поэтому они используют одни и те же переменные. Если вы закрываете сокет в родительском потоке, он также будет закрыт в дочернем потоке.

EDIT:

  • man fork: ребенок наследует копии родительского набора дескрипторов открытых файлов.

  • man pthreads: потоки совместно используют ряд других атрибутов (т.е. эти атрибуты являются общесистемными, а не потоковыми): [...] открывать дескрипторы файлов

И некоторый код:

#include <cstring>
#include <iostream>
using namespace std;

#include <errno.h>
#include <fcntl.h>
#include <pthread.h>
#include <unistd.h>

// global variable
int fd = -1;

void * threadProc(void * param) {
    cout << "thread: begin" << endl;
    sleep(2);
    int rc = close(fd);
    if (rc == -1) {
        int errsv = errno;
        cout << "thread: close() failed: " << strerror(errsv) << endl;
    }
    else {
        cout << "thread: file is closed" << endl;
    }
    cout << "thread: end" << endl;
}

int main() {
    int rc = open("/etc/passwd", O_RDONLY);
    fd = rc;

    pthread_t threadId;
    rc = pthread_create(&threadId, NULL, &threadProc, NULL);

    sleep(1);

    rc = close(fd);
    if (rc == -1) {
        int errsv = errno;
        cout << "main: close() failed: " << strerror(errsv) << endl;
        return 0;
    }
    else {
        cout << "main: file is closed" << endl;
    }

    sleep(2);
}

Выход:

thread: begin
main: file is closed
thread: close() failed: Bad file descriptor
thread: end

Ответ 2

В потоках Linux реализовано с помощью clone syscall с использованием флага CLONE_FILES:

Если установлено CLONE_FILES, вызов процесс и доля дочерних процессов ту же таблицу дескриптора файла. Любые файловый дескриптор, созданный вызывающим процесс или дочерний процесс также действительны в другом процессе. Аналогично, если один из процессов закрывает дескриптор файла или изменения связанных с ним флагов (используя fcntl (2) F_SETFD операция), другой процесс также затронут.

Также посмотрите исходный код glibc, чтобы узнать, как он используется в createthread.c:

  int clone_flags = (CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGNAL
             | CLONE_SETTLS | CLONE_PARENT_SETTID
             | CLONE_CHILD_CLEARTID | CLONE_SYSVSEM
#if __ASSUME_NO_CLONE_DETACHED == 0
             | CLONE_DETACHED
#endif
             | 0);

Ответ 3

В принципе, Linux clone() может реализовать не только новый процесс (например, fork()), либо новый поток (например, pthread_create), но и все, что между ними.

На практике он используется только для одного или другого. Темы, созданные с помощью pthread_create, совместно используют файловые дескрипторы со всеми остальными потоками процесса (а не только для родителя). Это не подлежит обсуждению.

Совместное использование дескриптора файла и наличие копии. Если у вас есть копия (например, fork()), все копии должны быть закрыты до удаления файла. Если вы разделите FD в потоке, как только он его закроет, он исчез.