diff --git a/.github/workflows/vtkm.yml b/.github/workflows/vtkm.yml deleted file mode 100644 index 5c0452e..0000000 --- a/.github/workflows/vtkm.yml +++ /dev/null @@ -1,25 +0,0 @@ -name: Test VT Kernel Module - -on: - push: - branches: - - main - pull_request: - paths: - - 'lab/vtkm/**' - -jobs: - build: - runs-on: ubuntu-latest - defaults: - run: - working-directory: ./lab/vtkm - steps: - - uses: actions/checkout@v4 - - name: Clang Format - run: | - find . -path ./build -prune -o \ - \( -iname '*.c' -o -iname '*.h' -o -iname '*.cpp' -o -iname '*.hpp' \) \ - -exec clang-format --style=file --dry-run --verbose {} \; - - name: Build - run: make diff --git a/.github/workflows/vtpc.yml b/.github/workflows/vtpc.yml deleted file mode 100644 index 979f74e..0000000 --- a/.github/workflows/vtpc.yml +++ /dev/null @@ -1,54 +0,0 @@ -name: Test VT Page Cache - -on: - push: - branches: - - main - pull_request: - paths: - - 'lab/vtpc/**' - -jobs: - build: - strategy: - matrix: - cmake_build_type: - - Asan - - Release - runs-on: ubuntu-latest - container: - image: silkeh/clang:latest - defaults: - run: - working-directory: ./lab/vtpc - steps: - - name: Checkout - uses: actions/checkout@v4 - - - name: Clang Format - run: | - find . -path ./build -prune -o \ - \( -iname '*.c' -o -iname '*.h' -o -iname '*.cpp' -o -iname '*.hpp' \) \ - -exec clang-format --style=file --dry-run --verbose {} \; - - - name: Configure - run: cmake -B build -DCMAKE_BUILD_TYPE=${{ matrix.cmake_build_type }} - - - name: Build - run: cmake --build build - - - name: Clang Tidy - if: matrix.cmake_build_type == 'Release' - run: | - find . -path ./build -prune -o \ - \( -iname '*.c' -o -iname '*.h' -o -iname '*.cpp' -o -iname '*.hpp' \) -exec \ - clang-tidy -p ./build {} \; - - - name: Test Basic - run: ./build/test/test_basic - - - name: Test Sequential - run: ./build/test/test_seq - - - name: Test Random - run: ./build/test/test_random diff --git a/.github/workflows/vtsh.yml b/.github/workflows/vtsh.yml index 2fbfefe..04ca522 100644 --- a/.github/workflows/vtsh.yml +++ b/.github/workflows/vtsh.yml @@ -15,7 +15,7 @@ jobs: cmake_build_type: - Asan - Release - runs-on: ubuntu-latest + runs-on: self-hosted container: image: silkeh/clang:latest defaults: @@ -46,4 +46,4 @@ jobs: - name: Test working-directory: ./lab/vtsh/test - run: python3 -m unittest discover + run: python3 -m unittest discover \ No newline at end of file diff --git a/lab/vtld/.clang-format b/lab/vtld/.clang-format new file mode 100644 index 0000000..dd99110 --- /dev/null +++ b/lab/vtld/.clang-format @@ -0,0 +1,16 @@ +BasedOnStyle: Google + +AccessModifierOffset: -2 + +AllowShortFunctionsOnASingleLine: None +AllowShortIfStatementsOnASingleLine: false +AllowShortLoopsOnASingleLine: false + +DerivePointerAlignment: false +BreakConstructorInitializers: BeforeComma +AlignAfterOpenBracket: BlockIndent + +BinPackArguments: false +BinPackParameters: false + +AlignArrayOfStructures: Right diff --git a/lab/vtld/.gitignore b/lab/vtld/.gitignore new file mode 100644 index 0000000..cfb1085 --- /dev/null +++ b/lab/vtld/.gitignore @@ -0,0 +1,5 @@ +cpu_dedup +cpu_dedup_pthreads +ema_replace_str +ema_replace_str_pthreads +*.bin \ No newline at end of file diff --git a/lab/vtld/.vscode/settings.json b/lab/vtld/.vscode/settings.json new file mode 100644 index 0000000..f094915 --- /dev/null +++ b/lab/vtld/.vscode/settings.json @@ -0,0 +1,5 @@ +{ + "files.associations": { + "errno.h": "c" + } +} \ No newline at end of file diff --git a/lab/vtld/README.md b/lab/vtld/README.md new file mode 100644 index 0000000..5f45463 --- /dev/null +++ b/lab/vtld/README.md @@ -0,0 +1,165 @@ +# Базовый трек. Лабораторная работа 1 + +Вариант: `cpu-dedup ema-replace-str` + +## Часть 3. Анализ системы и мониторинг + +Разработайте параметризируемую программу-нагрузчик, которая будет однопоточно нагружать подсистему ввода-вывода (IO). Она должна принимать на вход следующие параметры: + +- `rw: read/write` - режим нагрузки: чтение или запись; +- `block_size: ` - размер блока в байтах, с которым производится чтение/запись; +- `block_count: ` - количество блоков; +- `file: ` - имя файла, с которым происходит работа; +- `range: -` - границы в пределах файла, в которые должны + осуществляться запись/чтение, значение по умолчанию, 0-0, означает, что доступен + весь файл; +- `direct: on/off` - открывать файл с опцией O_DIRECT (в обход кэшей ОС) или нет; +- `type: sequence/random` - режим выбора следующего блока для записи/чтения + последовательно или случайно; + +Разработайте комплекс программ-нагрузчиков согласно выданному варианту. +Каждый нагрузчик должен принимать параметр, который определяет +количество повторений для алгоритма, указанного в задании, а также другие +вспомогательные параметры. Варианты программ рассчитаны так, чтобы по-разному +нагружать вычислительную подсистему (CPU) и подсистему ввода-вывода (IO) - это +необходимо учитывать при их реализации. Разработанные программы необходимо +скомпилировать без дополнительных опций оптимизации компилятора. + +Проведите исследование поведения ОС во время исполнения разработанных +программ-нагрузчиков по следующему плану: + +1. Перед запуском нагрузчика, попробуйте оценить время работы вашей программы + или ее результаты (если по варианту вам досталось измерение чего либо) и + обоснуйте свои предположения. + +2. Запустите программу-нагрузчик и зафиксируйте метрики ее работы с помощью + инструментов для мониторинга и профилирования (см. лекции). Сравните полученные + результаты с ожидаемыми. Объяснените наблюдаемое поведение. Продолжительность + каждого запуска должна занимать достаточное для прекращения переходных + процессов время, по крайней мере, минуту. + +3. Определите количество одновременно запущенных процессов с программой-нагрузчиком, + которое эффективно нагружает все ядра процессора в вашей системе. Как + распределяются показатели времени USER%, SYS%, WAIT%, а также полное время + выполнения нагрузчика, какое количество переключений контекста (вынужденных и + невынужденных) происходит при этом? Подумайте над тем, как вы определяете + эффективность. + +4. Увеличьте количество нагрузчиков вдвое, втрое, вчетверо. Как изменились + исследуемые показатели? Почему? + +5. Объедините программы-нагрузчики в одну, реализованную при помощи потоков + выполнения, чтобы один нагрузчик эффективно нагружал все ядра вашей системы. Как + изменились показатели времени для того же объема вычислений? Запустите одну, + две, три таких программы. Как изменились исследуемые показатели? Почему? + +6. Скомпилируйте программу-нугрузчик с опцией агрессивной оптимизации. Как + изменились исследуемые показатели? На сколько сократилось реальное время + исполнения программы нагрузчика? Почему? + +В процессе защиты вашей работы преподаватель будет просить вас запустить +программу-нагрузчик с различными комбинациями параметров и просить объяснить +результат. + +## Требования к реализации + +1. Программа (комплекс программ) должна быть реализован на языке C. + +2. Дочерние процессы должны быть созданы через указанные в варианте системные + вызовы операционной системы, с обеспечением корректного запуска и завершения + процессов. Запрещено использовать высокоуровневые абстракции над системными + вызовами. Необходимо использовать, процедуры libc. + +## Требования к отчету и защите + +Отчет должен содержать: + +1. Титульный лист с указанием номера и названия ЛР, вашего ФИО, ФИО + преподавателя практики, номера вашей группы, варианта ЛР. + +2. Текст задания в соответствии с вариантом. + +3. Листинг исходного кода всех программ, написанных в рамках данной ЛР. + +4. Предположения о свойствах программ-нагрузчиков + +5. Результаты измерений и метрик программ-нагрузчиков, полученных инструментами + мониторинга. Должно быть описано, какие утилиты запускались, с какими + параметрами и их вывод. + +6. Сравнительный анализ ожидаемых и фактических значений исследованных показателей. + +7. Заключение. + +Студент должен быть готов: + +1. Продемонстрировать работоспособность Shell и предоставить исходный код. + +2. Воспроизвести ход работы по исследованию поведения ОС и продемонстрировать схожие + результаты работы программ-нагрузчиков. + +3. Ответить на вопросы по реализации программ и контрольные вопросы. + +## Описание вариантов + +Нагрузчик вычислительной подсистемы (группа **cpu**): + +- `cpu-factorize`: Разложение числа на простые множители. Задаваемое число + должно являться произведением нескольких больших простых чисел. + +- `cpu-sort`: Любая сортировка массива чисел за N*log(N) и любая за N**2. + Алгоритм должен быть реализован самим студентом, а не взят из стандартной + библиотеки. + +- `cpu-short-path`: Поиск кратчайшего пути в графе. Граф генерируется в момент + запуска или записан в исходный код. + +- `cpu-linreg`: Построение модели линейной регрессии для последовательности случайных чисел. Количество чисел и диапозон значений задаются параметром. + +- `cpu-dedup`: Дедупликация элементов в массиве. Массив генерируется на основе + случайных чисел. Размер массива задаётся параметром. + +- `cpu-mat-mul`: Перемножение матриц. Размер матриц задаётся параметром, а + элементы матриц генерируются на основе случайных чисел. + +- `cpu-calc-md5`: Подсчитать хеш md5 для текста полученного конкатенацией + фрагментов текста выбранных с помощью генератора случайных чисел. + +- `cpu-calc-crc`: Посчитать контрольную сумму crc для текста полученного + конкатенацией фрагментов текста выбранных с помощью генератора случайных чисел. + + +Нагрузчик, работающий с внешней памятью (группа **ema**): + +- `ema-replace-int`: Поиск элемента в массиве чисел во внешней памяти и замена + значения на другое. Файл для поиска значения генерируется в произвольном + формате, размер задаётся через параметр. + +- `ema-replace-str`: Поиск подстроки в тексте во внешней памяти и замена на + другую строку того же размера. Файл для поиска значения генерируется в + произвольном формате, размер задаётся через параметр. + +- `ema-sort-int`: Сортировка массива чисел во внешней памяти. Весь массив не + может быть загружен в память, поэтому сортировка происходит частями. + +- `ema-traverse-graph`: Обход k-регулярного направленного графа, + сериализованного в файл, и выполнение модифицирующей операции над определенной + вершиной графа. Поиск вершины происходит по хранимому в ней значению, возможно с + ограничением глубины поиска. + Формат хранения: вершина (struct Node) определяется её отступом от начала файла (id=index*(sizeof struct Node)), в вершине хранится одно значение типа фиксированного размера и k номеров вершин, в которые из неё можно попасть; в файле вершины хранятся последовательно с выравниванием соответствующим размеру структуры. Тип значения, хранимого в вершине, и значение k подбираются таким образом, чтобы с учетом выравнивания не было неиспользуемой памяти. + При генерации графа случайно задается значение, хранимое в вершине и номера соседних вершин, отдельным параметром генератора задется шанс получения номера соседней вершины больше или меньше номера данной вершины (соседняя вершина раньше или позже). + +- `ema-join-*`: Необходимо реализовать алгоритм equi-join для объединения + таблиц в файлах. На вход подаются два файла, результат их объединения пишется в + новый файл. В первой строке файла содержится количество строчек в файле. В + каждой последующей строчке входного файла содержится два значения, разделенных + пробелами: численный идентификатор и строка без пробелов фиксированного размера + 8 байт. Строка является валидным английским словом. Таблицы необходимо + объединять по идентификатору. + + По варианту выдается стратегия джоина: + - `ema-join-nl`: Nested Loop Join + - `ema-join-hash`: Hash Join Join + - `ema-join-sm`: Sort + Merge Join + + Количество строк входных таблиц включают все комбинации пар значений: 5, 10, 100, 1000, 10000." \ No newline at end of file diff --git a/lab/vtld/cpu_dedup.c b/lab/vtld/cpu_dedup.c new file mode 100644 index 0000000..9212cf3 --- /dev/null +++ b/lab/vtld/cpu_dedup.c @@ -0,0 +1,51 @@ +#include +#include +#include +#include + +#include "cpu_utils.h" +#include "util.h" + +static void print_usage(const char* prog) { + fprintf(stderr, "Usage: %s --size N --repeats N [--max_value N]\n", prog); +} + +int main(int argc, char** argv) { + const char* size_s = get_opt_value(argc, argv, "size"); + const char* repeats_s = get_opt_value(argc, argv, "repeats"); + const char* max_value_s = get_opt_value(argc, argv, "max_value"); + + if (!size_s || !repeats_s) { + print_usage(argv[0]); + return 1; + } + + long size = strtol(size_s, NULL, 10); + long repeats = strtol(repeats_s, NULL, 10); + long max_value = max_value_s ? strtol(max_value_s, NULL, 10) : (size * 2); + + if (size <= 0 || repeats <= 0 || max_value <= 0) { + fprintf(stderr, "Invalid size, repeats, or max_value.\n"); + return 1; + } + + long* results = calloc((size_t)repeats, sizeof(long)); + if (!results) { + fprintf(stderr, "calloc failed.\n"); + return 1; + } + + unsigned int seed = (unsigned int)time(NULL); + if (dedup_run(size, max_value, &seed, 0, repeats, results) != 0) { + fprintf(stderr, "dedup_run failed.\n"); + free(results); + return 1; + } + + for (long r = 0; r < repeats; ++r) { + printf("repeat %ld: unique=%ld\n", r + 1, results[r]); + } + + free(results); + return 0; +} diff --git a/lab/vtld/cpu_dedup_pthreads.c b/lab/vtld/cpu_dedup_pthreads.c new file mode 100644 index 0000000..188b32e --- /dev/null +++ b/lab/vtld/cpu_dedup_pthreads.c @@ -0,0 +1,148 @@ +#include +#include +#include +#include +#include + +#include "cpu_utils.h" +#include "util.h" + +typedef struct { + long size; + long max_value; + long repeat_start; + long repeat_end; + long* results; + int* error_flag; + pthread_mutex_t* error_lock; + unsigned int seed; +} worker_ctx_t; + +static void print_usage(const char* prog) { + fprintf( + stderr, + "Usage: %s --size N --repeats N [--max_value N] [--threads N]\n", + prog + ); +} + +static void set_error(worker_ctx_t* ctx) { + pthread_mutex_lock(ctx->error_lock); + *ctx->error_flag = 1; + pthread_mutex_unlock(ctx->error_lock); +} + +static void* worker_main(void* arg) { + worker_ctx_t* ctx = (worker_ctx_t*)arg; + unsigned int seed = ctx->seed; + + if (dedup_run( + ctx->size, + ctx->max_value, + &seed, + ctx->repeat_start, + ctx->repeat_end, + ctx->results + ) != 0) { + fprintf(stderr, "dedup_run failed.\n"); + set_error(ctx); + return NULL; + } + + return NULL; +} + +int main(int argc, char** argv) { + const char* size_s = get_opt_value(argc, argv, "size"); + const char* repeats_s = get_opt_value(argc, argv, "repeats"); + const char* max_value_s = get_opt_value(argc, argv, "max_value"); + const char* threads_s = get_opt_value(argc, argv, "threads"); + + if (!size_s || !repeats_s) { + print_usage(argv[0]); + return 1; + } + + long size = strtol(size_s, NULL, 10); + long repeats = strtol(repeats_s, NULL, 10); + long max_value = max_value_s ? strtol(max_value_s, NULL, 10) : (size * 2); + long threads = 0; + if (threads_s) { + threads = strtol(threads_s, NULL, 10); + } else { + long cpu = sysconf(_SC_NPROCESSORS_ONLN); + threads = cpu > 0 ? cpu : 1; + } + + if (size <= 0 || repeats <= 0 || max_value <= 0 || threads <= 0) { + fprintf(stderr, "Invalid size, repeats, max_value, or threads.\n"); + return 1; + } + + if (threads > repeats) { + threads = repeats; + } + + long* results = calloc((size_t)repeats, sizeof(long)); + if (!results) { + fprintf(stderr, "calloc failed.\n"); + return 1; + } + + pthread_t* tids = malloc((size_t)threads * sizeof(pthread_t)); + worker_ctx_t* ctxs = malloc((size_t)threads * sizeof(worker_ctx_t)); + if (!tids || !ctxs) { + fprintf(stderr, "malloc failed.\n"); + free(results); + free(tids); + free(ctxs); + return 1; + } + + pthread_mutex_t error_lock = PTHREAD_MUTEX_INITIALIZER; + int error_flag = 0; + + long base = repeats / threads; + long rem = repeats % threads; + long cur = 0; + unsigned int seed = (unsigned int)time(NULL); + + for (long i = 0; i < threads; ++i) { + long count = base + (i < rem ? 1 : 0); + ctxs[i].size = size; + ctxs[i].max_value = max_value; + ctxs[i].repeat_start = cur; + ctxs[i].repeat_end = cur + count; + ctxs[i].results = results; + ctxs[i].error_flag = &error_flag; + ctxs[i].error_lock = &error_lock; + ctxs[i].seed = seed ^ (unsigned int)(i * 2654435761u); + if (pthread_create(&tids[i], NULL, worker_main, &ctxs[i]) != 0) { + fprintf(stderr, "pthread_create failed.\n"); + error_flag = 1; + threads = i; + break; + } + cur += count; + } + + for (long i = 0; i < threads; ++i) { + pthread_join(tids[i], NULL); + } + + if (error_flag) { + free(results); + free(tids); + free(ctxs); + return 1; + } + + for (long r = 0; r < repeats; ++r) { + printf("repeat %ld: unique=%ld\n", r + 1, results[r]); + } + + free(results); + free(tids); + free(ctxs); + return 0; +} diff --git a/lab/vtld/cpu_utils.c b/lab/vtld/cpu_utils.c new file mode 100644 index 0000000..4fe0139 --- /dev/null +++ b/lab/vtld/cpu_utils.c @@ -0,0 +1,54 @@ +#include "cpu_utils.h" + +#include + +long dedup_run( + long size, + long max_value, + unsigned int* seed, + long repeat_start, + long repeat_end, + long* results +) { + if (repeat_start < 0 || repeat_end < repeat_start || !results) { + return -1; + } + + if (size <= 0 || max_value <= 0 || !seed) { + return -1; + } + + for (long r = repeat_start; r < repeat_end; ++r) { + int32_t* arr = malloc((size_t)size * sizeof(int32_t)); + if (!arr) { + return -1; + } + int32_t* dedup = malloc((size_t)size * sizeof(int32_t)); + if (!dedup) { + free(arr); + return -1; + } + for (long i = 0; i < size; ++i) { + arr[i] = (int32_t)(rand_r(seed) % max_value); + } + long unique = 0; + for (long i = 0; i < size; ++i) { + int32_t value = arr[i]; + int found = 0; + for (long j = 0; j < unique; ++j) { + if (dedup[j] == value) { + found = 1; + break; + } + } + if (!found) { + dedup[unique++] = value; + } + } + free(arr); + free(dedup); + results[r] = unique; + } + + return 0; +} diff --git a/lab/vtld/cpu_utils.h b/lab/vtld/cpu_utils.h new file mode 100644 index 0000000..c22d59a --- /dev/null +++ b/lab/vtld/cpu_utils.h @@ -0,0 +1,12 @@ +#pragma once + +#include + +long dedup_run( + long size, + long max_value, + unsigned int* seed, + long repeat_start, + long repeat_end, + long* results +); diff --git a/lab/vtld/ema_replace_str.c b/lab/vtld/ema_replace_str.c new file mode 100644 index 0000000..38cab6d --- /dev/null +++ b/lab/vtld/ema_replace_str.c @@ -0,0 +1,238 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ema_utils.h" +#include "util.h" + +static void print_usage(const char* prog) { + fprintf( + stderr, + "Usage: %s --block_size N --file PATH --repeats N --string STR " + "--replace STR [--size N] [--range A-B] [--direct on/off] " + "[--type sequence/random]\n", + prog + ); +} + +int main(int argc, char** argv) { + const char* block_size_s = get_opt_value(argc, argv, "block_size"); + const char* file_s = get_opt_value(argc, argv, "file"); + const char* range_s = get_opt_value(argc, argv, "range"); + const char* direct_s = get_opt_value(argc, argv, "direct"); + const char* type_s = get_opt_value(argc, argv, "type"); + const char* repeats_s = get_opt_value(argc, argv, "repeats"); + const char* string_s = get_opt_value(argc, argv, "string"); + const char* replace_s = get_opt_value(argc, argv, "replace"); + const char* size_s = get_opt_value(argc, argv, "size"); + + if (!block_size_s || !file_s || !repeats_s || !string_s || !replace_s) { + print_usage(argv[0]); + return 1; + } + + uint64_t block_size_u = 0; + uint64_t repeats = 0; + uint64_t gen_size = 0; + if (!parse_u64(block_size_s, &block_size_u) || + !parse_u64(repeats_s, &repeats)) { + fprintf(stderr, "Invalid block_size or repeats.\n"); + return 1; + } + if (size_s && !parse_u64(size_s, &gen_size)) { + fprintf(stderr, "Invalid size.\n"); + return 1; + } + if (block_size_u == 0 || repeats == 0) { + fprintf(stderr, "block_size and repeats must be > 0.\n"); + return 1; + } + + size_t block_size = (size_t)block_size_u; + size_t pattern_len = strlen(string_s); + if (pattern_len == 0 || strlen(replace_s) != pattern_len) { + fprintf(stderr, "string and replace must be non-empty and same length.\n"); + return 1; + } + if (pattern_len > block_size) { + fprintf(stderr, "string length must be <= block_size.\n"); + return 1; + } + + int direct = 0; + if (direct_s) { + if (strcmp(direct_s, "on") == 0) { + direct = 1; + } else if (strcmp(direct_s, "off") != 0) { + fprintf(stderr, "Invalid direct value: use on/off.\n"); + return 1; + } + } + + int random_mode = 0; + if (type_s) { + if (strcmp(type_s, "random") == 0) { + random_mode = 1; + } else if (strcmp(type_s, "sequence") != 0) { + fprintf(stderr, "Invalid type value: use sequence/random.\n"); + return 1; + } + } + + uint64_t range_start = 0; + uint64_t range_end = 0; + if (!parse_range(range_s, &range_start, &range_end)) { + fprintf(stderr, "Invalid range format, expected A-B.\n"); + return 1; + } + + srand((unsigned)time(NULL)); + + if (size_s) { + if (gen_size == 0) { + fprintf(stderr, "size must be > 0.\n"); + return 1; + } + if (!generate_file(file_s, gen_size, block_size, string_s)) { + return 1; + } + } + + if (direct) { + if ((block_size % 512) != 0) { + fprintf(stderr, "block_size must be a multiple of 512 for O_DIRECT.\n"); + return 1; + } + } + + int fd = open_file_direct(file_s, direct); + if (fd < 0) { + perror("open"); + return 1; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + perror("fstat"); + close(fd); + return 1; + } + uint64_t file_size = (uint64_t)st.st_size; + if (file_size == 0) { + fprintf(stderr, "File is empty.\n"); + close(fd); + return 1; + } + + if (range_start == 0 && range_end == 0) { + range_start = 0; + range_end = file_size; + } + if (range_end > file_size || range_start >= range_end) { + fprintf(stderr, "Invalid range bounds.\n"); + close(fd); + return 1; + } + + uint64_t range_len = range_end - range_start; + uint64_t full_blocks = range_len / block_size; + uint64_t tail = range_len % block_size; + if (direct && tail != 0) { + range_end = range_start + (full_blocks * block_size); + range_len = range_end - range_start; + tail = 0; + if (range_len == 0) { + fprintf( + stderr, "Range too small for O_DIRECT with the given block_size.\n" + ); + close(fd); + return 1; + } + fprintf(stderr, "Warning: range truncated to fit O_DIRECT alignment.\n"); + } + + uint64_t blocks = full_blocks + ((tail > 0 && !direct) ? 1 : 0); + if (blocks == 0) { + fprintf(stderr, "No blocks to process.\n"); + close(fd); + return 1; + } + + void* buf = NULL; + size_t alloc_size = block_size; + if (direct) { + size_t align = 4096; + alloc_size = ((block_size + align - 1) / align) * align; + buf = aligned_alloc(align, alloc_size); + if (!buf) { + fprintf(stderr, "aligned_alloc failed.\n"); + close(fd); + return 1; + } + } else { + buf = malloc(block_size); + if (!buf) { + fprintf(stderr, "malloc failed.\n"); + close(fd); + return 1; + } + } + + uint64_t total_replaced = 0; + for (uint64_t i = 0; i < repeats; ++i) { + uint64_t block_index = + random_mode ? (uint64_t)(rand() % (int)blocks) : (i % blocks); + uint64_t offset = range_start + block_index * block_size; + size_t len = block_size; + if (!direct && tail > 0 && block_index == blocks - 1) { + len = (size_t)tail; + } + + if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) { + perror("lseek"); + free(buf); + close(fd); + return 1; + } + ssize_t r = read(fd, buf, len); + if (r < 0) { + perror("read"); + free(buf); + close(fd); + return 1; + } + if (r == 0) { + continue; + } + size_t replaced = + replace_in_buffer((char*)buf, (size_t)r, string_s, replace_s); + if (replaced > 0) { + if (lseek(fd, (off_t)offset, SEEK_SET) == (off_t)-1) { + perror("lseek"); + free(buf); + close(fd); + return 1; + } + ssize_t w = write(fd, buf, (size_t)r); + if (w < 0 || w != r) { + perror("write"); + free(buf); + close(fd); + return 1; + } + total_replaced += replaced; + } + } + + printf("replacements=%" PRIu64 "\n", total_replaced); + + free(buf); + close(fd); + return 0; +} diff --git a/lab/vtld/ema_replace_str_pthreads.c b/lab/vtld/ema_replace_str_pthreads.c new file mode 100644 index 0000000..d19dade --- /dev/null +++ b/lab/vtld/ema_replace_str_pthreads.c @@ -0,0 +1,318 @@ +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "ema_utils.h" +#include "util.h" + +typedef struct { + const char* file_path; + const char* needle; + const char* repl; + uint64_t range_start; + uint64_t range_end; + uint64_t blocks; + size_t block_size; + size_t tail; + int direct; + int random_mode; + uint64_t repeats; + uint64_t start_iter; + uint64_t iter_count; + uint64_t replaced; + unsigned int rng_state; +} worker_ctx; + +static pthread_mutex_t g_total_mu = PTHREAD_MUTEX_INITIALIZER; +static uint64_t g_total_replaced = 0; + +static void print_usage(const char* prog) { + fprintf( + stderr, + "Usage: %s --block_size N --file PATH --repeats N --string STR " + "--replace STR --threads N [--size N] [--range A-B] " + "[--direct on/off] [--type sequence/random]\n", + prog + ); +} + +static void* worker_main(void* arg) { + worker_ctx* ctx = (worker_ctx*)arg; + int fd = open_file_direct(ctx->file_path, ctx->direct); + if (fd < 0) { + perror("open"); + return NULL; + } + + void* buf = NULL; + if (ctx->direct) { + size_t align = 4096; + if (posix_memalign(&buf, align, ctx->block_size) != 0) { + fprintf(stderr, "posix_memalign failed.\n"); + close(fd); + return NULL; + } + } else { + buf = malloc(ctx->block_size); + if (!buf) { + fprintf(stderr, "malloc failed.\n"); + close(fd); + return NULL; + } + } + + for (uint64_t i = 0; i < ctx->iter_count; ++i) { + uint64_t global_iter = ctx->start_iter + i; + uint64_t block_index = 0; + if (ctx->random_mode) { + block_index = (uint64_t)(rand_r(&ctx->rng_state) % (int)ctx->blocks); + } else { + block_index = (global_iter % ctx->blocks); + } + uint64_t offset = ctx->range_start + block_index * ctx->block_size; + size_t len = ctx->block_size; + if (!ctx->direct && ctx->tail > 0 && block_index == ctx->blocks - 1) { + len = ctx->tail; + } + + ssize_t r = pread(fd, buf, len, (off_t)offset); + if (r < 0) { + perror("pread"); + break; + } + if (r == 0) { + continue; + } + size_t replaced = + replace_in_buffer((char*)buf, (size_t)r, ctx->needle, ctx->repl); + if (replaced > 0) { + ssize_t w = pwrite(fd, buf, (size_t)r, (off_t)offset); + if (w < 0 || w != r) { + perror("pwrite"); + break; + } + ctx->replaced += replaced; + } + } + + free(buf); + close(fd); + pthread_mutex_lock(&g_total_mu); + g_total_replaced += ctx->replaced; + pthread_mutex_unlock(&g_total_mu); + return NULL; +} + +int main(int argc, char** argv) { + const char* block_size_s = get_opt_value(argc, argv, "block_size"); + const char* file_s = get_opt_value(argc, argv, "file"); + const char* range_s = get_opt_value(argc, argv, "range"); + const char* direct_s = get_opt_value(argc, argv, "direct"); + const char* type_s = get_opt_value(argc, argv, "type"); + const char* repeats_s = get_opt_value(argc, argv, "repeats"); + const char* string_s = get_opt_value(argc, argv, "string"); + const char* replace_s = get_opt_value(argc, argv, "replace"); + const char* size_s = get_opt_value(argc, argv, "size"); + const char* threads_s = get_opt_value(argc, argv, "threads"); + + if (!block_size_s || !file_s || !repeats_s || !string_s || !replace_s) { + print_usage(argv[0]); + return 1; + } + + uint64_t block_size_u = 0; + uint64_t repeats = 0; + uint64_t gen_size = 0; + uint64_t threads = 0; + if (!parse_u64(block_size_s, &block_size_u) || + !parse_u64(repeats_s, &repeats)) { + fprintf(stderr, "Invalid block_size or repeats.\n"); + return 1; + } + if (threads_s) { + if (!parse_u64(threads_s, &threads)) { + fprintf(stderr, "Invalid threads.\n"); + return 1; + } + } else { + long cpu = sysconf(_SC_NPROCESSORS_ONLN); + threads = cpu > 0 ? (uint64_t)cpu : 1; + } + if (size_s && !parse_u64(size_s, &gen_size)) { + fprintf(stderr, "Invalid size.\n"); + return 1; + } + if (block_size_u == 0 || repeats == 0 || threads == 0) { + fprintf(stderr, "block_size, repeats, and threads must be > 0.\n"); + return 1; + } + + size_t block_size = (size_t)block_size_u; + size_t pattern_len = strlen(string_s); + if (pattern_len == 0 || strlen(replace_s) != pattern_len) { + fprintf(stderr, "string and replace must be non-empty and same length.\n"); + return 1; + } + if (pattern_len > block_size) { + fprintf(stderr, "string length must be <= block_size.\n"); + return 1; + } + + int direct = 0; + if (direct_s) { + if (strcmp(direct_s, "on") == 0) { + direct = 1; + } else if (strcmp(direct_s, "off") != 0) { + fprintf(stderr, "Invalid direct value: use on/off.\n"); + return 1; + } + } + + int random_mode = 0; + if (type_s) { + if (strcmp(type_s, "random") == 0) { + random_mode = 1; + } else if (strcmp(type_s, "sequence") != 0) { + fprintf(stderr, "Invalid type value: use sequence/random.\n"); + return 1; + } + } + + uint64_t range_start = 0; + uint64_t range_end = 0; + if (!parse_range(range_s, &range_start, &range_end)) { + fprintf(stderr, "Invalid range format, expected A-B.\n"); + return 1; + } + + srand((unsigned)time(NULL)); + + if (size_s) { + if (gen_size == 0) { + fprintf(stderr, "size must be > 0.\n"); + return 1; + } + if (!generate_file(file_s, gen_size, block_size, string_s)) { + return 1; + } + } + + if (direct) { + if ((block_size % 512) != 0) { + fprintf(stderr, "block_size must be a multiple of 512 for O_DIRECT.\n"); + return 1; + } + } + + int fd = open_file_direct(file_s, direct); + if (fd < 0) { + perror("open"); + return 1; + } + + struct stat st; + if (fstat(fd, &st) != 0) { + perror("fstat"); + close(fd); + return 1; + } + uint64_t file_size = (uint64_t)st.st_size; + if (file_size == 0) { + fprintf(stderr, "File is empty.\n"); + close(fd); + return 1; + } + + if (range_start == 0 && range_end == 0) { + range_start = 0; + range_end = file_size; + } + if (range_end > file_size || range_start >= range_end) { + fprintf(stderr, "Invalid range bounds.\n"); + close(fd); + return 1; + } + + uint64_t range_len = range_end - range_start; + uint64_t full_blocks = range_len / block_size; + uint64_t tail = range_len % block_size; + if (direct && tail != 0) { + range_end = range_start + (full_blocks * block_size); + range_len = range_end - range_start; + tail = 0; + if (range_len == 0) { + fprintf( + stderr, "Range too small for O_DIRECT with the given block_size.\n" + ); + close(fd); + return 1; + } + fprintf(stderr, "Warning: range truncated to fit O_DIRECT alignment.\n"); + } + + uint64_t blocks = full_blocks + ((tail > 0 && !direct) ? 1 : 0); + if (blocks == 0) { + fprintf(stderr, "No blocks to process.\n"); + close(fd); + return 1; + } + + close(fd); + + pthread_t* tids = malloc(sizeof(pthread_t) * (size_t)threads); + worker_ctx* ctxs = calloc((size_t)threads, sizeof(worker_ctx)); + if (!tids || !ctxs) { + fprintf(stderr, "malloc failed.\n"); + free(tids); + free(ctxs); + return 1; + } + + uint64_t base = repeats / threads; + uint64_t rem = repeats % threads; + uint64_t cur = 0; + for (uint64_t i = 0; i < threads; ++i) { + uint64_t count = base + (i < rem ? 1 : 0); + ctxs[i].file_path = file_s; + ctxs[i].needle = string_s; + ctxs[i].repl = replace_s; + ctxs[i].range_start = range_start; + ctxs[i].range_end = range_end; + ctxs[i].blocks = blocks; + ctxs[i].block_size = block_size; + ctxs[i].tail = (size_t)tail; + ctxs[i].direct = direct; + ctxs[i].random_mode = random_mode; + ctxs[i].repeats = repeats; + ctxs[i].start_iter = cur; + ctxs[i].iter_count = count; + ctxs[i].replaced = 0; + ctxs[i].rng_state = + (unsigned int)(time(NULL) ^ (unsigned int)(i * 2654435761u)); + cur += count; + } + + for (uint64_t i = 0; i < threads; ++i) { + if (pthread_create(&tids[i], NULL, worker_main, &ctxs[i]) != 0) { + fprintf(stderr, "pthread_create failed.\n"); + return 1; + } + } + + for (uint64_t i = 0; i < threads; ++i) { + pthread_join(tids[i], NULL); + } + + printf("replacements=%" PRIu64 "\n", g_total_replaced); + + free(tids); + free(ctxs); + return 0; +} diff --git a/lab/vtld/ema_utils.c b/lab/vtld/ema_utils.c new file mode 100644 index 0000000..3ff1273 --- /dev/null +++ b/lab/vtld/ema_utils.c @@ -0,0 +1,126 @@ +#include "ema_utils.h" + +#include +#include +#include +#include +#include +#include + +int parse_u64(const char* s, uint64_t* out) { + if (!s || !*s) { + return 0; + } + char* end = NULL; + errno = 0; + unsigned long long v = strtoull(s, &end, 10); + if (errno != 0 || !end || *end != '\0') { + return 0; + } + *out = (uint64_t)v; + return 1; +} + +int parse_range(const char* s, uint64_t* start, uint64_t* end) { + if (!s) { + *start = 0; + *end = 0; + return 1; + } + const char* dash = strchr(s, '-'); + if (!dash) { + return 0; + } + char left[32]; + char right[32]; + size_t left_len = (size_t)(dash - s); + size_t right_len = strlen(dash + 1); + if (left_len == 0 || right_len == 0 || left_len >= sizeof(left) || + right_len >= sizeof(right)) { + return 0; + } + memcpy(left, s, left_len); + left[left_len] = '\0'; + memcpy(right, dash + 1, right_len); + right[right_len] = '\0'; + if (!parse_u64(left, start) || !parse_u64(right, end)) { + return 0; + } + return 1; +} + +int open_file_direct(const char* path, int direct) { + int flags = O_RDWR; +#ifdef O_DIRECT + if (direct) { + flags |= O_DIRECT; + } +#else + if (direct) { + fprintf(stderr, "Warning: O_DIRECT is not supported, ignoring.\n"); + } +#endif + return open(path, flags); +} + +int generate_file( + const char* path, uint64_t size, size_t block_size, const char* pattern +) { + int fd = open(path, O_CREAT | O_TRUNC | O_RDWR, 0644); + if (fd < 0) { + perror("open"); + return 0; + } + size_t pat_len = strlen(pattern); + char* buf = malloc(block_size); + if (!buf) { + fprintf(stderr, "malloc failed.\n"); + close(fd); + return 0; + } + uint64_t remaining = size; + while (remaining > 0) { + size_t chunk = (remaining > block_size) ? block_size : (size_t)remaining; + for (size_t i = 0; i < chunk; ++i) { + buf[i] = (char)('a' + (rand() % 26)); + } + if (pat_len > 0 && pat_len <= chunk && (rand() % 3) == 0) { + size_t pos = (size_t)(rand() % (chunk - pat_len + 1)); + memcpy(buf + pos, pattern, pat_len); + } + ssize_t w = write(fd, buf, chunk); + if (w < 0 || (size_t)w != chunk) { + perror("write"); + free(buf); + close(fd); + return 0; + } + remaining -= chunk; + } + free(buf); + if (fsync(fd) != 0) { + perror("fsync"); + close(fd); + return 0; + } + close(fd); + return 1; +} + +size_t replace_in_buffer( + char* buf, size_t len, const char* needle, const char* repl +) { + size_t nlen = strlen(needle); + if (nlen == 0 || nlen > len) { + return 0; + } + size_t count = 0; + for (size_t i = 0; i + nlen <= len; ++i) { + if (memcmp(buf + i, needle, nlen) == 0) { + memcpy(buf + i, repl, nlen); + ++count; + i += nlen - 1; + } + } + return count; +} diff --git a/lab/vtld/ema_utils.h b/lab/vtld/ema_utils.h new file mode 100644 index 0000000..f33af3e --- /dev/null +++ b/lab/vtld/ema_utils.h @@ -0,0 +1,14 @@ +#pragma once + +#include +#include + +int parse_u64(const char* s, uint64_t* out); +int parse_range(const char* s, uint64_t* start, uint64_t* end); +int open_file_direct(const char* path, int direct); +int generate_file( + const char* path, uint64_t size, size_t block_size, const char* pattern +); +size_t replace_in_buffer( + char* buf, size_t len, const char* needle, const char* repl +); diff --git a/lab/vtld/makefile b/lab/vtld/makefile new file mode 100644 index 0000000..986e0b9 --- /dev/null +++ b/lab/vtld/makefile @@ -0,0 +1,19 @@ +CC=gcc +CFLAGS=-O0 + +all: cpu_dedup cpu_dedup_pthreads ema_replace_str ema_replace_str_pthreads + +cpu_dedup: cpu_dedup.c cpu_utils.c + $(CC) $(CFLAGS) -o cpu_dedup cpu_dedup.c cpu_utils.c util.c + +cpu_dedup_pthreads: cpu_dedup_pthreads.c cpu_utils.c + $(CC) $(CFLAGS) -pthread -o cpu_dedup_pthreads cpu_dedup_pthreads.c cpu_utils.c util.c + +ema_replace_str: ema_replace_str.c ema_utils.c + $(CC) $(CFLAGS) -o ema_replace_str ema_replace_str.c ema_utils.c util.c + +ema_replace_str_pthreads: ema_replace_str_pthreads.c ema_utils.c + $(CC) $(CFLAGS) -pthread -o ema_replace_str_pthreads ema_replace_str_pthreads.c ema_utils.c util.c + +clean: + rm -f cpu_dedup cpu_dedup_pthreads ema_replace_str ema_replace_str_pthreads diff --git a/lab/vtld/util.c b/lab/vtld/util.c new file mode 100644 index 0000000..d0839ba --- /dev/null +++ b/lab/vtld/util.c @@ -0,0 +1,21 @@ +#include "util.h" + +const char* get_opt_value(int argc, char** argv, const char* key) { + size_t key_len = strlen(key); + for (int i = 1; i < argc; ++i) { + const char* arg = argv[i]; + while (*arg == '-') { + ++arg; + } + const char* eq = strchr(arg, '='); + if (eq) { + size_t len = (size_t)(eq - arg); + if (len == key_len && strncmp(arg, key, len) == 0) { + return eq + 1; + } + } else if (strcmp(arg, key) == 0 && i + 1 < argc) { + return argv[i + 1]; + } + } + return NULL; +} diff --git a/lab/vtld/util.h b/lab/vtld/util.h new file mode 100644 index 0000000..4efea74 --- /dev/null +++ b/lab/vtld/util.h @@ -0,0 +1,6 @@ +#pragma once + +#include +#include + +const char* get_opt_value(int argc, char** argv, const char* key); diff --git a/lab/vtsh/.vscode/tasks.json b/lab/vtsh/.vscode/tasks.json new file mode 100644 index 0000000..08d9005 --- /dev/null +++ b/lab/vtsh/.vscode/tasks.json @@ -0,0 +1,28 @@ +{ + "tasks": [ + { + "type": "cppbuild", + "label": "C/C++: gcc build active file", + "command": "/usr/bin/gcc", + "args": [ + "-fdiagnostics-color=always", + "-g", + "${file}", + "-o", + "${fileDirname}/${fileBasenameNoExtension}" + ], + "options": { + "cwd": "${fileDirname}" + }, + "problemMatcher": [ + "$gcc" + ], + "group": { + "kind": "build", + "isDefault": true + }, + "detail": "Task generated by Debugger." + } + ], + "version": "2.0.0" +} \ No newline at end of file diff --git a/lab/vtsh/README.md b/lab/vtsh/README.md index 28ba737..21aa211 100644 --- a/lab/vtsh/README.md +++ b/lab/vtsh/README.md @@ -1,5 +1,7 @@ # Базовый трек. Лабораторная работа 1 +Вариант: `proc-fork shell-and` + ## Введение Базовый пользовательский интерфейс операционной системы - это терминал, с его @@ -27,65 +29,6 @@ 3. `;` - последовательное выполнение, 4. `&` - выполнение в фоне. -## Часть 3. Анализ системы и мониторинг - -Разработайте параметризируемую программу-нагрузчик, которая будет однопоточно нагружать подсистему ввода-вывода (IO). Она должна принимать на вход следующие параметры: - -- `rw: read/write` - режим нагрузки: чтение или запись; -- `block_size: ` - размер блока в байтах, с которым производится чтение/запись; -- `block_count: ` - количество блоков; -- `file: ` - имя файла, с которым происходит работа; -- `range: -` - границы в пределах файла, в которые должны - осуществляться запись/чтение, значение по умолчанию, 0-0, означает, что доступен - весь файл; -- `direct: on/off` - открывать файл с опцией O_DIRECT (в обход кэшей ОС) или нет; -- `type: sequence/random` - режим выбора следующего блока для записи/чтения - последовательно или случайно; - -Разработайте комплекс программ-нагрузчиков согласно выданному варианту. -Каждый нагрузчик должен принимать параметр, который определяет -количество повторений для алгоритма, указанного в задании, а также другие -вспомогательные параметры. Варианты программ рассчитаны так, чтобы по-разному -нагружать вычислительную подсистему (CPU) и подсистему ввода-вывода (IO) - это -необходимо учитывать при их реализации. Разработанные программы еобходимо -скомпилировать без дополнительных опций оптимизации компилятора. - -Проведите исследование поведения ОС во время исполнения разработанных -программ-нагрузчиков по следующему плану: - -1. Перед запуском нагрузчика, попробуйте оценить время работы вашей программы - или ее результаты (если по варианту вам досталось измерение чего либо) и - обоснуйте свои предположения. - -2. Запустите программу-нагрузчик и зафиксируйте метрики ее работы с помощью - инструментов для мониторинга и профилирования (см. лекции). Сравните полученные - результаты с ожидаемыми. Объяснените наблюдаемое поведение. Продолжительность - каждого запуска должна занимать достаточное для прекращения переходных - процессов время, по крайней мере, минуту. - -3. Определите количество одновременно запущенных процессов с программой-нагрузчиком, - которое эффективно нагружает все ядра процессора в вашей системе. Как - распределяются показатели времени USER%, SYS%, WAIT%, а также полное время - выполнения нагрузчика, какое количество переключений контекста (вынужденных и - невынужденных) происходит при этом? Подумайте над тем, как вы определяете - эффективность. - -4. Увеличьте количество нагрузчиков вдвое, втрое, вчетверо. Как изменились - исследуемые показатели? Почему? - -5. Объедините программы-нагрузчики в одну, реализованную при помощи потоков - выполнения, чтобы один нагрузчик эффективно нагружал все ядра вашей системы. Как - изменились показатели времени для того же объема вычислений? Запустите одну, - две, три таких программы. Как изменились исследуемые показатели? Почему? - -6. Скомпилируйте программу-нугрузчик с опцией агрессивной оптимизации. Как - изменились исследуемые показатели? На сколько сократилось реальное время - исполнения программы нагрузчика? Почему? - -В процессе защиты вашей работы преподаватель будет просить вас запустить -программу-нагрузчик с различными комбинациями параметров и просить объяснить -результат. - ## Требования к реализации 1. Программа (комплекс программ) должна быть реализован на языке C. @@ -148,70 +91,6 @@ - `shell-bg`: Выполнение команды в фоновом процессе (`&` в bash). - -Нагрузчик вычислительной подсистемы (группа **cpu**): - -- `cpu-factorize`: Разложение числа на простые множители. Задаваемое число - должно являться произведением нескольких больших простых чисел. - -- `cpu-sort`: Любая сортировка массива чисел за N*log(N) и любая за N**2. - Алгоритм должен быть реализован самим студентом, а не взят из стандартной - библиотеки. - -- `cpu-short-path`: Поиск кратчайшего пути в графе. Граф генерируется в момент - запуска или записан в исходный код. - -- `cpu-linreg`: Построение модели линейной регрессии для последовательности случайных чисел. Количество чисел и диапозон значений задаются параметром. - -- `cpu-dedup`: Дедупликация элементов в массиве. Массив генерируется на основе - случайных чисел. Размер массива задаётся параметром. - -- `cpu-mat-mul`: Перемножение матриц. Размер матриц задаётся параметром, а - элементы матриц генерируются на основе случайных чисел. - -- `cpu-calc-md5`: Подсчитать хеш md5 для текста полученного конкатенацией - фрагментов текста выбранных с помощью генератора случайных чисел. - -- `cpu-calc-crc`: Посчитать контрольную сумму crc для текста полученного - конкатенацией фрагментов текста выбранных с помощью генератора случайных чисел. - - -Нагрузчик, работающий с внешней памятью (группа **ema**): - -- `ema-replace-int`: Поиск элемента в массиве чисел во внешней памяти и замена - значения на другое. Файл для поиска значения генерируется в произвольном - формате, размер задаётся через параметр. - -- `ema-replace-str`: Поиск подстроки в тексте во внешней памяти и замена на - другую строку того же размера. Файл для поиска значения генерируется в - произвольном формате, размер задаётся через параметр. - -- `ema-sort-int`: Сортировка массива чисел во внешней памяти. Весь массив не - может быть загружен в память, поэтому сортировка происходит частями. - -- `ema-traverse-graph`: Обход k-регулярного направленного графа, - сериализованного в файл, и выполнение модифицирующей операции над определенной - вершиной графа. Поиск вершины происходит по хранимому в ней значению, возможно с - ограничением глубины поиска. - Формат хранения: вершина (struct Node) определяется её отступом от начала файла (id=index*(sizeof struct Node)), в вершине хранится одно значение типа фиксированного размера и k номеров вершин, в которые из неё можно попасть; в файле вершины хранятся последовательно с выравниванием соответствующим размеру структуры. Тип значения, хранимого в вершине, и значение k подбираются таким образом, чтобы с учетом выравнивания не было неиспользуемой памяти. - При генерации графа случайно задается значение, хранимое в вершине и номера соседних вершин, отдельным параметром генератора задется шанс получения номера соседней вершины больше или меньше номера данной вершины (соседняя вершина раньше или позже). - -- `ema-join-*`: Необходимо реализовать алгоритм equi-join для объединения - таблиц в файлах. На вход подаются два файла, результат их объединения пишется в - новый файл. В первой строке файла содержится количество строчек в файле. В - каждой последующей строчке входного файла содержится два значения, разделенных - пробелами: численный идентификатор и строка без пробелов фиксированного размера - 8 байт. Строка является валидным английским словом. Таблицы необходимо - объединять по идентификатору. - - По варианту выдается стратегия джоина: - - `ema-join-nl`: Nested Loop Join - - `ema-join-hash`: Hash Join Join - - `ema-join-sm`: Sort + Merge Join - - Количество строк входных таблиц включают все комбинации пар значений: 5, 10, 100, 1000, 10000." - - ## Источники - Тесты взяты из репозитория [sergey-v-galtsev/shad-os][shad-os-tests] diff --git a/lab/vtsh/bin/main.c b/lab/vtsh/bin/main.c index fd9b122..691e0ca 100644 --- a/lab/vtsh/bin/main.c +++ b/lab/vtsh/bin/main.c @@ -2,6 +2,6 @@ #include int main() { - printf("%s", vtsh_prompt()); - printf("Hello, world!\n"); + vtsh_run(); + return 0; } diff --git a/lab/vtsh/lib/CMakeLists.txt b/lab/vtsh/lib/CMakeLists.txt index 962968c..e83284c 100644 --- a/lab/vtsh/lib/CMakeLists.txt +++ b/lab/vtsh/lib/CMakeLists.txt @@ -1,7 +1,9 @@ -add_library( - libvtsh - STATIC +add_library(libvtsh STATIC vtsh.c + utils.c + tokenizer.c + parser.c + exec.c ) target_include_directories( diff --git a/lab/vtsh/lib/exec.c b/lab/vtsh/lib/exec.c new file mode 100644 index 0000000..fe588f8 --- /dev/null +++ b/lab/vtsh/lib/exec.c @@ -0,0 +1,160 @@ +#include "exec.h" + +#include +#include +#include +#include +#include +#include +#include +#include + +#include "utils.h" + +static void exec_self(char* const argv[]) { + char self[PATH_MAX]; + ssize_t n = readlink("/proc/self/exe", self, sizeof(self) - 1); + if (n > 0) { + self[n] = '\0'; + execv(self, argv); + } +} + +static void exec_with_fallback(char* const argv[]) { + const char* cmd = argv[0]; + + if (strchr(cmd, '/')) { + execv(cmd, argv); + + if (strcmp(cmd, "./shell") == 0) { + exec_self(argv); + } + + return; + } + + execvp(cmd, argv); + + if (errno == ENOENT) { + char path[PATH_MAX]; + + snprintf(path, sizeof(path), "/bin/%s", cmd); + execv(path, argv); + + snprintf(path, sizeof(path), "/usr/bin/%s", cmd); + execv(path, argv); + + if (strcmp(cmd, "shell") == 0) { + exec_self(argv); + } + } +} + +static int run_builtin(const Cmd* c, bool* handled) { + *handled = true; + + if (strcmp(c->argv[0], "exit") == 0) { + int code = 0; + if (c->argc >= 2) + code = atoi(c->argv[1]); + exit(code); + } + + if (strcmp(c->argv[0], "cd") == 0) { + const char* path = NULL; + if (c->argc >= 2) { + path = c->argv[1]; + } else { + path = getenv("HOME"); + } + + if (!path || chdir(path) != 0) { + dprintf(STDOUT_FILENO, "I/O error\n"); + return 1; + } + return 0; + } + + *handled = false; + return 0; +} + +static int run_one(const Cmd* c) { + if (c->argc == 0) + return 0; + + bool handled = false; + int builtin_status = run_builtin(c, &handled); + if (handled) + return builtin_status; + + long long t0 = nsec_now(); + pid_t pid = fork(); + if (pid < 0) { + dprintf(STDOUT_FILENO, "I/O error\n"); + return 1; + } + + if (pid == 0) { + int saved_stdout = dup(STDOUT_FILENO); + if (saved_stdout < 0) + saved_stdout = STDOUT_FILENO; + + for (int i = 0; i < c->nredirs; i++) { + int fd; + if (c->redirs[i].type == '<') { + fd = open(c->redirs[i].filename, O_RDONLY); + if (fd < 0) { + dprintf(saved_stdout, "I/O error\n"); + _exit(1); + } + if (dup2(fd, STDIN_FILENO) < 0) { + close(fd); + dprintf(saved_stdout, "I/O error\n"); + _exit(1); + } + close(fd); + } else { + fd = open(c->redirs[i].filename, O_WRONLY | O_CREAT | O_TRUNC, 0666); + if (fd < 0) { + dprintf(saved_stdout, "I/O error\n"); + _exit(1); + } + if (dup2(fd, STDOUT_FILENO) < 0) { + close(fd); + dprintf(saved_stdout, "I/O error\n"); + _exit(1); + } + close(fd); + } + } + + exec_with_fallback(c->argv); + + dprintf(STDOUT_FILENO, "Command not found\n"); + _exit(127); + } + + int st = 0; + waitpid(pid, &st, 0); + long long t1 = nsec_now(); + + double elapsed_ms = (double)(t1 - t0) / 1000000.0; + fprintf(stderr, "time_ms=%.3f\n", elapsed_ms); + + if (WIFEXITED(st)) + return WEXITSTATUS(st); + if (WIFSIGNALED(st)) + return 128 + WTERMSIG(st); + return 1; +} + +int run_commands(const CmdVec* cmds) { + int last_status = 0; + for (int i = 0; i < cmds->n; i++) { + if (cmds->v[i].and_after_prev && last_status != 0) + continue; + last_status = run_one(&cmds->v[i]); + } + return last_status; +} diff --git a/lab/vtsh/lib/exec.h b/lab/vtsh/lib/exec.h new file mode 100644 index 0000000..ae21b78 --- /dev/null +++ b/lab/vtsh/lib/exec.h @@ -0,0 +1,4 @@ +#pragma once +#include "parser.h" + +int run_commands(const CmdVec* cmds); diff --git a/lab/vtsh/lib/parser.c b/lab/vtsh/lib/parser.c new file mode 100644 index 0000000..ac6b90a --- /dev/null +++ b/lab/vtsh/lib/parser.c @@ -0,0 +1,161 @@ +#include "parser.h" + +#include +#include + +static void cmd_init(Cmd* c, bool and_after_prev) { + memset(c, 0, sizeof(*c)); + c->and_after_prev = and_after_prev; +} + +static void cmd_free(Cmd* c) { + for (int i = 0; i < c->argc; i++) + free(c->argv[i]); + free(c->argv); + for (int i = 0; i < c->nredirs; i++) + free(c->redirs[i].filename); + free(c->redirs); + memset(c, 0, sizeof(*c)); +} + +static void cmd_add_arg(Cmd* c, char* s_owned) { + if (c->argc + 1 >= c->argv_cap) { + c->argv_cap = c->argv_cap ? c->argv_cap * 2 : 8; + c->argv = (char**)xrealloc(c->argv, (size_t)c->argv_cap * sizeof(char*)); + } + c->argv[c->argc++] = s_owned; + c->argv[c->argc] = NULL; +} + +static void cmd_add_redir(Cmd* c, char type, char* filename_owned) { + if (c->nredirs == c->redir_cap) { + c->redir_cap = c->redir_cap ? c->redir_cap * 2 : 4; + c->redirs = + (Redir*)xrealloc(c->redirs, (size_t)c->redir_cap * sizeof(Redir)); + } + c->redirs[c->nredirs].type = type; + c->redirs[c->nredirs].filename = filename_owned; + c->nredirs++; +} + +static void cv_push(CmdVec* cv, Cmd* move_from) { + if (cv->n == cv->cap) { + cv->cap = cv->cap ? cv->cap * 2 : 8; + cv->v = (Cmd*)xrealloc(cv->v, (size_t)cv->cap * sizeof(Cmd)); + } + cv->v[cv->n++] = *move_from; + memset(move_from, 0, sizeof(*move_from)); +} + +void cv_free(CmdVec* cv) { + for (int i = 0; i < cv->n; i++) + cmd_free(&cv->v[i]); + free(cv->v); + cv->v = NULL; + cv->n = cv->cap = 0; +} + +static bool is_bad_filename_token(const char* t) { + if (!t || !*t) + return true; + if (strcmp(t, "&&") == 0) + return true; + if (t[0] == '<' || t[0] == '>') + return true; + return false; +} + +bool parse_tokens(const StrVec* toks, CmdVec* cmds) { + cmds->v = NULL; + cmds->n = cmds->cap = 0; + + Cmd cur; + bool cur_inited = false; + bool next_and = false; + + for (int i = 0; i < toks->n; i++) { + const char* tok = toks->v[i]; + + if (!cur_inited) { + cmd_init(&cur, next_and); + cur_inited = true; + next_and = false; + } + + if (strcmp(tok, "&&") == 0) { + if ((cur.argc == 0 && cur.nredirs == 0) || cur.argc == 0) { + cmd_free(&cur); + return false; + } + cv_push(cmds, &cur); + cur_inited = false; + next_and = true; + continue; + } + + if (tok[0] == '>' || tok[0] == '<') { + char type = tok[0]; + if (type == '>' && tok[1] == '>') { + cmd_free(&cur); + return false; + } + + char* fname = NULL; + if (tok[1] == '\0') { + if (i + 1 >= toks->n) { + cmd_free(&cur); + return false; + } + const char* nxt = toks->v[i + 1]; + if (is_bad_filename_token(nxt)) { + cmd_free(&cur); + return false; + } + fname = xstrdup(nxt); + i++; + } else { + fname = xstrdup(tok + 1); + if (!*fname) { + free(fname); + cmd_free(&cur); + return false; + } + } + + if (type == '<') { + if (cur.has_in) { + free(fname); + cmd_free(&cur); + return false; + } + cur.has_in = true; + } else { + if (cur.has_out) { + free(fname); + cmd_free(&cur); + return false; + } + cur.has_out = true; + } + + cmd_add_redir(&cur, type, fname); + continue; + } + + cmd_add_arg(&cur, xstrdup(tok)); + } + + if (cur_inited) { + if (cur.argc == 0 && cur.nredirs == 0) { + cmd_free(&cur); + } else { + if (cur.argc == 0) { + cmd_free(&cur); + return false; + } + cv_push(cmds, &cur); + } + } + + return true; +} diff --git a/lab/vtsh/lib/parser.h b/lab/vtsh/lib/parser.h new file mode 100644 index 0000000..417cb22 --- /dev/null +++ b/lab/vtsh/lib/parser.h @@ -0,0 +1,32 @@ +#pragma once +#include + +#include "utils.h" + +typedef struct { + char type; // '<' or '>' + char* filename; // owned +} Redir; + +typedef struct { + char** argv; // owned, NULL-terminated + int argc; + int argv_cap; + + Redir* redirs; + int nredirs; + int redir_cap; + + bool and_after_prev; + bool has_in; + bool has_out; +} Cmd; + +typedef struct { + Cmd* v; + int n; + int cap; +} CmdVec; + +void cv_free(CmdVec* cv); +bool parse_tokens(const StrVec* toks, CmdVec* cmds); diff --git a/lab/vtsh/lib/tokenizer.c b/lab/vtsh/lib/tokenizer.c new file mode 100644 index 0000000..ced05b7 --- /dev/null +++ b/lab/vtsh/lib/tokenizer.c @@ -0,0 +1,63 @@ +#include "tokenizer.h" + +#include +#include +#include +#include + +static void split_by_andand(StrVec* out, char* tok_owned) { + const char* s = tok_owned; + size_t len = strlen(s); + + bool has = false; + for (size_t i = 0; i + 1 < len; i++) { + if (s[i] == '&' && s[i + 1] == '&') { + has = true; + break; + } + } + if (!has) { + sv_push(out, tok_owned); + return; + } + + size_t i = 0; + while (i < len) { + size_t j = i; + while (j + 1 < len && !(s[j] == '&' && s[j + 1] == '&')) + j++; + if (j > i) + sv_push(out, xstrndup(s + i, j - i)); + if (j + 1 < len && s[j] == '&' && s[j + 1] == '&') { + sv_push(out, xstrdup("&&")); + i = j + 2; + } else { + i = j + 1; + } + } + free(tok_owned); +} + +StrVec tokenize_line(const char* line) { + StrVec raw = {0}; + StrVec out = {0}; + + const char* p = line; + while (*p) { + while (*p && isspace((unsigned char)*p)) + p++; + if (!*p) + break; + const char* start = p; + while (*p && !isspace((unsigned char)*p)) + p++; + sv_push(&raw, xstrndup(start, (size_t)(p - start))); + } + + for (int i = 0; i < raw.n; i++) { + split_by_andand(&out, raw.v[i]); + raw.v[i] = NULL; + } + free(raw.v); + return out; +} diff --git a/lab/vtsh/lib/tokenizer.h b/lab/vtsh/lib/tokenizer.h new file mode 100644 index 0000000..8fd2d98 --- /dev/null +++ b/lab/vtsh/lib/tokenizer.h @@ -0,0 +1,4 @@ +#pragma once +#include "utils.h" + +StrVec tokenize_line(const char* line); diff --git a/lab/vtsh/lib/utils.c b/lab/vtsh/lib/utils.c new file mode 100644 index 0000000..e3fb177 --- /dev/null +++ b/lab/vtsh/lib/utils.c @@ -0,0 +1,52 @@ +#include "utils.h" + +#include +#include +#include +#include + +void* xrealloc(void* p, size_t n) { + void* q = realloc(p, n); + if (!q) { + perror("realloc"); + exit(1); + } + return q; +} + +char* xstrndup(const char* s, size_t n) { + char* p = (char*)malloc(n + 1); + if (!p) { + perror("malloc"); + exit(1); + } + memcpy(p, s, n); + p[n] = '\0'; + return p; +} + +char* xstrdup(const char* s) { + return xstrndup(s, strlen(s)); +} + +void sv_push(StrVec* sv, char* s_owned) { + if (sv->n == sv->cap) { + sv->cap = sv->cap ? sv->cap * 2 : 16; + sv->v = (char**)xrealloc(sv->v, (size_t)sv->cap * sizeof(char*)); + } + sv->v[sv->n++] = s_owned; +} + +void sv_free(StrVec* sv) { + for (int i = 0; i < sv->n; i++) + free(sv->v[i]); + free(sv->v); + sv->v = NULL; + sv->n = sv->cap = 0; +} + +long long nsec_now(void) { + struct timespec ts; + clock_gettime(CLOCK_MONOTONIC, &ts); + return (long long)ts.tv_sec * 1000000000LL + (long long)ts.tv_nsec; +} diff --git a/lab/vtsh/lib/utils.h b/lab/vtsh/lib/utils.h new file mode 100644 index 0000000..c5b2dfb --- /dev/null +++ b/lab/vtsh/lib/utils.h @@ -0,0 +1,17 @@ +#pragma once +#include + +typedef struct { + char** v; + int n; + int cap; +} StrVec; + +void* xrealloc(void* p, size_t n); +char* xstrndup(const char* s, size_t n); +char* xstrdup(const char* s); + +void sv_push(StrVec* sv, char* s_owned); +void sv_free(StrVec* sv); + +long long nsec_now(void); diff --git a/lab/vtsh/lib/vtsh.c b/lab/vtsh/lib/vtsh.c index 8c0cb60..4661049 100644 --- a/lab/vtsh/lib/vtsh.c +++ b/lab/vtsh/lib/vtsh.c @@ -1,5 +1,56 @@ #include "vtsh.h" -const char* vtsh_prompt() { +#include +#include +#include + +#include "exec.h" +#include "parser.h" +#include "tokenizer.h" + +const char* vtsh_prompt(void) { return "vtsh> "; } + +void vtsh_run(void) { + setvbuf(stdin, NULL, _IONBF, 0); + setvbuf(stdout, NULL, _IONBF, 0); + + char* line = NULL; + size_t cap = 0; + + while (1) { + fputs(vtsh_prompt(), stdout); + fflush(stdout); + + ssize_t n = getline(&line, &cap, stdin); + if (n < 0) + break; + + while (n > 0 && (line[n - 1] == '\n' || line[n - 1] == '\r')) { + line[n - 1] = '\0'; + n--; + } + + StrVec toks = tokenize_line(line); + if (toks.n == 0) { + sv_free(&toks); + continue; + } + + CmdVec cmds = {0}; + if (!parse_tokens(&toks, &cmds)) { + dprintf(STDOUT_FILENO, "Syntax error\n"); + sv_free(&toks); + cv_free(&cmds); + continue; + } + + (void)run_commands(&cmds); + + sv_free(&toks); + cv_free(&cmds); + } + + free(line); +} diff --git a/lab/vtsh/lib/vtsh.h b/lab/vtsh/lib/vtsh.h index 919515a..f35e297 100644 --- a/lab/vtsh/lib/vtsh.h +++ b/lab/vtsh/lib/vtsh.h @@ -1,3 +1,4 @@ #pragma once -const char* vtsh_prompt(); +const char* vtsh_prompt(void); +void vtsh_run(void); diff --git a/lab/vtsh/test/test_redirection.py b/lab/vtsh/test/test_redirection.py index cb14495..9196eaa 100644 --- a/lab/vtsh/test/test_redirection.py +++ b/lab/vtsh/test/test_redirection.py @@ -2,7 +2,7 @@ import unittest from base_test import BaseShellTest -REQUIRED_REDIRECTION_FUNCTIONALITY = False +REQUIRED_REDIRECTION_FUNCTIONALITY = True @unittest.skipIf(not REQUIRED_REDIRECTION_FUNCTIONALITY, ("Redirection functionality is not required in the task. "