[Linux] Введение в системное программирование Linux

Linux

Автор: No Dishwashing Studio - Marklux
Источник:marklux.cn/blog/56
Все права принадлежат автору, при перепечатке указывать источник

файлы и файловые системы

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

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

В ядре Linux файлы представлены целым числом, называемымфайловый дескриптор, с точки зрения непрофессионала, вы можете понять, что это id (уникальный идентификатор) файла

обычный файл

  • Обычные файлы представляют собой данные, организованные в виде потока байтов.
  • Файлы реализуются не путем ассоциации с именами файлов, а путем ассоциацииинодаДля этого файловый узел имеет уникальное целочисленное значение (ino), выделенное файловой системой для обычных файлов, и хранит некоторые связанные метаданные файла.

Содержание и ссылки

  • Обычно файлы открываются по имени файла.
  • содержаниепредставляет собой сопоставление удобочитаемых имен с номерами индексов, а пары между именами и индексными дескрипторами называютсяСсылка на сайт.
  • Каталог можно рассматривать как обычный файл, за исключением того, что он содержит сопоставление (ссылку) имен файлов с индексными дескрипторами.

процесс

Процесс — это абстрактное понятие, уступающее только файлу.Проще говоря, процесс — это исполняемый целевой код, активная работающая программа. Однако в сложных случаях процесс также будет содержать различные данные, ресурсы, состояние и даже виртуальные компьютеры.

Вы можете думать о процессе так: это основная единица конкуренции за компьютерные ресурсы.

Процессы, программы и потоки

  1. программа

    Простыми словами, программы — это двоичные файлы, которые существуют на диске и представляют собой код, который может выполняться ядром.

  2. процесс

    Когда пользователь запускает программу, она открывает пространство в памяти, которое создает процесс, процесс содержит уникальный PID и параметры атрибута разрешения исполнителя, а также код и сопутствующую информацию, требуемую программой.

    Процесс — это основная единица, с помощью которой система распределяет ресурсы.

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

  3. нить

    Каждый процесс содержит один или несколько потоков.Поток — это единица активности внутри процесса, абстракция, отвечающая за выполнение кода и управление текущим состоянием процесса.

    Поток — это основная единица независимой работы и планирования.

Иерархия процессов (родительский и дочерний)

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

В результате в системе Linux родилась иерархия процессов, дерево процессов.

Корнем дерева процессов является первый процесс (процесс инициализации).

Поток вызова процедуры: fork & exec

Процесс генерации дочернего процесса заключается в том, что система сначала копирует (разветвляет) родительский процесс и генерирует временный процесс.Разница между этим временным процессом и родительским процессом заключается в том, что pid отличается, и у него есть ppid. В это время система перейдет к выполнению (exec) этого временного процесса, позволит ему загрузить фактическую программу для запуска и в конечном итоге станет дочерним процессом.

конец процесса

Когда процесс завершается, он не сразу удаляется из системы, ядро ​​сохраняет часть процесса в памяти, позволяя родительскому процессу запрашивать его состояние (это называется ожиданием завершенного процесса).

Когда родительский процесс определяет, что дочерний процесс завершен, дочерний процесс будет полностью удален.

Но если дочерний процесс завершился, а родительский процесс не знает его состояния, то процесс станетпроцесс зомби

Услуги и процессы

Проще говоря, служба (демон) — это процесс, который находится в памяти, обычно служба запускается во время загрузки через скрипт в init.d.

связь процесса

Несколько основных способов взаимодействия процессов: конвейеры, семафоры, очереди сообщений, разделяемая память, быстрые мьютексы пользовательского управления.

Программы, процессы и потоки

Теперь мы снова подробно обсудим эти три понятия.

программа

Программа представляет собой скомпилированный исполняемый двоичный код, хранящийся на носителе данных,не бегать.

процесс

процесс означаетБегпрограмма о.

Процесс включает в себя множество ресурсов и имеет собственное независимое пространство памяти.

нить

Поток — это единица активности внутри процесса.

