Index by title

Hello World

#include<stdio.h>

void main()
{
    printf("Hello World\n");
    exit(0);
}

Shell

Способы человеко-машинного взаимодействия (интерфейса):

Текстовый — наименьший "inpedance mismatch", следовательно наиболее простая разработка.

Shell — это интерфейс ОС, который дает доступ к сервисам ее ядра.

Примеры

Shell есть в любой ОС. Даже в стандарте POSIX: Shell Command Language

Основные концепции

Окружение

Окружение — это контекст текущего процесса Shell. В Unix оно определяется переменными окружения. Вообще говоря, у любого процесса есть определенные ресурсы: общие (разделяемые) и индивидуальные. Окружение относится к индивидуальным, в то время как файловая система — к разделяемым.

$ env
...
$ echo $PATH
...
$ PATH=/tmp
...
$ env
bash: env: command not found
$ export PATH=/tmp
...
$ source PATH=/tmp
...

Синтаксис

Команды/программы

Case studies

init-скрипты

make

Программа make — это простейший сборщик программ. По сути, она выполняют функцию обработчика иерархии целей, заданных в Makefile. Пример Makefile — см. файл-приложение. Формат записи в Makefile:
<цель>: <другие цели, от которых зависит эта>
    <набор shell-команд>

Литература:


Системное программирование и операционные системы

Литература

Операционные системы

Конкретные ОС

Системное программирование

Проблемы СП

Задания

Лабораторные работы

Всеволод Дёмкин <vseloved@gmail.com>

Лицензия на использование: Creative Commons Attribution-Noncommercial-Share Alike 3.0 Unported License


UNIX

История

Правила программы-хорошего гражданина Unix-среды
(сформулированные Эриком Реймондом)

  1. Модульности: создавать простые части, соединенные чистыми интерфейсами
  2. Ясности: ясность лучше заумности
  3. Композиции: проектировать программы с учетом того, что они будут соединяться с другими программами
  4. Разделения: отделять политику и механизм, интерфейс и реализацию
  5. Простоты: проектировать как можно проще, добавлять сложность только по мере необходимости
  6. Бережливости: писать большую программу только, когда четко продемонстрированно, что другого выхода нет
  7. Прозрачности: проектировать программы так, чтобы сделать удобной отладку и инспекцию
  8. Устойчивости: устойчивость — производная прозрачности и простоты
  9. Представления: вкладывать знания в данные, чтобы логика программы была тупой и простой
  10. Наименьшего удивления: в дизайне интерфейсов самое главное — меньше всего удивлять
  11. Тишины: если у программы нет никаких особенных сообщений, лучше ничего не сообщать
  12. Починки: а если же происходит сбой, то нужно создавать как можно больше шума
  13. Экономии: уменьшать затраченное программистом время за счет машинного времени
  14. Генерации: стараться писать программы, которые пишут другие программы
  15. Оптимизации: сперва прототип, потом полировка и оптимизация
  16. Разнообразия: нет единственного правильного пути
  17. Расширяемости: проектируйте с учетом будующего, поскольку оно прийдет раньше, чем вы думаете

Сильные стороны


Анализ лог-файлов

Базовая оценка: 1 балл

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

Хит — это одна загрузка веб-страницы по данному адресу.

Пример записи в лог-файле:
host-24-225-218-245.patmedia.net - - [01/Oct/2006:06:33:45 -0700] "GET /example/example.atom HTTP/1.1" 304 - "-" "NetNewsWire/2.0b37 (Mac OS X; Lite; http://ranchero.com/netnewswire/)"

Формат записи: <DNS клиента> - - [<Штамп времени>] <Строка HTTP-запроса (тип, URL, версия)> <Код HTTP-ответа> <Количество переданных байт> <Строка реферера> <Название клиента>


Ассемблер

С помощью GNU Assembler (GAS) или Netwide Assembler (NASM) в ОС Linux создать утилиту обработки файлов, которая реализует один из перечисленных ниже алгоритмов:

Для минимальной оценки — XOR-шифрование. Запускаться она должна так:
Шифрование:  $ ./xorc -e <входной файл> <зашифрованный файл>
Расшифровка: $ ./xorc -d <зашифрованный файл> <расшифрованный файл>
В результате содержимое входного и расшифрованного файлов должно быть идентичным.

Иначе — по вариантам:

Шифрование:  $ ./tea -e <исходный файл> <зашифрованный файл>
Расшифровка: $ ./tea -d <зашифрованный файл> <расшифрованный файл>
В результате содержимое исходного и расшифрованного файлов должно быть идентичным.
Получение дельта-файла: $ ./deltac <исходный файл> <измененный файл> <дельта-файл>
Наложение дельта-файла: $ ./deltac -d <исходный файл> <дельта-файл> <результирующий файл>
В результате содержимое измененного и результирующего файлов должно быть идентичным.
Вычисление суммы: $ ./bsdsum <исходный файл> <файл с сумой>
Проверка суммы: $ ./bsdsum -c <файл с сумой> <файл> - результат (true или false) выводится на экран
Вычисление суммы: $ ./crc32sum <исходный файл> <файл с сумой>
Проверка суммы: $ ./crc32sum -c <файл с сумой> <файл> - результат (true или false) выводится на экран
Вычисление суммы: $ ./adler32sum <исходный файл> <файл с сумой>
Проверка суммы: $ ./adler32sum -c <файл с сумой> <файл> - результат (true или false) выводится на экран

Безопасность

Литература:


Бинарный интерфейс системы и язык ассемблера

Презентация

Язык ассемблера

Писать на АСМе сейчас практически не нужно. Но ВСЕГДА рано или поздно сталкивался с тем, что нужно уметь читать АСМ. Либо для того, чтобы сделать действительно оптимальный код, либо — что гораздо чаще — чтобы суметь правильно прочитать stack trace и поймать эту самую чертову ошибку. Мне довольно часто приходилось видеть этот "кошмар прикладного программиста", когда большая и сложная программа валится на невнятном сообщении "выполнена недопустимая операция". И без знания АСМа понять, что случилось, невозможно.

Андрей Светлов

Языки ассемблеров были первыми "высокоуровневыми" языками по сравнению с программированием непосредственно в адресных кодах. В них появились:

Пример эквивалентных программ в адресных кодах, на ассемблере (GNU Assembler) и на C:
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF

push %ebp
mov  %esp, %ebp
sub  $0x8, %esp
movl $0x1, -4(%ebp)‏
sub  $0xc, %esp
push $0x0
call 8048348

int main()‏
{
    int i = 1;
    exit(0);
}
Еще один пример — классический Hello World
#include <stdio.h>
int main() 
{
    printf("hello, world");
    return 0;
};
преобразованный в ассемблер компилятором MSVC (cl 1.cpp /Fa1.asm):
CONST    SEGMENT
$SG3830    DB    'hello, world', 00H
CONST    ENDS
PUBLIC    _main
EXTRN    _printf:PROC
; Function compile flags: /Odtp
_TEXT    SEGMENT
_main    PROC
; File c:\...\1.cpp
; Line 4
    push    ebp
    mov    ebp, esp
; Line 5
    push    OFFSET $SG3830
    call    _printf
    add    esp, 4
; Line 6
    xor    eax, eax
; Line 7
    pop    ebp
    ret    0
_main    ENDP
_TEXT    ENDS
и компилятором GCC:
.file    “ctest.c”
.version “01.01”
gcc2_compiled.:
        .section         .rodata
.LC0:
        .string “Hello, world!\n”
.text
        .align 16
.globl  main
.type   main,@function
main:
        pushl    %ebp
        movl     %esp, %ebp
        subl     $8, %esp
        subl     $12, %esp
        pushl    $.LC0
        call     printf
        addl     $16, %esp
        subl     $12, %esp
        pushl    $0
        call     exit
