Я пишу гостевую ОС поверх Linux (дистрибутив Ubuntu) в контейнере Docker. Файловая система реализована в виде одного файла, находящегося внутри хост-ОС, поэтому каждый раз, когда файл изменяется в файловой системе гостевой ОС, файл в хост-ОС должен быть открыт, правильные блоки должны быть перезаписаны, а файл должен быть быть закрытым.
Мой партнер и я разработали следующую рекурсивную вспомогательную функцию, которая принимает номер блока и смещает его, чтобы абстрагироваться от всех деталей на уровне блока для функций более высокого уровня:
/**
* Recursive procedure to write n bytes from buf to the
* block specified by block_num. Also updates FAT to
* reflect changes.
*
* @param block_num identifier for block to begin writing
* @param buf buffer to write from
* @param n number of bytes to write
* @param offset number of bytes to start writing from as
* measured from start of file
*
* @returns number of bytes written
*/
int write_bytes(int block_num, const char *buf, int n, int offset) {
BlockTuple red_tup = reduce_block_offset(block_num, offset);
block_num = red_tup.block;
offset = red_tup.offset;
FILE *fp = fopen(fat->fname, "r+");
int bytes_to_write = min(n, fat->block_size - offset);
int write_n = max(bytes_to_write, 0);
fseek(fp, get_block_start(block_num) + offset, SEEK_SET);
fwrite(buf, 1, write_n, fp); // This line is returning 48 bytes written
fclose(fp);
// Check if there are bits remaining
int bytes_left = n - write_n;
if (bytes_left > 0) {
// Recursively write on next block
int next_block = get_free_block();
set_fat_entry(block_num, next_block); // point block to next block
set_fat_entry(next_block, 0xFFFF);
return write_bytes(next_block, buf + write_n, bytes_left, max(0, offset - fat->block_size)) + write_n;
} else {
set_fat_entry(block_num, 0xFFFF); // mark file as terminated
return write_n;
}
}
Проблема в том, что fwrite(3) сообщает о записанных 48 байтах (когда n передается как 48), но шестнадцатеричный дамп файла в хост-ОС показывает, что байты не были изменены:
00000000 00 01 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008000
Это особенно странно, потому что, когда мой партнер запускает код в той же самой фиксации (без незафиксированных изменений), ее запись выполняется, и файл в основной ОС преобразуется в шестнадцатеричный код:
00000000 00 01 ff ff ff ff 00 00 00 00 00 00 00 00 00 00 |................|
00000010 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000100 66 31 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |f1..............|
00000110 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
00000120 00 01 00 00 02 00 01 06 e7 36 75 63 00 00 00 00 |.........6uc....|
00000130 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00000200 e0 53 f8 88 c0 0d 37 ca 84 1f 19 b0 6c a8 68 7b |.S....7.....l.h{|
00000210 57 83 cf 13 f0 42 21 d3 21 e1 da de d4 8a f1 e6 |W....B!.!.......|
00000220 f0 12 98 fb 1c 30 4c 04 b3 16 1d 96 17 ba d7 5a |.....0L........Z|
00000230 7e f3 8a f5 6a 42 6b ef 58 f6 bc 01 db 0c 02 53 |~...jBk.X......S|
00000240 e5 10 7e f3 4a d5 3f ac 8e 38 82 c3 95 f8 11 8e |..~.J.?..8......|
00000250 a6 82 eb 3b 24 56 9a 75 44 36 8b 25 60 83 4c 04 |...;$V.uD6.%`.L.|
00000260 07 9e 14 99 9c 9f 87 3c 8a d4 c3 e8 17 60 81 0e |.......<.....`..|
00000270 bc eb 1d 35 68 fc d5 be 4f 1c 9d 5e 72 57 65 01 |...5h...O..^rWe.|
00000280 b7 43 54 26 d6 6d ba 51 bf 12 8c a1 03 d5 66 b3 |.CT&.m.Q......f.|
00000290 90 0d 60 b8 95 8d 15 bd 53 9a 70 77 4f 7a 04 1e |..`.....S.pwOz..|
000002a0 9e b2 4c 9a 79 dd de 48 cd fe 1e dc 57 7d d1 7f |..L.y..H....W}..|
000002b0 3f f5 77 96 fa e7 d7 33 33 48 ce 0a 4d 61 ab 96 |?.w....33H..Ma..|
000002c0 5f c4 88 bf c6 3a 09 37 76 c4 b8 db bc 6a 7d c0 |_....:.7v....j}.|
000002d0 c4 89 68 e7 b4 70 f8 a6 a8 00 9d c4 63 da fb 66 |..h..p......c..f|
000002e0 be d2 cd 68 1c d2 ff bf 00 e9 37 ab 6b 1a 3c f2 |...h......7.k.<.|
000002f0 7b c1 a2 c4 46 ae db 93 b4 4f 64 79 14 2a 1a d4 |{...F....Ody.*..|
00000300 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 |................|
*
00008000
48 байтов, о которых я говорю, которые не записываются, - это байты, записанные в блок каталогов, работающий с адреса 00000100-0000012E (байты ниже, которые представляют фактически записываемый файл, ошибки сегментации кода на моем конце, прежде чем достичь этого написать). Стоит отметить, что мой контейнер по-прежнему может форматировать файл файловой системы, поэтому все операции записи не прерываются. Этот фрагмент представляет собой первую запись, которая не сработала.
Мы оба запускаем код в идентичном контейнере Docker. Единственная разница, которую я мог себе представить, это то, что мой компьютер — Windows, а ее — Mac. В чем может быть проблема?
Первое, что я подумал, это то, что был какой-то конфликт с ОС хоста, который заблокировал мою запись, но назначение и печать возвращаемого значения fwrite(3)
вернули, что 48 байт действительно были записаны на обеих машинах.
Я также ожидал, что мой буфер состоит из нулей (изначально он выделяется с помощью calloc(3)), но распечатка первых 48 байт буфера доказала, что эта теория неверна.
В конце концов я решил, что это какая-то проблема с интерфейсом более высокого уровня в <stdio.h>, а не с интерфейсом более низкого уровня в <unistd.h>. Я заменил fopen(3), fwrite(3), flseek(3), fclose(3)
каждый их эквивалентами более низкого уровня (write(2)
и т. д.), и все равно получилось 48 байтов, записанных без каких-либо фактических изменений в файлах.
Обновлено: Файловая система гостевой ОС может быть отформатирована с учетом пользовательских параметров. Все вышеперечисленные тесты проводились с размером блока 256 байт и общим количеством блоков 128. Я снова попытался выполнить ту же самую последовательность записи с размером блока 1024 байта и общим количеством блоков 16384, и ошибки не было. До сих пор неясно, почему код работает на машине моего партнера для обеих конфигураций формата, а не для моей, но это может сузить его.
Бег strace
показывает следующий отрывок вокруг записи:
openat(AT_FDCWD, "minfs", O_RDWR) = 4
newfstatat(4, "", {st_mode=S_IFREG|0777, st_size=32768, ...}, AT_EMPTY_PATH) = 0
lseek(4, 0, SEEK_SET) = 0
read(4, "\0\1\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 256) = 256
write(4, "f1\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 48) = 48
close(4)
Снова появляется, что байты записываются, но hd
после завершения программы показывает тот же вывод, что и выше. Я подумал, что, возможно, байты, записанные в отрывке, позже будут перезаписаны, но единственная запись после отрывка выше в strace
:
lseek(4, 0, SEEK_SET) = 0
read(4, "\0\1\377\377\377\377\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0\0"..., 320) = 320
write(4, "\0", 1) = 1
close(4)
который должен быть по адресу 320, сразу после записи по адресу 256 выше.
«Самое первое, во что я поверил, это какой-то конфликт с хостовой ОС, которая заблокировала мою запись»: Ну, обычно самое первое, во что вы должны поверить, это «в моем коде есть ошибка, которую я еще не нашел». Как говорится, «select
не ломается», и fwrite
тоже.
Чтобы доказать свои подозрения, напишите небольшие экспериментальные программы и запустите их вместо своей ОС. В какой-то момент дополнительный «уровень» WSL может ввести вас в заблуждение. -- Как говорит Нейт, такие проблемы наверняка есть в вашем коде. Это может быть какое-то неопределенное поведение, проявляющееся в различных средах хост-ОС.
@NateEldredge Хорошие моменты, и да, оба были проверены (распечатка get_block_start(block_num) + offset
показывает правильное значение, и да, имена файлов были проверены). Я признаю, что формулировка была плохой, но под конфликтом я подразумеваю неправильное использование, то есть мы создали набор условий, при которых fwrite
не ожидается, что он будет работать должным образом, и поэтому он вернет 0 записанных байтов. strace
отличный звонок, отрывок, где нужно выполнить запись, добавлен внизу поста
@thebusybee Неопределенное поведение кажется беспроигрышным вариантом. Отметив, что отладка может только выявить наличие неопределенного поведения, но не доказать его отсутствие, до сих пор ни один инструмент не выявил, откуда берется неопределенное поведение. Запуск программы через valgrind на машине моего партнера вообще не показывает ошибок (нет недопустимых операций чтения/записи и т. д.), но я разработаю некоторые экспериментальные программы, как вы предлагаете.
Я бы сказал, что журнал strace
является доказательством того, что запись выполняется в какой-то файл, и поэтому сам fwrite
не виноват. Спорим на пять баксов, что окажется, что вы открыли не тот файл, который планировали. Вы действительно проверяли текущий рабочий каталог? (Ищите вызовы chdir
в strace или фактически выполняйте getcwd
в своем коде.) Кроме того, вы говорите, что находитесь в контейнере Docker — вы уверены, что ищете файл внутри правильного контейнера?
Например, если вы передадите достаточно флагов -v
в strace
, вы увидите полные результаты системного вызова newfstatat
(который действительно делает fstat
). Сравните номер устройства и инода с нужным файлом (ls -li
).
Оказывается, несоответствие произошло из-за неопределенного поведения, связанного с синхронизацией изменений в mmap(2)
. Был раздел кода, в котором область памяти, отображенная через mmap(2)
, была изменена, а затем сразу же последовало чтение/запись в файл на хост-ОС, содержащий отображенную область памяти. Похоже, что Mac будет записывать изменения до следующего раздела, в то время как Windows не будет синхронизироваться до тех пор, пока это не произойдет, что приведет к неопределенному поведению.
Проблема была устранена путем вызова msync(2)
сразу после изменения отображаемой области из mmap(2)
с флагом MS_SYNC
, вызывающим сквозную запись.
Ссылки на документацию здесь: mmap(2) , msync(2).
Глупый вопрос, но проверили ли вы, что файл, который вы открыли, — это тот, который вы намеревались открыть, и что файл, который вы проверяете, — тот же самый? (И что вы находитесь в правильном рабочем каталоге и все такое.) И вы подтвердили, что ваш счетчик поиска соответствует вашему мнению?
strace
может быть полезным инструментом.