The Programmer and the Operating System. Part 2
In the first part, we started our excursion into the depths of the operating system to see what’s going on there when we want to open a file and write something there.
The request to open a file, transferring from the system call components to the filesystem components, eventually reached the hard disk driver. At this point, there’s a need to read the filesystem superblock to be able to bring it to life. The request includes the number of the block and the code of the “read” command.
Input/output devices mainly have two levels: logical and mechanical. The logical component is called the controller. Its function is to control the mechanical part and provide a relatively comfortable interface for the software.
For example, in our case, we’ll use that exact interface. It is a series of registers, which provide three main functionalities: control, data exchange, and state.
In our example, the driver has to write in the control registry the code of the command, and in the data registry, most likely, the number of the block has to be transitioned and the memory address where we want to see the result — the contents of the superblock.
To get the command to the controller we need to use one of the commands provided by the central processor: in, out, if we have port-mapped I/O, or if we can use commands to write/read in the corresponding bytes of the memory if we’re dealing with a case of memory-mapped I/O.
The controller of the external device gets the command to read and transforms it to physical impulses — a sequence of electrical voltages that control the mechanical part of the device: different engines, relays, and other devices. As a result, the hard disk drive head moves to a corresponding position and the disk starts spinning. The reading process takes place and the controlling component writes the read bytes to the specified address.
Now we need to let the central processor know that the command has successfully been implemented. For this, the hard disk controller sends an interrupt. It’s an electric impulse sent to the interrupt controller, which transfers it to the central processor literally interrupting its work. In this case, the processor has to find the interrupt handler function and call it. As you remember, the addresses of those functions are loaded to a location familiar and accessible to the processor in the first steps of the operating system load process. Now, based on the interrupt number, the processor finds the handler function and starts implementing the commands stored in it. It’s important to note that those functions are a part of the hard disk driver. So the management returned to the point where we stopped, waiting till the external device will complete the command given to it.
The later contains a list of the files and folders stored on the top level of the filesystem and the number of i-nodes corresponding to those. In our example, it, in fact, contains the contents of the /home folder.
To read it we need to send numerous requests to the hard disk driver and wait for its response. In particular, we need to read the exact i-node. There might also be a need to read the blocks linked in it. In any case, in a few steps, we’ll have the contents of /home/me folder, where we can see that it, in fact, does contain a file named “file1”.
File i-nodes contain information about the access rights to it for particular users. In this case, we need to ensure that the user, on behalf of the calling process is being executed, has read access to this file. After checking this fact in the corresponding i-node bits, the specific implementation component of the filesystem returns the management to the virtual file system reporting that there were no problems with opening the file. The virtual file system goes to the next step, it creates a file descriptor, that has the following information:
1. Which file is open — its i-node number
2. What is the purpose of opening it? In this case, only to read.
3. Where did we get in our reading? In this case, we’re at the beginning of the file.
The file descriptor fits in a specific table: under the lowest available number and that exact number is returned to the calling process. So in case of a successful call of the open() function, the calling process gets the number of the descriptor. It’s going to be used as a parameter for all the functions that have to implement actions regarding that file.
Thus, the first line of the code sample is over. During its implementation a variety of operating system components were activated, several objects were created, values were returned, interrupts were processed, requests to external devices were done, memory allocation took place and more. And this was only the first line. So much more is yet to come in the second line.
To be continued…
The text was originally featured on Vahram Martirosyan’s Medium in Armenian.
Keep in touch with HelpSystems Armenia on Facebook, Instagram or LinkedIn.