.Lfe1:
        .size     main,.Lfe1-main
        .ident “GCC: (GNU) 2.96 20000731 (Linux-Mandrake 8.0 2.96-0.48mdk)”

Соглашения о вызовах

Это частично определяемый аппаратной архитектурой и частично конкретной средой исполнения механизм вызова подпрограмм. Соглашения включают ответы на вопросы: как передаются/возвращаются аргументы, как распределяется работа при вызове/завершении подпрограммы.

Конкретные примеры реализации:

Типичная структура вызова подпрограммы на архитектуре x86:
push eAX            ; последний аргумент — содержимое регистра eAX
push byte[eBP+20]   ; предпоследний аргумент — содержимое ячейки памяти по адресу = адрес в eBP + 20 байт
push 3              ; первый аргумент — константа 3
call calc           ; вызов подпрограммы calc, возвращаемое значение будет в eAX
Типичная структура подпрограммы:
calc:
  push eBP          ; сохранить указатель на стек
  mov eBP,eSP       ; получить новый указатель на стек
  sub eSP,4*3       ; выделить место для локальных переменных
  ...               ; произвести вычисления, в конце концов результат записать в eAX
  mov eSP,eBP       ; "освободить" место, занимаемое локальными переменными
  pop eBP           ; восстановить указатель на стек
  ret 0             ; вернуться в место вызова

Литература:


Взаимодействие через HTTP

Не используя клиентские библиотеки, а работая напрямую с HTTP-протоколом, связаться с сервером хранения/обработки данных и выполнить простую последовательность команд.

Базовые варианты:

  1. сервер Apache Solr
  2. сервер CouchDB
  3. сервер Kyoto Tycoon
  4. сервер Keyspace

Продвинутые варианты:

  1. AMQP-REST
  2. Neo4j (через REST API)
  3. AllegroCache
  4. Terrastore
  5. GeoServer
  6. Riak (через REST API)

Взаимодействие через сокеты

Не используя клиентские библиотеки, а работая напрямую с сокетными соединениями, связаться с сервером хранения/обработки данных и выполнить определенную простую последовательность команд (в зависимости от варианта). Сами команды и их результат отображать на экране.

Базовые варианты:

  1. сервер Tokyo Tyrant
  2. сервер Memcached
  3. сервер Redis
Последовательность команд:
  1. прочитать значение по ключу "key" (должно отсутствовать)
  2. установить значение "тест" по ключу "key"
  3. прочитать значение по ключу "key"
  4. удалить значение по ключу "key"
  5. установить значение "test" по ключу "ключ 1"
  6. прочитать значение по ключу "ключ 1"

Продвинутые варианты:

  1. сервер Apache ActiveMQ
  2. сервер RabbitMQ
  3. сервер PostgreSQL
  4. сервер MySQL
  5. сервер MongoDB
  6. сервер Riak (через PBC API)

Последовательность команд:


Виртуальные машины

Изначальное определение: это эффективный изолированный дупликат реальной машины.

С точки зрения целевого предназначения:

Типы:


Исполняемые файлы

Литература


Классические задачи синхронизации

Задание: необходимо решить эти задачи с помощью различных подходов к синхронизации:

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

Иначе — по вариантам:
  1. Спящий парикмахер (сообщения), Обедающие философы (STM), Читатели-писатели (классика)
  2. Спящий парикмахер (сообщения), Обедающие философы (классика), Читатели-писатели (STM)
  3. Спящий парикмахер (классика), Обедающие философы (сообщения), Читатели-писатели (STM)
  4. Спящий парикмахер (классика), Обедающие философы (STM), Читатели-писатели (сообщения)
  5. Спящий парикмахер (STM), Обедающие философы (сообщения), Читатели-писатели (классика)
  6. Спящий парикмахер (STM), Обедающие философы (классика), Читатели-писатели (сообщения)

Производитель-потребитель

http://en.wikipedia.org/wiki/Producer-consumer_problem

Есть буффер ограниченного размера N, к которому имеют доступ 2 нити: производитель и потребитель. Производитель в бесконечном цикле генерирует пакет данных в течение случайного интервала времени и записывает их в буфер в течение какого-то времени t1, если в нем есть свободное место. Потребитель в бесконечном цикле считывает и удаляет данные из буфера (если они там есть) по 1 пакету в течение времени t2, а затем случайный интервал времени обрабатывает их.

Задание: Необходимо смоделировать эту ситуацию в программе в виде 2 нитей исполнения (производителя и потребителя). Работа нитей должна фиксироваться в виде журнала и отображаться на экране. Например (размер буфера: 20):
12:01:10.353 Производитель создал новый пакет данных. В буфере: 19
12:01:11.353 Производитель записал пакет в буфер. В буфере: 20
12:01:11.700 Производитель создал новый пакет данных. В буфере: 20
12:01:11.701 Производитель ожидает. В буфере: 20
12:01:11.910 Потребитель считал пакет из буфера. В буфере: 19
12:01:13.011 Потребитель обработал данные. В буфере: 19
12:01:14.005 Потребитель считал пакет из буфера. В буфере: 18
12:01:14.243 Производитель записал пакет в буфер. В буфере: 19
...

Читатели-писатели

http://en.wikipedia.org/wiki/Readers-writers_problem

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

Задание: Необходимо обеспечить, чтобы чтение могло происходить несколькими нитями одновременно. Необходимо обеспечить, чтобы каждый писатель ждал возможности записи не больше необходимого (т.е. если писатель запросил запись в то время, как какие-то нити читали, он должен ожидать права записи только до того момента, как эти нити закончат чтение, но не до того момента, как его закончат другие, которые могли бы начать чтение позже, чем писатель запросил запись).

Отображение работы программы должно быть подобно задаче Производитель-потребитель.

Спящий парикмахер

http://en.wikipedia.org/wiki/Sleeping_barber_problem

В парикмахерской работает 1 парикмахер, у которого есть 1 кресло для стрижки, и N кресел для ожидания клиентов. Клиенты приходят к парикмахеру через случайные промежутки времени. Когда клиентов нет, парикмахер спит в кресле. Когда приходит клиент он будит парикмахера и начинается стрижка. Если парикмахер стрижет кого-то, клиент садится в одно из свободных кресел. Если все кресла заняты, клиент уходит. Когда парикмахер освобождается, он начинает стричь клиента, который ждет дольше всего, или засыпает, если в очереди нет клиентов. Время стрижки клиента также является случайной величиной.

Задание: Смоделировать эту ситуацию, избежав появления возможностей создания ситуаций deadlock и голодание. Результаты работы программы должны отображаться также, как и в предыдущих задачах, например:
12:01:10.353 Парикмахер спит.
12:01:11.412 Клиент 1 пришел в парикмахерску. Очередь: 0
12:01:11.700 Клиент 1 будит парикмахера.
12:01:11.701 Клиент 1 садится на стрижку. Очередь: 0
12:01:11.910 Клиент 2 пришел в парикмахерскую. Очередь: 0
12:01:11.815 Клиент 2 садится в кресло для ожидания. Очередь: 1
12:01:14.810 Клиент 3 пришел в парикмахерскую. Очередь: 1
12:01:14.951 Клиент 1 постригся и уходит из парикмахерской.
12:01:15.115 Клиент 3 садится в кресло для ожидания. Очередь: 2
12:01:15.222 Клиент 1 садится на стрижку. Очередь: 1
...

Обедающие философы

http://en.wikipedia.org/wiki/Dining_philosophers_problem

5 философов сидят за столом. Они могут быть в 2-х взаимоисключающих состояниях: думать и есть. Между каждым философом лежит 1 вилка, а перед каждым -- тарелка. Для того чтобы начать есть, философу нужно взять 2 вилки (левую и правую). Сначала философ берет левую, а затем правую.

