Compare commits

...

4 Commits

Author SHA1 Message Date
Yury Kurlykov 377e68bf21
Update 12th lab 2020-06-18 14:36:54 +10:00
Yury Kurlykov bd10ce553a
Add 12th lab 2020-06-18 13:19:27 +10:00
Yury Kurlykov 1243883cec
Update 11th lab 2020-06-18 12:24:32 +10:00
Yury Kurlykov be91d8d06c
Add 8th lab 2020-06-18 11:37:04 +10:00
15 changed files with 1122 additions and 11 deletions

View File

@ -19,5 +19,7 @@ define_lab(lab4)
define_lab(lab5) define_lab(lab5)
define_lab(lab6) define_lab(lab6)
define_lab(lab7) define_lab(lab7)
define_lab(lab8)
define_lab(lab9) define_lab(lab9)
define_lab(lab10) define_lab(lab10)
define_lab(lab12)

View File

@ -8,9 +8,11 @@
- [Лабораторная работа 5](lab5/README.md) - [Лабораторная работа 5](lab5/README.md)
- [Лабораторная работа 6](lab6/README.md) - [Лабораторная работа 6](lab6/README.md)
- [Лабораторная работа 7](lab7/README.md) - [Лабораторная работа 7](lab7/README.md)
- [Лабораторная работа 8](lab8/README.md)
- [Лабораторная работа 9](lab9/README.md) - [Лабораторная работа 9](lab9/README.md)
- [Лабораторная работа 10](lab10/README.md) - [Лабораторная работа 10](lab10/README.md)
- [Лабораторная работа 11](lab11/README.md) - [Лабораторная работа 11](lab11/README.md)
- [Лабораторная работа 12](lab12/README.md)
## Запуск ## Запуск

View File

