Лекции.ИНФО


Материалы к лекции № 6. Ядерная физика для программистов, или Ядро Linux. (Сжато)



27)Каково назначение ядер ОС?

Ядро обеспечивает инфраструктуру для работы приложений и использования различных аппаратных средств. Это код низкого уровня, который взаимодействует с интерфейсами аппаратных средств, планирует и распределяет память и т. д.

28) К какому типу принадлежит ядро Linux?

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

Достоинства: Скорость работы, упрощённая разработка модулей.

Недостатки: Поскольку всё ядро работает в одном адресном пространстве, сбой в одном из компонентов может нарушить работоспособность всей системы.

29) Зачем нужны модули ядра Linux?

Расширение функциональности, введение ограничений. Ядро, способное поддерживать большое разнообразие аппаратных средств, будет слишком большим. Значительная часть кода не используется и лишь занимает память. Модули ядра позволяют при необходимости загрузить обеспечивающее поддержку программное обеспечение, такое как драйверы для аппаратных средств или файловые системы. Это позволяет запускать систему с небольшим ядром и затем подгружать модули по мере необходимости. Часто эта подгрузка происходит автоматически, например, при подключении устройств USB. Без поддержки модулей пришлось бы писать монолитные ядра и добавлять возможности прямо в ядро, при этом после добавления в ядро новых возможностей пришлось бы перезагружать систему.

30) В чём отличия модуля от программы?

Отличаются тем, какие функции принимают; модуль не имеет права не использовать никакие библиотеки; модули больше уязвимы для ошибок.

Модуль содержит код, расширяющий возможности ядра операционной системы. Это код низкого уровня и он работает в адресном пространстве ядра. Программы же работают в пространстве пользователя. Модули при компоновке могут использовать только функции ядра, приложения могут использовать любые сторонние функции. Заголовки только ядерные.

Программы пишутся на каком то языке, а модули на диалекте языка С. Для компиляции программ - какой-то компилятор, а здесь специальный компилятор который поддерживает расширение языка си. При сборке проги обычный файл сборки, здесь специальный написание makefile-ов разные.

31) Может ли модуль ядра наносить вред всему ядру?

Монолитному – да. Микроядру – нет. Оно устойчиво к сбоям оборудования, так как для каждого модуля существует свое адресное пространство.

32) В чём отличие ядер Linux, используемых в серверах и во встраиваемых системах?

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

33) Зачем точно нужный компонент ядра компилировать в виде модуля?

Чтобы использовать память, только тогда, когда нужна определенная функциональность.

34)Какие файлы используются после сборки ядра для его дальнейшего использования?

Образ ядра, модули, базовые драйвера, заголовочные файлы, файлы конфигурации (Inicrd, module, headers, system.map).

35)Почему надо делать привязку к ядру процессора?

Потому что у ядра есть кэш память и если раздельные потоки, то о ней можно забыть.

36) Для чего приложения создают с несколькими потоками?

Для скорость переключения

37) По какой лицензии должны распространяться модули ядра Linux?

Модули ядра не обязаны распространяться по какой-либо лицензии (например основные модули под CNU CPL, а драйвер видеокарты — по проприетарной лицензии) MODULE_LICENSE() сообщает ядру, под какой лицензией распространяется исходный код модуля, что влияет на то, к каким символам (функциям, переменным и т.д.) он может получить доступ в главном ядре. Модуль под лицензией GPLv2, GPL, GPL and additional rights, Dual BSD/GPL, Dual MPL/GPL, имеет доступ ко всем символам. Кроме того, дополнительно имеются следующие идентификаторы "Proprietary" (проприетарный, не свободный продукт). Другие лицензии модуля предупредят ядро о том, что был загружен закрытый модуль, или модуль, которому нельзя доверять. Модули без MODULE_LICENSE() распознаются ядром, как модули, выпущенные не под GPLv2. для установления авторства - MODULE_AUTHOR(), а для описания типов устройств, поддерживаемых модулем MODULE_SUPPORTED_DEVICE().

38) Зачем нужен механизм контроля версий для модулей?

Чтобы контролировать совместимость модуля с ядром. В случае возникновения проблем рекомендуется пересобрать ядро без поддержки механизма контроля версий

