авторефераты диссертаций БЕСПЛАТНАЯ БИБЛИОТЕКА РОССИИ

КОНФЕРЕНЦИИ, КНИГИ, ПОСОБИЯ, НАУЧНЫЕ ИЗДАНИЯ

<< ГЛАВНАЯ
АГРОИНЖЕНЕРИЯ
АСТРОНОМИЯ
БЕЗОПАСНОСТЬ
БИОЛОГИЯ
ЗЕМЛЯ
ИНФОРМАТИКА
ИСКУССТВОВЕДЕНИЕ
ИСТОРИЯ
КУЛЬТУРОЛОГИЯ
МАШИНОСТРОЕНИЕ
МЕДИЦИНА
МЕТАЛЛУРГИЯ
МЕХАНИКА
ПЕДАГОГИКА
ПОЛИТИКА
ПРИБОРОСТРОЕНИЕ
ПРОДОВОЛЬСТВИЕ
ПСИХОЛОГИЯ
РАДИОТЕХНИКА
СЕЛЬСКОЕ ХОЗЯЙСТВО
СОЦИОЛОГИЯ
СТРОИТЕЛЬСТВО
ТЕХНИЧЕСКИЕ НАУКИ
ТРАНСПОРТ
ФАРМАЦЕВТИКА
ФИЗИКА
ФИЗИОЛОГИЯ
ФИЛОЛОГИЯ
ФИЛОСОФИЯ
ХИМИЯ
ЭКОНОМИКА
ЭЛЕКТРОТЕХНИКА
ЭНЕРГЕТИКА
ЮРИСПРУДЕНЦИЯ
ЯЗЫКОЗНАНИЕ
РАЗНОЕ
КОНТАКТЫ


Pages:     | 1 || 3 | 4 |

«УДК 004.45(075.8) ББК -018.2*32.973я73 МИНОБРНАУКИ РОССИИ ...»

-- [ Страница 2 ] --

В обоих случаях выбор процесса на выполнение из очереди готовых осуществляется оди наково: выбирается процесс, имеющий наивысший приоритет. По разному решается проблема определения момента смены активного процесса. В системах с относительными приоритетами активный процесс выполняется до тех пор, пока он сам не покинет процессор, перейдя в состоя ние ОЖИДАНИЕ (или же произойдет ошибка, или процесс завершится). В системах с абсолют ными приоритетами выполнение активного процесса прерывается еще при одном условии: если в очереди готовых процессов появился процесс, приоритет которого выше приоритета активного процесса. В этом случае прерванный процесс переходит в состояние готовности.

Во многих операционных системах алгоритмы планирования построены с использованием как квантования, так и приоритетов. Например, в основе планирования лежит квантование, но величина кванта и/или порядок выбора процесса из очереди готовых определяется приоритетами процессов.

Существует два основных типа процедур планирования процессов - вытесняющие (preemptive) и невытесняющие (non-preemptive).

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

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

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

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

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

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

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

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

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

Однако распределение функций планировщика между системой и приложениями не все гда является недостатком, а при определенных условиях может быть и преимуществом, потому что дает возможность разработчику приложений самому проектировать алгоритм планирования, наиболее подходящий для данного фиксированного набора задач. Так как разработчик сам опре деляет в программе момент времени отдачи управления, то при этом исключаются нерациональ ные прерывания программ в "неудобные" для них моменты времени. Кроме того, легко разре шаются проблемы совместного использования данных: задача во время каждой итерации исполь зует их монопольно и уверена, что на протяжении этого периода никто другой не изменит эти данные. Существенным преимуществом non-preemptive систем является более высокая скорость переключения с задачи на задачу.

Примером эффективного использования невытесняющей многозадачности является файл сервер NetWare, в котором, в значительной степени благодаря этому, достигнута высокая ско рость выполнения файловых операций. Менее удачным оказалось использование невытесняю щей многозадачности в операционной среде Windows 3.х.

Однако почти во всех современных операционных системах, ориентированных на высо копроизводительное выполнение приложений (UNIX, Windows NT, OS/2, VAX/VMS), реализо вана вытесняющая многозадачность. В последнее время дошла очередь и до ОС класса настоль ных систем, например, OS/2 Warp и Windows 95. Возможно в связи с этим вытесняющую много задачность часто называют истинной многозадачностью.

Механизмы ввода-вывода и доступа к файлам Каждое устройство ввода-вывода вычислительной системы — диск, принтер, терминал и т. п. — снабжено специализированным блоком управления, называемым контроллером. Кон троллер взаимодействует с драйвером — системным программным модулем, предназначенным для управления данным устройством. Контроллер периодически принимает от драйвера выводи мую на устройство информацию, а также команды управления, которые говорят о том, что с этой информацией нужно сделать (например, вывести в виде текста в определенную область термина ла или записать в определенный сектор диска). Под управлением контроллера устройство может некоторое время выполнять свои операции автономно, не требуя внимания со стороны централь ного процессора. Это время зависит от многих факторов — объема выводимой информации, сте пени интеллектуальности управляющего устройством контроллера, быстродействия устройства и т. п. Даже самый примитивный контроллер, выполняющий простые функции, обычно тратит до вольно много времени на самостоятельную реализацию подобной функции после получения оче редной команды от процессора. Это же справедливо и для сложных контроллеров, так как ско рость работы любого устройства ввода-вывода, даже самого скоростного, обычно существенно ниже скорости работы процессора.

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

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

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

В подсистеме ввода-вывода для согласования скоростей обмена также широко использу ется буферизация данных в оперативной памяти. В тех специализированных операционных сис темах, в которых обеспечение высокой скорости ввода-вывода является первоочередной задачей (управление в реальном времени, услуги сетевой файловой службы и т. п.), большая часть опера тивной памяти отводится не под коды прикладных программ, а под буферизацию данных. Одна ко буферизация только на основе оперативной памяти в подсистеме ввода-вывода оказывается недостаточной — разница между скоростью обмена с оперативной памятью, куда процессы по мещают данные для обработки, и скоростью работы внешнего устройства часто становится слишком значительной, чтобы в качестве временного буфера можно было бы использовать опе ративную память — ее объема может просто не хватить. Для таких случаев необходимо преду смотреть особые меры, и часто в качестве буфера используется дисковый файл, называемый также спул-файлом (от spool — шпулька, тоже буфер, только для ниток). Типичный пример при менения спулинга дает организация вывода данных на принтер. Для печатаемых документов объем в несколько десятков мегабайт — не редкость, поэтому для их временного хранения (а пе чать каждого документа занимает от нескольких минут до десятков минут) объема оперативной памяти явно недостаточно.

Другим решением этой проблемы является использование большой буферной памяти в контроллерах внешних устройств. Такой подход особенно полезен в тех случаях, когда помеще ние данных на диск слишком замедляет обмен (или когда данные выводятся на сам диск). На пример, в контроллерах графических дисплеев применяется буферная память, соизмеримая по объему с оперативной, и это существенно ускоряет вывод графики на экран.

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

Контрольные вопросы 1. Чем ограничивается максимальный размер физической памяти, которую можно установить в ком пьютере определенной модели?

2. Чем ограничивается максимальный размер виртуального адресного пространства, доступного приложению?

3. Может ли прикладной процесс использовать системную часть виртуальной памяти?

4. В каких случаях транслятор создает объектный код программы не в виртуальных, а в физиче ских адресах?

5. Поясните употребление терминов «программа», «процесс», «задача», «поток», «нить».

6. В чем состоит принципиальное отличие состояний «ожидания» и «готовности» потока, ведь и в том и в другом он ожидает некоторого события?

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

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

8. За счет каких устройств удается распараллелить ввод-вывод даже в однопроцессорных системах?

9. Какие функции выполняет менеджер ввода-вывода?

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

11. Каким из двух типов драйверов — блок-ориентированным или байт-ориентированным — обслу живается диск?

Литература [1], гл. 2;

