Последовательные и параллельные порты.

После́довательный порт. Порт называется «последовательным», так как информация через него передаётся по одному биту, последовательно бит за битом. Некоторые интерфейсы компьютера (например, Ethernet, FireWire и USB) используют последовательный способ обмена информацией.

Паралле́льный порт — тип интерфейса, разработанный для компьютеров (персональных и других) для подключения различных периферийных устройств. В вычислительной технике параллельный порт является физической реализацией принципа параллельного соединения. До появления USB параллельный интерфейс был адаптирован помимо принтеров к большому числу периферийных устройств.

USB (Universal Serial Bus — «универсальная последовательная шина») — последовательный интерфейс передачи данных для периферийных устройств в вычислительной технике. ХАРАКТЕРИСТИКИ???

• Устройства подразделяются на две большие группы -- блочные и символьные

• Основное различие – обмен данными с блочным устройством производится порциями байт – блоками

• Они имеют внутренний буфер, благодаря чему повышается скорость обмена

• Символьные же устройства – это лишь каналы передачи информации, по которым данные следуют последовательно, байт за байтом

• Большинство устройств относятся к классу символьных, поскольку они не ограничены размером блока и не нуждаются в буферизации Если первый символ в списке, полученном командой ls -l /dev, 'b', тогда это блочное устройство, если 'c', тогда – символьное

ДРАЙВЕРЫ УСТРОЙСТВ

Драйверы устройств являются одной из разновидностей модулей ядра. Например, драйвер звуковой платы связывает файл устройства /dev/sound со звуковой платой. Пользовательское приложение может использовать для своей работы /dev/sound, ничего не подозревая о типе установленной звуковой платы.

Одному устройству могут соответствовать несколько файлов устройств (узлов) - 1, нескольким устройствам — один файл устройства - 2, ни одному устройству — файл устройства - 3, одному устройству — ни один файл устройства - 4.

1) Если на одном жестком диске несколько разделов (каждому разделу – файл)

2) Если устройства имеют одинаковый контроллер и соответственно одинаковый драйвер

3) /dev/null (вся информация, попавшая туда, пропадает) /dev/random (для устройств генерирующих рандомные данные) /dev/urandom (для устройств генерирующих псевдорандомные данные) /dev/zero (устройство генерирует нули)

4) Если устройство не опознано или вообще не является устройством.

МЛАДШИЙ И СТАРШИЙ НОМЕРА

• Все файлы устройств создаются в процессе установки системы с помощью утилиты mknod

• Чтобы создать новое устройство, например, с именем "coffee", со старшим номером 12 и младшим номером 2, нужно выполнить команду mknod /dev/coffee c 12 2

• Когда происходит обращение к файлу устройства, ядро использует старший номер файла, для определения драйвера, который должен обработать это обращение • Это означает, что ядро в действительности не использует и даже ничего не знает о младшем номере • Единственный, кто обеспокоен этим – это сам драйвер • Он использует младший номер, чтобы отличить разные физические устройства.

НАПИСАНИЕ ДРАЙВЕРА

-лицензия GPL - GNU Public License

-структура file_operations

• Определена в файле linux/fs.h • Содержит указатели на функции драйвера, которые отвечают за выполнение различных операций с устройством • Например, практически любой драйвер символьного устройства реализует функцию чтения данных из устройства, и адрес этой функции, среди всего прочего, хранится в этой структуре. • Компилятор gcc предоставляет программисту довольно удобный способ заполнения полей структуры в исходном тексте • Ниже приводится пример подобного заполнения:

struct file_operations fops = {.read=device_read, .write=device_write, .open=device_open, .release=device_release};

• Добавление драйвера в систему подразумевает его регистрацию в ядре • Это означает - получение старшего номера в момент инициализации модуля • Получить его можно вызовом функции register_chrdev(), определенной в файле linux/fs.h

int register_chrdev(unsigned int major, const char *name, struct file_operations *fops);

• где unsigned int major -- это запрашиваемый старший номер устройства, • const char *name -- название устройства, которое будет отображаться в /proc/devices • struct file_operations *fops -- указатель на таблицу file_operations драйвера

IOCTL