39) В чём отличие модулей ядра Linux от драйверов Linux?

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

40) Когда мы используем программу в различных процессах?

Если запускаем программу несколько раз.

*Процесс не является программой, но процесс и поток очень походи в linux. Процесс – вспомогательная программа, адресное пространство, набор ресурсов.

41) Модуль загружен. Какие функции в нём точно выполнились или выполнятся?

Функция инициализации выполнилась init_module() но сейчас уже можно называть как мы хотим, но тем не менее в основном называют так, функция завершения выполнится cleanup_modle(). Эти две функции обязательно должны быть в каждом модуле.

42) При написании модуля понадобилась уже разработанная функциональность. Как использовать её в модуле в виде библиотеки?

Мы можем собрать модуль с нужной нам функциональностью, затем собрать наш модуль, используя функциональность уже собранного модуля. В данном случае модуль будет зависим от модуля-«библиотеки». При запуске нашего модуля командой insmod может возникнуть ошибка, если не загружен модуль, от которого он зависит. Нужно либо самому следить за тем, какие модули загружены, либо можно использовать команду modprobe, которая автоматически загрузит модуль-«библиотеку» перед загрузкой нашего модуля.

43) В чём отличие системных вызовов от функций?

Функции находятся в пользовательском режиме, они обращаются к системным вызовам (которые работают в адресном пространстве ядра) и уже системные вызовы осуществляют нужное действие и возвращает управление функции. Пример функция pritnf()-> системный вызов write()

44) Зачем необходим переход приложения в пространство ядра?

Чтобы выполнять работу на уровне ядра. Часто приложениям нужен доступ к тем или иным ресурсам системы, за это отвечает ядро. Для того чтобы производить системный вызов. Printf()-> open(). Когда выполняется системный вызов приложение находится в пространстве ядра, это не страшно, т.к. системный вызов является частью ОС.

45) Чем отличаются адресное пространство ядра от адресного пространства модуля?

У операционной системы на монолитном ядре модули работают в адресном пространстве ядра (то есть ничем), у ОС на микроядре у каждого модуля есть свое адресное пространство.

46) В каких случаях в системе одному устройству могут соответствовать несколько файлов устройств (узлов), нескольким устройствам — один файл устройства, ни одному устройству — файл устройства, одному устройству — ни один файл устройства?

1. Если на жестком диске несколько разделов (каждому свой : sda1, sda2, sdb1, ..)

2. Если устройства имеют один контроллер и соответственно одинаковый драйвер, пример: массив видеокарт в SLI

3. Псевдоустройства: /dev/null — черная дыра, /dev/random — генератор случайных данных, /dev/urandom — генератор псевдослучайных данных, /dev/zero — генератор нулей

4. Если устройство не опознано или вообще не является устройством. / не установлено ПО для него.

47) Зачем файлу устройства младший номер?

Старший номер устройства говорит о том, какой драйвер, с каким файлом устройства связан. Он нужен ядру для того, чтобы определить какой драйвер нужно задействовать в данном случае. Ядро «не знает» о существовании младшего номера. Младший номер используется самим драйвером для идентификации устройства, если он обслуживает несколько таких устройств. (для того, чтобы различать разные устройства)

48) Что делает функция printf в модулях?

Модули не могут использовать функцию printf(). Для того, чтобы вывести что- либо на экран мы можем использовать функцию printk(). Она не выводит никуда сообщения. Не она занимается этим. Printk() порождает сообщение в ядре с определённым приоритетом с которым происходит ЧТО-ТО. Где-то записывается в журнал, где-то выводится на консоль, мы хз. Основное назначение этой функции – дать ядру механизм регистрации событий и предупреждений. Поэтому, каждый вызов printk() сопровождается указанием приоритета. Всего в ядре определено 8 различных уровней приоритета для функции printk() и каждый из них имеет свое макроопределение (имена уровней приоритета и их числовые значения можно найти в файле linux/kernel.h). Если уровень приоритета не указывается, то по умолчанию он принимается равным DEFAULT_MESSAGE_LOGLEVEL.

49) Чем компиляция модулей отличается от компиляции программ?

