Русский
Русский
English
Статистика
Реклама

Swap

Почему линукс использует swap-файл

03.02.2021 02:16:03 | Автор: admin

Жажда тюнинга может завести в неведомые дебри. И, пожалуй, едва ли не самая частая неправильная оптимизация отключение swap-файла. Если прикинуть частоту, с которой эта ошибка встречается, то, наверное, она входит в негласный top-10 (а может и top-5) самых распространенных, самых бесполезных и самых вредных оптимизаций - потому что swap-файл это одна из самых интересных, сложно понимаемых и недооцененных сущностей в подсистеме управления виртуальной памятью.

Эта статья является обзорной и предназначена для того, чтобы дать представление об общих процессах, происходящих в системе, и объяснить, откуда берутся некоторые мифы, следование которым может доставить неприятные моменты

В начале был вопрос

Когда возникает необходимость в swap-файле? Самый частый ответ когда не хватает памяти. И, естественно, это ответ неправильный почти полностью. Правильный ответ - необходимость в использовании swap-файла возникает тогда, когда система не может удержать в памяти необходимый кэш и грязные страницы. А чтобы понять, что это значит, нам надо сделать маленький экскурс внутрь ядра операционной системы.

Вся память, которой оперирует система, разбита на страницы. Каждый процесс в системе имеет свое плоское адресное пространство. Для каждого процесса система поддерживает карту страниц какая страница адресного пространства процесса (страница виртуальной памяти) отображена в какую страницу физической памяти - если вообще отображена, разумеется.

И когда процесс пытается получить доступ к какой-то странице своей памяти, MMU (memory management unit) процессора фактически производит обращение к той странице физической оперативной памяти, куда страница отображена.

А если страница не отображена ни в какую физическую страницу, то возникает page fault исключительная ситуация страница не найдена. При обработке этой ситуации система (ядро) проверяет, имеет ли процесс право получить доступ к своей логической странице: если не имеет (например эта страница заразервирована ядром или находится за хвостом кучи процесса) - то процессу придет сигнал SEGFAULT и процесс умрет.

А если имеет то ядро выполнит все нужные действия, чтобы восстановить правильное содержимое страницы, и предъявит её процессу - после чего операция успешно выполнится.

Какие группы страниц памяти живут в системе?

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

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

  • Используемая страница, хранящая данные, не принадлежащие кэшу

  • Страница анонимного кэша (anonymous page cache) страница числится как принадлежащая кэшу, но не закрепленная ни за каким файлом. Очень похожа на используемую страницу (и в определенных ситуациях может превращаться в неё)

  • Чистая страница кэша (clean cache page)- страница, в которой хранятся закэшированные данные файла, которые не менялись.

  • Грязная страница кэща (dirty cache page) страница, в которой хранятся данные файла, которые были изменены (данные в кэше поменяли но на диск изменения не сохранили)

Страница процесса, в свою очередь, также может иметь несколько состояний

  • Недоступная процесс не имеет права на доступ к этой странице. Если процесс к ней обращается "неподобающим образом" то получает SEGFAULT

  • Доступная, сопоставленная физической странице, не принадлежащей кэшу. Процесс может работать с этой страницей

  • Доступная, сопоставленная физической странице кэша, в которую загружены данные файла. Процесс может работать с этой страницей.

  • Доступная, не сопоставленная физической странице, но сопоставленная региону какого-либо файла. Прежде чем процесс сможет работать с этой страницей, система должна выделить физическую страницу, отдать ее в кэш, загрузить в нее данные из файла и после этого процесс сможет с ней работать (потому, что она превратится в предыдущую разновидность)

И сейчас, когда мы более-менее определились с видами страниц, мы переходим к самой интересной части

Работа процесса

Чтобы понять как все эти механизмы связаны и работают, давайте рассмотрим запуск нового процесса. Мы запускаем новый процесс, и система загружает исполняемый файл. Как это происходит?

Система открывает бинарный файл с программой и Нет, она не читает его. Она отображает файл в адресное пространство процесса то есть создает записи (например)

  • Страница 0 ... 15 -> /usr/bin/filename сегмент 0 ... 65535

  • Страница 16 ... 31 -> /usr/bin/filename сегмент 65536 ... 131071

Ну да, там всё сложнее но нам сейчас главное понять идею.

