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


Состояние выполнения в состояние готовности



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

TASK_RUNNING в TASKJIUNNING

Так как в Linux нет специального состояния для задачи, контекст которой выполня­ется на процессоре, задача в Linux в этом случае не претерпевает перехода между состоя­ниями и остается в состоянии TASK_RUNNING. Планировщик выбирает, когда пере­ключить эту задачу из состояния выполнения и поместить ее в очередь выполнения в со­ответствии со временем, потраченным задачей на выполнение и ее приоритетом. (Подроб­ности описываются в гл. 7.)

Состояние выполнения в состояние блокировки

Когда процесс блокируется, он может быть в одном из следующих состояний: TASK_ INTERRUPTIBLE, TASK_UNINTERRUPTIBLE, TASK_ZOMBIE ИЛИ TASK_STOPPED. Теперь опишем, как задача переходит из состояния TASK_RUNNING в каждое из этих со­стояний, как описано в табл. 3.7.

TASKJIUNNING в TASKJNTERRUPTIBLE

Это состояние обычно вызывается с помощью блокирования функций ввода-вывода, которые ожидают поступления сообщения или ресурса. Что это значит для задачи, нахо­дящейся в состоянии TASK_INTERRUPTIBLE? Она просто остается в очереди выполне­ния, так как она не готова для выполнения. Задача в состоянии TASK_INTERRUPTIBLE просыпается, если ее ресурс становится доступным (время или аппаратура) или поступает


Жизненный цикл процесса



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

while(1) {

if(resource_available) break(); set_current_state(TASK_INTERRUPTIBLE); schedule(); } set_current_state(TASK_RUNNING);

В этом примере процесс входит в состояние TASK_INTERRUPTIBLE за время, в течение которого выполняется вызов open (). В этой точке он выходит из состояния вы­полнения с помощью вызова schedule (), а другой процесс из очереди выполнения ста­новится выполняемым процессом. После того как ресурс становится доступным, процесс удаляется из цикла и его состояние изменяется на TASK_RUNNING, которое помещает его обратно в очередь обработки. После этого он ждет, пока планировщик не решит запустить процесс на выполнение.

Следующий листинг демонстрирует функцию interruptible_sleep_on (), ко­торая устанавливает задачу в состояние TASK_INTERRUPTIBLE.

kernel/sched.с

2504 void interruptible_sleep_on(wait_queue_head_t *q)

2505 {

2506 SLEEP_ON_VAR
2507

2508 current-> state = TASK_INTERRUPTIBLE; 2509

2510 SLEEP_ON_HEAD

2511 scheduleO;

2 512 SLEEP_ON_TAIL 2513 }

Макросы SLEEP_ON_HEAD и SLEEP_ON_TAIL заботятся о добавлении и удалении задачи из очереди ожидания (см. раздел «Очередь ожидания» в этой главе). Макрос SLEEP_ON_VAR инициализирует запись о задаче в очереди ожидания, которая добавля­ется в очередь ожидания.



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


TASK_RUNNING в TASKJJNINTERRUPTIBLE

Состояние TASK__UNINTERRUPTIBLE похоже на TASK_INTERRUPTIBLE за ис­ключением того, что процесс не формирует сигналы, получаемые, когда он находится в режиме ядра. Это состояние также является состоянием по умолчанию, в которое задача устанавливается при инициализации в процессе ее создания с помощью do_fork(). Функция sleep_on () устанавливает задачу в состояние TASK_UNINTERRUPTIBLE.

kernel/sched. с

2 545 long fastcall _____ sched sleep_on(wait_queue_head_t *q)

2546 {

2 547 SLEEP_ON_VAR

2549 current-> state = TASK_UNINTERRUPTIBLE;

2551 SLEEP_ON_HEAD

2552 schedule*);

2553 SLEEP_ON_TAIL 2554

2555 return timeout;

2556 )

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

TASKJOJNNING в TASKJZOMBIE

