File I/O Calls in Linux

June 7, 2021 0 Comments

File I/O Calls in Linux

File I/O Calls in Linux, The Linux Kernel supports different types of file I/O operations.

  1. Standard I/O
  2. Synchronized I/O
  3. Direct I/O
  4. Read ahead I/O
  5. Memory-mapped I/O
  6. Vectored I/O
  7. Multiplexed I/O

Standard I/O:

read:

#include <unistd.h>

ssize_t read(int fd, void *buf, size_t count);

ReadAPI invokes FS-specific write operation through VFS system call sys_read. read() attempts to read up to count bytes from file descriptor fd into the buffer starting at buf.

write:

#include <unistd.h>

ssize_t write(int fd, const void *buf, size_t count);

Write API invokes FS-specific write operation through VFS system call sys_write. write() writes up to count bytes from the buffer starting at buf to the file referred to by the file descriptor fd.

Synchronized I/O:

Opening a file in synchronized mode. Pass O_SYNC flag at the time of opening the flag so that any write operations over the file will happen synchronously.

This mode should be used if and only if the file is open for writing.

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include<stdio.h>
#include <unistd.h>

int main() {
        int fd, ret;
        char *wrbuff = "Welcome to my Blog";
        fd = open("abc.txt",O_WRONLY|O_SYNC|O_CREAT,0666);
        if(fd < 0) {
                printf("open error\n");
                return -1;
        }
        ret = write(fd, wrbuff, strlen(wrbuff));
        getchar();
        getchar();
        ret = write(fd, wrbuff, strlen(wrbuff));
        getchar();
        getchar();
        close(fd);
        return 0;
}

Direct I/O:

This mode of I/O disables the kernel I/O cache and allows the application to set up a user-mode buffer that can use as an I/O cache. while opening a file pass the O_DIRECT flag.

#include <stdlib.h>

int posix_memalign(void **memptr, size_t alignment, size_t size);

The function posix_memalign() allocates size bytes and places the address of the allocated memory in *memptr. The address of the allocated memory will be a multiple of alignment, which must be a power of two and a multiple of sizeof(void *). If the size is 0, then posix_memalign() returns either NULL or a unique pointer value that can later successfully pass to free(3).

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include <unistd.h>
#define BLOCKSIZE 512
char s[512] = "The function posix_memalign() allocates size bytes and places the address of the allocated memory in *memptr. The address of the allocated memory will be a multiple of alignment, which must be a power of two and a multiple of sizeof(void *). If the size is 0, then posix_memalign() returns either NULL or a unique pointer value that can later successfully pass to free(3) The function posix_memalign() allocates size bytes and places the address of the allocated memory in *memptr. The address of the allocated  ";
int main()
{
        void *buffer;
        posix_memalign(&buffer, BLOCKSIZE, BLOCKSIZE);
        memcpy(buffer, s, BLOCKSIZE);
        int f = open("text.txt", O_CREAT|O_TRUNC|O_WRONLY|O_DIRECT, S_IRWXU);
        write(f, buffer, BLOCKSIZE);
        close(f);
        free(buffer);
        return 0;
}

Read ahead I/O:

Read ahead means normal reading only but you said to the driver that you will read the same amount of data repeatedly from the cache.

#include <fcntl.h>

int posix_fadvise(int fd, off_t offset, off_t len, int advice);

Programs can use posix_fadvise() to announce an intention to access file data in a specific pattern in the future, thus allowing the kernel to perform appropriate optimizations.

The advice applies to a (not necessarily existent) region starting at offset and extending for len bytes (or until the end of the file if len is 0) within the file referred to by fd.

Permissible values for advice include:

POSIX_FADV_NORMAL – Indicates that the application has no advice to give about its access pattern for the specified data. If no advice is given for an open file, this is the default assumption.

POSIX_FADV_SEQUENTIAL – The application expects to access the specified data sequentially (with lower offsets read before higher ones).

POSIX_FADV_RANDOM – The specified data will be accessed in random order.

POSIX_FADV_NOREUSE – The specified data will be accessed only once.

#define _GNU_SOURCE
#include <string.h>
#include <stdlib.h>
#include <fcntl.h>
#include<stdio.h>
#include <unistd.h>

int main()
{
	char buff[512];
	int f = open("text.txt", O_RDONLY);
	posix_fadvise(f, 20,20,POSIX_FADV_RANDOM);
        read(f,buff,512);
	buff[512] ='\0';
	printf("%s \n", buff);
	close(f);
	return 0;
}

Memory-mapped I/O:

This method allows the application to directly access the buffer associated with a file without the need for system calls. mmap invokes FS-specific operations which appends a new page table entry, granting access to file buffer.

