QNX is a UNIX-like real-time operating system that complies with POSIX specifications and has good portability.
Writing any driver has the same problem: how the application interacts with the driver. In fact, this problem is very simple. QNX has a lot of information to illustrate this point.
This problem occurs when the client calls FD = open ("Dev/mydevice", o_rdwr) to open the device mydevice and expects to read and write data from the specified address. In fact, QNX provides a flexible message interaction mechanism, which can be divided into the following three steps:
First, load the driver, create a service thread, connect the underlying Io function to the POSIX function, register the device name in the namespace, and use the event loop or thread
The pool waits for the message to be received and enables the parent process to run in the background to load other applications.
Second, when an application calls the open () function, process manger is requested to find the resource named DEV/mydevice in the namespace.
Manger, QNX kernel library to open it, the application uses the returned handle to establish a connection with it.
Third, when the read (FD, Buf, 512) function is called, the kernel library sends a _ io_read message. In this case, the previously created event loop or thread
The pool can receive the message and call the corresponding Io function by determining the message type. For example:
Int io_read (resmgr_context_t * CTP, io_read_t * MSG resmgr_ocb_t * OCB );
In fact, we can see that the FD, Buf, and 512 parameters are passed through the io_read_t * msg parameter. In fact, the client mainly specifies a device and wants to read 512 bytes of data from a specific address of the device, and then store the data in the Buf.
The next step is to parse the parameter passed by the message in the self-implemented io_read function and provide a response. In the client-server message interaction model, the client is in the reply
The status of blocked is waiting for the server's reply. What has been done in io_read? First, verify whether the received message is a correct io_read message, and check whether the message is opened in nonblock mode. Then, parse MSG-> I. nbytes to determine how much data needs to be passed, and then call the underlying function to read hardware data, through _ io_set_read_nbytes
(CTP, MSG-> I. nbytes); To tell the client the amount of data that can be returned.
For how to reply data, QNX does provide many simple methods. Return an error using return (enomem) or return success using return (Eok. If you want to return a certain amount of data, you can set the IOV array to return,
For example, you can set IOV to return one or more arrays, for example:
Setiov (CTP-> IOV, buffer, nbytes );
Return (_ resmgr_nparts (1 ));
Or directly call the macro to return a complete Buffer:
Return (_ resmgr_ptr (CTP, buffer, nbytes ));
Write operations are similar to read operations. This completes the upper-layer data request. When the lower-layer data is read and returned, the server returns to the receive blocked status again. A careful friend may have seen how the address is set for reading and writing data? It is actually set through devctl, and its format is
Int devctl (int fd, int dcmd, void * data, size_t nbytes, int * return_info );
The most noteworthy parameter is the int dcmd parameter. This is a custom command that can be used to pass a struct pointer. For example:
Typedef struct {
Uint32_t addr_t;
Uint32_t status_r;
} My_cfg_t;
# Define my1__set_addr _ diot (_ d1__misc, 0x01, my_1__t)
Define my_mongo_t ADDR in the client application; Use the command
Devctl (FD, my1__set_addr, & ADDR, sizeof (my_1__t), null );
Complete the settings. At the underlying layer, it is relatively simple, mainly divided into obtaining data pointers, and parsing commands to obtain data.