Вход/изход на данни

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

За някои приложения оптимизацията на изчислителните аспекти осигурява само малка печалба в производителността, тъй като тясното място в тях са I/O операции, като прехвърляне на данни по мрежа или достъп до диск. Според нашия опит можем да кажем, че значителна част от проблемите с производителността изобщо не са свързани с използването на неоптимални алгоритми или прекомерно натоварванена процесор, но с неефективно използване на I / O устройства. Нека да разгледаме две ситуации, при които I/O оптимизацията може да подобри цялостната производителност:

    Едно приложение може да претърпи сериозно изчислително претоварване поради неефективни I/O операции, които увеличават режийните разходи. Още по-лошо, претоварването може да бъде толкова голямо, че да се превърне в ограничаващ фактор, предотвратяващ максималното използване на честотната лента на I/O устройството.

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

Тази статия описва общи I/O концепции и предоставя препоръки за подобряване на производителността на всеки тип I/O. Тези препоръки се отнасят еднакво за мрежови приложения, интензивни дискови процеси и дори програми, които имат достъп до нестандартни, високопроизводителни хардуерни устройства.

Синхронен и асинхронен I/O

Когато се изпълняват в синхронен режим, входно-изходните функции на Win32 API (като ReadFile, WriteFile или DeviceloControl) блокират изпълнението на програмата, докато операцията приключи. Въпреки че този модел е много удобен за използване, той не е много ефективен. В интервалите от време между изпълнението на последователни входно-изходни заявки устройството може да е неактивно, тоест да не се използва напълно.

Друг проблем със синхронния режим е, че нишката на изпълнение губи време за всяка едновременна I/O операция. Например, в сървърно приложение, обслужващо много клиенти едновременно, може да е възможно да се създаде отделна нишка за изпълнение за всяка сесия. Тези нишки, които са неактивни през повечето време, губят памет и могат да създават ситуации разбиване на конци, когато много нишки на изпълнение едновременно възобновяват работата си при завършване на I/O и започват да се борят за процесорно време, което води до увеличаване на контекстните превключвания за единица време и намаляване на мащабируемостта.

I/O подсистемата на Windows (включително драйвери на устройства) работи вътрешно в асинхронен режим - програма може да продължи да се изпълнява едновременно с I/O операция. Почти всички съвременни хардуерни устройства са асинхронни по природа и не изискват постоянно запитване за прехвърляне на данни или определяне кога I/O операция е приключила.

Повечето устройства поддържат способността директен достъп до паметта (Direct Memory Access, DMA)за прехвърляне на данни между устройството и RAM на компютъра, без да се изисква участието на процесора в операцията, и генериране на прекъсване, когато прехвърлянето на данни приключи. Синхронният вход/изход, който е вътрешно асинхронен, се поддържа само в приложния слой на Windows.

В Win32 се извиква асинхронен I/O припокрит вход/изход (припокрит I/O), сравнението на синхронен и припокриващ се I/O режими е показано на фигурата по-долу:

Когато дадено приложение отправи асинхронна заявка за извършване на I/O операция, Windows или изпълнява операцията незабавно, или връща код на състояние, показващ, че операцията е в процес на изчакване. След това нишката може да започне други I/O операции или да извърши някои изчисления. Програмистът има няколко начина да организира получаването на известия за завършване на I / O операции:

    Събитие Win32: Операцията, чакаща това събитие, ще бъде изпълнена, когато I/O завърши.

    Извикване на дефинирана от потребителя функция с механизъм Извикване на асинхронна процедура (APC): Нишката на изпълнение трябва да е в състояние на изчакване, което може да бъде предупредено.

    Получаване на известия чрез I/O Completion Ports (IOCP): Обикновено това е най-ефективният механизъм. Ще го проучим по-подробно.

Някои I/O устройства (например файл, отворен в небуфериран режим) могат да осигурят допълнителни предимства, ако приложението може да гарантира, че малък брой чакащи I/O заявки винаги присъстват. За да направите това, се препоръчва първо да направите няколко заявки за извършване на входно-изходни операции и за всяка изпълнена заявка да създадете нова заявка. Това ще гарантира, че следващата операция ще бъде инициализирана от драйвера на устройството най-рано възможно най-скоробез да чакате приложението да направи следващата заявка. Но не прекалявайте с количеството прехвърляни данни, защото това ще погълне ограничени ресурсипамет на ядрото.

Портове за завършване на I/O

Windows поддържа ефективен механизъм за уведомяване за завършване на асинхронни I/O операции, наречен I/O Completion Ports (IOCP). В .NET приложенията е достъпен чрез метода ThreadPool.BindHandle(). Този механизъм се използва вътрешно от няколко типа в .NET, които извършват I/O операции: FileStream, Socket, SerialPort, HttpListener, PipeStream и някои .NET Remoting канали.

Механизмът IOCP, показан на фигурата по-горе, комуникира с множество I/O манипулатори (сокети, файлове и обекти на специализирани драйвери на устройства), които са отворени асинхронно и със специфична нишка за изпълнение. След като I/O операцията, свързана с такъв манипулатор, завърши, Windows ще добави известие към съответния IOCP порт и ще го предаде на свързаната нишка за изпълнение за обработка.

Използването на набор от нишки, които обслужват известия и възобновяват изпълнението на нишки, които са инициирали асинхронни I/O операции, намалява броя на контекстните превключвания за единица време и увеличава използването на процесора. Не е изненадващо, че високопроизводителни сървъри като напр Microsoft SQLСървърът използва портове за завършване на I/O.

Портът за завършване се създава чрез извикване на функцията Win32 API CreateIoCompletionPort, на който се предава максималната стойност на паралелност (брой нишки), ключът за завършване и незадължителен манипулатор към I/O обекта. Ключът за завършване е дефинирана от потребителя стойност, която се използва за идентифициране на различни I/O манипулатори. Можете да свържете множество манипулатори към един и същ IOCP порт, като многократно извиквате функцията CreateIoCompletionPort и й предавате манипулатор към съществуващ порт за завършване.

За да се установи връзка с посочения IOCP порт, потребителските нишки извикват функцията GetCompletionStatusи чакат завършването му. Една нишка за изпълнение може да бъде свързана само с един IOCP порт в даден момент.

Извикване на функция GetQueuedCompletionStatusблокира изпълнението на нишката, докато не бъде уведомена (или изтече времето за изчакване) и след това връща информация за завършената I/O операция, като например броя на прехвърлените байтове, ключа за завършване и структурата на асинхронната I/O операция . Ако всички нишки, свързани с I/O порта, са заети по време на известието (т.е. няма нишки, чакащи извикването GetQueuedCompletionStatus), IOCP механизмът ще създаде нова нишка за изпълнение до максималната стойност на едновременност . Ако нишка, наречена GetQueuedCompletionStatus, и опашката за уведомяване не е празна, функцията се връща незабавно, без да блокира нишката в ядрото операционна система.

Механизмът IOCP е в състояние да открие, че някои от „заетите“ нишки всъщност извършват синхронен I/O и стартира допълнителна нишка, вероятно надвишаваща максималната стойност на едновременност. Известията могат да се изпращат и ръчно, без извършване на I/O, чрез извикване на функцията PostQueuedCompletionStatus.

Следният код демонстрира пример за използване на ThreadPool.BindHandle() с Win32 файлов дескриптор:

Използване на системата; използване на System.Threading; използване на Microsoft.Win32.SafeHandles; използване на System.Runtime.InteropServices; public class Extensions ( internal static extern SafeFileHandle CreateFile(string lpFileName, EFileAccess dwDesiredAccess, EFileShare dwShareMode, IntPtr lpSecurityAttributes, ECreationDisposition dwCreationDisposition, EFileAttributes dwFlagsAndAttributes, IntPtr hTemplateFile); static unsafe extern bool WriteFile(SafeFileHandle hFile, byte lpBuffer, uint nNumberOfBytesToWrite, out uint lpNumberOfBytesWritten , System.Threading.NativeOverlapped* lpOverlapped); enum EFileShare: uint ( None = 0x00000000, Read = 0x00000001, Write = 0x00000002, Delete = 0x00000004 ) enum ECreationDisposition: uint ( New = 1, CreateAlways = 2,4 OpenAlways = 3, OpenAlways = , TruncateExisting = 5 ) enum EFileAttributes: uint ( // ... Някои флагове не са показани Normal = 0x00000080, Overlapped = 0x40000000, NoBuffering = 0x20000000, ) enum EFileAccess: uint ( // ... Някои флагове не са показани GenericRead = 0x80000000 , GenericWrite = 0x40000000, ) статичен lo ng_numBytesWritten; // Спирачка за запис на поток static AutoResetEvent _waterMarkFullEvent; static int _pendingIosCount; const int MaxPendingIos = 10; // Рутина за завършване, извикана от I/O нишки static unsafe void WriteComplete(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP) ( _numBytesWritten += numBytes; Overlapped ovl = Overlapped.Unpack(pOVERLAP); Overlapped.Free(pOVERLAP); / / Уведомете нишката за писане, че броят на чакащите I/O операции // е намалял до разрешената граница, ако (Interlocked.Decrement(ref _pendingIosCount) = MaxPendingIos) ( _waterMarkFullEvent.WaitOne(); ) ) ) ) )

Нека първо разгледаме метода TestIOCP. Това извиква функцията CreateFile(), която е функция на механизъм P/Invoke, използвана за отваряне или създаване на файл или устройство. За извършване на I/O операции в асинхронен режим, флагът EFileAttributes.Overlapped трябва да бъде предаден на функцията. Ако е успешна, функцията CreateFile() връща файлов дескриптор на Win32, който свързваме към I/O порта за завършване чрез извикване на ThreadPool.BindHandle(). След това се създава обект на събитие, който се използва за временно блокиране на нишката, която е инициирала I/O операцията, ако има твърде много такива операции (ограничението се задава от константата MaxPendingIos).

След това започва цикълът на асинхронни операции за запис. Във всяка итерация се създава буфер с данни за запис и припокрита структура, съдържащ отместването във файла (в този пример записът винаги се извършва при отместване 0), манипулатор на събитие, който да бъде изпратен, когато операцията завърши (не се използва от механизма IOCP) и незадължителен потребителски обект IAsyncResultТова може да се използва за предаване на състояние към функцията за финализиране.

След това се извиква методът Overlapped.Pack(), който приема функция за обвиване и буфер за данни. Той създава еквивалентна I/O структура на ниско ниво в неуправлявана памет и закрепва буфера за данни. Освобождаването на неуправляваната памет, заета от структурата на ниско ниво, и отделянето на буфера трябва да се извърши ръчно.

Ако няма твърде много I/O, които се извършват едновременно, ние извикваме WriteFile(), като му предаваме указаната структура от ниско ниво. В противен случай изчакваме, докато настъпи събитие, което показва, че броят на чакащите операции е паднал под горната граница.

Функцията за завършване WriteComplete се извиква от нишка в пула от нишки за завършване на I/O веднага щом операцията приключи. Подава се указател към асинхронна I/O структура на ниско ниво, която може да бъде разопакована и преобразувана в управлявана припокриваща се структура.

В обобщение, когато работите с високопроизводителни I/O устройства, използвайте асинхронни I/O с портове за завършване, или директно чрез създаване и използване на собствен порт за завършване в неуправляваната библиотека, или чрез свързване на манипулатори на Win32 към порта за завършване в .NET с метод ThreadPool.BindHandle().

Пул от нишки в .NET

Пулът от нишки в .NET може успешно да се използва за различни цели, за всяка от които се създават нишки различни видове. При обсъждането на паралелните изчисления по-рано се запознахме с API на пула от нишки, където го използвахме за паралелизиране на изчислителни задачи. Въпреки това, пуловете от нишки могат да се използват и за решаване на други видове проблеми:

    Работните нишки могат да обработват асинхронни извиквания към потребителски делегати (като BeginInvoke или ThreadPool.QueueUserWorkItem).

    Нишките за завършване на I/O могат да обслужват известия от глобалния IOCP порт.

    Чакащите нишки могат да чакат регистрирани събития, което ви позволява да чакате множество събития в една и съща нишка наведнъж (с помощта на WaitForMultipleObjects), до горната граница на Windows (максимален брой обекти на чакане = 64). Приемането на изчакване на събитие се използва за организиране на асинхронен I/O без използване на портове за завършване.

    Нишките на таймерите чакат изтичане на няколко таймера наведнъж.

    Резби за портаконтролирайте използването на процесора от нишки от пула и също така променяйте броя на нишките (в рамките на зададените ограничения), за да постигнете най-висока производителност.

Възможно е да се инициират I/O операции, които изглеждат асинхронни, но не са. Например извикването на делегата ThreadPool.QueueUserWorkItem и след това извършването на синхронна I/O операция не е наистина асинхронна операция и не е по-добро от извършването на същата операция в нормална нишка на изпълнение.

Копие на паметта

Не е необичайно физическо I/O устройство да връща буфер от данни, които се копират отново и отново, докато приложението не приключи обработката им. Такова копиране може да изразходва значителна част от процесорната мощ на процесора, така че трябва да се избягва, за да се осигури максимална производителност. След това ще разгледаме няколко ситуации, в които е обичайно да се копират данни, и ще се запознаем с техниките за избягване на това.

Неуправляема памет

Работата с буфер в неуправлявана памет е много по-трудна в .NET, отколкото с управляван байтов масив, така че програмистите често копират буфера в управлявана памет в търсене на най-лесния начин.

Ако функциите или библиотеките, които използвате, ви позволяват изрично да посочите буфер в паметта или да предадете вашата функция за обратно извикване към него, за да разпределите буфер, разпределете управляван буфер и го закачете в паметта, така че да може да бъде достъпен както от указател, така и от управлявана препратка. Ако буферът е достатъчно голям (> 85 000 байта), той ще бъде създаден на купчина от големи обекти (Large Object Heap), така че опитайте да използвате повторно съществуващите буфери. Ако повторното използване на буфера е усложнено от несигурността на живота на обекта, използвайте пулове памет.

В други случаи, когато самите функции или библиотеки разпределят (неуправлявана) памет за буфери, можете да получите достъп до тази памет директно чрез указател (от опасен код) или чрез използване на класове обвивки като UnmanagedMemoryStreamи UnmanagedMemoryAccessor. Въпреки това, ако трябва да предадете буфера на някакъв код, който работи само с байтови масиви или низови обекти, копирането може да е неизбежно.

Дори ако не можете да избегнете копирането на паметта и някои или повечето от вашите данни се филтрират рано, можете да избегнете ненужното копиране, като проверите дали данните са необходими, преди да ги копирате.

Експортиране на част от буфера

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

За да избегнете ненужно копиране на паметта, организирайте отместването и дължината да се приемат навсякъде, където получавате байтов параметър. Използвайте параметъра за дължина вместо свойството Length на масива и добавете стойността на отместването към текущите индекси.

Произволно четене и обединяване на запис

Произволното сливане при четене и запис е възможност, поддържана от Windows за четене в несъседни региони или запис на данни от несъседни региони, сякаш заемат непрекъсната част от паметта. Тази функционалност се предоставя в Win32 API като функции ReadFileScatterи WriteFileGather. Библиотеката на Windows Sockets също поддържа произволно четене и обединяване на записи, като предоставя свои собствени функции: WSASend, WSARecv и други.

Произволното четене и обединяването може да бъде полезно в следните ситуации:

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

    Когато е желателно да се отървете от допълнителните разходи за извиквания на системни повиквания при извършване на I/O с множество буфери.

В сравнение с функциите ReadFileScatter и WriteFileGather, които изискват всеки буфер да бъде точно с размера на една страница и манипулаторът да се отваря асинхронно и небуфериран (още по-голямо ограничение), базираните на сокет разпръснати функции за четене и сливане за запис изглеждат по-практични , защото те нямат тези ограничения. рамка. NET Frameworkподдържа разпръснати четения и обединени записи за сокети чрез претоварени методи Socket.Send()и Socket.Receive()без експортиране на общи функции за четене/запис.

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

Файл I/O

Обикновено файловите I/O операции се извършват чрез кеша на файловата система, което осигурява някои предимства в производителността: кеширане на наскоро използвани данни, четене напред (предварително четене на данни от диск), мързеливи записи (асинхронни записи на диск) и конкатенация на запис на малки части от данни. Като подканите Windows за очаквания модел за достъп до файлове, можете да получите допълнително повишаване на производителността. Ако приложението ви извършва асинхронен вход/изход и може да се справи с някои от проблемите с буферирането, избягването на механизма за кеширане може да бъде по-добро решение.

Управление на кеша

Когато създават или отварят файлове, програмистите предават флагове и атрибути на функцията CreateFile, някои от които засягат поведението на механизма за кеширане:

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

    Флаг FILE_FLAG_RANDOM_ACCESSуказва, че файлът ще бъде достъпен в произволен ред. В този случай мениджърът на кеша ще извърши четене малко по-напред, поради намаления шанс предварително прочетените данни действително да са необходими на приложението.

    Флаг FILE_ATTRIBUTE_TEMPORARYпоказва, че файлът е временен, така че действителните операции по запис на физическия носител (за предотвратяване на загуба на данни) могат да бъдат отложени.

В .NET тези опции се поддържат (с изключение на последната) с помощта на претоварения конструктор FileStream, който приема параметър тип enum на FileOptions.

Произволният достъп има отрицателно въздействие върху производителността, особено при работа с дискови устройства, тъй като изисква движещи се глави. С развитието на технологията пропускателната способност на диска се е увеличила само чрез увеличаване на плътността на съхранението, а не чрез намаляване на латентността. Съвременните дискове са способни да пренареждат изпълнението на заявка с произволен достъп, за да намалят общото време, прекарано в преместване на глави. Този подход се нарича опашка от хардуерни команди (Native Command Queuing, NCO). За да направи тази техника по-ефективна, дисковият контролер трябва да изпрати няколко I / O заявки наведнъж. С други думи, ако е възможно, опитайте да имате множество асинхронни входно/изходни заявки наведнъж.

Небуфериран I/O

Небуферираните I/O операции винаги се извършват без използване на кеша. Този подход има своите предимства и недостатъци. Както при трика за контрол на кеша, необработеният I/O се активира чрез опцията "флагове и атрибути" по време на създаване на файл, но .NET не предоставя достъп до тази възможност.

    Флаг FILE_FLAG_NO_BUFFERINGзабранява кеширането на четене и запис, но не засяга кеширането, извършвано от дисковия контролер. Това избягва копирането (от буфера на потребителя в кеша) и "замърсяването" на кеша (запълване на кеша с ненужни данни и изтласкване на необходимите). Небуферираните четения и записи обаче трябва да се придържат към изискванията за подравняване.

    Следните параметри трябва да са равни или кратни на размера на сектора на диска: размерът на един трансфер, отместването във файла и адресът на буфера в паметта. Обикновено дисковият сектор е с размер 512 байта. Най-новите дискови устройства с голям капацитет имат размер на сектора от 4096 байта, но те могат да работят в режим на съвместимост, като емулират 512-байтови сектори (за сметка на производителността).

    Флаг FILE_FLAG_WRITE_THROUGHинструктира мениджъра на кеша, че трябва незабавно да изчисти данните за запис от кеша (ако флагът FILE_FLAG_NO_BUFFERING не е зададен) и казва на дисковия контролер, че трябва незабавно да записва на физически носител, без да съхранява данни в междинен хардуерен кеш.

Предварителното четене подобрява производителността чрез по-пълно използване на диска, дори когато приложение чете в синхронен режим със закъснения между операциите. Правилното определяне коя част от файла приложението ще поиска следващата зависи от Windows. Като деактивирате буферирането, вие също деактивирате изпреварващото четене и трябва да поддържате дисковото устройство заето, като извършвате множество припокриващи се I/O.

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

синхронен моделвход изход. Системните извиквания read(2), write(2) и техните еквиваленти връщат контрол само след като данните са били прочетени или записани. Често това води до блокиране на нишката.

Забележка

Всъщност не всичко е толкова просто. read(2) наистина трябва да изчака данните да бъдат физически прочетени от устройството, но write(2) работи в режим на обратно записване по подразбиране: той се връща, след като данните са прехвърлени в системния буфер, но обикновено преди данните да бъдат физически прехвърлени на устройството. Това като цяло подобрява значително наблюдаваната производителност на програмата и позволява паметта с данни да се използва за други цели веднага след връщане на write(2). Но мързеливото писане има и значителни недостатъци. Основният е, че няма да разберете резултата от физическа операция веднага по кода за връщане на write(2) , а само известно време след връщането, обикновено по кода за връщане на следващото извикване на write(2) . За някои приложения - за монитори на транзакции, за много програми в реално време и т.н. - това е неприемливо и те са принудени да изключат мързеливото писане. Това се прави с флага O_SYNC, който може да бъде зададен, когато файлът е отворен и променен в отворения файл чрез извикване на fcntl(2).

Синхронизирането на отделните операции за запис може да се осигури чрез извикване на fsync(2) . За много приложения, които работят с множество устройства и/или мрежови връзки синхронен моделнеудобно. Работата в режим на анкета също не винаги е приемлива. Това е така, защото select(3C) и poll(2) считат файлов дескриптор за готов за четене само след като данните са се появили физически в неговия буфер. Но някои устройства започват да дават данни само след като бъдат изрично помолени да го направят.

Също така, за някои приложения, особено за приложения в реално време, е важно да знаете точния момент, когато данните започват да пристигат. За такива приложения може също да е неприемливо, че select(3C) и poll(2) вземат предвид редовни файловевинаги готов за четене и писане. Наистина ли, файлова системасе чете от диска и въпреки че е много по-бърз от повечето интернет връзка, но все пак достъпът до него е свързан с известно забавяне. За твърди приложения в реално време тези забавяния може да не са приемливи - но без изрична заявка за четене файлова системане дава данни!

От гледна точка на твърди приложения в реално време, друг аспект на I/O проблема може да бъде важен. Факт е, че твърдите приложения в реално време имат по-висок приоритет от ядрото, така че те изпълняват системни повиквания - дори и неблокиращи! - може да доведе до инверсия на приоритета.

Решението на тези проблеми е известно отдавна и се нарича асинхронен I/O. В този режим входно/изходните системни повиквания се връщат веднага щом бъде направена заявка към драйвера на устройството, обикновено дори преди данните да бъдат копирани в системния буфер. Формирането на заявка се състои в поставяне на запис ( IRP , Input/Output Request Packet , входно/изходен пакет заявка ) в опашката. За да направите това, просто трябва да заснемете за кратко мютекса, който защитава "опашката" на опашката, така че проблемът с инверсията на приоритета се преодолява лесно. За да разберете дали обаждането е приключило и ако е приключило, как точно и дали е възможно да се използва паметта, в която са били съхранени данните, е предоставен специален API (виж Фиг. 8.1)


Ориз. 8.1.

Асинхронен моделбеше основният I/O модел в OS като DEC RT-11, DEC RSX-11, VAX/VMS, OpenVMS. Почти всички подкрепят този модел под една или друга форма. ОС в реално време. От края на 80-те години Unix системите използват няколко несъвместими API за асинхронен I/O. През 1993 г. ANSI/IEEE прие POSIX 1003.1b, който описва стандартизиран API, който ще разгледаме по-късно в този раздел.

В Solaris 10 асинхронните I/O функции са включени в библиотеката libaio.so. За да създадете програми, които използват тези функции, трябва да използвате ключа -laio. Функциите aio_read(3AIO), aio_write(3AIO) и lio_listio(3AIO) се използват за генериране на заявки за асинхронен I/O.

Функциите aio_read(3AIO) и aio_write(3AIO) имат един параметър, structaiocb *aiocbp. aiocb структура, дефинирана във файл< aio .h>и съдържа следните полета:

  • int aio_fildes - файлов дескриптор
  • off_t aio_offset - отместване във файла, от който да се пише или чете
  • volatile void* aio_buf - буфер за четене на данни от или за запис на данни.
  • size_t aio_nbytes - размер на буфера. Подобно на традиционното read(2), aio_read(3AIO) може да прочете по-малко данни от заявените, но никога повече.
  • int aio_reqprio - приоритет на заявката
  • struct sigevent aio_sigevent - начин за сигнализиране на завършване на заявка (обсъдено по-късно в този раздел)
  • int aio_lio_opcode - не се използва за aio_read(3AIO) и aio_write(3AIO), използва се само от функцията lio_listio.

Функцията lio_listio(3AIO) ви позволява да генерирате множество I/O заявки с едно системно извикване. Тази функция има четири параметъра:

  • режим int - може да приема стойностите LIO_WAIT (функцията изчаква завършването на всички заявки) и LIO_NOWAIT (функцията връща контрола веднага след формирането на всички заявки).
  • struct aiocb *list - списък с указатели към aiocb структури с описания на заявките.

    Заявките могат да се четат и записват, това се определя от полето aio_lio_opcode. Заявките за единичен дескриптор се изпълняват в реда, в който са посочени в масива от списък.

  • int nent - броят на записите в масива от списък.
  • struct sigevent *sig - начин за сигнализиране на изпълнението на всички заявки. Ако mode==LIO_WAIT, този параметър се игнорира.

Библиотеката POSIX AIO предоставя два начина за уведомяване на програма, че дадена заявка е завършена, синхронен и асинхронен. Нека първо да разгледаме синхронния метод. Функцията aio_return(3AIO) връща статуса на заявка. Ако заявката вече е завършена и е завършена успешно, тя връща размера на прочетените или записани данни в байтове. Подобно на традиционното read(2), aio_return(3AIO) връща 0 байта в края на файла. Ако заявката е неуспешна или все още не е завършена, се връща -1 и се задава errno. Ако заявката все още не е завършена, кодът на грешката е EINPROGRESS.

aio_return(3AIO) е разрушителен; ако бъде извикан при завършена заявка, той ще унищожи системния обект, който съдържа информация за състоянието на заявката. Следователно извикването на aio_return(3AIO) няколко пъти за една и съща заявка не е възможно.

Функцията aio_error(3AIO) връща кода на грешка, свързан със заявката. Връща 0, ако заявката е успешна, код за грешка, ако е неуспешна, или EINPROGRESS за непълни заявки.

Функцията aio_suspend(3AIO) блокира нишка, докато една от посочените асинхронни I/O заявки не завърши или за определен период от време. Тази функция има три параметъра:

  • const struct aiocb *const списък- масив от указатели към заявени дескриптори.
  • int nent - броят на елементите в списъка масив.
  • const struct timespec *изчакване- таймаут с точност до наносекунди (всъщност с точност до резолюция системен таймер).

Функцията връща 0, ако поне една от операциите, изброени в списъка, е завършена. Ако функцията се провали, тя връща -1 и задава errno. Ако времето за изчакване на функцията изтече, тя също връща -1 и errno==EINPROGRESS.

Пример за използване на асинхронен I/O със синхронна проверка на състоянието на заявка е показан в Пример 8.3.

Const char req="GET / HTTP/1.0\r\n\r\n"; int main() ( int s; static struct aiocb readrq; static const struct aiocb *readrqv=(&readrq, NULL); /* Отворен сокет […] */ memset(&readrq, 0, sizeof readrq); readrq.aio_fildes=s ; readrq.aio_buf=buf; readrq.aio_nbytes=sizeof buf; if (aio_read(&readrq)) ( /* ... */) write(s, req, (sizeof req)-1); while(1) ( aio_suspend (readrqv, 1, NULL); size=aio_return(&readrq); if (size>0) ( write(1, buf, size); aio_read(&readrq); ) else if (size==0) ( break; ) else if ( errno!=EINPROGRESS) ( perror("четене от сокет"); ) ) ) 8.3. Асинхронен I/O с проверка на статуса на синхронна заявка. Кодът е съкратен, отварянето на сокета и обработката на грешки са изключени от него.

Асинхронното уведомяване на приложението за завършване на операциите се състои в генериране на сигналв края на операцията. За да направите това, трябва да направите съответните настройки в полето aio_sigevent на дескриптора на заявката. Полето aio_sigevent е от тип struct sigevent. Тази структура е дефинирана в и съдържа следните полета:

  • int sigev_notify - режим на уведомяване. Валидни стойности са SIGEV_NONE (не изпращайте потвърждения), SIGEV_SIGNAL (генерирайте сигнал, когато заявката завърши) и SIGEV_THREAD (изпълнете посочената функция в отделна нишка, когато заявката завърши). Solaris 10 също поддържа типа предупреждение SIGEV_PORT, който е разгледан в приложението към тази глава.
  • int sigev_signo е номерът на сигнала, който ще бъде генериран при използване на SIGEV_SIGNAL.
  • union sigval sigev_value е параметърът, който трябва да бъде предаден на манипулатора на сигнала или функцията на манипулатора. Когато се използва за асинхронен I/O, това обикновено е указател към заявка.

    Когато използвате SIGEV_PORT, това трябва да е указател към структура port_event_t, съдържаща номера на порта и евентуално допълнителни данни.

  • void (*sigev_notify_function)(union sigval)- функцията, която ще бъде извикана при използване на SIGEV_THREAD.
  • pthread_attr_t *sigev_notify_attributes- атрибути на нишката, в която ще се стартира
  • sigev_notify_function при използване на SIGEV_THREAD.

Не всички реализации на libaio поддържат известието SIGEV_THREAD. Някои Unix системи използват вместо това нестандартното известие SIGEV_CALLBACK. По-късно в тази глава ще обсъдим само предупреждение със сигнал.

Някои приложения използват или SIGIO, или SIGPOLL като номер на сигнала (те са един и същ сигнал на Unix SVR4). Често се използват и SIGUSR1 или SIGUSR2; това е удобно, защото гарантира, че подобен сигнал няма да се появи по друга причина.

Приложенията в реално време също използват числа на сигнала в диапазона SIGRTMIN до SIGRTMAX. Някои реализации разпределят специален номер на сигнала SIGAIO или SIGASYNCIO за тази цел, но няма такъв сигнал в Solaris 10.

Разбира се, преди да изпълните асинхронни заявки с известие за сигнал, трябва да инсталирате манипулатор за този сигнал. За известяване трябва да използвате сигнали, обработени в режим SA_SIGINFO. Не е възможно да зададете такъв манипулатор чрез системните извиквания signal(2) и sigset(2), трябва да използвате sigaction(2). Инсталиране на манипулатори със сигакция

Операциите за въвеждане и извеждане са по своята същност по-бавни от другите видове обработка. Това забавяне се дължи на следните фактори:

Закъснения поради времето, прекарано в търсене на желаните песни и сектори на устройства с произволен достъп (дискове, компактдискове).

Закъснение, дължащо се на относително бавни скорости на трансфер на данни между физически устройства и системна памет.

Закъснения при предаване на данни по мрежата с помощта на файлове, сървъри, хранилища за данни и т.н.

Във всички предишни примери се изпълняват I/O операции в синхрон с потокатака че цялата нишка е принудена да не работи, докато завършат.

Тази глава показва как можете да уредите една нишка да продължи да се изпълнява, без да чака I/O да завърши, което би било същото като нишката. асинхроненвход изход. Различни техники, налични в Windows, са илюстрирани с примери.

Някои от тези техники се използват в таймерите за изчакване, които също са описани в тази глава.

И накрая, и най-важното, като научим за стандартния асинхронен I/O, можем да използваме Портове за завършване на I/O,които са изключително полезни при изграждане на мащабируеми сървъри, които могат да поддържат голям брой клиенти, без да създават отделна нишка за всеки от тях. Програма 14.4 е модифицирана версия на предварително разработения сървър, който позволява използването на I/O портове за завършване.

Общ преглед на асинхронните I/O методи на Windows

AT Изпълнение на Windowsасинхронният I/O се предоставя в съответствие с три техники.

Многопоточен вход/изход (Multihreaded I/O).Всяка от нишките в рамките на процес или набор от процеси извършва нормален синхронен I/O, докато други нишки могат да продължат да се изпълняват.

Припокриващи се I/O.След стартиране на четене, запис или друга I/O операция, нишката продължава своето изпълнение. Ако дадена нишка се нуждае от I/O резултати, за да продължи да изпълнява, тя изчаква, докато подходящата манипулатор стане налична или се появи определеното събитие. В Windows 9x припокритият вход/изход се поддържа само за серийни устройства, като наименувани канали.

Рутинни процедури за завършване (разширен I/O)Когато I/O операциите са завършени, системата извиква специален процедура за завършване,работи вътре в нишка. Разширеният I/O за дискови файлове не се поддържа в Windows 9x.

Многонишков I/O, използващ наименувани канали, е реализиран в многонишковия сървър, обсъден в Глава 11. Програмата grepMT (Програма 7.1) управлява паралелни I/O операции, включващи множество файлове. По този начин вече имаме редица програми, които изпълняват многонишков I/O и по този начин предоставят форма на асинхронен I/O.

Припокритият I/O е предмет на следващия раздел и примерите за конвертиране на файлове (от ASCII в UNICODE) в този раздел използват тази техника, за да илюстрират възможностите на последователната обработка на файлове. За целта се използва модифицирана версия на програмата 2.4. След припокриване на I/O се разглежда разширен I/O с помощта на рутинни процедури за завършване.

Забележка

Припокритите и разширени I/O методи често са трудни за прилагане, рядко осигуряват някакви предимства в производителността, понякога дори причиняват влошаване на производителността, а в случай на файлов I/O, могат да работят само под Windows NT. Тези проблеми се преодоляват с помощта на нишки, толкова много читатели вероятно ще искат да пропуснат напред към разделите за таймерите за изчакване и портовете за завършване на I/O,връщане към този раздел, ако е необходимо. От друга страна, елементи на асинхронен I/O присъстват както в наследените, така и в новите технологии, и следователно тези методи все още си струва да бъдат проучени.

Например, COM технологията на платформата NT5 поддържа асинхронно извикване на метод, така че тази техника може да бъде полезна за много читатели, които използват или възнамеряват да използват COM технология. Освен това операциите за извикване на асинхронни процедури (Глава 10) имат много общо с разширения I/O и въпреки че аз лично предпочитам да използвам нишки, други може да предпочетат този механизъм.

Припокриващи се I/O

Първото нещо, което трябва да направите, за да организирате асинхронен I/O, независимо дали е припокрит или разширен, е да зададете припокриващия атрибут на файл или друг дескриптор. За да направите това, когато извиквате CreateFile или друга функция, която води до създаването на файл, наименуван канал или друг манипулатор, флагът FILE_FLAG_OVERLAPPED трябва да бъде указан.

В случай на сокети (глава 12), независимо дали са създадени чрез socket или accept, атрибутът override е зададен по подразбиране в Winsock 1.1, но трябва да бъде зададен изрично в Winsock 2.0. Припокритите сокети могат да се използват асинхронно във всички версии на Windows.

До този момент структурите OVERLAPPED са били използвани във връзка с функцията LockFileEx и като алтернатива на използването на функцията SetFilePointer (Глава 3), но те също са съществен елемент на припокриващия се I/O. Тези структури действат като незадължителни параметри при извикване на четирите функции по-долу, които могат да блокират, когато операциите завършат.

Спомнете си, че когато посочите флага FILE_FLAG_OVERLAPPED като част от параметъра dwAttrsAndFlags (в случай на функцията CreateFile) или параметъра dwOpen-Mode (в случая на функцията CreateNamedPipe), съответният файл или канал може да се използва само в припокриване режим. При анонимни канали припокритият I/O не работи.

Забележка

В документацията за функцията CreateFile се споменава, че използването на флага FILE_FLAG_NO_BUFFERING подобрява производителността на припокриващи се I/O. Експериментите показват само незначително подобрение на производителността (около 15%, което може да се провери чрез експериментиране с програма 14.1), но трябва да се уверите, че общият размер на прочетените данни при извършване на операции ReadFile или WriteFile е кратно на размера на сектора на диска .

Припокриващи се гнезда

Едно от най-важните нововъведения в Windows Sockets 2.0 (Глава 12) е стандартизацията на припокриващите се I/O. По-специално, сокетите вече не се създават автоматично като припокриващи се файлови дескриптори. Функцията за гнездо създава дръжка без припокриване. За да създадете припокрит сокет, трябва да извикате функцията WSASocket, като изрично поискате създаването на припокрит сокет, като посочите стойността WSA_FLAG_OVERLAPPED за параметъра dwFlags на функцията WSASocket.

SOCKET WSAAPI WSASocket(int iAddressFamily, int iSocketType, int iProtocol, LPWSAPROTOCOL_INFO lpProtocolInfo, GROUP g, DWORD dwFlags);

За да създадете сокет, използвайте функцията WSASocket вместо функцията за сокет. Всеки сокет, върнат от функцията accept, ще има същите свойства като аргумента.

Последици от използването на припокриващи се I/O

Припокритият I/O се извършва асинхронно. Това има няколко извода.

Припокритите I/O операции не се блокират. Функциите ReadFile, WriteFile, TransactNamedPipe и ConnectNamedPipe се връщат, без да чакат завършването на I/O операцията.

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

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

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

Програмата трябва да може да чака (синхронизира) за завършване на I/O. Ако има множество чакащи I/O операции, свързани с един и същ манипулатор, програмата трябва да може да определи коя от операциите вече е завършена. I/O операциите не завършват непременно в същия ред, в който са започнали да се изпълняват.

За преодоляване на последните две от изброените по-горе трудности се използват ПРИПОКРИВАНИ структури.

ПРИПОКРИВАНЕ Конструкции

Използвайки структурата OVERLAPPED (посочена например от параметъра lpOverlapped на функцията ReadFile), можете да посочите следната информация:

Позицията във файла (64 бита), от която трябва да започне операцията за четене или запис, както е обсъдено в глава 3.

Събитие (ръчно изчистено), което ще бъде сигнализирано, когато свързаната операция приключи.

Следното е дефиницията на ПРИПОКРИТАТА структура.

Както полетата Offset, така и OffsetHigh трябва да се използват за указване на позицията във файла (показател), въпреки че високата част на показалеца (OffsetHigh) в много случаи е 0. Полетата Internal и InternalHigh, които са запазени за системна употреба, трябва да не се използва.

Параметърът hEvent е манипулатор на събитието (създадено с функцията CreateEvent). Това събитие може да бъде наименувано или неназовано, но то трябва дазадължително да може да се нулира ръчно (вижте глава 8), ако се използва за припокриващи се I/O; причините за това ще бъдат обяснени скоро. Когато I/O операцията завърши, събитието преминава в сигнализирано състояние.

В различен възможен вариантизползването на манипулатора hEvent е NULL; в този случай програмата може да изчака да бъде сигнализиран файлов дескриптор, който също може да действа като обект за синхронизация (вижте предупрежденията по-долу). Системата използва състоянията на сигнала на файловия дескриптор, за да проследи завършването на операциите, ако hEvent дескрипторът е NULL, тоест файловият дескриптор е обектът за синхронизация в този случай.

Забележка

За удобство терминът "файлов манипулатор", използван във връзка с манипулаторите, посочени при извикване на функциите ReadFile, WriteFile и т.н., ще бъде използван от нас дори в случаите, когато говорим за наименувани канали или манипулатори на устройства, не е файл.

Когато се направи извикване на I/O функция, това събитие незабавно се изчиства от системата (настройва се на състояние без сигнал). Когато I/O операция завърши, събитието се настройва на сигнализирано състояние и остава там, докато не бъде използвано от друга I/O операция. Едно събитие трябва да може да се нулира ръчно, ако множество нишки могат да чакат да бъде сигнализирано (въпреки че в нашите примери се използва само една нишка) и не е необходимо да чакат, когато операцията приключи.

Дори ако файловият дескриптор е синхронен (т.е. създаден без флага FILE_FLAG_OVERLAPPED), структурата OVERLAPPED може да служи като алтернатива на функцията SetFilePointer за указване на позиция на файл. В този случай връщане от повикване към ReadFile или друго повикване не се случва, докато I/O операцията не приключи. Вече използвахме тази функция в глава 3. Също така имайте предвид, че чакащите I/O операции се идентифицират уникално чрез комбинацията от файлов дескриптор и съответната структура OVERLAPPED.

По-долу са изброени някои предупреждения, които трябва да се вземат под внимание.

Избягвайте повторното използване на OVERLAPPED структура, докато свързаната с нея I/O операция, ако има такава, все още не е завършена.

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

Ако има множество чакащи заявки, които се отнасят до един и същ припокриващ се манипулатор, използвайте манипулатори на събития вместо файлови манипулатори за синхронизиране.

Ако OVERLAPPED структура или събитие действа като автоматична променлива в блок, уверете се, че блокът не може да излезе, докато не се синхронизира с I/O операцията. Освен това, за да избегнете изтичане на ресурси, трябва да се внимава да затворите манипулатора, преди да излезете от блока.

Припокрити I/O състояния

Функциите ReadFile и WriteFile, както и горните две наименувани канални функции, се връщат незабавно, когато се използват за извършване на припокриващи се I/O операции. В повечето случаи I/O операцията няма да е завършила до този момент и върнатата стойност за четене/запис ще бъде FALSE. Функцията GetLastError ще върне ERROR_IO_PENDING в тази ситуация.

След като изчакате обектът за синхронизиране (събитие или може би файлов дескриптор) да сигнализира завършването на операцията, трябва да разберете колко байта са прехвърлени. Това е основната цел на функцията GetOverlappedResult.

BOOL GetOverlappedResult(HANDLE hFile, LPOVERLAPPED lpOverlapped, LPWORD lpcbTransfer, BOOL bWait)

Индикацията за конкретна I/O операция се осигурява от комбинацията от манипулатора и структурата OVERLAPPED. Стойността на параметъра bWait TRUE показва, че функцията GetOverlappedResult трябва да изчака, докато операцията завърши; в противен случай връщането от функцията трябва да бъде незабавно. И в двата случая тази функция ще върне TRUE само след като операцията приключи успешно. Ако върнатата стойност на функцията GetOverlappedResult е FALSE, тогава функцията GetLastError ще върне ERROR_IO_INCOMPLETE, което позволява тази функция да бъде извикана за запитване за I/O завършване.

Броят на прехвърлените байтове се съхранява в променливата *lpcbTransfer. Винаги се уверявайте, че структурата OVERLAPPED остава непроменена от момента, в който се използва в припокриваща се I/O операция.

Анулиране на припокрити I/O операции

Булевата функция CancelIO ви позволява да отмените изпълнението на предстоящи припокрити I/O операции, свързани с посочения манипулатор (тази функция има само един параметър). Всички операции, инициирани от извикващата нишка, използваща този манипулатор, се отменят. Операциите, инициирани от други нишки, не се влияят от това извикване на функция. Отменените операции завършват с грешка ERROR OPERATION ABORTED.

Пример: Използване на файлов дескриптор като обект за синхронизация

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

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

OVERLAPPED ov = ( 0, 0, 0, 0, NULL /* Събитията не се използват. */);
hF = Създаване на файл (…, FILE_FLAG_OVERLAPPED, …);
ReadFile(hF, Buffer, sizeof(Buffer), &nRead, &ov);
/* Извършване на друга обработка. nRead не е непременно валидно.*/
/* Изчакайте операцията за четене да завърши. */
WaitForSingleObject(hF, INFINITE);
GetOverlappedResult(hF, &ov, &nRead, FALSE);

Пример: Конвертиране на файлове чрез припокриване на I/O и множествено буфериране

Програма 2.4 (atou) преобразува ASCII файл в UNICODE чрез последователна обработка на файла, а Глава 5 показа как да направите същата последователна обработка с помощта на преобразуване на файлове. Програма 14.1 (atouOV) решава същия проблем, като използва припокрити I/O и множество буфери, които съдържат записи с фиксиран размер.

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

Първо, програмата инициализира всички елементи на OVERLAPPED структури, които дефинират събития и позиции във файловете. За всеки входен и изходен буфер има отделна структура OVERLAPPED. След това се инициира припокрита операция за четене за всеки от входните буфери. След това, използвайки функцията WaitForMultipleObjects, програмата изчаква едно събитие, показващо завършването на четене или писане. Когато операцията за четене завърши, входният буфер се копира и преобразува в съответния изходен буфер, след което се инициира операцията за запис. Когато записът приключи, се инициира следващата операция за четене. Имайте предвид, че събитията, свързани с входните и изходните буфери, се поставят в един масив, който се използва като аргумент при извикване на функцията WaitForMultipleObjects.

Ориз. 14.1.Асинхронен модел за актуализиране на файлове


Програма 14.1. atouOV: преобразуване на файлове с помощта на припокриващи се I/O
Конвертиране на файл от ASCII в Unicode с помощта на припокриващи се I/O. Програмата работи само на Windows NT. */

#define MAX_OVRLP 4 /* Брой припокрити I/Os.*/
#define REC_SIZE 0x8000 /* 32 KB: Минимален размер на записа за приемлива производителност. */

/* Всеки от елементите на променливите масиви, дефинирани по-долу */
/* и структурите съответстват на една чакаща операция */
/* припокрит I/O. */
DWORD nin, nout, ic, i;
OVERLAPPED OverLapIn, OverLapOut;
/* Необходимо е да се използва солиден, двуизмерен масив */
/* продиктувано от функцията WaitForMultipleObjects. */
/* Стойността 0 на първия индекс съответства на четене, стойността 1 съответства на запис. */
/* Във всеки от двата буферни масива, дефинирани по-долу, първият индекс */
/* номерирайте I/O операциите. */
LARGE_INTEGER CurPosIn, CurPosOut, FileSize;
/* Общ брой записи за обработка, изчислен */
/* въз основа на размера на входния файл. Записът в края */
/* може да е непълна. */
за (ic = 0; ic< MAX_OVRLP; ic++) {
/* Създаване на събития за четене и запис за всяка ПРИПОКРИТА структура.*/
hEvents = OverLapIn.hEvent /* Четене на събитието.*/
hEvents = OverLapOut.hEvent /* Запис на събитие. */
= CreateEvent(NULL, TRUE, FALSE, NULL);
/* Начални файлови позиции за всяка ПРЕКРИВАНА структура. */
/* Иницииране на припокрита операция за четене на тази OVERLAPPED структура. */
ако (CurPosIn.QuadPart< FileSize.QuadPart) ReadFile(hInputFile, AsRec, REC_SIZE, &nin, &OverLapIn);
/* Всички операции за четене се изпълняват. Изчакайте събитието да завърши и го нулирайте незабавно. Събитията за четене и запис се съхраняват в масива за събития едно до друго. */
iWaits=0; /* Броят I/O операции, извършени досега. */
докато (iЧака< 2 * nRecord) {
ic = WaitForMultipleObjects(2 * MAX_OVRLP, hEvents, FALSE, INFINITE) - WAIT_OBJECT_0;
iWaits++; /* Увеличава брояча на завършените I/O операции. */
ResetEvent(hEvents);
/* Четенето е завършено. */
GetOverlappedResult(hInputFile, &OverLapIn, &nin, FALSE);
за (i =0; i< REC_SIZE; i++) UnRec[i] = AsRec[i];
WriteFile(hOutputFile, UnRec, nin * 2, &nout, &OverLapOut);
/* Подгответе се за следващото четене, което ще бъде инициирано, след като приключи операцията по запис, започната по-горе. */
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;
) иначе ако (ic< 2 * MAX_OVRLP) { /* Операция записи завершилась. */
/* Започнете да четете. */
ic -= MAX_OVRLP; /* Задаване на индекса на изходния буфер. */
if (!GetOverlappedResult (hOutputFile, &OverLapOut, &nout, FALSE)) ReportError(_T("Грешка при четене."), 0, TRUE);
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
ако (CurPosIn.QuadPart< FileSize.QuadPart) {
/* Стартиране на нова операция за четене. */
ReadFile(hInputFile, AsRec, REC_SIZE, &nin, &OverLapIn);
/* Затваряне на всички събития. */
за (ic = 0; ic< MAX_OVRLP; ic++) {

Програма 14.1 може да работи само под Windows NT. Асинхронните I/O съоръжения на Windows 9x не позволяват използването на дискови файлове. Приложение B изброява резултатите и коментарите относно сравнително лошото представяне на програмата atouOV. Експериментите показват, че размерът на буфера трябва да бъде най-малко 32 KB, за да се постигне приемлива производителност, но дори тогава нормалният синхронен I/O е по-бърз. В допълнение, производителността на тази програма не се подобрява и при условия на SMP, тъй като в този пример, който обработва само два файла, процесорът не е критичен ресурс.

Разширен I/O с помощта на процедура за завършване

Има и друг възможен подход за използване на обекти за синхронизация. Вместо нишката да чака сигнал за прекратяване от събитие или манипулатор, системата може да извика дефиниран от потребителярутинни процедури за завършване веднага след завършване на I/O операция. След това процедурата за прекратяване може да стартира следващата I/O операция и да извърши всички необходими отчети за използването на системни ресурси. Тази косвено извикана (обратно извикване) процедура за завършване е подобна на извикването на асинхронна процедура, използвана в глава 10, и изисква използването на състояния на изчакване, които могат да бъдат предупредени.

Как може да се посочи процедура за прекратяване в програма? Сред параметрите или структурите от данни на функциите ReadFile и WriteFile не са останали такива, които биха могли да се използват за съхраняване на адреса на процедурата за прекратяване. Съществува обаче семейство разширени I/O функции, които се обозначават със суфикса "Ex" и съдържат допълнителен параметър за предаване на адреса на рутинната програма за прекратяване. Функциите за четене и запис са съответно ReadFileEx и WriteFileEx. Освен това е необходима една от следните функции в режим на готовност.

Разширеният I/O понякога се нарича мито вход/изход(предупредителен I/O). Как да използвате разширени функции е описано в следващите раздели.

Забележка

Под Windows 9x разширеният I/O не може да работи с дискови файлове и комуникационни портове. В същото време Windows 9x Advanced I/O може да работи с именувани канали, пощенски кутии, сокети и серийни устройства.

Функции ReadFileEx, WriteFileEx и процедури за завършване

Разширените функции за четене и запис могат да се използват заедно с манипулатори за отваряне на файл, именуван канал и пощенска кутия, ако съответният обект е бил отворен (създаден) със зададен флаг FILE_FLAG_OVERLAPPED. Имайте предвид, че този флаг задава атрибута на манипулатора и въпреки че припокритият и разширеният I/O са различни, един и същ флаг се прилага и за двата типа асинхронни I/O манипулатори.

Припокритите сокети (Глава 12) могат да се използват с функциите ReadFileEx и WriteFileEx във всички версии на Windows.

BOOL ReadFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToRead, LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpcr)
BOOL WriteFileEx(HANDLE hFile, LPVOID lpBuffer, DWORD nNumberOfBytesToWrite, LPOVERLAPPED lpOverlapped, LPOVERLAPPED_COMPLETION_ROUTINE lpcr)

Вече сте запознати с двете функции, с изключение на това, че всяка от тях има допълнителен параметър, който ви позволява да посочите адреса на процедурата за завършване.

Всяка от функциите трябва да осигури OVERLAPPED структура, но не е необходимо да се указва елементът hEvent на тази структура; системата го игнорира. Този елемент обаче е много полезен за предаване на информация като напр сериен номер, използвани за разграничаване между отделните I/O операции, както е показано в Програма 14.2.

Сравнявайки с функциите ReadFile и WriteFile, можете да видите, че разширените функции не изискват параметри за съхраняване на броя прехвърлени байтове. Тази информация се предава на функцията за прекратяване, която трябва да бъде включена в програмата.

Функцията за завършване предоставя параметри за броя на байтовете, кода на грешката и адреса на структурата OVERLAPPED. Последният от тези параметри е необходим, за да може процедурата за завършване да определи коя от оставащите операции е приключила. Имайте предвид, че по-ранните предупреждения за повторно използване или унищожаване на OVERLAPPED структури се прилагат тук точно толкова, колкото и в случай на припокриващи се I/O.

ВАЖЕН WINAPI FileIOCompletionRoutine(DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpo)

Както в случая с функцията CreateThread, която също се извиква с името на някаква функция, името FileIOCompletionRoutineе заместител, а не действителното име на процедурата за прекратяване.

Стойностите за параметъра dwError са ограничени до 0 (успех) и ERROR_HANDLE_EOF (при опит за четене извън границите на файла). Структурата OVERLAPPED е тази, използвана от завършеното извикване на ReadFileEx или WriteFileEx.

Преди процедурата за прекратяване да бъде извикана от системата, трябва да се случат две неща:

1. I/O операцията трябва да завърши.

2. Извикващата нишка трябва да е в спящо състояние, уведомявайки системата, че трябва да изпълни рутинната процедура за завършване на опашка.

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

Ако и двете от тези условия са изпълнени, рутинните процедури за завършване, поставени на опашка в резултат на завършването на I/O операции, се изпълняват. Процедурите за завършване се изпълняват на същата нишка, която е направила първоначалното извикване на I/O функция и е в състояние на неактивно изчакване.Следователно нишката трябва да влезе в състояние на готовност само когато има безопасни условия за изпълнение на процедури за прекратяване.

Функции в режим на готовност

Има общо пет функции в режим на готовност, но следните са прототипи само на три от тях, които са от пряк интерес за нас:

DWORD WaitForSingleObjectEx(HANDLE hObject, DWORD dwMilliseconds, BOOL bAlertable)
DWORD WaitForMultipleObjectsEx(DWORD cObjects, LPHANDLE lphObjects, BOOL fWaitAll, DWORD dwMilliseconds, BOOL bAlertable)
DWORD SleepEx (DWORD dwMilliseconds, BOOL bAlertable)

Всяка от функциите на watchdog има флаг bAlertable, който трябва да бъде зададен на TRUE в случай на асинхронен I/O. Горните функции са разширения на функциите за изчакване и заспиване, с които сте запознати.

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

Манипулаторът(ите) преминава(т) към сигнализирано състояние, като по този начин удовлетворява стандартните изисквания на две от функциите за изчакване.

Времето за изчакване изтича.

Всички процедури за завършване в опашката на нишката спират да се изпълняват и bAlertable е настроен на TRUE. Процедурата за завършване се поставя на опашка, когато завърши съответната I/O операция (Фигура 14.2).

Имайте предвид, че няма събития, свързани със структурите OVERLAPPED във функциите ReadFileEx и WriteFileEx, така че нито един от манипулаторите, посочени при извикването на функцията за изчакване, не е свързан директно с конкретна I/O операция. В същото време функцията SleepEx не е свързана с обекти за синхронизация и затова е най-лесна за използване. В случай на функцията SleepEx, продължителността на интервала на заспиване обикновено е зададена на БЕЗКРАЙНО, така че връщането от тази функция ще се случи само след като една или повече процедури за завършване, които в момента са в опашката, са приключили изпълнението.

Изпълнете процедурата за завършване и се върнете от функцията в режим на готовност

Когато разширена I/O операция завърши, свързаната с нея рутинна процедура за завършване, с нейните аргументи, указващи OVERLAPPED структура, брой байтове и код за грешка, се поставя на опашка за изпълнение.

Всички процедури за завършване в опашката на нишката започват да се изпълняват, когато нишката влезе в състояние на неактивно изчакване. Те се изпълняват една след друга, но не непременно в същата последователност, в която са завършени I/O операциите. Връщането от функцията watchdog става само след връщане на процедурата за завършване. Тази функция е важна, за да се гарантира, че повечето програми функционират правилно, тъй като предполага, че рутинните програми за прекратяване получават шанс да се подготвят за следващото използване на структурата OVERLAPPED и да извършат други необходими действия, за да приведат програмата в известно състояние, преди да се върнат от спящо състояние. .

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

В заключение отбелязваме две точки:

1. Когато извиквате която и да е от функциите watchdog, използвайте INFINITE като стойност на параметъра за интервал на изчакване. При липса на опция за изчакване, функциите ще се върнат само след като всички процедури за завършване приключат с изпълнението си или дескрипторите са влезли в сигнализираното състояние.

2. Обичайно е да се използва членът с данни hEvent от структурата OVERLAPPED за предаване на информация към процедурата за завършване, тъй като това поле се игнорира от ОС.

Взаимодействието между основната нишка, рутинните процедури за завършване и функциите за наблюдение е илюстрирано на Фиг. 14.2. Този пример стартира три едновременни четения, две от които завършват до момента, в който започне чакането в режим на готовност.

Ориз. 14.2.Асинхронен I/O, използващ рутинни процедури за завършване

Пример: Конвертиране на файл чрез разширен I/O

Програма 14.3 (atouEX) е ревизирана версия на програма 14.1. Тези програми илюстрират разликата между двата метода на асинхронен I/O. atouEx е подобна на програмата 14.1, но е преместила голяма част от кода за последователност на ресурсите във финализатора и е направила много променливи глобални, така че финализаторът да има достъп до тях. Въпреки това, Приложение B показва, че по отношение на производителността atouEx е доста конкурентен на други методи, които не използват картографиране на файлове, докато atouOV е по-бавен.

Програма 14.2. atouEx: преобразуване на файлове с помощта на разширен I/O
Конвертиране на файл от ASCII в Unicode с помощта на РАЗШИРЕН I/O. */
/* atouEX файл1 файл2 */

#define REC_SIZE 8096 /* Размерът на блока не е толкова важен по отношение на производителността, колкото е при atouOV. */
#define UREC_SIZE 2 * REC_SIZE

статичен VOID WINAPI ReadDone(DWORD, DWORD, LPOVERLAPPED);
статичен VOID WINAPI WriteDone(DWORD, DWORD, LPOVERLAPPED);

/* Първата структура OVERLAPPED е за четене, а втората е за запис. За всяка следваща операция се разпределят структури и буфери. */
OVERLAPPED OverLapIn, OverLapOut;
CHAR AsRec;
WCHAR UnRec;
HANDLE hInputFile, hOutputFile;

int _tmain(int argc, LPTSTR argv) (
hInputFile = CreateFile(argv, GENERIC_READ, 0, NULL, OPEN_EXISTING, FILE_FLAG_OVERLAPPED, NULL);
hOutputFile = Създаване на файл (argv, GENERIC_WRITE, 0, NULL, CREATE_WAYS, FILE_FLAG_OVERLAPPED, NULL);
FileSize.LowPart = GetFileSize(hInputFile, &FileSize.HighPart);
nRecord = FileSize.QuadPart / REC_SIZE;
if ((FileSize.QuadPart % REC_SIZE) != 0) nRecord++;
за (ic = 0; ic< MAX_OVRLP; ic++) {
OverLapIn.hEvent = (HANDLE)ic; /* Презареди събитието. */
OverLapOut.hEvent = (HANDLE)ic; /* Полета. */
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;
ако (CurPosIn.QuadPart< FileSize.QuadPart) ReadFileEx(hInputFile, AsRec, REC_SIZE, &OverLapIn , ReadDone);
CurPosIn.QuadPart += (LONGLONG)REC_SIZE;
/* Всички операции за четене се изпълняват. Влезте в състояние на готовност и останете в него, докато всички записи бъдат обработени.*/
докато (nГотово< 2 * nRecord) SleepEx(INFINITE, TRUE);
_tprintf(_T("Преобразуването на ASCII в Unicode е завършено.\n"));

статичен VOID WINAPI ReadDone(DWORD код, DWORD nBytes, LPOVERLAPPED pOv) (
/* Четенето е завършено. Преобразуване на данни и иницииране на запис. */
LARGE_INTEGER CurPosIn, CurPosOut;
/* Обработване на записа и иницииране на операцията за запис. */
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
CurPosOut.QuadPart = (CurPosIn.QuadPart / REC_SIZE) * UREC_SIZE;
OverLapOut.Offset = CurPosOut.LowPart;
OverLapOut.OffsetHigh = CurPosOut.HighPart;
/* Конвертиране на запис от ASCII в Unicode. */
за (i = 0; i< nBytes; i++) UnRec[i] = AsRec[i];
WriteFileEx(hOutputFile, UnRec, nBytes*2, &OverLapOut, WriteDone);
/* Подгответе OVERLAPPED структурата за следващото четене. */
CurPosIn.QuadPart += REC_SIZE * (LONGLONG)(MAX_OVRLP);
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;

статичен VOID WINAPI WriteDone(DWORD код, DWORD nBytes, LPOVERLAPPED pOv) (
/* Записването е завършено. Започнете следващата операция за четене. */
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
ако (CurPosIn.QuadPart< FileSize.QuadPart) {
ReadFileEx(hInputFile, AsRec, REC_SIZE, &OverLapIn, ReadDone);

Асинхронен I/O, използващ множество нишки

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

Въпреки това, Windows осигурява многонишкова поддръжка, така че е възможно да се постигне същия ефект чрез извършване на синхронни I/O операции на множество независимо изпълняващи се нишки. Тези възможности вече бяха демонстрирани по-рано с многонишкови сървъри и програмата grepMT (Глава 7). Освен това нишките предоставят концептуално последователен и предполагаемо много по-прост начин за извършване на асинхронен I/O. Като алтернатива на методите, използвани в програми 14.1 и 14.2, може да се даде на всяка нишка собствен файлов дескриптор и след това всяка нишка може да обработва всеки четвърти запис синхронно.

Този начин на използване на потоци е демонстриран в програмата atouMT, която не е включена в книгата, но е включена в материала, публикуван на уеб сайта. Не само, че atouMT може да работи на всяка версия на Windows, но също така е по-прост от която и да е от двете асинхронни I/O програми, тъй като отчитането на използването на ресурси е по-малко сложно. Всяка нишка просто поддържа свои собствени буфери в собствен стек и изпълнява поредица от синхронни четения, трансформации и записи в цикъл. В същото време производителността на програмата остава на доста високо ниво.

Забележка

Програмата atouMT.c на уебсайта коментира няколко възможни „клопки“, които може да срещнете, когато позволявате на множество нишки да имат достъп до един и същ файл едновременно. По-специално, всички отделни манипулатори на файлове трябва да бъдат създадени с помощта на функцията CreateHandle, а не функцията DuplicateHandle.

Лично аз предпочитам да използвам многонишкова обработка на файлове, а не асинхронен I/O. Потоците са по-лесни за програмиране и осигуряват по-добра производителност в повечето случаи.

Има две изключения от това общо правило. Първата от тях, както беше показано по-рано в тази глава, се занимава със ситуации, в които може да има само една незавършена операция и файлов дескриптор може да се използва за целите на синхронизацията. Второто, по-важно изключение възниква в случай на асинхронни I/O портове за завършване, които ще бъдат обсъдени в края на тази глава.

Таймери за изчакване

Windows NT поддържа чакащи таймери, които са един тип обект на ядрото, който извършва изчакване.

Винаги можете да създадете свой собствен часовников сигнал, като създадете часовникова нишка, която задава събитието за събуждане след извикване на функцията Sleep. В програмата serverNP (програма 11.3), сървърът също използва часовниковия поток, за да излъчва периодично името на своя канал. Следователно таймерите за изчакване предоставят донякъде излишен, но удобен начин за организиране на задачи, които да се изпълняват периодично или според определен график. По-специално, таймерът за изчакване може да бъде конфигуриран по такъв начин, че сигналът да се генерира в строго определено време.

Таймерът за изчакване може да бъде или таймер за синхронизиране, или таймер за известяване с ръчно нулиране. Таймерът за синхронизиране е свързан с функция за непряко повикване, подобна на процедурата за разширено I/O завършване, докато функцията за изчакване се използва за синхронизиране при ръчно нулиран таймер за уведомяване.

Първо трябва да създадете манипулатор на таймера с помощта на функцията CreateWaitableTimer.

HANDLE CreateWaitableTimer(LPSECURITY_ATTRIBUTES lpTimerAttributes, BOOL bManualReset, LPCTSTR lpTimerName);

Вторият параметър, bManualReset, определя дали типът таймер, който ще бъде създаден, е синхронизиращ или уведомяващ. Програма 14.3 използва таймер за синхронизиране, но като промените коментарите и настройката на параметрите, можете лесно да я превърнете в таймер за известяване. Имайте предвид, че има и функция OpenWaitableTimer, която може да използва незадължителното име, предоставено от третия аргумент.

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

BOOL SetWaitableTimer(HANDLE hTimer, const LARGE_INTEGER *pDueTime, LONG IPeriod, PTIMERAPCROUTINE pfnCompletionRoutine, LPVOID lpArgToCompletionRoutine, BOOL fResume);

hTimer е валиден манипулатор на таймера, създаден с помощта на функцията CreateWaitableTimer.

Вторият параметър, посочен от указателя pDueTime, може да приема или положителни стойности, съответстващи на абсолютното време, или отрицателни стойности, съответстващи на относителното време, като действителните стойности са изразени във времеви единици от 100 наносекунди и техният формат е описан от FILETIME структура. Променливи от тип FILETIME бяха въведени в глава 3 и вече бяха използвани в глава 6 в програмата timep (програма 6.2).

Стойността на интервала между сигналите, посочен в третия параметър, се изразява в милисекунди. Ако тази стойност е зададена на 0, тогава таймерът се сигнализира само веднъж. Ако този параметър е положителен, таймерът е периодичен и се изпълнява периодично, докато действието му бъде прекратено чрез извикване на функцията CancelWaitableTimer. Не се допускат отрицателни стойности за посочения интервал.

Четвъртият параметър, pfnCompletionRoutine, се използва в случай на синхронизиращ таймер и указва адреса на рутинната програма за завършване, която се извиква, когато таймерът бъде сигнализиран. и при условиече нишката влиза в състояние на неактивно изчакване. Когато се извика тази процедура, указателят, определен от петия параметър, plArgToComplretionRoutine, се използва като един от аргументите.

Чрез задаване на таймер за синхронизиране можете да поставите нишката в състояние на заспиване, като извикате функцията SleepEx, за да сте сигурни, че процедурата за прекратяване може да бъде извикана. В случай на ръчно нулиране на таймера за известяване, трябва да изчакате манипулаторът на таймера да бъде сигнализиран. Манипулаторът ще остане в сигнализираното състояние до следващото извикване на функцията SetWaitableTimer. Пълната програма 14.3, намерена на уеб сайта, ви позволява да провеждате свои собствени експерименти, като използвате таймер по ваш избор, комбиниран с рутинна програма за прекратяване или изчакване на манипулатора на таймера да сигнализира, което води до четири различни комбинации.

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

Функцията CancelWaitableTimer се използва за отмяна на извиканата преди това функция SetWaitableTimer, но не променя сигнализираното състояние на таймера. За да направите това, трябва отново да извикате функцията SetWaitableTimer.

Пример: Използване на таймер за изчакване

Програма 14.3 демонстрира използването на таймер за заспиване за генериране на периодични сигнали.

Програма 14.3. TimeBeep: генериране на периодични сигнали
/* Глава 14. TimeBeep.p. Периодично звуково известяване. */
/* Използване: Период на TimeBeep (в милисекунди). */

статичен BOOL WINAPI Handler(DWORD CntrlEvent);
статичен звуков сигнал на VOID APIENTRY (LPVOID, DWORD, DWORD);
променлив статичен BOOL Изход = FALSE;

int _tmain(int argc, LPTSTR argv) (
/* Прихващане на натискания на клавиши за прекратяване на операцията. Вижте глава 4. */
SetConsoleCtrlHandler(Handler, TRUE);
DueTime.QuadPart = -(LONGLONG)Период * 10000;
/* Параметърът DueTime е отрицателен за първия период на изчакване и е относителен към текущото време. Периодът на изчакване се измерва в ms (10 -3 s), докато DueTime се измерва в единици от 100 ns (10 -7 s), за да бъде в съответствие с типа FILETIME. */
hTimer = CreateWaitableTimer(NULL, FALSE /* "Таймер за синхронизиране" */, NULL);
SetWaitableTimer(hTimer, &DueTime, Period, Beeper, &Count, TRUE);
_tprintf(_T("Брой = %d\n"), Брой);
/* Стойността на брояча се увеличава в процедурата на таймера. */
/* Влезте в състояние на готовност. */
_tprintf(_T("Завършване. Брояч = %d"), Брой);

статичен звуков сигнал на VOID APIENTRY(LPVOID lpCount, DWORD dwTimerLowValue, DWORD dwTimerHighValue) (
*(LPDWORD)lpCount = *(LPDWORD)lpCount + 1;
_tprintf(_T("Генериране на номер на сигнал: %d\n"), *(LPDWORD) lpCount);
Вентилатор (1000 /* Честота. */, 250 /* Продължителност (ms). */);

BOOL WINAPI Handler(DWORD CntrlEvent) (
_tprintf(_T("Изключване\n"));

Приложният програмист не трябва да мисли за неща като това как системните програми работят с регистрите на устройството. Системата скрива подробностите за работа на ниско ниво с устройства от приложения. Въпреки това, разграничението между запитване и прекъсване на I/O се отразява до известна степен на ниво системни функции, под формата на функции за синхронен и асинхронен I/O.

Изпълнение на функция синхронен I/O включва стартиране на I/O операция и изчакване тази операция да завърши. Едва след като I/O приключи, функцията връща контрола на извикващата програма.

Синхронният I/O е най-познатият начин за програмистите да работят с устройства. Стандартните процедури за вход/изход на езиците за програмиране работят по този начин.

Извикване на функция асинхронен I/O означава само началото на съответната операция. След това функцията незабавно връща управлението на извикващата програма, без да чака операцията да приключи.

Помислете например за асинхронно въвеждане на данни. Ясно е, че програмата не може да получи достъп до данни, докато не се увери, че въвеждането им е завършено. Но е напълно възможно програмата да върши друга работа за момента, а не да бездейства в очакване.

Рано или късно програмата все пак трябва да започне да работи с въведените данни, но първо се уверете, че асинхронната операция вече е приключила. За да направите това, различни операционни системи предоставят инструменти, които могат да бъдат разделени на три групи.

· Изчакване за завършване на операцията. Това е като "втората половина на синхронна операция". Програмата първо стартира операцията, след това извърши някои външни действия и сега чака края на операцията, както при синхронния вход / изход.

· Проверка на изпълнението на операцията. В този случай програмата не чака, а само проверява състоянието на асинхронната операция. Ако входът / изходът все още не е завършен, тогава програмата има възможност да се разходи още известно време.

· Назначаване на процедура по прекратяване. В този случай, когато стартира асинхронна операция, потребителската програма казва на системата адреса на дефинирана от потребителя процедура или функция, която трябва да бъде извикана от системата след приключване на операцията. Самата програма може вече да не се интересува от напредъка на I / O, системата ще й напомни за това в точното време, като извика определената функция. Този метод е най-гъвкавият, тъй като потребителят може да предостави всякакви действия в процедурата за завършване.

В Windows приложение са налични и трите метода за извършване на асинхронни операции. UNIX няма асинхронни I/O функции, но същият асинхронен ефект може да бъде постигнат по друг начин, чрез изпълнение на допълнителен процес.

Асинхронното I / O изпълнение позволява в някои случаи да се увеличи производителността и да се предоставят допълнителни функционалност. Без такава проста форма на асинхронно въвеждане като „въвеждане от клавиатурата без изчакване“, множество компютърни игри и симулатори не биха били възможни. В същото време логиката на програма, използваща асинхронни операции, е по-сложна, отколкото при синхронните операции.

И каква е връзката по-горе между синхронните/асинхронните операции и I/O методите, обсъдени в предишния параграф? Отговорете си сами на този въпрос.

I/O управление.

блоково ориентираниустройства и байт-ориентиран

Основна идея

ключпринципът е независимост на устройството

Обработка на прекъсвания,

драйвери на устройства,

Изглежда очевидно, че са възможни голямо разнообразие от прекъсвания поради голямо разнообразие от причини. Следователно едно число е свързано с прекъсване - така нареченото число на прекъсване.

Този номер уникално съответства на определено събитие. Системата е в състояние да разпознае прекъсванията и, когато възникнат, стартира процедурата, съответстваща на номера на прекъсването.

Някои прекъсвания (първите пет в числов ред) са запазени за използване от процесора в случай на някакви специални събития като опит за деление на нула, препълване и т.н. (това всъщност са J вътрешни прекъсвания).

Хардуерните прекъсвания винаги възникват асинхронно по отношение на изпълняваните програми. Освен това могат да възникнат няколко прекъсвания едновременно!

За да не се изгуби системата, когато решава кое прекъсване да обслужи първо, има специална приоритетна схема. На всяко прекъсване се присвоява собствен приоритет. Ако се появят няколко прекъсвания по едно и също време, системата приоритизира най-високия приоритет, като отлага останалите прекъсвания за известно време.

Приоритетната система е реализирана на два чипа Intel 8259 (или подобни). Всеки чип е контролер за прекъсване и обработва до осем приоритета. Чиповете могат да се комбинират (каскадно), за да се увеличи броят на нивата на приоритет в системата.

Нивата на приоритет се обозначават съкратено като IRQ0 - IRQ15.


24. Управление на вход / изход. Синхронен и асинхронен I/O.

Една от основните функции на ОС е да управлява всички входно-изходни устройства на компютъра. ОС трябва да изпраща команди към устройствата, да улавя прекъсвания и да обработва грешки; трябва също така да осигурява интерфейс между устройствата и останалата част от системата. За целите на разработката интерфейсът трябва да е еднакъв за всички типове устройства (независимост от устройството). Повече за I/O контролен въпрос 23.

Принципи на защита

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

Защита на файлове

Както е обичайно в многопотребителска операционна система, UNIX поддържа единен механизъм за контролиране на достъпа до файлове и директории на файловата система. Всеки процес може да има достъп до определен файл, ако и само ако правата за достъп, описани с файла, съответстват на възможностите на този процес.

Защитата на файловете от неоторизиран достъп в UNIX се основава на три факта. Първо, всеки процес, който създава файл (или директория), е свързан с някакъв системен уникален потребителски идентификатор (UID - Потребителски идентификатор), което в бъдеще може да се тълкува като идентификатор на собственика на новосъздадения файл. Второ, всеки процес, който се опитва да получи достъп до файл, има двойка идентификатори, свързани с него, идентификатори на текущия потребител и група. Трето, всеки файл отговаря уникално на своя дескриптор - i-node.

На последния факт си струва да се спрем по-подробно. Важно е да разберете, че имената на файловете и файловете като такива не са едно и също нещо. По-специално, когато има множество твърди връзки към един и същ файл, няколко имена на файлове всъщност представляват един и същ файл и са свързани с един и същи i-възел. Всеки i-node, използван във файловата система, винаги отговаря уникално на един и само един файл. I-node съдържа доста различна информация (по-голямата част от нея е достъпна за потребителите чрез системните извиквания stat и fstat) и сред тази информация има част, която позволява на файловата система да оцени правата за достъп на даден процес към даден файл в необходимия режим.

Основни принципизащитите са еднакви за всички съществуващи варианти на системата: Информацията за i-node включва UID и GID на текущия собственик на файла (веднага след създаването на файла, идентификаторите на текущия му собственик се задават на съответния активен идентификатор на процеса на създаване, но по-късно може да бъде променен от системните извиквания на chown и chgrp). Освен това в i-възела на файла се съхранява скала, която показва какво потребителят - неговият собственик може да прави с файла, какво потребителите, принадлежащи към същата потребителска група като собственика, могат да правят с файла и какво другите могат да правят с файловете потребители. Малки детайли на изпълнение в различните версии на системата се различават.

28. Контрол на достъпа до файлове в Windows NT. Списъци с права за достъп.

Системата за контрол на достъпа в Windows NT е различна висока степенгъвкавост, която се постига благодарение на голямото разнообразие от субекти и обекти за достъп, както и детайлността на операциите за достъп.

Контрол на достъпа до файлове

За споделени ресурси Windows NT използва общ обектен модел, който съдържа такива характеристики за сигурност като набор от разрешени операции, идентификатор на собственика и списък за контрол на достъпа.

Обектите в Windows NT се създават за всеки ресурс, в случай че те са или станат споделени - файлове, директории, устройства, секции на паметта, процеси. Характеристиките на обектите в Windows NT са разделени на две части - обща част, чийто състав не зависи от вида на обекта, и индивидуална част, определена от типа на обекта.
Всички обекти се съхраняват в дървовидни йерархични структури, чиито елементи са клонови обекти (директории) и листови обекти (файлове). За обектите на файловата система тази схема на връзка е пряко отражение на йерархията на директориите и файловете. За обекти от други типове схемата за йерархична връзка има собствено съдържание, например за процеси тя отразява отношенията родител-дете, а за устройствата отразява принадлежността към определен тип устройство и връзката на устройството с други устройства, например SCSI контролер с дискове.

Проверката на правата за достъп за обекти от всякакъв тип се извършва централно с помощта на Security Reference Monitor, който работи в привилегирован режим.

Системата за сигурност на Windows NT се характеризира с наличието на голям брой различни предварително дефинирани (вградени) субекти за достъп - както отделни потребители, така и групи. Така че в системата винаги има потребители като Adininistrator, System и Guest, както и групи Users, Adiniiistrators, Account Operators, Server Operators, Everyone и други. Смисълът на тези вградени потребители и групи е, че им се дават някои права, което улеснява създаването на администратора ефективна системаконтрол на достъпа. Когато добавя нов потребител, администраторът трябва само да реши към коя група или групи да присвои този потребител. Разбира се, администраторът може да създава нови групи, както и да добавя права към вградени групи, за да прилага собствена политика за сигурност, но в много случаи вградените групи са достатъчни.

Windows NT поддържа три класа операции за достъп, които се различават по вида на субектите и обектите, включени в тези операции.

□ Разрешенията са набор от операции, които могат да бъдат дефинирани за субекти от всякакъв тип по отношение на обекти от всякакъв тип: файлове, директории, принтери, секции на паметта и т.н. Разрешенията по своята цел съответстват на правата за достъп до файлове и директории в QC UNIX.

□ Права (потребителски права) - определят се за субекти от групов тип за извършване на някои системни операции: настройка на системното време, архивиране на файлове, изключване на компютъра и др. Тези операции включват специален обект за достъп - операционната система като цяло .

Основно правата, а не разрешенията, отличават една вградена потребителска група от друга. Някои права на вградена група също са вградени - те не могат да бъдат премахнати от тази група. Останалите права на вградената група могат да бъдат премахнати (или добавени от общия списък с права).

□ Потребителските способности са определени за отделните потребители да извършват действия, свързани с формирането на тяхната работна среда, например промяна на състава на главното меню на програмите, възможността за използване на елемента от менюто Run и т.н. Чрез намаляване на набора от възможности (които са достъпни за потребителя по подразбиране), администраторът може да принуди потребителя да работи с операционната среда, която администраторът счита за най-подходяща и предпазва потребителя от възможни грешки.

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

Когато потребител влезе в системата, за него се създава така наречения токен за достъп, който включва потребителския идентификатор и идентификаторите на всички групи, към които принадлежи потребителят. Токенът също така съдържа: списък за контрол на достъпа по подразбиране (ACL), който се състои от разрешения и се прилага към обекти, създадени от процеса; списък с потребителски права за извършване на системни действия.

Всички обекти, включително файлове, потоци, събития, дори маркери за достъп, са снабдени с дескриптор за сигурност, когато са създадени. Дескрипторът за защита съдържа списък за контрол на достъпа - ACL.

Файлов дескрипторе неотрицателно цяло число, присвоено от ОС на файла, отворен от процеса.

ACL(Английски) Списък за контрол на достъпа- списък за контрол на достъпа, на английски се произнася "ekl") - определя кой или какво има достъп до определен обект и какви операции са разрешени или забранени за този субект да извършва върху обекта.

Списъците за контрол на достъп са гръбнакът на системите за селективен контрол на достъпа. ( Wiki)

Собственикът на обект, обикновено потребителят, който го е създал, има правото избирателно да контролира достъпа до обекта и може да промени ACL на обекта, за да разреши или забрани на други хора достъп до обекта. Вграденият администратор на Windows NT, за разлика от суперпотребителя на UNIX, може да няма някои права за достъп до обект. За да се приложи тази функция, идентификаторите на администратори и идентификаторите на администраторските групи могат да бъдат включени в ACL, точно както обикновените потребителски идентификатори. Въпреки това, администраторът все още има възможност да извършва всякакви операции с всякакви обекти, тъй като той винаги може да стане собственик на обекта и след това, като собственик, да получи пълен комплектразрешения. Въпреки това, възвърнете притежанието предишен собственикобект, администраторът не може, така че потребителят винаги може да разбере, че администраторът е работил с неговия файл или принтер.

Когато даден процес поиска операция за достъп до обект в Windows NT, контролът винаги се прехвърля към монитора за сигурност, който сравнява идентификаторите на потребителя и потребителската група от маркера за достъп с идентификаторите, съхранени в ACL елементите на обекта. За разлика от UNIX, ACL елементите на Windows NT могат да съдържат както списъци с разрешени операции, така и списъци със забранени операции за потребителя.

Windows NT изрично дефинира правилата, по които на новосъздадения обект се присвоява ACL. Ако извикващият код по време на създаването на обект изрично задава всички права за достъп до новосъздадения обект, тогава системата за сигурност присвоява този ACL на обекта.

Ако извикващият код не предоставя на обекта ACL, но обектът има име, тогава се прилага принципът на наследяване на разрешения. Системата за сигурност разглежда ACL на обектната директория, където се съхранява името на новия обект. Някои от ACL записите в обектната директория могат да бъдат маркирани като наследени. Това означава, че те могат да бъдат присвоени на нови обекти, създадени в тази директория.

В случай, че процесът не е задал изрично ACL за създавания обект и обектът на директорията няма наследени ACL елементи, се използва ACL по подразбиране от токена за достъп на процеса.


29. Език за програмиране Java. Java виртуална машина. Java технология.

Javaе обектно-ориентиран език за програмиране, разработен от Sun Microsystems. Java приложенията обикновено се компилират в специален байт код, така че могат да работят на всяка Java виртуална машина (JVM), независимо от компютърната архитектура. Програмите на Java се превеждат в байт код, който се изпълнява от виртуалната машина на Java ( JVM) - програма, която обработва байт код и предава инструкции към оборудването като интерпретатор, но с тази разлика, че байт кодът, за разлика от текста, се обработва много по-бързо.

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

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

Java виртуална машина(съкратено Java VM, JVM) - виртуалната машина на Java - основната част от системата за изпълнение на Java, т. нар. Java Runtime Environment (JRE). Виртуалната машина на Java интерпретира и изпълнява байт код на Java, генериран преди това от изходния код на програмата на Java от компилатора на Java (javac). JVM може да се използва и за изпълнение на програми, написани на други езици за програмиране. Например изходният код на Ada може да бъде компилиран в Java байт код, който след това може да бъде изпълнен от JVM.

JVM е ключов компонент на платформата Java. Тъй като Java виртуалните машини са достъпни за много хардуерни и софтуерни платформи, Java може да се разглежда както като междинен софтуер, така и като самостоятелна платформа, оттук и принципът „пиши веднъж, изпълнявай навсякъде“. Използването на един байт код за много платформи позволява Java да бъде описана като "компилирай веднъж, стартирай навсякъде" (компилирай веднъж, стартирай навсякъде).

Време за изпълнение

Програмите, предназначени да работят на JVM, трябва да бъдат компилирани в стандартизиран преносим компютър двоичен формат, който обикновено се представя като .class файлове. Една програма може да се състои от много класове, поставени в различни файлове. За да се улесни хостването на големи програми, някои от .class файловете могат да бъдат пакетирани заедно в така наречения .jar файл (съкратено от Java Archive).

JVM изпълнява .class или .jar файлове, като емулира инструкции, написани за JVM чрез интерпретиране или използване на компилатор точно навреме (JIT), като HotSpot от Sun microsystems. Тези дни JIT компилацията се използва в повечето JVM, за да се постигне по-голяма скорост.

Подобно на повечето виртуални машини, Java Virtual Machine има ориентирана към стека архитектура, която е обща за микроконтролерите и микропроцесорите.

JVM, който е екземпляр на JRE (Java Runtime Environment), влиза в действие при изпълнение на Java програми. След завършване на изпълнението този екземпляр се премахва от събирача на отпадъци. JIT е част от Java Virtual Machine, която се използва за ускоряване на времето за изпълнение на приложенията. JIT едновременно компилира части от байт кода, които имат сходна функционалност и следователно намалява времето, необходимо за компилиране.

j2se (стандартно издание на Java 2) - Стандартната библиотека включва:

GUI, NET, база данни…


30. .NET платформа. Основни идеи и положения. Езици за програмиране .NET.

.NET Framework- софтуерна технология от Microsoft, предназначена за създаване редовни програмии уеб приложения.

Една от основните идеи на Microsoft .NET е оперативната съвместимост различни услугинаписани на различни езици. Например услуга, написана на C++ за Microsoft .NET, може да има достъп до метод на клас от библиотека, написана на Delphi; в C# можете да напишете клас, който наследява клас, написан в Visual Basic.NET и изключение, хвърлено от метод, написан на C#, може да бъде уловено и обработено в Delphi. Всяка библиотека (сборка) в .NET има информация за нейната версия, което ви позволява да елиминирате възможни конфликти между различни версиивъзли.

Приложенията могат също да бъдат разработени в текстов редактор и да използват конзолния компилатор.

Подобно на технологията Java, средата за разработка на .NET създава байт код, който да бъде изпълнен от виртуална машина. Входният език на тази машина в .NET се нарича MSIL (Microsoft Intermediate Language), или CIL (Common Intermediate Language, по-късно), или просто IL.

Използването на байт код ви позволява да получите крос-платформа на ниво компилиран проект (в термините на .NET: монтаж), а не само на ниво източник, както например в C. Преди да започне асемблирането в CLR runtime, байткодът се преобразува от JIT компилатора, вграден в средата (точно навреме, компилация в движение) в машинни кодове на целевия процесор. Също така е възможно да се компилира сборка в собствен код за избраната платформа с помощта на помощната програма NGen.exe, доставена с .NET Framework.

По време на процедурата за превод изходният код на програмата (написан на SML, C #, Visual Basic, C ++ или всеки друг език за програмиране, който се поддържа от .NET) се преобразува от компилатора в така нареченото асемблиране ( асемблиране) и записан като файл с динамично свързана библиотека (Dynamically Linked Library, DLL) или изпълним файл (Executable, EXE).

Естествено, за всеки компилатор (независимо дали е C# компилатор, csc.exe или Visual Basic, vbc.exe), средата за изпълнение извършва необходимото картографиране на използваните типове в CTS типове и програмния код в кода на " абстрактна машина" .NET - MSIL (междинен език на Microsoft).

В резултат на това софтуерният проект се формира като сборка - самодостатъчен компонент за внедряване, репликация и повторно използване. Сглобката се идентифицира с цифровия подпис на автора и уникален номерверсии.

Вградени езици за програмиране (доставени с .NET Framework):

° С#; J#; VB.NET JScript .NET C++/CLI- нова версия C++ (управляван).


31. Функционални компоненти на ОС. Управление на файлове

Функционални компоненти на ОС:

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

Управление на файлове:

Способността на ОС да "екранира" сложността на реалния хардуер се проявява много ясно в една от основните подсистеми на ОС - файловата система.

Файловата система свързва носител за съхранение от една страна и API (интерфейс за програмиране на приложения) за достъп до файлове от друга. Когато приложна програма има достъп до файл, тя няма представа как се намира информацията в даден файл, както и на какъв физически тип носител (CD, твърд диск, магнитна лента или флаш памет) е записана. Всичко, което програмата знае, е името на файла, неговия размер и атрибути. Той получава тези данни от драйвера на файловата система. Файловата система е тази, която определя къде и как ще бъде записан файлът на физически носител (например твърд диск).

От гледна точка на операционната система, целият диск е набор от клъстери от 512 байта и повече. Драйверите на файловата система организират клъстери във файлове и директории (които всъщност са файлове, съдържащи списък от файлове в тази директория). Същите драйвери следят кои клъстери се използват в момента, кои са свободни и кои са маркирани като неуспешни.

Файловата система обаче не е непременно директно свързана с физическия носител за съхранение. Има виртуални файлови системи, както и мрежови файлови системи, които са просто начин за достъп до файлове, които са на отдалечен компютър.

В най-простия случай всички файлове на този дисксе съхраняват в същата директория. Тази схема на едно ниво беше използвана в CP/M и в първата версия на MS-DOS 1.0. Йерархичната файлова система с вложени директории се появява за първи път в Multics, след това в UNIX.

Директориите на различни устройства могат да формират няколко отделни дървета, както в DOS/Windows, или могат да бъдат комбинирани в едно дърво, общо за всички устройства, както в UNIX-подобни системи.

Всъщност в системите DOS / Windows, както и в подобните на UNIX системи, има една главна директория с вложени директории, наречени „c:“, „d:“ и т.н. Дяловете на твърдия диск се монтират в тези директории. Тоест c:\ е просто връзка към file:///c:/. Въпреки това, за разлика от UNIX-подобните файлови системи, Windows забранява писането в главната директория, както и преглеждането на нейното съдържание.

В UNIX има само една основна директория и всички останали файлове и директории са вложени в нея. За да получите достъп до файлове и директории на устройство, трябва да монтирате това устройство с командата mount. Например, за да отворите файлове на компактдиск, с прости думи, трябва да кажете на операционната система: "вземете файловата система на този компактдиск и я покажете в директорията /mnt/cdrom." Всички файлове и директории на компактдиска ще се появят в тази директория /mnt/cdrom, която се нарича точка на монтиране. В повечето UNIX-подобни системи сменяемите дискове (дискети и компактдискове), флаш памети и други външни устройства за съхранение се монтират в директорията /mnt, /mount или /media. Unix и UNIX-подобните операционни системи също ви позволяват автоматично да монтирате дискове, когато операционната система се зарежда.

Забележете използването на наклонени черти файлови системи Windows, UNIX и UNIX-подобни операционни системи (Windows използва обратна наклонена черта „\“, а UNIX и UNIX-подобни операционни системи – проста наклонена черта „/“)

Освен това трябва да се отбележи, че горната система ви позволява да монтирате не само файловите системи на физическите устройства, но и отделни директории (параметърът --bind) или, например, iso изображение(опция за цикъл). Добавки като FUSE също ви позволяват да монтирате, например, цяла директория на FTP и много голям брой различни ресурси.

Още по-сложна структура се използва в NTFS и HFS. В тези файлови системи всеки файл е набор от атрибути. Атрибутите се считат не само за традиционните само за четене, системата, но също така и името на файла, размера и дори съдържанието. По този начин, за NTFS и HFS, това, което се съхранява във файл, е само един от неговите атрибути.

Следвайки тази логика, един файл може да съдържа множество варианти на съдържание. Така в един файл могат да се съхраняват множество версии на един и същи документ, както и допълнителни данни (икона на файл, програма, свързана с файла). Тази организация е типична за HFS на Macintosh.


32. Функционални компоненти на ОС. Управление на процеси.

Управление на процесите:

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

В многозадачна (мултипроцесна) система един процес може да бъде в едно от три основни състояния:

ИЗПЪЛНЕНИЕ - активното състояние на процеса, по време на което процесът разполага с всички необходими ресурси и се изпълнява директно от процесора;

ИЗЧАКВАНЕ - пасивно състояние на процеса, процесът е блокиран, не може да бъде изпълнен поради вътрешни причини, чака да се случи някакво събитие, например завършване на I / O операция, получаване на съобщение от друг процес , освобождавайки някакъв ресурс, от който се нуждае;

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

По време на жизнения цикъл всеки процес преминава от едно състояние в друго в съответствие с алгоритъма за планиране на процеса, внедрен в тази операционна система.

CP/M стандарт

Началото на създаването на операционни системи за микрокомпютри беше положено от OS SR / M. Разработен е през 1974 г., след което е инсталиран на много 8-битови машини. В рамките на тази операционна система е създадено значително количество софтуер, включително преводачи от BASIC, Pascal, C, Fortran, Cobol, Lisp, Ada и много други, текст. Те ви позволяват да подготвяте документи много по-бързо и по-удобно, отколкото с пишеща машина.

MSX стандарт

Този стандарт определя не само операционната система, но и характеристиките на хардуера за училищни компютри. Според стандарта MSX машината трябваше да има RAMкапацитет минимум 16 K, памет само за четене 32 K с вграден езиков интерпретатор BASIC, цветен графичен дисплей с резолюция 256x192 пиксела и 16 цвята, триканален звуков генераторза 8 октави, паралелен порт за свързване на принтер и контролер за управление на външен диск, свързан външно.

Операционната система на такава машина трябва да има следните свойства: необходима памет - не повече от 16 K, съвместимост с CP / M на ниво системни повиквания, съвместимост с DOS по отношение на файловите формати на външни устройства, базирани на флопи магнитни дискове , поддръжка за транслатори на BASIC, C, Fortran и Lisp.

Пи - система

В началния период от развитието на персоналните компютри е създадена операционната система USCD p-system. Основата на тази система беше така наречената P-машина - програма, която емулира хипотетичен универсален компютър. P-машината симулира работата на процесора, паметта и външните устройства чрез изпълнение на специални инструкции, наречени P-код. Софтуерни компоненти Pi-системите (включително компилаторите) се компилират в P-код, приложните програми също се компилират в P-код. Така основната отличителна черта на системата беше минималната зависимост от характеристиките на хардуера на компютъра. Това е, което гарантира преносимостта на Pi-системата към различни видовемашини. Компактността на P-кода и удобно реализираният механизъм за страниране направиха възможно изпълнението на относително големи програми на компютър с малка RAM.

I/O управление.

I/O устройствата са разделени на два типа: блоково ориентираниустройства и байт-ориентиранустройства. Блоково ориентираните устройства съхраняват информация в блокове с фиксиран размер, всеки със собствен адрес. Най-разпространеното блоково ориентирано устройство е дискът. Байт-ориентираните устройства не са адресируеми и не позволяват операция за търсене, те генерират или консумират поредица от байтове. Примери за това са терминали, линейни принтери, мрежови адаптери. Електронният компонент се нарича контролер на устройството или адаптер. Операционната система се занимава с контролера. Контролерът изпълнява прости функции, контролира и коригира грешки. Всеки контролер има няколко регистъра, които се използват за взаимодействие с централния процесор. ОС извършва I/O чрез записване на команди в регистрите на контролера. Контролерът за дискети на IBM PC приема 15 команди като READ, WRITE, SEEK, FORMAT и т.н. Когато командата бъде получена, процесорът напуска контролера и върши друга работа. Когато командата приключи, контролерът организира прекъсване, за да прехвърли управлението на процесора към операционната система, която трябва да провери резултатите от операцията. Процесорът получава резултатите и състоянието на устройството чрез четене на информация от регистрите на контролера.

Основна идея I/O софтуерна организациясе състои в разделянето му на няколко нива, като долните нива осигуряват скрининг на хардуерните характеристики от горните, а тези осигуряват удобен за потребителя интерфейсза потребители.

ключпринципът е независимост на устройството. Формата на програмата не трябва да зависи от това дали чете данни от дискета или от твърд диск. Друг важен въпрос за I/O софтуера е обработката на грешки. Най-общо казано, грешките трябва да се обработват възможно най-близо до хардуера. Ако контролерът открие грешка при четене, той трябва да се опита да я коригира. Ако не успее, тогава драйверът на устройството трябва да коригира грешките. И само ако по-ниското ниво не може да се справи с грешката, то докладва грешката на по-горното ниво.

Друг ключов проблем е използването на блокиращи (синхронни) и неблокиращи (асинхронни) трансфери. Повечето физически входно-изходни операции са асинхронни - процесорът започва прехвърляне и преминава към друга задача, докато не настъпи прекъсване. I/O операциите трябва да са блокиращи - след командата READ, програмата автоматично се спира, докато данните влязат в програмния буфер.

Последният проблем е, че някои устройства са споделени (дискове: едновременният достъп на няколко потребители до диска не е проблем), докато други са предназначени (принтери: не можете да смесвате редове, отпечатани от различни потребители).

За да разрешите поставените проблеми, препоръчително е да разделите I / O софтуера на четири слоя (Фигура 2.30):

Обработка на прекъсвания,

драйвери на устройства,

Независим от устройството слой на операционната система,

· Персонализиран софтуерен слой.

Концепцията за хардуерно прекъсване и неговата обработка.

Асинхронни или външни (хардуерни) прекъсвания - събития, които идват от външни източници (напр. периферни устройства) и може да възникне във всеки произволен момент: сигнал от таймер, мрежова картаили дисково устройство, натискане на клавиши на клавиатурата, движение на мишката; Те изискват незабавен отговор (обработка).

Почти всички I/O системи в компютъра работят с помощта на прекъсвания. По-специално, когато натискате клавиши или щраквате с мишката, хардуерът генерира прекъсвания. В отговор на тях системата съответно чете кода на натиснатия клавиш или запомня координатите на курсора на мишката. Прекъсванията се генерират от дисковия контролер, адаптера локална мрежа, серийни портове, аудио адаптер и други устройства.