Архитектура Аудит Военная наука Иностранные языки Медицина Металлургия Метрология
Образование Политология Производство Психология Стандартизация Технологии


Пробуждение после ожидания или активация



Вспомните, что, когда процесс вызывает fork (), создается новый процесс. Как говори­лось ранее, процесс, вызывающий fork (), называется родительским, а новый процесс -дочерним. Новый процесс нужно передать планировщику для того, чтобы он получил доступ к процессору. Это происходит в функции do_f ork ().

В функции do_f ork () операции, связанные с пробуждением процесса, выполняют две строчки; copy__process (), вызываемая в строке 1184 linux/kernel/fork, с, вызывает функцию sched_f ork (), которая инициализирует процесс для следующей его вставки в очередь выполнения планировщика; wake_up_f orked_process (), вызы­ваемая в строке 1222 linux/kernel/f ork. с, получает инициализированный процесс


3.6 Слежение за процессом: базовые конструкции планировщика



struct runqueue


prio_arrayJ *active


spinlockj lock


taskj *curr, *idle


 


prlo_arrayJ *expired


unsigned long nr_running


init best_expired_prio


P- prio_array_t *arrays[0]


int nr_active


unsigned long bitmap [BITMAP_SIZE]


 

 

 

struct list_head_queue [MAX.PRIO]
I | 0| 1 | 2 | 3 | 4| 5 | 6 |... | 138 1139 | 140 |
     
U*| A | В |... | С |
  (очередь указателей на задачи  
  с приоритетом 0) UJ х I Y I . I Z I
  (очередь указателей на задачи с приоритетом 138)

г- prio_array_tarrays[1]


int nr active


unsigned long bitmap [BITMAP_SIZE]


 

 

 

