• The first public account: Rand_cs

In LinuxLinuxLinux file system, there is a very important concept is mount, mount everyone should be familiar with, except the root file system, all other file systems must be mounted to a directory in the root file system before access.

The root file system is the first file system installed when the system boots, and it is also the file system where the kernel image resides. A directory mounted to a directory is called a mount point.

LinuxLinuxLinux has special commands for mounting file systems. Mount device dir deviceDevicedevice indicates the file name of the device to be mounted, and dirdirdir indicates the mount point. The device is not really a single physical device, but a logical device on top of it. For example, different partitions on a disk can be considered different devices.

Each device is identified by a device NUMBER, which can be divided into two parts: MajorMajorMajor NumberNumberNumber, which identifies a certain type of device, such as a disk. The other part is called MinorMinorMinor NumberNumberNumber, which identifies a specific device, such as a specific partition on a disk.

When a file system is mounted to a directory, we can access the file system through the directory, a lot of places is just a simple talk about this, but in fact this can only be said to be mounted, what is mounted, to solve this problem or can only start from the source code. The following I will be based on LinuxLinuxLinux 0.110.110.11 code to tell about mount, involving more things, we only discuss the relevant part.

The data structure

M_inode, the inode in memory

struct m_inode {
    / * * * * * * * * just * * * * * * * * * /
	unsigned short i_mode;   // File type and properties
	unsigned char i_mount;   // Whether file systems are mounted here
    unsigned short i_zone[9]; // Index, if block/character device file i_zone[0] is the device number
    / * * * * * * * * just * * * * * * * * * /
};
Copy the code

Super_block, a superblock in memory

struct super_block {
    / * * * * * * * * just * * * * * * * * * /
	unsigned short s_magic;    // File system magic
    / * * * * * * * * just * * * * * * * * * /
	unsigned short s_dev;      / / device number
    / * * * * * * * * just * * * * * * * * * /
    struct m_inode * s_isup;   // The root directory inode of the mounted file system
	struct m_inode * s_imount; // The file system is installed in the inode
};
Copy the code

The so-called in-memory superblock and inodeinodeinode refer to the in-memory cache of both:

struct super_block super_block[NR_SUPER];
#define NR_SUPER 8
struct m_inode inode_table[NR_INODE];
#define NR_INODE 32
Copy the code

It can be seen that in LinuxLinuxLinux 0.110.110.11, there are up to 8 superblocks and 32 inodeinodeinodes in memory at the same time. When the system wants to retrieve an inodeinodeinode, the system will first look for inodeinodeinode in inode_tableinode_table. If there is an inodeinodeinode, the system will return it. If not, read inodeinodeinode to inode_tableinode\_tableinode_table from the device and return.

The relevant operation

Before looking at mountmountmount, let’s look at some of the operation functions.

static struct super_block * read_super(int dev);
Copy the code

If the device does not have a superblock in the cache, it first looks for a free superblock slot and then reads the superblock from the device to the found free superblock slot. If the device’s superblock is already in the cache and the data is valid, a pointer to the superblock is returned.

struct super_block * get_super(int dev);
Copy the code

Retrieves the superblock from the superblock array based on the device number devdevdev.

void put_super(int dev);
Copy the code

To release the specified device superblock, clear the s_devs\_devs_dev field of the superblock to 0, so that the superblock slot is free.

struct m_inode * namei(const char * pathname);
Copy the code

Nameinameinamei gets the inodeinodeinode of the trailing file according to pathnamepathnamepathname. If it’s not clear, here’s an example: if the parameter path is /a/b/c, calling nameinameinamei returns the inodeinodeinode of the file CCC

mount

The implementation of the mount is quite simple, look at the code