Это включает в себя собственное виртуальное хранилище, такое как стеки, состояние процесса, такое как регистры, и указатели инструкций.

  • В однопоточном процессе поток — это процесс. В многопоточном процессе несколько потоков будут совместно использовать одно и то же адресное пространство памяти.

  • эталонное чтение

PID

Вы можете обратиться к предыдущему разделу основных понятий.

В языке C PID определяется типом данныхpid_tпредставлять.

запустить процесс

Создайте процесс, который разделен на два процесса в системе Unix.

  1. Загрузите программу в память и выполните операцию образа программы:exec
  2. Создайте новый процесс:fork

exec

Простейшая функция системного вызова exec:execl()

  • Прототип функции:
int execl(const char * path,const chr * arg,...)

Вызов execl() загрузит образ пути, на который указывает path, в память, заменив образ текущего процесса.

Параметр arg является первым параметром, содержимое параметра является переменным, но последний должен заканчиваться NULL.

  • Пример:
int ret;

ret = execl("/bin/vi","vi",NULL);

if (ret == -1) {
    perror("execl");
}

Приведенный выше код пройдет/bin/viЗаменить текущую программу

Обратите внимание, что первый параметр vi здесь является соглашением по умолчанию для систем unix.При создании и выполнении процесса оболочка помещает последнюю часть пути в первый параметр нового процесса, чтобы процесс мог анализировать двоичный образ. имя файла.

int ret;

ret = execl("/bin/vi","vi","/home/mark/a.txt",NULL);

if (ret == -1) {
    perror("execl");
}

Вышеприведенный код является очень репрезентативной операцией, которая эквивалентна выполнению следующей команды в терминале:

vi /home/mark/a.txt
  • возвращаемое значение:

При нормальных обстоятельствах execl() не возвращает значение После успешного вызова выполняется переход к новой точке входа в программу.

Успешный вызов execl() изменит адресное пространство и образ процесса, а также многие другие атрибуты процесса.

Однако PID, PPID, приоритет и другие параметры процесса будут сохранены, и даже дескрипторы открытых файлов (что означает, что он может получить доступ ко всем файлам, открытым исходным процессом).

В случае сбоя будет возвращено -1, а значение errno будет обновлено.

Другие исполнительные функции

опущен, ищите при использовании

fork

С помощью системного вызова fork() вы можете создать дочерний процесс, точно такой же, как текущий образ процесса.

  • прототип функции
pid_t fork(void)

После успешного вызова создается новый процесс (дочерний процесс), и оба процесса продолжают работать.

  • возвращаемое значение

Если вызов удался,
В родительском процессе fork() вернет pid дочернего процесса и вернет 0 в дочернем процессе;
Если это не удается, верните -1 и обновите errno, дочерний процесс не будет создан.

  • Пример

Давайте посмотрим на следующий код

#include <unistd.h>
#include <stdio.h>
int main ()
{
    pid_t fpid; //fpid表示fork函数返回的值
    int count=0;

    printf("this is a process\n");

    fpid=fork();

    if (fpid < 0)
        printf("error in fork!");
    else if (fpid == 0) {
        printf("i am the child process, my process id is %d\n",getpid());
        printf("我是爹的儿子\n");
        count++;
    }
    else {
        printf("i am the parent process, my process id is %d\n",getpid());
        printf("我是孩子他爹\n");
        count++;
    }
    printf("统计结果是: %d\n",count);
    return 0;
}

Результат выполнения этого кода довольно волшебный, вот он:

this is a process
i am the parent process, my process id is 21448
我是孩子他爹
统计结果是: 1
i am the child process, my process id is 21449
我是爹的儿子
统计结果是: 1

После выполнения fork() программа имеет два процесса: родительский процесс и дочерний процесс продолжают выполнять код и входят в разные ветки if.

Как понять, что pid отличается в родительском и дочернем процессах?

По сути, это эквивалентно связному списку. Процесс формирует связанный список. PID родительского процесса указывает на pid дочернего процесса. Поскольку дочерний процесс не имеет дочернего процесса, pid равен 0.

копирование при записи