Задание: Необходимо смоделировать эту ситуацию в программе в виде 5 нитей исполнения (для каждого философа). Работа нитей должна фиксироваться в виде журнала и отображаться на экране. Например:
12:01:10.353 Философ 1 думает. Состояни вилок: 01100
12:01:10.389 Философ 5 взял левую вилку (№5). Состояни вилок: 01101
12:01:10.390 Философ 5 взял правую вилку (№1). Состояни вилок: 11101
12:01:10.391 Философ 5 начал есть.
12:01:11.800 Философ 3 закончил есть.
12:01:11.812 Философ 4 взял левую вилку (№4). Состояни вилок: 11111
12:01:11.813 Философ 4 не смог взять правую вилку (№5). Состояни вилок: 11111
12:01:11.815 Философ 2 положил левую вилку (№2). Состояни вилок: 10111
...

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


Конкуренция и паралеллизм

Презентация

Литература


Литература

По-русски/по-украински

По-английски

Примечания


Л/р 1 - Утилиты командной строки

По мотивам проекта Wide Finder 2

Имеется текстовый файл log.txt, сожержащий записи access log сервера Apache.

Формат записи:
<DNS клиента> - - [<Штамп времени с временной зоной>] <Строка HTTP-запроса (тип, URL, версия)> <Код HTTP-ответа> <Количество переданных байт или '-', если ответ не имеет тела> <Строка реферера ('-' означает прямой запрос без реферера)> <Название клиента>
Пример записи:
host-24-225-218-245.patmedia.net - - [01/Oct/2006:06:33:45 -0700] "GET /example/example.atom HTTP/1.1" 304 - "-" "NetNewsWire/2.0b37 (Mac OS X; Lite; http://ranchero.com/netnewswire/)"

Написать Shell-скрипт, который по этому файлу расчитает и выдаст в консоль такие величины:

  1. Tоп 10 URL по количеству обращений
  2. Топ 10 клиентов по количеству обращений к 10 URL из предыдущего задания и количество обращений каждого клиента
  3. Всех рефереров для самой популярной URL с количеством обращений для каждого реферера
  4. Топ 10 URL, которые вернули коды 3хх или 4хх (т.е. 301, 302, 404 и т.п.) c количеством обращений по каждому коду
  5. Топ 10 URL по скаченным байтам с объемом байт для каждой URL
  6. Общее количество обращений в каждый из дней недели

Оценка за каждый пункт — 2 балла.

Подсказка: команды Shell, которые могут пригодиться: grep, cut, sort и uniq, awk

Литература:

Сроки сдачи: 5.10.2011


Л/р 2 - Системные вызовы

С помощью GNU Assembler (GAS) или Netwide Assembler (NASM) в ОС Linux создать утилиту обработки файлов, которая реализует алгоритм подсчета Контрольной суммы CRC по одному из модулей (ниже). В программе не использовать библиотечные функции (только системные вызовы и операции ассемблера).

Программа должна запускаться так:
Печать ФИО, номера зачетки и модуля: ./crc -v
Результат (строка вида "Vasya Pupkin IS-8201 CRC-4-ITU x4 + x + 1") выводится в STDOUT

Вычисление суммы: ./crc <имя файла>
Результат (число) выводится в STDOUT

Проверка суммы: ./crc -c <сумма> <имя файла>
Результат (true или false) выводится в STDOUT
Модуль в зависимости от последних двух цифр номера зачетки:
01 - CRC-4-ITU
02 - CRC-5-EPC
03 - CRC-5-ITU
04 - CRC-5-USB
05 - CRC-6-ITU
06 - CRC-7
07 - CRC-8-CCITT
08 - CRC-8-Dallas/Maxim
09 - CRC-8
10 - CRC-8-SAE J1850
11 - CRC-8-WCDMA
12 - CRC-10
13 - CRC-11
14 - CRC-12
15 - CRC-15-CAN
16 - CRC-16-IBM
17 - CRC-16-CCITT
18 - CRC-16-T10-DIF
19 - CRC-16-DNP
20 - CRC-16-DECT
21 - CRC-16-Fletcher
22 - CRC-24
23 - CRC-24-Radix-64
24 - CRC-30
25 - CRC-32
26 - CRC-32C
27 - CRC-32K
28 - CRC-32Q
29 - CRC-40-GSM
30 - CRC-64-ISO

Оценка — 10 баллов.

Литература:

Сроки сдачи: 19.10.2011 (перненосится на 26.10.2011)


Л/р 3 - Процессы

Написать на языке С 2 программы, которые, симулируют некоторые функции bash (запуск процессов, перенаправление ввода-вывода) с помощью системных вызовов (fork, exec, kill, signal, wait, waitpid, dup2, open, sleep, pipe,...). Программы должны работать следующим образом:

prog1:
[0] echo 'tail -f $1' > /tmp/rtail
[1] /tmp/rtail ~/.bash_history >> /tmp/1.txt &
PID процесса [1] должен быть сохранен.
[2] Ожидает завершения команды, запущенной на шаге [1]. После завершения выводит: "Program 1 terminated."

prog2:
[3] Выводит на экран: "Program 2 started."
[4] устанавливает обработчики следующих сигналов:

[5] sleep 30
[6] kill PID
PID — это PID процесса, запущенного в строчке [1].
[7] После завершения выводит: "Program 2 terminated."

Т.е. в задании все вызовы cat, sleep, kill, а также конструкции |, >, >>, & должны быть заменены на соответствующие системые вызовы (и/или последовательность вызова функций в С).

Запуск программ:
./prog1 &
./prog2

Оценка: 10 баллов

Литература:

Срок сдачи: 2.11.2011


Л/р 4 - Синхронизация

Необходимо на языке C средствами библиотеки PThreads смоделировать одну из классических задач синхронизации.

Читатели-писатели

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

Результаты работы программы должны отображаться по мере их появления следующим образом:
12:01:10.353 Читатель 1 хочет читать данные. Ресурс не занят.
12:01:10.354 Читатель 1 начал чтение.
12:01:10.453 Читатель 2 хочет читать данные. Читатель 1 читает ресурс.
12:01:10.456 Читатель 2 начал чтение.
12:01:11.000 Писатель 1 хочет писать данные. Читатели 1, 2 читают ресурс.
12:01:11.001 Писатель 1 ожидает.
12:01:11.100 Читатель 1 закончил чтение. Читатель 2 читает ресурс. Писатель 1 ожидает. 
12:03:08.500 Читатель 3 хочет читать данные. Читатель 2 читает ресурс. Писатель 1 ожидает. 
12:03:08.501 Читатель 3 ожидает. Читатель 2 читает ресурс. Писатель 1 ожидает. 
12:05:11.100 Читатель 2 закончил чтение. Ресурс свободен. Писатель 1 ожидает. Читатель 3 ожидает.
12:05:11.105 Писатель 1 начал запись. Писатель 1 пишет. Читатель 3 ожидает.
...

Спящий парикмахер

В парикмахерской работает 1 парикмахер, у которого есть 1 кресло для стрижки, и N кресел для ожидания клиентов. Клиенты приходят к парикмахеру через случайные промежутки времени. Когда клиентов нет, парикмахер спит в кресле. Когда приходит клиент он будит парикмахера и начинается стрижка. Если парикмахер стрижет кого-то, клиент садится в одно из свободных кресел. Если все кресла заняты, клиент уходит. Когда парикмахер освобождается, он начинает стричь клиента, который ждет дольше всего, или засыпает, если в очереди нет клиентов. Время стрижки клиента также является случайной величиной.

В этой задаче необходимо избежать появления ситуаций deadlock или голодание.

Результаты работы программы должны отображаться по мере их появления следующим образом:
12:01:10.353 Парикмахер спит.
12:01:11.412 Клиент 1 пришел в парикмахерску. Очередь: 0
12:01:11.700 Клиент 1 будит парикмахера.
12:01:11.701 Клиент 1 садится на стрижку. Очередь: 0
12:01:11.910 Клиент 2 пришел в парикмахерскую. Очередь: 0
12:01:11.815 Клиент 2 садится в кресло для ожидания. Очередь: 1
12:01:14.810 Клиент 3 пришел в парикмахерскую. Очередь: 1
12:01:14.951 Клиент 1 постригся и уходит из парикмахерской.
12:01:15.115 Клиент 3 садится в кресло для ожидания. Очередь: 2
12:01:15.222 Клиент 1 садится на стрижку. Очередь: 1
...