последовательные и параллельные порты. - student2.ru • В POSIX следует использовать специальную функцию с именем ioctl (сокращенно от Input Output ConTroL) • Любое устройство может иметь свои команды ioctl, которые могут читать (для передачи данных от процесса ядру), писать (для передачи данных от ядра к процессу), и писать и читать, и ни то ни другое • Функция ioctl вызывается с тремя параметрами: дескриптор файла устройства, номер ioctl и третий параметр, который имеет тип long, используется для передачи дополнительных аргументов

УПРАВЛЕНИЕ ПРОЦЕССАМИ. ВЫПОЛНЕНИЕ ПРОГРАММНОГО КОДА. ОСНОВНЫЕ ПОНЯТИЯ

Выполнение программного кода включает: – Сегмент текста (text section) – объектный код, находящийся в состоянии исполнения – Сегмент данных (data section) – глобальные переменные – Набор ресурсов (открытые файлы, ожидающие обработку сигналы) – Адресное пространство – Не менее одного потока выполнения (execution thread) • Реализацию выполнения программного кода называют процессом(process) – абстрактное понятие в системах POSIX

Поток выполнения – объект, выполняющий операции внутри процесса:

– Счётчик команд (program counter) – Стек выполнения – Набор регистров процессора

• Ядро планирует выполнение отдельных потоков, а не процессов • Раньше отдельный процесс содержал только один поток • Процесс, содержащий несколько потоков, называется многопоточным (многопоточной программой) • В Linux уникальная реализация потоков – это специальный тип процессов

Каждый процесс предусматривает наличие двух виртуальных ресурсов:

– Виртуальный процессор

• Создаёт для процесса иллюзию монопольного использования всей вычислительной системы (ВС)

• Физическим процессором совместно пользуются десятки процессов

• Эта виртуализация и помещение процессов на физический процессор называется планированием процессов – Виртуальная память

• Создаёт для процесса иллюзию того, что процесс один располагает всей памятью ВС

Потоки одного процесса: – Совместно используют одну и ту же виртуальную память (адресное пространство) – Каждый получает свой виртуальный процессор

Программа не является процессом

–Процесс – выполняющаяся программа + набор ресурсов + адресное пространство (АО)

– Несколько процессов могут выполнять одну и ту же программу

– Несколько процессов могут использовать одни и те же ресурсы (файлы, АО)

В Linux создание процесса осуществляется с помощью системного вызова fork() (fork – ветвление)

– новый процесс создаётся путём полного копирования уже существующего

• Вызвавший fork() процесс – родительский (parent) – Продолжает выполнение

• Новый процесс – дочерний (child) – Начинает выполняться с места возврата системного вызова

• Функции exec* () – создание нового адресного пространства и загрузка в неё новой программы

• Выход из программы – системный вызов exit() – Завершает процесс – Освобождает все занятые ресурсы

• Запрос о состоянии порождённых процессов – wait4() – один процесс ожидает завершение другого (wait, waitpid, wait3, wait4)

• Состояние процесса, когда он завершён, но порождающий процесс ещё не вызвал wait() или waitpid() – зомби

• Понятие задача и процесс взаимозаменяемы – Процессы в ядре называют задачами(tasks) (представление работающей программы в ядре) – Представление работающей программы в режиме пользователя – процесс.

ОПИСАТЕЛЬ ПРОЦЕССА

• Двусвязный список с информацией обо всех процессах – список задач(task list)

• Элемент списка – описатель процесса(process descriptor) – Содержит всю информацию об определённом процессе и данные, которые описывают выполняемую программу: • Открытые файлы • Адресное пространство • Ожидающие обработку сигналы • Состояние процесса и т.д.

ВЫДЕЛЕНИЕ ОПИСАТЕЛЯ:

• До 2.6 – task_struct – в конце стека ядра процессора • Местоположение описателя процесса – значение регистра указателя стека (stack pointer) (без использования дополнительных регистров).

• В серии ядер 2.6 – Память для task_struct – с помощью подсистемы выделения памяти – плиточного распределителя (slab allocator)

– Для каждой задачи введена структура thread_info • Хранится в области дна стека при стеке, растущем в сторону уменьшения значения адреса, или вершины – если в сторону увеличения • Поле task структуры является указателем на task_struct – описатель процесса.