Традиционный механизм fork заключается в том, что при вызове fork ядро ​​копирует все внутренние структуры данных, копирует записи таблицы страниц процесса, а затем постранично копирует адресное пространство родительского процесса в дочерний процесс (очень трудоемко). .

Современные механизмы форка используют ленивую алгоритмическую стратегию оптимизации.

Чтобы избежать накладных расходов системы при копировании, операция «копирования» максимально сокращена.Когда нескольким процессам нужно прочитать собственные копии собственных ресурсов, они не копируют несколько копий, а устанавливают по одной для каждого процесса. файловые указатели, пусть они читают один и тот же фактический файл.

Очевидно, что этот метод будет вызывать конфликты (аналогичные параллелизму) при записи, поэтому, когда процесс хочет изменить свою собственную копию, он будет копировать ресурс (только при записи копируется, поэтому это называется копированием при записи). частота дублирования.

совместная инстанция

Создайте подпроцесс в программе, чтобы открыть другое приложение.

pid_t pid;

pid = fork();

if (pid == -1)
    perror("fork");

//子进程
if (!pid) {
    const char * args[] = {"windlass",NULL};

    int ret;

    // 参数以数组方式传入
    ret = execv("/bin/windlass",args);

    if (ret == -1) {
        perror("execv");
        exit(EXIT_FAILURE);
    }
}

Приведенная выше программа создает дочерний процесс и заставляет дочерний процесс запускать программу /bin/windlas.

убить процесс

exit()

  • прототип функции
void exit (int status)

Эта функция используется для завершения текущего процесса.Статус параметра используется только для определения статуса завершения процесса.Это значение будет передано родительскому процессу текущего процесса для оценки.

Есть и другие функции вызова завершения, которые здесь не описываются.

дождитесь завершения дочернего процесса

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

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

Поэтому Unix изначально сделал такой дизайн.Если дочерний процесс завершается раньше родительского процесса, ядро ​​переводит дочерний процесс в особое рабочее состояние.Процесс в этом состоянии называетсяпроцесс зомби, он сохраняет только минимальную сводную информацию и будет уничтожен после того, как родительский процесс получит эту информацию.

wait()

  • прототип функции
pid_t wait(int * status);

Эту функцию можно использовать для получения информации о завершившихся дочерних процессах.

Возвращает pid завершившегося дочернего процесса в случае успеха или -1 в случае ошибки. Если ни один дочерний процесс не завершится, вызов будет заблокирован до тех пор, пока не завершится один из дочерних процессов.

waitpid()

  • прототип функции
pid_t waitpid(pid_t pid,int * status,int options);

waitpid() — более мощный системный вызов, поддерживающий более детальное управление.

Некоторые другие функции ожидания, с которыми можно столкнуться

  • wait3()

  • wait4()

Проще говоря, wait3 ожидает завершения любого дочернего процесса, а wait4 ожидает завершения указанного дочернего процесса.

Создать и дождаться нового процесса

Часто мы сталкиваемся со следующей ситуацией:

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

Подходящим вариантом являетсяsystem():

int system(const char * command);

Функция system() будет вызывать команду, предоставленную командой, которая обычно используется для запуска простых инструментов и сценариев оболочки.

В случае успеха возвращается статус возврата, полученный при выполнении команды command.

Вы можете использовать fork(), exec(), waitpid() для реализации system().

Простая реализация приведена ниже:

int my_system(const char * cmd)
{
    int status;
    pid_t pid;

    pid = fork();

    if (pid == -1) {
        return -1;
    }

    else if (pid == 0) {
        const char * argv[4];

        argv[0] = "sh";
        argv[1] = "-c";
        argv[2] = cmd;
        argv[3] = NULL;

        execv("bin/sh",argv);
        // 这传参调用好像有类型转换问题

        exit(-1);

    }//子进程

    //父进程
    if (waitpid(pid,&status,0) == -1)
        return -1;
    else if (WIFEXITED(status))
        return WEXITSTATUS(status);

    return -1;
}

призрачный процесс

