connected devices. This structure contains a pointer to the driver managing the
device, any driver-specific device data, the number of times this device is opened
and the security descriptor, identifying who can access the device.
For representing the drivers, the system uses a
DRIVER_OBJECT
structure. The
word driver can refer to either this structure, or to the code of the driver in
the form of
.sys
file, but this rarely causes confusion, since each driver has
corresponding DRIVER_OBJECT and vice versa.
For us, the most important part of the
DRIVER_OBJECT
is an array of pointers
to functions. These functions are responsible for writing to, reading from, opening
and otherwise manipulating the device managed by this driver. This array is
called the driver dispatch-table and is how the kernel communicates with the
driver. Using an analogy from C++, a driver can be thought of as a class and the
device is instance of this class.
As on most systems, before a particular file is accessed, it must be opened. On
NT this is done using the
NtCreateFile
function (corresponds to
CreateFile
in
Windows API ). The file to be opened is identified by a path – but in different format
then the usual Windows/DOS path. Suppose that the user wanted to open a file
named
C:\TEST.TXT
. The file-name gets translated to
\GLOBAL??\C:\TEST.TXT
before calling NtCreateFile in the Native API.
The
\GLOBAL??\C:
is the name of the device where the file resides and is used
by NT to find the associated
DEVICE_OBJECT
. The
GLOBAL??
prefix signifies that
the name is a system-wide driver-letter mapping. The name translation is more
complex then described, but for our purposes we only need to know how devices
are named internally in NT.
The remainder of the path is the name of the file on the device, in this case
\TEST.TXT
. Some devices, serial port for example, do not have a file-system, but
still can be opened. In that case, the path consists only of the device name, like
\GLOBAL??\COM1
. Windows does not distinguish between files and devices in any
important ways and when we speak about open file, it can mean an open device
as well.
Each time a file on a device is opened a new structure called
FILE_OBJECT
is
created, to represent the open file. The structure keeps track of current file position,
the underlying device, permitted access, locks etc. Because the
FILE_OBJECT
will
be used for any subsequent I/O operations, the driver can use it to cache data
about the file for quicker access (in its FileContext member).
Since the pointer to the
FILE_OBJECT
can not be returned to the user-space
application, an identifier called handle is created and the mapping from the handle
to the
FILE_OBJECT
is stored in the handle table of the calling process (similar to
file descriptor on Unix ).
Once the file is opened and the process gets a handle, it can use the handle to
perform all I/O operations, such as reading and writing. The operating system
can perform the I/O, because the
FILE_OBJECT
contains the file name and the
underlying device. When the program is done manipulating the file, it closes the
handle using NtClose and the associated FILE_OBJECT is destroyed.
11