• Ядро определяет процессы с помощью уникального значения идентификатора процесса (process identification value, PID) – Поле pid описателя процесса – PID – целое число (скрытый тип pid_t ~ int)

– Для обратной совместимости макс. 32768 • Макс. кол-во процессов • Мало для серверов • Чем меньше pid_max, тем скорее нумерация начнётся с начала – нарушение свойства «больший номер – позже запущен»

• Обычно в ядре на задачи ссылаются через указатель на описатель процесса • Макрос current – описатель процесса, выполняющегося в данный момент.

СОСТОЯНИЕ ПРОЦЕССА:

• Текущее состояние процесса описывается полем stateописателя процесса

• Каждый процесс в одном из 5 состояний – TASK_RUNNUNG – запускаем (runnable) – TASK_INTERRUPTIBLE – приостановлен (sleeping) – заблокирован в ожидании некоторого условия, может возобновиться при получении сигнала – TASK_UNINTERRUPTIBLE – приостановлен (sleeping) – заблокирован в ожидании некоторого условия, не может возобновиться при получении сигнала – TASK_ZOMBIE – завершён, порождающий процесс ещё не вызвал wait4() – TASK_STOPED – остановлен

• Изменение состояния – set_task_state(task, state) (с установкой барьера памяти для SMP- систем) – task->state = state – set_current_state(state) или set_task_state(current, state)

КОНТЕКСТ ПРОЦЕССА

• Исполняемый программный код – Считывается из исполняемого файла (executable file) – Выполняется в адресном пространстве процесса • Обычно в пространстве пользователя – Если выполняется системный вызов или возникает исключительная ситуация – входит в пространство ядра – При выходе – работа в пространстве пользователя (только если не появляется более приоритетный процесс, иначе активизируется планировщик).

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

ДЕРЕВО ПРОЦЕССОВ.

• Иерархия: все процессы – потомки процесса init (PID=1) – Запуск init – на последнем шаге загрузки ядра • init читает системные сценарии инициализации (initscripts) • Выполняет другие программы для загрузки системы • Каждый процесс имеет: – Один порождающий (родительский) процесс. task_struct -> parent – Может порождать множество процессов • Процессы от одного родительского – родственные (siblings) • Список task_struct -> children • Каждый процесс имеет свой PID и PPID (Parent Process ID) • init: PPID = PID = 1

• Указатель на следующее задание в списке задач list_entry(task->tasks.next, struct task_struct, tasks); или макрос next_task(task) • Указатель на предыдущее задание в списке задач list_entry(task->tasks.prev, struct task_struct, tasks); или макрос prev_task(task)

• Цикл по всему списку задач

struct task_struct *task;

for_each_process(task) { printk("%s[%d] \n", task->comm, task->pid); }

СОЗДАНИЕ НОВОГО ПРОЦЕССА

• В большинстве ОС – метод порождения (spawn) – Создаётся процесс в новом адресном пространстве – В него считывается исполняемый файл – Начинается исполнение процесса • В системах POSIX – операции разбиваются на две: fork() – порождается процесс-копия текущего задания с отличиями: • PID • PPID (PID родителя) = PID порождающего процесса • Ресурсы (без ожидающих на обработку сигналов) • Статистика использования ресурсов. exec*() (в Linux через системный вызов execve()) • загружает исполняемый файл в адресное пространство процесса и исполняет его • fork () + exec*() – аналог единой функции в других ОС

КОПИРОВАНИЕ ПРИ ЗАПИСИ

• В системах POSIX при fork() – дубликат всех ресурсов родительского процесса • В Linux – механизм копирования при записи (copy-on-write, COW). Откладывает или вообще предотвращает копирование. Вместо создания дубликата адресного пр-ва (АП) оба процесса могут использовать одну копию АП (м. б. > 10 Мб). При изменении данных создаётся копия. Если никогда не делается запись (exec() сразу после fork()), то копирование не проводится вовсе. fork() требует только: • Копирование таблиц страниц • Создание описателя процесса.

Важно, чтобы порождённый процесс запускался на выполнение раньше порождающего. Это позволяет избежать накладных расходов, связанных с тем, что если родительский процесс начинает выполняться первым, то он будет ожидать возможности записи в АП посредством механизма копирования при записи.

РАБОТА COPY_PROCESS