Выше мы говорили о процессах-зомби, но если родительский процесс не будет ждать операции дочернего процесса, то все его дочерние процессы станутпризрачный процесс, процесс-призрак всегда будет существовать (поскольку он не завершится до тех пор, пока не будет вызван родительский процесс), что приведет к замедлению работы системы.

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

В ядре Linux есть механизм, позволяющий избежать этого.

Всякий раз, когда процесс завершается, ядро ​​​​обходит все его дочерние процессы и сбрасывает их родительский процесс в состояние init, которое периодически ожидает всех дочерних процессов, чтобы убедиться, что нет долгоживущих процессов-призраков.

процессы и разрешения

пропущено, будет добавлено

Сеансы и группы процессов

группа процессов

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

Идентификатор группы процессов — это pid первого процесса в группе процессов (если в группе процессов только один процесс, между группой процессов и процессом нет никакой разницы).

Значение группы процессов заключается в том, что сигнал может быть отправлен всем процессам в группе процессов. Это позволяет выполнять одновременные операции над несколькими процессами.

беседа

Сеанс — это набор из одной или нескольких групп процессов.

Вообще говоря, между сеансом и оболочкой нет существенной разницы.

Мы обычно описываем сеанс на примере входа пользователя в терминал и выполнения ряда операций.

  • Пример
$cat ship-inventory.txt | grep booty|sort

Выше приведена команда оболочки в сеансе, которая создаст группу процессов, состоящую из 3 процессов.

Демон (Сервис)

Демон работает в фоновом режиме и не связан с каким-либо управляющим терминалом. Обычно он запускается при запуске системы путем вызова сценария инициализации.

В системе Linux нет разницы между демоном и службой.

Для процесса-демона есть два основных требования: первое: он должен работать как подпроцесс процесса инициализации, и второе: он не должен взаимодействовать ни с каким управляющим терминалом.

Процесс порождения процесса демона

  1. перечислитьfork()создать дочерний процесс (он вот-вот станет демоном)
  2. Вызывается в родительском процессе этого процессаexit(), который гарантирует, что родительский процесс родительского процесса завершится, когда его дочерний процесс завершится, гарантируя, что родительский процесс демона не будет продолжать работать, а демон не будет первым процессом. (Он наследует идентификатор группы процессов родительского процесса и не должен быть лидером)
  3. перечислитьsetsid(), создать новую группу процессов и новый сеанс для демона и служить первым процессом обоих. Это гарантирует отсутствие управляющего терминала, связанного с демоном.
  4. перечислитьchdir(), измените текущий рабочий каталог на корневой каталог. Это сделано для того, чтобы демон не запускался в случайном каталоге, открытом родительским процессом исходного форка, что удобно для управления.
  5. Закройте все файловые дескрипторы.
  6. Откройте файловые дескрипторы 0, 1, 2 (stdin, stdout, err) и перенаправьте их на/dev/null.

daemon()

Используется для реализации вышеуказанной операции для создания процесса демона.

  • прототип функции
int daemon(int nochdir,int noclose);

Если параметр nochdir не равен нулю, рабочий каталог не будет направлен в корневой каталог.
Если параметр noclose не равен нулю, все открытые файловые дескрипторы не будут закрыты.

Возвращает 0 в случае успеха и -1 в случае неудачи.

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

Это выглядит так, как будто текущий выполняемый процесс оборачивается в процесс демона, но на самом деле он оборачивает его разветвленную копию.

нить

Базовые концепты

Поток — это единица выполнения внутри процесса (концепция более низкого уровня, чем процесс), включая виртуальные процессоры, стеки, состояния программы и т. д.

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

Современные операционные системы делают две основные абстракции пользовательского пространства: виртуальную память и виртуальные процессоры. Это заставляет процесс внутренне «чувствовать», что он монополизирует машинные ресурсы.

Виртуальная память

Система будет выделять отдельное пространство памяти для каждого процесса, что заставит процесс думать, что у него есть только вся оперативная память.

Но все потоки внутри одного и того же процесса совместно используют пространство памяти этого процесса.

виртуальный процессор

