Атомарные операции
Дозапись в файл
Допустим, есть два процесса, которые пишу данные в один и тот же файл (например, глобальный лог). Пусть каждый процесс независимо открывает файл по имени у себя и выполняет запись сообщения в него.
Старые программы могли делать запись следующим образом:
if (lseek(fd, 0L, 2) < ) /* position to EOF */ err_sys("lseek error"); if (write(fd, buf, 100) != 100) /* and write */ err_sys("write error");
Есть проблема гонки между двумя системными вызовами. Если между lseek и write первого процесса влезет второй процесс и успеет сделать то же, то первый процесс в итоге запишет свои данные поверх тех, что только что записал второй.
На сегодняшний день для решения этой задачи следует открывать файл в режиме O_APPEND. Тогда перемотка позиции на конец файла в соответствии с его текущим размером и сама запись выполняются атомарно.
pread и pwrite
#include <unistd.h> ssize_t pread(int fd, void *buf, size_t count, off_t offset); ssize_t pwrite(int fd, const void *buf, size_t count, off_t offset);
Выполняют чтение и запись по заданному смещению. Текущее смещение они вообще не трогают.
Эти функции могу быть полезны в многопоточных программах и в случае использования одного дескриптора из разных процессов. Позволяют избежать гонки.
Создание файла
Допустим, стоит задача создать файл, если он не существует, или вывести ошибку, если он уже существует.
Наивный подход такой:
if ((fd = open(path, O_WRONLY)) < ) { if (errno == ENOENT) { if ((fd = creat(path, mode)) < ) { err_sys("creat error"); } else { err_sys("open error"); } } }
Но тут возможна гонка.
Следует использовать комбинацию флагов O_CREAT и O_EXCL.
Операции с файловой системой
Операции rename, link, symlink, mkdir атомарны.
Если надо подменить файл:
- Записать данные во временный файл.
- Сделать fsync.
- Закрыть временный файл.
- Переименовать временный файл в требуемое имя.
Задачи
- содействовать облегчению переноса кода прикладных программ на иные платформы;
- способствовать определению и унификации интерфейсов заранее при проектировании, а не в процессе их реализации;
- сохранять по возможности и учитывать все главные, созданные ранее и используемые прикладные программы;
- определять необходимый минимум интерфейсов прикладных программ, для ускорения создания, одобрения и утверждения документов;
- развивать стандарты в направлении обеспечения коммуникационных сетей, распределенной обработки данных и защиты информации;
- рекомендовать ограничение использования бинарного (объектного) кода для приложений в простых системах.
Блокирующий режим
По умолчанию все файловые дескрипторы в Unix-системах создаются в “блокирующем” режиме. Это означает, что системные вызовы для ввода-вывода, такие как , или , могут заблокировать выполнение программы вплоть до готовности результата операции. Легче всего понять это на примере чтения данных из потока stdin в консольной программе. Как только вы вызываете для stdin, выполнение программы блокируется, пока данные не будут введены пользователем с клавиатуры и затем прочитаны системой. То же самое происходит при вызове функций стандартной библиотеки, таких как , , , поскольку все они в конечном счёте используют системный вызов . Если говорить конкретнее, ядро погружает процесс в спящее состояние, пока данные не станут доступны в псевдо-файле stdin. То же самое происходит и для любых других файловых дескрипторов. Например, если вы пытаетесь читать из TCP-сокета, вызов заблокирует выполнение, пока другая сторона TCP-соединения не пришлёт ответные данные.
Блокировки — это проблема для всех программ, требующих конкурентного выполнения, поскольку заблокированные потоки процесса засыпают и не получают процессорное время. Существует два различных, но взаимодополняющих способа устранить блокировки:
- неблокирующий режим ввода-вывода
- мультиплексирование с помощью системного API, такого как либо
Эти решения часто применяются совместно, но предоставляют разные стратегии решения проблемы. Скоро мы узнаем разницу и выясним, почему их часто совмещают.
Ответ 1
POSIX — это семейство стандартов, разработанных IEEE для уточнения и унификации интерфейсов прикладного программирования (и вспомогательных вопросов, таких как утилиты командной строки), предоставляемых операционными системами Unix. Когда вы пишете свои программы, опираясь на стандарты POSIX, вы можете быть уверены, что сможете легко переносить их среди большого семейства производных Unix (включая Linux, но не ограничиваясь им!); если и когда вы используете какой-то API Linux, который не стандартизован как часть Posix, вам будет труднее, если и когда вы захотите перенести эту программу или библиотеку на другие Unix системы (например, MacOSX) в будущем.
6 ответов
Лучший ответ
Это может быть проблема с кодировкой файла.
Я столкнулся с проблемами кодирования типов файлов при работе с файлами между разными операционными системами и редакторами — в моем случае особенно между системами Linux и Windows.
Я предлагаю проверить кодировку вашего файла, чтобы убедиться, что он подходит для целевой среды Linux. Я предполагаю, что проблема с кодировкой менее вероятна, если вы используете MAC, чем если бы вы использовали текстовый редактор Windows, однако я думаю, что кодирование файлов все же стоит рассмотреть.
— РЕДАКТИРОВАТЬ (добавить реальное решение, как рекомендовано @Potatoswatter) Чтобы продемонстрировать, как кодирование типа файла может быть этой проблемой, я скопировал / вставил ваш пример скрипта в Блокнот в Windows (у меня нет доступа к Mac), затем скопировал его на машину Linux и запустил:
В этом случае Блокнот сохранил файл с возвратом каретки и переводом строки, что вызвало ошибку, показанную выше. указывает на возврат каретки (в системах Linux строки заканчиваются только переводом строки ).
На Linux-машине вы можете проверить эту теорию, выполнив следующую команду, чтобы удалить символы возврата каретки из файла, если они есть:
Затем попробуйте запустить новый файл . Если это сработает, проблема заключалась в возврате каретки в виде скрытых символов.
Примечание. Это не точная копия вашей среды (у меня нет доступа к Mac), однако мне кажется, что проблема в том, что где-то редактор сохранил символы возврата каретки в файл.
— /РЕДАКТИРОВАТЬ
Вкратце, операционные системы и редакторы могут иметь разные значения по умолчанию для кодировки файлов. Как правило, приложения и редакторы будут влиять на используемую кодировку типа файла, например, я думаю, что Microsoft Notepad и Notepad ++ по умолчанию используют Windows-1252. Также могут быть различия в новой строке, которые следует учитывать (в средах Windows возврат каретки и перевод строки часто используются для завершения строк в файлах, тогда как в Linux и OSX обычно используется только перевод строки).
Аналогичный вопрос и ответ, который ссылается на кодировку файла, находится здесь: отображается неверный символ при выполнении скрипта bash
32
Community
23 Май 2017 в 12:10
У меня была такая же проблема, когда я работал с armbian linux и Windows. Я пытался скопировать свои коды из окон в armbian, и когда я запустил его, появляется эта ошибка. Моя проблема Решена следующим образом: 1 — попробуйте скопировать файлы из окон с помощью WinSCP. 2- убедитесь, что имя вашего файла не содержит символов ()
Digital_affection
24 Июл 2019 в 07:33
Я хочу добавить к ответ выше: как проверить, является ли это проблемой возврата каретки в Unix-подобной среде (я тестировал в MacOS)
1) Использование cat
Если вы видите строки, оканчивающиеся на , то да, это проблема возврата каретки.
2) Найдите первую строку с символом возврата каретки
3) Использование vim
Затем в vim введите
Если вы видите , значит, файл взят из среды dos, которая содержит возврат каретки.
Узнав, вы можете использовать вышеупомянутые методы другими людьми для исправления вашего файла.
traceformula
4 Июн 2018 в 19:23
Спасибо @jdt за ваш ответ.
После этого, и поскольку у меня по-прежнему возникают проблемы с возвратом каретки, я написал этот небольшой скрипт. Запустите только , и вам будет предложено «очистить» файл.
}
2
kartonnade
14 Май 2014 в 08:27
Простой способ преобразовать файл в , если вы работаете в Windows, — это использовать NotePad ++ (Правка> Преобразование EOL> Формат UNIX / OSX)
Вы также можете установить EOL по умолчанию в блокноте ++ (Настройки> Настройки> Новый документ / Каталог по умолчанию> выберите Unix / OSX в поле Формат)
4
JAR
4 Авг 2016 в 19:11
Попробуйте что-нибудь вроде
4
Amos Folarin
17 Сен 2014 в 14:05
ОПИСАНИЕ
На стадии компиляции это осуществляется при помощи включения
<unistd.h> и/или <limits.h> и проверки значений
определённых макросов.
Во время выполнения можно запрашивать числовые значение посредством функции
sysconf(). Можно запросить числовые значения, которые могут зависеть от
файла в файловой системе, с помощью вызовов fpathconf(3) и
pathconf(3). Строковые значения можно запрашивать с помощью
confstr(3).
Значения, полученные с помощью этих функций, являются системными
настроечными константами. Они не изменятся пока выполняется процесс.
Для параметров, как правило, используются константы вида _POSIX_FOO,
которые могут быть определены в <unistd.h>. Если параметр не
определён, то его можно запросить во время выполнения. Если он определён со
значением -1, то этот параметр не поддерживается. Если его значение равно 0,
то соответствующие функции и заголовочные файлы существуют, но нужно во
время выполнения запрашивать степень поддержки. Если он определён со
значениями не -1 и 0, то параметр поддерживается. Обычно значение (например
200112L) отражает год и месяц версии POSIX, в которой описан параметр. В
glibc используется значение 1, означающее, что поддержка в версии POSIX пока
не опубликована. В этом случае аргумент sysconf() будет выглядеть как
_SC_FOO. Список параметров смотрите в posixoptions(7).
Как O_NONBLOCK сочетается с мультиплексером select
Допустим, мы пишем простую программу-daemon, обслуживающее клиентские приложения через сокеты. Мы воспользуемся мультиплексером и блокирующими файловыми дескрипторами. Для простоты предположим, что мы уже открыли файлы и добавили их в переменную , имеющую тип (то есть “набор файлов”). Ключевым элементом цикла событий, обрабатывающего файл, будет вызов и дальнейшие вызовы для каждого из дескрипторов в наборе.
Тип данных представляет просто массив файловых дескрипторов, где с каждым дескриптором связан ещё и флаг (0 или 1). Примерно так могло бы выглядеть объявление:
Функция принимает несколько объектов . В простейшем случае мы передаём один с набором файлов для чтения, а модифицирует их, проставляя флаг для тех дескрипторов, из которых можно читать данные. Также функция возвращает число готовых для обработки файлов. Далее с помощью макроса можно проверить, установлен ли флаг, т.е. можно ли читать данные без блокировки.
Такой подход работает, но давайте предположим, что размер буфера очень маленький, а объём пакетов данных, читаемых из дескрипторов файлов, очень велик. Например, в примере выше размер всего 1024 байта, допустим что через сокеты приходят пакеты по 64КБ. Для обработки одного пакета потребуется 64 раза вызвать , а затем 64 раза вызвать . В итоге мы получим 128 системных вызовов, но каждый вызов приводит к одному переключению контекста между kernel и userspace, в итоге обработка пакета обходится дорого.
Можем ли мы уменьшить число вызовов ? В идеале, для обработки одного пакета мы хотели бы вызвать только один раз. Чтобы сделать это, потребуется перевести все файловые дескрипторы в неблокирующий режим. Ключевая идея — вызывать в цикле до тех пор, пока вы не получите код ошибки , обозначающий отсутствие новых данных в момент вызова. Идея реализована в примере:
В этом примере при наличии буфера в 1024 байта и входящего пакета в 64КБ мы получим 66 системных вызовов: будет вызван один раз, а будет вызываться 64 раза без каких-либо ошибок, а 65-й раз вернёт ошибку .
Основные функции
В стандарте определено более 60 вызовов функций. Они могут быть разделены на 4 категории:
- Потоковое управление — creating, joining thread
- Мьютексы
- Условные переменные
- Синхронизация между потоками используя read/write блокировки и барьеры.
- Спинлок
Опишем ряд самых основных функций, чтобы дать представление о том, как они работают.
Вызовы, связанные с потоком | Описание |
---|---|
Создание нового потока | |
Завершение работы вызвавшего потока | |
Ожидание выхода из указанного потока | |
Освобождение центрального процессора, позволяющее
выполняться другому потоку |
|
Создание и инициализация структуры атрибутов потока | |
Удаление структуры атрибутов потока |
Все потоки Pthreads имеют определенные свойства. У каждого потока есть свои идентификатор, набор регистров (включая счетчик команд) и набор атрибутов, которые сохраняются в определенной структуре. Атрибуты включают размер стека, параметры планирования и другие элементы, необходимые при использовании потока. Новый поток создается с помощью вызова функции . В качестве значения функции возвращается идентификатор только что созданного потока. Этот вызов намеренно сделан очень похожим на системный вызов (за исключением параметров), а идентификатор потока играет роль PID, главным образом для идентификации ссылок на потоки в других вызовах.
Когда поток заканчивает возложенную на него работу, он может быть завершен путем вызова функции . Этот вызов останавливает поток и освобождает пространство, занятое его стеком. Зачастую потоку необходимо перед продолжением выполнения ожидать окончания работы и выхода из другого потока. Ожидающий поток вызывает функцию , чтобы ждать завершения другого указанного потока. В качестве параметра этой функции передается идентификатор потока, чьего завершения следует ожидать.
Иногда бывает так, что поток не является логически заблокированным, но считает, что проработал достаточно долго, и намеревается дать шанс на выполнение другому потоку. Этой цели он может добиться за счет вызова функции . Для процессов подобных вызовов функций не существует, поскольку предполагается, что процессы сильно конкурируют друг с другом и каждый из них требует как можно больше времени центрального процессора. Но поскольку потоки одного процесса, как правило, пишутся одним и тем же программистом, то он добивается от них, чтобы они давали друг другу шанс на выполнение.
Два следующих вызова функций, связанных с потоками, относятся к атрибутам. Функция создает структуру атрибутов, связанную с потоком, и инициализирует ее значениями по умолчанию. Эти значения (например, приоритет) могут быть изменены за счет работы с полями в структуре атрибутов.
И наконец, функция удаляет структуру атрибутов, принадлежащую потоку, освобождая память, которую она занимала. На поток, который использовал данную структуру, это не влияет, и он продолжает свое существование.
Чтобы лучше понять, как работают функции пакета Pthread, рассмотрим простой пример, показанный ниже. Основная программа этого примера работает в цикле столько раз, cколько указано в константе (количество потоков), создавая при каждой итерации новый поток и предварительно сообщив о своих намерениях. Если создать поток не удастся, она выводит сообщение об ошибке и выполняет выход. После создания всех потоков осуществляется выход из основной программы. При создании поток выводит однострочное сообщение, объявляя о своем существовании, после чего осуществляет выход. Порядок, в котором выводятся различные сообщения, не определен и при нескольких запусках программы может быть разным.
1 #include <pthread.h> 2 #include <stdio.h> 3 #include <stdlib.h> 4 #define NUMBER_OF_THREADS 10 5 void *print_hello_world(void *tid) 6 { 7 /* Эта функция выводит идентификатор потока, а затем осуществляет выход */ 8 printf("Привет, мир. Тебя приветствует поток № %d\n", tid); 9 pthread_exit(NULL); 10 } 11 12 int main(int argc, char *argv[]) 13 { 14 /* Основная программа создает 10 потоков, а затем осуществляет выход. */ 15 pthread_t threadsNUMBER_OF_THREADS]; 16 int status, i; 17 for (i = ; i < NUMBER_OF_THREADS; i++) { 18 printf("Это основная программа. Создание потока № %d\n"", i); 19 status = pthread_create(&threadsi], NULL, print_hello_world, 20 (void *)i); 21 if (status != ) { 22 printf("Жаль, функция pthread_create вернула код ошибки %d\n"", 23 status); 24 exit(-1); 25 } 26 } 27 exit(NULL); 28 }
Семафор Posix
Семафор Posix |
|
Знаменитый семафор |
Анонимный семафор |
sem_open |
sem_init |
sem_close |
sem_destroy |
sem_unlink |
|
sem_wait |
|
sem_post |
Знаменитый семафор
Это похоже на использование IPC типа Posix: имя идентифицируется в форме / somename, и может быть только один /, а общая длина не может превышать NAME_MAX-4 (то есть, 251).
Знаменитые семафоры Posix необходимо создавать или открывать с помощью функции sem_open. Операциями PV являются sem_wait и sem_post, которые можно закрыть с помощью sem_close и удалить с помощью sem_unlink.
Именованные семафоры используются для межпроцессной синхронизации, которая не требует общей памяти (доступной по имени), аналогично семафорам System V.
Анонимный семафор
Анонимные семафоры существуют только в памяти и требуют, чтобы процессы, использующие семафоры, имели доступ к памяти, это означает, что их можно применять только к потокам в одном и том же процессе или в разных процессах.
Потоки, которые отображают одинаковое содержимое памяти в свое адресное пространство.
Анонимные семафоры должны быть инициализированы с помощью sem_init. Второй параметр pshared функции sem_init определяет разделение потоков (pshared = 0) или разделение процессов (pshared! = 0), а также
Вы можете использовать sem_post и sem_wait для работы. Перед освобождением разделяемой памяти анонимный семафор должен быть уничтожен с помощью sem_destroy.
Работа с семафором Posix PV
Операция ожидания уменьшает семафор на 1, если счетчик семафоров изначально равен 0, произойдет блокировка;
Операция post добавляет 1 к семафору. Когда вызывается sem_post, если во время вызова sem_wait происходит блокировка процесса, то процесс будет разбужен, а счетчик семафоров sem_post, увеличенный на 1, будет снова уменьшен на sem_wait