This chapter covers the following topics:
The I/O resources of QNX are not built into the Microkernel. Instead, the I/O services are provided by processes that may be started dynamically while the system is running. Since the QNX filesystem is optional, the pathname space isn't built into the filesystem as it is in most monolithic systems.
With QNX, the pathname space is divided into regions of authority. Any process that wishes to provide file-oriented I/O services will register a prefix with the Process Manager defining the portion of the namespace it wishes to administer (i.e. its region of authority). These prefixes make up a prefix tree that is maintained in memory on each computer running QNX.
When a process opens a file, the file's pathname is applied against the prefix tree in order to direct the open() to the appropriate I/O resource manager. For example, the character Device Manager (Dev) usually registers the prefix /dev. If a process calls open() with /dev/xxx, a prefix match of /dev will occur and the open() will be directed to Dev (its owner).
The prefix tree may contain partially overlapping regions of authority. In this case, the longest match wins. For example, suppose we have three prefixes registered:
/ | disk-based filesystem (Fsys) |
/dev | character device system (Dev) |
/dev/hd0 | raw disk volume (Fsys) |
The Filesystem Manager has registered two prefixes, one for a mounted QNX filesystem (i.e. /) and one for a block special file that represents an entire physical hard drive (i.e. /dev/hd0). The character Device Manager has registered a single prefix. The following table illustrates the longest-match rule for pathname resolution.
This pathname: | matches: | and resolves to: |
---|---|---|
/dev/con1 | /dev | Dev |
/dev/hd0 | /dev/hd0 | Fsys |
/usr/dtdodge/test | / | Fsys |
The prefix tree is managed as a list of prefixes separated by colons as follows:
prefix=pid,unit:prefix=pid,unit:prefix=pid,unit
where pid is the process ID of the I/O resource manager, and the unit number is a single character used by the manager to select one of possibly several prefixes it owns. In the above example, if Fsys were process 3 and Dev were process 5, then the system prefix tree might look like this:
/dev/hd0=3,a:/dev=5,a:/=3,e
If you want to: | Use the: |
---|---|
Display the prefix tree | prefix utility |
Obtain the prefix tree from within a C program | qnx_prefix_query() function |
QNX supports the concept of a super or network root that lets you apply a pathname against a specific node's prefix tree, which is specified by a pathname that starts with two slashes followed by a node number. This also lets you easily access files and devices that aren't in your node's normal pathname space. For example, in a typical QNX network the following paths would map as follows:
/dev/ser1 | local serial port |
//10/dev/ser1 | serial port on node 10 |
//0/dev/ser1 | local serial port |
//20/usr/dtdodge/test | file on node 20 |
Note that //0 always refers to the local node.
When a program is executed remotely, you typically want any given pathnames to be resolved within the context of your own node's pathname space. For example, this command:
//5 ls /
which executes ls on node 5, should return the same thing as:
ls /
which executes ls on your node. In both cases, / should be resolved through your node's prefix tree, not through the one on node 5. Otherwise, imagine the chaos that would result if node 5's root (/) were its local hard disk and your node's root were a hard disk local to your machine -- executing remotely would get files from a completely different filesystem!
To resolve pathnames properly, each process has associated with it a default network root that defines which node's prefix tree will be used to resolve any pathnames starting with a single slash. When a pathname starting with a single / is resolved, the default network root is prepended to it. For example, if a process had a default network root of //9, then:
/usr/home/luc
would be resolved as:
//9/usr/home/luc
which can be interpreted as "resolve via node 9's prefix tree the pathname /usr/home/luc".
The default network root is inherited by new processes when they're created, and is initialized to your local node when your system starts up. For example, let's say you're running on node 9, sitting in a shell with its default network root set to node 9 (a very typical case). If you were to run the command:
ls /
the command would inherit the default network root of //9 and you would get back the equivalent of:
ls //9/
Likewise, if you were to enter this command:
//5 ls /
you would be starting the ls command on node 5, but it would still inherit a default network root of //9, so you would again get back the equivalent of ls //9/. In both cases the pathname is resolved according to the same pathname space.
If you want to: | Use the: |
---|---|
Get your current default network root | qnx_prefix_getroot() function |
Set your default network root | qnx_prefix_setroot() function |
Run a program with a new default network root | on utility |
If you have several processes running, they may not all have the same default network root -- even if they're running on the same node. For example, one process may have inherited a default network root from its parent process elsewhere on the network or it may have had its default network root explicitly overridden by its parent process.
When passing a pathname between processes whose network roots may differ (e.g. when submitting a file to a spooler for printing), you should prepend the default network root to the pathname before passing the pathname to the recipient process. If you're sure the sending process and the recipient process have the same default network root (or if the pathname already has a leading //node/), then you may omit this step in the sending process.
We have discussed prefixes that map to an I/O resource manager. A second form of prefix, known as an alias prefix, is a simple string substitution for a matched prefix. An alias prefix is of the form:
prefix=replacement-string
For example, assume you're running on a machine that doesn't have a local filesystem (so there's no process to administer "/"). However, there's a filesystem on another node (say 10) that you wish to access as "/". You accomplish this using the following alias prefix:
/=//10/
This will cause the leading slash (/) to be mapped into the //10/ prefix. For example, /usr/dtdodge/test will be replaced with the following:
//10/usr/dtdodge/test
This new pathname will again be applied against the prefix tree, but this time the prefix tree on node 10 will be used because of the leading //10 pathname. This will resolve to the Filesystem Manager on node 10, where the open() request will be directed. With just a few characters, this alias has allowed us to access a remote filesystem as though it were local.
It's not necessary to run a local filesystem process to perform the redirection. A diskless workstation's prefix tree might look something like this:
/dev=5,a:/=//10/
With this prefix tree, pathnames under /dev will be routed to the local character device manager, while requests for other pathnames will be routed to the remote filesystem.
You can also use aliases to create special device names. For example, if a print spooler were running on node 20, you could alias a local printer pathname to it as follows:
/dev/printer=//20/dev/spool
Any request to open /dev/printer will be directed across the network to the real spooler. Likewise, if a local floppy drive didn't exist, an alias to a remote floppy on node 20 could be made as follows:
/dev/fd0=//20/dev/fd0
In both cases above, the alias redirection could be bypassed and the remote resource could be directly named as:
//20/dev/spool OR //20/dev/fd0
Pathnames need not start with a single or a double slash. In such cases, the path is considered relative to the current working directory. QNX maintains the current working directory as a character string. Relative pathnames are always converted to full network pathnames by prepending the current working directory string to the relative pathname.
Note that different behaviors result when your current working directory starts with a single slash versus starting with a network root.
If the current working directory has a leading double slash (network root), it is said to be specific and locked to the pathname space of the specified node. If instead it has a single leading slash, the default network root is prepended.
For example, this command:
cd //18/
is an example of the first (specific) form, and would lock future relative pathname evaluation to be on node 18, no matter what your default network root happens to be. Subsequently entering cd dev would put you in //18/dev.
On the other hand, this command:
cd /
would be of the second form, where the default network root would affect the relative pathname resolution. For example, if your default network root were //9, then entering cd dev would put you in //9/dev. Because the current working directory doesn't start with a node override, the default network root is prepended to create a fully specified network pathname.
This really isn't as complicated as it may seem. Typically, network roots (//node/) are not specified, and everything you do will simply work within your namespace (defined by your default network root). Most users will log in, accept the normal default network root (i.e. the namespace of their own node), and work within that environment.
In some traditional UNIX systems, the cd (change directory) command modifies the pathname given to it if that pathname contains symbolic links. As a result, the pathname of the new current working directory -- which you can display with pwd -- may differ from the one given to cd.
In QNX, however, cd doesn't modify the pathname -- aside from collapsing .. references. For example:
cd /usr/home/luc/test/../doc
would result in a current working directory of /usr/home/luc/doc, even if some of the elements in the pathname were symbolic links.
For more information about symbolic links and .. references, see Chapter 5, The Filesystem Manager.
To display a fully resolved network pathname, you can use the fullpath utility. |
Once an I/O resource has been opened, a different namespace comes into play. The open() returns an integer referred to as a file descriptor (FD) which is used to direct all further I/O requests to that manager. (Note that the Sendfd() kernel call is used within library routines to direct the request.)
The file descriptor namespace, unlike the pathname space, is completely local to each process. The manager uses the combination of a PID and FD to identify the control structure associated with the previous open() call. This structure is referred to as an open control block (OCB) and is contained within the I/O manager.
The following diagram shows an I/O manager taking some PID, FD pairs and mapping them to OCBs.
The PID and FD map to an OCB of an I/O Manager.
The OCB contains active information about the open resource. For example, the filesystem keeps the current seek point within the file here. Each open() creates a new OCB. Therefore, if a process opens the same file twice, any calls to lseek() using one FD will not affect the seek point of the other FD.
The same is true for different processes opening the same file.
The following diagram shows two processes, in which one opens the same file twice, and the other opens it once. There are no shared FDs.
Process A opens the file /tmp/file twice. Process B opens the same file once.
Several file descriptors in one or more processes can refer to the same OCB. This is accomplished by two means:
When several FDs refer to the same OCB, then any change in the state of the OCB is immediately seen by all processes that have file descriptors linked to the same OCB.
For example, if one process uses the lseek() function to change the position of the seek point, then reading or writing takes place from the new position no matter which linked file descriptor is used.
The following diagram shows two processes in which one opens a file twice, then does a dup() to get a third. The process then creates a child that inherits all open files.
A process opens a file twice, then is duplicated via dup() to get a third FD. Its child process will inherit these three file descriptors.
You can prevent a file descriptor from being inherited when you spawn() or exec() by calling the fcntl() function and setting the FD_CLOEXEC flag.