Brain Dump

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 to write() will succeed.
  • exceptfds: Is a system-specific, not well defined, argument. Leave as NULL.

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);
Code Snippet 1: Example of using epoll to wait until a file descriptor is ready to be read.