Предположим, что процесс с несколькими (реализуемыми в ядре) потоками делает системный вызов fork. Следует ли в новом процессе создавать все остальные потоки? Предположим, что мы ответили на этот вопрос утвердительно. Допустим также, что один из остальных потоков был заблокирован (в ожидании ввода с клавиатуры). Должен ли поток в новом процессе также быть блокирован ожиданием ввода с клавиатуры? Если да, то какому потоку достанется следующая набранная на клавиатуре строка? Если нет, то что должен делать этот поток в новом процессе?
Эта проблема касается и многих других аспектов. В однопоточном процессе такой проблемы не возникает, так как единственный поток не может быть блокирован при вызове fork. Теперь рассмотрим случай, при котором в дочернем процессе остальные потоки не создаются. Предположим, что один из несозданных потоков удерживает мьютекс, который пытается получить единственный созданный поток нового процесса (после выполнения вызова fork). В этом случае мьютекс никогда не будет освобожден и новый поток повиснет навсегда. Существует также множество других проблем. И простого решения нет.
Файловый ввод-вывод представляет собой еще одну проблемную область. Предположим, что один поток заблокирован при чтении из файла, а другой поток закрывает файл или делает системный вызов lseek, чтобы изменить текущий указатель файла. Что произойдет дальше? Кто знает?
Обработка сигналов тоже представляет собой сложный вопрос. Должны ли сигналы направляться определенному потоку или всему процессу в целом? Вероятно, сигнал SIGFPE (Floating-Point Exception SIGnal — сигнал исключения при выполнении операции с плавающей точкой) должен перехватываться тем потоком, который его вызвал. А что, если он его не перехватывает? Следует ли убить этот поток? Или следует убить все потоки? Рассмотрим теперь сигнал SIGINT, генерируемый сидящим за клавиатурой пользователем. Какой поток должен перехватывать этот сигнал? Должен ли у всех потоков быть общий набор масок сигналов? При решении подобных проблем любые попытки вытянуть нос в одном месте приводят к тому, что в каком-либо другом месте увязает хвост. Корректная реализация семантики потоков (не говоря уже о коде) представляет собой нетривиальную задачу.
В 2000 году в Linux был введен новый мощный системный вызов clone, который размыл различия между процессами и потоками и, возможно, даже инвертировал первенство этих двух концепций. Вызова clone нет ни в одной из версий UNIX. Классически при создании нового потока исходный поток (потоки) и новый поток совместно использовали все, кроме регистров, — в частности, дескрипторы для открытых файлов, обработчики сигналов, прочие глобальные свойства — все это было у каждого процесса, а не у потока. Системный вызов clone дал возможность все эти аспекты сделать характерными как для процесса, так и для потока. Формат вызова выглядит следующим образом:
```
pid = clone(function, stack_ptr, sharing_flags, arg);
```
Вызов clone создает новый поток либо в текущем, либо в новом процессе (в зависимости от флага sharing_flags). Если новый поток находится в текущем процессе, то он совместно с существующими потоками использует адресное пространство и каждая последующая запись в любой байт адресного пространства (любым потоком) тут же становится видима всем остальным потокам процесса. Если же адресное пространство совместно не используется, то новый поток получает точную копию адресного пространства, но последующие записи из нового потока уже не видны старым потокам. Здесь используется та же семантика, что и у системного вызова fork по стандарту POSIX.
В обоих случаях новый поток начинает выполнение функции function с аргументом arg в качестве единственного параметра. Также в обоих случаях новый поток получает собственный приватный стек, при этом указатель стека инициализируется параметром stack_ptr.
Параметр sharing_flags представляет собой битовый массив, обеспечивающий существенно более тонкую настройку совместного использования, чем традиционные системы UNIX. Каждый бит может быть установлен независимо от остальных, и каждый из них определяет, копирует новый поток эту структуру данных или использует ее совместно с вызывающим потоком. В табл. 10.4 показаны некоторые элементы, которые можно использовать совместно или копировать — в соответствии со значением битов в sharing_flags.
| Флаг | Значение при установке в 1 | Значение при установке в 0 |
| ----- | ---- |----|
| CLONE_VM | Создать новый поток | Создать новый процесс |
| CLONE_FS | Общие рабочий каталог, каталог root и umask | Не использовать их совместно |
|CLONE_FILES | Общие дескрипторы файлов | Копировать дескрипторы файлов |
| CLONE_SIGHAND | Общая таблица обработчика сигналов | Копировать таблицу |
| CLONE_PARENT | Новый поток имеет того же родителя, что и вызывающий |Родителем нового потока является вызывающий |