Процесс в состоянии TASKJZOMBIE называется зомби-процессом. Каждый процесс в течение своего жизненного цикла проходит через это состояние. Длительность времени, в течение которого процесс остается в этом состоянии, зависит от родителя. Чтобы это по­нять, представьте, что в UNIX-системах каждый процесс может получить статус выхода дочернего процесса с помощью вызовов wait () или waitpid () (см. раздел «Оповеще­ние родителей и sys_wait4()»). Поэтому родительскому процессу должен быть доступен минимум информации, даже когда дочерний процесс уничтожен. Оставлять процесс жи­вым только для того, чтобы родитель мог получить о нем информацию, - слишком наклад­но, поэтому используется состояние зомби, в котором ресурсы процесса освобождаются и он возвращается, но его описатель остается.

Это временное состояние устанавливается во время вызова sys_exit () (см. более подробную информацию в разделе «Завершение процесса»). Процесс в этом состоянии никогда снова не запустится. Из этого состояния он может перейти только в состояние TASK_STOPPED.

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


Завершение процесса



Это значит, что задач для убиения не существует, а существуют только описатели, ожи­дающие освобождения.

TASK_RUNNING в TASKJTOPPED

Этот переход выполняется в двух случаях. Первый случай - это когда отладчик или утилита трассировки манипулирует процессом. Второй случай - это когда процесс по­лучает SIGSTOP или один из сигналов на остановку.

TASKJJNINTERRUPTIBLE или TASKJNTERRUPTIBLE в TASKJSTOPPED

TASK_STOPPED управляет процессами в SMP-системах или в течение обработки сигнала. Процесс устанавливается в состояние TASK_STOPPED, когда процесс получает сигнал на пробуждение или если ядру необходимо, чтобы именно этот процесс не отвечал ни на какие сигналы (как будто он установлен, например, в TASK_INTERRUPTIBLE).

Если задача не находится в состоянии TASK_ZOMBIE, процесс устанавливается в со­стояние TASK_STOPPED до того, как он получит сигнал SIGKILL.

Состояние блокировки в состояние готовности

Переход из блокированного состояния в состояние готовности происходит после получе­ния данных или доступа к оборудованию, которого ожидает процесс. Два специфичных для Linux перехода, подпадающие под эту категорию, - это из TASKJNTERRUPTIBLE

В TASK_RUNNING и ИЗ TASK_INTERRUPTIBLE в TASK_RUNNING.

Завершение процесса

Процесс может завершаться добровольно явным образом и добровольно неявным обра­зом либо принудительно. Добровольное завершение может быть выполнено двумя спосо­бами:

1. В результате возврата из функции main () (неявное завершение).

2. С помощью вызова exit () (явное завершение).

Выполнение возвращения из функции main () преобразуется в вызов exit (). При этом компоновщик вставляет вызов exit ().

Принудительное завершение может быть получено несколькими способами:

1. Процесс получает сигнал, который не может обработать.

2. Во время выполнения в режиме ядра происходит исключение.

3. Программа получает SIGABRT или другой сигнал на завершение.

Завершение процесса обрабатывается различным образом в зависимости от того, ж^ его родитель или нет. Процесс может:

• завершиться до родителя,



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


• завершиться после родителя.

В первом случае дети превращаются в зомби-процессы до того момента, как роди­тель сделает вызов wait () /waitpid(). Во втором случае статус родителя дочернего процесса будет наследоваться от процесса init(). Таким образом, при завершении процесса ядро проверяет все активные процессы на предмет того, что завершаемый процесс является их родителем. Если такие процессы были найдены, то PID их родителя устанавливается в I1.

Посмотрим на пример еще раз и проследим его до самого его конца. Процесс явно вызывает exit (0). (Обратите внимание, что, кроме этого, может быть вызван _exit (), return (0) или программа просто дойдет до конца main без всяких дополнительных вы­зовов.) Библиотечная С-функция exit () выполняет, в свою очередь, системный вызов sys_exit (). Мы можем просмотреть следующий код и увидим, что происходит с про­цессом далее.

Теперь мы посмотрим функцию, завершающую процесс. Как говорилось ранее, наш процесс f оо вызывает exit (), который вызывает первую рассматриваемую нами функ­цию - sys_exit(). Мы проследим вызов sys_exit() и углубимся в детали do_exit ().

3.5.1 Функция sys_exit()

kernel/exit.с

asmlinkage long sys_exit(int error_code)