Обедающие философы

5 философов сидят за столом. Они могут быть в 2-х взаимоисключающих состояниях: думать и есть. Между каждым философом лежит 1 вилка, а перед каждым — тарелка. Для того чтобы начать есть, философу нужно взять 2 вилки (левую и правую). Сначала философ берет левую, а затем правую.

Необходимо избежать ситуаций deadlock или голодание.

Результаты работы программы должны отображаться по мере их появления следующим образом:
12:01:10.353 Философ 1 думает. Состояние вилок: 01100
12:01:10.389 Философ 5 взял левую вилку (№5). Состояние вилок: 01101
12:01:10.390 Философ 5 взял правую вилку (№1). Состояние вилок: 11101
12:01:10.391 Философ 5 начал есть.
12:01:11.800 Философ 3 закончил есть.
12:01:11.812 Философ 4 взял левую вилку (№4). Состояние вилок: 11111
12:01:11.813 Философ 4 не смог взять правую вилку (№5). Состояние вилок: 11111
12:01:11.813 Философ 4 положил левую вилку. Состояние вилок: 11101
12:01:11.815 Философ 2 положил левую вилку (№2). Состояние вилок: 10101
...

Задания по вариантам (номер варианта — остаток от деления номера зачетки на 3 плюс 1):

  1. Задача Спящий парикмахер (примечание: не использовать Read-write lock, только обычные семафоры).
  2. Задача Обедающие философы.
  3. Задача Читатели-писатели.

Литература:

Оценка: 10 баллов.

Дополнительно можно решить любую из 3х задач методом передачи сообщений на языке Erlang или с помощью STM на языке Clojure — +10 баллов.

Срок сдачи: 16.11.2011


Л/р 5 - Сокеты

На языке С/C++, не используя клиентские библиотеки, а работая напрямую с сокетными соединениями, связаться с сервером хранения/обработки данных и выполнить определенную простую последовательность команд (в зависимости от варианта). Сами команды и их результат отображать на экране.

Базовые варианты (номер варианта равен остатку от деления номера зачетки на 3, увеличенному на 1 ) — оценка 10 баллов:

  1. сервер Tokyo Tyrant
  2. сервер Memcached
  3. сервер Redis
Последовательность команд:
  1. прочитать значение по ключу "key" (должно отсутствовать)
  2. установить значение "тест" по ключу "key"
  3. прочитать значение по ключу "key"
  4. удалить значение по ключу "key"
  5. установить значение "test" по ключу "ключ 1"
  6. прочитать значение по ключу "ключ 1"

Продвинутые варианты - можно выбрать любой (оценка 20 баллов):

  1. сервер RabbitMQ
  2. сервер PostgreSQL
  3. сервер MySQL
  4. сервер MongoDB
  5. сервер Riak (через PBC API)

Последовательность команд:

Сроки сдачи: 30.11.2011


Лр 6 - Файловая система

С помощью библиотеки FUSE создать виртуальную файловую систему, дерево которой задано в файле заданий. Номер варианта — последняя цифра номера зачетки. Файловая должна монтироваться в режиме только чтение, после чего должна быть возможность осуществить листинг ее директорий и просмотр содержимого виртуальных файлов. При обращении к файловой системе должны проверяться права доступа (маска прав указана в файле задания). Владельцем всех файлов должен быть текущий пользователь, который выполняет монтирование системы.

Файловая система содержит 4 директории: bin, test, txt и zdir. А также 4 файла, из которых 3 — текстовые файлы: example, readme.txt, test,— и 1 бинарный файл по варианту (содержимое бинарного файла должно быть взято из соответствующей стандартной системной утилиты: ls, grep, pwd,...) Содержимое остальных файлов:

Оценка: 10 баллов
Срок сдачи: 31.12.2011

Дополнительные задания

Дополнительно реализовать следующие операции:


Парадигмы программирования

Модели компьютера

Фон-нейманновская архитектура — императивный подход

Императивный подход — исторически первый подход к программированию компьютеров, который применялся с момента их появления. Его суть в том, чтобы дать компьютеру точную команду, что и как нужно сделать, например переместить содержимое ячейки памяти по такому-то адресу в такой-то регистр. Таким образом происходило программирование в адресных кодах, затем в ассемблере, затем в первом "высокоуровневом" языке Fortran, затем в C и т.д. до наших дней. Иными словами, этот подход постепенно эволюцтонировал по мере развития мейнстрим инструментов и языков разработки. Эта эволюция постепенно прошла через стадии: "наивного" (без четко определенных правил и парадигм) программирования, структурного, затем объектно-ориентированного и скриптингового, которые являются основными на сегодняшний день.

Лямбда-исчисление — декларативный подход

Если первым "высокоуровневым" языком программирования был Fortran, то вторым — Lisp — первый представитель совсем другой модели вычислений. Декларативный подход ставит своей целью описать не детали выполнения, а требуемый результат каких-то операций.

Лямбда исчисление Черча является основой большинства (хотя и не всех) декларативных парадигм.

Хороший кейс дл сравнения императивного и декларативного подходов — это навигационные и реляционные БД и использующиеся для запросов к ним языки: FoxPro и SQL.

Парадигмы императивного подхода

Структурное программирование

Модульность

Объектно-ориентированное программирование

Наследование

Полиморфизм

Передача сообщений

Скриптинговая парадигма

Парадигмы декларативного подхода

Метапрограммирование

Функциональная парадигма

Другие парадигмы

Логическое программирование (язык Prolog)

Основанно на модели формальной логики предикатов I порядка. В рамках него появились такие подходы к вычислениям как:

Программирование в массивах (языки APL, J, K, Q)

Обобщение операций над скалярными данными на матричные операции. Основные идеи:

Конкурентное программирование

Эзоязыки


Порядок сдачи

Лабораторная работа может быть прислана на email <vseloved@gmail.com> в виде zip-архива, содержащего файлы исходного кода в сопровождении краткого описания (файл README) и файлов, необходимых для сборки (как правило, файл Makefile). Названия всех файлов на английском языке. Либо сдана лично на занятии. Программа должна выполняться в среде Linux.

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

Работа будет проверена на плагиат путем сравнения с помощью утилиты diff с другими уже сданными работами.

Для работ присланных по email:

Таблица результатов будет опубликована в Интернет.


Процесс загрузки ОС

Презентация

Литература:


Работа с сетью

Литература:


Работа с устройствами

Презентация

Литература:


Распределенные системы

Литература


Расчетная работа

Необходимо выбрать Open-source проект, связанный с системным программированием, и реализовать в нем какую-то новую функцию. Эта функция должна быть принята мейнтенером проекта.

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

Найти подходящие проекты можно здесь:

Задание выполняется в группе 2-3 человека.


Синхронизация

Презентация

Синхронизация процессов

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

Семафоры

Семафоры являются одной из первых концепций синхронизации процессов. Они придуманы в 1965–м году Эдсгером Вайбом Дейкстрой.

Семафор представляет из себя целую неотрицательную переменную, доступ процессов к которой (за исключением момента инициализации) осуществляется с помощью двух атомарных операций: P (от датского слова proberen — проверять) и V (от verhogen – увеличивать).

Традиционно данные операции определяются следующим образом:
P(S):   пока S == 0 процесс блокируется; 
        S = S – 1; 
V(S):   S = S + 1;
Реализация POSIX semaphores (заголовочный файл semaphores.h) содержит функции sem_init(), sem_wait(), sem_post(), sem_getvalue() и sem_destroy(), а также специальный тип sem_t, позволяющие использовать семафоры в ваших программах.