Это специфичная для потока концепция, которая заставляет каждый поток «чувствовать», что он имеет эксклюзивный доступ к ЦП. На самом деле то же самое верно и для процессов.

Многопоточность

Преимущества многопоточности

  • абстракция программирования

    Шаблоны модульного проектирования

  • параллелизм

    Настоящий параллелизм может быть достигнут на многоядерных процессорах, повышая пропускную способность системы.

  • Улучшить отзывчивость

    Предотвращение зависания последовательной операции

  • предотвратить блокировку ввода/вывода

    Избегайте ситуации, когда весь процесс блокируется операциями ввода-вывода в рамках одного потока. Ее также можно решить с помощью асинхронного ввода-вывода и неблокирующего ввода-вывода.

  • уменьшить переключение контекста

    Многопоточное переключение потребляет гораздо меньше производительности, чем переключение контекста между процессами.

  • совместное использование памяти

    Поскольку потоки внутри одного процесса могут совместно использовать память, эти функции можно использовать в некоторых сценариях для замены нескольких процессов несколькими потоками.

Стоимость многопоточности

Отладка чрезвычайно сложна.

Параллельные операции чтения и записи в одном и том же пространстве памяти могут вызвать различные проблемы (например, грязные данные), становится трудно синхронизировать ресурсы в сценарии с несколькими процессами, а время и порядок нескольких независимо работающих потоков непредсказуемы, что может привести к всякие странные проблемы.

Это может относиться к проблемам, вызванным параллелизмом.

резьбовая модель

Концепция потоков существует как в ядре, так и в пользовательском пространстве.

Модель потоков на уровне ядра

Каждый поток ядра напрямую транслируется в поток пользовательского пространства. то есть потоки ядра: потоки пользовательского пространства = 1: 1

Модель потоков на уровне пользователя

В этой модели пользовательский процесс, защищающий n потоков, будет сопоставлен только с одним процессом ядра. То есть n:1.

Это может снизить стоимость переключения контекста, но под linux это бессмысленно, т.к. переключение контекста между процессами под linux само по себе много не потребляет, поэтому используется редко.

Гибридная многопоточная модель

Смесь двух вышеуказанных моделей, тип n:m.

сложно осознать.

* Сопрограммы

‌Обеспечивает более легкий исполнительный модуль, чем поток.

режим потока

Один поток на соединение

То есть блокировка ввода-вывода, что на самом деле является однопоточным режимом.

Потоки выполняются последовательно. Когда поток сталкивается с вводом-выводом, поток должен быть приостановлен и дождаться завершения операции, прежде чем продолжить.

шаблон потока, управляемый событиями

В однопоточной модели операций большая часть системной нагрузки приходится на ожидание (особенно операции ввода-вывода), поэтому в режиме, управляемом событиями, эти ожидающие операции удаляются из процесса выполнения потока, а асинхронный ввод-вывод выполняется. отправляются путем отправки асинхронных операций ввода-вывода.Запрос или мультиплексирование ввода-вывода, введение циклов обработки событий и обратных вызовов для обработки взаимосвязи между потоками и вводом-выводом.

Для получения информации о нескольких режимах ввода-вывода см. здесь

Вкратце можно выделить четыре типа:

  • Блокирующий ввод-вывод: последовательная обработка, один поток, синхронное ожидание
  • Неблокирующий ввод-вывод: после того, как поток инициирует запрос ввода-вывода, он получит результат немедленно, а не ждет. Если ввод-вывод не обработан, он вернет ОШИБКУ. Поток должен активно обращаться к ядру, чтобы непрерывно запрашивать, чтобы определить, ИО завершен.
  • Асинхронный ввод-вывод: после того, как поток инициирует запрос ввода-вывода, он немедленно получит результат.После того, как ядро ​​выполнит ввод-вывод, оно будет активно отправлять СИГНАЛ, чтобы уведомить поток
  • Управляемый событиями ввод-вывод: это обновление неблокирующего ввода-вывода. В основном используется в случаях, когда есть много подключений. Пусть ядро ​​​​наблюдает за несколькими сокетами (каждый сокет является неблокирующим вводом-выводом) и какой сокет имеет результат, это будет продолжать выполнять какой сокет.

