Unix/Linux Programming: a universal IO model

Posted by pabs1983 on Wed, 09 Feb 2022 03:07:21 +0100


  • All system calls that perform IO operations refer to the open file with a file descriptor, a non negative integer (usually a small integer)
  • File descriptors are used to represent all types of open files, including pipes, FIFO s, socket s, terminals, devices and ordinary files
  • For each process, the file descriptor has its own set

According to the management, most programs expect to use three standard file descriptors, as shown in the following table:

  • Before the program starts running, the shell opens these three file descriptors on behalf of the program.
  • More specifically, the program inherits a copy of the shell file descriptor - these three descriptors are always open in the daily operation of the shell (in an interactive shell, these three file descriptors usually point to the terminal where the shell runs).
  • If the command line specifies redirection of input / output, the shell will make appropriate changes to the file descriptor before starting the program

    When referring to these file descriptors in the program, you can use numbers (0, 1, 2) or < unistd h> POSIX standard name defined ----- is preferred.


File descriptor

  • For the kernel, all open files are referenced through file descriptors
    • When an existing file is opened or a new file is created, the kernel returns a file descriptor to the process
    • When the file is read / write, the file ID is passed as a read / write parameter, or the file ID is returned as a write parameter
  • The file descriptor is a nonnegative integer
    • The file descriptor 0 (STDIN_FILENO) is associated with the standard input of the process
    • File descriptor 1 (STDOUT_FILENO) is associated with the standard output of the process
    • File descriptor 2 (STDERR_FILENO) is associated with the standard error of the process



       open - Used to open a file or device

       #include <sys/types.h>
       #include <sys/stat.h>
       #include <fcntl.h>

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

describe (DESCRIPTION)
       open()Usually used to[Pathname]Convert to a[File descriptor](A nonnegative small integer, stay read, write etc.[I/O In operation]Will be used). 
             When open()Call succeeded, It returns a new file descriptor(Always take the minimum value of unused descriptors). This call creates a new open file, 
             That is, assign a new unique file descriptor, It will not be shared with any other programs running(But it can be passed fork(2)System call for sharing). 
             This new file descriptor is used in subsequent functions for open file operations.(reference resources fcntl(2)).  The read-write pointer of the file is placed in the file header

       parameter flags Yes O_RDONLY, O_WRONLY or O_RDWR (Indicates that the file is read-only , Open in write only or read-write mode) Bitwise with zero or more optional modes below -or Obtained by operation:

              If the file does not exist, a new file will be created.  Owner of the new file (user ID) Is set as a valid user of this program ID.  Similarly, the group to which the file belongs is also set as the effective group of this program ID perhaps
              Grouping of upper level directories ID (This depends on the file system type ,Loading options and mode of upper level directory, reference resources,stay mount(8) Described in ext2 Mount options for file systems bsdgroups and sysvgroups )

       O_EXCL adopt O_CREAT, Generate file , If the file already exists , be open error , Call failed . If there is a symbolic connection , The point of its join pointer to the file will be ignored.  O_EXCL is broken on NFS file sys‐
              tems,  programs which rely on it for performing locking tasks will contain a race condition.  The solution for performing atomic file locking using a lockfile is to cre‐
              ate a unique file on the same fs (e.g., incorporating hostname and pid), use link(2) to make a link to the lockfile. If link() returns 0, the lock is successful.  Other‐
              wise, use stat(2) on the unique file to check if its link count has increased to 2, in which case the lock is also successful.

              If pathname Reference a terminal device - Reference tty(4) — Even if the process has no control terminal ,This terminal will not become the control terminal of the process.


Although stdin, stdout and stderr variables are used to refer to the standard input, standard output and standard error of the process during program initialization, calling the freopen() library function can make these variables refer to any other file object. As part of its standard operation, freopen () can replace the hidden file descriptor when the stream is reopened. That is to say, after calling the freopen() function for stdout, there is no guarantee that the stdout variable value has not been changed.

Application: cp command

#include <sys/stat.h>
#include <fcntl.h>
#include <string.h>
#include <stdio.h>
#include <stdlib.h>
#include <zconf.h>

#ifndef BUF_SIZE
#define BUF_SIZE 1024

int main(int argc, char *argv[]){
    int inputFd, outputFd, openFlags;
    mode_t filePerms;
    ssize_t numRead;
    char buf[BUF_SIZE];

    if(argc != 3 || strcmp(argv[1], "--help") == 0){
        fprintf(stderr, "%s old-file new-file\n", argv[0]);

    inputFd = open(argv[1], O_RDONLY);
    if (inputFd == -1){
        fprintf(stderr, "opening file %s \n", argv[1]);

    openFlags = O_CREAT | O_WRONLY | O_TRUNC;
    filePerms = S_IRUSR | S_IWUSR | S_IRGRP | S_IWGRP | S_IROTH | S_IWOTH;
    outputFd = open(argv[2], openFlags, filePerms);
    if (outputFd == -1){
        fprintf(stderr, "opening file %s \n", argv[2]);

    while ((numRead = read(inputFd, buf, BUF_SIZE)) > 0){
        if (write(outputFd, buf, numRead) != numRead){
            fprintf(stderr, "fatal:  couldn't write whole buffer!\n");
            goto _endl;

    if(numRead == -1){
        fprintf(stderr, "read !\n");

    if(close(inputFd) == -1){
        fprintf(stderr, "close file %s \n", argv[1]);
    if(close(outputFd) == -1){
        fprintf(stderr, "close file %s \n", argv[1]);


./copy oldfile newfile

One of the most remarkable features of UNIX IO model is its universal concept of input / output. This means that using the same four system calls open(), read(), write(), and close(), you can perform IO operations on all types of files, including devices such as terminals. In other words, programs written only with system calls are valid for any type of file. in other words:

To achieve universal IO:

  • It must be ensured that the same set of IO system calls is implemented in each file system and device driver.
  • Because the operation details specific to the file system or device are handled in the kernel, device specific factors can usually be ignored in programming.
  • Once the application needs to access the proprietary functions of the file system or device, it can choose the Swiss Army knife like ioctl() system call, which provides an access interface for the proprietary features outside each general IO model.