Link to home
Start Free TrialLog in
Avatar of Maarten Bruins
Maarten Bruins

asked on

How many entries in the "open file table" are involved when it's about the default terminal file descriptors (stdin, stdout, stderr)?

File descriptor table:      Open file table
FD 0 (stdin)                ?
FD 1 (stdout)               ?
FD 2 (stderr)               ?

Open in new window


By default file descriptors 0, 1 and 2 are associated with the terminal. The keyboard input is associated with the standard input. The monitor is associated with the standard output and standard error.

The question is: Do fd 0,1,2 all refer to the same entry in the "open file table"? Or do they refer to two entries?

 
FD 0  -> entry A
FD 1    
       > entry B  
FD 2    

Open in new window


Or do they refer to three entries?

 
FD 0  -> entry A
FD 1  -> entry B
FD 2  -> entry C

Open in new window


This seems a pretty basic question, but I'm reading different things about it on the internet.

See: https://www.usna.edu/Users/cs/aviv/classes/ic221/s16/lec/21/lec.html#orgheadline6

If fd 1 and fd 2 refer to a different entry in the open file table, then this should be also the case for fd 0. So according to this website, they refer to three different entries in the open file table.
 
But now see: https://www.enseignement.polytechnique.fr/informatique/INF422/INF422_8.pdf#page=160    (page 160, example of no redirection)

There, it's like:

 
FD 0  -> entry A
FD 1    
       > entry B  
FD 2    

Open in new window


So according to that website, they refer to two different entries in the open file table.

And see: https://www.experts-exchange.com/questions/29119936/How-the-open-file-table-entries-look-like-for-stdin-stdout-stderr.html?anchorAnswerId=42694025#a42694025


the fd[0] , fd[1] & fd[2] should all point to the same central entry

According to this, they all refer to the same entry in the open file table.

I can execute the following command:

lsof | grep 'bash'

Open in new window


This i.a. prints:

bash       8789    root    0u      CHR    136,1       0t0          4 /  dev/pts/1
bash       8789    root    1u      CHR    136,1       0t0          4 /  dev/pts/1
bash       8789    root    2u      CHR    136,1       0t0          4 /  dev/pts/1

Open in new window


It's: 0u, 1u, 2u. The "u" means read/write. If it were separate entries in the open file table, then I would not expect read/write for all of them.

So what I have to believe? What/who is correct and what/who is incorrect?
Avatar of David Favor
David Favor
Flag of United States of America image

Likely a bit more complex than you imagine.

https://www.usna.edu/Users/cs/aviv/classes/ic221/s16/lec/21/lec.html provides a good decode of how the Kernel works.

And... there can be multiple interpretations of how this works exactly.

Best bet is for you to refer to your Kernel's version's source code.... If this answer is truly important...

https://stackoverflow.com/questions/44452321/how-many-inodes-open-file-table-entries-and-file-descriptors-this-program-use provides some test code to attempt making an accurate count + likely exactly how this works will vary between Kernel versions.

I'm wondering why you care...

Specifically, if there's another question behind this question...

For example, if you're building an application where you're concerned about running out of file table entries.

If this is just an academic question, compile + run the test code above on your Kernel.

If you have another question, behind your original question, post this other question also.
Avatar of Maarten Bruins
Maarten Bruins

ASKER

I was reading https://www.usna.edu/Users/cs/aviv/classes/ic221/s16/lec/21/lec.html, but as you can see in my previous post, this document is incorrect or at least incomplete about what my question is about.

And with: https://stackoverflow.com/questions/44452321/how-many-inodes-open-file-table-entries-and-file-descriptors-this-program-use 
This doesn't count the open files. It counts the different files associated with the file descriptor table. This is something different then the number of entries in the open file table associated with fd 0,1,2.

From the "u" in 0u,1u,2u it should already be possible to draw some conclusions. I think if 0 and 1 would point to two different entries in the "open file table" then probably it would be 0r,1w instead of 0u, 1u. So I need someone who really knows a bit more about it and also really understands it.

And why I care? Just because I want to understand things. That's the only reason behind it. I was reading about this subject and everytime I saw people saying different things that were in contrast with each other (see my examples in my first post).
Ok, so I have something of an answer for this one.. and basically it depends.