@ -2,16 +2,49 @@
## Задание 1 ## Задание 1
> vector-deadlock.c, main-common.c и др.:
> - Выполнить ./vector-deadlock -n 2 -l 1 -v
> которая инициирует 2 потока (-n 2) и каждый из них осуществляет одно сложение (-l 1) с опцией (-v).
> Объяснить результат. Меняется ли он от вызова к вызову?
> - Добавить флаг -d и изменить количество циклов -l .
> Всегда ли возникает состояние взаимной блокировки потоков (deadlock)?
> - Теперь меняем число потоков -n. Есть ли такое число потоков, при котором блокировка не возникает?
Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции. Программа печатает в каждом потоке начальные данные перед `vector_add` и результат выполнения данной функции.
Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой Результат может меняться от вызова к вызову, так как весь worker не покрыт мьютексами, но на практике на такой небольшой
программе это маловероятно достижимо. программе это маловероятно достижимо.
Обычно программа возвращает следующее:
```text
->add(0, 1)
<-add(0, 1)
->add(0, 1)
<-add(0, 1)
```
Спустя примерно 30 запусков, удалось получить другой результат:
```text
->add(0, 1)
->add(0, 1)
<-add(0, 1)
<-add(0, 1)
```
При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами. При добавлении `-d` взаимная блокировка возникает не всегда, а только при попадании переключения потоков между мьютексами.
Удалось достичь стабильный дедлок при запуске с ключами `-t -n 2 -l 100 -d` (без `-v`)
В случае, если число потоков = 1, взаимной блокировки не возникнет. В случае, если число потоков = 1, взаимной блокировки не возникнет.
## Задание 2 ## Задание 2
> vector-global-order.c:
> - За счет чего программа избегает блокировок?
> - Для чего предусмотрен специальный случай в vector add(), когда исходный и результирующий вектор совпадают?
> - Флаги: -t -n 2 -l 100000 -d. Как меняется время в зависимости от числа циклов и числа потоков?
> - Что происходит, когда включается ключ -p (при сложении различных векторов и одного и того же)?
Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок Программа избегает мёртвой блокировки за счёт упорядочивания по адресам, что позволяет постоянно сохранять порядок
блокировки. блокировки.
@ -23,43 +56,89 @@
## Задание 3 ## Задание 3
> vector-try-wait.c:
> - Нужен ли первый вызов pthread_mutex_trylock()?
> - Как меняется число повторных попыток, когда растет число потоков?
Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока. Вызовы pthread_mutex_trylock необходимы для создания порядка блокировки, для того чтобы избежать дедлока.
```text
$ ./vector-try-wait -t -n 2 -l 100 -d
Retries: 0
Time: 0.00 seconds
$ ./vector-try-wait -t -n 4 -l 100 -d
Retries: 847
Time: 0.00 seconds
```
С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками С увеличением числа потоков происходит рост повторных попыток, что является логичным, так как переключение между потоками
становится более частым. становится более частым.
При использовании `-p` повторных попыток не возникает.
## Задание 4 ## Задание 4
Данный подход защищает уязвимое место дедлока, созданием глобального мьютекса, но при этом не даёт различным векторам > vector-avoid-hold-and-wait.c:
> - Сравнить с другими подходами.
> - Как меняется производительность в зависимости от наличия флага -p?
Данный подход защищает уязвимое место дедлока созданием глобального мьютекса, но при этом не даёт различным векторам
выполняться параллельно. выполняться параллельно.
При использовании `-p` время уменьшается. При использовании `-p` время уменьшается.
## Задание 5 ## Задание 5
Указав memory, мы дожидаемся завершения всех операцией с памятью, что своего рода позволяет заменить мьютексы. > vector-nolock.c:
> - Сравнить семантику и производительность с другими вариантами при работе с двумя одинаковыми векторами и в случае,
> когда каждый поток работает на своем векторе -p.
Указав memory, программа дожидается завершения всех операцией с памятью, что позволяет заменить мьютексы в данном случае.
(https://ru.wikipedia.org/wiki/GCC_Inline_Assembly) (https://ru.wikipedia.org/wiki/GCC_Inline_Assembly)
Фактически в программе производится атомарное сложение, и в стандарте C11 для этих целей есть особые типы:
(https://en.cppreference.com/w/c/language/atomic)
Также в C11 ввели поддержку потоков в стандартную библиотеку, что позволяет писать кроссплатформенный код:
(https://en.cppreference.com/w/c/thread)
Но атомарные операции очень дорого стоят.
Сравним время выполнения следующих команд: Сравним время выполнения следующих команд:
```text ```text
./vector-nolock -t -n 2 -l 1000000 -d = 4.08 $ ./vector-nolock -t -n 2 -l 1000000 -d
./vector-nolock -t -n 2 -l 1000000 -d -p = 0.65 Time: 7.20 seconds
$ ./vector-nolock -t -n 2 -l 1000000 -d -p
Time: 1.07 seconds
``` ```
```text ```text
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d = 2.98 $ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d
./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p = 0.45 Time: 4.46 seconds
$ ./vector-avoid-hold-and-wait -t -n 2 -l 1000000 -d -p
Time: 0.40 seconds
``` ```
```text ```text
./vector-try-wait -t -n 2 -l 1000000 -d = 1.30 $ ./vector-try-wait -t -n 2 -l 1000000 -d
./vector-try-wait -t -n 2 -l 1000000 -d -p = 0.18 Retries: 5979033
Time: 2.55 seconds
$ ./vector-try-wait -t -n 2 -l 1000000 -d -p
Retries: 0
Time: 0.25 seconds
``` ```
```text ```text
./vector-global-order -t -n 2 -l 1000000 -d = 0.69 $ ./vector-global-order -t -n 2 -l 1000000 -d
./vector-global-order -t -n 2 -l 1000000 -d -p = 0.19 Time: 1.23 seconds
$ ./vector-global-order -t -n 2 -l 1000000 -d -p
Time: 0.26 seconds
``` ```
Таким образом видно, что vector-nolock работает медленнее других в любом случае. Таким образом видно, что vector-nolock работает медленнее других в любом случае.

10
lab12/.execme Executable file
View File

@ -0,0 +1,10 @@
#!/usr/bin/env bash
set -euo pipefail
IFS=$'\n\t'
pushd "$1" > /dev/null
./lab12_cache.c_run > ./data && lscpu && cat /proc/cpuinfo && python2 ./graph_data.py
popd > /dev/null

36
lab12/CMakeLists.txt Normal file
View File

@ -0,0 +1,36 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_C_STANDARD 11)
# Lab name
set(LAB_NAME "lab12")
# Lab tasks
list(APPEND SOURCE_FILES
cache.c
)
list(APPEND NON_COMPILABLE_SRC
.execme
graph_data.py
thinkplot.py
)
### Here goes the template
project("${LAB_NAME}" C)
add_custom_target("${LAB_NAME}")
foreach (file IN LISTS SOURCE_FILES)
add_executable("${LAB_NAME}_${file}_run" "${file}")
add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run")
endforeach ()
foreach (file IN LISTS NON_COMPILABLE_SRC)
add_custom_command(
TARGET "${LAB_NAME}" POST_BUILD
DEPENDS "${file}"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/${file}"
"${CMAKE_CURRENT_BINARY_DIR}/${file}"
)
endforeach ()

56
lab12/README.md Normal file
View File

@ -0,0 +1,56 @@
# Лабораторная работа №12
> Проанализируйте cache.c и с ее использованием исследуйте параметры кэша на вашем компьютере. Для этого
> 1. постройте графики времени доступа как функции длины массива, шага выборки и размера буфера.
> 2. на их основе сформулируйте обоснованные гипотезы о размере кэша, размере блока, наличию кэша более высокого уровня.
> 3. сравните свои оценки с реальными значениями, полученными через вызов системных функций или из технического описания вашего компьютера.
График:
![](g1.png)
Как видно из графика, стремительный рост access time происходит на 2^22 B, что примерно равно 4 мегабайтам.
Из этого можно предположить, что размер кэша -- 4Мб. На размере блока выше 64 байт происходит увеличение access time,
что может быть связано с тем, что физический размер блока -- 64 байта. Также наблюдаются ускорения при размере 2^22, что
может говорить о существовании некоторых кэша размером в 4 Мб.
Видны изменения во времени на 2^19, что равно 512 Кб. Скорее всего, это L2-кэш (исходя из средних размеров кэшей
на современных процессорах).
Вывод `cat /proc/cpuinfo`:
```text
...
cache size : 3072 KB
bugs : cpu_meltdown spectre_v1 spectre_v2 spec_store_bypass l1tf mds swapgs itlb_multihit srbds
bogomips : 3792.26
clflush size : 64
cache_alignment : 64
address sizes : 39 bits physical, 48 bits virtual
...
```
Вывод `lscpu`:
```text
...
L1d cache: 64 KiB
L1i cache: 64 KiB
L2 cache: 512 KiB
L3 cache: 3 MiB
Vulnerability Itlb multihit: KVM: Mitigation: Split huge pages
Vulnerability L1tf: Mitigation; PTE Inversion; VMX conditional cache flushes, SMT vulnerable
Vulnerability Mds: Mitigation; Clear CPU buffers; SMT vulnerable
Vulnerability Meltdown: Mitigation; PTI
Vulnerability Spec store bypass: Mitigation; Speculative Store Bypass disabled via prctl and seccomp
Vulnerability Spectre v1: Mitigation; usercopy/swapgs barriers and __user pointer sanitization
Vulnerability Spectre v2: Mitigation; Full generic retpoline, IBPB conditional, IBRS_FW, STIBP conditional, RSB filling
Vulnerability Srbds: Mitigation; Microcode
Vulnerability Tsx async abort: Not affected
...
```
Исходя из этих данных, можно предположить, что в связи с патчами для устранения уязвимостей процессора
(Spectre, Meltdown, L1TF и прочие) график может не вполне корректно отражать реальное положение дел.
Но выводы оказались достаточно приближены к действительности: мы видим два L1-кэша размера 64 Кб (не видно
на графике, т.к. 2^16 Б меньше левой границы графика), L2-кэш размера 512 Кб (2^19 Б) и L3-кэш размера 3 Мб
(~2^(21.6) Б).

72
lab12/cache.c Normal file
View File

@ -0,0 +1,72 @@
/******************************************************************
* CACHE project *
* *
* Using this program, on as many different kinds of computers as *
* possible, investigate these cache parameters: *
* -- total cache size *
* -- cache width *
* -- cache replacement policy *
******************************************************************/
/* I got this program from Brian Harvey, who teaches CS61C at
Berkeley. He didn't put a copyright on it, but he should
at least get credit for it. Thanks, Brian! */
#include <stdio.h>
#include <unistd.h>
#include <sys/times.h>
#include <sys/types.h>
#include <time.h>
#define CACHE_MIN (32*1024) /* smallest cache */
#define CACHE_MAX (32*1024*1024) /* largest cache */
#define SAMPLE 10 /* to get a larger time sample */
int x[CACHE_MAX]; /* array going to stride through */
long clk_tck;
double get_seconds() { /* routine to read time */
struct tms rusage;
times(&rusage); /* UNIX utility: time in clock ticks */
return (double) (rusage.tms_utime)/clk_tck;
}
int main() {
int register i, index, stride, limit, temp;
int steps, tsteps, csize;
double sec0, sec; /* timing variables */
clk_tck = sysconf(_SC_CLK_TCK);
for (csize=CACHE_MIN; csize <= CACHE_MAX; csize=csize*2)
for (stride=1; stride <= csize/2; stride=stride*2) {
sec = 0; /* initialize timer */
limit = csize-stride+1; /* cache size this loop */
steps = 0;
do { /* repeat until collect 1 second */
sec0 = get_seconds(); /* start timer */
for (i=SAMPLE*stride;i!=0;i=i-1) /* larger sample */
for (index=0; index < limit; index=index+stride)
x[index] = x[index] + 1; /* cache access */
steps = steps + 1; /* count while loop iterations */
sec = sec + (get_seconds() - sec0);/* end timer */
} while (sec < 1.0); /* until collect 1 second */
/* Repeat empty loop to loop subtract overhead */
tsteps = 0; /* used to match no. while iterations */
do { /* repeat until same no. iterations as above */
sec0 = get_seconds(); /* start timer */
for (i=SAMPLE*stride;i!=0;i=i-1) /* larger sample */
for (index=0; index < limit; index=index+stride)
temp = temp + index; /* dummy code */
tsteps = tsteps + 1; /* count while iterations */
sec = sec - (get_seconds() - sec0);/* - overhead */
} while (tsteps<steps); /* until = no. iterations */
printf("Size: %7ld Stride: %7ld read+write: %4.4lf ns\n",
csize*sizeof(int), stride*sizeof(int),
(double) sec*1e9/(steps*SAMPLE*stride*((limit-1)/stride+1)));
}; /* end of both outer for loops */
}

BIN
lab12/g1.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 43 KiB

20
lab12/graph_data.py Normal file
View File

@ -0,0 +1,20 @@
import thinkplot
import matplotlib.pyplot as pyplot
d = {}
for line in open('data'):
t = line.split()
size, stride, time = int(t[1]), int(t[3]), float(t[5])
d.setdefault(stride, []).append((size, time))
thinkplot.PrePlot(num=7)
for stride in sorted(d.keys()):
if stride >= 512: continue
xs, ys = zip(*d[stride])
thinkplot.plot(xs, ys, label=str(stride))
print stride, len(d[stride])
pyplot.xscale('log', basex=2)
thinkplot.show(xlabel='size (B)', ylabel='access time (ns)')

504
lab12/thinkplot.py Normal file
View File

@ -0,0 +1,504 @@
"""This file contains code for use with "Think Stats",
by Allen B. Downey, available from greenteapress.com
Copyright 2010 Allen B. Downey
License: GNU GPLv3 http://www.gnu.org/licenses/gpl.html
"""
import math
import matplotlib
import matplotlib.pyplot as pyplot
import numpy as np
# customize some matplotlib attributes
#matplotlib.rc('figure', figsize=(4, 3))
#matplotlib.rc('font', size=14.0)
#matplotlib.rc('axes', labelsize=22.0, titlesize=22.0)
#matplotlib.rc('legend', fontsize=20.0)
#matplotlib.rc('xtick.major', size=6.0)
#matplotlib.rc('xtick.minor', size=3.0)
#matplotlib.rc('ytick.major', size=6.0)
#matplotlib.rc('ytick.minor', size=3.0)
class Brewer(object):
"""Encapsulates a nice sequence of colors.
Shades of blue that look good in color and can be distinguished
in grayscale (up to a point).
Borrowed from http://colorbrewer2.org/
"""
color_iter = None
colors = ['#081D58',
'#253494',
'#225EA8',
'#1D91C0',
'#41B6C4',
'#7FCDBB',
'#C7E9B4',
'#EDF8B1',
'#FFFFD9']
# lists that indicate which colors to use depending on how many are used
which_colors = [[],
[1],
[1, 3],
[0, 2, 4],
[0, 2, 4, 6],
[0, 2, 3, 5, 6],
[0, 2, 3, 4, 5, 6],
[0, 1, 2, 3, 4, 5, 6],
]
@classmethod
def Colors(cls):
"""Returns the list of colors.
"""
return cls.colors
@classmethod
def ColorGenerator(cls, n):
"""Returns an iterator of color strings.
n: how many colors will be used
"""
for i in cls.which_colors[n]:
yield cls.colors[i]
raise StopIteration('Ran out of colors in Brewer.ColorGenerator')
@classmethod
def InitializeIter(cls, num):
"""Initializes the color iterator with the given number of colors."""
cls.color_iter = cls.ColorGenerator(num)
@classmethod
def ClearIter(cls):
"""Sets the color iterator to None."""
cls.color_iter = None
@classmethod
def GetIter(cls):
"""Gets the color iterator."""
return cls.color_iter
def PrePlot(num=None, rows=1, cols=1):
"""Takes hints about what's coming.
num: number of lines that will be plotted
"""
if num:
Brewer.InitializeIter(num)
# TODO: get sharey and sharex working. probably means switching
# to subplots instead of subplot.
# also, get rid of the gray background.
if rows > 1 or cols > 1:
pyplot.subplots(rows, cols, sharey=True)
global SUBPLOT_ROWS, SUBPLOT_COLS
SUBPLOT_ROWS = rows
SUBPLOT_COLS = cols
def SubPlot(rows, cols, plot_number):
"""Configures the number of subplots and changes the current plot.
rows: int
cols: int
plot_number: int
"""
pyplot.subplot(rows, cols, plot_number)
class InfiniteList(list):
"""A list that returns the same value for all indices."""
def __init__(self, val):
"""Initializes the list.
val: value to be stored
"""
list.__init__(self)
self.val = val
def __getitem__(self, index):
"""Gets the item with the given index.
index: int
returns: the stored value
"""
return self.val
def Underride(d, **options):
"""Add key-value pairs to d only if key is not in d.
If d is None, create a new dictionary.
d: dictionary
options: keyword args to add to d
"""
if d is None:
d = {}
for key, val in options.iteritems():
d.setdefault(key, val)
return d
def Clf():
"""Clears the figure and any hints that have been set."""
Brewer.ClearIter()
pyplot.clf()
def Figure(**options):
"""Sets options for the current figure."""
Underride(options, figsize=(6, 8))
pyplot.figure(**options)
def Plot(xs, ys, style='', **options):
"""Plots a line.
Args:
xs: sequence of x values
ys: sequence of y values
style: style string passed along to pyplot.plot
options: keyword args passed to pyplot.plot
"""
color_iter = Brewer.GetIter()
if color_iter:
try:
options = Underride(options, color=color_iter.next())
except StopIteration:
print 'Warning: Brewer ran out of colors.'
Brewer.ClearIter()
options = Underride(options, linewidth=3, alpha=0.8)
pyplot.plot(xs, ys, style, **options)
def Scatter(xs, ys, **options):
"""Makes a scatter plot.
xs: x values
ys: y values
options: options passed to pyplot.scatter
"""
options = Underride(options, color='blue', alpha=0.2,
s=30, edgecolors='none')
pyplot.scatter(xs, ys, **options)
def Pmf(pmf, **options):
"""Plots a Pmf or Hist as a line.
Args:
pmf: Hist or Pmf object
options: keyword args passed to pyplot.plot
"""
xs, ps = pmf.Render()
if pmf.name:
options = Underride(options, label=pmf.name)
Plot(xs, ps, **options)
def Pmfs(pmfs, **options):
"""Plots a sequence of PMFs.
Options are passed along for all PMFs. If you want different
options for each pmf, make multiple calls to Pmf.
Args:
pmfs: sequence of PMF objects
options: keyword args passed to pyplot.plot
"""
for pmf in pmfs:
Pmf(pmf, **options)
def Hist(hist, **options):
"""Plots a Pmf or Hist with a bar plot.
The default width of the bars is based on the minimum difference
between values in the Hist. If that's too small, you can override
it by providing a width keyword argument, in the same units
as the values.
Args:
hist: Hist or Pmf object
options: keyword args passed to pyplot.bar
"""
# find the minimum distance between adjacent values
xs, fs = hist.Render()
width = min(Diff(xs))
if hist.name:
options = Underride(options, label=hist.name)
options = Underride(options,
align='center',
linewidth=0,
width=width)
pyplot.bar(xs, fs, **options)
def Hists(hists, **options):
"""Plots two histograms as interleaved bar plots.
Options are passed along for all PMFs. If you want different
options for each pmf, make multiple calls to Pmf.
Args:
hists: list of two Hist or Pmf objects
options: keyword args passed to pyplot.plot
"""
for hist in hists:
Hist(hist, **options)
def Diff(t):
"""Compute the differences between adjacent elements in a sequence.
Args:
t: sequence of number
Returns:
sequence of differences (length one less than t)
"""
diffs = [t[i+1] - t[i] for i in range(len(t)-1)]
return diffs
def Cdf(cdf, complement=False, transform=None, **options):
"""Plots a CDF as a line.
Args:
cdf: Cdf object
complement: boolean, whether to plot the complementary CDF
transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'
options: keyword args passed to pyplot.plot
Returns:
dictionary with the scale options that should be passed to
Config, Show or Save.
"""
xs, ps = cdf.Render()
scale = dict(xscale='linear', yscale='linear')
for s in ['xscale', 'yscale']:
if s in options:
scale[s] = options.pop(s)
if transform == 'exponential':
complement = True
scale['yscale'] = 'log'
if transform == 'pareto':
complement = True
scale['yscale'] = 'log'
scale['xscale'] = 'log'
if complement:
ps = [1.0-p for p in ps]
if transform == 'weibull':
xs.pop()
ps.pop()
ps = [-math.log(1.0-p) for p in ps]
scale['xscale'] = 'log'
scale['yscale'] = 'log'
if transform == 'gumbel':
xs.pop(0)
ps.pop(0)
ps = [-math.log(p) for p in ps]
scale['yscale'] = 'log'
if cdf.name:
options = Underride(options, label=cdf.name)
Plot(xs, ps, **options)
return scale
def Cdfs(cdfs, complement=False, transform=None, **options):
"""Plots a sequence of CDFs.
cdfs: sequence of CDF objects
complement: boolean, whether to plot the complementary CDF
transform: string, one of 'exponential', 'pareto', 'weibull', 'gumbel'
options: keyword args passed to pyplot.plot
"""
for cdf in cdfs:
Cdf(cdf, complement, transform, **options)
def Contour(obj, pcolor=False, contour=True, imshow=False, **options):
"""Makes a contour plot.
d: map from (x, y) to z, or object that provides GetDict
pcolor: boolean, whether to make a pseudocolor plot
contour: boolean, whether to make a contour plot
imshow: boolean, whether to use pyplot.imshow
options: keyword args passed to pyplot.pcolor and/or pyplot.contour
"""
try:
d = obj.GetDict()
except AttributeError:
d = obj
Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)
xs, ys = zip(*d.iterkeys())
xs = sorted(set(xs))
ys = sorted(set(ys))
X, Y = np.meshgrid(xs, ys)
func = lambda x, y: d.get((x, y), 0)
func = np.vectorize(func)
Z = func(X, Y)
x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)
axes = pyplot.gca()
axes.xaxis.set_major_formatter(x_formatter)
if pcolor:
pyplot.pcolormesh(X, Y, Z, **options)
if contour:
cs = pyplot.contour(X, Y, Z, **options)
pyplot.clabel(cs, inline=1, fontsize=10)
if imshow:
extent = xs[0], xs[-1], ys[0], ys[-1]
pyplot.imshow(Z, extent=extent, **options)
def Pcolor(xs, ys, zs, pcolor=True, contour=False, **options):
"""Makes a pseudocolor plot.
xs:
ys:
zs:
pcolor: boolean, whether to make a pseudocolor plot
contour: boolean, whether to make a contour plot
options: keyword args passed to pyplot.pcolor and/or pyplot.contour
"""
Underride(options, linewidth=3, cmap=matplotlib.cm.Blues)
X, Y = np.meshgrid(xs, ys)
Z = zs
x_formatter = matplotlib.ticker.ScalarFormatter(useOffset=False)
axes = pyplot.gca()
axes.xaxis.set_major_formatter(x_formatter)
if pcolor:
pyplot.pcolormesh(X, Y, Z, **options)
if contour:
cs = pyplot.contour(X, Y, Z, **options)
pyplot.clabel(cs, inline=1, fontsize=10)
def Config(**options):
"""Configures the plot.
Pulls options out of the option dictionary and passes them to
the corresponding pyplot functions.
"""
names = ['title', 'xlabel', 'ylabel', 'xscale', 'yscale',
'xticks', 'yticks', 'axis']
for name in names:
if name in options:
getattr(pyplot, name)(options[name])
loc = options.get('loc', 0)
legend = options.get('legend', True)
if legend:
pyplot.legend(loc=loc)
def Show(**options):
"""Shows the plot.
For options, see Config.
options: keyword args used to invoke various pyplot functions
"""
# TODO: figure out how to show more than one plot
Config(**options)
pyplot.show()
def Save(root=None, formats=None, **options):
"""Saves the plot in the given formats.
For options, see Config.
Args:
root: string filename root
formats: list of string formats
options: keyword args used to invoke various pyplot functions
"""
Config(**options)
if formats is None:
formats = ['pdf', 'eps']
if root:
for fmt in formats:
SaveFormat(root, fmt)
Clf()
def SaveFormat(root, fmt='eps'):
"""Writes the current figure to a file in the given format.
Args:
root: string filename root
fmt: string format
"""
filename = '%s.%s' % (root, fmt)
print 'Writing', filename
pyplot.savefig(filename, format=fmt, dpi=300)
# provide aliases for calling functons with lower-case names
preplot = PrePlot
subplot = SubPlot
clf = Clf
figure = Figure
plot = Plot
scatter = Scatter
pmf = Pmf
pmfs = Pmfs
hist = Hist
hists = Hists
diff = Diff
cdf = Cdf
cdfs = Cdfs
contour = Contour
pcolor = Pcolor
config = Config
show = Show
save = Save
def main():
color_iter = Brewer.ColorGenerator(7)
for color in color_iter:
print color
if __name__ == '__main__':
main()

