Лаборатория Tarantool

Системное программирование

Лекция 3:

Память. Виртуальная и физическая. Уровни кэша, кэш линия. Пользовательская память и память ядра. False sharing. High and low memory.

Version: 2

The presentation is outdated and not maintained anymore. Please, refer to the English version for the most actual slides.

Новости

Дедлайны:

  • планировщик корутин: 19 марта, 5 дней. Дальше -1 балл в день.

Процесс

.text

.data

.stack

.heap

.stack

.stack

Файловые дескрипторы

Очередь сигналов

IPC

Память

Адресное пространство процесса

0x0

0xffffffff

.text

.data

.bss

.heap

.stack

.env

Файловые дескрипторы

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 - число. Индекс в массиве дескрипторов.

План лекции

  1. Регистры, уровни кэшей
  2. Оперативная память
  3. Виртуальная память
  4. Аллокаторы

Уровни памяти

Регистры

Кэш L1

Кэш LN

Оперативная память

Флеш память

Магнитная память

Скорость

Объем

Регистры [1]

  • Самая быстрая память
  • Скорость доступа - такт процессора
  • Нет виртуальных адресов - прямой доступ
  • Дорого - занимают место на процессоре и сложная реализация
  • Фиксированный набор, запаяны на чипе

Регистры [2]

Регистры [3]

Сохранение

Что сохранить

Хранимое значение

NAND - Not And

Static Random Access Memory - SRAM

pc

mar

ir

asid

eax

ebx

ecx

edx

esi

...

<128 бит

Кэш [1]

Кэш [2]

Временная локальность

Пространственная локальность

for (int a = 0; i < count; ++i)
{
    /* ... cache 'a' */
}
char buffer[128];
/* ... cache buffer */
read(buffer, 0, 64);
/* ... */
read(buffer, 64, 128);
/* ... */
read(buffer, 32, 96);
/* ... */

Кэш [3]

Кэш Размер Скорость доступа
L1 десятки Кб ~1 такт, < наносекунды
L2 единицы Мб десятки тактов, единицы наносекунд
L3 десятки Мб сотни тактов, десятки наносекунд

Кэш [4]

Почему такая разница скоростей кэшей?

Больше размер - дольше поиск. Дальше от процессора - ток передается дольше.

2 балла

Кэш [5]

1 - в большом кеше дольше поиск, много сравнений

c = 299 792 458\ м/с;\\ v = 3GHz;\\ 1 такт = 1/3 нс; \\ 1 такт * c ~= 10 см

2 - дорога до кеша и обратно тоже стоит времени

Кэш [6]

Оперативная память

Шина

Кэш L3

Кэш L2

Кэш L1

Ядро

Чип процессора

Политики включаемости кэша

Inclusive

L3

L2

L1

Exclusive

L3

L2

L1

NINE

L3

L2

L1

Inclusive кэш

L1

L2

Х

Пусть процессор читает Х ...

Взять из L1

L1

L2

Х

Скопировать в L1

Х

L1

L2

Х

Скопировать в L1 и L2

Х

Y

Y

Взять из L1

Починить включаемость

Взять из L1

Y

Exclusive кэш [1]

L1

L2

Х

Пусть процессор читает Х ...

Взять из L1

L1

L2

Х

Переместить в L1

Х

L1

L2

Х

Скопировать в L1

Взять из L1

Взять из L1

Exclusive кэш [2]

Как пополняются L2, L3 exclusive кэши?

За счет вытеснения из предыдущих кэшей.

L1

L2

Х

Вытеснить старое в L2, новое положить в L1

Взять из L1

Y

Y

2 балла

NINE кэш

Not Inclusive Not Exclusive

L1

L2

Х

Взять из L1

L1

L2

Х

Скопировать в L1 и L2

Взять из L1

L1

L2

Х

Скопировать в L1

Х

Взять из L1

Y

Y

X

Нет миграции в L2 - not exclusive

Нет вытеснения L1 из-за L2 - not inclusive

Y

Политики записи в кэш

L3

L2

L1

Оперативная память

Write through

Нужно ждать конец записи

L3

L2

L1

Оперативная память

Write back

Установить dirty bit

Нужно синхронизировать кеши c другими ядрами

Устройство кэша [1]

