2

I am reading through the source of MIT's xv6 OS. This snippet comes at the beginning of sh.c:

// Ensure that three file descriptors are open.
while((fd = open("console", O_RDWR)) >= 0){
    if(fd >= 3){
      close(fd);
      break;
    }
}

I understand that this checks to see if atleast 3 file descriptors are open (presumably for stdin, stdout and stderr) by checking if the newly allocated file descriptor is above (or same as) 3.

1) How is it possible to open the same device multiple times from the same process and expect different file descriptors?

2) To understand this, I ran a similar snippet on my host machine (x86_64 Linux 4.6.0.1). The test program repeatedly opened a text file in a loop to see if we can expect a different fd but it always produced the same file descriptor. From this, I concluded that open-ing a real file and a device (like /dev/console) somehow differs because the snippet from xv6 obviously works (tested in Qemu). What exactly is the difference?

#include <stdlib.h>
#include <fcntl.h>
#include <stdio.h>

int main(void)
{
    int fd;
    int cnt = 0;

    while ((fd = open("sample.txt", O_RDWR) > 0)) {
        if (cnt != 10) {
            cnt++;
            printf("File descriptor opened: %d\n", fd);
        } else {
            break;
        }
    }

    return 0;
}

Here's the output on running it:

$ ./a.out
File descriptor opened: 1
File descriptor opened: 1
[snip]
File descriptor opened: 1
File descriptor opened: 1

EDIT Based on one of the answers, I ran strace on the executable and found that open indeed returns multiple file descriptors but all of which are not printed, for some reason. Why would that be?

3) Somewhat unrelated, but isn't the convention of using stdio streams in fds 0-2 is just that - a convention? For example, if the initializing sequence alloted the input/output file descriptors to something else - would it somehow affect how its children do their I/O?

  • 1
    #2 doesn't sound right. Why not show the similar snippet of code? – RobertL Sep 30 '16 at 5:41
1

That's actually 3 questions. Dispose of #2 immediately because the program is incorrect:

    while ((fd = open("sample.txt", O_RDWR) > 0)) {

you probably meant

    while ((fd = open("sample.txt", O_RDWR)) > 0) {

with the improperly-placed parentheses, you are only testing if fd is greater than zero (which since file descriptors 0, 1 and 2 are open, that is probably a good assumption).

For #1: the open call (when successful) is defined to return distinct file descriptors. If the device cannot be reopened, open would return -1.

For #3: sure, that's a convention, but also in the POSIX standard. Other systems have used other conventions, including having a fourth open stream for every program.

Further reading: Using Your Aegis Environment (July 1988)
See page 6-9, which says that Apollo Domain/OS had error input and **output*

1

No, the code does not check descriptors, it actually opens them. Giving no descriptors yet. Each open will give a new file descriptor, i.e. 0,1,2,3. The code breakes when reaching fd 3 leaving 0 to 2 open.

Each file descriptor is simply a pointer to some location in some file. Therefore it is no problem to have more than one descriptor for the same file.

If your test program gives the same fd for different open calls, there is a bug in it. Please show the code.

Yes, there is a strong convention on fd 0 to 2. If some code wants to print on stdout, it actually prints on fd 1. There is no way to "map" stdout to something else.

1

1) If the system supports opening the same file from multiple processes at the same time, then why not allow one process to open at multiple times, too? Since file descriptors are inherited, you could end up with the same file twice in the same process anyway, i.e. if it's inherited once and once opened by the process itself. Checking what files the process has open and returning a reference to the earlier one would be extra work.

Also, then there's the question if different parts of the process using the same file simultaneously. Say a library uses some configuration file at the same time the main program uses it. The file descriptor is tied to the access mode and the position of the file pointer. If there was only a single copy of them, strange things would happen. Also, if the file was opened twice (to the same fd), what should happen when it's closes? There could be another layer of reference counting to decide when to really close the file, but that wouldn't help with the other problems.


2) Depends on your code. A smart language (i.e. not C), might refcount the variable holding the open file, and close it just before you reopen the file. Hard to say without seeing the code.

But a little test, opening the same file twice to the same variable in Perl results in the same FD number:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open F, "test.txt"; printf "%d\n", fileno(F)'
3 3

Running it in strace shows that the file is closed immediately before the reopen.

With two different variables we get two FD numbers:

perl -e 'open F, "test.txt"; printf "%d ", fileno(F); open G, "test.txt"; printf "%d\n", fileno(G)'
3 4

3) Technically, you could say that the standard file numbers are a convention. A convention codified in POSIX and the ISO C standard:

At program start-up, three streams shall be predefined and need not be opened explicitly: standard input (for reading conventional input), standard output (for writing conventional output), and standard error (for writing diagnostic output).

But a convention anyway in that it may be possible to run a program without them, without the kernel minding. Or it may not: reading the specification for the exec calls, it seems to be allowed for an implementation to open something for you:

If file descriptor 0, 1, or 2 would otherwise be closed after a successful call to one of the exec family of functions, implementations may open an unspecified file for the file descriptor in the new process image.

(You can of course close them in the program itself.)

If you arrange to not have them at startup, compatibility is thrown out the door:

If a standard utility or a conforming application is executed with file descriptor 0 not open for reading or with file descriptor 1 or 2 not open for writing, the environment in which the utility or application is executed shall be deemed non-conforming, and consequently the utility or application might not behave as described in this standard.

The Linux man pages put it somewhat practically:

As a general principle, no portable program, whether privileged or not, can assume that these three file descriptors will remain closed across an execve().

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.