Опубликован: 06.12.2004 | Уровень: специалист | Доступ: платный
Лекция 6:

Приоритетное планирование

< Лекция 5 || Лекция 6: 123 || Лекция 7 >

Функции управления планированием

Мы приступаем к рассмотрению функций для опроса и/или установки политики и/или параметров планирования применительно к процессам. Функции аналогичной направленности для потоков управления были рассмотрены нами ранее.

Стандарт POSIX-2001 предусматривает следующие функции управления планированием: sched_getscheduler() ( опрос политики планирования процесса ), sched_getparam() ( опрос параметров планирования процесса ), sched_setscheduler() ( установка политики и параметров планирования процесса ) и sched_setparam() ( установка параметров планирования процесса ) (см. листинг 6.1).

#include <sched.h>

int sched_getscheduler (pid_t pid);

int sched_getparam (pid_t pid, 
	struct sched_param *param);

int sched_setscheduler (pid_t pid, 
    int policy, 	
    const struct sched_param *param);

int sched_setparam (pid_t pid, 
    const struct sched_param *param);
Листинг 6.1. Описание функций управления планированием.

Функция sched_getscheduler() возвращает в качестве (нормального) результата политику планирования процесса с заданным идентификатором (в случае неудачи результат равен -1 ). Если значение аргумента pid равно нулю, имеется в виду вызывающий процесс. Вообще говоря, опрос атрибутов других процессов подвержен контролю прав доступа, зависящему от реализации.

Функция sched_getparam() записывает параметры планирования заданного процесса по указателю param, в структуру типа sched_param ; ее нормальный результат равен нулю. (Напомним, что, согласно стандарту POSIX-2001, у структуры типа sched_param только одно обязательное полеприоритет sched_priority.)

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

Вызов sched_setscheduler() не влияет впрямую на планирование потоков управления. Косвенно могут быть затронуты лишь потоки заданного процесса, если их область планирования конкуренции есть PTHREAD_SCOPE_PROCESS (из-за того, что меняются атрибуты планирования содержащего их процесса ).

Любопытно отметить, что стандарт POSIX-2001 не требует атомарности вызова sched_setscheduler() с точки зрения выполняющихся потоков управления. Хотя в итоге изменится все или ничего, эти изменения могут наблюдаться как поэтапные; соответственно они будут сказываться на поведении потоков.

Функция sched_setparam(), в отличие от sched_setscheduler(), изменяет только параметры планирования, но ее воздействие на заданный процесс стандартизовано детальнее.

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

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

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

Стандарт POSIX-2001 предоставляет средства для опроса характеристик политик планирования – минимального ( sched_get_priority_min() ) и максимального ( sched_get_priority_max() ) среди допустимых приоритетов, а также величины кванта выделяемого процессорного времени для политики циклического планирования ( sched_rr_get_interval() ) (см. листинг 6.2).

#include <sched.h>

int sched_get_priority_min (int policy);

int sched_get_priority_max (int policy);

int sched_rr_get_interval (pid_t pid, 
    struct timespec *q_ptr);
Листинг 6.2. Описание функций опроса характеристик политик планирования.

Функция sched_rr_get_interval() записывает по указателю q_ptr в структуру типа timespec величину кванта процессорного времени, выделяемого заданному процессу, и возвращает нуль в качестве нормального результата. Нормальными результатами функций sched_get_priority_min() и sched_get_priority_max(), разумеется, служат, соответственно, минимальный и максимальный приоритеты.

Несколько особняком стоит альтруистическая функция sched_yield() (см. листинг 6.3), позволяющая вызывающему потоку управления добровольно уступить процессор. Результат (нулевой) поток получит только после того, как вновь окажется в голове своего списка и будет выбран планировщиком на выполнение.

#include <sched.h>
int sched_yield (void);
Листинг 6.3. Описание функции sched_yield().