[6], гл. 2-3.

Лекция 3. Низкоуровневые средства языка Си для реализации системного программного обеспечения в среде MS DOS Динамическое распределение памяти — способ выделения оперативной памяти компью тера для объектов в программе, при котором выделение памяти под объект осуществляется во время исполнения программы.

При динамическом распределении памяти объекты размещаются в т.н. «куче» (англ. heap): при конструировании объекта указывается размер запрашиваемой под объект памяти, и, в случае ус пеха, выделенная область памяти, условно говоря, «изымается» из «кучи», становясь недоступ ной при последующих операциях выделения памяти. Противоположная по смыслу операция — освобождение занятой ранее под какой-либо объект памяти: освобождаемая память, также ус ловно говоря, возвращается в «кучу» и становится доступной при дальнейших операциях выде ления памяти.

По мере создания в программе новых объектов, количество доступной памяти уменьшается. От сюда вытекает необходимость постоянно освобождать ранее выделенную память. В идеальной ситуации программа должна полностью освободить всю память, которая потребовалась для ра боты. По аналогии с этим, каждая процедура (функция или подпрограмма) должна обеспечить освобождение всей памяти, выделенной в ходе выполнении процедуры. Некорректное распреде ление памяти приводит к т.н. «утечкам» памяти, когда выделенная память не освобождается.

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

Другая проблема — это проблема фрагментации памяти. Выделение памяти происходит блоками — непрерывными фрагментами оперативной памяти (таким образом, каждый блок — это несколько идущих подряд байтов). В какой-то момент, в куче попросту может не оказаться блока подходящего размера и, даже, если свободная память достаточна для размещения объекта, операция выделения памяти окончится неудачей.

Для управления динамическим распределением памяти используется «сборщик мусора»

— программный объект, который следит за выделением памяти и обеспечивает её своевременное освобождение. Сборщик мусора также следит за тем, чтобы свободные блоки имели максималь ный размер, и, при необходимости, осуществляет дефрагментцию памяти.

Элементы реализации.Язык программирования C (Си) В языке программирования Си имеются следующие четыре функции для динамического распре деления памяти, входящие в стандартную библиотеку:

• malloc (от англ. memory allocation, выделение памяти), • calloc (от англ. clear allocation, чистое выделение памяти) • realloc (от англ. reallocation, перераспределение памяти).

• free (англ. free, освободить) Эти функции имеют следующие описания:

#include stdlib.h void *malloc (size_t size);

void *calloc (size_t num, size_t size);

void *realloc(void *block, size_t size);

void *free(void *block);

В C++ имеются два оператора:

• new • delete.

Регистр процессора — блок ячеек памяти, образующий сверхбыструю оперативную па мять (СОЗУ) внутри процессора;

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

Этими регистрами пользуются в основном разработчики операционных систем).

Существуют также так называемые регистры общего назначения (РОН), представляющие собой часть регистров процессора, использующихся без ограничения в арифметических операциях, но имеющие определенные ограничения, например в строковых. РОН, не характерные для эпохи мейнфреймов типа IBM/370[1] стали популярными в микропроцессорах архитектуры X86 — i8085, i8086 и последующих[2].

Специальные регистры содержат данные, необходимые для работы процессора — смеще ния базовых таблиц, уровни доступа и т. д.

Часть специальных регистров принадлежит устройству управления, которое управляет процес сором путём генерации последовательности микрокоманд.

Доступ к значениям, хранящимся в регистрах, как правило, в несколько раз быстрее, чем доступ к ячейкам оперативной памяти (даже если кеш-память содержит нужные данные), но объ ём оперативной памяти намного превосходит суммарный объём регистров (объём среднего мо дуля оперативной памяти сегодня составляет 1-4 Гб[4], суммарная «ёмкость» регистров общего назначения/данных для процессора Intel 80x86 16 битов * 4 = 64 бита (8 байт)).

IP (англ. Instruction Pointer) — регистр, обозначающий смещение следующей команды от носительно кодового сегмента.

IP — 16-битный (младшая часть EIP) EIP — 32-битный аналог (младшая часть RIP) RIP — 64-битный аналог Сегментные регистры — Регистры указывающие на сегменты.

CS (англ. Code Segment), DS (англ. Data Segment), SS (англ. Stack Segment), ES, FS, GS В реальном режиме работы процессора сегментные регистры содержат адрес начала 64Kb сегмента, смещенный вправо на 4 бита.

В защищенном режиме работы процессора сегментные регистры содержат селектор сег мента памяти, выделенного ОС.

CS — указатель на кодовый сегмент. Связка CS:IP (CS:EIP/CS:RIP — в защищенном/64-битном режиме) указывает на адрес в памяти следующей команды.

Регистры данных — служат для хранения промежуточных вычислений.

RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, R8 — R15 — 64-битные EAX, ECX, EDX, EBX, ESP, EBP, ESI, EDI, R8D — R15D — 32-битные (extended AX) AX, CX, DX, BX, SP, BP, SI, DI, R8W — R15W — 16-битные AH, AL, CH, CL, DH, DL, BH, BL, SPL, BPL, SIL, DIL, R8B — R15B — 8-битные (половинки 16 ти битных регистров) например, AH — high AX — старшая половинка 8 бит AL — low AX — младшая половинка 8 бит RAX RCX RDX RBX EAX ECX EDX EBX AX CX DX BX AH AL CH CL DH DL BH BL RSP RBP RSI RDI Rx ESP EBP ESI EDI RxD SP BP SI DI RxW SPL BPL SIL DIL RxB Регистры RAX, RCX, RDX, RBX, RSP, RBP, RSI, RDI, Rx, RxD, RxW, RxB, SPL, BPL, SIL, DIL доступны только в 64-битном режиме работы процессора.

Регистр флагов FLAGS (16 бит) / EFLAGS (32 бита) / RFLAGS (64 бита) — содержит те кущее состояние процессора.

Регистром называется функциональный узел, осуществляющий приём, хранение и пере дачу информации. Регистры состоят из группы триггеров, обычно D. По типу приёма и выдачи информации различают 2 типа регистров:

• С последовательным приёмом и выдачей информации — сдвиговые регистры.

• С параллельным приёмом и выдачей информации — параллельные регистры.

Сдвиговые регистры представляют собой последовательно соединённую цепочку триггеров. Ос новной режим работы — сдвиг разрядов кода от одного триггера к другому на каждый импульс тактового сигнала.

По назначению регистры различаются на:

• аккумулятор — используется для хранения промежуточных результатов арифметиче ских и логических операций и инструкций ввода-вывода;

• флаговые — хранят признаки результатов арифметических и логических операций;

• общего назначения — хранят операнды арифметических и логических выражений, ин дексы и адреса;

• индексные — хранят индексы исходных и целевых элементов массива;

• указательные — хранят указатели на специальные области памяти (указатель текущей операции, указатель базы, указатель стека);

• сегментные — хранят адреса и селекторы сегментов памяти;

• управляющие — хранят информацию, управляющую состоянием процессора, а также адреса системных таблиц.

IP (англ. Instruction Pointer) — регистр, содержащий адрес-смещение следующей команды, подлежащей исполнению, относительно кодового сегмента CS в процессорах семейства x86.

Регистр IP связан с CS в виде CS:IP, где CS является текущим кодовым сегментом, а IP — текущим смещением относительно этого сегмента.

Регистр IP является 16-разрядным регистром-указателем. Кроме него, в состав регистров этого типа входят SP (англ. Stack Pointer — указатель стека) и BP (англ. Base Pointer — базовый указатель).

Принцип работы Например, CS содержит значение 2CB5[0]H, в регистре IP хранится смещение 123H.

Адрес следующей инструкции, подлежащей исполнению, вычисляется путем суммирования ад реса в CS (сегменте кода) со смещением в регистре IP:

2CB50H + 123H = 2CC73H Таким образом, адрес следующей инструкции для исполнения равен 2CC73H.

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