int sys_mount(char * dev_name, char * dir_name, int rw_flag) // Mount the file system on device named dev_name to directory dir_name
{
	struct m_inode * dev_i, * dir_i;
	struct super_block * sb;
	int dev;

	if(! (dev_i=namei(dev_name)))// The device was not found
		return -ENOENT;
	dev = dev_i->i_zone[0];
	if(! S_ISBLK(dev_i->i_mode)) {// Not a block device
		iput(dev_i);
		return -EPERM;
	}
	iput(dev_i);   // Release the inode for the device
	if(! (dir_i=namei(dir_name)))// Parse the inode to get the mount point
		return -ENOENT;
	if(dir_i->i_count ! =1 || dir_i->i_num == ROOT_INO) { // If the number of references to the mount point is not equal to 1, the mount point is the root directory
		iput(dir_i);
		return -EBUSY;
	}
	if(! S_ISDIR(dir_i->i_mode)) {// Mount points are not directories
		iput(dir_i);
		return -EPERM;
	}
	if(! (sb=read_super(dev))) {// Read the superblock of the file system on the device into memory
		iput(dir_i);
		return -EBUSY;
	}
	if (sb->s_imount) {  // If the file system is already mounted
		iput(dir_i);
		return -EBUSY;
	}
	if (dir_i->i_mount) {  // If the mount point has already mounted another file system
		iput(dir_i);
		return -EPERM;
	}
	sb->s_imount=dir_i;   // Record the mount point inode to the device superblock
	dir_i->i_mount=1;     // Indicates that the file system has been mounted at the mount point
	dir_i->i_dirt=1;		/* NOTE! we don't iput(dir_i) */ // We will not release the mount point inode
	return 0;			/* we do that in umount */ // We release it when umount unmounts the file system
}
Copy the code

Its flow chart is as follows:

Above is the implementation of MountmountMount. In addition to various checks, Mountmountmount actually does two things:

  1. The superblock of the file system to be mounted reads to the superblock slot in memory
  2. Set the superblock’s sb→s_imountsb \rightarrow s\_imountsb→s_imount field to the mount point inodeinodeinode, indicating that the file system is mounted to this directory. If the i_mounti\_mounti_mount field of mount point inodeinodeinode is set to 1, file systems are mounted to the directory

A few more points:

  1. Character devices, which provide a continuous stream of data that applications can read sequentially, usually do not support random access, like the keyboard serial ports mentioned earlier. Block devices: Applications can randomly access data on the device and determine the location of data by themselves. For example, hard disks are typical block devices. Obviously file systems can only be stored on block devices.
  2. A mount point can only be a directory file to which file systems are mounted
  3. When mounting a file system, the mount point directory file can only be referenced at the current time, which means that the mount point directory is being used elsewhere at the time of mounting the file system

That’s the nature of mounting. Does it feel easy and fuzzy at the same time? Why can a file system be mounted to a directory that represents the mounted file system? So again, I’m going to try to figure out how the file system is going to find a file, in other words, the nameinameinamei function. For example, given a path /a/b, which is an absolute path, how do I get the file BBB from the original root directory?

I’ve covered this in detail in the xv6xv6xv6 file system, and LinuxLinuxLinux is similar. Here we assume that aaa files are all directory files and BBB is a normal file. Find the inode_ainode\ _ainode_A of the aaa directory file. Find the data of the AAA directory file according to the inode_ainode\ _ainode_A index field. The inode_binode\ _binode_B of the ordinary file BBB is retrieved from the directory entry named BBB and returned.

Iget (dev,nr)iget(dev, nr) Obtain the NRNRNR inodeinodeinode from device devdevdev. File system mounted on inodeinodeinode (NRNRNR);