Для компиляции модулей нужны заголовочные файлы ядра. Дело в том, что модули ядра всегда собираются вместе с ядром, используя его заголовочные файлы, т.к. любое отклонение и несоответствие версий модуля и загруженного ядра ведет к невозможности загрузить этот модуль в ядро. (Приложение может использовать любые сторонние функции, модуль только функции ядра) Заголовки только ядерные. Программы пишутся на каком то языке, а модули на диалекте языка С. Для компиляции программ - какой-то компилятор, а здесь специальный компилятор который поддерживает расширение языка си. При сборке проги обычный файл сборки, здесь специальный написание makefile-ов разные.

50) Какие функции обязан поддерживать любой файл устройства?

Файл устройства не обязан поддерживать никакие функции.

51) В каких случаях два или более потока могут одновременно изменять одни и те же данные?

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

52) Как избавиться от критических участков?

Никак от них не избавиться. Но можно предотвратить состояние гонки возникающее из-за критических участков кода, см. вопрос 55) .

53) Почему состояние гонки сложно воспроизвести?

Потому что оно зависит от многих параметров и возникает неопределенно (Сложно, но МОЖНО) если очень постараться и если повезет (два потока направить на одну задачу, где изменяются данные и прогнать их пошагово)

54) Чем хорошо одновременное выполнение критических участков кода?

Ничем, оно не определено, и мы не знаем, что будет на выходе.

55) Как можно защититься от возникновения состояния гонки?

Использовать синхропримитивы: атомарные операции (часть кода подается на процессор целиком: либо выполняется полностью, либо не выполняется, ничто не может разделить операцию); семафоры (объекты, ограничивающие количество потоков, которые могут зайти в заданный участок кода); мьютекс (семафор, у которого количество потоков равно 1); спин-локи (примитив, который не допускает одновременного доступа к ресурсам при вытеснении кода ядра)

56) В каких случаях возможен истинный параллелизм на одноядерной однопроцессорной системе?

Hyper-threading (англ. Hyper-threading — Гиперпоточность, официальное название — Hyper-Threading Technology, HTT) — разработка компании Intel для реализации технологии «одновременной мультипоточности» (англ. Simultaneous multithreading) в процессорах на микроархитектуре NetBurst. Одно физическое ядро процессора (с включённым HT) операционная система определяет как два раздельных процессора. Физический Процессор способен запомнить состояние 2-х потоков, что вопринимается ОС-ой как наличие 2-х логических процессоров. Без этой технологии истинный параллелизм не возможен.

57) Какие преимущества вытесняемости кода ядра?

Возможность отключения модулей при зависании, например. Обеспечивает большую безопасность ядра/Для надёжности. Завис какой то процесс мы могли его убить. Также при вытесняемости ядра пользовательская задача может прервать работу ядра, даже если оно выполняет нечто сложное (чтобы избежать вызванных этим явных коллизий, ядро имеет части, которые не могут быть прерваны в процессе выполнения). Основной выигрыш, достигнутый этими изменениями, заключается в том, что улучшается интерактивная производительность(интерактивность) (например, при работе пользователя настольной системы) и таким образом система становится более чувствительной к таким вещам как пользовательский ввод. (Система более отзывчива, нет «голодающих» процессов)

58) Какие отличия средств защиты от состояния гонки при псевдо- и истинном параллелизме?

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

59) Каким образом критические участки могут участвовать в параллелизме?

Критические участки не должны участвовать в параллелизме – не должны выполняться один во время прерывания другого или одновременно. Они будут только мешать: получим состояние гонки.

60) По каким причинам может возникать параллелизм?

· Прерывания. Могут возникать асинхронно

· Вытеснение ядра. В режиме ядра одно задание может вытеснить другое

· Переход в состояние ожидания и синхронизация с пространством пользователя

· Симметричная многопроцессорность. Несколько процессоров могут выполнять код одновременно.

61) Какие недостатки применения атомарных операций?

Написание и поддержка кода становится сложнее, код не переносится на другие процессорные архитектуры (у каждой свой набор атомарных операций).

62) Чем отличаются поток, процесс, программа и задача(нити) на уровне ядра linux?

o Реализацию выполнения программного кода называют процессом (process) – абстрактное понятие в системах POSIX

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