64 б

64 б

64 б

64 б

64 б

...

addr1

addr2

addr3

addr4

addrN

char *
read_from_cache(unsigned long addr)
{
    /* This is machine code, 64 bit. */
    unsigned offset = addr & 63;
    unsigned cache_addr = addr ~ offset;
    char *line = lookup_line(cache_addr);
    return line + offset;
}

Политика отображения адресов

Хранение и чтение блоками

Устройство кэша [2]

64 байта

Кэш-линия:

Интерпретация адреса кэшом:

tag

tag

offset

6 бит

policy helper

  • Полноассоциативный кэш
  • Кэш прямого отображения
  • N-ассоциативный кэш

user data

Полноассоциативный кэш

Адреса

Кеш

...

Адрес

tag

offset

6 бит

58 бит

Аппаратная схема

Кэш прямого отображения

Адреса

Кеш

Адрес

tag

offset

6 бит

26 бит

Аппаратная схема

index

32 бита

N-ассоциативный кэш [1]

Адреса

Кеш

Адрес

tag

offset

6 бит

Аппаратная схема

index

...

58 - log_{2}{r}
log_{2}{r}
r

- количество cache lines

N-ассоциативный кэш [2]

Разница сглаживается

Кэширование инструкций [1]

<opcode> := <argcount> <argtypes> <indirect_args>
            <ret_type> <target_dev>

Число параметров

Типы параметров

Неявные параметры

Возвращаемый тип

Где выполнять?

Кэширование инструкций [2]

CPU Pipeline:

  1. ​Instruction fetch
  2. Instruction decode
  3. Execute
  4. Memory access
  5. Register write back

Многопроцессорный кэш [1]

Общий кэш?

  • придется вынести с чипа
  • борьба за доступ

Раздельный кэш?

  • нужна синхронизация - когерентность кэшей

Многопроцессорный кэш [2]

  • Modified - процесс-владелец модифицировал линию, но в других кэшах ее нет;
  • Exclusive - линия не модифицирована и в дргих кэшах ее нет;
  • Shared - линия не модифицирована, но может быть в кэшах других процессов;
  • Invalid - линия свободна для новых записей

False sharing [1]

struct my_object {
    int32_t a;
    int32_t b;
    int32_t c;
    int32_t d;
};

struct my_object object;

void
thread1_func()
{
    while (true) {
        do_something(object.a);
        do_something(object.b);
    }
}

void
thread2_func()
{
    while (true) {
        write_into(&object.c);
        write_info(&object.d);
    }
}

a, b

c, d

Кэш-линия

8 байт

8 байт

48 байт

Читающий поток

Пишущий поток, инвалидирует кеш

False sharing [2]

struct my_object {
    int32_t a;
    int32_t b;
    char padding[56];
    int32_t c;
    int32_t d;
};

a, b

padding

Кэш-линии

8 байт

56 байт

Читающий поток

Пишущий поток

c, d

8 байт

56 байт

Meltdown [1]

Сейчас тут

Выполнение наперед, предсказание условий

Проверка привелегий уже после кеширования

Meltdown [2]

static jmp_buf jmp;

void
process_signal(int code)
{
	printf("Process SIGSEGV, code = %d, "\
               "SIGSEGV = %d\n", code, SIGSEGV);
	longjmp(jmp, 1);
}

int
main()
{
	char *p = NULL;
	signal(SIGSEGV, process_signal);
	printf("Before SIGSEGV\n");
	if (setjmp(jmp) == 0)
		*p = 100;
	printf("After SIGSEGV\n");
	return 0;
}

Добиться сегфолта не сложно - сделать доступ в плохую память

vladislav$> gcc 2_catch_sigsegv.c

vladislav$> ./a.out
Before SIGSEGV
Process SIGSEGV, code = 11, SIGSEGV = 11
After SIGSEGV

vladislav$>

На SIGSEGV вешается обработчик, который сразу возвращает управление

Meltdown [3]

char userspace_array[256 * 4096];
char kernel_byte_value;

char
get_kernel_byte(const char *kernel_addr)
{
    clear_cache_for(userspace_array);
    register_exception_handler(process_exception);

    char index = *kernel_addr;
    /* Next code is for speculation. */
    char unused = userspace_array[index * 4096];
    /* Next code is after exception handler. */
    return kernel_byte_value;
}