struct m_inode * iget(int dev, int nr){
    / * * * * * * * * * just * * * * * * * * * * /
    inode = inode_table;    // start with the first element in the inode table
	while (inode < NR_INODE+inode_table) {  // Scan the inode table in memory
		if(inode->i_dev ! = dev || inode->i_num ! = nr) {// If the device numbers are inconsistent or the inode numbers are inconsistent
			inode++;   / / the next
			continue;
		}
		wait_on_inode(inode);   // Wait for the inode to unlock
		if(inode->i_dev ! = dev || inode->i_num ! = nr) {// The inode may change during the waiting process
			inode = inode_table;
			continue;
		}
		inode->i_count++;  // The inode is found and the number of references to it is increased by 1
		if (inode->i_mount) {   // If the inode has a file system mounted on it
			int i;

			for (i = 0 ; i<NR_SUPER ; i++)   // Look for the current inode superblock in the cache in memory
				if (super_block[i].s_imount==inode)  // find break
					break;
			if (i >= NR_SUPER) {  // Not found, return
				printk("Mounted inode hasn't got sb\n");
				if (empty) 
					iput(empty);
				return inode;
			}
			iput(inode);   // Release the current inode
			dev = super_block[i].s_dev;  // Reset the device id to that of the mounted file system
			nr = ROOT_INO;    // Reset the inode number to the root inode number
			inode = inode_table;   // Start again from the first element of the memory inode table to find the inode
			continue;
		}
		if (empty)  // Free the temporarily found free inode
			iput(empty);  
		return inode;   // return the obtained inode}}Copy the code

The complete flow chart is as follows:

This is my drawing based on Zhao Jiong adapted, because there is no detailed description of igetigetiget code, so mainly focus on the dotted box inside the line.

If the corresponding inodeinodeinode is found in inode_tableinode_table, Inode →i_mount==1inode \rightarrow I \_mount ==1inode →i_mount==1inode →i_mount==1inode →i_mount==1inode →i_mount==1inode Dev = device dev= device dev= device dev= device dev= device dev= device dev= device dev= device dev= device dev= device dev= device nr=1nr =1nr =1 The original directory is hidden. Just to illustrate, if I call iget(1,99) IGet (1,99) IGet (1,99), I’m supposed to get the 99th inodeinodeinode of device 1, The file system of device 2 is mounted to the directory that the inodeinodeinode points to. The file system of device 2 is mounted to the directory that inodeinodeinode points to. So it looks like calling iget(1,99) IGet (1,99)iget(1, 99)iget(1,99) is actually calling IGet (2,1) IGet (2,1)iget(2, 1)iget(2,1), which is why a file system is mounted to a directory and the directory is masked.

File system unmount is basically the reverse operation of mount, let’s have a brief look:

int sys_umount(char * dev_name)
{
	struct m_inode * inode;
	struct super_block * sb;
	int dev;

	if(! (inode=namei(dev_name)))// Parse the inode to get the device file
		return -ENOENT;
	dev = inode->i_zone[0];   // For block/character devices, the device number is recorded in i_zone[0]
	if(! S_ISBLK(inode->i_mode)) {// If not a block device
		iput(inode);
		return -ENOTBLK;
	}
	iput(inode);  // Release the device file inode
	if (dev==ROOT_DEV)  // Unmount the root file system
		return -EBUSY;
	if(! (sb=get_super(dev)) || ! (sb->s_imount))// If the device superblock is not obtained or if the mount point is empty
		return -ENOENT;
	if(! sb->s_imount->i_mount)// If the mount identifier of the mount point is empty
		printk("Mounted inode has i_mount=0\n");
    // Check whether a process is using files on the filesystem to be unmounted
	for (inode=inode_table+0 ; inode<inode_table+NR_INODE ; inode++) 
		if (inode->i_dev==dev && inode->i_count)
				return -EBUSY;
	sb->s_imount->i_mount=0;  // Set the mount identifier to 0
	iput(sb->s_imount);   // Release the mount point inode
	sb->s_imount = NULL;  // The mount point field of the superblock is set to empty
	iput(sb->s_isup);    // Release the inode root directory of the unmounted file system
	sb->s_isup = NULL;   // The root directory inode field is clear
	put_super(dev);    // Release the device superblock
	sync_dev(dev);     // Update information is synchronized to the device
	return 0;
}
Copy the code

File system unmount is basically to release the superblock and restore some of the field values, which is not detailed in the above comments.

Well in this paper, on the file system mount so much, so back to what is mounted in the beginning, but from the implementation, is the super block loaded into memory, because the superblock is a file system information collection, super block can represent a file system, so the superblock loaded into memory, we can think mount the file system. Of course, the mount mechanism is not only dependent on whether the superblock is in memory, but also needs other functions to assist, such as the igetigetiget function to fetch inodeinodeinode. This function determines whether the currently retrieved inodeinodeinode is a mount point. If so, mask the directory file to which the current inodeinodeinode points and replace it with the root directory of the mounted file system. All of this adds up to an implementation of the mount mechanism, rather than a single mountmountmount function.

OKOKOK this article is over here, if you have any questions, please also criticize, welcome to discuss with me to exchange study together progress.