Зберігання дескриптора процесу

Система ідентифікує процеси за допомогою унікального значення, яке називаетсо я ідентифікатором процесу (process identification, PID)Ідентифікатором р PID – це ціле число, представлене за допомогою прихованого типу pid_t5, який зазвичай відповідає знакової цілого-int

5 Приховані й тип (opaqu e type) – це ти п даних, физическо е представлени е которог про невідоме або не суттєво

Однак, для зворотної сумісності зі старими версіями ОС Unix і Linux максимальне значення цього параметра за замовчуванням складає всього лише 32 768 (що відповідає типу даних shor t int) Ядро зберігає значення даного параметра в полі pi d дескриптора процесу

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

Зазвичай в ядрі на завдання посилаються безпосередньо за допомогою покажчика на їх структури task_struct І дійсно, велика частина коду ядра, що працює з процесами, працює прямо зі структурами task_struct Отже, дуже корисною можливістю було б швидко знаходити дескриптор процесу, який виконується в даний момент, що і робиться за допомогою макросу current Цей макрос повинен бути окремо реалізований для всіх підтримуваних апаратних платформ Для одних платформ покажчик на структуру task_struc t процесу, що виконується в даний момент, зберігається в регістрі процесора, що забезпечує більш ефективний доступ Для інших платформ, у яких доступно менше регістрів процесора, щоб даремно не витрачати регістри, використовується той факт, що структура thread_inf про зберігається в стеку ядра При цьому обчислюється положення структури thread_info, а слідом за цим і адреса структури task_struc t процесу

Для платформи х86 значення параметра current обчислюється шляхом маскування

13 молодших біт покажчика стека для отримання адреси структури thread_infо Це може бути зроблено за допомогою функції current_thread_inf o () Відповідний код на мові асемблера показаний нижче

movl $-8192, %eax andl %esp, %eax

Остаточно значення параметра curren t виходить шляхом разименованія значення поля tas k отриманої структури thread_info:

current_thread_info()-&gttask

Для контрасту можна порівняти такий підхід з використовуваним на платформі PowerPC (сучасний процесор на основі RISC-архітектури фірми IBM), для якого значення змінної curren t зберігається в регістрі процесора r2 На платформі РРС такий підхід можна використовувати, оскільки, на відміну від платформи х8б, тут регістри процесора доступні в достатку Так як доступ до дескриптора процесу – це дуже часта і важлива операція, розробники ядра для платформи РРС визнали правильним пожертвувати одним регістром для цієї мети

Стан процесу

Поле stat e дескриптора процесу описує поточний стан процесу (рис 3-3) Кожен процес в системі гарантовано знаходиться в одному з пяти різних станів

Існуючий процес викликає функцію fork ()

і створює новий процес

Планувальник відправляє завдання на виконання: функція schedule () викликає функцію concext_switch ()

TASK_ZOMBIE (процес завершений)

Завдання

розгалужується

Завдання завершується через do exit ()

TASK_RUNNING (готовий, але поки не виконується)

TASK_RUNNING

(Виконується]

Завдання витісняється більш пріорітетнойзадачей

Подія сталася, завдання відновлює виконання

і втягнений в чергу готових до виконання завдань

TASK_INTЕRRUPTIBLE TASK_UNINTERRUPTTВLE

(Задача очікує)

Завдання знаходиться

в загальмованому стані в черзі очікувань на певну подію

Рис 33 Діаграма станів процесу

Ці стани представляються значенням одного з пяти можливих прапорів, описаних нижче

• TASK_RUNNING-процес готовий до виконання (runnable) Іншими словами, або процес виконується в даний момент, або знаходиться в одній з черг процесів, що очікують на виконання (ці черги, runqueue, обговорюються в розділі 4 Планування виконання процесів)

• TASK_INTERRUPTIBLE – процес призупинено (перебуває в стані очікування, sleeping), тобто заблокований в очікуванні виконання деякого

умови Коли ця умова виконається, ядро ​​переведе процес в стан TASK    RUNNING Процес також відновлює виконання (wake up)  передчасно при отриманні ним сигналу

• TASK_UNNTERRUPTIBLE аналогічно TASK_INTERRUPTIBLE, за винятком того, що процесНЕ відновлює виконання при отриманні сигналу Використовується у випадку, коли процес повинен чекати безперервно або коли очікується, що деяка подія може виникати досить часто Так як завдання в цьому стані не відповідає на сигнали, TASK_UNINTERRUPTIBLE використовується менш часто, ніж TASK_INTERRUPTIBLE6

• TASK_ZOMBIE – процес завершено, проте породжує його процес ще не викликав системний виклик wait 4 () Дескриптор такого процесу повинен залишатися доступним на випадок, якщо батьківському процесу буде потрібно доступ до цього дескриптору Коли батьківський процес викликає функцію wait 4 (), то такий дескриптор звільняється

• TASK_STOPPED – виконання процесу зупинено Завдання не виконується і не має право виконуватися Таке може статися, якщо завдання отримує будь-якої із сигналів SIGSTOP, SIGTSTP, SIGTTIN або SIGTTOU, а також якщо сигнал приходить в той момент, коли процес знаходиться в стані налагодження

Маніпулювання поточним станом процесу

Виконуваного коду ядра часто необхідно змінювати стан процесу Найбільш переважно для Цього використовувати функцію

set_task state(task, state)

/ * Встановити завдання task в стан state * /

яка встановлює вказаний стан для зазначеної задачі Якщо застосовне, то ця функція також намагається застосувати барєр памяті (memory barrier), щоб гарантувати доступність встановленого стану для всіх процесорів (необхідно тільки для SMP-систем) В інших випадках це еквівалентно висловом:

task-&gtstate = state

Виклик se t curren t stat e (state) є синонімом до виклику set_task _

state(current ,  state)

Контекст процесу

Одна з найбільш важливих частин процесу-це виконуваний програмний код Цей код зчитується звиконуваного файлу (executable)і виконується в адресному просторі процесу Зазвичай виконання програми здійснюється в просторі користувачаКоли програма виконує системний виклик (див главу 5, Системні виклики) або виникає виняткова ситуація, то програма входить впростір ядра

6 Саме через це зявляються навідні жах невбивані процеси, для яких х команда ps (1)  показує значення стану, рівне D, Так як процес не відповідає на сигнали, йому не можна послати сигналSIGKILL Більш того, завершувати такий процес було б нерозумно, так як цей процес, швидше за все, виконує яку-небудь важливу операцію і може утримувати семафор

З цього моменту кажуть, що ядро ​​виконується від імені процесу і робить це в контексті процесу У контексті процесу макрос curren t є действітельним1 При виході з режиму ядра процес продовжує виконання у просторі користувача, якщо в цей час не зявляється готовий до виконання більш пріоритетний процес У такому випадку активізується планувальник, який вибирає для виконання більш пріоритетний процес

Системні виклики і обробники виняткових ситуацій є чітко визначеними інтерфейсами ядра Процес може розпочати виконання у просторі ядра тільки за допомогою одного з цих інтерфейсів – Будь-які звернення до ядра можливі тільки через ці інтерфейси

Дерево сімейства процесів

В операційній системі Linux існує чітка ієрархія процесів Всі процеси є нащадками процесу init, значення ідентифікатора PID для якого дорівнює 1 Ядро запускає процес ini t на останньому кроці процедури завантаження системи Процес init, в свою чергу, читає системні файли сценаріїв початкового завантаження (initscripts) та виконує інші програми, що врешті-решт завершує процедуру завантаження системи

Кожен процес в системі має всього один породжує процес Крім того, кожен процес може мати один або більше породжених процесів Процеси, які породжені одним і тим же батьківським процесом, називаютьсяродинними (siblings)Інформація про взаємозвязок між процесами зберігається в дескрипторі процесу Кожна структура task_struc t містить покажчик на структуру task_struc t батьківського процесу, який називається parent, ця структура також має список породжених процесів, який називається children Отже, якщо відомий поточний процес (current), то для нього можна визначити дескриптор батьківського процесу за допомогою виразу:

struct task_struct *task = current-&gtparent

Аналогічно можна виконати цикл по процесам, породженим від поточного процесу, за допомогою коду:

struct task_struct *task

struct list_head *list

list_for_each (list, scurrent-&gtchildren) {

task = list_entry(list, struct task_struct, sibling)

/ * Змінна task тепер вказує на один з процесів,

породжених поточним процесом * /

}

Дескриптор процесу ini t – це статично виділена структура даних з імям inittask Хороший приклад використання звязків між всіма процесами – це наведений нижче код, який завжди виконується успішно

1 Відмінним від контексту процесу є контекст переривання, описаний в розділі 6, Переривання та обробка переривань У контексті переривання я система працює не від імені процесу, а виконують т обработчі до переривання З обробником переривання не повязаний ні один процес, тому й контекст процесу відсутня

struct task_struct *task

for (task = current task = $init_task task = task-&gtparent)

/ * Змінна task тепер вказує на процес init * /

Звичайно, проходячи по ієрархії процесів, можна перейти від одного процесу системи до іншого Іноді, однак, бажано виконати цикл по всімпроцесів системи Таке завдання вирішується дуже просто, так як список завдань – це двохзвязної список Для того щоб отримати покажчик на наступне завдання з цього списку, маючи дійсний покажчик на дескриптор якого процесу, можна використовувати показаний нижче код:

list_entry (task-> tasksnext, struct task_struct, tasks) Отримання покажчика на попереднє завдання працює аналогічно list_entry (task-> tasksprev, struct task_struct, tasks)

Дна зазначених вище вираження доступні також у вигляді макросів next_task (task)

(Отримати таку завдання), prev_tas k (task) (отримати попередню задачу) Нарешті, макрос for_each_proces s (task) дозволяє виконати цикл по всьому списку завдань На кожному кроці циклу змінна tas k вказує на таку завдання зі списку:

struct task_struct *task

for_each_process(task) {

/ * Просто друкується імя команди і ідентифікатор PID

для кожного завдання * /

printk(&quot%s[%d]\n&quot,  task-&gtcomm, task-&gtpid)

}

Слід зауважити, що організація циклу по всіх завдань системи, в якій виконується багато процесів, може бути досить дорогою операцією Для застосування такого коду повинні бути вагомі причини (і відсутніми інші альтернативи)

Джерело: Лав, Роберт Розробка ядра Linux, 2-е видання : Пер з англ – М: ТОВ «ІД Вільямс »2006 – 448 с : Ил – Парал тит англ

Схожі статті:


Сподобалася стаття? Ви можете залишити відгук або підписатися на RSS , щоб автоматично отримувати інформацію про нові статтях.

Коментарів поки що немає.

Ваш отзыв

Поділ на параграфи відбувається автоматично, адреса електронної пошти ніколи не буде опублікований, допустимий HTML: <a href="" title=""> <abbr title=""> <acronym title=""> <b> <blockquote cite=""> <cite> <code> <del datetime=""> <em> <i> <q cite=""> <s> <strike> <strong>

*

*