{

do_exit( (error_code& 0xff )«8);

}

sys_exit () для различных архитектур не различается, а их работа довольно понятна -все они выполняют вызов do_exit () и преобразуют код выхода в формат, требуемый ядром.

3.5.2 Функция do_exit()

kernel/exit.с

7 07 NORET_TYPE void do_exit(long code)

708 (

709 struct ta? k_struct *tsk = current;
710

711 if (unlikely (in_interrupt()))

1 To есть их родителем становится процесс init (). Примеч. науч. ред.


Завершение процесса



712 panic(" Aiee, killing interrupt handler! " );

713 if (unlikely(! tsk-> pid))

714 panic(" Attempted to kill the idle task! " );

715 if (unlikely(tsk-> pid == 1))

716 panic(" Attempted to kill init! " );

717 if (tsk-> io_context)

718 exit_io_context();

719 tsk-> flags |= PF_EXITING;

720 del__timer_sync (& tsk-> real__timer); 721

722 if (unlikely(in_atomic()))

723 printk(KERN_INFO " note: %s[%d] exited with preempt_count %d\n",

724 current-> comm, current-> pid,

725 preempt_count());

Строка 707

Код параметра представляет собой код выхода, который процесс возвращает роди­телю.

Строки 711-716

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

1. Проверку, что мы не внутри обработчика прерывания.

2. Проверку, что мы не в задаче idle (PID=0) или в задаче init (PID=l). Обратите внимание, что процесс init убивается только при завершении работы системы.

Строка 719

Здесь мы устанавливаем PF_EXITING в поле flags структуры задачи. Это оз­начает, что процесс завершается. Например, такая конструкция используется при создании временного интервала для заданного процесса. Флаги процесса проверяются, и тем самым достигается экономия процессорного времени.

kernel/exit.с

727 profile_exit__task(tsk);

729 if (unlikely(current-> ptrace & PT_TRACE_EXIT)) {

73 0 current-> ptrace_message = code;

731 ptrace_notify((PTRACE_EVENT_EXIT « 8) | SIGTRAP);

732 }
733

734 acct_process(code);



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


735 exit_mm(tsk);

737 exit_sem(tsk);

73 8 exit_f iles (tsk);

739 exit_fs(tsk);

740 exit__namespace(tsk);

741 'exit_thread();

Строки 729-732

Если процесс отслеживается и установлен флаг PT_TRACE_EXIT, мы передаем код выхода и уведомляем об этом родительский процесс.

Строки 735-742

Эти строки выполняют очистку и перераспределение ресурсов, используемых за­
дачей, и больше не нужны; __ exit__mm() освобождает выделенную для процесса

память и освобождает структуру mm_struct, ассоциированную с процессом;

exit_sem() убирает связь задачи с любыми семафорами IPC; ____ exit_f iles ()

освобождает любые файлы, используемые процессом, и декрементирует счетчик
файла; __ exit_f s () освобождает все системные данные.

kernel/exit.с

744 if (tsk-> leader)

745 disassociate_ctty(l);
746

747 module_put(tsk-> thread_info-> exec_domain-> module);

748 if (tsk-> binfmt)

749 module_put(tsk-> binfmt-> module);

Строки 744-475

Если процесс является лидером сессии, можно ожидать, что он имеет контрольный терминал или tty. Эта функция убирает связь между задачей-лидером и конт­ролирующим tty.

Строки 747-749

В этом блоке мы уменьшаем счетчик ссылок для модуля:

kernel/exit.с

751 tsk-> exit_code = code;


Завершение процесса



752 exit_notify(tsk);
753

754 if (tsk-> exit_signal == -1 & & tsk-> ptrace == 0)

755 release_task(tsk);
756

757 schedule();

758 BUGO;

759 /* Избегание " noreturn function does return". */

760 for(;; );

761 }

Строка 751

Устанавливает код выхода в поле exit_code структуры task_struct.

Строка 752

Родителю посылается сигнал SIGCHLD, а состояние задачи устанавливается в TASK_Z0MBIE; exit_notify() уведомляет всех, кто связан с задачей, о ее приближающейся смерти. Родитель информируется о коде выхода, а в качестве родителя детей процесса назначается процесс init. Единственным исключением из этого правила является ситуация, когда другой существующий процесс выходит из той же группы процессов: в этом случае существующий процесс используется как суррогатный родитель.