void
process_exception()
{
    uint min_time = UINT_MAX;
    for (char i = 0; i < 256; ++i) {
        uint start = time();
        char unused = userspace_array[i * 4096];
        uint duration = time() - start;
        if (duration < min_time) {
            min_time = duration;
            kernel_byte_value = i;
        }
    }
}

Сюда будет читаться ядро

Очищаем кеши, так как атака требует пустых кешей

Чтение запретного адреса в переменную, которая поместит в кэш другую доступную переменную

Ядро даст исключение, но нужный элемент уже в кеше. Его индекс - это значение из ядра

PROFIT

Польза кэша

AAT = hit\_time + miss\_rate * miss\_penalty

Average Access Time

AAT = hit_{cache} + miss_{cache} * ram\_time

Допустим такие значения:

ram\_time = 100ns\\ miss_{L1\_cache} = 10\%\\ hit_{L1\_cache} = 1ns\\ miss_{L2\_cache} = 5\%\\ hit_{L2\_cache} = 10ns\\ miss_{L3\_cache} = 1\%\\ hit_{L3\_cache} = 50ns

Тогда:

no cache: AAT = 100ns\\ cache L1: AAT = 1ns + 10\% * 100ns = 11ns\\ cache L2: AAT = 1ns + 10\% * (10ns + 5\% * 100ns) = 2.5ns\\ cache L3: AAT = 1ns + 10\% * (10ns + 5\% * (50ns + 1\% * 100ns)) = 2.255ns

x44

Стоимость кэша

Latency Comparison Numbers (~2012)
----------------------------------
L1 cache reference                           0.5 ns
Branch mispredict                            5   ns
L2 cache reference                           7   ns
Mutex lock/unlock                           25   ns
Main memory reference                      100   ns
Compress 1K bytes with Zippy             3,000   ns
Send 1K bytes over 1 Gbps network       10,000   ns
Read 4K randomly from SSD*             150,000   ns
Read 1 MB sequentially from memory     250,000   ns
Round trip within same datacenter      500,000   ns
Read 1 MB sequentially from SSD*     1,000,000   ns
Disk seek                           10,000,000   ns
Read 1 MB sequentially from disk    20,000,000   ns
Send packet CA->Netherlands->CA    150,000,000   ns

Пример помощи кэшу

struct complex_struct {
	int id;
	double a;
	long d;
	char buf[10];
	char *long_buf;
};

struct complex_struct *
complex_struct_bad_new(int long_buf_len)
{
	struct complex_struct *ret =
		(struct complex_struct *) malloc(sizeof(*ret));
	ret->long_buf = (char *) malloc(long_buf_len);
	return ret;
}

struct complex_struct *
complex_struct_good_new(int long_buf_len)
{
	struct complex_struct *ret;
	int size = sizeof(*ret) + long_buf_len;
	ret = (struct complex_struct *) malloc(size);
	ret->long_buf = (char *) ret + sizeof(*ret);
	return ret;
}

int main()
{
	return 0;
}

Это плохо

В одной кэш линии

Ничего не знаем про кэш

Оперативная память [1]

Dynamic Random Access Memory - DRAM

DRAM бит

SRAM бит

  • емкость - фемтофарад (10^-15)
  • сопротивление - тераом 10^12
  • нужен ресет раз в 50-100мс

Оперативная память [2]

Основная проблема - установка/снятие бита занимает время

Что дальше?

Диски?

Просто внешние устройства, передают данные через оперативную память, обращения через ядро

Выше - виртуальная память.

Виртуальная память [1]

Memory Management Unit - устройство для аппаратной трансляции в физический адрес

Виртуальная память [2]

Физическая и виртуальная память бьются на страницы по 4-8Кб

virt_page

offset

log_{2}{page\_count}

MMU:

virtual\_page \Rightarrow physical\_page

Виртуальный адрес

Виртуальная память [3]

1.

2.

3.

4.

5.

6.

7.

123

54

68

90

230

170

13

Номер физической страницы

Индекс массива - номер виртуальнойстраницы