– Счётчик команд (program counter)

– Стек выполнения

– Набор регистров процессора

o Поток в Linux:

– Процесс, использующий ресурсы (АП) совместно с другими процессами

– Имеет структуру task_struct

– Представляется для ядра обычным процессом

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

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

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

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

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

– Виртуальная память

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

o Потоки одного процесса:

– Совместно используют одну и ту же виртуальную память (адресное пространство)

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

• В Linux уникальная реализация потоков – это специальный тип процессов.

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

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

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

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

o Понятие задача и процесс взаимозаменяемы

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

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

o Процессы в системе

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

o Нить и задача

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

Помимо процессов описанного выше вида бывают еще «ущербные», порождаемые с помощью функции kernel_thread для внутренних системных нужд. У них нет параметров командной строки, как правило, они не имеют открытых файлов и т. д. Поскольку, несмотря на свою ущербность, эти процессы все равно фигурируют в списке задач, в литературе иногда различают полноценные процессы, порожденные из «пространства пользователя» (userspace), и задачи, т. е. все процессы, включая внутренние процессы ядра.

o Процесс и программа

Процессы, выполняющие разные программы, образуются благодаря применению имеющихся в стандартной библиотеке Unix функций «семейства exec»: execl, execlp, execle, execv, execve, execvp. Эти функции отличаются форматом вызова, но в конечном итоге делают одну и ту же вещь: замещают внутри текущего процесса исполняемый код на код, содержащийся в указанном файле.

Таким образом, операция запуска программы, которая в DOS и Windows выполняется как единое целое, в Linux (и в Unix вообще) разделена на две: сначала производится запуск, а потом определяется, какая программа будет работать.

63) Больший номер – позже запущен – почему это важно

 

 

*Однако, для обратной совместимости со старыми версиями ОС Unix и Linux максимальное значение этого параметра по умолчанию составляет всего лишь (что соответствует типу данных short int). Ядро хранит значение данного параметра в поле pid дескриптора процесса.

Это максимальное значение является важным, потому что оно определяет максимальное количество процессов, которые одновременно могут существовать в системе. Хотя значения 32768 и достаточно для офисного компьютера, для больших серверов может потребоваться значительно больше процессов. Чем меньше это значение, тем скорее нумерация процессов будет начинаться сначала, что приводит к нарушению полезного свойства: больший номер процесса соответствует процессу, который запустился позже. Если есть желание нарушить в системе обратную совместимость со старыми приложениями, то администратор может увеличить это макси мальное значение во время работы системы с помощью записи его в файл /ргос/ sys/kernel/pid_max.

64)Зачем тратить регистр на хранение описателя процесса?

Для ускорения процесса.

Чтобы знать все о процессе: содержит всю информацию об определённом процессе и данные, которые описывают выполняемую программу:

· Открытые файлы

· Адресное пространство

· Ожидающие обработку сигналы

· Состояние процесса

· Многое другое

Обычно в ядре на задачи ссылаются через указатель на описатель процесса.

65)Зачем создание процесса разбивать на два системных вызова?

Процессы, выполняющие разные программы, образуются благодаря применению имеющихся в стандартной библиотеке Unix функций «семейства exec»: execl, execlp, execle, execv, execve, execvp. Эти функции отличаются форматом вызова, но в конечном итоге делают одну и ту же вещь: замещают внутри текущего процесса исполняемый код на код, содержащийся в указанном файле.

Таким образом, операция запуска программы, которая в DOS и Windows выполняется как единое целое, в Linux (и в Unix вообще) разделена на две: сначала производится запуск, а потом определяется, какая программа будет работать. Есть ли в этом смысл и не слишком ли велики накладные расходы? Ведь создание копии процесса предполагает копирование весьма значительного объема информации.