Строка 754

Если exit_signal равен -1 (что означает ошибку) и процесс не является ptraced, ядро вызывает планировщик для освобождения описателя процесса этой задачи и для освобождения его временного среза.

Строка 757

Передача процессора новому процессу. Как мы увидим в гл. 7, вызов schedule () не возвращается. Весь код после этой строки обрабатывает неправильные ситуации или избегает замечаний компилятора.

3.5.3 Уведомление родителя и sys_wait4()

Когда процесс завершается, об этом уведомляется его родитель. Перед этим процесс на­ходите^ состоянии зомби, когда все ресурсы возвращаются в ядро, и остается только описатель процесса. Родительская задача (например, оболочка Bash) получает сигнал SIGCHLD, посылаемый ядром, когда дочерний процесс завершается. В примере оболочка вызывает wait (), когда хочет получать уведомления. Родительский процесс может иг­норировать сигнал, не реализуя обработчик прерывания, и может вместо этого выбрать вызов wait () [или waitpid () ] в любой точке.



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


Семейство функций wait служит для решения двух основных задач:

Гробовщик. Получение информации о смерти задачи.

Гробокопатель. Избавление ото всех отслеживаемых процессов.

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

• pid_t wait(int *status)

• pid_t waitpid(pid_t pid, int *status, int options)

• pid_t wait3(int *status, int options, struct rusage *rusage)

• pid__t wait4 (pid_t pid, int *status, int options, struct rusage *rusage)

Каждая функция, в свою очередь, вызывает sys_wait4 (), который порождает мно­жество уведомлений.

Процесс, вызывающий функцию wait, блокируется до того, как один из его дочерних процессов завершается или возвращается сразу, если дочерний процесс уже за­вершен (или если у процесса нет дочерних процессов). Функция sys_wait4 () показы­вает нам, как ядро управляет этим уведомлением:

kernel/exit.с

1031 asmlinkage long sys__wait4 (pid_t pid, unsigned int * stat_addr, int options, struct rusage * ru)

1032 (

 

1033 DECLARE_WAITQUEUE(wait, current);

1034 struct task_struct *tsk;

Int flag, retval; 1036

1037 if (options & ~(WNOHANG|WUNTRACED|___ WNOTHREAD|___ WCLONE |___ WALL) )

1038 return -EINVAL;
1039

1040 add_wait_queue(& current-> wait_chldexit, & wait);

1041 repeat:

1042 flag = 0;

1043 current-> state = TASK__INTERRUPTIBLE;

1044 read__lock(& tasklist_lock);


Завершение процесса



Строка 1031

Параметры включают РШ целевого процесса, адрес, куда помещается статус выхода дочернего процесса, флаги для sys_wait4 () и адрес, по которому размещена ин­формация об используемых ресурсах.

Строки 1033 и 1040

Определение очереди ожидания и добавление в нее процесса. (Более подробно это описано в разделе «Очередь ожидания».)

Строки 1037-1038

Этот код в основном проверяет ошибочные состояния. Функция возвращает код ошибки, если в системный вызов переданы неправильные параметры. В этом случае возвращается ошибка EINVAL.

Строка 1042

Переменная flag устанавливается в начальное значение 0. Эта переменная изменя­ется, как только аргумент pid оказывается принадлежащим к одной из дочерних за­дач вызова.

Строка 1043

Это код, в котором вызывающий код блокируется. Состояние задачи изменяется С TASK_RUNNING на TASK_INTERRUPTIBLE.

kernel/exit.с

1045 tsk = current;

1046 do {

 

1047 struct task_struct *p;

1048 struct list_head *_p;

1049 int ret; 1050

1051 list_for_each(_p, & tsk-> children) {

1052 p = list_entry(_p, struct task_struct, sibling);
1053

1054 ret = eligible_child(pid/ options, p);

1055 if (lret)

1056 continue;

1057 flag = 1;

1058 switch (p-> state) {

1059 case TASK_STOPPED:

1060 if (! (options & WUNTRACED) & &

1061! (p-> ptrace & PT_PTRACED))

1062 continue;

1063 retval = wait_task_stopped(p, ret == 2,



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


1064 stat_addr, ru);

1065 if (retval! = 0) /* Освобождает блокировку. */

1066 goto end_wait4;

1067 break;

1068 case TASK_ZOMBIE:

1072 if (ret == 2)

1073 continue;

1074 retval = wait_task__zombie(p, stat_addr, ru);

1075 if (retval! = 0) /* Освобождает блокировку. */

1076 goto end_wait4;

1077 break;

1078 }

1079 }

1091 tsk = next_thread(tsk);

1092 if (tsk-> signal! = current-> signal)

1093 BUG();

1094 } while (tsk! = current);

Строки 1046 и 1094

Цикл do while выполняется один раз за цикл при поиске себя и затем продолжает­ся при поиске других задач.

Строка 1051

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

Строка 1054

Определение, имеет ли передаваемый параметр pid допустимое значение.

Строки 1058-1079

Проверка состояния каждой дочерней задачи. Действия выполняются, только если ребенок остановлен или в состоянии зомби. Если задача спит, готова или выполня­ется (предыдущее состояние), ничего не делается. Если дочерний процесс находится в состоянии TASK_STOPPED и используется опция UNTRACED (что означает, что задача не останавливается по причине отслеживания процесса), мы проверяем со­стояние дочернего процесса, о котором получена информация, и возвращаем ин­формацию об этом дочернем процессе. Если дочерний процесс находится в состоя­нии TASK_ZOMBIE, он убирается.


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



kernel/exit.с

1106 retval = -ECHILD;

1107 end_wait4:

1108 current-> state = TASK_RUNNING;

1109 remove_wait_queue(& current-> wait_chldexit, & wait);

1110 return retval;

1111 }