• dup_task_struct(), создаёт

– Стек ядра; – Копирование task_info, task_struct

• Не много ли процессов для пользователя?

• Начальные значения описателя

• В состояние TASK_UNINTERRUBLE

• copy_flags() – Обновляет значения поля flags (в task_struct); – Сброс права суперпользователя; – Установка PF_FORKNOEXEC (ещё не вызывана exec())

• get_pid() – получение PID

• В зависимости от флагов-аргументов clone() Копирование или сомв. использование – Открытых файлов – Информации о файловой системе – Обработчиков сигналов – АП – Пространства имён (namespace)

• Разделение оставшейся части кванта времени между родит. и дочерним процессами

• Зачистка структур данных • Возврат указателя на описатель нового процесса

РЕАЛИЗАЦИЯ ПОТОКОВ

Согласованное программирование(concurrent programming) – программирование, при к. ПО разрабатывается как набор взаимодействующих вычислительных процессов, к. могут исполнятся параллельно

Многопоточность — обеспечивает выполнение нескольких потоков в совместно используемом адресном пространстве памяти. Потоки также могут совместно использовать открытые файлы и другие ресурсы. Многопоточность используется для параллельного программирования (concurrent programming), что на многопроцессорных системах обеспечивает истинный параллелизм.

Реализация потоков в операционной системе Linux уникальна. Для ядра Linux не существует отдельной концепции потоков. В ядре Linux потоки реализованы так же, как и обычные процессы. В ОС Linux нет никакой особенной семантики для планирования выполнения потоков или каких-либо особенных структур данных для представления потоков. Поток— это просто процесс, который использует некоторые ресурсы совместно с другими процессами. Каждый поток имеет структуру task_struct и представляется для ядра обычным процессом (который совместно использует ресурсы, такие как адресное пространство, с другими процессами). Для операционной системы Linux потоки — это просто способ совместного использования ресурсов несколькими процессами.

• Некоторые ОС имеют явные средства поддержки потоков в ядре: – Microsoft Windows, Oracle Solaris. – Потоки называются легковесными процессами. – процессами с быстрым переключением контекста.

СОЗДАНИЕ ПОТОКОВ В ЯДРЕ

Потоки создаются так же, как и обычные задания, за исключением того, что в системный вызов clone() передаются флаги с указанием, какие ресурсы должны использоваться совместно:

clone(CLONE_VM | CLONE_FS | CLONE_FILES | CLONE_SIGHAND, 0);

– Результат – как при вызове fork(), но общие:

• АП • Ресурсы файловой системы (ФС) • Описатели файлов • Обработчики сигналов

ФЛАГИ:

• CLONE_FILES – общие открытые файлы • CLONE_FS – общая информация о ФС • CLONE_IDLETASK – PID = 0 (холостые idle задачи) • CLONE_NEWNS – новое пространство имён • CLONE_PARENT – наследуется родитель • CLONE_PTRACE – трассировать и порождённый процесс • CLONE_SETTLS – создать новую область локальных данных потока (thread local storage) • CLONE_SIGHAND – общие обработчики сигналов • CLONE_THREAD – оба процессов принадлежат одной группе потоков • CLONE_VFORK – vfork(): род. процесс приостанавливается, пока порождённый его не возобновит • CLONE_UNTRACED – запретить родителю использовать CLONE_PTRACE для порождённого процесса • CLONE_STOP – запустить процесс в состоянии TASK_STOPED • CLONE_VM – общее АП

ПОТОКИ ЯДРА

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

int kernel_thread(int (*fn)(void*), void* arg, unsigned long flags);

Новая задача создается с помощью обычного системного вызова clone() с соответствующими значениями флагов, указанными в параметре flags. При возврате из системного вызова родительский поток режима ядра завершается и возвращает указатель на структуру task_struct порожденного процесса. Порожденный процесс выполняет функцию, адрес которой указан в параметре fn, в качестве аргумента этой функции передается параметр arg. Для указания обычных флагов потоков пространства ядра существует флаг CLONE_KERNEL, который объединяет в себе флаги CLONE_FS, CLONE_FILES и CLONE_SIGHAND, так как большинство потоков пространства ядра должны указывать эти флаги в параметре flags.

ЗАВЕРШЕНИЕ ПРОЦЕССА