struct list_head_queue [MAX.PRIO]
| 0 | 1 | 2 | 3 | 4 [5 | 6 |... | 138 1139 | 140 [
     
L| D | E |. | Г)
  (очередь указателей на задачи Г  
  с приоритетом 0) UJ и 1 V 1 - н
  (очередь указателей на задачи с приоритетом 138)

Рис. 3.12. Массив приоритетов в очереди выполнения

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



Глава 3 • Процессы: принципиальная модель выполнения


3.6.2.1 sched_fork(): инициализация планировщика для нового ответвленного процесса Функция sched_fork() выполняет настройку инфраструктуры, требуемой планиров­щику для нового ответвленного процесса:

kernel/sched.с

719 void sched_fork(task_t *р)

720 {

 

721 /*

722 * Здесь мы помечаем процесс выполняемым, но еще не помещаем

723 * в очередь выполнения. Это дает гарантию, что никто не запустит

724 * его на выполнение и что сигнал или другое внешнее

72 5 * событие не сможет его разбудить и вставить в очередь выполнения.

726 */

727 p-> state = TASKJRUNNING;

728 INIT_LIST_HEAD(& p-> run_list);

729 p-> array = NULL;

730 spin_lock_init(& p-> switch_lock);

Строка 727

Процесс обозначается выполняемым с помощью установки атрибута state в структуре задачи в TASK_RUNNING для того, чтобы удостовериться, что в очередь выполнения не добавлены события, а запуск процесса до do__f or k () и copy_process () проверяет, что процесс был создан правильно. Когда проверка завершена, do__f ork () добавляет его в очередь ожидания с помощью wake_up_f orked__process ().

Строки 728-730

Инициализация поля run_list-npouecca. Когда процесс активируется, поле run_list связывает вместе очередь структуры массива приоритетов и очередь вы­полнения. Поле процесса array устанавливается в NULL для того, чтобы обо­значить, что он не является частью ни одного из массивов приоритетов очереди вы­полнения. Следующий блок - sched_fork() со строки 731 до 739 работает с приоритетом обслуживания ядра. (Более подробную информацию об этом можно увидеть в гл. 7.)

kernel/sched.с

740 /*

741 * Разделение временного среза между родителем и детьми, при котором

742 * общее количество временного среза, выделенного системой,

743 * не изменяется, обеспечивая более честное планирование.

744 */


3.6 Слежение за процессом: базовые конструкции планировщика



745 local_irq_disable();

746 p-> time_slice = (current-> time_slice +1) » 1;

 

747 /*

748 * Остатки первого временного среза возвращаются

749 * родителю, если дочерний процесс слишком рано закончит работу.

750 */

 

751 p-> first_time_slice = 1;

752 current-> time_slice »= 1;

753 p-> timestamp = sched_clock();

754 if (! current-> time_slice) {

755 /*

756 * Это редкий случай, возникающий, когда у родителя остается только

757 * один миг от его временного среза. В этом случае блокировка

758 * очереди выполнения не представляет никаких сложностей.

759 */

 

760 current-> time_slice = 1;

761 preempt_disable();

762 scheduler_tick(0, 0);

763 local_irq_enable();

764 preempt_enable();

765 } else

766 local__irq_enable ();

767 }

Строки 740-753

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

Строки 754-767

Если временной срез родителя равен 1, в результате деления родителю остается время 0 на выполнение. Так как родитель является текущим процессом планиров­щика, нам понадобится планировщик для выбора нового процесса. Это делается с помощью вызова scheduler_tick() (в строке 762). Приоритет отключается для того, чтобы удостовериться, что планировщик выбирает новый процесс без прерывания. Как только это сделано, мы включаем приоритет и восстанавливаем ло­кальные прерывания.

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



Глава 3 • Процессы: принципиальная модель выполнения


отнимать слишком большую часть процессорного времени. Если процессу выделить слишком много времени, нехороший процесс может наплодить множество дочерних и бы­стро съесть все процессорное время.

После успешной инициализации процесса и проверки инициализации do_f ork () вызывает wake_up_forked_process ():

kernel/sched.с

922 /*

923 * wake_up_forked__process - будит свежеответвленный процесс.

924 * Эта функция производит некоторые статистические приготовления 92 5 * планировщика, которые необходимо выполнить для каждого нового

92 6 * созданного процесса.

927 */

928 void fastcall wake_up_forked_process(task_t * p)

929 {

93 0 unsigned long flags;

931 runqueue_t *rq = task_rq_lock(current, & flags);

933 BUG_ON(p-> state! = TASK_RUNNING);

935 /*

93 6 * Мы уменьшаем среднее время сна родительского процесса

937 * и дочернего процесса для предотвращения порождения

938 * сверхинтерактивных задач от сверхинтерактивных задач.

939 */

940 current-> sleep_avg = JIFFIES_TO_NS(CURRENT_BONUS( current) *

941 PARENT_PENALTY / 100 * MAX_SLEEP_AVG / MAX_BONUS);
942

943 p-> sleep_avg = JIFFIES_TO_NS (CURRENT_BONUS (p) *

944 CHILD_PENALTY / 100 * MAX_SLEEP_AVG / MAX_BONUS);
945

946 p-> interactive_credit = 0; 947

948 p-> prio = effective_prio(p);

949 set_task_cpu(p, smp_processor_id()); 950

951 if (unlikely(! current-> array))

952 __ activate_task(p, rq);

953 else {

954 p-> prio = current-> prio;

955 list_add_tail(& p-> run_list, & current-> run_list);

956 p-> array = current-> array;

957 p-> array-> nr_active++;

958 rq-> nr_running++;


3.6 Слежение за процессом: базовые конструкции планировщика



959 }

960 task__rq__unlock (rq, & flags);

961 }

Строки 930-934

Первое, что делает планировщик, - это блокирование структуры очереди ожидания. Любое изменение очереди ожидания производится с заблокированной структурой. Также выдается сообщение об ошибке, если процесс не помечен как TASK_RUNNING. Процесс должен устанавливаться в это состояние в функции sched__f ork () (см. строку 727 в ранее приведенном kernel/sched. с).

Строки 940-947

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

Строка 948

Функция ef f ective_prio () изменяет статический приоритет процесса. Она возвращает приоритет в пределах от 100 до 139 (от MAX_RT_PRIO до MAX_PRI0-1). Статический приоритет процесса может быть изменен до 5 в каждом направле­нии на основе использования процессора в прошлом и времени, потраченного на сон, но он всегда остается в этих пределах. Из командной строки мы сообщаем о значении nice-процесса, который варьируется и от +19 до -20 (от наименьшего до максимального приоритета). Приоритет nice, равный 0, соответствует ста­тическому приоритету 120.



Глава 3 • Процессы: принципиальная модель выполнения


Строка 949

Процесс устанавливает свой атрибут процессора в значение текущего процессора.

Строки 951-960

В этом блоке кода новый процесс или дочерний процесс копирует информацию для планировщика из родителя, который является текущим, и затем вставляет себя в со­ответствующее место очереди выполнения. Мы закончили наше изменение очереди сообщении и теперь должны ее разблокировать. Разд. 3.7 и рис. 3.13 освещает этот процесс более подробно.

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                               
J-C Задача А       Задача В    
prio = 120 [ prio = 120 ]  
     
array     { array ]  
/      
   
runjist [ runjist J  
    N      
     
    t        
             
    Активный массив     struct runqueue          
  .        
         
                   
    A    
>  
         
u В    
       
             

Рис. 3.13. Вставка в очередь выполнения

Указатель array указывает на массив приоритетов в очереди выполнения. Если теку­щий процесс не указывает на массив приоритетов, это означает, что текущий процесс за­вершен или спит. В этом случае поле текущего процесса runlist не находится в массиве приоритетов очереди выполнения, что значит, что операция list__add_tail () (в стро­ке 955) провалится. Вместо этого мы вставляем новый созданный процесс, используя

__ activeate_task (), которая добавляет новый процесс в очередь без обращения к его

родителю.

В нормальной ситуации, когда текущий процесс ожидает процессорного времени в очереди выполнения, процесс добавляется в очередь в слот p__prior массива приори­тета. Массив, в который добавляется процесс, имеет собственный счетчик процесса


3.6 Слежение за процессом: базовые конструкции планировщика



nr_ac tive, который мы увеличиваем, как увеличиваем и собственный счетчик процесса nr_running. Наконец, мы снимаем блокировку с очереди выполнения.

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

kernel/sched.с

3 66 static inline void activate_task(task_t *p, runqueue_t *rq)

367 {

3 68 enqueue_task(p, rq-> active);

3 69 rq-> nr_running++;

370 }

__ activate_task () помещает данный процесс р в активный массив приоритетов

в очереди выполнения rq и увеличивает поле nr_running, которое является счетчиком общего количества процессов, находящихся в очереди выполнения.

kernel/sched.с

311 static void enqueue_task (struct task_struct *p, prio_array_t *array)

312 {

313 list_add_tail(& p-> run_list, array-> queue + p-> prio);

314 ___ set_bit(p-> prio, array-> bitmap);

315 array-> nr_active++;

316 p-> array = array;

317 }

Строки 311-312

enqueue_task() берет процесс р и помещает его в массив приоритетов array при инициализации различных аспектов массива приоритетов.

Строка 313

run_list процесса добавляется в хвост очереди, находящейся в p-> prio в мас­сиве приоритетов.

Строка 314

Битовая карта массива приоритетов p-> prio устанавливается при запуске пла­нировщика, когда он видит, что существует процесс, выполняемый с приоритетом p-> prio.

Строка 315

Счетчик массива приоритетов процесса увеличивается, отражая добавление нового процесса.



Глава 3 • Процессы: принципиальная модель выполнения


Строка 316

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

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

Очередь ожидания

Мы обсудили процесс перехода между состояниями TASK_INTERRUPTIBLE и TASK_RUNNING или TASK_UNINTERRUPTIBLE. Теперь мы рассмотрим другую струк­туру, вовлеченную в этот переход. Когда процесс ожидает наступления внешнего собы­тия, он удаляется из очереди выполнения и помещается в очередь ожидания. Очередь ожидания - это дву связный список структур wait_queue_t. Структура wait_queue_t получает всю необходимую для слежения за ожидающим процессом информацию. Все задачи, ожидающие определенного события, помещаются в очередь ожидания. Задачи данной очереди ожидания пробуждаются, как только наступает ожи­даемое ими событие, убирают себя из очереди ожидания и переходят обратно в состоя­ние TASK_RUNNING. Вы можете вспомнить, что системный вызов sys_wait4 () ис­пользует очередь ожидания, когда родитель посылает требование на получение статуса ответвленного дочернего процесса. Обратите внимание, что задача, ожидающая внешне­го события (и поэтому больше не находящаяся в очереди выполнения1), может находить­ся либо в состоянии TASK_UNINTERRUPTIBLE, либо в состоянии TASK_INTERRUPTIBLE.

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

include/linux/wait.h

19 typedef struct _ wait_queue wait_queue_t;

23 struct _ wait_queue {

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


Очередь ожидания



 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

 

                    task_struct   task_struct    
               
  wait.queuej     wait_queue_t     wait_queue_t  
  flags _   flags     flags  
  wait_queue_head_t   task task task
  lock   tunc   func   func  
taskjist taskjist ^taskjist taskjist    
    next       next       next I     next      
    prev prev prev I prev    
                     
                 
     
   
                                           

Рис. 3.14. Структура очереди ожидания

24 unsigned int flags;

25 ttdefine WQ_FLAG_EXCLUSIVE 0x01

2 6 struct task_struct * task;

27 wait_queue_func_t func;

28 struct list_head task_list;

29 };
30

31 struct _ wait_queue_head {

32 spinlock_t lock;

33 struct list_head task_list;

34 };

3 5________________ typedef struct wait_queue_head wait_queue_head_t;

Структура wait_queue_t состоит из следующих полей:

flags. Может хранить значение WO FLAG EXCLUSIVE, соответствующее 1,

и ~WO FLAG EXCLUSIVE, соответствующее 0. Флагом помечаются эксклюзивные процессы. Эксклюзивные и неэксклюзивные процессы описываются в подразд. 3.7.1.

task. Указатель на описатель процесса, помещаемого в очередь ожидания.

func. Структура, хранящая функцию, используемую для пробуждения задачи в очереди ожидания. Поле по умолчанию использует def ault_wake_f unction (), которая описывается в подразд. 3.7.2.



Глава 3 • Процессы: принципиальная модель выполнения


wait_gueue_t определена следующим образом:

include/1inux/wai t.h

typedef int (*wait_queue_func__t) (wait_queue__t *wait,

unsigned mode, int sync);

где wait - указатель на очередь ожидания, mode имеет значение TASK_INTERRUPTIBLE или TASK_UNINTERRUPTIBLE, a sync определяет, нуж­но ли синхронизировать пробуждение.

task__list. Структура, хранящая указатели на предыдущий и следующий
элементы в очереди ожидания.

Структура__ wait__queue__head__t - является головой списка очереди ожидания

и состоит из следующих полей:

lock. Единственная блокировка очереди позволяет синхронизировать добавление и удаление элементов из очереди ожидания.

task_list. Структура, указывающая на первый и последний элементы очереди ожидания.

Раздел «Очередь ожидания» в гл. 10 («Добавление вашего кода в ядро») описывает пример реализации очереди ожидания. В общих чертах способ, которым процесс погружает себя в сон, требует вызова одного из макросов wait_event* (кратко описы­вающихся там же) или выполнения такой последовательности шагов, показанных в примере в гл. 10:

1. Описывая очередь ожидания, процесс засыпает с помощью DECLARE_WAITQUEUE.

2. Добавляет себя в очередь ожидания с помощью add_wait_queue() или add_wait_queue_exclusive ().

3. Состояние процесса изменяется на TASK_INTERRUPTIBLE или на TASK_UNINTERRUPTIBLE.

4. Проверяется наступление внешнего события или вызывается schedule (), если оно еще не наступило.

5. После наступления внешнего события устанавливается в состояние

TASK_RUNNING.

6. Удаляет себя из очереди ожидания с помощью вызова remove_wait_queue ().

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


3.7 Очередь ожидания



занной очереди ожидания. Это устанавливает задачу в состояние TASK_RUNNING и поме­щает ее обратно в очередь выполнения.

Давайте теперь рассмотрим, что происходит, когда мы вызываем функцию add_wait_queue ().


Поделиться:



Популярное:

Последнее изменение этой страницы: 2016-03-25; Просмотров: 718; Нарушение авторского права страницы


lektsia.com 2007 - 2024 год. Все материалы представленные на сайте исключительно с целью ознакомления читателями и не преследуют коммерческих целей или нарушение авторских прав! (0.111 с.)
Главная | Случайная страница | Обратная связь