Мониторы

Мониторы были придуманы Хоаром (C. A. R. Hoare) как более безопасный примитив синхронизации. Они проще в использовании, так как не требуют столь высокого внимания, как семафоры.

Монитор представляет собой объект, содержащий один или несколько методов. Примечательной особенностью является то, что в один конкретный момент времени только одна нить может находиться в мониторе, то есть исполнять один из его методов. Ещё одним важным свойством является то, что нити могут на время лишаться эксклюзивного доступа в ожидании некоего события (это реализуется с помощью условных переменных — conditional variables).

Некоторые языки (например, Java) имеют встроенные реализации мониторов. Впрочем, их несложно реализовать и самим, используя, например, библиотеку POSIX Threads (pthread) с её эксклюзивными замќами (mutal lock, mutex — тип pthread_mutex_t) и условными переменными (тип pthread_cond_t).

Передача сообщений

Передача сообщений является интуитивно понятным методом синхронизации процессов, объединяющем в себе и синхронизацию, и межпроцессное взаимодействие — IPC (InterProcess Communication). Он базируется на понятии очереди сообщений и двух методах — send(), позволяющем отсылать сообщения, и receive(), позволяющем их принимать. В стандарте POSIX существует собственный интерфейс для работы с очередями, рассматриваемый, например, в mq_overview(7)

STM

STM является самым высокоуровнёвым и современным методом синхронизации. Идея впервые появилась в 1986 и окончательно трансформировалась в современный STM в 1995–м.

Суть метода транзакций заключается в том, что изменения данных не станут видны другим нитям до тех пор, пока не будет выполнен коммит (commit). После коммита система пытается применить сделанные изменения к общему участку памяти. Если сделать это не удаётся (например, если кто–то другой тоже сделал коммит), производится откат до начального состояния и повторная попытка (retry).

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

Аппаратные инструкции для синхронизации (Rad-Modify-Write)

Уровень консенсуса:
∞ — memory-to-memory move and swap, augmented queue, compare-and-swap, fetch-and-cons, sticky byte, Load-Link/Store-Conditional1
2n-2 — n-register assignment
2 — test-and-set, swap, fetch-and-add, queue, stack
1 — atomic read and atomic write

Невозможно реализовать операцию, которая имеет больше участников, чем уровень консенсуса атомарной инструкции.

TSL (test-and-set lock)

Запись в память и возврат предыдущего значения по этому адресу.

Создание замка на основе TSL:
#define LOCKED 1
int TestAndSet(int* lockPtr) {
    int initial = *lock;
    *lock = LOCKED;
    return initial;
}

volatile int lock = 0;

void Critical() {
    while (TestAndSet(&lock) == 1) /* busy waiting */;
    /* critical section */
    lock = 0; //release lock when finished with the critical section
}

CAS (compare-and-swap)

Запись в память, при условии, что значение в ней совпадает с заданным.

int compare_and_swap (int* register, int oldval, int newval) { 
    int old_reg_val = *register;
    if (old_reg_val == oldval) *register = newval;
    return old_reg_val;
}

void acquire_mutex(int *mutex) {
    while (cas(mutex, 1, 0)) /* busy waiting */;
}

void release_mutex(int *mutex) {
    *mutex =  1;
}

В x86 архитекуре — инструкция CMPXCHG.

LL/SC (load-link/store-conditional)

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

Особенность: проблематичная реализация.

Литература


Система оценивания

Формула для определения итоговой оценки: S + E
S — оценка за семестр
S = L + C + T

L — оценка за лабораторные
L = ∑Li, Li — оценка за лабораторную i
Li = bLi + round(4−(accept_date−start_date)/7)
bLi — базовая оценка за лабораторную i (указывается в задании), bLi ∈ [1,3]
accept_date — дата сдачи л/р
start_date — дата проведения реальной л/р (индивидуальная для каждой группы)

C — оценка за курсовую работу, С ∈ [0,17]
С = С1 + С2 + round(4−(accept_date−`2010/11/26`)/7)
C1 — оценка программы, C2 ∈ [0,10]
С2 — оценка тестов, примеров, интеграционной части, документации, C3 ∈ [0,3]

T — оценка за контрольную работу, T ∈ [0,18]
T = q1 + q2 + q3
qi — оценка за вопрос i, qi = 2×(q−2), q ∈ [2,5]

E — оценка за экзамен, E ∈ [0,30]
E = Q1 + Q2
Qi — оценка за вопрос i, Qi = 5×(Q−2), Q ∈ [2,5]

Требуемые баллы:
A - 91..100
B - 81..90
C - 71..80
D - 61..70
E - 51..60


Системные вызовы

Литература:


Управление памятью

Презентация

Литература


Управление процессами

Презентация

Процесс

Процесс – это адресное пространство и единая нить управления — Э. Танненбаум

Понятие процесса включает в себя:

Нити

Нить управления (thread) — это одна логическая цепочка выполнения команд. На самом деле, в одном процессе может быть несколько одновременно запущенных таких цепочек, т.е. нитей. В таком случае имеет место многопоточность (multi-threading).

Дилемма:

Волокна (fibers) — аналогичны нитям, но используют кооперативную многозадачность (см. Планирование процессов)

Со-процедуры (co-routines) — особый вид процедур с несколькими точками входа. Реализует кооперативную многозадачность.

Простейший пример использования со-процедур: допустим, у вас имеется отношение производитель-потребитель. Первый добавляет в очередь некие предметы, второй их удаляет. Для повышения производительности предметы добавляются и удаляются по нескольку за раз, пока очередь не заполнится. В таком случае код (на неком абстрактном языке) будет выглядеть так:
var q := new queue

coroutine produce
    loop
        while q is not full
            create some new items
            add the items to q
        yield to consume

coroutine consume
    loop
        while q is not empty
            remove some items from q
            use the items
        yield to produce

После запуска со-процедуры produce очередь будет заполняться, пока не закончится место. После этого управление будет передано (команда yield) со-процедуре consume. Она будет удалять элементы очереди до тех пор, пока та не опустеет, после чего снова будет вызвана со-процедура produce.

Процесс для ОС

Структура для хранения информации о процессе (Process control block) содержит, как прави‏ло:

Пример реальной структуры (из Linux): attachment:pcb.txt

Жизненный цикл процесса

Процесс создания нового процесса в Linux состоит из двух шагов: Небольшой пример создания нового процесса (для Linux):
 1 #include <sys/types.h>
 2 #include <unistd.h>
 3 #include <stdio.h>
 4 #include <stdlib.h>
 5 
 6 int main() {
 7     /* Клонируем текущий процесс */
 8     pid_t childpid = fork();
 9 
10     /* Обратите внимание на то, что переменная childpid будет существовать и в
11      * оригинальном процессе, и в его клоне, но во втором случае будет равна
12      * нулю. Именно этот факт используется в следующей конструкции */
13     if(childpid == 0) {
14         /* Это клон. С помощью стандартной команды echo выведем приветствие и завершимся */
15 
16         char* command[3] = {"/bin/echo", "Hello, world!", NULL};
17 
18         execvp(command[0], command);
19 
20         /* Если не произошло никаких ошибок, execvp() не завершается и мы
21          * никогда не достигнем этого участка кода. Если же мы всё-таки сюда
22          * добрались, клону следует завершиться с кодом возврата,
23          * сигнализирующем об ошибке */
24         exit(EXIT_FAILURE);
25     } else if(childpid == -1) {
26         /* fork() возвращает -1 в случае ошибки */
27         fprintf(stderr, "Can't fork, exiting...\n");
28         exit(EXIT_FAILURE);
29     } else {
30         /* Это родительский процесс. Просто завершимся с кодом возврата 0 */
31         exit(EXIT_SUCCESS);
32     }
33 
34     return 0;
35 }
Завершение процесса:

