Блокировка файлов - это механизм взаимного исключения, обеспечивающий безопасное чтение/запись файла несколькими процессами.В этом уроке мы разберем проблему интерферирующего обновления в многопроцессной системе. Затем мы познакомимся с двумя типами блокировок в Linux. Попутно мы изучим некоторые команды, связанные с блокировкой файлов, на примерах.
Проблема с перемежающимся обновлением
Опережающее обновление - это типичная проблема состояния гонки в параллельной системе. Давайте рассмотрим пример, чтобы лучше понять суть проблемы.
Допустим, у нас есть файл balance.dat, хранящий баланс счета, и он имеет начальное значение "100". Наша параллельная система имеет два процесса для обновления значения баланса:
- Процесс A: считывает текущее значение, вычитает 20 и сохраняет результат в файл.
- Процесс B: считывает текущее значение, прибавляет 80 и записывает результат обратно в файл.
Очевидно, что после выполнения двух процессов мы ожидаем, что файл будет иметь значение: 100-20+80=160.
Однако в этой ситуации может возникнуть проблема интерферирующего обновления:
- Процесс A считывает текущее значение файла (100) и готовится к дальнейшим вычислениям.
- Теперь процесс B читает тот же файл и получает текущий баланс (100).
- Процесс A вычисляет 100-20 и сохраняет результат 80 обратно в файл.
- Процесс B не знает, что баланс был обновлен с момента последнего чтения. Поэтому он по-прежнему будет использовать устаревшее значение 100 для вычисления 100+80 и запишет результат 180 в файл.
В результате в файле balance.dat вместо ожидаемого значения 160 мы имеем 180.
Блокировка файлов в Linux
Блокировка файла - это механизм, позволяющий ограничить доступ к файлу между несколькими процессами. Он позволяет только одному процессу получить доступ к файлу в определенное время, что позволяет избежать проблемы чередующихся обновлений.
Все мы знаем, что rm -rf / - очень опасная команда в Linux. Если мы выполним эту команду от имени пользователя root, все файлы в запущенной системе будут удалены. Это происходит потому, что Linux обычно не блокирует открытые файлы автоматически. Однако Linux поддерживает два вида блокировок файлов: рекомендательные и обязательные.
В ближайшее время мы познакомимся с обоими типами блокировок, но в этой статье речь пойдет в основном о рекомендательных блокировках.
Консультативная блокировка
Рекомендательная блокировка не является схемой принудительной блокировки. Она будет работать только в том случае, если участвующие процессы сотрудничают, явно приобретая блокировки. В противном случае рекомендательные блокировки будут игнорироваться, если процесс вообще не знает о блокировках.
Пример может помочь нам лучше понять схему кооперативной блокировки. Давайте рассмотрим наш предыдущий пример с балансом.
- Сначала мы предположим, что файл balance.dat по-прежнему содержит начальное значение "100".
- Процесс A приобретает эксклюзивную блокировку на файл balance.dat, затем открывает и читает файл, чтобы получить текущее значение: 100.
Мы должны понимать, что рекомендательная блокировка не была установлена операционной системой или файловой системой. Поэтому, даже если процесс A заблокирует файл, процесс B все равно может свободно читать, записывать или даже удалять файл с помощью системных вызовов.
Если процесс B выполняет операции с файлом, не пытаясь получить блокировку, мы говорим, что процесс B не сотрудничает с процессом A.
Но теперь давайте посмотрим, как блокировка будет работать для сотрудничающих процессов:
- Процесс B пытается получить блокировку на файл balance.dat перед чтением файла (сотрудничает с процессом A).
- Поскольку процесс A заблокировал файл, процессу B приходится ждать, пока процесс A снимет блокировку.
- Процесс A вычисляет 100-20 и записывает 80 обратно в файл.
- Процесс A снимает блокировку.
- Теперь процесс B получает блокировку, читает файл и получает обновленное значение: 80.
- Процесс B запускает свою логику и записывает результат 160 (80+80) обратно в файл.
- Процесс B снимает блокировку, чтобы другие взаимодействующие процессы могли читать и писать в файл.
В следующем разделе мы рассмотрим, как этот пример реализуется с помощью команды flock.
Обязательная блокировка
Прежде чем приступить к рассмотрению обязательной блокировки файлов, следует помнить, что "реализация обязательной блокировки в Linux ненадежна".
В отличие от рекомендательной блокировки, обязательная блокировка не требует никакого сотрудничества между участвующими процессами. Как только обязательная блокировка активирована на файле, операционная система не позволяет другим процессам читать или записывать этот файл.
Чтобы включить обязательную блокировку файлов в Linux, необходимо выполнить два требования:
- Мы должны смонтировать файловую систему с опцией mand (mount -o mand FILESYSTEM MOUNT_POINT).
- Мы должны включить бит set-group-ID и выключить бит group-execute для файлов, которые мы собираемся заблокировать (chmod g+s,g-x FILE).
Проверка всех блокировок в системе
В этом разделе мы рассмотрим два способа проверки текущих блокировок в работающей системе.
Команда lslocks
Команда lslocks входит в пакет util-linux и доступна во всех дистрибутивах Linux. Она может вывести список всех текущих файловых блокировок в нашей системе.
1 | lslocks |
Давайте посмотрим пример вывода:
В приведенном выше списке мы видим все заблокированные в данный момент файлы в системе. Мы также можем увидеть подробную информацию о каждой блокировке, например тип блокировки и то, какой процесс ее удерживает.
/proc/locks
/proc/locks - это не команда. Вместо этого это файл в виртуальной файловой системе procfs. В этом файле хранятся все текущие блокировки файлов. Команда lslocks тоже опирается на этот файл при формировании списка.
Чтобы получить информацию о файле /proc/locks, выполним команду
1 | cat /proc/locks |
Давайте выберем первую строку, чтобы понять, как организована информация о блокировках в файловой системе /proc/locks:
1 2 | 1: FLOCK ADVISORY WRITE 185266 00:17:4858705 0 EOF -1- --2-- ---3--- --4-- ---5-- ------6----- -7- -8- |
- Первый столбец - это номер последовательности.
- Второе поле указывает на класс используемой блокировки, например FLOCK (из системного вызова flock) или POSIX (из системного вызова lockf, fcntl).
- В этом столбце указывается тип блокировки. Он может иметь два значения: ADVISORY или MANDATORY.
- Четвертое поле показывает, является ли блокировка блокировкой WRITE или READ.
- Затем указывается идентификатор процесса, удерживающего блокировку.
- Это поле содержит строку с разделенными двоеточием значениями, показывающую идентификатор заблокированного файла в формате "major-device:minor-device:inode".
- Этот столбец вместе с последним показывает начало и конец заблокированной области блокируемого файла. В этой строке примера заблокирован весь файл.
Введение в команду flock
Команда flock также входит в пакет util-linux. Эта утилита позволяет нам управлять блокировками консультативных файлов в сценариях оболочки или в командной строке.
Основной синтаксис использования следующий:
1 | flock FILE_TO_LOCK COMMAND |
Далее продемонстрируем пример обновления баланса с помощью команды flock.
Помимо текстового файла balance.dat, содержащего текущее значение баланса, нам еще нужны два процесса, A и B, чтобы обновлять баланс в файле.
Сначала мы создадим простой сценарий оболочки update_balance.sh для обработки логики обновления баланса для обоих процессов:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | !/bin/bash file="balance.dat" value=$(cat $file) echo "Read current balance:$value" #sleep 10 секунд для имитации вычислений progress=10 while [[ $progress -lt 101 ]]; do echo -n -e "\033[77DCalculating new balance..$progress%" sleep 1 progress=$((10+progress)) done echo "" value=$((value+$1)) echo "Write new balance ($value) back to $file." echo $value > "$file" echo "Done." |
Мы создадим простой сценарий оболочки a.sh для имитации процесса A:
1 2 3 4 5 6 | #!/bin/bash #----------------------------------------- # процесс A: заблокируйте файл и вычтите 20 # из текущего баланса #------------------------------------- flock --verbose balance.dat ./update_balance.sh '-20' |
Теперь запустим процесс A для проверки:
1 2 3 4 5 6 7 | ./a.sh flock: getting lock took 0.000002 seconds flock: executing ./update_balance.sh Read current balance:100 Calculating new balance..100% Write new balance (80) back to balance.dat. Done. |
Из вывода видно, что сначала команда flock получила блокировку на файл balance.dat, затем сценарий update_balance.sh прочитал и обновил файл.
Во время его выполнения мы можем проверить информацию о блокировке с помощью команды lslocks:
1 2 | lslocks | grep 'balance' flock 825712 FLOCK 4B WRITE 0 0 0 /tmp/test/balance.dat |
Вывод показывает, что команда flock удерживает блокировку WRITE на всем файле /tmp/test/balance.dat.
Демонстрация работы стаи с несотрудничающими процессами
Мы узнали, что рекомендательные блокировки работают только в том случае, если участвующие процессы сотрудничают. Давайте сбросим баланс до 100 и проверим, что произойдет, если мы получим рекомендательную блокировку на файл для процесса A, но запустим процесс B некооперативным способом.
Теперь создадим простой shell-скрипт b_non-cooperative.sh:
1 2 3 4 5 6 | #!/bin/bash #---------------------------------------- # процесс B: добавить 80 к текущему балансу в # некооперативным способом #---------------------------------------- ./update_balance.sh '80' |
Мы видим, что процесс B вызывает update_balance.sh, не пытаясь получить блокировку на файл данных баланса.
Демонстрация работы флока с кооперативными процессами
Наконец, давайте создадим еще один кооперативный процесс B, b.sh, и посмотрим, как работает рекомендательная блокировка:
1 2 3 4 5 6 | #!/bin/bash #---------------------------------------- # процесс B: добавить 80 к текущему балансу # кооперативным способом #---------------------------------------- flock --verbose balance.dat ./update_balance.sh '80' |
В демонстрационном примере мы заставили два процесса сотрудничать.
Мы заметили, что когда процесс B пытался получить блокировку на файл balance.dat, он ждал, пока процесс A снимет блокировку. Таким образом, рекомендательная блокировка сработала, и мы получили ожидаемый результат, 160, в файле данных баланса.
Заключение
В этой статье мы начали с понимания проблемы интерферирующего обновления. Затем мы обсудили различные типы схем блокировки файлов в системах Linux.
Мы также изучили команду lslocks для проверки блокировок в системе и утилиту flock для реализации рекомендательной блокировки.
Наконец, мы увидели две демонстрации. Одна из них помогла нам понять взаимосвязь между рекомендательной блокировкой и сотрудничеством процессов, а другая показала, как работает рекомендательная блокировка.