Понимание ввода-вывода в Java из ядра Linux

Java

предисловие

Новое в JavaIO, всегда была путаница: почемуBufferedInputStreamСравниватьFileInputStreamБыстро? С правомLinuxПоймите, этот вопрос также был решен. Я смотрел его недавноLinux 内核Аспекты книги, хотите понять программу вLinuxВ процессе бега я чувствую, что впереди еще много выигрышей.

Только из соображений безопасностиLinux内核иметь разрешение на доступ к оборудованию компьютера,Linux内核Некоторые интерфейсы (системные вызовы) будут предоставлены, чтобы мы могли взаимодействовать с оборудованием. Однако данные, как правило, взяты из硬件прибыть内核态, то изLinux内核скопировать в用户态В пространстве памяти процесса, чтобы процесс мог обрабатывать считанные данные.

image-20200704231239764

Содержание этой статьи:

  • Введение в виртуальные файловые системы в Linux
  • Кэш страницы и грязная страница
  • Когда данные, записанные API Java, будут сброшены на диск

Виртуальная файловая система (VFS) в Linux

Виртуальная файловая система (сокращенно VFS) — одна из подсистем ядра Linux, обеспечивающая унифицированный интерфейс для операционных файлов (обычные файлы, сокеты и т. д.) и скрывающая различные аппаратные различия и детали работы. мы просто звонимopen,read,write,close,fsyncЭти системные вызовы служат для управления файлами.

Каталог linux, который мы на самом деле видим, на самом деле является путем в VFS.Мы можем смонтировать раздел на жестком диске вlinuxПо пути в виртуальной файловой системе вы можете получить доступ к содержимому жесткого диска, обратившись к пути в виртуальной файловой системе.

df -iВы можете увидеть разделы, смонтированные по пути в VFS.

image-20200704233624173

# 将分区挂载到虚拟文件系统的 /boot 目录下
mount /dev/sda1 /boot

# 卸载分区
umount /boot

Операционная система делит жесткий диск на две области: одна — область данных, которая используется для сохранения данных файла, а другая — область данных.InodeЗона используется для хранения метаданных файла (создатель файла, время создания файла, права доступа к файлу, размер файла, расположение блока и т. д.).

Наименьшая единица хранения жесткого диска называется «сектором», и каждый сектор хранит 512 байт (эквивалентно 0,5 КБ).Linux 内核При чтении содержимого с жесткого диска он будет считывать не посекторно, а считывать несколько секторов за раз, то есть считывать по одному块(Block). Содержимое файла хранится всередина.

Основываясь на приведенном выше введении, можно понять, что на самом деле файл должен заниматьInodeи хотя бы одинblock.

df -iВы можете просматривать разделы,inodeсоответствие использованию и разделуLinuxпуть к файлу под.

посмотреть файлInodeа такжебазовый размер (обычно 4 КБ)

image-20200705002110818

Когда приложение вызывает системный вызов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), каждая программа будет иметь.

image-20200705004254331

Почему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

image-20200705122935804

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, сбросьте данные на диск.

image-20200704201130013

Ядро Linux выполняет планирование ввода-вывода для управления размещением данных.

  1. Когда свободная память падает ниже определенного порога, ядро ​​должно записать грязные страницы обратно на диск, чтобы освободить память.
  2. Когда грязные страницы остаются в памяти дольше определенного порога, ядро ​​должно записать истекшие грязные страницы обратно на диск.
  3. Вызов пользовательского процесса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