Когда процесс завершается, ядро должно освободить ресурсы, занятые процессом, и оповестить процесс, который является родительским для завершившегося, о том, что его порожденный процесс, к сожалению, "умер". Обычно уничтожение процесса происходит тогда, когда процесс вызывает системный вызов exit() явно или неявно при выходе из главной функции программы (компилятор языка С помещает вызов функции exit() после возврата из функции main()). Процесс также может быть завершен непроизвольно. Это происходит, когда процесс получает сигнал или возникает исключительная ситуация, которую процесс не может обработать или проигнорировать. Независимо от того, каким образом процесс завершается, основную массу работы выполняет функция do_exit(), а именно указанные далее операции.

• Устанавливается флаг PF_EXITING в поле flags структуры task struct.

• Вызывается функция del_timer_sync(), чтобы удалить все таймеры ядра.

• Если включена возможность учета системных ресурсов, занятых процессами, то вызывается функция acct_process() для записи информации об учете ресурсов.

• Вызывается функция __exit_mm() для освобождения структуры mm_struct, занятой процессом.

• Вызывается функция exit_sem(). Если процесс находится в очереди ожидания на освобождение семафора подсистемы IPC, то в этой функции процесс удаляется из этой очереди.

• Вызываются функции __exit_files(), __exit_fs(), exit_namespace() и exit_signals() для уменьшения счетчика ссылок на объекты, которые отвечают файловым дескрипторам, данным по файловой системе, пространству имен и обработчикам сигналов соответственно.

• Устанавливается код завершения задания, который хранится в поле exit_code структуры task struct.

• Вызывается функция exit_notify(), которая отправляет сигналы родительскому процессу завершающегося задания и назначает новый родительский процесс (reparent) для всех порожденных завершающимся заданием процессов, этим процессом становится или какой-либо один поток из группы потоков завершающегося процесса, или процесс init. Состояние завершающегося процесса устанавливается в значение TASK_ZOMBIE.

• Вызывается функция schedule() для переключения на новый процесс.

После возврата из функции do_exit() дескриптор завершенного процесса все еще существует в системе, но процесс находится в состоянии TASK_ZOMBIE и не может выполняться. Как уже рассказывалось выше, это позволяет системе получить информацию о порожденном процессе после его завершения. Следовательно, завершение процесса и удаление его дескриптора происходят в разные моменты времени. После того как родительский процесс получил информацию о завершенном порожденном процессе, структура task_struct порожденного процесса освобождается. Когда приходит время окончательно освободить дескриптор процесса, вызывается функция release_task() (декремент счетчика ссылок, освобождение страниц памяти со структурой thread_info и кэша со структурой task_struct).

ПЛАНИРОВАНИЕ

Планировщик (scheduler) — это компонент ядра, который выбирает из всех процессов системы тот, который должен выполняться следующим. Таким образом, планировщик (или, как еще его называют, планировщик выполнения процессов) можно рассматривать как программный код, распределяющий конечные ресурсы процессорного времени между теми процессами операционной системы, которые могут выполняться. Планировщик является основой многозадачных (multitasking) операционных систем, таких как ОС Linux. Принимая решение о том, какой процесс должен выполняться следующим, планировщик несет ответственность за наилучшее использование ресурсов системы и создает впечатление того, что несколько процессов выполняются одновременно. Эффективное использование процессора (процессорного времени) – всегда выполняется какой-нибудь процесс. Планировщик планирует работоспособные процессы (runnable). Неработоспособные процессы блокируются ядром до наступления нужного им события, становясь работоспособными.

Все действия по выбору следующего задания на исполнение и переключение на выполнение этого задания реализованы в виде функции schedule(). Эта функция вызывается явно кодом ядра при переходе в приостановленное состояние (sleep), a также в случае, когда какое-либо задание вытесняется. Функция schedule() выполняется независимо каждым процессором. Следовательно, каждый процессор самостоятельно принимает решение о том, какой процесс выполнять следующим.

ВИДЫ МНОГОЗАДАЧНЫХ ОС

