I have a set of file descriptors that I am "watching" with select.
All of the other file descriptors are sockets; I want to introduce a new file descriptor so that I can "event driven"-ly register a change (without the need of a two way socket, which sounds a bit "wasteful").
After a bit of searching, I found the memfd_create function; which creates "anonymous files". According to the memfd_create man page:
memfd_create() creates an anonymous file and returns a file descriptor that refers to it.
So my idea is: I will write to that file descriptor, as if it were a file, and that will make select "pop". I will then read the contents of the file descriptor and follow the appropriate procedure.
However, select seems to be registering the file as if it had been read from (even when this is not the case). The file descriptor hasn't even been changed.
Here's some example code that shows this behaviour:
#include <sys/mman.h>
#include <unistd.h>
#include <sys/select.h>
#include <fcntl.h>
#include <iostream>
#define MAXMESSAGES 12
int main() {
unsigned char message_buffer[MAXMESSAGES] = {0};
int message_buffer_fd = memfd_create( "message_buffer", MFD_CLOEXEC);
// POINT A
ftruncate(message_buffer_fd, MAXMESSAGES);
fcntl(message_buffer_fd , F_ADD_SEALS, F_SEAL_SHRINK);
// write test data to the the "file"
write( message_buffer_fd, message_buffer, sizeof(message_buffer) );
lseek( message_buffer_fd, 0, SEEK_SET );
// POINT B
fd_set fdSet;
fd_set fdSetWrite;
FD_ZERO(&fdSet);
FD_SET(message_buffer_fd, &fdSet);
FD_ZERO(&fdSetWrite);
FD_SET(message_buffer_fd, &fdSetWrite);
fd_set ready_sockets;
fd_set ready_sockets_write;
while (true) {
ready_sockets = fdSet;
ready_sockets_write = fdSetWrite;
//Wait until a fd is changed
select(FD_SETSIZE, &ready_sockets, &ready_sockets_write, nullptr, nullptr);
for (int i = 0; i < FD_SETSIZE; i++) {
if (FD_ISSET(i, &ready_sockets)) {
std::cout << "I have been read from" << std::endl;
}
else if (FD_ISSET(i, &ready_sockets_write)) {
std::cout << "I have been written to" << std::endl;
}
}
}
}
Result:
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
I have been read from
(...)
I am doing this in C++, however the code is basically C.
So, how could I make it so that select is only "awaken" when the file descriptor of this "anonymous file" is actually written (following select's default behavior).
PS: I've tried several combinations of including and not including the lines From POINT A to POINT B.
From your wording and code, I think you have a misunderstanding of
select(). This function tells you about I/O operations that can be performed on a file without blocking, not about what has been done with a file. I don't generally expect I/O on a memfd file ever to block -- fail perhaps, but not block. So I expect such a file always to be ready for both reading and writing.For example, you create your file, size it, write some contents to it, and seek to its beginning. At this point
select()reports it both readable and writable, which it is. You could read back some of the data written to it or overwrite that data with new. Your program reports the first, and skips testing the second. And of course, even if the fd were associated with a file that might at times not be ready for reading and / or writing, your program does nothing to it inside theselectloop, so there is no reason for its readiness to change.You can't as such. Use a pipe instead. This is a relatively common technique for providing a means to trigger a break from
select()and similar functions. The read end of the pipe is included in the (read) selection set. Another thread can then write to the write end, making the read end readable until all the data that were written to the pipe are consumed.