Merge pull request #1 from zinchenko291/lab-1

БЛР1 P3312 Зинченко Иван Николаевич
This commit is contained in:
Ivan Zinchenko 2026-02-13 22:02:48 +03:00 committed by GitHub
commit 2636aef752
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
33 changed files with 1785 additions and 212 deletions

View File

@ -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

View File

@ -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

View File

@ -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

16
lab/vtld/.clang-format Normal file
View File

@ -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

5
lab/vtld/.gitignore vendored Normal file
View File

@ -0,0 +1,5 @@
cpu_dedup
cpu_dedup_pthreads
ema_replace_str
ema_replace_str_pthreads
*.bin

5
lab/vtld/.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,5 @@
{
"files.associations": {
"errno.h": "c"
}
}

165
lab/vtld/README.md Normal file
View File

@ -0,0 +1,165 @@
# Базовый трек. Лабораторная работа 1
Вариант: `cpu-dedup ema-replace-str`
## Часть 3. Анализ системы и мониторинг
Разработайте параметризируемую программу-нагрузчик, которая будет однопоточно нагружать подсистему ввода-вывода (IO). Она должна принимать на вход следующие параметры:
- `rw: read/write` - режим нагрузки: чтение или запись;
- `block_size: <number>` - размер блока в байтах, с которым производится чтение/запись;
- `block_count: <number>` - количество блоков;
- `file: <path string>` - имя файла, с которым происходит работа;
- `range: <number>-<number>` - границы в пределах файла, в которые должны
осуществляться запись/чтение, значение по умолчанию, 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."

51
lab/vtld/cpu_dedup.c Normal file
View File

@ -0,0 +1,51 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
#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;
}

View File

@ -0,0 +1,148 @@
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <time.h>
#include <unistd.h>
#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;
}

54
lab/vtld/cpu_utils.c Normal file
View File

@ -0,0 +1,54 @@
#include "cpu_utils.h"
#include <stdlib.h>
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;
}

12
lab/vtld/cpu_utils.h Normal file
View File

@ -0,0 +1,12 @@
#pragma once
#include <stdint.h>
long dedup_run(
long size,
long max_value,
unsigned int* seed,
long repeat_start,
long repeat_end,
long* results
);

238
lab/vtld/ema_replace_str.c Normal file
View File

@ -0,0 +1,238 @@
#include <fcntl.h>
#include <inttypes.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#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;
}

View File

@ -0,0 +1,318 @@
#include <fcntl.h>
#include <inttypes.h>
#include <pthread.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#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;
}

126
lab/vtld/ema_utils.c Normal file
View File

@ -0,0 +1,126 @@
#include "ema_utils.h"
#include <errno.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
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;
}

14
lab/vtld/ema_utils.h Normal file
View File

@ -0,0 +1,14 @@
#pragma once
#include <stddef.h>
#include <stdint.h>
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
);

19
lab/vtld/makefile Normal file
View File

@ -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

21
lab/vtld/util.c Normal file
View File

@ -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;
}

6
lab/vtld/util.h Normal file
View File

@ -0,0 +1,6 @@
#pragma once
#include <stddef.h>
#include <string.h>
const char* get_opt_value(int argc, char** argv, const char* key);

28
lab/vtsh/.vscode/tasks.json vendored Normal file
View File

@ -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"
}

View File

@ -1,5 +1,7 @@
# Базовый трек. Лабораторная работа 1
Вариант: `proc-fork shell-and`
## Введение
Базовый пользовательский интерфейс операционной системы - это терминал, с его
@ -27,65 +29,6 @@
3. `;` - последовательное выполнение,
4. `&` - выполнение в фоне.
## Часть 3. Анализ системы и мониторинг
Разработайте параметризируемую программу-нагрузчик, которая будет однопоточно нагружать подсистему ввода-вывода (IO). Она должна принимать на вход следующие параметры:
- `rw: read/write` - режим нагрузки: чтение или запись;
- `block_size: <number>` - размер блока в байтах, с которым производится чтение/запись;
- `block_count: <number>` - количество блоков;
- `file: <path string>` - имя файла, с которым происходит работа;
- `range: <number>-<number>` - границы в пределах файла, в которые должны
осуществляться запись/чтение, значение по умолчанию, 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]

View File

@ -2,6 +2,6 @@
#include <vtsh.h>
int main() {
printf("%s", vtsh_prompt());
printf("Hello, world!\n");
vtsh_run();
return 0;
}

View File

@ -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(

160
lab/vtsh/lib/exec.c Normal file
View File

@ -0,0 +1,160 @@
#include "exec.h"
#include <errno.h>
#include <fcntl.h>
#include <limits.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <sys/wait.h>
#include <unistd.h>
#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;
}

4
lab/vtsh/lib/exec.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "parser.h"
int run_commands(const CmdVec* cmds);

161
lab/vtsh/lib/parser.c Normal file
View File

@ -0,0 +1,161 @@
#include "parser.h"
#include <stdlib.h>
#include <string.h>
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;
}

32
lab/vtsh/lib/parser.h Normal file
View File

@ -0,0 +1,32 @@
#pragma once
#include <stdbool.h>
#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);

63
lab/vtsh/lib/tokenizer.c Normal file
View File

@ -0,0 +1,63 @@
#include "tokenizer.h"
#include <ctype.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
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;
}

4
lab/vtsh/lib/tokenizer.h Normal file
View File

@ -0,0 +1,4 @@
#pragma once
#include "utils.h"
StrVec tokenize_line(const char* line);

52
lab/vtsh/lib/utils.c Normal file
View File

@ -0,0 +1,52 @@
#include "utils.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <time.h>
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;
}

17
lab/vtsh/lib/utils.h Normal file
View File

@ -0,0 +1,17 @@
#pragma once
#include <stddef.h>
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);

View File

@ -1,5 +1,56 @@
#include "vtsh.h"
const char* vtsh_prompt() {
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#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);
}

View File

@ -1,3 +1,4 @@
#pragma once
const char* vtsh_prompt();
const char* vtsh_prompt(void);
void vtsh_run(void);

View File

@ -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. "