Смысл в данном подходе определенно есть. Очень часто программа должна совершить некоторые действия еще до того, как начнется собственно ее выполнение. Скажем, в разбиравшемся выше примере мы запускали две программы, передающие друг другу данные через неименованный канал. Такие каналы создаются системным вызовом pipe; он возвращает пару файловых дескрипторов, с которыми в нашем случае оказались связаны стандартный поток ввода (stdin) программы wc и стандартный поток вывода (stdout) программы dd. Стандартный вывод wc (как, кстати, и стандартный ввод dd, хотя он никак не использовался) связывался с терминалом, а кроме того, требовалось, чтобы командный интерпретатор после выполнения команды не потерял связь с терминалом. Как удалось этого добиться? Да очень просто: сначала были отпочкованы процессы, затем проделаны необходимые манипуляции с дескрипторами файлов и только после этого вызван exe.

66)Что для системы дает копирование при записи?(см. вопрос 68)

67) Почему важно запускать порождаемый поток раньше родительского?

Это связано с копированием при записи. Это важно, т к при этом учитывается, что данные используются процессором совместно

Если возврат из функции copy_process() происходит успешно, то новый порожденный процесс возобновляет выполнение. Порожденный процесс намеренно запускается на выполнение раньше родительского.

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

68) Что дает системе COW?

В Linux – механизм копирования при записи (copyon-write, COW)

– Откладывает или вообще предотвращает копирование

– Вместо создания дубликата АП оба процесса могут использовать одну копию АП (м. б. > 10 Мб)

– При изменении данных создаётся копия

– Если никогда не делается запись (exec() сразу после fork()), то копирование не проводится вовсе

– fork() требует только:

• Копирование таблиц страниц

• Создание описателя процесса

Механизм копирования при записи (англ. Copy-On-Write, COW) используется для оптимизации многих процессов, происходящих в операционной системе, таких как, например, работа с памятью или файлами на диске (пример — ext3cow).

Главная идея copy-on-write — при копировании областей данных создавать реальную копию только когда ОС обращается к этим данным с целью записи.

Например, при работе UNIX-функции fork() вместо копирования выполняется отображение образа памяти материнского процесса в адресное пространство дочернего процесса, после чего ОС запрещает обоим процессам запись в эту память. Попытка записи в отображённые страницы вызывает исключение (exception), после которого часть данных будет скопирована в новую область.

69)Какие ОП пометки/какие данные процесса после завершения процесса?

Счетчик обнуляется, Остается: продолжает опис процессором структуры

Task_struct(находится в стеке на уровне ядра) thread_info – освоб. С пом. Родителей

70)Если род процесс завершился до завершения дочернего?

Если родительский процесс по какой-то причине завершится раньше дочернего, последний становится "сиротой" (orphaned process). "Сироты" автоматически "усыновляются" программой init, выполняющейся в процессе с номером 1, которая и принимает сигнал об их завершении.

71)Зачем при завершении обходить список порожденных процессов, находившихся в состоянии трассировки?

___________________________________________________________________________

При вызове fork() возникают два полностью идентичных процесса. Весь код после fork() выполняется дважды, как в процессе-потомке, так и в процессе-родителе.

Процесс-потомок и процесс-родитель получают разные коды возврата после вызова fork(). Процесс-родитель получает идентификатор (PID) потомка. Если это значение будет отрицательным, следовательно при порождении процесса произошла ошибка. Процесс-потомок получает в качестве кода возврата значение 0, если вызов fork() оказался успешным.

!Потомок наследует от родителя все кроме следующих признаков:

  • идентификатора процесса (PID, PPID);
  • израсходованного времени ЦП (оно обнуляется);
  • сигналов процесса-родителя, требующих ответа;
  • блокированных файлов (record locking).

Наследует:

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

Когда потомок вызывает exit(), код возврата передается родителю, который ожидает его, вызывая wait(). WEXITSTATUS() представляет собой макрос, который получает фактический код возврата потомка из вызова wait().

Функция wait() ждет завершения первого из всех возможных потомков родительского процесса. Иногда необходимо точно определить, какой из потомков должен завершиться. Для этого используется вызов waitpid() с соответствующим PID потомка в качестве аргумента. Еще один момент, на который следует обратить внимание при анализе примера, это то, что и родитель, и потомок используют переменную rv. Это не означает, что переменная разделена между процессами. Каждый процесс содержит собственные копии всех переменных.









Читайте также:

Последнее изменение этой страницы: 2016-03-25; Просмотров: 107;


lektsia.info 2017 год. Все права принадлежат их авторам! Главная