Начиная с процессора 80386 была введена 32-разрядная версия регистра-указателя — EIP.

В данном случае IP является младшей частью этого регистра (первые 16 разрядов). Принцип ра боты EIP в целом схож с работой регистра IP. Основная разница состоит в том, что в защищён ном режиме, в отличие от реального режима, регистр CS является селектором (селектор указыва ет не на сам сегмент в памяти, а на его дескриптор сегмента в таблице дескрипторов).

В 64-разрядных процессорах используется свой регистр-указатель инструкций — RIP.

Младшей частью этого регистра является регистр EIP.

На основе RIP в 64-разрядных процессорах введён новый метод адресации RIP-relative. В осталь ном работа RIP аналогична работе регистра EIP.

Процедуры доступа к портам ввода/выводы находятся в /usr/include/asm/io.h (или в li nux/include/asm-i386/io.h в некоторых дистрибутивах ядра). Процедуры представляют собой встроенные (inline) макроопределения, так что вам не нужны никакие библиотеки, и достаточно просто добавить #include asm/io.h.

Из-за ограничения в gcc (присутствующего, как минимум, в версии 2.7.2.3 и ниже) и в egcs (всех версий), вы должны компилировать любые исходные тексты, использующие эти процедуры, с включенной оптимизацией (gcc -O1 или выше), или же определить пустое #define extern перед #include asm/io.h..

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

Перед тем, как вы получите доступ к какому-нибудь порту, вы должны дать вашей про грамме права на это. Это выполняется, при помощи функции ioperm(from, num, turn_on) (опреде ленной в unistd.h и находящейся в ядре), где from это первый порт, а num это количество подряд идущих портов, которым нужно дать доступ. Например, ioperm(0x300, 5, 1) дает доступ к порту с 0x300 по 0x304 (всего 5 портов). Последний аргумент - это двоичное значение, определяющее, дать ли доступ к портам (истина (1)) или запретить его (ложь (0)). Для включения портов, иду щих не подряд, вы можете вызывать ioperm() несколько раз. Для дополнительной информации читайте руководство ioperm(2).

Для вызова ioperm() необходимо иметь права root;

таким образом, вы должны запускать программу от пользователя root или установить на файл флаг setuid. После того, как определен доступ к портам, права суперпользователя больше вам не нужны. Вам не нужно непосредственно освобождать порты при помощи ioperm(..., 0), т.к. это делается автоматически, когда программа заканчивает работу.

Выполнение setuid() для переключения на другого пользователя не отключает доступ к портам, данный ioperm(), но это происходит при fork (наследованный процесс теряет доступ, ко гда как у порождающего процесса он остается). ioperm() может дать доступ только к портам с 0x000 по 0x3ff;

для других портов вам нужно использовать iopl() (который дает доступ ко всем портам сразу). Используйте уровень 3 (iopl(3)), чтобы дать доступ вашей программе ко всем пор там ввода/вывода (но будьте осторожны --- доступ к неправильным портам может сделать неко торые нехорошие вещи с вашим компьютером). Опять таки, вам необходимы привилегии root.

Дополнительно читайте руководство iopl(2).

Теперь собственно о доступе к портам: Чтобы считать байт (8 бит) из порта, вызовите функцию inb(port), возвращающую считанный байт. Чтобы вывести байт в порт, вызовите проце дуру outb(value, port) (обратите внимание на порядок аргументов). Чтобы считать компьютерное слово (16 бит) из портов x и x+1 (по одному байту из каждого образуют слово), вызовите функ цию inw(x). Чтобы вывести слово в два порта, используйте outw(value, x). Если вы не уверены, работать с одинарным или двойным портом, вам, скорее всего, нужны inb() и outb(), т.к. порты большинства устройств имеют однобайтный доступ. Также замечу, что все функции, работаю щие с портами, требуют, как минимум, около микросекунды для выполнения.

Макросы inb_p(), outb_p(), inw_p() и outw_p() работают аналогично вышеуказанным, но добавляют короткую (около микросекунды) задержку после доступа к порту;

вы можете устано вить задержку около четырех микросекунд при помощи #define REALLY_SLOW_IO перед #in clude asm/io.h. Обычно эти макросы (за исключением случаев с #define SLOW_IO_BY_JUMPING, при которых выполнение становится менее точным) используют вы вод в порт 0x80 в качестве задержки, так что вам необходимо сначала дать доступ к порту 0x80, при помощи ioperm() (вывод в порт 0x80 никак не влияет на систему). О других способах за держки см. руководства.

Альтернативный способ: /dev/port Другой путь доступа к портам ввода/вывода лежит через открытие файла /dev/port (сим вольное устройство - major 1, minor 4) на чтение и/или запись (функции f*() из stdio.h работают через буфер, поэтому избегайте их). Затем выполняем lseek() на необходимый байт в файле (по зиция файла 0 = порт 0x00, позиция файла 1 = порт 0x01, и т.д.), и считываем/записываем байт или слово при помощи read() или write().

Естественно, для работы программы вам нужен доступ на чтение/запись к файлу /dev/port.

Этот способ конечно медленней, чем стандартный, но не требует ни оптимизации при компиля ции, ни ioperm(). Кроме того, если вы дадите доступ соответствующим пользователю/группе к файлу /dev/port, вашей программе не нужны права root --- впрочем, это не очень хорошая вещь с точки зрения безопасности, поскольку это может нарушить работу системы путем получения прав root через доступ к диску, сетевой плате и т.д. напрямую.

Контрольные вопросы 1. Модели оперативной памяти.

2. Распределение памяти при работе С-программы.

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

4. Генерация прерываний.

5. Средства для написания обработчиков прерываний.

6. Низкоуровневая работа с файлами, клавиатурой и экраном.

Литература [2], гл. 1-3.

Лекция 4. Visual C++ и библиотека MFC как средства реализации системного ПО в среде Windows.

Библиотеки предоставляют программисту возможность использовать при разработке про граммы готовые фрагменты кода. В библиотеки могут быть включены подпрограммы, структуры данных, классы, макросы. Для языка C++ разработано много библиотек различного назначения.

Некоторые библиотеки используются по умолчанию, их подключение к проекту осуществляется автоматически (библиотека времени выполнения – RTL, Runtime Library). Другие библиотеки можно использовать по требованию (в консольном приложении VC++ – библиотека MFC), для этого следует подключить директивой include их заголовки и/или сделать соответствующие на стройки проекта. Файлы, содержащие библиотеки, имеют расширение *.lib (статическая) и *.dll (динамическая).

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

Стандартная библиотека языка C++ (C++ Standard Library) – межплатформенная библио тека (Windows, Linux), основу которой составляет стандартная библиотека шаблонов STL (Standard Template Library), включающая:

базовую поддержку средств языка времени выполнения, например, средства управление памятью, стандартную библиотеку языка C, строки и потоки ввода/вывода с поддержкой национальных алфавитов, структуры данных, называемые контейнерами, и алгоритмы их обработки (сортировка, слияние), поддержку численных расчетов, например, операции над векторами, обработку ком плексных чисел.

Компоненты библиотеки подключаются с помощью заголовочных файлов:

vector – одномерный массив элементов;

list – двусвязный список элементов;

queue – очередь элементов;

stack – стек элементов;

ctime – дата и время;

algorithm – основные алгоритмы;

cstdlib – функции обработки данных (поиск, сортировка, обработка строк в стиле C, ге нератор случайных чисел);

string – строка;

iostream – стандартные потоки ввода/вывода;

complex – комплексные числа;

c_math – общие математические функции;

new – работа с динамической памятью.

Имена компонентов библиотеки определены в пространстве имен std. Пространство имен представляет собой область видимости имен, это средство логического группирования иденти фикаторов Приведем пример программы, использующей элементы стандартной библиотеки C++ (компоненты библиотеки STL).