Следующая программа (см. листинг 6.4) иллюстрирует применение описанных функций. Возможные результаты ее выполнения для ОС Linux и операционной системы реального времени oc2000 показаны на листингах 6.5 и 6.6.

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа опрашивает характеристики политик планирования, */
/* а также атрибуты планирования текущего процесса 	*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <sched.h>

int main (void) {
	struct sched_param shdprm; 	/* Значения параметров 	*/
											/* планирования 	*/
	struct timespec qp; 			/* Величина кванта 	*/
											/* процессорного времени 	*/

	printf ("Допустимые диапазоны приоритетов для разных "
				"политик планирования\n");
	printf ("SCHED_FIFO : от %d до %d\n", 
		sched_get_priority_min (SCHED_FIFO), 
		sched_get_priority_max (SCHED_FIFO));
	printf ("SCHED_RR   : от %d до %d\n", 
		sched_get_priority_min (SCHED_RR), 
		sched_get_priority_max (SCHED_RR));
	printf ("SCHED_OTHER: от %d до %d\n", 
		sched_get_priority_min (SCHED_OTHER), 
		sched_get_priority_max (SCHED_OTHER));

	printf ("Текущая политика планирования для текущего "
				"процесса: ");
	switch (sched_getscheduler (0)) {
		case SCHED_FIFO:
			printf ("SCHED_FIFO\n");
			break;
		case SCHED_RR:
			printf ("SCHED_RR\n");
			break;
		case SCHED_OTHER:
			printf ("SCHED_OTHER\n");
			break;
		case -1:
			perror ("SCHED_GETSCHEDULER");
			break;
		default:
			printf ("Неизвестная политика планирования\n");
	}

	if (sched_getparam (0, &shdprm) == 0) {
		printf ("Текущий приоритет текущего процесса: %d\n", 
					shdprm.sched_priority);
	} else {
		perror ("SCHED_GETPARAM");
	}

	shdprm.sched_priority = 50;
	if (sched_setscheduler (0, SCHED_RR, &shdprm) == -1) {
		perror ("SCHED_SETSCHEDULER");
	}

	if (sched_rr_get_interval (0, &qp) == 0) {
		printf ("Квант процессорного времени при "
					"циклическом планировании: %g сек\n", 
					qp.tv_sec + qp.tv_nsec / 1000000000.0);

	} else {
		perror ("SCHED_RR_GET_INTERVAL");
	}

	return 0;
}
Листинг 6.4. Пример программы, использующей функции управления планированием.
Допустимые диапазоны приоритетов для разных политик планирования

	SCHED_FIFO : 		от 1 до 99
	SCHED_RR : 		от 1 до 99
	SCHED_OTHER : 	от 0 до 0

Текущая политика планирования для текущего процесса: SCHED_OTHER 
Текущий приоритет текущего процесса: 0 
Квант процессорного времени при циклическом планировании: 0.15 сек
Листинг 6.5. Возможные результаты выполнения программы, использующей функции управления планированием, в ОС Linux.
Допустимые диапазоны приоритетов для разных политик планирования
	SCHED_FIFO : 		от 1 до 255
	SCHED_RR : 		от 1 до 255
	SCHED_OTHER : 	от 0 до 255
Текущая политика планирования для текущего процесса: SCHED_FIFO 
Текущий приоритет текущего процесса: 100 
Квант процессорного времени при циклическом планировании: 0.08 сек
Листинг 6.6. Возможные результаты выполнения программы, использующей функции управления планированием, в операционной системе реального времени oc2000.

Проиллюстрируем теперь ситуацию с инверсией приоритетов (см. листинг 6.7).

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Программа моделирует ситуацию инверсии приоритетов. 	*/
/* Идея состоит в том, что имеется три потока 	*/
/* управления	 с низким, средним 	*/
/* и высоким приоритетами.		*/
/* Поток с низким приоритетом захватывает семафор, 	*/
/* когда на него никто больше не претендует, 	*/
/* но не может освободить его, 	*/
/* потому что его вытесняет с процессора 	*/ 
/* поток со средним приоритетом. 	*/
/* В это время поток с высоким приоритетом хочет 	*/
/* захватить тот же семафор, 	*/
/* но он занят и неизвестно когда освободится... 	*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */

#include <stdio.h>
#include <pthread.h>
#include <sched.h>
#include <semaphore.h>
#include <assert.h>

static sem_t sem_mi; 	/* Семафор для потока со средним 	*/
								/* приоритетом 	*/
static sem_t sem_hi; 	/* Семафор для потока с высоким 	*/
								/* приоритетом 	*/