С точки зрения системы, эти логические страницы принадлежат процессу, отображены на какой-то файл файловой системы и не сопоставлены никакой физической странице. И теперь, управление отдается процессу например вызывается инструкция с какой-то страницы или читаются или записываются данные в страницу.

MMU обнаруживает ошибку странице процесса не сопоставлена никакая физическая страница - ошибка, page fault! Ядро получает эту ошибку, смотрит на статус страницы:

  • процессу разрешен доступ

  • физической странице не сопоставлена (и это логично, иначе бы page fault не возник)

  • но сопоставлена сегменту файла

  • указанный фрагмент файла в кэше не обнаружен

В указанной ситуации, ядро берет свободную страницу, объявляет её страницей кэша и загружает в неё данные из файла с диска. Теперь это чистая страница кэша - clean page. Затем ядро добавляет ссылку этой странице процесса сопоставлена эта физическая страница, принадлежащая кэшу и взятая из указанного места файла. Управление возвращается процессу, а проблемная операция завершается успешно. Ну а если это была операция модификации данных (и модификация допустима), то после записи данных страница помечается как измененная и становится грязной страницей кэша - dirty page с точки зрения ядра.

Если же в кэше системы уже есть страница, в которой лежат данные из файла в неизменном виде (найдена чистая страница кэша), то страница процесс просто начинает ссылаться на обнаруженную страницу кэша.

Точно такие же процессы происходят для разделяемых библиотек (бинарник программы, на самом деле, тоже фактически разделяемая библиотека, которая просто содержит специальную точку входа).

Примечание "на полях": на самом деле, когда файл разделяемой библиотеки загружается, он частично прочитывается (как минимум его заголовок и секция символов: описание расположения объектов (функций и переменных) содержащихся в файле разделяемой библиотеки) но в большинстве случаев, целиком файл не читается, и подгружается в процессе работы программы по мере обращения к функциям и переменным, описанным в загруженных разделяемых библиотеках. Поэтому, после перезагрузки система может немного подтупливать - в том числе всё то время, пока кэш заполняется разделяемыми библиотеками.

Второе "примечание": механизмы работы с отображаемыми в память файлами намного сложнее и если за них браться, эта статья превратилась бы в многостраничный трактат. Но нам сейчас надо понять остовые линии поведения системы в стрессовых условиях нехватки RAM

Откуда (и когда) начинается использование swap-файла?

Представим, что мы загрузили с большой бинарник с большим количеством библиотек (привет, браузер!) в котором открыли сколько-то вкладок с документами, и исчерпали доступную физическую память. При этом, у нас образуется две больших группы страниц чистые страницы кэша, сопоставленные разделяемым библиотекам причем в этих страницах содержится использующийся код (мы ведь помним про то, что библиотеки загружаются по мере необходимости) и приватные страницы процесса или страницы anonymous page cache (этот механизм часто используется для предварительной аллокации памяти). Но, в обоих случаях, мы можем рассматривать их как данные процесса.

Свободных страниц у нас нет всё ушло на кэш и данные.

Теперь мы открываем еще одну вкладку, что при этом случается? Чтобы найти память, система вынуждена освободить часть чистых страниц кэша и отдать их под данные приложения ведь данные которые там лежали, можно снова загрузить с диска (это ведь "чистые" страницы!).

Но в этих страницах был ИСПОЛЬЗУЕМЙ код иначе бы они не подтянулись в кэш.

А это значит, что как только (и скорей всего через не самый долгий промежуток времени) программа пойдет в код, который был выгружен из памяти, она получит page fault и система снова прочтет код из файла библиотеки с диска. В новую пустую страницу. Но чтобы её получить, надо сбросить другую чистую страницу кэша! И система входит в трэшинг - она постоянно вынуждена освобождать и снова погружать код, раз за разом, из одного файла за другим.

То есть фактически, в системе уже есть swap-файл, из которого постоянно ведется чтение и это разделяемые библиотеки. Но система не может использовать его эффективно, поскольку он ограничен и фактически неуправляем - и скатывается в трэшинг

А если бы у нас был настоящий swap-файл?

На основе учета частоты обращения к различным страницам, система скорей всего не освободила бы страницу с кодом, а поместила в swap данные наиболее давно не использовавшихся вкладок. И вместо постоянного чтения с диска, у нас случились бы одна запись и одно чтение причем именно в момент обращения к выгруженной в swap вкладке.