35
lab8/CMakeLists.txt Normal file
View File

@ -0,0 +1,35 @@
cmake_minimum_required(VERSION 3.16)
set(CMAKE_C_STANDARD 11)
# Lab name
set(LAB_NAME "lab8")
# Lab tasks
list(APPEND SOURCE_FILES
server.c
client.c
)
list(APPEND NON_COMPILABLE_SRC
# .execme
)
### Here goes the template
project("${LAB_NAME}" C)
add_custom_target("${LAB_NAME}")
foreach (file IN LISTS SOURCE_FILES)
add_executable("${LAB_NAME}_${file}_run" "${file}")
add_dependencies("${LAB_NAME}" "${LAB_NAME}_${file}_run")
endforeach ()
foreach (file IN LISTS NON_COMPILABLE_SRC)
add_custom_command(
TARGET "${LAB_NAME}" POST_BUILD
DEPENDS "${file}"
COMMAND ${CMAKE_COMMAND} -E copy
"${CMAKE_CURRENT_SOURCE_DIR}/${file}"
"${CMAKE_CURRENT_BINARY_DIR}/${file}"
)
endforeach ()

38
lab8/README.md Normal file
View File

@ -0,0 +1,38 @@
# Лабораторная работа №8
> Разработка сервера с использованием цикла ожидания событий
На сервере создаётся и биндится сокет, слушается клиент. Далее в бесконечном цикле с
использованием `select()` обрабатываются запросы (с возможностью подключения нескольких
клиентов)
Для каждого запроса обрабатывается запрос в plain-text-формате, возвращается ответ в том же формате.
Для асинхронной работы использованы неблокирующие сокеты.
Их отличие в том, что во время выполнения операции программа не блокирует своё исполнение.
Используя функцию `fcntl()`, сокеты переводятся в неблокирующий режим.
Вызов любой функции с таким сокетом будет возвращать управление немедленно.
> Добавить обработку сигналов
Для обработки сигналов используем системный вызов `signal()`.
Существуют и другие подходы для создания сервера с циклом ожидания событий. Например, с использованием `fork()`,
как это делается, например, в Apache2, где для обработки клиентов заранее делается массив форков (т.к. форк --
дорогостоящая операция).
Этот способ подразумевает создание дочернего процесса для обслуживания каждого нового клиента.
При этом родительский процесс занимается только прослушиванием порта и приёмом соединений.
Но у такого подхода есть минусы.
1. Если клиентов очень много, создание нового процесса для обслуживания каждого из них может оказаться слишком
дорогостоящей операцией (решается пулом пре-форков).
2. Такой способ неявно подразумевает, что все клиенты обслуживаются независимо друг от друга.
Для решения этих проблем мы можем использовать `select()`.
Его плюсы заключаются в разрешении вышеописанных проблем.
Минусы заключаются в том, что программы получаются более сложными по сравнению с первым способом,
а также программа вынуждена отслеживать дескрипторы всех клиентов и работать с ними параллельно
и каждый раз нужно пробегаться в цикле по дескрипторам всех сокетов и проверять, произошло ли какое-либо событие.