Программа в заданном наборе чисел подсчитывает количество чисел, имеющих значение 5, 6, меньше 8:

//подключение заголовочных файлов библиотеки STL #include vector #include algorithm #include iostream #include functional using namespace std;

int main () { int sequence[10] = {1,2,3,4,5,5,7,8,9,10};

int i=0,j=0,k=0;

// //Создание вектора vectorint v(sequence+0, sequence+10);

i=count(v.begin(),v.end(),5);

//Считает пятерки j=count(v.begin(),v.end(),6);

//Считает шестерки // // Считает значения меньшие, чем k=count_if(v.begin(),v.end(),bind2nd(lessint(),8));

cout i " " j " " k endl;

return 0;

} Библиотека Win32 API содержит большое количество функций, типов данных и структур, предназначенных для создания приложений для операционной системы Windows. Win32 API (API – Application Programming Interface) – это промежуточное звено (интерфейс) между про граммой, исполняемой в 32-разрядной операционной системе Windows, и пользователем или устройствами. Функции интерфейса Win32 API служат для создания различного вида окон, ме ню, позволяют обращаться к устройствам (дисплею, принтеру, клавиатуре и т. д.). Любая про грамма, предназначенная для работы в ОС Windows, прямо или косвенно, например, через биб лиотеки OWL или MFC, использует библиотеку Win32 API. Реализация функций Win32 API скрыта от прикладного программиста и не может быть изменена.

Библиотека OWL (Object Windows Library) используется для создания объектно ориентированных приложений под Windows в среде Borland C++ 4.0 и выше. Содержит опреде ления классов C++, реализующих интерфейс с Windows. Классы OWL используют прямой вызов функций Win API.

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

Классы в OWL делятся на категории, неполный перечень которых приводится ниже.

Окна – базовый класс TWindow, обеспечивающий базовый интерфейс окна.

Окна с обрамлением – базовый класс TFrameWindow, производный от TWindow, обычно используется для главного окна программы.

Окна MDI – классы, реализующие многооконный интерфейс.

Диалоговые панели – базовый класс TDialog.

Классы элементов управления – кнопки, полосы прокрутки;

базовый класс для классов элементов управления – TControl.

Классы модулей и приложений – TModule и TApplication;

TApplication включает в себя цикл приема сообщений и обработку ошибок.

Графические классы – TClientDC, TPaintDC – представляют графическое устройство в программе. Программам в Windows запрещено обращаться к устройствам напрямую, поэтому они должны использовать так называемые контексты устройств (DC – Device Context). Контекст устройства – это логический представитель внешнего графического устройства в программе.

Точкой входа в программу, использующую библиотеку OWL, является функция OwlMain():