Для страницы, которая была поднята из swap-файла, применяются дополнительные изящные механизмы она фактически считается чистой страницей кэша пока не изменена, но место в swap-файле за ней по-прежнему резервируется - и пока страница остается чистой, она может быть при необходимости просто забыта без сохранения в swap аналогично обычным чистым страницам кэша.

Для этого, пока поднятая из swap-файла страница не поменялась, место, которое страница занимала в swap-файле, будет по возможности за ней удерживаться и не считаться освобожденным пока страница не изменится. Это означает, что в случае если у вас большой swap-файл, редко читаемые и не изменившиеся данные могут уехать в swap и многократно доставаться оттуда по мере надобности то есть в нормальной ситуации, swap работает по принципу одна запись, много чтений.

А если swap-файл маленький, то начинается веселье с постоянным записью-чтением в swap-файл.

Таким образом, отключение / отсутствие swap-файла, равно как и его малый размер, отрицательно влияет на производительность в условиях начала нехватки памяти, не позволяя системе выгружать наименее редко используемые страницы и заставляя делать лишние записи там, где запись, на самом деле, не нужна ведь вместо того, чтобы удержать код, к которому мы обращаемся часто, система вынуждена сохранять в памяти данные, к которым мы не обращаемся - потому что их негде больше сохранить.

Какие выводы можно сделать из всего вышеописанного?

Кэш (page cache) это не только кэш данных! Кэш - это очень существенный компонент в схеме организации виртуальной памяти - и теперь мы понимаем неправильность вопроса почему система ушла в свал а не освободила кэш. Ведь ответ очень прост - этот кэш на самом деле не просто кэш, а код. И если код используется чаще, чем данные - то в данном случае более важно сохранить в памяти код (сохранить кэш) чем удерживать в памяти данные!

Поэтому не надо бояться наличия swap-файла если статистика не покажет необходимости - система не полезет в свап.

Swap-файл может быть считаться используемым даже если все данные из него уже подняты обратно в память.

Малый размер swap-файла вреден он не позволяет системе работать эффективно, поскольку увеличивает объем непродуктивного I/O

Резюме?

Это прекрасно, когда объем оперативной памяти достаточно велик, чтобы вместить и весь необходимый кэш, и данные. Но если у вас бюджетная система с8 16ГБ оперативной памяти (не говоря уж об ультрабюджетных ноутбуках с распаянными и нерасширяемыми 4GB), то swap достаточного объема (не менее 1 x ) это "то, что доктор прописал".

И достаточный размер swap-файла на быстром накопителе (кто сказал NVMe?) на бюджетном ноутбуке может очень хорошо увеличить производительность системы, особенно если вы любитель держать много открытых вкладок, документов, рисунков в графическом редакторе и т.д.

Не мешайте системе работать. Нет нужды бояться swap-файла. Если он не нужен он не будет использован. И то, что вы видите, что он якобы использован - на самом деле совсем не значит, что он использован. Просто нам показали картинку без подробностей.

Подробнее..

Почему линукс использует swap-файл, часть 2

07.02.2021 16:04:25 | Автор: admin
Первая часть маленького срывания покрова о работе подсистемы виртуальной памяти, связи механизмов mmap, разделяемых библиотек и кэшей вызвало такое бурное обсуждение, что я не смог удержаться от того, чтобы не продолжить исследование на практике

Поэтому, сегодня мы сделаем Крошечную лабораторную работу. В виде крошечной же программы на C, которую мы напишем, скомпилируем и проверим в деле со свапом и без свапа.

Программа делает очень простую вещь она запрашивает большой кусок памяти, обращается к нему и активно с ним работает. Чтобы не мучаться с загрузкой каких-либо библиотек, мы просто создадим большой файл, который отобразим в память так, как это делает система при загрузке разделяемых библиотек.

А вызов кода из этой библиотеки мы просто эмулируем чтением из такого mmap-нутого файла.

Программа сделает несколько итераций, на каждой итерации она будет параллельно обращаться к коду и к одному из участков большого сегмента данных.

И, чтобы не писать лишнего кода, мы определим две константы, которые определят размер сегмента кода и общий размер оперативной памяти:

  • MEM_GBYTES размер оперативной памяти для теста
  • LIB_GBYTES размер кода

Объем данных у нас меньше объема физической памяти:

  • DATA_GBYTES = MEM_GBYTES 2

Суммарный объем кода и данных чуть больше объема физической памяти:

  • DATA_GBYTES + LIB_GBYTES = MEM_GBYTES + 1

