Remark from MSDN about CompletionKey in CreateIoCompletionPort function:
Use the CompletionKey parameter to help your application track which
I/O operations have completed. This value is not used by
CreateIoCompletionPort for functional control; rather, it is attached
to the file handle specified in the FileHandle parameter at the time
of association with an I/O completion port. This completion key should
be unique for each file handle, and it accompanies the file handle
throughout the internal completion queuing process. It is returned in
the GetQueuedCompletionStatus function call when a completion packet
arrives. The CompletionKey parameter is also used by the
PostQueuedCompletionStatus function to queue your own special-purpose
completion packets.
The above remarks leave me a question. Why use the CompletionKey given that we can associate user context with the file handle in an extended overlapped structure like this:
typedef struct s_overlappedplus
{
OVERLAPPED ol;
int op_code;
/*we can alternatively put user context over here instead of CompletionKey*/
LPVOID user_context;
} t_overlappedplus;
and retreive through CONTAINING_RECORD macro after completion?
Cool, I'm only convinced that CompletionKey is per-handle context while the extended overlapped structure is per-I/O one. But what's the philosophy behind such design and in what circumstance can it be necessary to use CompletionKey instead of an extended overlapped structure in term of user context?
Related
I'm trying to understand Qt's serial port module and I'm not too familiar with how Qt handles asynchronous I/O. On Windows, the QSerialPort::writeData method places the data to be written in a ring buffer and then starts a single-shot QTimer to actually perform the write when its timeout signal fires:
qint64 QSerialPortPrivate::writeData(const char *data, qint64 maxSize)
{
Q_Q(QSerialPort);
writeBuffer.append(data, maxSize);
if (!writeBuffer.isEmpty() && !writeStarted) {
if (!startAsyncWriteTimer) {
startAsyncWriteTimer = new QTimer(q);
QObjectPrivate::connect(startAsyncWriteTimer, &QTimer::timeout, this, &QSerialPortPrivate::_q_startAsyncWrite);
startAsyncWriteTimer->setSingleShot(true);
}
if (!startAsyncWriteTimer->isActive())
startAsyncWriteTimer->start();
}
return maxSize;
}
The readData method doesn't use a timer in this way, instead calling ReadFileEx directly.
What does the single-shot timer accomplish versus just calling WriteFileEx?
There is a special case for a QTimer with an interval of 0: this timer will fire once control returns to the event loop. The implementation on Unix/Linux does something similar, but not using a QTimer, instead having a subclass of QSocketNotifier that will get called when the port is able to be written to. Both of these implementations mean that you will buffer the data and write it out once you get back to the main event loop.
There are two reasons that I can think of for doing this:
There is something different between the POSIX and Win32 serial APIs that require the code to be structured this way. As far as I am aware, this is not the case
What #Mike said in a comment: this will allow for data to be buffered before it is written
The buffering seems like the most likely reason for this, as doing a syscall for each piece of data that you want to write would be a rather expensive operation.
I call CreateFile with FILE_FLAG_NO_BUFFERING | FILE_FLAG_WRITE_THROUGH | FILE_FLAG_OVERLAPPED and then call many WriteFile with OVERLAPPED structures with both Offset and OffsetHigh members set to 0xFFFFFFFF to append new data to file.
Is it guaranteed that operations will be completed in same order as requested?
It seem logical for me but I see no explicit and non-ambiguous proofs of that.
Quote from https://support.microsoft.com/en-us/kb/156932 tells that operation is going to be synchronous:
On Windows NT, any write operation to a file that extends its length will be synchronous.
Great. Synchronous operation preserves order. But then:
The FILE_FLAG_NO_BUFFERING flag has the most effect on the behavior of the file system for asynchronous operation. This is the best way to guarantee that I/O requests are actually asynchronous.
Latter raised doubts in me. Could someone clarify this please?
I'm handling a non-standard modem via serial port in an overlapped manner. Besides reading from and writing to the telecommunication line, I have to check the control lines like CTS and DSR using the WaitCommEvent() function.
DWORD EvtMask;
/// (some scopes/levels ommitted)
const BOOL syncChange = WaitComEvent(hFile, &EvtMask, &overlapped);
if (!syncChange) {
assert(GetLastError() == ERROR_IO_PENDING);
/// *background activity* probably writing into EvtMask
/// until overlapped.hEvent gets signalled
}
In the (practically all) cases the function call indicates *background activity*, I have to wait on the overlapped.hEvent to happen. Since I'm also waiting for events from alternative sources (like IPC caused by user input, program termination), I use the WaitForMuiltipleObjects() function. But, if the blocking wait is finished for other reasons than control line changes, how can I stop the background activity on EvtMask? The code I'm based on, currently uses SetCommMask(hFile, 0), but I did not find a reliable reference for this being appropriate.
I also observe cases where changes to control lines are not supported properly (driver?, VM?), so I have to do a sliced wait with in-between checking.
What must be done to safely leave the scope where the variable EvtMask is declared?
The code you have is correct, and fully supported by the documentation, which clearly says:
If a process attempts to change the device handle's event mask by using the SetCommMask function while an overlapped WaitCommEvent operation is in progress, WaitCommEvent returns immediately.
I've used this fact on both "real" serial ports, and USB virtual serial port emulations, and it works reliably.
(In my particular case, I was watching for EV_TXEMPTY so that I could guarantee a minimal separation between certain transmissions on the wire)
I understand the the ReadDirectoryChangesW Function uses a buffer to store the notifications, but what does overlap mean?
I presume there is protection to stop the notification you're reading from being over-written?
It is for asynchronous operations.
A call to ReadDirectoryChangesW can be completed synchronously or asynchronously. To specify asynchronous completion, open the directory with CreateFile as shown above, but additionally specify the FILE_FLAG_OVERLAPPED attribute in the dwFlagsAndAttributes parameter. Then specify an OVERLAPPED structure when you call ReadDirectoryChangesW.
See ReadDirectoryChangesW Function remarks sections.
this argument is for asynchronous operation.
on Windows, this is called "overlapped i/o". you can find this kind of parameter, with the same way of working, on a lot of function calls related to input/output (ReadFile, WriteFile, ...). more information about overlapped i/o can be found in the MSDN.
Is it possible to use IO Completion Ports for Serial I/O? According to Windows via C/C++ it is alluded to that it is possible, and does give an example of using IOCP with physical files showing work with CreateFile, ReadFile, WriteFile, etc. However can this actually work with serial comms - has anyone got it working?
I can't find any examples of this on the web, but I cannot be the first to attempt it?
Yes, using I/O Completion Ports for Serial I/O works fine. There is some setup work needed to create a file handle for a serial port that is appropriate for IOCP. But once the setup is done, you can do asynchronous ReadFile() and WriteFile() operations just like with regular file handles and socket handles.
The setup is basically:
Open serial port with CreateFile() passing in the FILE_FLAG_OVERLAPPED value as the dwFlagsAndAttributes parameter.
Modify the serial port state as desired using GetCommState() and SetCommState(). Do this just like you would do when not using IOCP.
Use GetCommTimeouts() and SetCommTimeouts() to turn off total-timeouts for read operations, since it typically doesn't make sense to have timeouts for asynchronous operations. (You would instead explicitly call CancelIO() to cancel a read operation instead.) Turning off total-timeouts is done by setting the ReadTotalTimeoutMultiplier and ReadTotalTimeoutConstant fields of the COMMTIMEOUTS structure to zero.
Now you can use the handle with IOCP just like you would do with regular file handles and socket handles. I.e. attach the handle to a completion port using CreateIoCompletionPort(), initiate I/O operations with ReadFile() or WriteFile() using an OVERLAPPED structure, dequeue completed, failed or canceled operations from the completion port using the GetQueuedCompletionStatus() function.
Additional serial port specific events can also be retrieved asynchronously using the WaitCommEvent() function.