93
lab8/client.c Normal file
View File

@ -0,0 +1,93 @@
#include "config.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/types.h>
#include <sys/stat.h>
#include <time.h>
#include <unistd.h>
#define USAGE_STRING "Usage: %s [address [port]]\n" \
"`address' should be valid IPv4 address\n" \
"`port' should be in range [1, 65535]\n"
#define MAX(a,b) ((a)>(b)?(a):(b))
#define STR_EQ(a, b) (!strcmp((a),(b)))
#define WARNING(fmt, ...) fprintf(stderr,fmt,##__VA_ARGS__)
#define FATAL(fmt, ...) do {\
WARNING(fmt,##__VA_ARGS__);\
exit(1);\
} while (0)
int main(int argc, char *argv[]) {
struct in_addr address = {.s_addr = inet_addr("127.0.0.1")};
int port = PORT;
switch (argc) {
case 3:
port = strtol(argv[2], NULL, 10);
if (port <= 0 || port >= 0xFFFF) {
FATAL("Could not parse port\n" USAGE_STRING, argv[0]);
}
case 2:
if (!inet_aton(argv[1], &address)) {
FATAL("Could not parse IPv4 address\n" USAGE_STRING, argv[0]);
}
case 1:
break;
default:
FATAL(USAGE_STRING, argv[0]);
}
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket()");
FATAL("Error while creating socket\n");
}
struct sockaddr_in srv_addr = {
.sin_family = AF_INET,
.sin_addr = address,
.sin_port = htons(port)
};
int err = connect(sockfd, (const struct sockaddr *) &srv_addr, sizeof(srv_addr));
if (err == -1) {
perror("connect()");
FATAL("Connection error.\n");
}
char buff[4096];
for (;;) {
memset(buff, 0, 4096);
printf("time - print current server time\n"
"<filepath> - print contents of the file\n"
"quit - quit client\n"
"Enter command: ");
int n = 0;
while ((buff[n++] = getchar()) != '\n');
buff[n - 1] = 0;
// Some commands should be parsed on client side
if (STR_EQ(buff, "quit")) {
break;
}
if (send(sockfd, buff, n, 0) == -1) {
perror("send()");
FATAL("Could not send data.\n");
}
memset(buff, 0, 4096);
if (recv(sockfd, buff, 4096, 0) == -1) {
perror("recv()");
FATAL("Receiving failed.\n");
}
printf("%s\n", buff);
}
close(sockfd);
return 0;
}