Для теста на ноутбуке я взял MEM_GBYTES = 16, и получил следующие характеристики:

  • MEM_GBYTES = 16
  • DATA_GBYTES = 14 значит данных будет 14GB, то есть памяти достаточно
  • Swap size = 16GB

Текст программы


#include <sys/mman.h>#include <fcntl.h>#include <string.h>#include <errno.h>#include <stdio.h>#include <stdlib.h>#define GB       1073741824l#define MEM_SIZE    16#define LIB_GBYTES   3#define DATA_GBYTES   (MEM_SIZE - 2)long random_read(char * code_ptr, char * data_ptr, size_t size) {long rbt = 0;for (unsigned long i=0 ; i<size ; i+=4096) {rbt += code_ptr[(8l * random() % size)] + data_ptr[i];}return rbt;}int main() {size_t libsize = LIB_GBYTES * GB;size_t datasize = DATA_GBYTES * GB;int fd;char * dataptr;char * libptr;srandom(256);if ((fd = open("library.bin", O_RDONLY)) < 0) {printf("Required library.bin of size %ld\n", libsize);return 1;}if ((libptr = mmap(NULL, libsize,PROT_READ, MAP_SHARED, fd, 0)) == MAP_FAILED) {printf("Failed build libptr due %d\n", errno);return 1;}if ((dataptr = mmap(NULL, datasize,PROT_READ | PROT_WRITE, MAP_SHARED | MAP_ANONYMOUS,-1, 0)) == MAP_FAILED) {printf("Failed build dataptr due %d\n", errno);return 1;}printf("Preparing test ...\n");memset(dataptr, 0, datasize);printf("Doing test ...\n");unsigned long chunk_size = GB;unsigned long chunk_count = (DATA_GBYTES - 3) * GB / chunk_size;for (unsigned long chunk=0 ; chunk < chunk_count; chunk++) {printf("Iteration %d of %d\n", 1 + chunk, chunk_count);random_read(libptr, dataptr + (chunk * chunk_size), libsize);}return 0;}

Тест без использования swap


Запрещаем swap указав vm.swappines=0 и запускаем тест
$ time ./swapdemoPreparing test ...Killedreal 0m6,279suser 0m0,459ssys 0m5,791s


Что произошло? Значение swappiness=0 отключило свап анонимные страницы в него больше не вытесняются, то есть данные всегда в памяти. Проблема в том, что оставшихся 2GB не хватило для работающих в фоне Chrome и VSCode, и OOM-killer убил тестовую программу. А заодно нехватка памяти похоронила вкладку Chrome, в которой я писал эту статью. И мне это не понравилось пусть даже автоматическое сохранение сработало. Я не люблю когда мои данные хоронят.

Включенный swap


Выставляем vm_swappines = 60 (по умолчанию)
Запускаем тест:
$ time ./swapdemoPreparing test ...Doing test ...Iteration 1 of 11Iteration 2 of 11Iteration 3 of 11Iteration 4 of 11Iteration 5 of 11Iteration 6 of 11Iteration 7 of 11Iteration 8 of 11Iteration 9 of 11Iteration 10 of 11Iteration 11 of 11real 1m55,291suser 0m2,692ssys 0m20,626s

Фрагмент top:
Tasks: 298 total,  2 running, 296 sleeping,  0 stopped,  0 zombie%Cpu(s): 0,6 us, 3,1 sy, 0,0 ni, 85,7 id, 10,1 wa, 0,5 hi, 0,0 si, 0,0 stMiB Mem : 15670,0 total,  156,0 free,  577,5 used, 14936,5 buff/cacheMiB Swap: 16384,0 total, 12292,5 free,  4091,5 used.  3079,1 avail MemPID USER   PR NI  VIRT  RES  SHR S %CPU %MEM   TIME+ COMMAND10393 viking  20  0  17,0g 14,2g 14,2g D 17,3 93,0  0:18.78 swapdemo136 root   20  0    0   0   0 S  9,6  0,0  4:35.68 kswapd0


Плохой-плохой линукс!!! Он использует swap почти на 4 гигабайт хотя у него 14 гигабайт кэша и 3 гигабайта доступно! У линукса неправильные настройки! Плохой outlingo, плохие старые админы, они ничего не понимают, они сказали включить swap и теперь у меня из-за них система свапится и плохо работает. Надо отключить swap как советуют намного более молодые и перспективные интернет-эксперты, ведь они точно знают что делать!

