System call I/O (file I/O)

Keywords: C++ Back-end

File descriptor

  • By opening a FILE with the open function, you can get a structure that stores all the attributes of the FILE (similar to the FILE of standard io), but not exactly like standard io. The open of system io will store the pointer of the structure in an array, and then return the subscript of the pointer in the array, but an int value (unlike fopen, which returns a FILE *), that is, fd. Then operate the fd to operate the FILE.
  • The positions of subscripts 0, 1 and 2 of this array store stdin, stdout and stderr reserved by the system respectively
  • The file descriptor takes precedence over the one with the smallest subscript in the array within the allowable range
  • Each process has its own array

IO operation

open close

int open(const char *pathname, int flags);
int open(const char *pathname, int flags, mode_t mode);

The parameter flags must contain one of the following three: O_RDONLY, O_WRONLY, O_RDWR. In addition, 0 or more file creation options and file status options can be added to flags in the form of bitwise OR (|)

  • O_ Created when the creat file does not exist
  • O_ If a TRUNC file exists and is opened with write only or read-write, the file is truncated to a length of 0
  • O_EXCL if the file exists, an error is reported to test whether the file exists. General and o_ Use with creat, and create if it doesn't exist
  • O_APPEND append at the end of the file when writing
  • O_NONBLOCK is opened in a non blocking manner. If you need to wait, you don't queue up and open it later. Blocking opening is waiting in line until it is opened

If compared with the opening method in fopen of standard io:
r -> O_RDONLY
r+ -> O_RDWR
w -> O_WRONLY | O_CREAT | O_TRUNC
w+ -> O_RDWR | O_CREAT | O_TRUNC
a -> O_APPEND | O_CREAT
a+ -> O_RDWR | O_APPEND | O_CREAT

int close(int fd);

Return value

Open(), openat(), and create() returns a new file descriptor. If it fails, it returns - 1

read write

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

Read count contents from fd and put them into buf

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

Take count contents from buf and write them to fd. Note that the buf here is const, because the buf here takes the content, we can't change it, but read needs to change the buf

Return value

If read succeeds, it will return the number of bytes read. If it reads the end of the file, it will return 0. If it fails, it will return - 1, and set errno
write success will return the number of bytes successfully written. If 0 is returned, nothing is written in, and - 1 is returned, indicating an error.

Use sample

int fds = open(argv[1], O_RDONLY);
if (fds < 0)
{
	perror("open()");
    exit(1);
}
int fdd = open(argv[2], O_WRONLY | O_CREAT | O_TRUNC, 0600);
if (fdd < 0)
{
	close(fds);
	perror("open()");
	exit(1);
}

char buf[BUFSIZE];
int pos;
long int len = 0L, ret = 0L;
while (1)
{
	len = read(fds, buf, BUFSIZE);
    if (len < 0)
    {
    	perror("read()");
        break;
    }
    else if (len == 0)
     	break;
    //Prevent failure to write all len bytes
    pos = 0;
    while (len > 0)
    {
    	ret = write(fdd, buf + pos, len);
        if (ret < 0)
        {
        	perror("write()");
            exit(1);
        }
        pos += ret;
        len -= ret;
     }
}
close(fdd);
close(fds);

lseek

off_t lseek(int fd, off_t offset, int whence);

It is the same as fseek, but lseek returns the current position pointed by the current file content pointer. If it fails, it returns - 1

Difference between system IO and standard IO

The system IO switches from the user to the kernel state every time it is called, with high real-time performance. Standard IO has a buffer mechanism, and each input and output is actually put into the buffer. Therefore, the response speed of the system IO is fast. The standard IO is stacked first by the buffer and then transmitted at one time, so the throughput is large.

int fileno(FILE *stream);
//This function converts the FILE pointer to a FILE descriptor fd
//The operation of standard IO becomes the operation of system io

FILE *fdopen(int fd, const char *mode);
//Encapsulate a FILE descriptor opened by system IO into FILE *

Note: do not mix standard IO with system IO!

File sharing

Atomic operation

Atomic operations are inseparable operations used to solve competition and conflict

Program redirection: dup dup2

int dup(int oldfd);

dup will copy the oldfd file in the array, put it in the unused position with the smallest subscript in the available range of the array, and then return the new file descriptor. - 1 if failed

Use sample

Now I want to output to the specified file instead of the standard output (the default is the screen). The fd of standard output is 1 by default, so if close(1), turn off the file of the display, and then dup creates the fd of the file, you can copy the fd to the place with the smallest subscript, here is 1, and then realize the standard output to your own file.

int main(int argc, char *argv[])
{
    int fd = open("./test.txt", O_WRONLY | O_CREAT | O_TRUNC, 0600);
    if (fd < 0)
    {
        perror("open()");
        exit(1);
    }

    close(1); //a
    dup(fd); //b
    close(fd); //Now 1 is your own file, and fd is also your own file, so you can turn off fd

    puts("helloworld"); //It is now displayed in. / test.txt

    exit(0);
}

There are two big hidden problems here. We default that 0, 1 and 2 are occupied by stdin out err, but they are not necessarily occupied. Assuming that 1 is not occupied, the above code will have problems. If 1 itself is empty, the fd after we open the file is 1. Look at the comment a in the code, where 1 file is released, and b will copy this empty place, even close(fd)... This is the first problem
The second problem is to consider concurrency. Suppose we execute place a, and then the time slice arrives, switch to another process, and this process also creates a file to directly replace our close No. 1. Then switch back to our process. At this time, dup will not be copied to No. 1 we want. In other words, the operations of a and b in the code are not atomic.
Use dup2 to solve the above problems:

int dup2(int oldfd, int newfd);
//dup2 will first close the newfd file, and then copy the oldfd file to newfd

It can be seen that this dup2 is equivalent to the additive atomic version of close and DUP in the code, which solves the second problem. What about the first problem? In practice dup2, there are two main points:

  • If oldfd is not a valid file descriptor, then the call fails, and newfd is not closed.
  • If oldfd is a valid file descriptor, and newfd has the same value as oldfd, then dup2() does nothing, and returns newfd.

The second point can be seen that if newfd is the same as oldfd, dup2 will do nothing. This leads to new problems. In the above codes a and b, we turn off fd ourselves. If dup2 does nothing, we will release our own files... So make a judgment:

dup2(fd, 1);
if (fd != 1)
	close(fd);
puts("helloworld");

Synchronization: sync fsync fdatasync

other

fcntl ioctl

/dev/fd/

Posted by FuzziNectar on Sun, 31 Oct 2021 09:40:35 -0700