#include <sys/mman.h>

void *mmap(void *addr, size_t length, int prot, int flags, int fd, off_t offset);

int munmap(void *addr, size_t length);

Application can indicate data access pattern on a memory mapped file using madvise. madvise takes the address of I/O cache as an argument along with access pattern constant.

#include<stdio.h>
#include<sys/types.h>
#include<sys/mman.h>
#include<unistd.h>
#include<fcntl.h>
#include<string.h>
#include <sys/stat.h>

int main() {
        int fd;
        unsigned char *filedata = NULL, *tmp;
        fd = open("test.txt", O_RDWR);
        struct stat fileInfo = {0};

        if (fstat(fd, &fileInfo) == -1)
        {
                perror("Error getting the file size");
                return -1;
        }
        if (fileInfo.st_size == 0)
        {
                printf("Error: File is empty\n");
                return -1;
        }
        filedata = mmap(NULL, fileInfo.st_size, PROT_READ | PROT_WRITE, MAP_SHARED, fd, 0);
        if(!filedata) {
                perror("mmap");
                close(fd);
                return -1;
        }
        printf("%s", filedata);

        if (munmap(filedata, fileInfo.st_size) == -1)
        {
                close(fd);
                perror("munmap");
                return -1;
        }
        close(fd);
        return 0;
}

Vectored I/O:

readv:

#include <sys/uio.h>

ssize_t readv(int fd, const struct iovec *iov, int iovcnt);

The readv() system call reads iovcnt buffers from the file associated with the file descriptor fd into the buffers described by iov (“scatter input”).

The readv() system call works just like read(2) except that multiple buffers are filled.

struct iovec {
void *iov_base; /* Starting address */
size_t iov_len; /* Number of bytes to transfer */
};

writev:

#include <sys/uio.h>

ssize_t writev(int fd, const struct iovec *iov, int iovcnt);

The writev() system call writes iovcnt buffers of data described by iov to the file associated with the file descriptor fd (“gather output”).

The writev() system call works just like write(2) except that multiple buffers are written out.

#include<stdio.h>
#include<sys/types.h>
#include<sys/stat.h>
#include<fcntl.h>
#include<sys/uio.h>
#include<unistd.h>
#include<string.h>

int main() {
    int fd, fi, i;
    char *d[] = {"India.\n","Blog.\n"},a[10],b[10];
    struct iovec buf[2], buff[2];
    fd = open("buf.txt",O_CREAT|O_WRONLY|O_TRUNC,0666);
    if(fd < 0) {
        printf("open error\n");
        return -1;
    }
    for(i=0;i<2;i++){
    buf[i].iov_base = d[i];
    buf[i].iov_len = strlen(d[i])+1;
    }
    writev(fd,buf,2);
    close(fd);
    fi = open("test.txt",O_RDONLY);
    if(fi < 0) {
        printf("open error\n");
        return -1;
    }
    buff[0].iov_base = a;
    buff[0].iov_len = sizeof(a);
    buff[1].iov_base = b;
    buff[1].iov_len = sizeof(b);
    readv(fi,buff,2);
    for(i=0;i<2;i++) {
        printf("%s\n",(char *)buff[i].iov_base);
    }

    close(fi);
    return 0;
}

Multiplexed I/O:

This mode helps an application to perform IO operations on a group of file descriptors based on the readiness of the descriptor instead of a sequence of the I/O calls.

Select:

#include <sys/select.h>

int select(int nfds, fd_set *restrict readfds, fd_set *restrict writefds, fd_set *restrict exceptfds, struct timeval *restrict timeout);

void FD_CLR(int fd, fd_set *set);

int FD_ISSET(int fd, fd_set *set);

void FD_SET(int fd, fd_set *set);

void FD_ZERO(fd_set *set);

select() allows a program to monitor multiple file descriptors, waiting until one or more of the file descriptors become “ready” for some class of I/O operation (e.g., input possible). A file descriptor is considered ready if it is possible to perform a corresponding I/O operation (e.g., read(2), or a sufficiently small write(2)) without blocking.

FD_ZERO() – This macro clears (removes all file descriptors from) set.

FD_SET() – This macro adds the file descriptor fd to set.

FD_CLR() – This macro removes the file descriptor fd from set.

FD_ISSET() – macro can be used to test if a file descriptor is still present in a set.

#include <stdio.h>
#include <stdlib.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netdb.h>
#include <memory.h>
#include <errno.h>
#include <arpa/inet.h>
#include <unistd.h>

#define MAX_CLIENT    32
#define SERVER_PORT_NUM     2000 

struct test_struct{

	unsigned int a;
	unsigned int b;
};
struct res_struct{
	unsigned int c;
} ;