static double s = 0;

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока с высоким приоритетом 	*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_hi (void *dummy) {
	printf ("Поток с высоким приоритетом перед захватом "
				"семафора\n");
	assert (sem_wait (&sem_hi) == 0);
	printf ("Поток с высоким приоритетом после захвата "
				"семафора\n");
	assert (sem_post (&sem_hi) == 0);

	return (NULL);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока со средним приоритетом */
/* * * * * * * * * * * * * * * * * * * * * * * * * */
void *start_mi (void *dummy) {
	int i;
	double d = 1;

	printf ("Поток со средним приоритетом перед захватом "
				"семафора\n");
	assert (sem_wait (&sem_mi) == 0);
	/* Займем процессор вычислениями */
	for (i = 1; i < 100000000; i++) {
		s += d/i;
		d = -d;
	}
	printf ("Поток со средним приоритетом перед "
				"освобождением семафора\n");
	assert (sem_post (&sem_mi) == 0);

	return (&s);
}

/* * * * * * * * * * * * * * * * * * * * * * * * * * */
/* Стартовая функция потока с низким приоритетом 	*/
/* * * * * * * * * * * * * * * * * * * * * * * * * * */
int main (void) {
	pthread_attr_t attrob; 		/* Атрибутный объект 	*/
											/* создаваемых потоков 	*/
	struct sched_param shdprm; 	/* Значения параметров 	*/
											/* планирования 	*/
	int shdplc; 						/* Политика планирования 	*/
	pthread_t pt_mi; 				/* Идентификатор потока 	*/
											/* со средним приоритетом 	*/
	pthread_t pt_hi; 				/* Идентификатор потока 	*/
											/* с высоким приоритетом 	*/

		/* Создадим два семафора в захваченном состоянии */
		assert (sem_init (&sem_mi, 0, 0) == 0);
		assert (sem_init (&sem_hi, 0, 0) == 0);
	

	/* Установим политику планирования и сделаем 	*/
	/* текущий поток управления низкоприоритетным 	*/
	shdprm.sched_priority = sched_get_priority_max 
		(SCHED_FIFO) – 31;
	assert (pthread_setschedparam (pthread_self (), 
		SCHED_FIFO, &shdprm) == 0);

	/* Инициализируем атрибутный объект 	*/
	/* для создаваемых потоков управления 	*/
	assert (pthread_attr_init (&attrob) == 0);

	/* Установим атрибуты планирования */
	assert (pthread_attr_setinheritsched (&attrob, 
		PTHREAD_EXPLICIT_SCHED) == 0);
	assert (pthread_attr_setschedpolicy (&attrob, 
		SCHED_FIFO) == 0);
	shdprm.sched_priority += 15;
	assert (pthread_attr_setschedparam 
		(&attrob, &shdprm) == 0);

	/* Создадим поток управления со средним приоритетом */
	assert (pthread_create (&pt_mi, &attrob, 
		start_mi, NULL) == 0);

	/* Подправим атрибутный объект и создадим */
	/* поток управления с высоким приоритетом */
	shdprm.sched_priority += 15;
	assert (pthread_attr_setschedparam 
		(&attrob, &shdprm) == 0);
	assert (pthread_create (&pt_hi, &attrob, 
		start_hi, NULL) == 0);

	/* Опросим параметры планирования потоков управления */
	assert (pthread_getschedparam (pthread_self (), 
		&shdplc, &shdprm) == 0);
	assert (shdplc == SCHED_FIFO);
	printf ("Низкий приоритет: %d\n", 
				shdprm.sched_priority);
	assert (pthread_getschedparam 
		(pt_mi, &shdplc, &shdprm) == 0);
	assert (shdplc == SCHED_FIFO);
	printf (&quot;Средний приоритет: %d\n", 
		shdprm.sched_priority);
	assert (pthread_getschedparam 
		(pt_hi, &shdplc, &shdprm) == 0);
	assert (shdplc == SCHED_FIFO);
	printf ("Высокий приоритет: %d\n", shdprm.sched_priority);

	/* Создадим ситуацию инверсии приоритетов */
	printf ("Поток с низким приоритетом\n" 
				"перед освобождением семафора для потока " 
				"со средним приоритетом\n");
	assert (sem_post (&sem_mi) == 0);

	printf ("Поток с низким приоритетом\n"
				"перед освобождением семафора для потока "
				"с высоким приоритетом\n");
	assert (sem_post (&sem_hi) == 0);

	(void) pthread_join (pt_mi, NULL);
	(void) pthread_join (pt_hi, NULL);

	assert (sem_destroy (&sem_mi) == 0);
	assert (sem_destroy (&sem_hi) == 0);

	return 0;
}
Листинг 6.7. Пример программы, моделирующей ситуацию инверсии приоритетов.

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

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

Возможные результаты выполнения приведенной программы под управлением операционной системы реального времени oc2000 показаны на листинге 6.8.

Поток со средним приоритетом перед захватом семафора
Поток с высоким приоритетом перед захватом семафора
Низкий приоритет: 224
Средний приоритет: 239
Высокий приоритет: 254
Поток с низким приоритетом
перед освобождением семафора для потока со средним приоритетом
Поток со средним приоритетом перед освобождением семафора
Поток с низким приоритетом
перед освобождением семафора для потока с высоким приоритетом
Поток с высоким приоритетом после захвата семафора
Листинг 6.8. Возможные результаты выполнения программы, моделирующей ситуацию инверсии приоритетов, под управлением операционной системы реального времени oc2000.
< Лекция 5 || Лекция 6: 123 || Лекция 7 >
Максим Соколов
Максим Соколов
Россия, г. Москва
фыв ыва
фыв ыва
Афганистан