Если потомок завершается, но родительский процесс не вызывает wait(), потомок становится зомби. Таким образом, процессы-зобми — это завершившиеся процессы, информация о завершении которых никем не запрошена (т.е. никто не вызывал для этого процесса wait()). Впрочем, после завершения родительского процесса все его потомки переходят к процессу с PID 1, т.е. init. Он самостоятельно очищает информацию, оставшуюся после зомби.

Планирование процессов

Вытесняющая многозадачность (англ. «preemptive multitasking») — в системе работает планировщик (англ. «scheduler»), который имеет право в любой момент приостановить выполняющийся процесс и передать управление другому. Такое переключение между процессами носит имя «переключение контекста» (англ. «context switch»). Вытесняющая многозадачность даёт некоторые гарантии того, что каждый процесс получит некое количество процессорного времени.

Кооперативная многозадачность (англ. «cooperative multitasking») — каждый процесс должен самостоятельно передавать управление. За примером кооперативной многозадачности см. определение со-процессов в разделе о нитях.

Литература:


Утилиты командной строки

Написать Shell-скрипт, который по текстовому файлу log.txt расчитает и выдаст такие параметры:

Базовый вариант: TOP 10 URL по количеству обращений

Иначе:

  1. Топ 10 клиентов по количеству обращений к TOP 50 URL (c количеством обращений)
  2. Топ 10 URL по скаченным байтам (с объемом байт)
  3. Всех рефереров на самую популярную URL (с количеством хитов по рефереру)
  4. Топ 10 URL, которые вернули коды 3хх или 4хх (т.е. 301, 302, 404 и т.п.) (c количеством обращений)
  5. Общее количество обращений по каждому из 7ми дней недели
Пример записи в лог-файле:
host-24-225-218-245.patmedia.net - - [01/Oct/2006:06:33:45 -0700] "GET /example/example.atom HTTP/1.1" 
  304 - "-" "NetNewsWire/2.0b37 (Mac OS X; Lite; http://ranchero.com/netnewswire/)"
Формат записи:
<DNS клиента> - - [<Штамп времени>] <Строка HTTP-запроса (тип, URL, версия)>
  <Код HTTP-ответа> <Количество переданных байт> <Строка реферера> <Название клиента>

Файловая система на основе FUSE

С помощью библиотеки FUSE создать виртуальную файловую систему, дерево файлов для которой закодированно в одном из форматов данных.

Эта файловая система должна монтироваться в режиме только чтение, после чего можно будет осуществить листинг виртуальных директорий и просмотр содержимого виртуальных файлов (содержимое файлов будет находится в соответствующем fuse-файле описания).