char buff[1024];

int monitoring_fd_set[32];

static void
init_monitor_fd_set(){

	int i = 0;
	for(; i < MAX_CLIENT; i++)
		monitoring_fd_set[i] = -1;
}

static void 
add_monitoring_fd_set(int soc_fd){

	int i = 0;
	for(; i < MAX_CLIENT; i++){

		if(monitoring_fd_set[i] != -1)
			continue;   
		monitoring_fd_set[i] = soc_fd;
		break;
	}
}

static void
remove_monitoring_fd_set(int soc_fd){

	int i = 0;
	for(; i < MAX_CLIENT; i++){

		if(monitoring_fd_set[i] != soc_fd)
			continue;

		monitoring_fd_set[i] = -1;
		break;
	}
}

static void
re_init_fds(fd_set *fd_set){

	FD_ZERO(fd_set);
	int i = 0;
	for(; i < MAX_CLIENT; i++){
		if(monitoring_fd_set[i] != -1){
			FD_SET(monitoring_fd_set[i], fd_set);
		}
	}
}

static int 
max_fd(){

	int i = 0;
	int max = -1;

	for(; i < MAX_CLIENT; i++){
		if(monitoring_fd_set[i] > max)
			max = monitoring_fd_set[i];
	}

	return max;
}

void tcp_server(){

	int master_sock_fd = 0, recv_bytes = 0, addr_len = 0, opt = 1;

	int commu_socket_fd = 0;
	fd_set read_fds;             
	struct sockaddr_in srv_addr, cli_addr;

	init_monitor_fd_set();

	if ((master_sock_fd = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP )) == -1)
	{
		printf("socket creation failed\n");
		exit(1);
	}

	srv_addr.sin_family = AF_INET;
	srv_addr.sin_port = SERVER_PORT_NUM;
	srv_addr.sin_addr.s_addr = INADDR_ANY; 
	addr_len = sizeof(struct sockaddr);


	if (bind(master_sock_fd, (struct sockaddr *)&srv_addr, sizeof(struct sockaddr)) == -1)
	{
		printf("bind failed\n");
		return;
	}

	if (listen(master_sock_fd, 5)<0)  
	{
		printf("listen failed\n");
		return;
	}

	add_monitoring_fd_set(master_sock_fd);

	while(1){

		re_init_fds(&read_fds);               

		select(max_fd() + 1, &read_fds, NULL, NULL, NULL); 

		if (FD_ISSET(master_sock_fd, &read_fds))
		{            
			commu_socket_fd = accept(master_sock_fd, (struct sockaddr *)&cli_addr, &addr_len);
			if(commu_socket_fd < 0){
				printf("accept error : errno = %d\n", errno);
				exit(0);
			}
			add_monitoring_fd_set(commu_socket_fd); 
		}
		else
		{

			int i = 0, commu_socket_fd = -1;
			for(; i < MAX_CLIENT; i++){


				if(FD_ISSET(monitoring_fd_set[i], &read_fds)){

					commu_socket_fd = monitoring_fd_set[i];

					memset(buff, 0, sizeof(buff));
					recv_bytes = recvfrom(commu_socket_fd, (char *)buff, sizeof(buff), 0, (struct sockaddr *)&cli_addr, &addr_len);
					if(recv_bytes == 0){
						close(commu_socket_fd);
						remove_monitoring_fd_set(commu_socket_fd);
						break;

					}


					struct test_struct *client_data = (struct test_struct *)buff;
					if(client_data->a == 0 && client_data->b ==0){

						close(commu_socket_fd);
						remove_monitoring_fd_set(commu_socket_fd);
						printf("Server closes client connection : %s:%u\n", inet_ntoa(cli_addr.sin_addr), ntohs(cli_addr.sin_port));
						break;
					}

					struct res_struct result;
					result.c = client_data->a + client_data->b;
					recv_bytes = sendto(commu_socket_fd, (char *)&result, sizeof(struct res_struct), 0,
							(struct sockaddr *)&cli_addr, sizeof(struct sockaddr));
				}
			}
		}

	}   
}

int
main(int argc, char **argv){
	tcp_server();
	return 0;
}

Poll:

#include <poll.h>

int poll(struct pollfd *fds, nfds_t nfds, int timeout);

poll() performs a similar task to select(2): it waits for one of a set of file descriptors to become ready to perform I/O. The Linux-specific epoll(7) API performs a similar task but offers features beyond those found in poll().

The set of file descriptors to be monitor is specified in the fds argument, which is an array of structures of the following form:

struct pollfd {
int fd; /* file descriptor */
short events; /* requested events */
short revents; /* returned events */
};

Share This: