Definition of a file descriptor

Cynthia75 -  
 WhiteDeath313 -
Hello everyone,

I have a lot of trouble with the concept of a "file descriptor". I have searched for definitions on the internet, Wikipedia, etc., but it's not really clear. Can you explain to me in simple words what a file descriptor is and what its usefulness is?

Thank you :)

Configuration: Linux / Firefox 43.0

1 answer

  1. mamiemando
     
    Hello Cynthia,

    What is a file descriptor?

    A file descriptor allows you to identify a file (in the broad sense) for reading from or writing to it.

    What do I mean by a file "in the broad sense"? Everything that is referred to as a "file" in the Linux context:
    - directories (for example
    /home/toto
    )
    - data files (for example
    /home/toto/file.txt
    ),
    - network sockets,
    - symbolic links,
    - a pipe (the famous
    |
    often seen in Linux)
    - standard input (
    /dev/stdin
    ), standard output (
    /dev/stdout
    ), standard error output (
    /dev/stderr
    )
    - and many others ...

    What is it used for?

    Your program can potentially interact with any file(s) (in the broad sense), so when you want, for example, to write to a file, you will need to indicate to
    fprintf
    .

    When you started with C, you were probably told that when you want to write to a terminal, you would write for example:
    printf("hello");
    . Implicitly, write the string hello to
    stdout
    .

    Now suppose you want to write to the error output or a data file, you will need a function that allows you to specify where to write. That’s the role of the file descriptor.

    The previous instruction is equivalent to
    fprintf(stdout, "hello");
    . If we want to write to error output we would write
    fprintf(stderr, "hello");
    .

    Note that on the same principle,
    stdin
    is the file descriptor from which
    scanf
    reads (standard input), and this function is actually equivalent to
    fscanf(stdin, ...)
    .

    Why create file descriptors?

    Standard streams are directly usable and thus there is no need to prepare a file descriptor to access them. That would obviously not be the case for reading or writing any file: how could the program guess which file we're talking about? Do we want to always pass the path of this file and find it on the hard drive? This is the reason that motivates the use of a file descriptor.

    In practice, a file descriptor is an integer, and the system has been "prepared" so that when the program writes to it, it knows whom it has to work with. This preparation has a cost: it is necessary to find the file on the hard drive, verify that the program has the right to access it, etc. This is not a trivial operation, and in any case, it is not a task we want to trigger every time our program wants to read from or write to a file.

    How to manipulate file descriptors? (regular files)

    For a regular file, this is done through the function
    fopen
    . Depending on who runs the program and the rights associated with this file, the system may or may not accept to create a file descriptor, which is why the return of
    fopen
    must be checked.

    As long as this file descriptor for reading (or writing) exists, you can read (or write) to it.

    Once you have finished working with the file, you need to signal to the system when it can release it. This is the role of
    fclose
    . It is essential to properly close the file, as the effective writing of data to a file is only guaranteed when you close the file. Indeed, to optimize program execution, the system may decide to wait until it has a bit more data to write before proceeding with the write. This technical choice is due to the fact that an input/output operation is a slow operation in computing, so we try to limit the number of times it occurs. Furthermore, leaving a file descriptor open can lead to security holes.

    Thus, in C, opening a file is done as follows:

    #include <stdio.h> int main() { const char * filename = "/home/toto.txt"; int x = 7; FILE * fd = fopen(filename, "w"); if (fd) { // success // use the file fprintf(fd, "hello %d\n", x); fclose(fd); } else { // failure fprintf(stderr, "Can't read %s\n", filename); } return 0; }


    Note that
    fclose
    should only be called if the file descriptor is successfully opened. You can also imagine more complicated cases, in which you manipulate several files simultaneously. In this case, there should be as many file descriptors as there are files.

    To go further (1/2)

    To understand a concept, it is interesting to see how it materializes in other languages, especially in an object-oriented language.

    In C++, the notion of file descriptor is hidden by the notion of stream. These are objects that "wrap" those famous file descriptors and are used in exactly the same way.

    Thus, the fd
    stdin
    ,
    stderr
    ,
    stdout
    , correspond respectively to the streams
    std::cin
    ,
    std::cerr
    ,
    std::cout
    . Writing operations (
    fprintf
    ) become
    <<
    , and reading operations (
    fscanf
    ) become
    >>
    .

    To write to a file, we pass through an output stream (
    std::ostream
    ), and more specifically a
    std::ofstream
    when it is a data file. The previous program then becomes:

    #include <fstream> #include <iostream> int main() { const char * filename = "/home/toto.txt"; int x = 7; std::ofstream ofs(filename); if (ofs) { // success // use the file ofs << "hello " << x << std::endl; ofs.close(); } else { // failure std::cerr << "Can't read " << filename << std::endl; } return 0; }


    To go further (2/2)

    To create a program that works with another program via the network, we use sockets. In C, sockets are also manipulated via file descriptors. The principle remains fundamentally the same:
    1) create the socket (to retrieve a file descriptor)
    2) use the file descriptor (write to send data, read to receive it)
    3) close the socket to interrupt communication (or close it in case of a problem)

    The functions are of course a bit different but the principle remains identical. Be careful to keep in mind that depending on the transport protocol (UDP or TCP), the use of sockets differs a bit, so they shouldn’t be mixed. To understand this well, I refer you to a tutorial on sockets ;-)

    Good luck
    52
    1. Sadikoi
       
      Clear and complete response! Thank you.
      0
    2. WhiteDeath313
       
      Thank you very much for your help :)
      0