Параллелизм, Параллелизм, Конкуренция!

Параллелизм и параллелизм

Параллелизм относится к необходимости запуска (обработки) нескольких потоков в один и тот же период времени.

Параллелизм означает, что несколько потоков выполняются одновременно.

По сути, параллелизм — это концепция программирования, а параллелизм — это аппаратное свойство, и параллелизм может быть реализован параллельно или без параллелизма (один процессор).

конкурировать

Самая большая проблема параллельного программированияконкурировать, это в основном потому, что при одновременном выполнении нескольких потоков существует порядок результатов выполнения.непредсказуемость

  • Для простейшей демонстрации вы можете обратиться к базовому примеру параллельного программирования на Java.

    См. следующую строку кода:

      x++;

    Предположим, что начальное значение x равно5, мы используем два потока для одновременного выполнения этой строки кода, будет много разных результатов, то есть после завершения операции значение x может быть 6 или 7. (Это самая простая демонстрация параллелизма, и ее легко понять самостоятельно.)

    Кратко причины описываются следующим образом:

    один поток выполняетсяx++Процесс условно делится на 3 этапа:

    1. загрузить x в регистр
    2. +1 значение регистра
    3. записать значение регистра обратно в x

      Когда два потока конкурируют, именно процесс выполнения этих трех шагов является непредсказуемым во времени.Предположим, что когда потоки 1 и 2 загружают x в регистр, x равно 5, но когда поток 1 записывает обратно в x, x становится 6, и когда поток 2 записывает обратно в x, x по-прежнему равен 6, что противоречит первоначальному намерению.

      Если потоков больше, результат станет более непредсказуемым.

Средства решения конкуренции: синхронизация

Короче говоря, это отменяет параллелизм ресурсов, которые будут конкурировать, и вместо этого использует синхронный способ доступа и работы.

Замок

Наиболее распространенным механизмом для работы с параллелизмом является механизм блокировки.Конечно, блокировки системного уровня проще, чем блокировки в других сложных системах, таких как СУБД (нет более сложных понятий, таких как разделяемые блокировки и монопольные блокировки).

Но блокировки приносят две проблемы:死锁и饿死.

Решение этих двух проблем требует некоторой механики, а также концепций дизайна. Дополнительные сведения о блокировках см. в примечаниях по параллелизму СУБД.

Одна вещь, которую нужно помнить о замках.

Блокирует ресурсы, а не код

Этот принцип следует учитывать при написании кода.

Реализация системного потока: PThreads

В исходных системных вызовах Linux нет полной библиотеки потоков, такой как C++11 или Java.

В целом API-интерфейс pthread избыточен и сложен, но основные операции — это в основном создание, выход и т. д.

Следует отметить, что в механизме Linux есть поток с именемjoinableстатус. Вот краткий обзор:

Присоединяйтесь и отсоединяйтесь

Концепция этого блока очень похожа на предыдущую часть родительско-дочернего процесса,дождитесь завершения дочернего процессаcontent (ряд функций ожидания).

В механизме Linux потоки существуют в двух разных состояниях:joinableиunjoinable.

Если поток помечен какjoinable, даже если его функция потока завершает выполнение или используетpthread_exit()После завершения потока ресурсы стека и дескрипторы процессов, которые он занимает, не будут освобождены (аналогично зомби-процессу), который должен быть вызван создателем потока.pthread_join()Дождаться окончания потока и перезапустить его ресурсы (аналогично функции ожидания). Потоки, созданные по умолчанию, находятся в этом состоянии.

Если поток помечен какunjoinable, говорят, что он отсоединен (detached), в это время, если поток завершится, все его ресурсы будут автоматически высвобождены. Избавь себя от необходимости вытирать задницу.

Поскольку созданный поток по умолчаниюjoinable, поэтому либо вызовите родительский потокpthread_detach(thread_id)Чтобы отсоединить его, либо внутри потока, вызовитеpthread_detach(pthread_self())пометить себя как отстраненного.