mirror of
https://github.com/zinchenko291/os-course.git
synced 2026-09-12 19:59:56 +05:00
Merge pull request #1 from zinchenko291/lab-1
БЛР1 P3312 Зинченко Иван Николаевич
This commit is contained in:
commit
2636aef752
25
.github/workflows/vtkm.yml
vendored
25
.github/workflows/vtkm.yml
vendored
@ -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
|
||||
54
.github/workflows/vtpc.yml
vendored
54
.github/workflows/vtpc.yml
vendored
@ -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
|
||||
4
.github/workflows/vtsh.yml
vendored
4
.github/workflows/vtsh.yml
vendored
@ -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
16
lab/vtld/.clang-format
Normal 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
5
lab/vtld/.gitignore
vendored
Normal 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
5
lab/vtld/.vscode/settings.json
vendored
Normal file
@ -0,0 +1,5 @@
|
||||
{
|
||||
"files.associations": {
|
||||
"errno.h": "c"
|
||||
}
|
||||
}
|
||||
165
lab/vtld/README.md
Normal file
165
lab/vtld/README.md
Normal 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
51
lab/vtld/cpu_dedup.c
Normal 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;
|
||||
}
|
||||
148
lab/vtld/cpu_dedup_pthreads.c
Normal file
148
lab/vtld/cpu_dedup_pthreads.c
Normal 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
54
lab/vtld/cpu_utils.c
Normal 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
12
lab/vtld/cpu_utils.h
Normal 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
238
lab/vtld/ema_replace_str.c
Normal 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;
|
||||
}
|
||||
318
lab/vtld/ema_replace_str_pthreads.c
Normal file
318
lab/vtld/ema_replace_str_pthreads.c
Normal 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
126
lab/vtld/ema_utils.c
Normal 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
14
lab/vtld/ema_utils.h
Normal 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
19
lab/vtld/makefile
Normal 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
21
lab/vtld/util.c
Normal 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
6
lab/vtld/util.h
Normal 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
28
lab/vtsh/.vscode/tasks.json
vendored
Normal 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"
|
||||
}
|
||||
@ -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]
|
||||
|
||||
@ -2,6 +2,6 @@
|
||||
#include <vtsh.h>
|
||||
|
||||
int main() {
|
||||
printf("%s", vtsh_prompt());
|
||||
printf("Hello, world!\n");
|
||||
vtsh_run();
|
||||
return 0;
|
||||
}
|
||||
|
||||
@ -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
160
lab/vtsh/lib/exec.c
Normal 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
4
lab/vtsh/lib/exec.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "parser.h"
|
||||
|
||||
int run_commands(const CmdVec* cmds);
|
||||
161
lab/vtsh/lib/parser.c
Normal file
161
lab/vtsh/lib/parser.c
Normal 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
32
lab/vtsh/lib/parser.h
Normal 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
63
lab/vtsh/lib/tokenizer.c
Normal 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
4
lab/vtsh/lib/tokenizer.h
Normal file
@ -0,0 +1,4 @@
|
||||
#pragma once
|
||||
#include "utils.h"
|
||||
|
||||
StrVec tokenize_line(const char* line);
|
||||
52
lab/vtsh/lib/utils.c
Normal file
52
lab/vtsh/lib/utils.c
Normal 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
17
lab/vtsh/lib/utils.h
Normal 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);
|
||||
@ -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);
|
||||
}
|
||||
|
||||
@ -1,3 +1,4 @@
|
||||
#pragma once
|
||||
|
||||
const char* vtsh_prompt();
|
||||
const char* vtsh_prompt(void);
|
||||
void vtsh_run(void);
|
||||
|
||||
@ -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. "
|
||||
|
||||
Loading…
Reference in New Issue
Block a user