Internet Windows Android

Terms: Data input-output synchronous and asynchronous. Synchronous and asynchronous I/O Asynchronous I/O

Asynchronous I/O using multiple threads

Overlapping and extended I/O allow for asynchronous I/O within a single thread, although the OS creates its own threads to support this functionality. In one form or another, methods of this type are often used in many early operating systems to support limited forms of performing asynchronous operations in single-threaded systems.

However, Windows provides multi-threading support, so it is possible to achieve the same effect by performing synchronous I/O operations on multiple independently executing threads. These capabilities have already been demonstrated previously with multithreaded servers and the grepMT program (Chapter 7). In addition, threads provide a conceptually sequential and purportedly much simpler way to perform asynchronous I/O. As an alternative to the methods used in Programs 14.1 and 14.2, one could give each thread its own file descriptor, and then each thread could process every fourth record synchronously.

This way of using streams is demonstrated in the atouMT program, which is not included in the book but is included in the material posted on the Web site. Not only is atouMT able to run on any version of Windows, but it's also simpler than either of the two asynchronous I/O programs because accounting for resource usage is less complicated. Each thread simply maintains its own buffers on its own stack and performs a series of synchronous reads, transforms, and writes in a loop. At the same time, the performance of the program remains at a fairly high level.

Note

The atouMT.c program on the Web site comments on several possible "pitfalls" you might encounter when allowing multiple threads to access the same file at the same time. In particular, all individual file handles must be created using the CreateHandle function, not the DuplicateHandle function.

Personally, I prefer to use multi-threaded file processing rather than asynchronous I/O. Streams are easier to program and provide better performance in most cases.

There are two exceptions to this general rule. The first of these, as shown earlier in this chapter, deals with situations in which there can be only one outstanding operation, and a file descriptor can be used for synchronization purposes. The second, more important exception occurs in the case of asynchronous I/O completion ports, which will be discussed at the end of this chapter.

From the book Let's Build a Compiler! by Crenshaw Jack

From the book Prolog Programming by Kloxin W.

From the book The C# 2005 Programming Language and the .NET 2.0 Platform. author Troelsen Andrew

From the Informix Database Administrator's Guide. the author Kustov Victor

From the book Microsoft Visual C++ and MFC. Programming for Windows 95 and Windows NT author Frolov Alexander Vyacheslavovich

2.2.3.2 Asynchronous I/O To speed up I/O operations, the server uses its own Asynchronous I/O (AIO) package or the Kernel Asynchronous I/O (KAIO) package, if available. User I/O requests are processed asynchronously,

From the book Fundamentals of Object-Oriented Programming by Meyer Bertrand

I/O As you know, operators<< и >> shift the numeric value left and right by a specified number of bits. The programs in this book also use these operators for keyboard input and screen output. If on the left side

From the book System Programming in the Windows Environment author Hart Johnson M

Input and Output Two classes in the KERNEL library provide the basic means of input and output: FILE and STD_FILES. Among the operations defined on a FILE object f are: create f.make ("name") -- Associates f with the file named name. f.open_write -- Open f for writing f.open_read -- Open f for

From the book Programming in the Ruby Language [Language Ideology, Theory and Practice of Application] author Fulton Hal

CHAPTER 14 Asynchronous I/O and Completion Ports I/O operations are inherently slower than other types of processing. The following factors are responsible for this slowdown: Delays due to time spent searching

From the book Programming in Prolog for Artificial Intelligence author Bratko Ivan