It is all up whatever process in managing the terminal. You may have seen Linux processes called getty (or some variant, agetty, mingetty, etc). These are the processes that are responsible for opening the terminal device, and this process then (usually) forks to the shell which (usually) forks to your process that you are running. STDIN, STDOUT and STDERR in this last process are inherited from the shell, which inherited them from the terminal manager. So it is (usually) here that these file descriptors are setup for subsequent child processes.

So as you can see, it can depend on what terminal manager that is in use that determines exactly how these are opened. Also if you are logging in via SSH, you can see from the process tree that "sshd" is the top (other than "init") and so the opening of these file descriptors would be done there, and so another possible "different" way of it being done.

Despite the above, I think the answer (at least in most cases) would be... ONE "open file table" entry per terminal. I have found some source code for agetty and mingetty that confirms this...

agetty.c (abbreviated)
...

		if (open(buf, O_RDWR|O_NOCTTY|O_NONBLOCK, 0) != 0)
			log_err(_("/dev/%s: cannot open as standard input: %m"), tty);

...

	/* set up stdout and stderr */
	if (dup(STDIN_FILENO) != 1 || dup(STDIN_FILENO) != 2)
		log_err(_("%s: dup problem: %m"), tty);

...

Open in new window

mingetty.c (abbreviated)
...

	if ((fd = open (buf, O_RDWR, 0)) < 0)
		error ("%s: cannot open tty: %s", tty, strerror (errno));

...

	/* Set up stdin/stdout/stderr. */
	if (dup2 (fd, 0) != 0 || dup2 (fd, 1) != 1 || dup2 (fd, 2) != 2)
		error ("%s: dup2(): %s", tty, strerror (errno));
	if (fd > 2)
		close (fd);

...

Open in new window



You can see that although done in slightly different ways, both of these programs open the terminal ONCE only and "dup" the other file descriptors, meaning there is only one open file table entry, but with multiple FD's pointing to it.

But hopefully you can also see that there is nothing that really stops a different implementation of this terminal manager, from executing 3 (or 2) different open calls instead of dup'ing the FD's.

As for the open file table entry's "reference count" that you have asked about (in other questions), it would be quite hard to say what value it would have at any particular point in time. Why? Well at the end of the code above, yes the ref count should be 3, but as soon as this process forks, that would become 6, and if forked to a shell, then it could fork a number of processes that will again increase the ref count. So all you could really say is that it should always be greater than or equal to 3.
I think that's indeed the correct answer, thanks! All you're saying is clear and does make sence to me.

Only one last small question about it. So usually fd 0,1,2 point to the same entry in the "open file table", Let's say this is not the case. Let's say this:

0 <- entry 1 of "open file table"
1 -> entry 2 of "open file table"
2 -> entry 2 of "open file table"

Anyway in the end they are all pointing to the same terminal-file (special character file), but fd 0 opens the terminal-file for reading and fd 1,2 for writing. So in such a case, it's about 1 file, but it's about two different connections to that file (so two different entries in the "open file table"). Now let's take a look at something like this:

bash       8789    root    0u      CHR    136,1       0t0          4 /  dev/pts/1
bash       8789    root    1u      CHR    136,1       0t0          4 /  dev/pts/1
bash       8789    root    2u      CHR    136,1       0t0          4 /  dev/pts/1

Open in new window


So we see: 0u, 1u, 2u. Can I conclude from that "u" that all three fd's refer to the same entry in the "open file table"? Probably otherwise it would be something like:

0r,1w,2w

And then we can conclude that it's about two entries in the "open file table"?

And the offset (position in file where reading or writing starts) is probably just 0 for all these entries, right? The terminal-file is a special character file, so it's more a stream of data. Probably stdin, stdout, stderr is never sent through that terminal-file at the same time so then they all can have an offset of 0. First you need input (stdin), before the output (stdout) can be generated so these steams will not interfere/conflict with each other anyway. Stdout/stderr could conflict with each other, but probably they are never sent through the terminal-file at the same time. Can I see it like that?
ASKER CERTIFIED SOLUTION
Avatar of mccarl
mccarl
Flag of Australia image

Link to home
membership
This solution is only available to members.
To access this solution, you must be a member of Experts Exchange.
Start Free Trial
Thanks a lot again! The second alinea is clear to me.

