Vladislav Shpilevoy PRO
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.
Системное программирование
Лекция 5:
Файловая система. Виртуальная ФС в ядре. Файлы, их типы. I/O операции и их планировщики в ядре. Page cache. Режимы работы с файлом.
Version: 2
The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.
Дедлайны:
Обратная связь:
Ссылка на анонимное голосование: https://PollEv.com/vladislavshp093
В нем можно отмечать все ли понятно, или совсем нет, или не до конца.
struct task_struct {
/* ... */
struct files_struct *files;
/* ... */
};
struct files_struct {
struct fdtable *fdt;
};
struct fdtable {
unsigned int max_fds;
struct file **fd;
};
struct file {
struct path f_path;
struct inode *f_inode;
atomic_long_t f_count;
unsigned int f_flags;
fmode_t f_mode;
loff_t f_pos;
const struct cred *f_cred;
};
Процесс хранит открытые файлы
В таблице дескрипторов
Таблица - обычный массив
Дескриптор в ядре - это структура. В user space - число. Индекс в массиве дескрипторов.
task 1
task 2
struct file
stdout
fd1
fd1
stdout
pos = 0
count = 1
struct file
pos = 0
count = 1
struct inode
struct file: stdout
pos = 0
count = 1
struct file: stdout
pos = 0
count = 1
struct inode
struct inode
Процессы
Аппаратура
Время
Файловая система
IPC
Сеть
Пользователи
Структуры данных
Виртуализация
HDD
SSD
DRAM
DRAM
DRAM
Volatile
Non-volatile
HDD
DRAM
Sort
Sort
Sort
DRAM 4 GB, 1700р
1 Gb - 425р
HDD 2000 GB, 3900р
1 GB - 1.95р
x217
SSD 250 GB, 4100р
1 GB - 16р
x26
Что это?
1. " / "
2. FAT, ext, NSF, USF, NTFS ...
3. Partition
Задачи
/home/v.shpilevoy/Work/Repositories/tarantool
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_fs) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
};
struct super_operations ext2_sops = {
.alloc_inode = ext2_alloc_inode,
.destroy_inode = ext2_destroy_inode,
.write_inode = ext2_write_inode,
.evict_inode = ext2_evict_inode,
.put_super = ext2_put_super,
.sync_fs = ext2_sync_fs,
.freeze_fs = ext2_freeze,
.unfreeze_fs = ext2_unfreeze,
.remount_fs = ext2_remount,
};
MOUNT(8) BSD System Manager's Manual MOUNT(8)
NAME
mount -- mount file systems
SYNOPSIS
mount [-adfruvw] [-t lfs | external_type]
mount [-dfruvw] special | mount_point
mount [-dfruvw] [-o options] [-t lfs | external_type] special mount_point
Реализация ФС - программа, не устройство.
ext, ext2, ext3, ext4, NTFS, FAT,
NFS, tmpfs, ramfs, procfs, sysfs
NAME
SSHFS - filesystem client based on ssh
SYNOPSIS
mounting
sshfs [user@]host:[dir] mountpoint [options]
unmounting
umount mountpoint
$> sudo mkdir /mnt/remote_dir
$> sudo sshfs username@xxx.xxx.xxx.xxx:/some/path/on/remote/serv \
/mnt/remote_dir
$> cd /mnt/remote_dir
$> # you are on remote server
Подготовить папку для монтирования
Подключить удаленную ФС в локальную папку
Можно пользоваться как локальной папкой, а под капотом - ssh
File System In USEr Space
$> cat /proc/filesystems
sysfs
rootfs
ramfs
proc
tmpfs
securityfs
pipefs
ext3
ext2
ext4
vfat
fuse
...
ФС - программа, не зависит от устройства
Устройство
?
Что здесь?
Что между ФС и устройством?
Драйвер
1 балл
Символьное устройство
Блочное устройство
Сетевое устройство
Последовательный доступ
Произвольный доступ
Комбинированный доступ
Контроллер
Считывающие головки
Магнитные диски
HDD - Hard Disk Drive
Стекло/пластик/металл
Кобальт/сталь
Кобальт/сталь
Оксид стали
Кобальт
0
1
1
0
xx нанометров
Запись
Чтение
CHS - Cylinder - Head - Sector
Секторы
1
2
3
Цилиндр
Секторы
Цилиндр
Головки
Этаж - Кольцо - Сегмент
LBA - Logical Block Addressing
0
N
Абстракция над любой адресацией. Устройство - непрерывный массив блоков байт.
Трансляция CHS в LBA:
Скорости вращения в rpm - Revolutions Per Minute:
>= 4 ms на полное вращение, 2 ms в среднем
Последовательные обращения - меньше пустых прокруток
vs
4 оборота
1 оборот
"Head crash"
Падение
Магнит
SSD - Solid State Drive
Ячейки Flash памяти
Контроллер
DRAM
Диэлектрик
Проводник
"Ловушка" для электронов
?
Диэлектрик
Проводник
Control Gate
Floating Gate
Source
Drain
Диэлектрик
Проводник
Control Gate
Floating Gate
Source
Drain
Как прогнать электроны?
2 балла
Диэлектрик
Проводник
Диэлектрик
Проводник
С этой точки заряд в сумме 0 - ток не потечет
Утечка заряда
LBA адресация
Чтения страницами по 512 - 8192 байт
1. В блоке X заняли страницы ABCD.
2. Записали новые страницы EFGH, обновили ABCD.
3. Чтобы снова можно было писать в ABCD, нужно очистить весь блок.
Это проблема всех append-only сущностей
SSD
LSM-tree
Задачи SSD:
Скорость:
Как понять, какая файловая система на устройстве?
В начале хранилища каждого устройства есть особый блок байтов, где указана метаинформация об ФС и ее "магическое число".
2 балла
MBR - Master Boot Record
Bootstrap code
Partition record 1
Partition record 2
Partition record 3
Partition record 4
512 байт
Носитель данных
MBR
Part. 1
Part. 2
Part. 3
Part. 4
struct part_record {
lba_t start;
lba_t end;
/** Partition type. */
int part_type;
/** Filesystem header. */
struct fs_header fs_header;
};
Part. i - 1
Part. i
Part. i + 1
Filesystem header
OS bootstrap code
Data block
1
... data blocks ...
Data block
N
Суперблок
struct fs_super_block {
int32_t block_count;
int32_t free_block_count;
int32_t block_size;
int32_t flags;
int32_t mount_time;
/* ... */
int16_t magic;
/* ... */
};
#define RAMFS_MAGIC 0x858458f6
#define TMPFS_MAGIC 0x01021994
#define EXT2_SUPER_MAGIC 0xEF53
#define EXT3_SUPER_MAGIC 0xEF53
#define EXT4_SUPER_MAGIC 0xEF53
#define MINIX_SUPER_MAGIC 0x137F
#define MSDOS_SUPER_MAGIC 0x4d44
#define NFS_SUPER_MAGIC 0x6969
FAT - File Allocation Table
Файл - однонаправленный список блоков
HDD
/**
* Linux kernel,
* fs/fat/fat.h
* 30.09.2018.
*/
struct fat_entry {
int entry;
union {
u8 *ent12_p[2];
__le16 *ent16_p;
__le32 *ent32_p;
} u;
int nr_bhs;
struct buffer_head *bhs[2];
struct inode *fat_inode;
};
/** uapi/linux/msdos_fs.h */
struct msdos_dir_entry {
__u8 name[MSDOS_NAME];/* name and extension */
__u8 attr; /* attribute bits */
__u8 lcase; /* Case for base and extension */
__u8 ctime_cs; /* Creation time, centiseconds (0-199) */
__le16 ctime; /* Creation time */
__le16 cdate; /* Creation date */
__le16 adate; /* Last access date */
__le16 starthi; /* High 16 bits of cluster in FAT32 */
__le16 time,date,start;/* time, date and first cluster */
__le32 size; /* file size (in bytes) */
};
Ядерная структура для цепочки файлов
Ядерная структура для каталога
Ext2 - 2nd Extended filesystem
Эндрю Танненбаум - автор Minix и minixfs - прародителя ext
ext inode
name;
rights;
time;
-------
b1_addr;
b2_addr;
b3_addr;
...
b12_addr;
-------
ind1_addr;
ind2_addr;
ind3_addr;
block level 1
b1_addr;
b2_addr;
b3_addr;
...
bN_addr;
block level 2
ind1_1_addr;
ind1_2_addr;
...
ind1_N_addr;
block level 1
block level 1
block level 1
...
block level 3
ind2_1_addr;
ind2_2_addr;
...
ind2_N_addr;
block level 2
block level 2
block level 2
...
Ext2 superblock
Block Group 1
Block Group
2
...
Block Group
N
Ext2 superblock
Block Bitmask
Inode Bitmask
Inode Table
... data blocks ...
Структура Ext2
Структура одной группы блоков
Битовая маска - если бит i = 0, то i-й объект (блок/inode) свободен
Подряд идущие struct ext2_inode
/**
* Linux kernel,
* fs/ext2/ext2.h
* 30.09.2018
* 53 lines.
*/
struct ext2_inode {
__le16 i_mode;
__le16 i_uid;
__le32 i_size;
__le32 i_atime;
__le16 i_links_count;
__le32 i_blocks;
__le32 i_flags;
__le32 i_block[15];
/* ... */
};
struct ext2_group_desc
{
__le32 bg_block_bitmap;
__le32 bg_inode_bitmap;
__le32 bg_inode_table;
__le16 bg_free_blocks_count;
__le16 bg_free_inodes_count;
__le16 bg_used_dirs_count;
__le16 bg_pad;
__le32 bg_reserved[3];
};
#define EXT2_MIN_BLOCK_SIZE 1024
#define EXT2_MAX_BLOCK_SIZE 4096
#define EXT2_NDIR_BLOCKS 12
#define EXT2_IND_BLOCK EXT2_NDIR_BLOCKS
#define EXT2_DIND_BLOCK (EXT2_IND_BLOCK + 1)
#define EXT2_TIND_BLOCK (EXT2_DIND_BLOCK + 1)
#define EXT2_N_BLOCKS (EXT2_TIND_BLOCK + 1)
Номера 12 блоков данных, и три номера на непрямую адресацию
Номера блоков, где лежат битовые маски, таблица inode
Размеры блоков, размеры таблиц косвенности
/**
* Linux kernel.
* include/linux/fs.h
* 30.09.2018.
* 33 lines.
*/
struct super_operations {
struct inode *(*alloc_inode)(struct super_block *sb);
void (*destroy_inode)(struct inode *);
void (*dirty_inode) (struct inode *, int flags);
int (*write_inode) (struct inode *, struct writeback_control *wbc);
int (*drop_inode) (struct inode *);
void (*evict_inode) (struct inode *);
void (*put_super) (struct super_block *);
int (*sync_fs)(struct super_block *sb, int wait);
int (*freeze_super) (struct super_block *);
int (*freeze_fs) (struct super_block *);
int (*thaw_super) (struct super_block *);
int (*unfreeze_fs) (struct super_block *);
int (*statfs) (struct dentry *, struct kstatfs *);
int (*remount_fs) (struct super_block *, int *, char *);
void (*umount_begin) (struct super_block *);
int (*show_options)(struct seq_file *, struct dentry *);
int (*show_devname)(struct seq_file *, struct dentry *);
int (*show_path)(struct seq_file *, struct dentry *);
int (*show_stats)(struct seq_file *, struct dentry *);
long (*free_cached_objects)(struct super_block *,
struct shrink_control *);
};
write(fd, buf, size);
User space
Kernel space
file = find_file(fd);
file->write(buf, size);
file->inode->write_inode(buf, size);
ext2_write_inode(buf, size);
fat_write_inode(buf, size);
bdev_write_page(page);
Hardware
#include <stdio.h>
#include <dirent.h>
int main()
{
DIR *dir = opendir(".");
struct dirent *dirent = readdir(dir);
while (dirent != NULL) {
printf("name = %s, inode number = "\
"%d, type = %d\n",
dirent->d_name,
(int) dirent->d_ino,
(int) dirent->d_type);
dirent = readdir(dir);
}
closedir(dir);
return 0;
}
$> gcc 1_dirent.c
$> ./a.out
name = ., inode number = 19537325,
type = 4
name = .., inode number = 18730940,
type = 4
name = 2_fstat.c, inode number =
19537344, type = 8
name = a.out, inode number = 19641892,
type = 8
name = 1_dirent.c, inode number =
19537330, type = 8
#include <stdio.h>
#include <sys/stat.h>
#include <unistd.h>
int main(int argc, char **argv) {
struct stat st;
stat(argv[1], &st);
printf("inode = %d, protection = %d, links = "\
"%d, uid = %u, size = %d, blocks = "\
"%d\n", (int)st.st_ino, (int)st.st_mode,
(int)st.st_nlink, (unsigned)st.st_uid,
(int)st.st_size, (int)st.st_blocks);
if ((st.st_mode & S_IFDIR) == S_IFDIR)
printf("the file is directory\n");
if ((st.st_mode & S_IFREG) == S_IFREG)
printf("the file is regular\n");
if ((st.st_mode & S_IFLNK) == S_IFLNK)
printf("the file is symbolic link\n");
if ((st.st_mode & S_IRUSR) == S_IRUSR)
printf("can read it\n");
if ((st.st_mode & S_IWUSR) == S_IWUSR)
printf("can write it\n");
if ((st.st_mode & S_IXUSR) == S_IXUSR)
printf("can execute it\n");
printf("my uid: %d\n", (int)getuid());
return 0;
}
$> gcc 2_fstat.c
$> ./a.out a.out
inode = 19642912, protection = 33261,
links = 1, uid = 502, size = 8536,
blocks = 24
the file is regular
can read it
can write it
can execute it
my uid: 502
$> ./a.out .
inode = 19537325, protection = 16877,
links = 5, uid = 502, size = 160,
blocks = 0
the file is directory
can read it
can write it
can execute it
my uid: 502
$> ./a.out 2_fstat.c
inode = 19537344, protection = 33188,
links = 1, uid = 502, size = 838,
blocks = 8
the file is regular
can read it
can write it
my uid: 502
Пакетирование операций работы с диском - суть всех планировщиков
read_blocks(1);
read_blocks(2);
I/O планировщик
read_blocks(1, 2);
split_blocks(res);
res1
res2
I/O планировщик
read_blocks(3, 1, 4, 2);
read_blocks(1, 2, 3, 4);
Слияние
Сортировка
+
1. Найти запрос, смежный по началу/концу. Если найден, приклеить.
...
...
5-10
10-15
2. Найти отдаленных соседей. Если найдены - между ними.
...
...
5-8
10-15
1-3
3. Иначе в конец "очереди"
...
100-120
10-15
* если в очереди есть запрос, старше некоторого порога, то все новые идут в конец
Итог:
Write starving - феномен "голодающих записей". Чтения более требовательны к скорости, но их предпочтение в планировщике морит записи "голодом" до устройства.
Merge/sort queue
Read FIFO queue
Write FIFO queue
req_t pick_next() {
req_t ro = next_ro();
req_t rw = next_rw();
if (ro.deadline <= curr_time ||
rw.deadline <= curr_time) {
if (ro.deadline < rw.deadline)
return ro;
return rw;
}
return next_merge_sort();
}
+
Положить в 2 из 3х очередей. У чтений дедлайн меньше в 10 раз.
Как Deadline I/O Scheduler решает проблему голодающих записей?
Вводится ограничение на число подряд выполненных чтений.
2 балла
"Предупреждающий"
Как Deadline, но после выполнения чтения ждет несколько мс на случай новых чтений
/* ... */
while (read(fd, buf, size) != 0) {
/* do something ... */
}
/* ... */
Типичная "читалка" - последовательные блокирующие чтения
...
CFQ - Completely Fair Queuing
Merge/sort queue
Deadline queue
Process queues
Process queues
Process queues
Process queues
...
time slice
time slice
time slice
time slice
Почти как CFS для процессов
BFQ - Budget Fair Queuing. Это взвешенный CFQ, с приоритетами. Еще ближе к CFS.
Noop. Самый простой - только мерж. Нет сортировки, справедливости и тд. Одна очередь мержа.
CFQ на момент чтения лекции - по умолчанию. Хорошо управляется с интерактивными приложениями
Linus Elevator - наилучшая пропускная способность, но тотальная несправедливость
$> # Template:
$> # cat /sys/block/{device_name}/queue/scheduler
$>
$> cat /sys/block/sda/queue/scheduler
noop deadline [cfq]
Посмотреть свой шедулер:
Как еще ускорить доступ к носителям?
Кэш
1 балл
Дерево закэшированных блоков
read(fd, buf, size);
если найдено - вернуть сразу
положить в кэш
Кэш ляжет в RAM, кэши процессора - доступ за наносекунды
no-write
write-back
write-through
Инвалидировать кэш, записать на носитель сразу.
Обновить кэш. На носитель записать, когда вытеснится из кэша.
Записать и в кэш, и на носитель сразу.
+ изгнание из кэша по LRU, классика
int
printf(const char * restrict format, ...);
int
fprintf(FILE * restrict stream, const char * restrict format, ...);
int
fputs(const char *restrict s, FILE *restrict stream);
int
fflush(FILE *stream);
Это userspace понятия. В ядре буферизуется и кэшируется вообще все.
Обратная связь: goo.gl/forms/TAeraYrqJcil7GDt1
и на портале Техносферы
В следующий раз:
Потоки. Отличие от процессов. Атомарные операции. Синхронизация. Атрибуты. Особенности многопоточных процессов. Вид в ядре.
By Vladislav Shpilevoy
Файловая система и ядро. Виртуальная ФС. Файлы и типы: block, char, network. Представление в ядре: inode. Секторы. Планировщики IO операций - elevator-ы: Linus, Deadline, Anticipatory, CFQ, Noop. Page cache. Page nowrite/writethrough/writeback. Работа с файлом - buffered, unbuffered, line.
Database C developer at Tarantool. Backend C++ developer at VirtualMinds.