Страницы

Поиск по вопросам

четверг, 9 января 2020 г.

Многозадачность Linux

#linux #unix


Читал книгу про устройство Linux. Прочитал про многозадачность следующее: 

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


Процессор (реальное аппаратное средство) прерывает текущий процесс, опираясь на внутренний
таймер, переключается в режим ядра и возвращает ему управление.
Ядро записывает текущее состояние процессора и памяти, которые будут необходимы для
возобновления только что прерванного процесса.
Ядро выполняет любые задачи, которые могли появиться в течение предыдущего кванта
времени (например, сбор данных или операции ввода/вывода).
Теперь ядро готово к запуску другого процесса. Оно анализирует список процессов,
готовых к запуску, и выбирает какой либо из них.
Ядро готовит память для нового процесса, а затем подготавливает процессор.
Ядро сообщает процессору, сколько будет длиться квант времени для нового процесса.
Ядро переводит процессор в режим пользователя и передает процессору управление.
Переключение контекста дает ответ на важный вопрос: когда работает ядро? Ответ следующий:
ядро работает между отведенными для процессов квантами времени, когда происходит переключение
контекста.


Не понятен пункт 3. Что делает ядро в данный момент? Оно выполняет грубо говоря функцию
рендера? (обновляет отображение положения курсора, инициализирует процессы программ,
которые запустил пользователь или выводит информацию на экран и т.д.)

И еще в книге было написано, что ядро - это программа в памяти. Что тогда значит
процесс в режиме ядра и режиме пользователя? 

В книге есть пояснение: (кратко) в режиме ядра - нет ограничений, а в режиме пользователя
- есть.
    


Ответы

Ответ 1