21
lab8/config.h Normal file
View File

@ -0,0 +1,21 @@
#pragma once
/**
* Server port. Also used in client to specify connection port.
*/
#define PORT 8841
/**
* Maximum number of connections pending.
*/
#define PENDING_CONNS_MAX 32
/**
* Timeout value in seconds.
*/
#define TIMEOUT_SECS 120
/**
* Buffer size for client request.
*/
#define BUF_SIZE 4096

143
lab8/server.c Normal file
View File

@ -0,0 +1,143 @@
#include "config.h"
#include <arpa/inet.h>
#include <ctype.h>
#include <fcntl.h>
#include <signal.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <sys/socket.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <time.h>
#include <unistd.h>
#define MAX(a,b) ((a)>(b)?(a):(b))
#define STR_EQ(a, b) (!strcmp((a),(b)))
#define WARNING(fmt, ...) fprintf(stderr,fmt,##__VA_ARGS__)
#define FATAL(fmt, ...) do {\
WARNING(fmt,##__VA_ARGS__);\
exit(1);\
} while (0)
char *handle_cmd(char *buff) {
char *ret = NULL;
if (!*buff) {
ret = strdup("Empty request\n");
}
else if (isalpha(buff[0])) {
if (STR_EQ(buff, "time")) {
// Respond with current time
time_t t = time(NULL);
ret = strdup(ctime(&t));
} else {
ret = strdup("Unknown command\n");
}
} else {
// Get files
int fd = open(buff, O_RDONLY);
if (fd == -1) {
perror("open()");
WARNING("Can't open file %s\n", buff);
return strdup("ERROR: server failed to obtain file.\n");
}
struct stat f_stat;
if(fstat(fd, &f_stat) == -1) {
perror("fstat()");
WARNING("Can't stat file %s\n", buff);
return strdup("ERROR: server failed to obtain file.\n");
}
ret = malloc(f_stat.st_size);
read(fd, ret, f_stat.st_size);
close(fd);
}
return ret;
}
void sig_hnd(int signum) {
switch (signum) {
case SIGUSR1:
WARNING("SIGUSR1 handled\n");
break;
case SIGUSR2:
WARNING("SIGUSR2 handled\n");
break;
}
}
int main() {
int sockfd = socket(AF_INET, SOCK_STREAM, 0);
if (sockfd == -1) {
perror("socket()");
FATAL("Error while creating socket\n");
}
fcntl(sockfd, F_SETFL, O_NONBLOCK);
struct sockaddr_in srv_addr = {.sin_family = AF_INET,
.sin_addr.s_addr = htonl(INADDR_ANY),
.sin_port = htons(PORT)},
clt_addr;
int err = bind(sockfd, (const struct sockaddr *) &srv_addr, sizeof(srv_addr));
if (err == -1) {
perror("bind()");
FATAL("Error binding socket to address\n");
}
err = listen(sockfd, PENDING_CONNS_MAX);
if (err == -1) {
perror("listen()");
FATAL("Error putting socket to passive mode\n");
}
printf("Listening on port %d...\n", PORT);
signal(SIGUSR1, sig_hnd);
signal(SIGUSR2, sig_hnd);
char buff[4096] = {0};
fd_set readset, allset;
FD_ZERO(&allset);
FD_SET(sockfd, &allset);
struct timeval timeout = {.tv_sec = TIMEOUT_SECS, .tv_usec = 0};
socklen_t socklen = sizeof(clt_addr);
for (;;) {
readset = allset;
if (select(FD_SETSIZE, &readset, NULL, NULL, &timeout) == -1) {
perror("select()");
FATAL("Server error\n");
}
for (int i = 0; i < FD_SETSIZE; ++i) {
if (FD_ISSET(i, &readset)) {
if (i == sockfd) {
int sock = accept(sockfd, (struct sockaddr *) &clt_addr, &socklen);
if (sock == -1) {
perror("accept()");
FATAL("Acception failed.\n");
}
printf("Client accepted...\n");
fcntl(sock, F_SETFL, O_NONBLOCK);
FD_SET(sock, &allset);
} else {
int bytes_read = recv(i, buff, sizeof(buff), 0);
if (bytes_read <= 0) {
close(i);
FD_CLR(i, &allset);
printf("Client disconnected...\n");
continue;
}
char *read_buff = handle_cmd(buff);
if (send(i, read_buff, strlen(read_buff), 0) == -1) {
perror("send()");
FATAL("Error sending data.\n");
}
free(read_buff);
memset(buff, 0, 4096);
}
}
}
}
return 0;
}