Ну Пусть будет так. Давайте максимально отключим свап по советам экспертов?

Тест почти без swap


Выставляем vm_swappines = 1

Это значение приведет к тому, что свапинг анонимных страниц будет производиться только если нет другого выхода.

Я верю Крису Дауну, поскольку считаю что он отличный инженер и знает что говорит, когда объясняет что swap-файл позволяет системе лучше работать. Поэтому, ожидая, что, что-то пойдет не так и возможно система будет ужасно неэффективно работать, я заранее подстраховался и запустил тестовую программу, лимитировав её таймером, чтобы увидеть хотя бы ее аварийное завершение.

Сначала рассмотрим вывод top:

Tasks: 302 total,  1 running, 301 sleeping,  0 stopped,  0 zombie%Cpu(s): 0,2 us, 4,7 sy, 0,0 ni, 84,6 id, 10,0 wa, 0,4 hi, 0,0 si, 0,0 stMiB Mem : 15670,0 total,  162,8 free,  1077,0 used, 14430,2 buff/cacheMiB Swap: 20480,0 total, 18164,6 free,  2315,4 used.  690,5 avail MemPID USER   PR NI  VIRT  RES  SHR S %CPU %MEM   TIME+ COMMAND6127 viking  20  0  17,0g 13,5g 13,5g D 20,2 87,9  0:10.24 swapdemo136 root   20  0    0   0   0 S 17,2  0,0  2:15.50 kswapd0

Ура?! Свап используется всего лишь на 2.5 гигабайт, что почти 2 в два раза меньше чем в тесте со включенным swap (и swappiness=60). Свапа используется меньше. Свободной памяти тоже меньше. И наверное, мы можем смело отдать победу молодым экспертам. Но вот что странно наша программа так и не смогла завершить даже 1 (ОДНОЙ!) итерации за 2 (ДВЕ!) минуты:
$ { sleep 120 ; killall swapdemo ; } &[1] 6121$ time ./swapdemoPreparing test Doing test Iteration 1 of 11[1]+ Done          { sleep 120; killall swapdemo; }Terminatedreal1m58,791suser0m0,871ssys0m23,998s


Повторим программа не смогла завершить 1 итерацию за 2 минуты хотя в предыдущем тесте она сделала 11 итераций за 2 минуты то есть с почти отключенным свапом программа работает более чем в 10(!) раз медленнее.

Но есть один плюс ни одной вкладки Chrome не пострадало. И это хорошо.

Тест с полным отключением swap


Но может быть, просто задавить свап через swappiness недостаточно, и его надо полностью отключать? Естественно, что надо проверить и эту теорию. Мы сюда тесты пришли провести, или что?

Это идеальный случай:

  • унас нет свопа и все наши данные будут гарантированно в памяти
  • свап не будет использоваться даже случайно, потому что его нет


И теперь наш тест завершится со скоростью молнии, старики пойдут на заслуженное ими место и будут менять картриджи дорогу молодым.

К сожалению, результат запуска тестовой программы аналогичный не завершилось даже одной итерации.

Вывод top:
Tasks: 217 total,  1 running, 216 sleeping,  0 stopped,  0 zombie%Cpu(s): 0,0 us, 2,2 sy, 0,0 ni, 85,2 id, 12,6 wa, 0,0 hi, 0,0 si, 0,0 stMiB Mem : 15670,0 total,  175,2 free,  331,6 used, 15163,2 buff/cacheMiB Swap:   0,0 total,   0,0 free,   0,0 used.  711,2 avail MemPID USER   PR NI  VIRT  RES  SHR S %CPU %MEM   TIME+ COMMAND136 root   20  0    0   0   0 S 12,5  0,0  3:22.56 kswapd07430 viking  20  0  17,0g 14,5g 14,5g D  6,2 94,8  0:14.94 swapdemo

Почему это происходит


Объяснение очень простое сегмент кода который мы подключаем через mmap (libptr) лежит в кэше. Поэтому когда мы запрещаем (или почти запрещаем) swap тем или иным способом, не важно каким физическим ли отключением swap, или через vm.swappines=0|1 это всегда заканчивается одним и тем же сценарием вымыванием mmapнутого файла из кэша и последующей его загрузкой с диска. А библиотеки загружаются именно через mmap, и чтобы убедиться в этом, достаточно просто сделать ls -l /proc//map_files:

$ ls -l /proc/8253/map_files/ | head -n 10total 0lr-------- 1 viking viking 64 фев 7 12:58 556799983000-55679998e000 -> /usr/libexec/gnome-session-binarylr-------- 1 viking viking 64 фев 7 12:58 55679998e000-5567999af000 -> /usr/libexec/gnome-session-binarylr-------- 1 viking viking 64 фев 7 12:58 5567999af000-5567999bf000 -> /usr/libexec/gnome-session-binarylr-------- 1 viking viking 64 фев 7 12:58 5567999c0000-5567999c4000 -> /usr/libexec/gnome-session-binarylr-------- 1 viking viking 64 фев 7 12:58 5567999c4000-5567999c5000 -> /usr/libexec/gnome-session-binarylr-------- 1 viking viking 64 фев 7 12:58 7fb22a033000-7fb22a062000 -> /usr/share/glib-2.0/schemas/gschemas.compiledlr-------- 1 viking viking 64 фев 7 12:58 7fb22b064000-7fb238594000 -> /usr/lib/locale/locale-archivelr-------- 1 viking viking 64 фев 7 12:58 7fb238594000-7fb2385a7000 -> /usr/lib64/gvfs/libgvfscommon.solr-------- 1 viking viking 64 фев 7 12:58 7fb2385a7000-7fb2385c3000 -> /usr/lib64/gvfs/libgvfscommon.so

И, как мы рассматривали в первой части статьи, система в условиях фактической нехватки памяти при отключенном свапинге анонимных страниц выберет единственный вариант который её оставил владелец, отключивший свап. И этот вариант реклейминг (освобождение) чистых страниц, занимаемых под данные mmap-нутых библиотек.

Заключение


Активное использование методики распространения программ всё свое везу с собой (flatpak, snap, docker image) приводит к тому, что количество кода, который подключается через mmap, существенно увеличивается.

Это может привести к тому, что использование экстремальных оптимизаций, связанных с настройкой/отключением swap, может привести к совершенно неожиданным эффектам, потому, что swap-файл это механизм оптимизации подсистемы виртуальной памяти в условиях memory pressure, а available memory это совсем не неиспользуемая память, а сумма размеров кэша и свободной памяти.

Отключая swap-файл, вы не убираете неправильный вариант, а не оставляете вариантов

Следует очень осторожно интерпретировать данные о потреблении памтяи процессом VSS и RSS. Они отображают текущее состояние а не оптимальное состояние.

Если вы не хотите, чтобы система использовала свап добавьте ей памяти но не отключайте свап. Отключение свапа на пороговых уровнях сделает ситуацию значительно хуже, чем она была бы, если бы система немного отсвапилась.

P.S.: В обсуждениях регулярно задаются вопросы а вот если включить сжатие памяти через zram.... Мне стало интересно, и я провел соответствующие тесты: если включить zram и swap, как это сделано по умолчанию в Fedora, то время работы ускоряется примерно до 1 минуты.

Но причина этого то, что страницы с нулями очень хорошо сжимаются, поэтому на самом деле данные уезжают не в swap, а хранятся в сжатом виде в оперативной памяти. Если заполнить сегмент данных случайными плохосжимаемыми данными, картина станет не такой эффектной и время работы теста опять же увеличится до 2 минут, что сравнимо (и даже чуть хуже), чем у честного swap-файла.
Подробнее..

Категории

Последние комментарии

  • Имя: Макс
    24.08.2022 | 11:28
    Я разраб в IT компании, работаю на арбитражную команду. Мы работаем с приламы и сайтами, при работе замечаются постоянные баны и лаги. Пацаны посоветовали сервис по анализу исходного кода,https://app Подробнее..
  • Имя: 9055410337
    20.08.2022 | 17:41
    поможем пишите в телеграм Подробнее..
  • Имя: sabbat
    17.08.2022 | 20:42
    Охренеть.. это просто шикарная статья, феноменально круто. Большое спасибо за разбор! Надеюсь как-нибудь с тобой связаться для обсуждений чего-либо) Подробнее..
  • Имя: Мария
    09.08.2022 | 14:44
    Добрый день. Если обладаете такой информацией, то подскажите, пожалуйста, где можно найти много-много материала по Yggdrasil и его уязвимостях для написания диплома? Благодарю. Подробнее..
© 2006-2024, personeltest.ru