Строка 1106

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

Строки 1107-1111

В этой точке весь список дочерних процессов обработан и все дочерние процессы, которые нужно было удалить, удалены. Блокировка родителя снимается, и его состоя­ние опять устанавливается в TASK_RUNNING. Наконец, удаляется очередь ожидания.

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

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

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

Базовая структура

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



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


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

Рис. 3.12 иллюстрирует массив приоритетов в очереди ожидания. Структура массива приоритетов определена следующим образом:

kernel/sched.с

192 struct prio__array {

193 int nr_active;

194 unsigned long bitmap[BITMAP_SIZE];

195 struct list_head queue [MAX__PRIO];

196 };

Структура prio_array имеет следующие поля:

nr_active. Счетчик, хранящий количество задач, находящихся в массиве приоритетов.

bitmap. Следит за приоритетами в массиве. Настоящая длина bitmap зависит от размера unsigned long в системе. Ее всегда достаточно для хранения MAX_PRIO бит, но может быть и больше.

queue. Массив, который хранит список задач. Каждый список хранит задачи с определенными приоритетом. Поэтому queue [ 0 ] хранит список всех задач с приоритетом 0, queue [ 1 ] хранит список всех задач с приоритетом 1 и т. д.

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


Поделиться:



Популярное:

  1. II. Организация выполнения выпускной квалификационной работы
  2. XI. Перепишите данные ниже предложения, определите в них видо- временную форму глаголов. Предложения переведите(см. образец выполнения 3).
  3. А.1 Определение условий выполнения проекта
  4. АЛГОРИТМЫ ВЫПОЛНЕНИЯ ПРАКТИЧЕСКИХ навыков
  5. Анализ общего равновесия, благосостояние
  6. Анализ прибыльности и рентабельности (финансовое состояние).
  7. Анализ факторов увеличения объема продаж и выполнения договорных обязательств
  8. Б. Техника выполнения упражнений и проблема перетренированности.
  9. Билет 13. Межбюджетные отношения: понятие, состояние проблемы, направление реформирования
  10. Благо-Состояние и достоинство в Платиновом Луче
  11. Благосостояние субъектов и граница возможных благосостояний. Множество возможных благосостояний
  12. Большинство оборудования этого типа предназначено для однокрасочной печати, но существуют также машины для двухкрасочной печати, используемые в основном для выполнения небольших коммерческих заказов.


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


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