int OwlMain(int,char**) { … return 0;

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

Библиотека VCL (Visual Component Library) – библиотека визуальных компонентов фир мы Borland, применяемая в системах быстрой разработки программ (RAD – Rapid Application Development) C++ Builder и Delphi. VCL предоставляет программисту набор программных ком понентов (кнопок, флажков, одно- и многострочных редакторов и т. д.) интерфейса пользователя в программах для операционной системы Windows. Компоненты доступны с помощью специаль ной панели инструментов – Палитры компонентов. В основе классов библиотеки используется класс TComponent.

Библиотека CLX (Component Library for Cross-Platform) – межплатформенная (Windows, Linux) библиотека компонентов пользовательского интерфейса, компонентов данных, Web компонентов. Применяется в системах разработки программ C++ Builder, Delphi, Kylix.

Библиотека MFC (Microsoft Foundation Classes) – фундаментальная библиотека классов фирмы Microsoft. MFC используется для создания объектно-ориентированных приложений под Windows в среде Visual C++, Borland C++ 5.0, C++ Builder. Предшественницей MFC была биб лиотека Application Framework (AFX), выпущенная в виде отдельного программного продукта (1992). MFC создает готовый каркас приложения, который дополняется в соответствии с постав ленной задачей. Библиотека содержит определения классов C++, реализующих интерфейс с Windows, на основе которых могут быть определены классы пользователя. Для большинства классов MFC базовым определен класс CObject. Производные от него классы представляют со бой различного вида окна (например, CFrameWnd – класс окна с рамкой), диалоговые панели (класс CDialog), контексты устройств (класс CDC), графические компоненты (класс CPen – перо).

Также в состав библиотеки MFC включены средства для работы со строками, файлами.

OpenGL (Open Graphics Library) – свободно распространяемая графическая библиотека, содержащая набор графических примитивов и средств для работы с ними (двух- и трехмерные объекты и сцены, текстура, туман, прозрачность, движение). За основу библиотеки была исполь зована графическая библиотека IRIS GL фирмы Silicon Graphics. Термин «открытый» в названии библиотеки означает, что OpenGL могут производить разные фирмы и отдельные разработчики при условии, что библиотека должна удовлетворять спецификации (стандарту) OpenGL и ряду тестов. Стандарт OpenGL создан (1992) ведущими фирмами в области разработки программного обеспечения как эффективный аппаратно-независимый интерфейс, пригодный для реализации на различных платформах.

OpenGL может быть использована для разработки программ в С++ Builder, Visual C++, Delphi и др. Доступ к библиотеке осуществляется путем подключения к проекту заголовочных файлов gl.h, glu.h, glaux.h Литература [2], гл. 4-6.

Лекция 5. Основные отличия операционных систем семейства Windows от MS-DOS Очереди сообщений являются одним из трех механизмов IPC (от англ. Inter Process Communication -- межпроцессное взаимодействие). Другие два -- это семафоры и разделяемая память. Очереди сообщений появились в UNIX system V release III и предназначались для асин хронной передачи сообщений между процессами.

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

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

------ Shared Memory Segments ------- key shmid owner perms bytes nattch status ------ Semaphore Arrays ------- key semid owner perms nsems status ------ Message Queues ------- key msqid owner perms used-bytes messages Нас интересует последний раздел. Кратко описание полей в этом разделе следует ниже:

key - ключ (имя), который присваивается очереди при ее создании вызовом функции msgget().

msqid - значение, возвращаемое функцией msgget(). Является дескриптором очереди. Лю бые операции над очередью сообщений осуществляются с указанием этого дескриптора.

owner - пользователь, создавший очередь.

perms - восьмеричное число, определяющее права доступа к очереди. Очень напоминает права доступа к файлам.

used-bytes - текущее количество байт, хранящихся в очереди.

messages - количество сообщений в очереди.

Создание любого элемента IPC производится вызовом соответствующей функции ipcget() (для семафоров -- semget(), для разделяемой памяти -- shmget(), прим перев.). Для очереди сооб щений -- это msgget(). Функция принимает 2 параметра: key - ключ, идентифицирующий оче редь, и msgflg -- набор флагов. Набор флагов может включать в себя комбинацию значений IPC_CREAT и IPC_EXCL. Первое из них указывает на необходимость создания новой очереди с ключом key (если таковой еще не существует). Если очередь уже существует, то этот флаг просто игнорируется, а функция msgget() возвращает существующую очередь. Второе -- при использо вании совместно с флагом IPC_CREAT приводит к неудаче, если очередь с заданным key уже существует. Что возвращает msgget()? Я полагаю вы уже догадались! Она возвращает id очереди (обычный файловый дескриптор). А теперь попробуйте собрать и запустить у себя: mesg1.c.. Эта программа создает очередь с ключом 10 (передается в функцию msgget() первым параметром).

Пусть вас не смущает тип key_t - это обычный int. После запуска программы можно убедиться в том, что очередь в действительности была создана. Используйте для этого команду ipcs. Про смотрев раздел Message Queues, вы обнаружите запись, в которой поле key имеет значение (0x0000000a в шестнадцатеричном виде) и поле msqid имеет значение 0 (как правило). Эта оче редь и была создана программой. Если у вас еще остались какие либо сомнения, то можете срав нить вывод команды ipcs до и после запуска этой программы, отличия вы увидите сразу.

Теперь, в качестве примера, попробуйте заменить в нашей программе флаг IPC_CREAT на комбинацию флагов IPC_CREAT | IPC_EXCL. Пересоберите программу и запустите ее. Резуль тат очевиден -- поскольку очередь с заданным ключом уже существует, то функция msgget() воз вратит управление с кодом ошибки (отрицательное значение). Созданную программой очередь можно удалить выполнив из командной строки:

ipcrm msg id-number Права доступа к очереди указываются во втором параметре функции msgget() в восьме ричном виде, объединяя их по ИЛИ (OR) с флагами IPC_CREAT и IPC_EXCL. Рассмотрим вто рой пример mesg.c, который отличается от первого лишь тем, что в вызове msgget() устанавли ваются права доступа к очереди, равные 0644, объединенные по ИЛИ (OR) с флагом IPC_CREAT. Таким образом, очередь создается с правами "чтение-запись" для владельца, и "только-чтение" для всех остальных. Следует отметить, что выдача прав "на-исполнение" для очередей не имеет смысла, поскольку они (очереди) не являются исполняемым кодом. Если не обходимо выдать права на "чтение-запись" для всех, то следует указать значение 0666, вместо приведенного выше 0644.

(Следует отметить тот факт, что в случае с примером mesg1.c вы не сможете просмотреть информацию о вновь созданной очереди, если пытаетесь всё проделать от имени непривилегиро ваного пользователя. Причина? В правах доступа. Процесс создаётся с правами доступа 0 и толь ко root может получить информацию о такой очереди сообщений. Поэтому будьте внимательны, назначая права доступа. Прим.ред.) Для каждой, вновь создаваемой очереди, в области ядра отводится пространство со сле дующей структурой:

Эта структура определена в файле bits/msg.h. Этот заголовочный файл обязательно дол жен подключаться к вашим программам, использующим очереди сообщений через подключение файла sys/msg.h.

/* Структура записи для одного сообщения в области ядра Тип time_t соответствует типу long int.

Все данные типы определены в заголовочном файле types.h*/ struct msqid_ds { struct ipc_perm msg_perm;

/* структура описывает права доступа */ time_t msg_stime;

/* время последней команды msgsnd (см. ниже) */ unsigned long int unused1;

time_t msg_rtime;

/* время последней команды msgrcv (см. ниже) */ unsigned long int unused2;

time_t msg_ctime;

/* время последнего изменения */ unsigned long int unused3;

unsigned long int msg_cbytes;

/* текущее число байт в очереди */ msgqnum_t msg_qnum;

/* текущее число сообщений в очереди */ msglen_t msg_qbytes;

/* максимальный размер очереди в байтах */ pid_t msg_lspid;

/* pid последнего процесса вызвавшего msgsnd() */ pid_t msg_lrpid;

/* pid последнего процесса вызвавшего msgrcv() */ unsigned long int unused4;

unsigned long int unused5;

};

Первый элемент структуры - это ссылка на другую структуру, которая имеет следующее определение в файле bits/ipc.h. подключение которого производится через файл sys/ipc.h.

/* Структура используется для передачи информации о правах доступа в операциях IPC. */ struct ipc_perm { key_t key;

/* Ключ. */ uid_t uid;

/* UID владельца. */ gid_t gid;

/* GID владельца. */ uid_t cuid;

/* UID создателя. */ gid_t cgid;

/* GID создателя. */ unsigned short int mode;

/* Права доступа. */ unsigned short int pad1;

unsigned short int seq;

/* Порядковый номер. */ unsigned short int pad2;

unsigned long int unused1;

unsigned long int unused2;

};

Структура ipc_perm хранит UID, GID и права доступа к очереди.

После того как очередь будет создана, она может быть изменена. Это означает, что поль зователь создавший очередь или авторизованный пользователь, может изменить права доступа к очереди и другие ее характеристики. Для внесения изменений используется функция msgctl().

Она имеет следующее определение:

int msgctl(int msqid, int cmd, struct msqid_ds *queuestat ) Где первый параметр msqid -- это дескриптор очереди. Это должен быть дескриптор сущест вующей очереди.

32-разрядные инструментальные средства Borland C++ обеспечивают создание 32 разрядных файлов.OBJ и.EXE в формате переносимых выполняемых файлов PE. Это формат выполняемого файла для программ Win32 и Windows NT.

Win32 - это расширение операционной системы Windows 3.1, обеспечивающее поддержку разработки и выполнения 32-разрядных выполняемых файлов Windows. Win32 - это набор DLL, отображающих вызовы 32-разрядного прикладного программного интерфейса (API) в соответст вующие 16-разрядные вызовы, использующие виртуальный драйвер устройства (VxD) для рабо ты с памятью и содержащие обновленные функции API. Эти DLL и VxD обеспечивают прозрач ный режим работы.

Чтобы обеспечить компиляцию и выполнение своего кода под Win32, вам следует:

обеспечить использование в своей программе API Win32;

писать переносимый программный код, используя типы и макрокоманды, предусмот ренные в файлах windows.h и windowssx.h.

API Win32 расширяют большинство существующих 16-битовых API Windows до 32 битовых. Сюда добавлены и новые вызовы API, совместимые с Windows NT. API Win32 - это подмножество API Win32 для Windows NT. API Win32 состоят из 16-битовых вызовов, преобра зованных и допускающих вызов в 32-разрядной среде, и 32-битовых вызовов API, реализуемых в 16-разрядной среде Windows.

Если выполняемые вызовы Win32 любой из функций API Win32 в Win32 не поддержива ются, на этапе выполнения возвращаются соответствующие коды ошибки. Если вы пишете при ложения, совпадающие с API Win32 и использующие соглашения по переносимости, ваше при ложение должно обладать переносимостью между 16- и 32-разрядной средой Windows.

Существующий 16-разрядный код Windows можно переносить с минимальными измене ниями в Win32 и Windows NT. Большинство изменений предусматривают подстановку вместо старых новых макрокоманд и типов и замену специфических 16-разрядных вызовов API анало гичными API Win32. После внесения этих изменений ваш программный код сможет компилиро ваться и выполняться в 16-разрядной и 32-разрядной среде Windows.

Чтобы облегчить создание переносимого кода, предусмотрена переменная среду этапа компиляции STRICT. Windows 3.1 поддерживает определение STRICT в windows.h. Например, если не определена переменная STRICT, то передача HWND функции, требующей HDC, не при ведет к выводу компилятором предупреждающего сообщения. Если вы определите STRICT, то получите ошибку компиляции.

Использование STRICT позволяет:

выполнять строгую проверку типов;

корректировать и согласовывать описания типа параметра и возвращаемого значения;

создавать прототипы определений типов для функций обратного вызова (оконные, диа логовые и специальные процедуры);

согласовывать с ANSI описания структур COMM, DCB и COMSTAT.

STRICT обладает обратной совместимостью с Windows 3.0, то есть ее можно использовать для создания приложений, работающих в Windows 3.0. Определение STRICT поможет вам нахо дить и корректировать несовместимость типов при переносе программ в 32-разрядную среду и поможет обеспечить переносимость между 16- и 32-разрядной Windows.

Чтобы сделать функции DLL доступными для других приложений (.EXE или других DLL), имена функций должны экспортироваться. Чтобы использовать экспортированные функ ции, нужно импортировать их мена. Экспорт функций выполняется двумя способами:

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

Перед именем каждой функции DLL, которая должна экспортироваться, можно указать ключевое слово _export (в определении функции). Кроме того, при построении или компоновке DLL в IDE нужно выбрать корректный параметр генерации кода или задать корректную команд ную строку компилятора.

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

Добавить в файл определения модуля секцию IMPORT и перечислить в ней каждую функцию DLL, которую будет использовать данный модуль.

Включить библиотеку импорта при компоновке модуля. IMPLIB позволяет создать библиотеку импорта для одной или более DLL.

Можно определить функцию с помощью ключевого слова _import (только для 32-разрядных приложений).

Функции DLL не компонуются непосредственно с приложением Windows. Они вызыва ются на этапе выполнения. Это означает, что такие функции должны иметь дальний тип вызова (так как DLL будет иметь другой сегмент кода). Используемые функцией DLL данные также должны иметь дальний тип.

Чтобы функцию можно было использовать в приложении, она должна также компилиро ваться как доступная для экспорта и затем экспортироваться. Для этого вы можете скомпилиро вать DLL так, чтобы все функции в ней были экспортируемыми (параметр -WD), и указать перед ними ключевое слово _export.

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


Библиотека Microsoft Foundation Class (MFC) предоставляет поддержку многопоточных приложений. В данном разделе описываются процессы и потоки, а также подход MFC к много поточности.

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

Поток — это ветвь выполнения внутри процесса. При запуске программы Блокнот опера ционная система создает процесс и начинает выполнение основного потока этого процесса. Ко гда поток прекращается, завершается и процесс. Этот основной поток передается операционной системе кодом запуска в форме адреса функции. Обычно предоставляется адрес функции main или WinMain.

При необходимости можно создавать дополнительные потоки. Это необходимо сделать для обработки фоновых или обслуживающих задач, если требуется, чтобы пользователь не ожи дал их завершения. Все потоки в приложениях MFC представлены объектами CWinThread. В большинстве ситуаций вам даже не нужно явно создавать эти объекты;

вместо этого вызовите вспомогательную функцию AfxBeginThread, которая создаст объект CWinThread.

MFC различает два типа потоков: потоки пользовательского интерфейса и рабочие пото ки. Потоки пользовательского интерфейса широко используются для обработки ввода и отклика на события и сообщения, создаваемые пользователем. Рабочие потоки применяются для выпол нения задач, которые не требую ввода со стороны пользователя, например пересчета. API интерфейс Win32 не делает различия между типами потоков;

ему достаточно "знать" начальный адрес потока, чтобы начать его обработку. MFC обрабатывает потоки пользовательского интер фейса, предоставляя конвейер сообщений для событий в интерфейсе. CWinApp является приме ром объекта потока пользовательского интерфейса, поскольку он наследуется от класса CWinThread и обрабатывает события и сообщения, создаваемые пользователем.

Особое внимание следует уделить ситуациям, когда несколько потоков пытаются полу чить доступ к одному и тому же объекту. В разделе Многопоточность. Советы по программиро ванию описываются способы устранения проблем, которые могут возникнуть в подобных ситуа циях. В разделе Многопоточность. Использование классов синхронизации описывается исполь зование классов, доступных для синхронизации доступа нескольких потоков к одному объекту.

Написание и отладка многопоточной программы — довольно сложная и непредсказуемая задача, поскольку необходимо быть уверенным, что к объектам не осуществляется одновремен ный доступ из более чем одного потока. В разделах, посвященных многопоточности, не описы ваются основы многопоточного программирования, в них говорится только о том, как использо вать MFC в многопоточных программах. Примеры многопоточных приложений MFC, включен ные в Visual C++, демонстрируют дополнительные возможности многопоточности и API интерфейса Win32, не включенные в MFC;

однако они только предполагаются как отправные точки.

Контрольные вопросы 1. Платформенно-независимый интерфейс POSIX.

2. Семейство операционных систем UNIX. Общая характеристика семейства операционных систем UNIX, особенности архитектуры семейства ОС UNIX.

3. Семейство операционных систем OS/2 Warp компании IBM.

4. Сетевая ОС реального времени QNX.

5. Языки и цепочки символов. Способы задания языков. Цепочки символов.

6. Операции над цепочками символов.

7. Понятие языка. Формальное определение языка.

8. Способы задания языков. Синтаксис и семантика языка.

9. Особенности языков программирования.

10. Определение грамматики. Форма Бэкуса-Наура.

11. Понятие о грамматике языка.

12. Принцип рекурсии в правилах грамматики.

13. Запись правил грамматик с использованием метасимволов.

14. Запись правил грамматик в графическом виде.

15. Классификация языков и грамматик.

16. Цепочки вывода. Сентенциальная форма.

Литература [3], гл. 1-5.

Лекция 6. Разработка компонент системного программного обеспечения Обычно после завершения очередной программы MS-DOS освобождает место в памяти, которое занимала программа, чтобы загрузить на это место новую. Однако есть способ оставить программу в памяти и после ее завершения. Такая программа и будет резидентной, т. е. постоян но присутствующей в памяти.

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

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

Другой пример использования резидентных программ: резидентные калькуляторы, спра вочные базы данных или интегрированные системы, наподобие Borland SideKick.

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

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

Аналогично работают резидентные модули некоторых систем управления базами данных (СУБД). Прикладная программа посылает запросы к базе данных через прерывание, устанавли ваемое при запуске такой СУБД.

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

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

Например, резидентным программам не разрешается использовать прерывания MS-DOS, когда вздумается. Это связано с тем, что MS-DOS с самого начала проектировалась как одноза дачная операционная система, поэтому функции прерываний MS-DOS не обладают свойством реентерабельности (повторной входимости).

Пусть обычная программа вызвала какую-либо функцию прерывания MS-DOS, на выпол нение которой требуется относительно много времени (например, запись на диск).

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

Функции BIOS также далеко не все реентерабельны. Резидентная программа может смело вызывать разве лишь прерывание INT 16h (которое предназначено для работы с клавиатурой).

Если резидентной программе нужно вывести что-нибудь на экран, то вместо прерывания INT 10h следует выполнить непосредственную запись символов и их атрибутов в видеопамять.

Без принятия специальных мер предосторожности резидентная программа не может вы зывать многие функции библиотеки транслятора, так как последние вызывают прерывания MS DOS. Например, функция malloc вызывает прерывание MS-DOS для определения размера сво бодной памяти в системе.

Могут возникнуть трудности и с использованием арифметических действий с числами в формате плавающей запятой. Это происходит потому, что функция _dos_keep восстанавливает прерывания, использовавшиеся для эмуляции арифметики с плавающей запятой и для работы с арифметическим сопроцессором.

У программы есть две возможности остаться резидентной в памяти - использовать прерывание INT 27h или функцию 31h прерывания INT 21h.

Для использования прерывания INT 27h сегментный регистр CS должен указывать на PSP программы. При этом в регистр DX следует записать смещение последнего байта программы плюс один байт.

Нетрудно заметить, что этот способ больше всего подходит для com-программ, так как с помощью прерывания INT 27h невозможно оставить в памяти резидентной программу длиннее 64 Кбайт.

Другой, более удобный, способ заключается в вызове функции 31h прерывания INT 21h.

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

Для того чтобы оставить резидентной в памяти программу, размер которой превышает Кбайт, вы можете использовать только последний метод. Но не стоит увлекаться большими ре зидентными программами, так как занимаемая ими память нужна другим программам.

Библиотека функций системы разработки Borland C++ содержит в своем составе специ альную функцию, предназначенную для оставления программы резидентной в памяти.

Эта функция использует прерывание INT 21h (функция 31h) и называется _dos_keep.

Первый параметр функции - код завершения (то, что записывается в регистр AL), а второй - раз мер резидентной части программы в параграфах.

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


Библиотека динамической компоновки (DLL) является исполняемым файлом, который выполняет функции общей библиотеки. Динамическая компоновка представляет способ вызова функции, который не является частью исполняемого кода. Исполняемый код функции располо жен в библиотеке DLL, которая содержит несколько компилированных, связанных и отдельно сохраненных функций в используемых процессах. Библиотеки DLL часто упрощают процесс общего доступа к данным и источникам. Многочисленные приложения могут иметь одновремен ный доступ к нескольким содержаниям одной копии DLL в памяти Проще всего создать новый проект DLL с помощью мастера AppWizard, который автома тически выполняет многие операции. Для простых DLL, таких как рассмотренные в этой главе, необходимо выбрать тип проекта Win32 Dynamic-Link Library. Новому проекту будут присвоены все необходимые параметры для создания библиотеки DLL. Файлы исходных текстов придется добавлять к проекту вручную.

Если же планируется в полной мере использовать функциональные возможности MFC, такие как документы и представления, или намерены создать сервер автоматизации OLE, лучше выбрать тип проекта MFC AppWizard (dll). В этом случае, помимо присвоения проекту парамет ров для подключения динамических библиотек, мастер проделает некоторую дополнительную работу. В проект будут добавлены необходимые ссылки на библиотеки MFC и файлы исходных текстов, содержащие описание и реализацию в библиотеке DLL объекта класса приложения, производного от CWinApp.

Иногда удобно сначала создать проект типа MFC AppWizard (dll) в качестве тестового приложения, а затем - библиотеку DLL в виде его составной части. В результате DLL в случае необходимости будет создаваться автоматически.

Большинство библиотек DLL - просто коллекции практически независимых друг от друга функций, экспортируемых в приложения и используемых в них. Кроме функций, предназначен ных для экспортирования, в каждой библиотеке DLL есть функция DllMain. Эта функция пред назначена для инициализации и очистки DLL. Она пришла на смену функциям LibMain и WEP, применявшимся в предыдущих версиях Windows. Структура простейшей функции DllMain мо жет выглядеть, например, так:

BOOL WINAPI DllMain (HANDLE hInst,DWORD dwReason, LPVOID IpReserved) {BOOL bAllWentWell=TRUE;

switch (dwReason) {case DLL_PROCESS_ATTACH: // Инициализация про цесса.break;

case DLL_THREAD_ATTACH: // Инициализация потока.break;

case DLL_THREAD_DETACH: // Очистка структур потока.break;

case DLL_PROCESS_DETACH: // Очистка структур процесса.break;

}if(bAllWentWell) return TRUE;

else return FALSE;

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

При первой загрузке библиотеки DLL процессом вызывается функция DllMain с dwReason, равным DLL_PROCESS_ATTACH. Каждый раз при создании процессом нового пото ка DllMainO вызывается с dwReason, равным DLL_THREAD_ATTACH (кроме первого потока, потому что в этом случае dwReason равен DLL_PROCESS_ATTACH).

По окончании работы процесса с DLL функция DllMain вызывается с параметром dwReason, равным DLL_PROCESS_DETACH. При уничтожении потока (кроме первого) dwReason будет равен DLL_THREAD_DETACH.

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

В состав DLL могут входить ресурсы, не принадлежащие вызывающему эту библиотеку приложению. Если функции DLL работают с ресурсами DLL, было бы, очевидно, полезно сохра нить где-нибудь в укромном месте дескриптор hInst и использовать его при загрузке ресурсов из DLL. Указатель IpReserved зарезервирован для внутреннего использования Windows. Следова тельно, приложение не должно претендовать на него. Можно лишь проверить его значение. Если библиотека DLL была загружена динамически, оно будет равно NULL. При статической загрузке этот указатель будет ненулевым.

В случае успешного завершения функция DllMain должна возвращать TRUE. В случае возникновения ошибки возвращается FALSE, и дальнейшие действия прекращаются.

Замечание. Если не написать собственной функции DllMain(), компилятор подключит стандарт ную версию, которая просто возвращает TRUE.

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

Метод declspec (dllexport) Можно экспортировать функцию из DLL, поставив в начале ее описания модификатор declspec (dllexport). Кроме того, в состав MFC входит несколько макросов, определяющих declspec (dllexport), в том числе AFX_CLASS_EXPORT, AFX_DATA_EXPORT и AFX_API_EXPORT.

Метод declspec применяется не так часто, как второй метод, работающий с файлами оп ределения модуля (.def), и позволяет лучше управлять процессом экспортирования.

Синтаксис файлов с расширением.def в Visual C++ достаточно прямолинеен, главным об разом потому, что сложные параметры, использовавшиеся в ранних версиях Windows, в Win более не применяются. Как станет ясно из следующего простого примера,.def-файл содержит имя и описание библиотеки, а также список экспортируемых функций:

MyDLL.defLIBRARY "MyDLL"DESCRIPTION 'MyDLL - пример DLL-библиотеки'EXPORTS MyFunction @ В строке экспорта функции можно указать ее порядковый номер, поставив перед ним символ @. Этот номер будет затем использоваться при обращении к GetProcAddress (). На самом деле компилятор присваивает порядковые номера всем экспортируемым объектам. Однако спо соб, которым он это делает, отчасти непредсказуем, если не присвоить эти номера явно. В строке экспорта можно использовать параметр NONAME. Он запрещает компилятору включать имя функции в таблицу экспортирования DLL:

MyFunction @1 NONAME Иногда это позволяет сэкономить много места в файле DLL. Приложения, использующие библитеку импортирования для неявного подключения DLL, не "заметят" разницы, поскоьку при неявном подключении порядковые номера используются автоматически. Приложениям, загру жающим библиотеки DLL динамически, потребуется передавать в GetProcAddress порядковый номер, а не имя функции.

При использовании вышеприведенного def-файл описания экспортируемых функций DLL-библиотеки может быть,например, не таким:

#define EXPORT extern "C" declspec (dllexport)EXPORT int CALLBACK MyFunction(char *str);

a таким:

extern "C" int CALLBACK MyFunction(char *str);

Создание.def-файла для экспортирования даже простых классов из динамической биб лиотеки может оказаться довольно сложным делом. Понадобится явно экспортировать каждую функцию, которая может быть использована внешним приложением.

Если взглянуть на реализованный в классе файл распределения памяти, в нем можно заме тить некоторые весьма необычные функции. Оказывается, здесь есть неявные конструкторы и деструкторы, функции, объявленные в макросах MFC, в частности _DECLARE_MESSAGE_MAP, а также функции, которые написанные программистом.

Хотя можно экспортировать каждую из этих функций в отдельности, есть более простой способ. Если в объявлении класса воспользоваться макромодификатором AFX_CLASS_EXPORT, компилятор сам позаботится об экспортировании необходимых функ ций, позволяющих приложению использовать класс, содержащийся в DLL.

В отличие от статических библиотек, которые, по существу, становятся частью кода при ложения, библиотеки динамической компоновки в 16-разрядных версиях Windows работали с памятью несколько иначе. Под управлением Win 16 память DLL размещалась вне адресного про странства задачи. Размещение динамических библиотек в глобальной памяти обеспечивало воз можность совместного использования их различными задачами.

В Win32 библиотека DLL располагается в области памяти загружающего ее процесса. Ка ждому процессу предоставляется отдельная копия "глобальной" памяти DLL, которая реинициа лизируется каждый раз, когда ее загружает новый процесс. Это означает, что динамическая биб лиотека не может использоваться совместно, в общей памяти, как это было в Winl6.

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

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

#pragma data_seg(".myseg")int sharedlnts[10] ;

// другие переменные общего пользования#pragma data_seg()#pragma comment(lib, "msvcrt" "-SECTION:.myseg,rws");

Все переменные, объявленные между директивами #pragma data_seg(), размещаются в сегменте.myseg. Директива #pragma comment () - не обычный комментарий. Она дает указание библиотеке выполняющей системы С пометить новый раздел как разрешенный для чтения, запи си и совместного доступа.

Если проект динамической библиотеки создан с помощью AppWizard и.def-файл моди фицирован соответствующим образом - этого достаточно. Если же файлы проекта создаются вручную или другими способами без помощи AppWizard, в командную строку редактора связей следует включить параметр /DLL. В результате вместо автономного выполняемого файла будет создана библиотека DLL.

Если в.def-файле есть строка LIBRART, указывать явно параметр /DLL в командной строке редактора связей не нужно.

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

Контрольные вопросы 1. Резидентные программы в MS-DOS.

2. Предотвращение повторной загрузки.

3. Проблемы безопасности использования функций DOS.

4. Работа с файлами и оперативной памятью.

5. Завершение и выгрузка резидентных программ.

6. DLL библиотеки в Windows.

7. Создание DLL.

8. Функции входа/выхода.

9. Экспорт и импорт функций и переменных.

10. Создание DLL для использования средствами разработки, отличными от Visual C++.

Литература [2], гл. 1-3;

[3], гл. 1-5.

Лекция 7. Основные тенденции развития методов и средств автоматизации проектирова ния и создания системного ПО Широкое внедрение вычислительной техники в различные сферы деятельности человека стимулировало развитие автоматизированных методов и инструментальных средств создания прикладного программного обеспечения (ПО). Производство современного ПО происходит на фоне высоких требований к его качеству, так как сложность и ответственность выполняемых им функций непрерывно возрастает. Создание качественной программы, как и любого другого про дукта, зависит не только от опыта разработчика, но и от инструмента, которым он пользуется.

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

Все время существования программного средства, от зарождения идеи по его созданию до завершения его эксплуатации, обычно определяют как жизненный цикл. Укрупнено можно вы делить пять наиболее важных этапов жизненного цикла программного средства (ЖЦ ПС): спе цификацию (10%), проектирование (10%), кодирование (10%), отладку (20%) и сопровождение (50%). В скобках записаны относительные затраты ресурсов на создание ПС.

По затратам времени, человеческих и машинных ресурсов все эти этапы не одинаковы.

Наиболее “дорогими”, в этом смысле, являются этапы, связанные с поиском ошибок в програм мах. Затраты ресурсов на них могут быть равными, или даже превосходить совокупные затраты ресурсов на остальных этапах. В стандарте DOD-STD-2167-A около 30% требований, докумен тов и соответствующих им процессов непосредственно связаны с отладкой, тестированием и ис пытаниями программ. Данный стандарт является обязательным при выполнении заказов Мини стерства обороны США.

Эти затраты быстро увеличиваются при возрастании требований к качеству ПС. По оцен кам, приведенным в работе, на долю устранения ошибок и сопровождение ПС приходится почти 75% всех затрат. Следует учитывать, что значительная часть работ, выполняемых на этапе со провождения, связана с поиском и устранением оставшихся в программе ошибок.

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

На этом этапе следует выделить развитие специализированных языков программирования, таких как КОБОЛ, СИМУЛА, СИМСКРИПТ, АСПИД, АНАЛИТИК и т.д.

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

Постепенное усложнение решаемых задач, а вместе с ними кодов и объемов разрабаты ваемых программных средств, заставило критически переосмыслить сложившийся к концу 60-х годов стиль и технику программирования. В связи с чем, начиная с 70-х годов усилиями рабочей группы Технического комитета по программированию Международной федерации по обработке информации (ИФИП), состоящей из таких известных ученых как Н. Вирт, Д. Грис, Э. Дейкстра, У. Дал, Д. Парнас, Ч. Хоар (руководимой профессором В. Турским), формируются основы мето дологии и теории программирования.

В среде программистов утверждается принцип структурного программирования, как наи более производительного и привлекательного стиля программирования. Развитие концепции структуризации привело к осознанию необходимости структуризации данных, что и определило появление в языках программирования механизмов абстрагирования типов данных (Клу, Моду ла и д.р.). Последнее легло в основу техники модульного программирования, содержательной ос новой которой является интерпретация типа как алгебры над множеством объектов и множест вом операций, а модуля как программного эквивалента абстрактного типа.

В модульном программировании акцент делается на разбиение программы на модули та ким образом, чтобы данные (обрабатываемые модулем) были спрятаны в нем. Эта доктрина, из вестная как “принцип ограничения доступа к данным”, в значительной степени повысила моди фицируемость и эффективность порождаемого кода.

Эволюция техники модульного программирования привела к появлению объектно ориентированного стиля программирования, который во многом унифицировал процесс создания ПС. К достоинствам этого метода относится то, что в нем более полно реализуется технология структурного программирования, облегчается процесс создания сложных иерархических систем, появляется удобная возможность создания пользовательских библиотек объектов в различных областях применения.

Параллельно с развитием процедурного стиля программирования в начале 70-х годов по являются непроцедурные языки логического программирования и программирования искусст венного интеллекта (LISP, PROLOG, РЕФАЛ, ПРИЗ). Программа в таких языках представляет собой совокупность правил (определяющих отношения между объектами) и цели (запроса). Про цесс выполнения программы трактуется как процесс установления общезначимости логической формулы по правилам, установленным семантикой того или иного языка. Результат вычислений является побочным продуктом процедуры вывода. Такой метод являет собой полную противопо ложность программирования на каком-либо из процедурных языков. Сегодня PROLOG - язык, предназначенный для программирования приложений, использующих средства и методы искус ственного интеллекта, создания экспертных систем и представления знаний.

В 80-х годах исследование причин неудач при реализации больших программных проек тов показало, что число ошибок в спецификациях на программы значительно превышает их ко личество в программных кодах. Так около 56% ошибок допускаются на этапе формулировки требований к программе при этом расходуется в среднем 82% всех усилий, затраченных коллек тивом на устранение ошибок проекта. В то время как на этапе кодирования программ допускает ся соответственно 7% ошибок и тратится 1% усилий на их ликвидацию Одним из основных факторов повышения эффективности и надежности программирования можно считать придание образности формам спецификации данных и описания алгоритма. В этом смысле главный недос таток существующих технологий программирования заключается в преимущественно текстовых формах представления основных компонент программы, что делает программу невыразительной и чрезвычайно затрудняет ее восприятие человеком.

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

Итак, занавес открывается, все внимание зрителей обращено на залитую светом сцену, встречай те: ТЕХНОЛОГИЯ ГРАФО-СИМВОЛИЧЕСКОГО ПРОГРАММИРОВАНИЯ.

Контрольные вопросы 1. Организация планирования разработки программного изделия. Виды планов. Декомпо зиция планов.

2. Организационная структура группы планирования.

3. Виды планов, связанных с созданием программного изделия.

4. Организация планирования разработки программного изделия.

5. Вопросы, рассматриваемые в фазовых обзорах группой планирования.

6. Управление проектом.

7. Организация работы группы разработки в фазах создания программного изделия.

8. Организация работы группы обслуживания в фазах создания программного изделия.

9. Организация работы группы выпуска документации в фазах создания программного изделия.

10. Организация испытаний программного изделия.

Литература [3], гл. 7.

3. Лабораторный практикум Лабораторная работа №1 Функции прерываний. Реализация механизма прерываний с ис пользованием средств языка Си Задание.

Выбрав вариант согласно последней цифре учебного шифра, составить программу на язы ке Си для решения приведённой в нём задачи. Для удобства после условий вариантов приводятся функции прерывания 21h, используемые в соответствующих вариантах.

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

Вариант Определить дисковод загрузки, а также номер версии DOS и ее подверсии.

Вариант Ввести с клавиатуры символ с выводом эха на консоль, затем вывести на консоль символ 0 и символьную строку.

Вариант Установить предназначен ли заданный файл только для чтения, а, если нет, то сделать его таковым.

Вариант Определить номер текущего диска и его имя, затем сделать текущим диск A:, если он та ковым не является.

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

Вариант Создать в текущем каталоге подкаталог KATAL, и сделать его текущим. При этом опре делить имя текущего каталога как до вхождения в подкаталог, так и после.



Pages:     | 1 || 3 | 4 |
 





 
© 2013 www.libed.ru - «Бесплатная библиотека научно-практических конференций»

Материалы этого сайта размещены для ознакомления, все права принадлежат их авторам.
Если Вы не согласны с тем, что Ваш материал размещён на этом сайте, пожалуйста, напишите нам, мы в течении 1-2 рабочих дней удалим его.