10.1.7. Simple I/O You are already familiar with some of the I/O methods from the Kernel module; we called them without specifying a caller. These include the gets and puts functions, as well as print, printf, and p (the latter calls the object's inspect method to print it in a way that we can understand.

From the book The C Programming Language for a Personal Computer author Bochkov S. O.

From the book Linux Programming by Example author Robbins Arnold

Chapter 6 Input and Output In this chapter, we'll look at some built-in facilities for writing data to and reading data from a file. Such facilities can also be used to format program data objects in order to obtain the desired form of their external representation.

From the book Fundamentals of Java Programming author Sukhov S. A.

Input and output The input and output functions in the C standard library allow you to read data from files or receive data from input devices (such as the keyboard) and write data to files or output it to various devices (such as a printer). output

From the book QT 4: GUI Programming in C++ by Blanchette Jasmine

4.4. I/O All Linux I/O operations are done through file descriptors. This section introduces file descriptors, describes how to acquire and release them, and explains how to perform

From the book Ideal Programmer. How to Become a Software Development Professional author Martin Robert S.

From the author's book

Chapter 12 I/O Almost every application needs to read or write files or perform other I/O operations. Qt provides excellent I/O support with QIODevice, a powerful abstraction of "devices" capable of reading and writing

From the author's book

Input and output It also seems to me very important that my results are fed by the appropriate "input". Writing code is a creative job. Usually, my creativity is at its greatest when I am confronted with a creative

Input and output operations are inherently slower than other types of processing. This slowdown is due to the following factors:

Delays due to the time spent searching for the desired tracks and sectors on random access devices (discs, CDs).

Latency due to relatively slow data transfer rates between physical devices and system memory.

Delays in data transmission over the network using file, servers, data storages, and so on.

In all the previous examples, I/O operations are performed in sync with the flow so the entire thread is forced to idle until they complete.

This chapter shows you how you can arrange for a thread to continue executing without waiting for I/O to complete, which is the same as threading. asynchronous input/output. Various techniques available in Windows are illustrated with examples.

Some of these techniques are used in the wait timers, which are also described in this chapter.

Finally, and most importantly, by learning about standard asynchronous I/O, we can use I/O Completion Ports, which are extremely useful when building scalable servers that can support a large number of clients without creating a separate thread for each of them. Program 14.4 is a modified version of the previously developed server that allows the use of I/O completion ports.

An Overview of Windows Asynchronous I/O Methods

On Windows, asynchronous I/O is handled in three ways.

Multithreaded input/output (Multihreaded I/O). Each of the threads within a process or set of processes performs normal synchronous I/O, while other threads can continue to execute.

Overlapped I/O. Having started a read, write, or other I/O operation, the thread continues its execution. If a thread needs I/O results to continue executing, it waits until the appropriate handle becomes available or the specified event occurs. In Windows 9x, overlapped I/O is only supported for serial devices, such as named pipes.

Completion routines (extended I/O) When I/O operations are completed, the system calls a special completion procedure, running inside a thread. Extended I/O for disk files is not supported on Windows 9x.

Multithreaded I/O using named pipes is implemented in the multithreaded server discussed in Chapter 11. The grepMT program (Program 7.1) manages parallel I/O operations involving multiple files. Thus, we already have a number of programs that perform multi-threaded I/O and thus provide a form of asynchronous I/O.

Overlapped I/O is the subject of the next section, and the file conversion examples (from ASCII to UNICODE) in that section use this technique to illustrate the capabilities of sequential file processing. For this purpose, a modified version of program 2.4 is used. Following overlapped I/O, extended I/O using completion routines is considered.

Note

The overlapped and extended I/O methods are often difficult to implement, rarely provide any performance benefits, sometimes even cause performance degradation, and in the case of file I/O, can only work under Windows NT. These problems are overcome with the help of threads, so many readers will probably want to skip ahead to the sections on wait timers and I/O completion ports, returning to this section as needed. On the other hand, elements of asynchronous I/O are present in both legacy and new technologies, and therefore these methods are still worth exploring.

For example, COM technology on the NT5 platform supports asynchronous method invocation, so this technique may be useful to many readers who use or intend to use COM technology. Also, asynchronous procedure call operations (Chapter 10) have a lot in common with extended I/O, and although I personally prefer to use threads, others may prefer this mechanism.

Overlapped I/O

The first thing to do to organize asynchronous I/O, whether overlapped or extended, is to set the overlapped attribute on a file or other descriptor. To do this, when calling CreateFile or another function that results in the creation of a file, named pipe, or other handle, the FILE_FLAG_OVERLAPPED flag must be specified.

In the case of sockets (Chapter 12), whether created using socket or accept, the override attribute is set by default in Winsock 1.1, but must be set explicitly in Winsock 2.0. Overlapped sockets can be used asynchronously on all versions of Windows.

Up to this point, OVERLAPPED structures have been used in conjunction with the LockFileEx function and as an alternative to using the SetFilePointer function (Chapter 3), but they are also an essential element of overlapped I/O. These structures act as optional parameters when calling the four functions below, which can block when operations complete.

Recall that when you specify the FILE_FLAG_OVERLAPPED flag as part of the dwAttrsAndFlags parameter (in the case of the CreateFile function) or the dwOpen-Mode parameter (in the case of the CreateNamedPipe function), the corresponding file or pipe can only be used in overlapped mode. With anonymous pipes, overlapped I/O does not work.

Note

The documentation for the CreateFile function mentions that using the FILE_FLAG_NO_BUFFERING flag improves the performance of overlapped I/O. Experiments show only a marginal performance improvement (about 15%, which can be verified by experimenting with program 14.1), but you should make sure that the total size of the data being read during a ReadFile or WriteFile operation is a multiple of the disk sector size.

Overlapping sockets

One of the most important innovations in Windows Sockets 2.0 (Chapter 12) is the standardization of overlapped I/O. In particular, sockets are no longer automatically created as overlapping file descriptors. The socket function creates a non-overlapping handle. To create an overlapped socket, you must call the WSASocket function, explicitly requesting the creation of an overlapped socket by specifying the WSA_FLAG_OVERLAPPED value for the dwFlags parameter of the WSASocket function.

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

To create a socket, use the WSASocket function instead of the socket function. Any socket returned by the accept function will have the same properties as the argument.

Consequences of using overlapped I/O

Overlapped I/O is done asynchronously. This has several implications.

Overlapped I/O operations are not blocked. The ReadFile, WriteFile, TransactNamedPipe, and ConnectNamedPipe functions return without waiting for the I/O operation to complete.

The return value of the function cannot be used as a criterion for the success or failure of its execution, since the I / O operation has not yet had time to complete by this moment. Indicating the state of I/O in progress requires the use of another mechanism.

The returned value for the number of bytes transferred is also of little use, since the data transfer might not have completed completely. To obtain this kind of information, Windows must provide another mechanism.

A program may repeatedly attempt to read or write using the same overlapping file descriptor. Therefore, the file pointer corresponding to such a descriptor also turns out to be insignificant. Therefore, an additional method must be provided to provide a position in the file for each read or write operation. In the case of named pipes, due to their inherent sequential nature of data processing, this is not a problem.

The program must be able to wait (synchronize) for I/O completion. If there are multiple pending I/O operations associated with the same handle, the program must be able to determine which of the operations have already completed. I/O operations do not necessarily complete in the same order in which they started executing.

To overcome the last two of the difficulties listed above, OVERLAPPED structures are used.

OVERLAPPED Structures

Using the OVERLAPPED structure (specified, for example, by the lpOverlapped parameter of the ReadFile function), you can specify the following information:

The position in the file (64 bits) at which the read or write operation should begin, as discussed in Chapter 3.

An event (manually cleared) that will become signaled when the associated operation is complete.

The following is the definition of the OVERLAPPED structure.

Both the Offset and OffsetHigh fields should be used to specify a file position (pointer), although the high part of the pointer (OffsetHigh) is 0 in many cases. The Internal and InternalHigh fields, which are reserved for system use, should not be used.

The hEvent parameter is a handle to the event (created with the CreateEvent function). This event can be either named or unnamed, but it must must be manually resettable (see chapter 8) if used for overlapped I/O; the reasons for this will be explained shortly. When the I/O operation completes, the event goes into the signaled state.

In another possible use case, the hEvent handle is NULL; in this case, the program can wait for a file descriptor to become signaled, which can also act as a synchronization object (see the caveats below). The system uses the signal states of the file descriptor to track the completion of operations if the hEvent descriptor is NULL, that is, the file descriptor is the synchronization object in this case.

Note

For the sake of convenience, the term "file handle" used in relation to the handles specified when calling the functions ReadFile, WriteFile, and so on, will be used by us even when we are talking about named pipe or device handles, not a file.

When an I/O function call is made, this event is immediately cleared by the system (set to a non-signaled state). When an I/O operation completes, the event is set to the signaled state and remains there until it is used by another I/O operation. An event must be manually resettable if multiple threads can be waiting for it to become signaled (although only one thread is used in our examples), and they need not be waiting when the operation completes.

Even if the file descriptor is synchronous (that is, created without the FILE_FLAG_OVERLAPPED flag), the OVERLAPPED structure can serve as an alternative to the SetFilePointer function for specifying a file position. In this case, no return from a ReadFile or other call occurs until the I/O operation has completed. We already used this feature in Chapter 3. Also note that pending I/O operations are uniquely identified by the combination of a file descriptor and the corresponding OVERLAPPED structure.

Listed below are some cautions that should be taken into account.

Avoid reusing an OVERLAPPED structure while its associated I/O operation, if any, has yet to complete.

Likewise, avoid reusing the event specified in the OVERLAPPED structure.

If there are multiple pending requests that refer to the same overlapping handle, use event handles instead of file handles for synchronization.

If an OVERLAPPED structure or an event is acting as an automatic variable within a block, make sure that the block cannot exit until synchronized with the I/O operation. Also, to avoid leaking resources, care must be taken to close the handle before exiting the block.

Overlapped I/O states

The ReadFile and WriteFile functions, as well as the above two named pipe functions, return immediately when they are used to perform overlapping I/O operations. In most cases, the I/O operation will not have completed by this point, and the read/write return value will be FALSE. The GetLastError function will return ERROR_IO_PENDING in this situation.

After waiting for the sync object (an event or perhaps a file descriptor) to signal the completion of the operation, you must find out how many bytes were transferred. This is the main purpose of the GetOverlappedResult function.

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

The indication of a particular I/O operation is provided by the combination of the handle and the OVERLAPPED structure. The bWait parameter value TRUE indicates that the GetOverlappedResult function should wait until the operation completes; otherwise, the return from the function must be immediate. In either case, this function will only return TRUE after the operation has successfully completed. If the return value of the GetOverlappedResult function is FALSE, then the GetLastError function will return ERROR_IO_INCOMPLETE, which allows this function to be called to poll for I/O completion.

The number of bytes transferred is stored in the *lpcbTransfer variable. Always make sure that the OVERLAPPED structure remains unchanged from the moment it is used in an overlapped I/O operation.

Cancellation of Overlapped I/O Operations

The Boolean function CancelIO allows you to cancel the execution of pending overlapped I/O operations associated with the specified handle (this function has only one parameter). All operations initiated by the calling thread using this handle are cancelled. Operations initiated by other threads are not affected by this function call. Canceled operations end with an ERROR OPERATION ABORTED error.

Example: Using a File Descriptor as a Synchronization Object

Overlapping I/O is very convenient and easy to implement in cases where there can be only one pending operation. Then, for synchronization purposes, the program can use not an event, but a file descriptor.

The code snippet below shows how a program can initiate a read operation to read a portion of a file, continue its execution to perform other processing, and then enter a state waiting for the file descriptor to signal.

OVERLAPPED ov = ( 0, 0, 0, 0, NULL /* Events are not used. */ );
hF = CreateFile(…, FILE_FLAG_OVERLAPPED, …);
ReadFile(hF, Buffer, sizeof(Buffer), &nRead, &ov);
/* Perform other processing. nRead is not necessarily valid.*/
/* Wait for the read operation to complete. */
WaitForSingleObject(hF, INFINITE);
GetOverlappedResult(hF, &ov, &nRead, FALSE);

Example: Converting Files Using Overlapped I/O and Multiple Buffering

Program 2.4 (atou) converted an ASCII file to UNICODE by processing the file sequentially, and Chapter 5 showed how to do the same sequential processing using file mapping. Program 14.1 (atouOV) solves the same problem using overlapped I/O and multiple buffers that hold fixed-size records.

Figure 14.1 illustrates the organization of a program with four fixed-size buffers. The program is implemented so that the number of buffers can be specified using a preprocessor symbolic constant, but in the following discussion we will assume that there are four buffers.

First, the program initializes all elements of OVERLAPPED structures that define events and positions in files. There is a separate OVERLAPPED structure for each input and output buffer. After that, an overlapped read operation is initiated for each of the input buffers. Next, using the WaitForMultipleObjects function, the program waits for a single event indicating the completion of reading or writing. When a read operation completes, the input buffer is copied and converted to the corresponding output buffer, after which the write operation is initiated. When the write completes, the next read operation is initiated. Note that the events associated with the input and output buffers are placed in a single array, which is used as an argument when calling the WaitForMultipleObjects function.

Rice. 14.1. Asynchronous file update model


Program 14.1. atouOV: file conversion using overlapped I/O
Converting a file from ASCII to Unicode using overlapped I/O. The program only works on Windows NT. */

#define MAX_OVRLP 4 /* Number of overlapped I/Os.*/
#define REC_SIZE 0x8000 /* 32 KB: Minimum record size for acceptable performance. */

/* Each of the elements of the variable arrays defined below */
/* and structs match a single pending operation */
/* overlapped I/O. */
DWORD nin, nout, ic, i;
OVERLAPPED OverLapIn, OverLapOut;
/* Need to use a solid, two-dimensional array */
/* dictated by the WaitForMultipleObjects Function. */
/* The value 0 of the first index corresponds to reading, the value 1 corresponds to writing. */
/* In each of the two buffer arrays defined below, the first index */
/* number the I/O operations. */
LARGE_INTEGER CurPosIn, CurPosOut, FileSize;
/* Total number of records to be processed, computed */
/* based on input file size. The entry at the end */
/* may be incomplete. */
for (ic = 0; ic< MAX_OVRLP; ic++) {
/* Create read and write events for each OVERLAPPED structure.*/
hEvents = OverLapIn.hEvent /* Read event.*/
hEvents = OverLapOut.hEvent /* Write event. */
= CreateEvent(NULL, TRUE, FALSE, NULL);
/* Starting file positions for each OVERLAPPED structure. */
/* Initiate an overlapped read operation on this OVERLAPPED structure. */
if (CurPosIn.QuadPart< FileSize.QuadPart) ReadFile(hInputFile, AsRec, REC_SIZE, &nin, &OverLapIn);
/* All read operations are performed. Wait for the event to complete and reset it immediately. The read and write events are stored in the event array next to each other. */
iWaits=0; /* The number of I/O operations performed so far. */
while (iWaits< 2 * nRecord) {
ic = WaitForMultipleObjects(2 * MAX_OVRLP, hEvents, FALSE, INFINITE) - WAIT_OBJECT_0;
iWaits++; /* Increment the counter of completed I/O operations. */
ResetEvent(hEvents);
/* Read completed. */
GetOverlappedResult(hInputFile, &OverLapIn, &nin, FALSE);
for (i =0; i< REC_SIZE; i++) UnRec[i] = AsRec[i];
WriteFile(hOutputFile, UnRec, nin * 2, &nout, &OverLapOut);
/* Prepare for the next read, which will be initiated after the write operation started above completes. */
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;
) else if (ic< 2 * MAX_OVRLP) { /* Операция записи завершилась. */
/* Start reading. */
ic -= MAX_OVRLP; /* Set the index of the output buffer. */
if (!GetOverlappedResult (hOutputFile, &OverLapOut, &nout, FALSE)) ReportError(_T("Error reading."), 0, TRUE);
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
if (CurPosIn.QuadPart< FileSize.QuadPart) {
/* Start a new read operation. */
ReadFile(hInputFile, AsRec, REC_SIZE, &nin, &OverLapIn);
/* Close all events. */
for (ic = 0; ic< MAX_OVRLP; ic++) {

Program 14.1 can only run under Windows NT. Windows 9x's asynchronous I/O facilities do not allow the use of disk files. Appendix B lists the results and comments about the relatively poor performance of the atouOV program. Experiments have shown that the buffer size must be at least 32 KB to achieve acceptable performance, but even then normal synchronous I/O is faster. In addition, the performance of this program is not improved under SMP conditions either, because in this example, which processes only two files, the CPU is not a critical resource.

Extended I/O using completion procedure

There is also another possible approach to using synchronization objects. Instead of having a thread wait for a termination signal from an event or handle, the system can initiate a call to a user-defined termination routine as soon as the I/O operation completes. The termination procedure can then start the next I/O operation and perform any necessary accounting for the use of system resources. This indirectly called (callback) completion procedure is similar to the asynchronous procedure call used in Chapter 10 and requires the use of alertable wait states.

How can a termination procedure be specified in a program? Among the parameters or data structures of the ReadFile and WriteFile functions, there are none left that could be used to store the address of the termination procedure. However, there is a family of extended I/O functions that are denoted by the suffix "Ex" and contain an additional parameter to pass the address of the termination procedure. The read and write functions are ReadFileEx and WriteFileEx, respectively. In addition, one of the following standby functions is required.

Extended I/O is sometimes referred to as duty input/output(alertable I/O). How to use advanced features is described in the following sections.

Note

Under Windows 9x, extended I/O cannot work with disk files and communication ports. At the same time, Windows 9x advanced I/O is capable of working with named pipes, mailboxes, sockets, and serial devices.

ReadFileEx, WriteFileEx functions and completion procedures

Extended read and write functions can be used in conjunction with open file, named pipe, and mailbox handles if the corresponding object was opened (created) with the FILE_FLAG_OVERLAPPED flag set. Note that this flag sets the handle attribute, and although overlapped and extended I/O are different, the same flag applies to both types of asynchronous I/O handles.

Overlapped sockets (Chapter 12) can be used with the ReadFileEx and WriteFileEx functions in all versions of 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)

You are already familiar with both functions, except that each of them has an additional parameter that allows you to specify the address of the completion procedure.

Each of the functions needs to provide an OVERLAPPED structure, but there is no need to specify the hEvent element of this structure; the system ignores it. However, this element is very useful for conveying information such as the sequence number used to distinguish between individual I/O operations, as shown in Program 14.2.

Comparing with the ReadFile and WriteFile functions, you can see that the extended functions do not require parameters to store the number of bytes transferred. This information is passed to the termination function, which must be included in the program.

The completion function provides parameters for the byte count, error code, and address of the OVERLAPPED structure. The last of these parameters is required so that the completion procedure can determine which of the outstanding operations has completed. Note that the earlier warnings about reusing or destroying OVERLAPPED structures apply here just as much as in the case of overlapped I/O.

VOID WINAPI FileIOCompletionRoutine(DWORD dwError, DWORD cbTransferred, LPOVERLAPPED lpo)

As in the case of the CreateThread function, which is also called with the name of some function, the name FileIOCompletionRoutine is a placeholder, not the actual name of the termination procedure.

Values ​​for the dwError parameter are limited to 0 (success) and ERROR_HANDLE_EOF (when trying to read out of bounds on the file). The OVERLAPPED structure is the one used by the completed call to ReadFileEx or WriteFileEx.

Before the termination procedure is called by the system, two things must happen:

1. The I/O operation must complete.

2. The calling thread must be in the sleepy state, notifying the system that it needs to execute the queued completion routine.

How does a thread transition into a standby wait state? It must make an explicit call to one of the watchdog functions described in the next section. Thus, the thread creates conditions that make it impossible to prematurely execute the termination procedure. A thread can only be in the sleepy state for as long as the call to the sleepy function is called; after this function returns, the thread exits the specified state.

If both of these conditions are met, the completion routines queued as a result of the completion of I/O operations are executed. Completion routines run on the same thread that made the original I/O function call and is in the idle wait state. Therefore, a thread should enter the standby state only when there are safe conditions for executing termination procedures.

Standby functions

There are five standby functions in total, but the following are prototypes of only three of them that are of direct interest to us:

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)

Each of the watchdog functions has a bAlertable flag, which must be set to TRUE in the case of asynchronous I/O. The above functions are extensions of the Wait and Sleep functions you are familiar with.

The duration of the wait intervals is specified, as usual, in milliseconds. Each of these three functions returns as soon as the any from the following situations:

The handle(s) transition(s) to the signaled state, thereby satisfying the standard requirements of two of the wait functions.

Timeout expires.

All completion procedures in the thread's queue stop executing, and bAlertable is set to TRUE. The completion procedure is queued when the corresponding I/O operation completes (Figure 14.2).

Note that no events are associated with the OVERLAPPED structures in the ReadFileEx and WriteFileEx functions, so none of the handles specified when the wait function is called are associated directly with any particular I/O operation. At the same time, the SleepEx function is not associated with synchronization objects, and therefore it is the easiest to use. In the case of the SleepEx function, the duration of the sleep interval is usually set to INFINITE, so the return from this function will only occur after one or more completion procedures that are currently in the queue have finished executing.

Execute completion procedure and return from standby function

When an extended I/O operation completes, its associated completion procedure, with its arguments specifying an OVERLAPPED structure, a byte count, and an error code, is queued for execution.

All completion routines in the thread's queue begin executing when the thread enters the idle wait state. They are executed one after the other, but not necessarily in the same sequence in which the I/O operations completed. The return from the watchdog function occurs only after the completion procedure has returned. This feature is important to ensure the correct functioning of most programs, since it assumes that termination routines get an opportunity to prepare for the next use of the OVERLAPPED structure and perform other necessary actions to bring the program to a known state before returning from the sleepy state.

If the return from the SleepEx function is due to the execution of one or more queued completion procedures, then the return value of the function will be WAIT_TO_COMPLETION, and the same value will be returned by the GetLastError function called after one of the wait functions has returned.

In conclusion, we note two points:

1. When calling any of the watchdog functions, use INFINITE as the value of the wait interval parameter. In the absence of the possibility of expiration of the waiting interval, the return from the functions will be carried out only after the execution of all completion procedures has ended or the descriptors have passed into the signaled state.

2. It is common to use the hEvent data member of the OVERLAPPED structure to pass information to the completion procedure, since this field is ignored by the OS.

The interaction between the main thread, completion routines, and watchdog functions is illustrated in Fig. 14.2. This example starts three concurrent reads, two of which complete by the time the standby wait begins.

Rice. 14.2. Asynchronous I/O using completion routines

Example: Converting a File Using Extended I/O

Program 14.3 (atouEX) is a revised version of program 14.1. These programs illustrate the difference between the two methods of asynchronous I/O. atouEx is similar to Program 14.1, but it has moved much of the resource-sequencing code into the finalizer, and made many variables global so that the finalizer can access them. However, Appendix B shows that in terms of performance, atouEx is quite competitive with other methods that do not use file mapping, while atouOV is slower.

Program 14.2. atouEx: file conversion using extended I/O
Converting a file from ASCII to Unicode using EXTENDED I/O. */
/* atouEX file1 file2 */

#define REC_SIZE 8096 /* Block size is not as important in terms of performance as it is with atouOV. */
#define UREC_SIZE 2 * REC_SIZE

static VOID WINAPI ReadDone(DWORD, DWORD, LPOVERLAPPED);
static VOID WINAPI WriteDone(DWORD, DWORD, LPOVERLAPPED);

/* The first OVERLAPPED structure is for reading and the second is for writing. Structures and buffers are allocated for each upcoming operation. */
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 = CreateFile(argv, GENERIC_WRITE, 0, NULL, CREATE_ALWAYS, FILE_FLAG_OVERLAPPED, NULL);
FileSize.LowPart = GetFileSize(hInputFile, &FileSize.HighPart);
nRecord = FileSize.QuadPart / REC_SIZE;
if ((FileSize.QuadPart % REC_SIZE) != 0) nRecord++;
for (ic = 0; ic< MAX_OVRLP; ic++) {
OverLapIn.hEvent = (HANDLE)ic; /* Reload the event. */
OverLapOut.hEvent = (HANDLE)ic; /* Fields. */
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;
if (CurPosIn.QuadPart< FileSize.QuadPart) ReadFileEx(hInputFile, AsRec, REC_SIZE, &OverLapIn , ReadDone);
CurPosIn.QuadPart += (LONGLONG)REC_SIZE;
/* All read operations are performed. Enter the standby state and remain in it until all records have been processed.*/
while (nDone< 2 * nRecord) SleepEx(INFINITE, TRUE);
_tprintf(_T("ASCII to Unicode conversion complete.\n"));

static VOID WINAPI ReadDone(DWORD Code, DWORD nBytes, LPOVERLAPPED pOv) (
/* Read completed. Convert data and initiate write. */
LARGE_INTEGER CurPosIn, CurPosOut;
/* Process the write and initiate the write operation. */
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
CurPosOut.QuadPart = (CurPosIn.QuadPart / REC_SIZE) * UREC_SIZE;
OverLapOut.Offset = CurPosOut.LowPart;
OverLapOut.OffsetHigh = CurPosOut.HighPart;
/* Convert entry from ASCII to Unicode. */
for (i = 0; i< nBytes; i++) UnRec[i] = AsRec[i];
WriteFileEx(hOutputFile, UnRec, nBytes*2, &OverLapOut, WriteDone);
/* Prepare the OVERLAPPED structure for the next read. */
CurPosIn.QuadPart += REC_SIZE * (LONGLONG)(MAX_OVRLP);
OverLapIn.Offset = CurPosIn.LowPart;
OverLapIn.OffsetHigh = CurPosIn.HighPart;

static VOID WINAPI WriteDone(DWORD Code, DWORD nBytes, LPOVERLAPPED pOv) (
/* Write completed. Initiate the next read operation. */
CurPosIn.LowPart = OverLapIn.Offset;
CurPosIn.HighPart = OverLapIn.OffsetHigh;
if (CurPosIn.QuadPart< FileSize.QuadPart) {
ReadFileEx(hInputFile, AsRec, REC_SIZE, &OverLapIn, ReadDone);

Asynchronous I/O using multiple threads

Overlapping and extended I/O allow for asynchronous I/O within a single thread, although the OS creates its own threads to support this functionality. In one form or another, methods of this type are often used in many early operating systems to support limited forms of performing asynchronous operations in single-threaded systems.

However, Windows provides multi-threading support, so it is possible to achieve the same effect by performing synchronous I/O operations on multiple independently executing threads. These capabilities have already been demonstrated previously with multithreaded servers and the grepMT program (Chapter 7). In addition, threads provide a conceptually sequential and purportedly much simpler way to perform asynchronous I/O. As an alternative to the methods used in Programs 14.1 and 14.2, one could give each thread its own file descriptor, and then each thread could process every fourth record synchronously.

This way of using streams is demonstrated in the atouMT program, which is not included in the book but is included in the material posted on the Web site. Not only is atouMT able to run on any version of Windows, but it's also simpler than either of the two asynchronous I/O programs because accounting for resource usage is less complicated. Each thread simply maintains its own buffers on its own stack and performs a series of synchronous reads, transforms, and writes in a loop. At the same time, the performance of the program remains at a fairly high level.

Note

The atouMT.c program on the Web site comments on several possible "pitfalls" you might encounter when allowing multiple threads to access the same file at the same time. In particular, all individual file handles must be created using the CreateHandle function, not the DuplicateHandle function.

Personally, I prefer to use multi-threaded file processing rather than asynchronous I/O. Streams are easier to program and provide better performance in most cases.

There are two exceptions to this general rule. The first of these, as shown earlier in this chapter, deals with situations in which there can be only one outstanding operation, and a file descriptor can be used for synchronization purposes. The second, more important exception occurs in the case of asynchronous I/O completion ports, which will be discussed at the end of this chapter.

Wait Timers

Windows NT supports waitable timers, which are one type of kernel object that performs a wait.

You can always create your own clock signal by creating a clock thread that sets the wake-up event after the Sleep function has been called. In the serverNP program (Program 11.3), the server also uses the clock stream to periodically broadcast its channel name. Therefore, wait timers provide a somewhat redundant but convenient way to organize tasks to run on a periodic basis or according to a specific schedule. In particular, the wait timer can be configured in such a way that the signal is generated at a strictly defined time.

The wait timer can be either a synchronization timer or a manual-reset notification timer. The sync timer is associated with an indirect call function similar to the extended I/O completion procedure, while a wait function is used to synchronize on a manually reset notify timer.

First, you need to create a timer handle using the CreateWaitableTimer function.

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

The second parameter, bManualReset, determines whether the type of timer to be created is synchronizing or notifying. Program 14.3 uses a sync timer, but by changing the comments and parameter setting, you can easily turn it into a notification timer. Note that there is also an OpenWaitableTimer function that can use the optional name provided by the third argument.

Initially, the timer is created in an inactive state, but using the SetWaitableTimer function, you can activate it and specify the initial time delay, as well as the duration of the time interval between periodically generated signals.

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

hTimer is a valid handle to the timer created using the CreateWaitableTimer function.

The second parameter pointed to by the pDueTime pointer can take either positive values, corresponding to absolute time, or negative values, corresponding to relative time, with the actual values ​​expressed in time units of 100 nanoseconds and their format described by the FILETIME structure. Variables of type FILETIME were introduced in Chapter 3 and were already used in Chapter 6 in the timep program (Program 6.2).

The value of the interval between signals, specified in the third parameter, is expressed in milliseconds. If this value is set to 0, then the timer is signaled only once. If this parameter is positive, the timer is periodic and runs periodically until its action is terminated by calling the CancelWaitableTimer function. Negative values ​​for the specified interval are not allowed.

The fourth parameter, pfnCompletionRoutine, is used in the case of a synchronizing timer and specifies the address of the completion routine that is called when the timer goes signaled. and provided that the thread enters the idle wait state. When this procedure is called, the pointer specified by the fifth parameter, plArgToComplretionRoutine, is used as one of the arguments.

By setting a synchronizing timer, you can put the thread into sleepy state by calling the SleepEx function to ensure that the termination procedure can be called. In the case of a manually reset notifying timer, you should wait for the timer handle to become signaled. The handle will remain in the signaled state until the next call to the SetWaitableTimer function. The full version of the 14.3 program, found on the Web site, allows you to run your own experiments using a timer of your choice, combined with a termination routine or waiting for the timer handle to signal, resulting in four different combinations.

The last parameter, fResume, is related to power saving modes. For more information on this subject, please refer to the help documentation.

The CancelWaitableTimer function is used to cancel the previously called SetWaitableTimer function, but does not change the signaled state of the timer. To do this, you need to call the SetWaitableTimer function again.

Example: Using a Wait Timer

Program 14.3 demonstrates the use of a sleep timer to generate periodic signals.

Program 14.3. TimeBeep: generation of periodic signals
/* Chapter 14. TimeBeep.p. Periodic sound notification. */
/* Usage: TimeBeep period (in milliseconds). */

static BOOL WINAPI Handler(DWORD CntrlEvent);
static VOID APIENTRY Beeper(LPVOID, DWORD, DWORD);
volatile static BOOL Exit = FALSE;

int _tmain(int argc, LPTSTR argv) (
/* Intercept keystrokes to terminate the operation. See chapter 4. */
SetConsoleCtrlHandler(Handler, TRUE);
DueTime.QuadPart = -(LONGLONG)Period * 10000;
/* The DueTime parameter is negative for the first timeout period and is relative to the current time. The timeout period is measured in ms (10 -3 s), while DueTime is measured in units of 100 ns (10 -7 s) to be consistent with the FILETIME type. */
hTimer = CreateWaitableTimer(NULL, FALSE /* "Sync Timer" */, NULL);
SetWaitableTimer(hTimer, &DueTime, Period, Beeper, &Count, TRUE);
_tprintf(_T("Count = %d\n"), Count);
/* The counter value is incremented in the timer procedure. */
/* Enter the standby state. */
_tprintf(_T("Completion. Counter = %d"), Count);

static VOID APIENTRY Beeper(LPVOID lpCount, DWORD dwTimerLowValue, DWORD dwTimerHighValue) (
*(LPDWORD)lpCount = *(LPDWORD)lpCount + 1;
_tprintf(_T("Generate signal number: %d\n"), *(LPDWORD) lpCount);
Fan(1000 /* Frequency. */, 250 /* Duration (ms). */);

BOOL WINAPI Handler(DWORD CntrlEvent) (
_tprintf(_T("Shutting down\n"));

The task that issued the request for the I/O operation is transferred by the supervisor to the state of waiting for the completion of the ordered operation. When the supervisor receives a message from the completion section that the operation has completed, it puts the task in a ready-to-run state and it continues its work. This situation corresponds to synchronous I/O. Synchronous I/O is standard on most operating systems. To increase the speed of application execution, it was proposed to use asynchronous I / O if necessary.

The simplest variant of asynchronous output is the so-called buffered output to an external device, in which data from the application is not transferred directly to the I / O device, but to a special system buffer. In this case, logically, the output operation for the application is considered to be completed immediately, and the task does not have to wait for the end of the actual data transfer process to the device. The process of actually outputting data from the system buffer is handled by the I/O supervisor. Naturally, a special system process is engaged in allocating a buffer from the system memory area at the direction of the I/O supervisor. So, for the considered case, the output will be asynchronous if, firstly, the I / O request indicated the need for data buffering, and secondly, if the I / O device allows such asynchronous operations and this is noted in the UCB. You can organize and asynchronous data entry. However, for this it is necessary not only to allocate a memory area for temporary storage of data read from the device and to associate the allocated buffer with the task that ordered the operation, but also to split the request for the I/O operation into two parts (into two requests). The first request specifies an operation to read data, similar to what is done with synchronous I/O. However, the type (code) of the request is used differently, and the request specifies at least one additional parameter - the name (code) of the system object that the task receives in response to the request and which identifies the allocated buffer. Having received the name of the buffer (we will conditionally call this system object in this way, although in various operating systems other terms are used to designate it, for example, a class), the task continues its work. It is very important to emphasize here that as a result of an asynchronous input request, the task is not transferred by the I/O supervisor to the state of waiting for the completion of the I/O operation, but remains in the running state or in the state of readiness for execution. After some time, after executing the necessary code that was defined by the programmer, the task issues a second request to complete the I/O operation. In this second request to the same device, which, of course, has a different code (or request name), the task specifies the name of the system object (buffer for asynchronous data input) and, in case of successful completion of the data reading operation, immediately receives them from the system buffer. If the data has not yet been completely rewritten from the external device to the system buffer, the I / O supervisor puts the task into the state of waiting for the completion of the I / O operation, and then everything resembles a normal synchronous data input.

Typically, asynchronous I/O is provided in most multiprogram OSes, especially if the OS supports multitasking through the threading mechanism. However, if asynchronous I / O is not explicitly available, you can implement its ideas yourself by organizing an independent thread for data output.

I/O hardware can be viewed as a collection hardware processors, which are able to work in parallel with respect to each other, as well as with respect to the central processing unit (processors). On such "processors" the so-called external processes. For example, for an external device (an input/output device), an external process can be a set of operations that move the print head, advance the paper one position, change the color of the ink, or print some characters. External processes, using I / O equipment, interact both with each other and with ordinary "software" processes running on the central processor. Important in this case is the fact that the speed of execution of external processes will differ significantly (sometimes by an order of magnitude or more) from the speed of execution of ordinary (" internal”) processes. For their normal operation, external and internal processes must be synchronized. To smooth out the effect of a strong speed mismatch between internal and external processes, the buffering mentioned above is used. Thus, we can speak of a system of parallel interacting processes (see Chapter 6).

Buffers are a critical resource in relation to internal (software) and external processes, which, in their parallel development, interact informationally. Through the buffer (buffers), data is either sent from some process to the addressed external one (the operation of outputting data to an external device), or transferred from an external process to some software process (the operation of reading data). The introduction of buffering as a means of information interaction puts forward the problem of managing these system buffers, which is solved by means of the supervisory part of the OS. At the same time, the supervisor is tasked not only with allocating and freeing buffers in the system memory area, but also with synchronizing processes in accordance with the state of operations for filling or freeing buffers, as well as waiting for them if there are no free buffers available, and a request for input / the output requires buffering. Usually, the I/O supervisor uses the standard synchronization tools adopted in the given OS to solve the above tasks. Therefore, if the OS has developed tools for solving the problems of parallel execution of interacting applications and tasks, then, as a rule, it also implements asynchronous I/O.

Data input/output

Most of the previous articles are devoted to the optimization of computing performance. We have seen many examples of customizing garbage collection, looping, and recursive algorithms, and even optimizing algorithms to reduce runtime overhead.

For some applications, optimization of computational aspects provides only a small performance gain, because the bottleneck in them is I / O operations, such as data transfer over a network or disk access. From our experience, we can say that a significant proportion of performance problems are not related to the use of suboptimal algorithms or excessive load on the processor, but to inefficient use of I / O devices. Let's look at two situations where I/O optimization can improve overall performance:

    An application can experience severe computational overload due to inefficient I/O operations that increase overhead. Worse, the overload can be so great that it becomes a limiting factor preventing maximum utilization of I/O device bandwidth.

    An I/O device may not be fully utilized or its capabilities may be wasted due to inefficient programming patterns, such as sending large amounts of data in small chunks or not using all of the bandwidth.

This article describes general I/O concepts and provides recommendations for improving the performance of any type of I/O. These recommendations apply equally to network applications, to disk-intensive processes, and even to programs that access non-standard, high-performance hardware devices.

Synchronous and asynchronous I/O

When executed in synchronous mode, Win32 API I/O functions (such as ReadFile, WriteFile, or DeviceloControl) block program execution until the operation completes. Although this model is very convenient to use, it is not very efficient. In the time intervals between the execution of successive I / O requests, the device may be idle, that is, not fully used.

Another problem with synchronous mode is that the thread of execution wastes time on any concurrent I/O operation. For example, in a server application serving many clients at the same time, it may be possible to create a separate thread of execution for each session. These threads, which are idle most of the time, waste memory and can create situations thread thrashing, when many threads of execution simultaneously resume work on completion of I / O and begin to fight for processor time, which leads to an increase in context switches per unit of time and a decrease in scalability.

The Windows I/O subsystem (including device drivers) operates internally in asynchronous mode - a program can continue executing at the same time as an I/O operation. Almost all modern hardware devices are asynchronous in nature and do not require constant polling to transfer data or determine when an I/O operation has completed.

Most devices support the ability direct memory access (Direct Memory Access, DMA) to transfer data between the device and the RAM of the computer without requiring the participation of the processor in the operation, and generate an interrupt when the data transfer is completed. Synchronous I/O, which is internally asynchronous, is only supported at the Windows application layer.

In Win32, asynchronous I/O is called overlapped input/output (overlapped I/O), the comparison of synchronous and overlapped I/O modes is shown in the figure below:

When an application makes an asynchronous request to perform an I/O operation, Windows either performs the operation immediately or returns a status code indicating that the operation is pending. The thread can then start other I/O operations or perform some calculations. The programmer has several ways to organize the receipt of notifications about the completion of I / O operations:

    Win32 event: The operation waiting for this event will be executed when I/O completes.

    Calling a User Defined Function with a Mechanism Asynchronous Procedure Call (APC): The thread of execution must be in the alertable wait state.

    Receive notifications via I/O Completion Ports (IOCP): This is usually the most efficient mechanism. We will explore it in detail further.

Some I/O devices (for example, a file opened in unbuffered mode) can provide additional benefits if the application can ensure that a small number of pending I/O requests are always present. To do this, it is recommended to first make several requests to perform I / O operations and for each executed request to produce a new request. This will ensure that the device driver initializes the next operation as soon as possible without waiting for the application to make the next request. But do not overdo it with the amount of data transferred, because this will consume limited kernel memory resources.

I/O Completion Ports

Windows supports an efficient mechanism for notifying the completion of asynchronous I/O operations called I/O Completion Ports (IOCP). In .NET applications, it is available through the method ThreadPool.BindHandle(). This mechanism is used internally by several types in .NET that perform I/O operations: FileStream, Socket, SerialPort, HttpListener, PipeStream, and some .NET Remoting channels.

The IOCP mechanism shown in the figure above communicates with a number of I/O handles (sockets, files, and specialized device driver objects) that are open asynchronously and with a specific thread of execution. Once the I/O operation associated with such a handle has completed, Windows will add a notification to the appropriate IOCP port and pass it to the associated thread of execution for processing.

Using a thread pool to service notifications and resume execution of threads that have initiated asynchronous I/O operations reduces the number of context switches per unit of time and increases CPU usage. Not surprisingly, high performance servers such as Microsoft SQL Server use I/O completion ports.

The completion port is created by calling the Win32 API function CreateIoCompletionPort, which is passed the maximum concurrency value (number of threads), the completion key, and an optional handle to the I/O object. The completion key is a user-defined value that is used to identify various I/O handles. You can bind multiple handles to the same IOCP port by repeatedly calling the CreateIoCompletionPort function and passing it a handle to an existing completion port.

To establish a connection with the specified IOCP port, user threads call the function GetCompletionStatus and await its completion. A thread of execution can only be associated with one IOCP port at a time.

Function call GetQueuedCompletionStatus blocks the execution of the thread until it is notified (or timed out), and then returns information about the completed I/O operation, such as the number of bytes transferred, the completion key, and the structure of the asynchronous I/O operation. If all threads associated with the I/O port are busy at the time of the notification (that is, there are no threads waiting on the GetQueuedCompletionStatus call), the IOCP mechanism will create a new thread of execution, up to the maximum concurrency value. If a thread called GetQueuedCompletionStatus and the notification queue is not empty, the function returns immediately without blocking the thread in the operating system kernel.

The IOCP mechanism is able to detect that some of the "busy" threads are actually doing synchronous I/O and start an additional thread, possibly exceeding the maximum concurrency value. Notifications can also be sent manually, without performing I/O, by calling the function PostQueuedCompletionStatus.

The following code demonstrates an example of using ThreadPool.BindHandle() with a Win32 file descriptor:

Using System; using System.Threading; using Microsoft.Win32.SafeHandles; using 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, OpenExisting = 3, OpenAlways = 3, OpenAlways = 3, OpenAlways = 3, OpenAlways = 3 4, TruncateExisting = 5 ) enum EFileAttributes: uint ( // ... Some flags not shown Normal = 0x00000080, Overlapped = 0x40000000, NoBuffering = 0x20000000, ) enum EFileAccess: uint ( // ... Some flags not shown GenericRead = 0x80000000 , GenericWrite = 0x40000000, ) static lo ng_numBytesWritten; // Brake for write stream static AutoResetEvent _waterMarkFullEvent; static int _pendingIosCount; const int MaxPendingIos = 10; // Completion routine, called by I/O threads static unsafe void WriteComplete(uint errorCode, uint numBytes, NativeOverlapped* pOVERLAP) ( _numBytesWritten += numBytes; Overlapped ovl = Overlapped.Unpack(pOVERLAP); Overlapped.Free(pOVERLAP); // Notify the writer thread that the number of pending I/O operations // has decreased to the allowed limit if (Interlocked.Decrement(ref _pendingIosCount) = MaxPendingIos) ( _waterMarkFullEvent.WaitOne(); ) ) ) ) )

Let's look at the TestIOCP method first. This calls the CreateFile() function, which is a P/Invoke mechanism function used to open or create a file or device. To perform I/O operations in asynchronous mode, the EFileAttributes.Overlapped flag must be passed to the function. If successful, the CreateFile() function returns a Win32 file descriptor, which we bind to the I/O completion port by calling ThreadPool.BindHandle(). Next, an event object is created that is used to temporarily block the thread that initiated the I/O operation if there are too many such operations (the limit is set by the MaxPendingIos constant).

Then the cycle of asynchronous write operations begins. In each iteration, a buffer is created with data to be written and overlapped structure, containing the offset within the file (in this example, writing is always done at offset 0), an event handle to be sent when the operation completes (not used by the IOCP mechanism), and an optional user object IAsyncResult An that can be used to pass state to the finalizer function.

Next, the Overlapped.Pack() method is called, which accepts a wrapping function and a data buffer. It creates the equivalent low-level I/O structure in unmanaged memory and pins the data buffer. Freeing the unmanaged memory occupied by the low-level structure and detaching the buffer must be done manually.

If there aren't too many I/Os going on at the same time, we call WriteFile(), passing it the specified low-level structure. Otherwise, we wait until an event occurs indicating that the number of pending operations has fallen below the upper limit.

The WriteComplete completion function is called by a thread in the I/O completion thread pool as soon as the operation is complete. It is passed a pointer to a low-level asynchronous I/O structure that can be unpacked and converted to a managed Overlapped structure.

In summary, when working with high-performance I/O devices, use asynchronous I/O with completion ports, either directly by creating and using your own completion port in the unmanaged library, or by associating Win32 handles with a completion port in .NET using method ThreadPool.BindHandle().

Thread pool in .NET

The thread pool in .NET can be successfully used for a variety of purposes, each of which creates different types of threads. In discussing parallel computing earlier, we were introduced to the thread pool API, where we used it to parallelize computational tasks. However, thread pools can also be used to solve other kinds of problems:

    Worker threads can handle asynchronous calls to user delegates (such as BeginInvoke or ThreadPool.QueueUserWorkItem).

    I/O completion threads can service notifications from the global IOCP port.

    Waiter threads can wait for registered events, allowing you to wait for multiple events on the same thread at once (using WaitForMultipleObjects), up to the Windows upper limit (maximum wait objects = 64). The event wait reception is used to organize asynchronous I/O without the use of completion ports.

    Timer threads waiting for multiple timers to expire at once.

    Gate threads control the use of the processor by threads from the pool, and also change the number of threads (within the set limits) to achieve the highest performance.

It is possible to initiate I/O operations that appear to be asynchronous but are not. For example, calling the ThreadPool.QueueUserWorkItem delegate and then performing a synchronous I/O operation is not truly an asynchronous operation, and is no better than performing the same operation on a normal thread of execution.

Memory copy

It is not uncommon for a physical I/O device to return a buffer of data that is copied over and over again until the application has finished processing it. Such copying can consume a significant portion of the processing power of the processor, so it should be avoided to ensure maximum throughput. Next, we will look at several situations where it is customary to copy data, and get acquainted with techniques to avoid this.

Unmanaged memory

Working with a buffer in unmanaged memory is much more difficult in .NET than with a managed byte array, so programmers often copy the buffer into managed memory in search of the easiest way.

If the functions or libraries you use allow you to explicitly specify a buffer in memory or pass your callback function to it to allocate a buffer, allocate a managed buffer and pin it in memory so that it can be accessed by both pointer and managed reference. If the buffer is large enough (> 85,000 bytes), it will be created at heap of large objects (Large Object Heap), so try to reuse existing buffers. If buffer reuse is complicated by the uncertainty of an object's lifetime, use memory pools.

In other cases where functions or libraries themselves allocate (unmanaged) memory for buffers, you can access that memory directly by pointer (from unsafe code) or by using wrapper classes such as UnmanagedMemoryStream And UnmanagedMemoryAccessor. However, if you need to pass the buffer to some code that only operates on byte arrays or string objects, copying may be unavoidable.

Even if you can't avoid memory copying and some or most of your data is filtered early on, you can avoid unnecessary copying by checking if the data is necessary before copying it.

Exporting Part of the Buffer

Programmers sometimes assume that byte arrays contain only the data they need, from start to finish, forcing the calling code to break the buffer (allocate memory for a new byte array and copy only the necessary data). This situation can often be seen in protocol stack implementations. The equivalent unmanaged code, in contrast, can take a simple pointer, without even knowing whether it points to the beginning of the actual buffer or the middle, and a buffer length parameter to determine where the end of the data being processed is.

To avoid unnecessary memory copying, arrange for an offset and length to be accepted wherever you receive a byte parameter. Use the length parameter instead of the array's Length property, and add the offset value to the current indexes.

Random Read and Merge Write

Random Read and Write Merge is a capability supported by Windows to read into non-contiguous regions or write data from non-contiguous regions as if they were occupying a contiguous piece of memory. This functionality is provided in the Win32 API as functions ReadFileScatter And WriteFileGather. The Windows Sockets Library also supports random read and merge writes by providing its own functions: WSASend, WSARecv, and others.

Random read and merge write can be useful in the following situations:

    When each packet has a fixed size header that precedes the actual data. Random read and merge write will allow you to avoid having to copy headers every time you need to get a contiguous buffer.

    When it is desirable to get rid of the extra overhead of system call calls when performing I/O with multiple buffers.

Compared to the ReadFileScatter and WriteFileGather functions, which require each buffer to be exactly the size of one page, and the handle to be opened asynchronously and unbuffered (an even greater limitation), the socket-based scatter read and merge write functions seem more practical. , because they do not have these restrictions. The .NET Framework supports scatter read and merge write for sockets through overloaded methods Socket.Send() And Socket.Receive() without exporting generic read/write functions.

An example of using the scatter read and merge write functions can be found in the HttpWebRequest class. It concatenates the HTTP headers with the actual data without resorting to creating a contiguous buffer to store it.

File I/O

Typically, file I/O is performed through the file system cache, which offers several performance benefits: caching of recently accessed data, read-ahead (pre-reading data from disk), lazy writes (asynchronous writes to disk), and concatenation of writes of small chunks of data. . By prompting Windows about the expected file access pattern, you can get an additional performance boost. If your application does asynchronous I/O and can handle some of the buffering issues, then avoiding the caching mechanism entirely might be a better solution.

Cache management

When creating or opening files, programmers pass flags and attributes to the CreateFile function, some of which affect the behavior of the caching mechanism:

    Flag FILE_FLAG_SEQUENTIAL_SCAN indicates that the file will be accessed sequentially, possibly skipping parts, and random access is unlikely. As a result, the cache manager will read-ahead, looking further than usual.

    Flag FILE_FLAG_RANDOM_ACCESS specifies that the file will be accessed in random order. In this case, the cache manager will perform reads slightly ahead, due to the reduced chance that the data read ahead will actually be needed by the application.

    Flag FILE_ATTRIBUTE_TEMPORARY indicates that the file is temporary, so actual write operations to the physical media (to prevent data loss) can be deferred.

In .NET, these options are supported (except for the last one) using the overloaded FileStream constructor that takes a FileOptions enum type parameter.

Random access has a negative impact on performance, especially when working with disk devices, as it requires moving heads. As technology has evolved, disk throughput has increased only by increasing storage density, not by reducing latency. Modern disks are capable of reordering random access query execution to reduce the overall time spent moving heads. This approach is called hardware command queuing (Native Command Queuing, NCO). To make this technique more effective, the disk controller needs to send several I / O requests at once. In other words, if possible, try to have multiple asynchronous I/O requests pending at once.

Unbuffered I/O

Unbuffered I/O operations are always performed without using the cache. This approach has its advantages and disadvantages. As with the cache control trick, raw I/O is enabled via the "flags and attributes" option during file creation, but .NET does not provide access to this capability.

    Flag FILE_FLAG_NO_BUFFERING disables caching of reads and writes, but does not affect the caching performed by the disk controller. This avoids copying (from the user's buffer to the cache) and "polluting" the cache (filling the cache with unnecessary data and pushing out the necessary ones). However, unbuffered reads and writes must adhere to alignment requirements.

    The following parameters must be equal to or a multiple of the disk sector size: the size of one transfer, the offset in the file, and the address of the buffer in memory. Typically, a disk sector is 512 bytes in size. The newest high-capacity disk devices have a sector size of 4096 bytes, but they can run in compatibility mode, emulating 512-byte sectors (at the expense of performance).

    Flag FILE_FLAG_WRITE_THROUGH tells the cache manager that it should immediately flush write data from the cache (if the FILE_FLAG_NO_BUFFERING flag is not set) and tells the disk controller that it should write to the physical media immediately, without storing the data in an intermediate hardware cache.

Read-ahead improves performance by making fuller disk usage, even when an application reads in synchronous mode with delays between operations. The correct determination of which part of the file the application will request next is up to Windows. By disabling buffering, you also disable read-ahead, and must keep the disk device busy by doing multiple overlapping I/Os.

Latency writes also improve the performance of applications that perform synchronous write operations by giving the illusion that writing to disk is very fast. The application will be able to improve CPU usage by blocking for shorter periods of time. With buffering disabled, the duration of write operations will be equal to the full length of time required to complete writing data to disk. Therefore, the use of asynchronous I / O mode with disabled buffering becomes even more important.

I/O management.

block-oriented devices and byte-oriented

Main idea

key principle is device independence

Interrupt handling,

device drivers,

It seems obvious that a wide variety of interruptions are possible for a wide variety of reasons. Therefore, a number is associated with an interrupt - the so-called interrupt number.

This number uniquely corresponds to a particular event. The system is able to recognize interrupts and, when they occur, starts the procedure corresponding to the interrupt number.

Some interrupts (the first five in numerical order) are reserved for use by the CPU in case of any special events such as an attempt to divide by zero, overflow, etc. (these are really J internal interrupts).

Hardware interrupts always occur asynchronously with respect to running programs. In addition, several interrupts can occur at the same time!

In order for the system not to get lost when deciding which interrupt to service first, there is a special priority scheme. Each interrupt is assigned its own priority. If multiple interrupts occur at the same time, the system prioritizes the highest priority, deferring other interrupts for a while.

The priority system is implemented on two Intel 8259 chips (or similar). Each chip is an interrupt controller and handles up to eight priorities. Chips can be combined (cascaded) to increase the number of priority levels in the system.

Priority levels are abbreviated as IRQ0 - IRQ15.


24. Input / output management. Synchronous and asynchronous I/O.

One of the main functions of the OS is to manage all the input / output devices of the computer. The OS must send commands to devices, catch interrupts, and handle errors; it must also provide an interface between devices and the rest of the system. For development purposes, the interface should be the same for all device types (device independence). More about I/O control question 23.

Protection principles

Since the UNIX operating system from its very inception was conceived as a multi-user operating system, the problem of authorizing the access of various users to the files of the file system has always been relevant in it. By access authorization, we mean the actions of the system that allow or deny access of a given user to a given file, depending on the user's access rights and access restrictions set for the file. The access authorization scheme used in the UNIX operating system is so simple and convenient and at the same time so powerful that it has become the de facto standard of modern operating systems (which do not pretend to be systems with multi-level protection).

File Protection

As is customary in a multiuser operating system, UNIX maintains a uniform mechanism for controlling access to files and file system directories. Any process can access a certain file if and only if the access rights described with the file correspond to the capabilities of this process.

Protecting files from unauthorized access in UNIX is based on three facts. First, any process that creates a file (or directory) is associated with some system-unique user identifier (UID - User Identifier), which in the future can be interpreted as the identifier of the owner of the newly created file. Second, each process attempting to access a file has a pair of identifiers associated with it, the current user and group identifiers. Thirdly, each file uniquely corresponds to its descriptor - i-node.

The last fact is worth dwelling on in more detail. It is important to understand that filenames and files as such are not the same thing. In particular, when there are multiple hard links to the same file, multiple filenames actually represent the same file and are associated with the same i-node. Any i-node used in the file system always uniquely corresponds to one and only one file. The I-node contains quite a lot of different information (most of it is available to users through the stat and fstat system calls), and among this information there is a part that allows the file system to evaluate the access rights of a given process to a given file in the required mode.

The general protection principles are the same for all existing variants of the system: The i-node information includes the UID and GID of the current owner of the file (immediately after the file is created, the identifiers of its current owner are set to the corresponding current identifier of the creator process, but can later be changed by the chown and chgrp system calls) . In addition, the i-node of the file contains a scale that indicates what the user - its owner can do with the file, what users belonging to the same user group as the owner can do with the file, and what others can do with the file users. Small details of implementation in different versions of the system differ.

28. File access control in Windows NT. Access rights lists.

The access control system in Windows NT is characterized by a high degree of flexibility, which is achieved through a wide variety of access subjects and objects, as well as the granularity of access operations.

File Access Control

For shared resources, Windows NT uses a common object model that contains such security characteristics as the set of allowed operations, the owner identifier, and the access control list.

Objects in Windows NT are created for any resource in the event that they are or become shared - files, directories, devices, memory sections, processes. The characteristics of objects in Windows NT are divided into two parts - a general part, the composition of which does not depend on the type of object, and an individual part, determined by the type of object.
All objects are stored in tree-like hierarchical structures, the elements of which are branch objects (directories) and leaf objects (files). For file system objects, this relationship scheme is a direct reflection of the hierarchy of directories and files. For objects of other types, the hierarchical relationship scheme has its own content, for example, for processes it reflects parent-child relationships, and for devices it reflects the belonging to a certain type of device and the connection of the device with other devices, for example, a SCSI controller with disks.

Checking access rights for objects of any type is performed centrally using the Security Reference Monitor, which runs in privileged mode.

The Windows NT security system is characterized by the presence of a large number of different predefined (built-in) access subjects - both individual users and groups. So, in the system there are always such users as Adininistrator, System and Guest, as well as groups Users, Adiniiiistrators, Account Operators, Server Operators, Everyone and others. The meaning of these built-in users and groups is that they are endowed with some rights, making it easier for the administrator to create an effective access control system. When adding a new user, the administrator only has to decide which group or groups to assign this user to. Of course, the administrator can create new groups, and also add rights to built-in groups to implement their own security policy, but in many cases, built-in groups are enough.

Windows NT supports three classes of access operations that differ in the type of subjects and objects involved in these operations.

□ Permissions are a set of operations that can be defined for subjects of all types with respect to objects of any type: files, directories, printers, memory sections, etc. Permissions, in their purpose, correspond to the access rights to files and directories in QC UNIX.

□ Rights (user rights) - are defined for subjects of the group type to perform some system operations: setting the system time, archiving files, turning off the computer, etc. These operations involve a special access object - the operating system as a whole.

It is primarily rights, not permissions, that distinguish one built-in user group from another. Some rights of a built-in group are also built-in - they cannot be removed from this group. The remaining rights of the built-in group can be removed (or added from the general list of rights).

□ User abilities are defined for individual users to perform actions related to the formation of their operating environment, for example, changing the composition of the main program menu, the ability to use the Run menu item, etc. By reducing the set of capabilities (which are available to the user by default), the administrator can force the user to work with the operating environment that the administrator considers the most suitable and protects the user from possible errors.

The rights and permissions given to a group are automatically granted to its members, allowing the administrator to treat a large number of users as a unit of account information and minimize their actions.

When a user logs into the system, a so-called access token is created for him, which includes the user ID and the IDs of all groups that the user belongs to. The token also contains: a default access control list (ACL), which consists of permissions and applies to objects created by the process; list of user rights to perform system actions.

All objects, including files, streams, events, even access tokens, are provided with a security descriptor when they are created. The security descriptor contains an access control list - ACL.

File descriptor is a non-negative integer assigned by the OS to the file opened by the process.

ACL(English) Access Control List- access control list, in English pronounced "ekl") - determines who or what can access a particular object, and what operations are allowed or prohibited for this subject to perform on the object.

Access control lists are the backbone of selective access control systems. ( Wiki)

The owner of an object, usually the user who created it, has the right to selectively control access to the object and can change the object's ACL to allow or not allow others to access the object. The Windows NT built-in administrator, unlike the UNIX superuser, may not have some permissions to access an object. To implement this feature, administrator IDs and administrator group IDs can be included in an ACL, just like regular user IDs. However, the administrator still has the ability to perform any operations with any objects, since he can always become the owner of the object, and then, as the owner, get the full set of permissions. However, the administrator cannot return ownership to the previous owner of the object, so the user can always find out that the administrator worked with his file or printer.

When a process requests an operation to access an object on Windows NT, control always passes to the security monitor, which compares the user and user group IDs from the access token with the IDs stored in the object's ACL elements. Unlike UNIX, Windows NT ACL elements can contain both lists of allowed operations and lists of prohibited operations for the user.

Windows NT explicitly defines the rules by which an ACL is assigned to a newly created object. If the calling code, during object creation, explicitly sets all access rights to the newly created object, then the security system assigns that ACL to the object.

If the calling code does not provide the object with an ACL, but the object has a name, then the principle of permission inheritance applies. The security system looks at the ACL of the object directory where the name of the new object is stored. Some of the object directory ACL entries can be marked as inherited. This means that they can be assigned to new objects created in this directory.

In the case where the process has not explicitly set an ACL for the object being created and the directory object has no inherited ACL elements, the default ACL from the process's access token is used.


29. Java programming language. Java virtual machine. Java technology.

Java is an object-oriented programming language developed by Sun Microsystems. Java applications are usually compiled into special bytecode, so they can run on any Java virtual machine (JVM), regardless of computer architecture. Java programs are translated into bytecode that is executed by the Java virtual machine ( JVM) - a program that processes byte code and transmits instructions to the equipment as an interpreter, but with the difference that byte code, unlike text, is processed much faster.

The advantage of this way of executing programs is the complete independence of the bytecode from the operating system and hardware, which allows you to run Java applications on any device for which there is a corresponding virtual machine. Another important feature of Java technology is flexible security due to the fact that program execution is fully controlled by the virtual machine. Any operation that exceeds the program's set permissions (such as attempting unauthorized access to data or connecting to another computer) causes an immediate abort.

Often, the disadvantages of the concept of a virtual machine include the fact that the execution of bytecode by a virtual machine can reduce the performance of programs and algorithms implemented in the Java language.

Java Virtual Machine(abbreviated Java VM, JVM) - the Java virtual machine - the main part of the Java runtime system, the so-called Java Runtime Environment (JRE). The Java Virtual Machine interprets and executes Java bytecode previously generated from Java program source code by the Java compiler (javac). The JVM can also be used to execute programs written in other programming languages. For example, Ada source code can be compiled into Java bytecode, which can then be executed by the JVM.

The JVM is a key component of the Java platform. Because Java virtual machines are available for many hardware and software platforms, Java can be viewed as both a middleware and a standalone platform, hence the "write once, run anywhere" principle. The use of a single bytecode for many platforms allows Java to be described as "compile once, run anywhere" (compile once, run anywhere).

Runtime

Programs intended to run on the JVM must be compiled in a standardized portable binary format, which is usually represented as .class files. A program may consist of many classes placed in different files. To make it easier to host large programs, some of the .class files can be packaged together into a so-called .jar file (short for Java Archive).

The JVM executes .class or .jar files, emulating instructions written for the JVM by interpreting or using a just-in-time (JIT) compiler such as HotSpot from Sun microsystems. These days, JIT compilation is used in most JVMs in order to achieve greater speed.

Like most virtual machines, the Java Virtual Machine has a stack-oriented architecture that is common to microcontrollers and microprocessors.

The JVM, which is an instance of the JRE (Java Runtime Environment), comes into play when executing Java programs. After completion of execution, this instance is removed by the garbage collector. The JIT is a part of the Java Virtual Machine that is used to speed up the execution time of applications. JIT simultaneously compiles parts of the bytecode that have similar functionality and therefore reduces the amount of time it takes to compile.

j2se (java 2 standard edition) - The standard library includes:

GUI, NET, Database…


30. .NET platform. Basic ideas and provisions. .NET programming languages.

.NET Framework- software technology from Microsoft, designed to create conventional programs and web applications.

One of the main ideas of Microsoft .NET is the compatibility of different services written in different languages. For example, a service written in C++ for Microsoft .NET might access a class method from a library written in Delphi; in C#, you can write a class that inherits from a class written in Visual Basic .NET, and an exception thrown by a method written in C# can be caught and handled in Delphi. Each library (assembly) in .NET has information about its version, which allows you to eliminate possible conflicts between different versions of assemblies.

Applications can also be developed in a text editor and use the console compiler.

Like Java technology, the .NET development environment creates bytecode to be executed by a virtual machine. The input language of this machine in .NET is called MSIL (Microsoft Intermediate Language), or CIL (Common Intermediate Language, later), or simply IL.

The use of bytecode allows you to get cross-platform at the level of a compiled project (in .NET terms: assembly), and not only at the source level, as, for example, in C. Before starting the assembly in the CLR runtime, the bytecode is converted by the JIT compiler built into the environment (just in time, compilation on the fly) into machine codes of the target processor. It is also possible to compile an assembly into native code for the chosen platform using the NGen.exe utility supplied with the .NET Framework.

During the translation procedure, the source code of the program (written in SML, C #, Visual Basic, C ++, or any other programming language that is supported by .NET) is converted by the compiler into a so-called assembly (assembly) and saved as a dynamically linked library file (Dynamically Linked Library, DLL) or executable file (Executable, EXE).

Naturally, for each compiler (whether it is a C# language compiler, csc.exe or Visual Basic, vbc.exe), the runtime environment performs the necessary mapping of the types used to CTS types, and the program code to the code of the "abstract machine" .NET - MSIL (Microsoft Intermediate Language).

As a result, the software project is formed as an assembly - a self-sufficient component for deployment, replication and reuse. The assembly is identified by the author's digital signature and a unique version number.

Built-in programming languages ​​(shipped with .NET Framework):

C#; J#; VB.NET JScript .NET C++/CLI - new version of C++ (Managed).


31. Functional components of the OS. File Management

Functional OS components:

The functions of a standalone computer's operating system are usually grouped either according to the types of local resources that the OS manages, or according to specific tasks that apply to all resources. Sometimes such groups of functions are called subsystems. The most important resource management subsystems are the process, memory, file, and external device management subsystems, while the subsystems common to all resources are the user interface, data protection, and administration subsystems.

File management:

The ability of the OS to "shield" the complexities of real hardware is very clearly manifested in one of the main subsystems of the OS - the file system.

The file system connects a storage medium on the one hand and an API (Application Programming Interface) for accessing files on the other. When an application program accesses a file, it has no idea how the information is located in a particular file, as well as on what physical type of media (CD, hard disk, magnetic tape, or flash memory unit) it is recorded. All the program knows is the name of the file, its size and attributes. It receives this data from the file system driver. It is the file system that determines where and how the file will be written on physical media (for example, a hard disk).

From the point of view of the operating system, the entire disk is a set of clusters of 512 bytes and above. File system drivers organize clusters into files and directories (which are actually files containing a list of files in that directory). The same drivers keep track of which clusters are currently in use, which are free, and which are marked as failed.

However, the file system is not necessarily directly linked to the physical storage medium. There are virtual file systems, as well as network file systems, which are just a way to access files that are on a remote computer.

In the simplest case, all files on a given disk are stored in a single directory. This single-level scheme was used in CP/M and in the first version of MS-DOS 1.0. The hierarchical file system with nested directories first appeared in Multics, then in UNIX.

Directories on different drives can form several separate trees, as in DOS/Windows, or they can be combined into one tree common to all drives, as in UNIX-like systems.

In fact, in DOS / Windows systems, as well as in UNIX-like systems, there is one root directory with nested directories named “c:”, “d:”, etc. Hard disk partitions are mounted into these directories. That is, c:\ is just a link to file:///c:/. However, unlike UNIX-like file systems, Windows prohibits writing to the root directory, as well as viewing its contents.

In UNIX, there is only one root directory, and all other files and directories are nested in it. To access files and directories on a drive, you must mount that drive with the mount command. For example, to open files on a CD, in simple terms, you need to tell the operating system: "take the file system on this CD and show it in the /mnt/cdrom directory." All files and directories on the CD will appear in this /mnt/cdrom directory, which is called the mount point. On most UNIX-like systems, removable disks (floppies and CDs), flash drives, and other external storage devices are mounted in the /mnt, /mount, or /media directory. Unix and UNIX-like operating systems also allow you to automatically mount disks when the operating system boots.

Note the use of slashes on Windows, UNIX, and UNIX-like operating systems (Windows uses a backslash "\" while UNIX and UNIX-like operating systems use a simple slash "/")

In addition, it should be noted that the above system allows you to mount not only the file systems of physical devices, but also individual directories (the --bind parameter) or, for example, an ISO image (the loop option). Add-ons such as FUSE also allow you to mount, for example, an entire directory on FTP and a very large number of different resources.

An even more complex structure is used in NTFS and HFS. In these file systems, each file is a set of attributes. Attributes are considered not only the traditional read-only, system, but also the file name, size, and even content. Thus, for NTFS and HFS, what is stored in a file is just one of its attributes.

Following this logic, a single file can contain multiple content variations. Thus, multiple versions of the same document can be stored in one file, as well as additional data (file icon, program associated with the file). This organization is typical of HFS on the Macintosh.


32. Functional components of the OS. Process management.

Process management:

The most important part of the operating system, which directly affects the functioning of the computer, is the process control subsystem. A process (or in other words, a task) is an abstraction that describes a running program. For the operating system, a process is a unit of work, a request for the consumption of system resources.

In a multitasking (multiprocess) system, a process can be in one of three basic states:

EXECUTION - the active state of the process, during which the process has all the necessary resources and is directly executed by the processor;

WAITING - a passive state of the process, the process is blocked, it cannot be executed for its own internal reasons, it is waiting for some event to occur, for example, the completion of an I / O operation, receiving a message from another process, releasing some resource it needs;

READY - also a passive state of the process, but in this case the process is blocked due to circumstances external to it: the process has all the resources required for it, it is ready to run, but the processor is busy executing another process.

During the life cycle, each process moves from one state to another in accordance with the process scheduling algorithm implemented in this operating system.

CP/M standard

The beginning of the creation of operating systems for microcomputers was laid by OS SR / M. It was developed in 1974, after which it was installed on many 8-bit machines. Within the framework of this operating system, a significant amount of software was created, including translators from BASIC, Pascal, C, Fortran, Cobol, Lisp, Ada and many others, text. They allow you to prepare documents much faster and more conveniently than with a typewriter.

MSX Standard

This standard determined not only the OS, but also the characteristics of the hardware for school PCs. According to the MSX standard, the machine had to have at least 16 K of RAM, 32 K of permanent memory with a built-in BASIC language interpreter, a color graphic display with a resolution of 256x192 pixels and 16 colors, a three-channel sound generator for 8 octaves, a parallel port for connecting a printer and a controller for controlling an external drive connected externally.

The operating system of such a machine should have the following properties: required memory - no more than 16 K, compatibility with CP / M at the level of system calls, compatibility with DOS in terms of file formats on external drives based on floppy disks, support for translators of BASIC, C, Fortran and Lisp.

Pi - system

In the initial period of the development of personal computers, the USCD p-system operating system was created. The basis of this system was the so-called P-machine - a program that emulates a hypothetical universal computer. The P-machine simulates the operation of the processor, memory, and external devices by executing special instructions called P-code. The software components of the Pi-system (including compilers) are compiled in P-code, application programs are also compiled into P-code. Thus, the main distinguishing feature of the system was the minimal dependence on the features of the PC hardware. This is what ensured the portability of the Pi-system to various types of machines. The compactness of the P-code and the conveniently implemented paging mechanism made it possible to execute relatively large programs on a PC with a small RAM.

I/O management.

I/O devices are divided into two types: block-oriented devices and byte-oriented devices. Block-oriented devices store information in fixed-size blocks, each with its own address. The most common block-oriented device is the disk. Byte-oriented devices are not addressable and do not allow a lookup operation, they generate or consume a sequence of bytes. Examples are terminals, line printers, network adapters. The electronic component is called a device controller or adapter. The operating system deals with the controller. The controller performs simple functions, monitors and corrects errors. Each controller has several registers that are used to interact with the central processor. The OS performs I/O by writing commands to controller registers. The IBM PC floppy disk controller accepts 15 commands such as READ, WRITE, SEEK, FORMAT, etc. When the command is received, the processor leaves the controller and does other work. When the command completes, the controller organizes an interrupt in order to transfer control of the processor to the operating system, which must check the results of the operation. The processor obtains the results and status of the device by reading information from the controller's registers.

Main idea I/O software organization consists in dividing it into several levels, with the lower levels providing shielding of hardware features from the upper ones, and those providing a convenient interface for users.

key principle is device independence. The form of the program should not depend on whether it reads data from a floppy disk or from a hard disk. Another important issue for I/O software is error handling. Generally speaking, errors should be handled as close to the hardware as possible. If the controller detects a read error, it must attempt to correct it. If it fails, then the device driver should fix the errors. And only if the lower level cannot handle the error, it reports the error to the upper level.

Another key issue is the use of blocking (synchronous) and non-blocking (asynchronous) transfers. Most physical I/O operations are asynchronous - the processor starts a transfer and moves on to another job until an interrupt occurs. I/O operations must be blocking - after the READ command, the program is automatically suspended until the data enters the program buffer.

The last problem is that some devices are shared (disks: simultaneous access of several users to the disk is not a problem), while others are dedicated (printers: you cannot mix lines printed by different users).

To solve the problems posed, it is advisable to divide the I / O software into four layers (Figure 2.30):

Interrupt handling,

device drivers,

Device-independent layer of the operating system,

· Custom software layer.

The concept of hardware interrupt and its processing.

Asynchronous or external (hardware) interrupts - events that come from external sources (for example, peripheral devices) and can occur at any arbitrary moment: a signal from a timer, a network card or a disk drive, pressing keyboard keys, mouse movement; They require an instant response (processing).

Almost all I/O systems in a computer operate using interrupts. In particular, when you press keys or click the mouse, the hardware generates interrupts. In response to them, the system, respectively, reads the code of the pressed key or remembers the coordinates of the mouse cursor. Interrupts are generated by the disk controller, LAN adapter, serial ports, audio adapter, and other devices.