No I don't think you can conclude that just from looking at the file mode. The only thing that you could say, is that if it showed 0r,1w,2w then there must be at least 2 entries (it could also be 3). But if it just said 0u,1u,2u then it could be 1,2 or 3 entries. You just don't know, without looking at the source code as to how it opened those FD's.

I still have to think about this one. Because of this question I dived deeper into the structures of the file descriptor table, open file table et cetera. However, I got a bit confused because I saw all kind of difference modes (read, write et cetera). Is that "u" part of my file descriptor table? Or is it part of my open file table? Probably if I know that it already helps a lot with understanding it.

Probably the answer is that the "u" refers to the file descriptor table? Then I could imagine two entries in the open file table:

1. read (stdin)
2. write (stdout)
    write (stderr)

But if the offset is probably 0 and it never gets read or updated in any operations to/from the terminal. Then why there would be 3 entries for that in the open file table? The possibility of 2 I can understand (one for reading, one for writing) but if the offset is probably 0, then I don't see any reason why there could/would be 3 entries in the open file table?
Ok,now you are getting off the track of your own question ;)

What you asked was... Can I include from the output (lsof) that there is 1, 2, 3 open file entries? And I think I answered that, the answer being NO, you are probably guessing the right answer but NO nothing can be concluded from the output.

Now you are saying... I can't see the reason why there would be X number of entries. I wasn't talking about any reasons why the answer would be 1, 2 or 3 just that you can't tell which from the lsof output.

Also, you still talk in a way where you think there is 1 definitive, black and white answer. But still as we have covered before, the answer is up to the terminal manager in use. So the answer might be 1 on your system but it might be 2 or 3 on mine. Again, I'm not trying to explain why just that it is a possibility, however unlikely or unreasonable.


Oh and lastly...

Probably the answer is that the "u" refers to the file descriptor table
No, the open mode is in the open file table.
Thanks a lot mccarl! Yeah you're right, you already answered my question, so I'll mark you last post as the correct answer. Your last sentence is really useful to me. And I can accept it's just like that and I don't have to wonder why exactly it's like that.

One last check if I understand your last post the correct way. If there can be 3 possible entries in my open file table for fd 0,1,2 then the entries can look always the same except the index, right? Because then probably they have offset 0, they are linking to the same terminal-file. I'm not saying that's always like that, but that's possible right? And I think pretty often that's the case, because I think you already said that the offset is probably 0. Or is this completely wrong now what I'm saying/thinking?
If there can be 3 possible entries in my open file table for fd 0,1,2 then the entries can look always the same except the index, right?
In general, I think you are correct. Definitely, the main members of those entries will look the same, such as the "path" and that they will point to exactly the same "inode" structure, and if they are opened in the same mode and with the same flags, then obviously these should be the same. However, looking at the actually details of the struct (these entries are defined in include/linux/fs.h as "struct file") there are a number of other members, and without having a real detailed knowledge of how all this works, I wouldn't like to guess if every one of those members would be the same though.


And one other last minor point to make, we talk about all this as the "open file table" but maybe a table is not the best description. That tends to give one a picture that it resembles an array, i.e. a data structure that stores everything one after the other in an ordered fashion, and therefore meaning that each entry has an "index". Unless I am mistaken, I don't believe this is the case. Each entry in this "open file table" is just a "struct file" object who's memory has been allocated whenever each entry is first created. So these entries can be all over the allocated memory space. Maybe I'll still get a chance to draw that diagram that I have promised before which should make this clearer too.
Thanks a lot again! About the first aliea I think exactly the same. But then at least I know now that I can see it like: at least many members are the same.

And about the second alinea:

and therefore meaning that each entry has an "index"

You're saying that probably this is not the case. I think I know what you mean. Maybe I have a reason that points to the same direction. Some entries in the open file table (lsof command) do not have a fd ("pure kernel files"). In other cases you could think of:

---
FD TABLE                                                              OPEN FILE TABLE
pointer to index of open file table                   index
---

But if an open-file-table-entry doesn't have a fd, then you can not see it like that. Then you just have to see it like:

---
OPEN FILE "TABLE"
rows/members/data whatever
---