Многозадачные операционные системы бывают двух видов: системы с кооперативной многозадачностьюи системы с вытесняющей многозадачностью. Операционная система Linux Unix и других современных операционных систем, обеспечивает вытесняющую многозадачность. В системе с вытесняющей многозадачностью решение о том, когда один процесс должен прекратить выполнение, а другой возобновить его, принимает планировщик. Событие, заключающееся в принудительном замораживании выполняющегося процесса, называется вытеснением этого процесса. Период времени, в течение которого процесс выполняется перед тем, как будет вытеснен называется квантом времени процесса. При этом, кроме всего прочего, предотвращается возможность монопольного использования ресурсов всей системы одним процессом. Как будет показано далее, величины квантов времени в операционной системе Linux рассчитываются динамически, что позволяет получить некоторые интересные преимущества.

В системах с кооперативной многозадачностью процесс продолжает выполняться до тех пор, пока он добровольно не примет решение о прекращении выполнения. Событие, связанное с произвольным замораживанием выполняющегося процесса, называется передачей управления. У такого подхода очень много недостатков: планировщик не может принимать глобальные решения относительно того, сколько процессы должны выполняться; процесс может монополизировать процессор на большее время, чем это необходимо пользователю; "зависший" процесс, который никогда не передает управление системе, потенциально может привести к неработоспособности системы. Наиболее известным исключением является операционная система Mac OS версии 9 и более ранних версий.

ВИДЫ ПРОЦЕССОВ

• Ограниченные вводом/выводом(I/Obound) – большую часть времени работают с вводом- выводом – как правило, могут выполняться в течение короткого периода • Желательно планировать часто • Примеры: приложения по работе с системами хранения, клавиатурой и т. п.

• Ограниченные процессором (processor-bound) – большую часть времени исполняют программный код – как правило, выполняются до вытеснения • Желательно планировать реже • Пример: вычисления, не требующие частых операций ввода/вывода

• Классификация условна • Сервер X Window – имеет частые операции ввода/вывода, но и сильно нагружает процессор • Vim – имеет редкие операции ввода/вывода – периодически нагружает процессор для автоматизации и поддержки синтаксиса.

ПРОБЛЕМЫ ПЛАНИРОВАНИЯ

• Нужно одновременно обеспечивать: – малую задержку (low latency) процессов — часто переключать процессы – высокую производительность (throughput) — позволять процессам работать дольше • В Linux процессы, ограниченные вводом/выводом имеют больший приоритет

ПРИОРИТЕТ ПРОЦЕССА

• Одни из алгоритмов планирования: – с управлением по приоритетам (priority-based) – c динамическим управлением по приоритетам (dynamic priority-based) — в Linux • Процессы с более высоким приоритетом: – выполняются раньше остальных – между собой планируются циклически (round-robin) – получают более длительный квант времени – вытесняют процессы, с меньшим приоритетом • Можно менять значение приоритета процесса

ВЫБОР КВАНТА ВРЕМЕНИ

• Больший квант времени — меньшая интерактивность приложения • Меньший квант времени — частое переключение процессов — увеличение накладных расходов • Процессам, ограниченным вводом/выводом, — не требуется продолжительный квант времени • Процессам, ограниченным процессором, — точно требуется продолжительный квант времени

• Квант (timeslice, quantum, processor slice) • Минимум — 5 мс (меньший приоритет или меньшая интерактивность) • По умолчанию — 100 мс • Максимум — 800 мс (больший приоритет или большая интерактивность) • Кванты времени определяются динамически от приоритета • Квант может расходоваться не за раз, а по частям — гарантия выполнения интерактивных задач в течение долгого времени • Пример: обозреватель и проигрыватель.

ПАРАЛЛЕЛИЗМ

• Несколько потоков (выполняющихся кодов) могут одновременно работать с одними данными – совместно используемыми ресурсами – Переписывать изменения других потоков – Обращаться к несогласованным ресурсам • Одновременный доступ к данным – нестабильность системы, причины сложно обнаружить.

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

КРИТИЧЕСКИЕ УЧАСТКИ

• Критические участки (critical regions) – ветки кода, совместно использующие данные • Состояние гонки (race condition) – два потока одновременно находятся в критическом участке – Возникает не определённо (undetermined) – Трудно воспроизвести – сложно обнаружить при отладке • Для предотвращения состояний гонок код д. б. атомарным (atomic) (без перерывов)

Наши рекомендации