File
- Tags
- comp-arch
Is an entry in a file-system that can contain some data.
You can create a file using the open system-call, and retrieve a file-descriptor id. You can then read, write, or otherwise interact with the file before finally closing it.
Non-Blocking File IO
Calls to read()
or write()
for a file-descriptor are blocking by default. This means
the active thread waits until the data to be read is available and the data to be
written has all been written before continuing.
This is useful when interacting with a hard-disk but its a different case when the
interacting through a slow network connection.
You can set non-blocking mode for a file-descriptor by using the fcntl syscall.
int flags = fcntl(fd, F_GETFL, 0);
fcntl(fd, F_SETFL, flags | O_NONBLOCK);
Note: You can also initialise a socket in non-blocking mode by passing the
SOCK_NONBLOCK
flag.
For a non-blocking file-descriptor any call to read()
and write()
returns
immediately. For example if you ask to read 150 bytes from a socket file
descriptor and only 100 arrived from the server already, then read returns
100 of the 150 asked for bytes. You can call read more times trying to read
the missing bytes read(fd, buf+100, 50)
and read()
will return -1 if no new
bytes have arrived yet.
Similarly when you try to write a lot of bytes to the socket it may take some
time for the kernel split it up and transmit the bytes so write()
will only return
the number of bytes that were sent immediately. Calling write()
again after this
would return -1
while there's still bytes from the previous write that haven't been
sent.
In either error case you can check errno
is EAGAIN
or EWOULDBLOCK
Availability Predicates
select
The select system-call will block the current thread (with an optional timeout) until one of 3 sets of file descriptors are ready and then return the number of ready file descriptors. It takes 4 arguments:
nfds
: The highest numbered file-descriptor in any of the following arguments.readfds
: A file-descriptor that will be ready when data can be read or we get EOF.writefds
: A file-descriptor that will be ready when a call towrite()
will succeed.exceptfds
: Is a system-specific, not well defined, argument. Leave asNULL
.
epoll
Is a non-POSIX modern and efficient way to wait for many file descriptors. See epoll.
To use epoll
you have to create an epoll
file descriptor (used only to communicate
with an epoll instance, but distinct from any specific file descriptor that is
being polled).
#define TIMEOUT -1
#define MAX_EVENTS 1
int epfd = epoll_create(1);
if (epfd == -1) {
perror("epoll_create()");
exit(1);
}
// Declare what events and file descriptor we want to monitor.
struct epoll_event event;
event.events = EPOLLOUT; // EPOLLIN is read, EPOLLOUT is write.
// Note you can assign to data.ptr to reference arbitrary data.
event.data.fd = some_fd;
if (epoll_ctl(epfd, EPOLL_CTL_ADD, some_fd, &event) != -1) {
perror("epoll_ctl()");
exit(1)
}
// Wait until the event is fired by the file descriptor.
struct epoll_event events[MAX_EVENTS];
int num_ready = epoll_wait(epfd, events, MAX_EVENTS, TIMEOUT);
if (num_ready > 0) {
for (int i=0; i < MAX_EVENTS; i++) {
printf("Ready to write to fd: %d\n", events[i]->data.fd);
}
}
// Unsubscribe a file descriptor from epoll, leaving others active.
epoll_ctl(epfd, EPOLL_CTL_DEL, mypointer->fd, NULL);
// Shutdown the epoll instance.
close(epfd);