предисловие
Новое в JavaIO
, всегда была путаница: почемуBufferedInputStream
СравниватьFileInputStream
Быстро? С правомLinux
Поймите, этот вопрос также был решен. Я смотрел его недавноLinux 内核
Аспекты книги, хотите понять программу вLinux
В процессе бега я чувствую, что впереди еще много выигрышей.
Только из соображений безопасностиLinux内核
иметь разрешение на доступ к оборудованию компьютера,Linux内核
Некоторые интерфейсы (системные вызовы) будут предоставлены, чтобы мы могли взаимодействовать с оборудованием. Однако данные, как правило, взяты из硬件
прибыть内核态
, то изLinux内核
скопировать в用户态
В пространстве памяти процесса, чтобы процесс мог обрабатывать считанные данные.
Содержание этой статьи:
- Введение в виртуальные файловые системы в Linux
- Кэш страницы и грязная страница
- Когда данные, записанные API Java, будут сброшены на диск
Виртуальная файловая система (VFS) в Linux
Виртуальная файловая система (сокращенно VFS) — одна из подсистем ядра Linux, обеспечивающая унифицированный интерфейс для операционных файлов (обычные файлы, сокеты и т. д.) и скрывающая различные аппаратные различия и детали работы. мы просто звонимopen
,read
,write
,close
,fsync
Эти системные вызовы служат для управления файлами.
Каталог linux, который мы на самом деле видим, на самом деле является путем в VFS.Мы можем смонтировать раздел на жестком диске вlinux
По пути в виртуальной файловой системе вы можете получить доступ к содержимому жесткого диска, обратившись к пути в виртуальной файловой системе.
df -i
Вы можете увидеть разделы, смонтированные по пути в VFS.
# 将分区挂载到虚拟文件系统的 /boot 目录下
mount /dev/sda1 /boot
# 卸载分区
umount /boot
Операционная система делит жесткий диск на две области: одна — область данных, которая используется для сохранения данных файла, а другая — область данных.Inode
Зона используется для хранения метаданных файла (создатель файла, время создания файла, права доступа к файлу, размер файла, расположение блока и т. д.).
Наименьшая единица хранения жесткого диска называется «сектором», и каждый сектор хранит 512 байт (эквивалентно 0,5 КБ).Linux 内核
При чтении содержимого с жесткого диска он будет считывать не посекторно, а считывать несколько секторов за раз, то есть считывать по одному块(Block)
. Содержимое файла хранится в块
середина.
Основываясь на приведенном выше введении, можно понять, что на самом деле файл должен заниматьInode
и хотя бы одинblock
.
df -i
Вы можете просматривать разделы,inode
соответствие использованию и разделуLinux
путь к файлу под.
посмотреть файлInode
а также块
базовый размер (обычно 4 КБ)
Когда приложение вызывает системный вызовopen
, вернет файловый дескриптор (сокращенно FD, File Decsriptor). мы можем поставитьFD
Под ним понимается указатель на файл, этот указатель будет указывать наInode
. несколькоFD
можно указать на то жеInode
, FD будет поддерживать смещение для работы содержимого файла (где читать и писать).FD
используется приложениями верхнего уровня,Inode
Он используется для обслуживания ядра.
но процесс открытFD
Ограничение есть, поэтому нам нужно закрыть поток (фактически освободить запрошенные ресурсы компьютера), иначеFD
Если он не отпущен, программа инициирует системный вызов безFD
При наличии будет сообщено об ошибке.
ulimit -n
Открыты процессы, которые могут просматривать ограничения системыFD
Количество , когда параллелизм программы высокий, вам нужно увеличить это значение, иначе он сообщит(Too many open files)
public class ErrorOpenFile {
public static void main(String[] args) throws IOException, InterruptedException {
final Path path = Paths.get("/root/testfileio/out.txt");
int count = 0;
while (true) {
// 为了查看 FD 的增长,所以设置阻塞五秒
Thread.sleep(5000);
count++;
Files.newBufferedReader(path);
System.out.println("打开一个文件描述符");
}
}
}
/proc/pid/fd
Ниже вы можете видеть, что процесс открытFD
,один из них0、1、2
Является вводом по умолчанию (System.in), выводом (System.out), выводом ошибок (System.err), каждая программа будет иметь.
ПочемуBufferedInputStream
СравниватьFileInputStream
быстро?
Следующая программа,FileOutputStream
а такжеBufferedOutputStream
Цикл 10000 раз, запись данных одинакового размера,FileOutputStream
Это заняло 468 мс.BufferedOutputStream
Занимает 3 мс.
public class IoOperation {
static byte[] data = "1234567890\n".getBytes();
static String path = "/root/testfileio/out.txt";
static int count = 0;
public static void main(String[] args) throws Exception {
switch (args[0]) {
case "0":
testBasicFileIO();
break;
case "1":
testBufferedFileIO();
break;
default:
}
}
// 468 毫秒执行完
public static void testBasicFileIO() throws Exception {
File file = new File(path);
FileOutputStream out = new FileOutputStream(file);
final long start = System.currentTimeMillis();
while (count < 10000) {
out.write(data);
count++;
}
System.out.println(System.currentTimeMillis() - start);
out.close();
}
// 3 毫秒执行完
public static void testBufferedFileIO() throws Exception {
File file = new File(path);
BufferedOutputStream out = new BufferedOutputStream(new FileOutputStream(file));
final long start = System.currentTimeMillis();
while (count < 10000) {
out.write(data);
count++;
}
System.out.println(System.currentTimeMillis() - start);
out.close();
}
}
VFS
Абстрактные системные вызовы (открытие, чтение, запись, закрытие) вызываются приложением. Мы можем использовать в Linuxman open(read/write/close)
Просмотр значения системных вызовов
также доступен вРуководство по Linux https://man7.org/linux/man-pages/dir_section_2.html
См. системные вызовы.
ssize_t write(int fd, const void *buf, size_t count);
write 系统调用
, который записывает первые байты count из буфера buf в fd и возвращает количество байтов, фактически записанных в файл, ssize_t, которое может быть меньше, чем count.
write 系统调用
Процесс переключится из пользовательского режима в режим ядра. ЦП должен сохранить контекст пользовательского режима процесса (где выполняется код, связанные данные и т. д.), а затем выполнить код ядра. Контекст восстанавливается. Условно говоря, переключение состояния процесса потребляет больше ресурсов процессора, мы должны уменьшить переключение ресурсов процессора.
# 执行上面代码,并追踪系统调用
strace -ff -o /root/testfileio/out java com.fly.io.IoOperation $1
FileInputStream
Он вызовет 10 000 системных вызовов, и процесс переключается из пользовательского режима в режим ядра 10 000 раз, поэтому время выполнения кода относительно велико.
BufferedOutputStream
существует один8192
буфер байтов при вызовеBufferedOutputStream.write
Этот буфер будет записан первым, и когда буфер будет заполнен, будет инициирован системный вызов для данных в этом буфере, что уменьшает количество системных вызовов, поэтому это занимает меньше времени.
Кэш страницы и грязная страница
Постоянство файловых данных, также известное как落盘
.内存
Скорость硬盘
N раз, они не одного порядка. такLinux
представлятьPage Cache
служить кэшем данных, когдаPage Cache
После модификации сталоDirty Page
, Linux обновит данные грязной страницы на жестком диске в соответствующее время (которое можно настроить с помощью параметров). Также можно вызвать системный вызов (fsync) для сброса грязных страниц на диск.
когдаJAVA 程序
перечислитьFileOutputStream.write
Когда данные пользовательского режима фактически записываются в режим ядраPage Cache
(Размер кэша страницы составляет около 4 КБ), когда мы вызываемFileOutputStream.close
Когда системный вызов действительно вызываетсяclose
, без падения диска, когда компьютер выключен в это время, данные не сохраняются.
когда мы звонимFileOutputStream.getFD().sync()
вызовет системный вызовfsync
, сбросьте данные на диск.
Ядро Linux выполняет планирование ввода-вывода для управления размещением данных.
- Когда свободная память падает ниже определенного порога, ядро должно записать грязные страницы обратно на диск, чтобы освободить память.
- Когда грязные страницы остаются в памяти дольше определенного порога, ядро должно записать истекшие грязные страницы обратно на диск.
- Вызов пользовательского процесса
sync(2)
,fsync(2)
,fdatasync(2)
Когда происходит системный вызов, ядро выполняет соответствующую операцию обратной записи.
Ниже приведена конфигурация параметров ядра для управления планированием ядра.
sysctl -a | grep dirty
Вы можете просмотреть конфигурацию, которая действует в текущей системе.
#若脏页占总物理内存10%以上,则触发flush把脏数据写回磁盘。内核后台线程写。
vm.dirty_background_ratio = 10
vm.dirty_background_bytes = 1048576
# 向内存写 pagecage 时,内核判断当前脏页占用物理内存的百分比,当超过这个值, 内核会阻塞掉写操作,并开始刷新脏页
vm.dirty_ratio = 10
vm.dirty_bytes = 1048576
# flush每隔5秒执行一次
vm.dirty_writeback_centisecs = 5000
#内存中驻留30秒以上的脏数据将由flush在下一次执行时写入磁盘
vm.dirty_expire_centisecs = 30000
проверка кодаFileOutputStream.close
Данные не будут удалены. Чтобы избежать влияния планирования Linux Io, я изменил параметры конфигурации ядра, чтобы данные до тех пор, пока системный вызов не вызывалсяfsync
Системные вызовы не будут инициированы.
# 编辑配置文件,将参数配置填入文件中
vim /etc/sysctl.conf
# 使配置生效
sysctl -p
vm.dirty_background_ratio = 90
vm.dirty_ratio = 90
vm.dirty_expire_centisecs = 300000
vm.dirty_writeback_centisecs = 50000
Логика кода такова: записываем данные в файл, затем закрываем поток, но блокирующая программа останавливается, программа останавливается, данные сбрасываются на диск, а затем виртуальная машина выключается, имитируя сбой питания.
при печати没有落盘的时候
,cat /root/testfileio/out.txt
Вы можете видеть данные.Когда я выключаю и перезапускаю, данные исчезают. иллюстрироватьclose
Размещение данных не может быть активировано.
public class IoOperation1 {
static byte[] data = "1234567890\n".getBytes();
static String path = "/root/testfileio/out.txt";
static int count = 0;
public static void main(String[] args) throws Exception {
File file = new File(path);
final FileOutputStream out = new FileOutputStream(file);
while (count < 10) {
out.write(data);
count++;
}
out.close();
System.out.println("没有落盘");
Thread.sleep(1000000);
}
}
Когда мы вызываем системный вызов, чтобы удалить диск, выключить и перезапустить виртуальную машину, мы обнаружим, чтоout.txt
Есть данные.
public class IoOperation1 {
static byte[] data = "1234567890\n".getBytes();
static String path = "/root/testfileio/out.txt";
static int count = 0;
public static void main(String[] args) throws Exception {
File file = new File(path);
final FileOutputStream out = new FileOutputStream(file);
while (count < 10) {
out.write(data);
count++;
}
// 发起了系统调用 fsync,进行数据的落盘
out.getFD().sync();
out.close();
System.out.println("落盘");
Thread.sleep(1000000);
}
}
Эта статья написанаБлог Чжан Паньциня www.mflyyou.cn/творчество. Ее можно свободно воспроизводить и цитировать, но с обязательной подписью автора и указанием источника статьи.
При перепечатке в публичную учетную запись WeChat добавьте QR-код публичной учетной записи автора в конец статьи. Имя общедоступной учетной записи WeChat: Mflyyou