So then there is not really an index or something. Maybe in some way you can see it as a table, but probably anyway (in general) you can not see it as a table with an index. But I'm just new to this stuff, so I can be wrong by thinking like that.
Yeah, it really is getting into the details of the implementation, and it probably doesn't really matter too much but let me try an analogy that might help...

Say there is a house, this represents your entire kernel memory space, and in the house you have a number of boxes, each boxes represents an open file table entry, so in the box is instructions about which file it is pertaining to and whether you are allowed to read, write or both to the file, and also there is a number in the box which is the current offset within the file that any operations would affect.

(Therefore, as we have already discussed, you can have more than one box that is pointing to the same file... it may have a different mode and/or a different offset)

Back to the story though, these boxes are randomly placed all throughout the house. This means that there is no way that you can say any box is box number 1 or box number 2, etc. (This is what I was saying about these entries not having an index).

Now, within the house you also have a number of people of the same family, and each person represents a process. So obviously you have parents and children in this house, with is analogous to parent/child processes. Each person in the house is holding a piece of paper, with is your per-process FD table. On the paper is a number of lines with each line numbered starting from 0 (the FD number). Next to each number may or may not be a description of where in the house a box is located. And so because these pieces of paper are the per-process lists, any one list doesn't hold the locations of all the boxes.

So using this analogy, we can think about a number of things...

How does lsof build the list of all open files (i.e. how do we get the "open file table")? Ok, this is how it is done. Say you have another piece of paper that is always in a known location, say it is nailed to the front door. On it is written the location of one of the boxes, it doesn't matter which one, and it may even change over time, but it points to one box. So the lsof command looks at that location and walks over to that box. Now I didn't mention this before but along with everything else in that box, there is another piece of paper which points to the location of "another" box, so the lsof goes there too. That one has a piece of paper too pointing to another box, and so on. So lsof eventually visits all the boxes and it has everything it needs to build the lsof output. Yes, the boxes are effective in a certain order, and you could think of a certain box being the say 5th box in that order, but the difference between this and an array is that you can't ever go directly to the 5th box in that order. You have to go to the 1st, and then the 2nd, then the 3rd, etc, etc. Where as the true array of FDs on your per-process fd table, you could directly go to the 2nd, or the 5th, or the 231st entry, because you have all those locations written down on your piece of paper.

Also from this, you might now be able to see some other points that we have covered, i.e. the sharing (or not) of open file table entries. In this analogy, if some process wants to open a file, say "some_file.txt", it doesn't want to have to look through every (or at least a lot of) open file table entries to see if there is already a "box" representing that file, and the mode that we want, and offset, etc, All it does is get another new empty box, and put the correct details in it, puts a piece of paper in it linking it to the other boxes in the house, and then writes the location of this new box on the first empty line of this person's (process) fd table piece of paper, thereby taking the next available FD to represent this file.

To model some other interactions, let's say that one person gives birth to a child process, that child process simply gets a copy of it's parent's fd table piece of paper, meaning that it has the locations of all the boxes it's parents had. But both parent and child are no free to remove entries from there own table (close), copy entries around on their table (dup,dup2) or, as above, open new files that only they will have listed on their fd table (open). When a child process exits (the person leaves the house) they simply throw away their piece of paper.

Ok, reference counting isn't taken into account in the above, but it is a fairly simple thing to setup... whenever you fork a new process and copy their fd table, just increment the ref count inside each box that is pointed to in the fd table. On dup,dup2 increment the ref count inside the box pointed to by the dup'ed FD entry. And on close(FD), decrement the ref count in the box pointed to, oh and if the ref count hits 0, you can throw the box away.


Phew, that ended up being bigger than I first imagined, but hopefully it is useful ;)
Thanks a lot! Yeah I had pretty much the same thing in my head. A few comments:

In this analogy, if some process wants to open a file, say "some_file.txt", it doesn't want to have to look through every (or at least a lot of) open file table entries to see if there is already a "box" representing that file, and the mode that we want, and offset, etc, All it does is get another new empty box

That's a nice one and a new eye opener for me. Now I understand better why it's possible to have two "almost" the same entries in the open file table. Thanks! Furthermore:

But both parent and child are no free to remove entries from there own table (close)

Probably you meant "noW free" instead of "no free", because they are free to do so. So then I think we're there with this topic :).
Ah, yeah sorry, it was meant to be "now free". Glad that it helped!