void *
translate(void *virt)
{
    int virt_page = virt >> offset_bits;
    int phys_page = page_table[virt_page];
    int offset = virt ~ (virt_page << offset_bits);
    return (phys_page << offset_bits) | offset;
}

Одна большая таблица? Слишком дорого.

Все в память? Слишком медленно.

Page table

Виртуальная память [4]

Как решить проблему MMU и таблицы страниц?

Не знаешь, что делать - сделай кэш.

2 балла

Виртуальная память [5]

1.

2.

3.

4.

5.

6.

7.

123

54

68

90

230

170

13

Page table

187.

232.

34.

48.

519.

94.

58

123

54

68

90

230

170

13

TLB

Translation Lookaside Buffer

std::vector<unsigned>
std::map<unsigned, unsigned>

Аппаратная, < 100 записей. По сути и есть MMU.

Программная, тысячи, миллионы записей

Процессор наперед подгружает нужные страницы. Можно помочь ему при помощи 

__builtin_prefetch

Content Addressable Memory - CAM

Виртуальная память [6]

Page global directory

Page middle directory

Page table entry

L1_idx

Виртуальный адрес

L2_idx

offset

Виртуальная память [7]

Как делить TLB между разными процессами?

Идентификатор адресного пространства - Address Space Identifier, ASID. Это неявный префикс для любого адреса активного процесса в таблицах страниц.

2 балла

Виртуальная память [8] в ядре

/**
 * 16.10.2018
 * 138 lines.
 */
struct page {
	unsigned long flags;
	void *virtual;
        struct list_head lru;
};

Понятие страницы скрыто от пользователя в ядре

В ядре тоже виртуальная память и свои аллокаторы

void *
kmalloc(size_t size, gfp_t flags);

void *
vmalloc(unsigned long size);

Таблицы страниц обслуживаются ядром

Виртуальная память [9]

0x0

0xffffffff

.text

.data

.bss

.heap

.stack

.env

0xс0000000

.kernel

Виртуальная память [10]

Проблема High и Low memory на 32-битной архитектуре

3Gb для пользователей

~800Мб ядро

~200Мб резерв

Виртуальная память [11]

KASLR - Kernel Address Space Layout Randomization

KAISER - Kernel Address Isolation to have Side-channels Efficiently Removed

Борьба с атаками на ошибки кода, на прямые уязвимости

Борьба с Meltdown - с побочным каналом утечки

Виртуальная память [12]

#define __builtin_prefetch
#define __builtin_expect

int
madvise(void *addr, size_t len, int advice);

Кэши, физическая память скрыты от пользователя, кроме нескольких подсказок

Но доступна виртуальная память

void *
mmap(void *addr, size_t len, int prot, int flags, int fd, off_t offset);

void *
brk(const void *addr);

void *
alloca(size_t size);

void *
malloc(size_t size);

Malloc

32MB

32MB

16MB

16MB

16MB

16MB

8MB

8MB

8MB

8MB

8MB

8MB

8MB

8MB

malloc(size);

Округляет до ближайшего блока, заполняет заголовки, отдает

Альтернативы malloc

  • jemalloc
  • tcmalloc

Процессы, память. Практикум

Shell

Нужно написать упрощенную версию командной строки. Она должна принимать на вход строки вида:

    > command_name param1 param2 ...

и выполнять их при помощи вызова команды command_name с параметрами. То есть работать как терминал.

 

Нужно реализовать поддержку pipe - |, перенаправления вывода в файл - >, >>.

Цена: 15 - 25 баллов.

Срок: 2 недели после дедлайна первого ДЗ.

Положить на ваш гитхаб и сказать мне его. Сдавать как угодно - лично/удаленно.

Заключение

Обратная связь: goo.gl/forms/TAeraYrqJcil7GDt1

и на портале Техносферы

В следующий раз:

Сигналы. Аппаратные и программные прерывания, их природа. Top and bottom halves. Сигналы и системные вызовы, контекст сигнала.

Системное программирование 3

By Vladislav Shpilevoy

Системное программирование 3

Виртуальная и физическая память. Cache, cache line, cache levels, cache coherence, false sharing. High and low memory, области памяти. Page tables. Память ядра и процесса пользователя, разметка, вызовы: kmalloc, vmalloc, brk, madvice, mmap, pmap. Malloc, его альтернативы.

  • 2,158