Абстрактное представление этой файловой системы (которое можно получить с помощью команды tree):
/
|--docs
|   |--pdfs
|   |   |--article.pdf
|   |   |--example.pdf
|   |   `--example.pdf~
|   `--spreadsheets
|       |--.hidden
|       `--test
|--etc
|   `--fuse
|       `--fuse.conf
`--forbidden dir
   `--test

Базовые варианты:

  1. XML — см. attachment:fuse.xml
  2. JSON — cм. attachment:fuse.json
  3. CSV — см. attachment:fuse.csv
  4. INI — см. attachment:fuse.ini
  5. S-expressions — см. attachment:fuse.sexp

Продвинутые варианты (для этих вариантов таже необходимо обеспечить проверку прав доступа при обращении к директориям и файлам):

  1. BSON — см. attachment:fuse.bson
  2. YAML — cм. attachment:fuse.yaml
  3. Protocol Buffers — см. attachment:fuse.protobuf (cм. также attachment:def.proto с определениями структур)
  4. ASN.1 — см. attachment:fuse.asn1 (cм. также attachment:def.asn1 с определениями структур)
  5. Thrift — см. attachment:fuse.thrift (cм. также attachment:def.thrift с определениями структур)

Файловые системы

Презентация

Литература:


Что такое ОС? Апаратные архитектуры, ядро, функции и роль ОС

Презентация

Литература:


Что такое СП? Основные понятия

Systems Software Research is Irrelevant
— Роб Пайк, http://doc.cat-v.org/bell_labs/utah2000

СП

Системное программирование (СП) — это вершина пирамиды изучения программирования. Это вовсе не обучение программированию на Ассемблере, а совокупность знаний о создании больших программных систем, состоящих из взаимодействующих компонентов, которая опирается на:

Операционные системы (ОС) являются эталонными примерами для изучения, но принципы СП применимы и используются в любых крупных программных системах: виртуальных машинах (ВМ), СУБД, серверах, системах распределенных вычислений и т.д.

Важные проблемы в теории СП:

Ключевые понятия, которые будут встречаться на протяжении всего курса:

Платформа и приложение

Платформа — это аппаратная архитектура и/или программная основа, которая позволяет исполняться "пользовательским" программам. Приложение — это собственно программа, которая выполняет какаое-то полезное действие.

Интерфейс

Это внешнее представление, абстракция какого-то информационного объекта. Интерфейс разделяет методы внешнего взаимодействия и внутренней работы. Один объект может иметь несколько интерфейсов для разных "потребителей". Интерфейс — это средства трансляции между сущностями внешней и внутренней для объекта среды. Интерфейс — это форма косвенного взаимодействия. Связанно с концепцией кибернетики "черный ящик".

Протокол

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

Вопрос: какая взаимосвязь между протоколом и интерфейсом?

Основные составные части большинства протоколов:

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

Закон Постела (принцип робастности): будьте консервативны в том, что отправляете, и либеральны в том, что принимаете

Стандарт

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

Ключевые характеристики изучаемых объектов:

Вопрос: какие ещё?

"Законы" СП

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

CAP-теорема Брюера (Brewer's CAP Theorem)

В распределенных системах из 3х качеств: целостность (consistency), доступность (availability) и терпимость к разделению (partition tolerance) одновременно могут быть достигнуты только 2.

Следствие: определенные подходы к разработке распределенных систем и движение NoSQL.

См. Amazon Dynamo

Закон оборачивания софта Завински (Zawinski's Law of Software Envelopment)

Любая программа пытается расшириться до тех пор, пока не научиться читать почту. Те программы, которые не могут так расшириться, заменяются теми, которые могут

Мораль:

Десятое правило программирования Гринспена (Greenspun's Tenth Rule of Programming)

"Любая достаточно сложная программа на C или Fortran содержит ad hoc, неформально специфицированную, переполненную багами, медленную реализацию половину Common Lisp"

Примечание: Common Lisp — динамический ЯП, который имеет наибольшие возможности расширения и абстракции.

Что отличает Lisp? (Питер Норвиг, PAIP, 1992)

Мораль:

Синдром системы второго поколения (Second System Effect/Syndrome)

Тенденция вслед за относительно небольшой, элегантной и успешной системой спроектировать систему следующего поколения как слонопотамного, переполненного функциями монстра. (Которого часто так и не удается довести до полноценной реализации) [из книги Фреда Брукса Мифический человеко-месяц]

Мораль:

Открытая система (понятие из Теории систем)

Принципы:

Вопрос: Почему важны открытые системы?

Вопрос: в чем разница между масштабруемостью и производительностью?

Масштабируемость бывает вертикальной и горизонтальной. Вертикальная масштабируемость — способность справляться с большей нагрузкой с помощью улучшения внутренних характеристик (например: добавить больше памяти в компьютер). Горизонтальная — путем добавления типичных компонент. У каждого подхода есть свой предел.

Экономический аспект: коммодитизация

Жизнеспособная система (Viable System) — концепция из кибернетики (Стаффорд Бир)

Уровни:

  1. функциональные модули
  2. регуляция и тактическое планирование
  3. аудит, операционное планирование и контроль
  4. развитие
  5. сохранение идентичности

Экзаменационные вопросы

  1. Расшифруйте понятия “протокол”, “интерфейс”. В чем разница между ними? Какие основные виды интерфейсов существуют у компьютерных программ согласно стандарта POSIX? Опишите их.
  2. Что такое ядро ОС? Какие особенности его работы по сравнению с другими программами? Какие архитектуры ОС по реализации ядра бывают? В чем их преимущества и недостатки?
  3. Какие принципиальные отличия языка Ассемблера от высокоуровневых языков программирования? Что такое байткод? В чем разнца между языком Ассемблера и байткодом?
  4. Приведите примеры форматов исполняемых файлов и кратко охарактеризуйте их. Подробно формат ELF.
  5. Из каких этапов состоит создание исполняемой программы из исходного кода? Опишите их суть. Для языков C++, Java и Python перечислите этапы создания программы, которые имеют место в реальности и укажите, в какое время они происходят. Для каких сред исполнения может создаваться программа?
  6. Перечислите этапы загрузки компьютера от включения питания до активизации GUI или CLI ОС. Охарактеризуйте роль каждого из них.
  7. Что такое процесс ОС? Чем он отличается от программы? Что такое нить? Какие есть подходы к созданию многонитевых (многопоточных программ)? Что такой фибр, в чем его отличие от нити?
  8. Опишите жизненный цикл процесса. Назовите требования к алгоритмам планирования процессов.
  9. Перечислите основные алгоритмы планирования процессов. Сформулируйте алгоритм “Карусель” (Round Robin) и охарактеризуйте его. Приведите простой пример. В каких системах он может применяться на практике?
  10. Перечислите основные алгоритмы планирования процессов. Сформулируйте и охарактеризуйте алгоритм “Очередь” (FIFO). Приведите простой пример. В каких системах он может применяться на практике?
  11. Перечислите алгоритмы планирования процессов. Сформулируйте и охарактеризуйте алгоритм “Многоуровневые очереди с обратной связью”. Приведите простой пример. В чем его преимущества и недостатки по сравнению с алгоритмом “Очередь” (FIFO)?
  12. В чем разница между статическими и динамическими алгоритмами планирования процессов? Приведите несколько примеров каждого из них. Перечислите параметры, которые как правило используются в таких алгоритмах.
  13. Назовите и кратко опишите существующие способы синхронизации многопоточных приложений.
  14. Что такое критическая область процесса? Что такое тупик? Какие виды тупиков бывают? Назовите принципы разработки многопоточных программ, которые позволят избежать для них попадания в тупики.
  15. Перечислите разные способы синхронизации работы многопоточных программ. Перечислите и охарактеризуйте проблемные ситуации, которые могут возникать в случае конкуренции за ресурсы между нитями. Какие существуют подходы, для того чтобы избежать их?
  16. Что представляет из себя примитив синхронизации “Семафор”? Опишите его интерфейс (набор операций) и приведите простой пример использования.
  17. Что представляет из себя примитив синхронизации “Монитор”? Опишите его интерфейс (набор операций) и приведите простой пример использования.
  18. Какие инструкции аппаратной синхронизации вы знаете? Сравните их. Приведите несколько примеров, как на их основе можно построить различные примитивы синхронизации (условные переменные, семафоры, ...).
  19. Что такое оптимистическое и пессимистическое блокирование? В каких случаях какое предпочтительнее? Какие еще виды блокирования вы знаете?
  20. Что такое программная транзакционная память (STM)? Какие качества имеют программы, которые ее используют?
  21. Расшифруйте аббревиатуру ACID в применении к системному программированию и кратко охарактеризуйте значение каждого из слов. Какая из букв аббревиатуры не применима, когда речь идет о программной транзакционной памяти (STM)?
  22. Что такое конвейер (PIPE)? Что такое именованный конвейер? Охарактеризуйте их. Как эти объекты можно использовать для взаимодействия программ (приведите несколько примеров)?
  23. Объясните разницу между взаимодействием программ с помощью разделяемой памяти и обмена сообщениями. Опишите преимущества и недостатки обоих вариантов. В каких случаях предпочтительно использование каждого из них (приведите несколько примеров)?
  24. Что такое фрагментация? Какие виды фрагментации бывают? Какие виды фрагментации проявляются в каждой из 3 основных схем размещения файлов?
  25. Какой максимальный адресуемый объем памяти для программы на 32-разрядной архитектуре? Почему объем доступной виртуальной памяти меньше максимального (куда девается разница)? На какие основные части делится память работающей программы? Как это соотносится с форматами исполняемых файлов?
  26. Нарисуйте обобщенную структуру программы в памяти. Каким образом на нее может повлиять использование сегментной модели виртуальной памяти?
  27. Опишите страничную и сегментную организацию виртуальной памяти. В чем преимущества и недостатки каждой из них?
  28. Какая главная проблема эффективной реализации систем виртуальной памяти? Назовите несколько способов ее решения?
  29. Сформулируйте алгоритм выбора кандидата на удаление из кэша “Часы”. Опишите его работу на простом примере. В чем его преимущества и недостатки?
  30. Сформулируйте алгоритм выбора кандидата на удаление из кэша “Наименее недавно использовавшийся” (LRU). Опишите его работу на простом примере. В чем его преимущества и недостатки?
  31. Сформулируйте алгоритм выбора кандидата на удаление из кэша “Второй шанс”. Опишите его работу на простом примере. В чем его преимущества и недостатки?
  32. Что такое “старение”, и как этот подход может применяться для улучшения работы алгоритмов выбора кандидата на удаления из кэша? Приведите простой пример. Какую проблему решает использование этого подхода?
  33. В чем разница между копированием при записи (copy-on-write) и изменением на месте (in-place modification)? В чем преимущества и недостатки этих способов изменения хранимых данных? В каких случаях эффективно применять каждый из них? Что такое сквозной кэш?
  34. Назовите способы учета свободного места на диске, кратко опишите их. В каких файловых системах какие способы используются?
  35. Опишите на примере непрерывную схему размещения файлов. Какие ее преимущества и недостатки? В каких случаях она используется (и в каких файловых системах)?
  36. Опишите схему размещения файлов при помощи связного списка. Какие ее преимущества и недостатки? Какая ее основная практическая реализация, и какую проблему эта реализация решает? В каких файловых системах это используется?
  37. Что такое индексные узлы (inode)? Опишите, как они используются в файловой системе. Как называется такая схема размещения файлов и какие ее преимущества и недостатки? Назовите подходы к ее оптимизации. В каких файловых системах это используется?
  38. Что такое файловая система на основе журнала? Чем она отличается от классической файловой системы, какие у нее есть преимущества и недостатки, основные проблемы и особенности реализации?
  39. Опишите Socket API ОС. В чем его особенности, сильные и слабые стороны?
  40. Опишите сетевой стек TCP/IP. Чем он отличается от эталонной модели OSI? Какой уровень к TCP/IP стеку добавляет RPC-приложение?
Только 4-й курс:
  1. Перечислите и кратко охарактеризуйте принципы, на которых должны строится безопасные системы.
  2. Охарактеризуйте подходы к учету прав доступа на основе списков контроля доступа (ACL) и способностей (capabilities). В чем преимущества и недостатки каждого из них?
  3. В чем основные проблемы реализации системы безопасности на основе способностей (capabilities)? В каких случаях они проявляются? Какие пути их решения существуют?
  4. Опишите технологию удаленного вызова процедур (RPC). Сравните 2 подхода к передаче данных в ней. Какие уровни Интернет-стека участвуют в организации распределенного взаимодействия в ней?
  5. Опишите клиент-серверную архитектуру распределенного приложения. Сколько звеньев бывает в ней, какие типы клиентов и серверов? Какое API ОС лежит в ее основе? Какие еще уровни участвуют в организации распределенного взаимодействия в ней?
  6. Опишите сервисную архитектуру распределенного приложения. В чем преимущества и недостатки по сравнению с клиент-серверной архитектурой? Какой протокол лежит в ее основе? Что такое REST?
  7. Опишите peer-to-peer архитектуру распределенного приложения. Какие протоколы взаимодействия могут использоваться в таких системах? В чем преимущества и недостатки по сравнению с клиент-серверной архитектурой?
  8. Опишите архитектуру распределенного на основе очереди. В чем преимущества и недостатки по сравнению с клиент-серверной архитектурой?

Язык ассемблера

Писать на АСМе сейчас практически не нужно. Но ВСЕГДА рано или поздно сталкивался с тем, что нужно уметь читать АСМ. Либо для того, чтобы сделать действительно оптимальный код, либо — что гораздо чаще — чтобы суметь правильно прочитать stack trace и поймать эту самую чертову ошибку. Мне довольно часто приходилось видеть этот "кошмар прикладного программиста", когда большая и сложная программа валится на невнятном сообщении "выполнена недопустимая операция". И без знания АСМа понять, что случилось, невозможно.

Андрей Светлов

Языки ассемблеров были первыми "высокоуровневыми" языками по сравнению с программированием непосредственно в адресных кодах. В них появились:

Пример эквивалентных программ в адресных кодах, на ассемблере (GNU Assembler) и на C:
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF

push %ebp
mov  %esp, %ebp
sub  $0x8, %esp
movl $0x1, -4(%ebp)‏
sub  $0xc, %esp
push $0x0
call 8048348

int main()‏
{
    int i = 1;
    exit(0);
}
Еще один пример — классический Hello World
#include <stdio.h>
int main() 
{
    printf("hello, world");
    return 0;
};
преобразованный в ассемблер компилятором MSVC (cl 1.cpp /Fa1.asm):
CONST    SEGMENT
$SG3830    DB    'hello, world', 00H
CONST    ENDS
PUBLIC    _main
EXTRN    _printf:PROC
; Function compile flags: /Odtp
_TEXT    SEGMENT
_main    PROC
; File c:\...\1.cpp
; Line 4
    push    ebp
    mov    ebp, esp
; Line 5
    push    OFFSET $SG3830
    call    _printf
    add    esp, 4
; Line 6
    xor    eax, eax
; Line 7
    pop    ebp
    ret    0
_main    ENDP
_TEXT    ENDS
и компилятором GCC:
.file    “ctest.c”
.version “01.01”
gcc2_compiled.:
        .section         .rodata
.LC0:
        .string “Hello, world!\n”
.text
        .align 16
.globl  main
.type   main,@function
main:
        pushl    %ebp
        movl     %esp, %ebp
        subl     $8, %esp
        subl     $12, %esp
        pushl    $.LC0
        call     printf
        addl     $16, %esp
        subl     $12, %esp
        pushl    $0
        call     exit
.Lfe1:
        .size     main,.Lfe1-main
        .ident “GCC: (GNU) 2.96 20000731 (Linux-Mandrake 8.0 2.96-0.48mdk)”

Соглашения о вызовах

Это частично определяемый аппаратной архитектурой и частично конкретной средой исполнения механизм вызова подпрограмм. Соглашения включают ответы на вопросы: как передаются/возвращаются аргументы, как распределяется работа при вызове/завершении подпрограммы.

Конкретные примеры реализации:

Типичная структура вызова подпрограммы на архитектуре x86:
push eAX            ; последний аргумент — содержимое регистра eAX
push byte[eBP+20]   ; предпоследний аргумент — содержимое ячейки памяти по адресу = адрес в eBP + 20 байт
push 3              ; первый аргумент — константа 3
call calc           ; вызов подпрограммы calc, возвращаемое значение будет в eAX
Типичная структура подпрограммы:
calc:
  push eBP          ; сохранить указатель на стек
  mov eBP,eSP       ; получить новый указатель на стек
  sub eSP,4*3       ; выделить место для локальных переменных
  ...               ; произвести вычисления, в конце концов результат записать в eAX
  mov eSP,eBP       ; "освободить" место, занимаемое локальными переменными
  pop eBP           ; восстановить указатель на стек
  ret 0             ; вернуться в место вызова

Литература:


Язык ассемблера и исполняемые файлы

Ассемблер

Писать на АСМе сейчас практически не нужно. Но ВСЕГДА рано или поздно сталкивался с тем, что нужно уметь читать АСМ. Либо для того, чтобы сделать действительно оптимальный код, либо — что гораздо чаще — чтобы суметь правильно прочитать stack trace и поймать эту самую чертову ошибку. Мне довольно часто приходилось видеть этот "кошмар прикладного программиста", когда большая и сложная программа валится на невнятном сообщении "выполнена недопустимая операция". И без знания АСМа понять, что случилось, невозможно.

Андрей Светлов

Языки ассемблеров были первыми "высокоуровневыми" языками по сравнению с программированием непосредственно в адресных кодах. В них появились:

Пример эквивалентных программ в адресных кодах, на ассемблере (GNU Assembler) и на C:
55
89 E5
83 EC 08
C7 45 FC 01 00 00 00
83 EC 0C
6A 00
E8 D1 FE FF FF

push %ebp
mov  %esp, %ebp
sub  $0x8, %esp
movl $0x1, -4(%ebp)‏
sub  $0xc, %esp
push $0x0
call 8048348

int main()‏
{
    int i = 1;
    exit(0);
}
Еще один пример — классический Hello World
#include <stdio.h>
int main() 
{
    printf("hello, world");
    return 0;
};
преобразованный в ассемблер компилятором MSVC (cl 1.cpp /Fa1.asm):
CONST    SEGMENT
$SG3830    DB    'hello, world', 00H
CONST    ENDS
PUBLIC    _main
EXTRN    _printf:PROC
; Function compile flags: /Odtp
_TEXT    SEGMENT
_main    PROC
; File c:\...\1.cpp
; Line 4
    push    ebp
    mov    ebp, esp
; Line 5
    push    OFFSET $SG3830
    call    _printf
    add    esp, 4
; Line 6
    xor    eax, eax
; Line 7
    pop    ebp
    ret    0
_main    ENDP
_TEXT    ENDS
и компилятором GCC:
.file    “ctest.c”
.version “01.01”
gcc2_compiled.:
        .section         .rodata
.LC0:
        .string “Hello, world!\n”
.text
        .align 16
.globl  main
.type   main,@function
main:
        pushl    %ebp
        movl     %esp, %ebp
        subl     $8, %esp
        subl     $12, %esp
        pushl    $.LC0
        call     printf
        addl     $16, %esp
        subl     $12, %esp
        pushl    $0
        call     exit
.Lfe1:
        .size     main,.Lfe1-main
        .ident “GCC: (GNU) 2.96 20000731 (Linux-Mandrake 8.0 2.96-0.48mdk)”

Соглашения о вызовах

Это частично определяемый аппаратной архитектурой и частично конкретной средой исполнения механизм вызова подпрограмм. Соглашения включают ответы на вопросы: как передаются/возвращаются аргументы, как распределяется работа при вызове/завершении подпрограммы.

Конкретные примеры реализации:

Типичная структура вызова подпрограммы на архитектуре x86:
push eAX            ; последний аргумент — содержимое регистра eAX
push byte[eBP+20]   ; предпоследний аргумент — содержимое ячейки памяти по адресу = адрес в eBP + 20 байт
push 3              ; первый аргумент — константа 3
call calc           ; вызов подпрограммы calc, возвращаемое значение будет в eAX
Типичная структура подпрограммы:
calc:
  push eBP          ; сохранить указатель на стек
  mov eBP,eSP       ; получить новый указатель на стек
  sub eSP,4*3       ; выделить место для локальных переменных
  ...               ; произвести вычисления, в конце концов результат записать в eAX
  mov eSP,eBP       ; "освободить" место, занимаемое локальными переменными
  pop eBP           ; восстановить указатель на стек
  ret 0             ; вернуться в место вызова

Литература:

Форматы исполняемых файлов