Описанная последовательность действий — это скорее классическая «ложь для детей», нежели реальное описаниеустройства, в ней много неточностей, упрощений и она частично не соответствует действительности. Я бы обругал автора за такое, но т.к. слова взяты из обзорного раздела книги, да и книга не то чтобы о самом ядре, думаю можно простить. Предложу от себя более точное описание переключения процессов: Периодически происходит прерывание по таймеру. Таймер — это [обычно] внешнее по отношению к процессору устройства, на современной архитектуре x86 доступно 3 варианта — классический i8254 (он же PIT), таймер в локальный APIC (основной источник большинства x86 систем в наши дни, является частью ядра процессора) и высокоточный HPET. Прерывание происходит ~100—1000 раз в секунду — настраивается при сборке ядра, см. CONFIG_HZ и HZ в man 7 time (или может реже, см. CONFIG_NO_HZ). При возникновении прерывания процессор переключает контекст в режим ядра (на x86 загружает [SS, ESP,] CS и IP из [TSS/]IDT) и сохраняет информацию необходимую для возврата информацию (на x86 — это [SS, ESP,] EFLAGS, CS и EIP), после чего ядро сохраняет в своём стеке основные регистры пользовательского пространства 1 (на x86 — E[ABCD]X, E[SD]I и [GFED]S). Обработчик прерывания делает кое-какие мелкие домашние дела вроде обновления системного времени, а затем проверяет, не превысил ли текущий процесс свой квант времени 2; если превысил, то он помечается для дальнейшего перепланирования (устанавливается флаг TIF_NEED_RESCHED); затем обработчик дальше занимается другими своими заботами вроде обновления loadavg. По завершении обработчика прерывания ядро проверяет нет ли так называемых «мягких прерываний» (softirq, функции отложенного вызова), требующих обработки и в зависимости от настроек ядра или сразу их обрабатывает или будит поток ядра, который этим занимается Перед самым возвратом в пространство пользователя происходит проверка, установлен ли флаг описанный выше и если да, то процесс отдаётся на растерзания schedule() — входной точки планировщика. Далее собственно планировщик, пользуясь замысловатыми эвристиками выбирает, на какой процесс переключиться. И вызывает context_switch — это единственная легальная точка, позволяющая переключиться в контекст другого процесса. Ядро переключает адресное пространства процесса (на x86 — загрузка адреса таблицы страниц связанной с процессом в CR3). Далее происходит собственно переключение контекста на новый процесс. Самим моментом переключения можно считать сохранение старого указателя на стек в структуру связанную со старым процессом и загрузку нового. Прелесть здесь в том, что чем бы ни занимался до этого новый процесс, освободить процессор он мог только именно в этом месте, и стек именно такой, каким он оставил его с теми же адресами возврата и теми же текущими регистрами, сохранёнными двумя строчками выше, так что при дальнейшем возврате из функций он сможет спокойно продолжить заниматься своими делами. Далее ядро, выполняясь в контексте уже нового процесса, сохраняет остатки старого контекста, такие как специальные регистры, вроде регистров FPU, MMX и SSE, обновляет данные о новом стеке ядра в TSS, загружает новые значения спец. регистров и официально провозглашает новый процесс текущим. Далее процесс, в зависимости от того, что он делал до того, как его прервали, может продолжить работу в режиме ядра или вернуть управление пользовательскому пространству, восстановив регистры общего назначения, которые были сохранены аналогично пунктам (1) и (2). Т.е. к чему это я всё... Ядро сообщает процессору, сколько будет длиться квант времени для нового процесса. Это не верно, такое поведение отчасти вводится CONFIG_NO_HZ_FULL и на каком-то этапе планировалось ввести его повсеместно, сначала для одпроцессорных систем, а затем для всех, но оказалось, что оно создаёт значительное количество разнообразных сложностей, так что пока оно остаётся «для тех кто запускает приложения реального времени определённого типа». Также эта опция требует дополнительных телодвижений от пользователя для активации. Ядро работает между отведенными для процессов квантами времени, когда происходит переключение контекста. Это также не верно. Ядро начинает работу при любом прерывании/системном вызове (которые вызывают переключение контекста в режим ядра), безотносительно, закончился ли квант времени какого-либо процесса или нет, он может в любой момент быть вытеснен ядром. Не понятен пункт 3. Что делает ядро в данный момент? Если подразумевать, что в пункте (3) автор подразумевал обработку softirq, то они наряду с такими примитивами, как тасклеты (tasklet, которые обрабатываются также по средствам softirq) используются для широкого круга задач, полный список составить пожалуй невозможно, но вот некоторые из них: Вызов событий по запросу обычных таймеров ядра, используются много-где, хотя во многом сейчас они заменены высокоточными таймерами (см. CONFIG_HIGH_RES_TIMERS) не зависящими от периодических тиков. DMA ввод/вывод Приём/отправка пакетов сетевой карты SCSI команды И много-чего ещё в десятках различных драйверов Но на самом деле, на большинстве современных ядер (см. CONFIG_IRQ_FORCED_THREADING) конкретно в этот момент (момент проверки, есть ли необработанные softirq) происходит только одно — пробуждение потока ядра3 softirqd/n, который впоследствии может быть выбран в schedeule() наравне с другими процессами. Оно выполняет грубо говоря функцию рендера? Нет, за небольшим исключением (см. фреймбуферная консоль) ядро вообще не занимается отрисовкой. Низкоуровневые функции рендеринга отдаются или на откупе видеокарте/BIOS'у, или выполняются программно, например X-сервером или графическими библиотеками. Высокоуровневые, как управление положением курсора, окон на экране, применением эффектов, 3d-сценами — выполняются только приложениями пространства пользователя, а ядро — лишь тонкая прослойка между ними передающая данные через специальные интерфейсы. (обновляет отображение положения курсора, инициализирует процессы программ, которые запустил пользователь или выводит информацию на экран и т.д.) На всё «нет», за исключением, что «инициализирует процессы программ», но не во время прерывания по таймеру, а только когда другой процесс сознательно попросил это через системные вызовы семейств fork()/ exec(). И еще в книге было написано, что ядро - это программа в памяти. Для определения не подходит, но на уровне «пельмени — это еда в холодильнике», пожалуй, верно. Что тогда значит процесс в режиме ядра и режиме пользователя? В книге есть пояснение: (кратко) в режиме ядра - нет ограничений, а в режиме пользователя - есть. Это достойно отдельного вопроса, но если кратко, то с каждым процессом в ОС ассоциирован свой контекст (стек, регистры) ядра и контекст пользователя, когда процесс выполняется в соответствующем контексте говорят, что он выполняется в режиме ядра/пользователя. Переключение в режим ядра происходит при выполнении прерывания/системного вызова. Основная суть различий в книжке раскрыта, «ограничения» в основном включают, выполнение определённых машинных инструкций, которые позволяют получить прямой доступ к оборудованию, а также доступ к некоторым областям памяти. ¹ Вообще говоря, прерывание может возникнуть и во время работы процесса в пространстве ядра и в некоторые моменты во время обработки прерывания, но дабы вводить в текст избыточное количество оговорок, условимся, что описывается только случаи, когда процесс переключается из режима пользователя. ² В планировщике современного ядра linux «квант времени» не является некоторой величиной, которая однократно задаётся при старте процесса. Вместо этого он рассчитывается на каждом тике заново исходя из текущей ситуации. ³ Потоки ядра — это по сути те же процессы, но работают постоянно в режиме ядра, не имеют пользовательского пространства и порождаются не init, а kthreadd. Обычно приоритет у них выше и они скорее будут выбраны, в качестве «следующего процесса» и переключение в их контекст несколько отличается (например для них не происходит замена адресного пространства).

Комментариев нет:

Отправить комментарий