In simple terms, the root file system includes a virtual root file system and a real root file system. In the initial phase of Kernel startup, first create a virtual root file system, then call do_mount to load the real file system, and switch the root file system to the real file system, which is the real file system.
one. What is the root file system?
In a traditional Windows machine directory structure, C: or D: disks may be included, and they are generally referred to as the root directory of a specific logical disk. From the perspective of the file system, each partition contains a root directory area, that is, multiple root directories exist in the system.
However, in Linux systems, the directory structure is quite different from that on Windows. There is only one root directory in the system, the path is "/", and other partitions are just a folder mounted in the root directory, such as "/proc" and "system", etc., where "/" is the root in Linux. table of Contents.
There is also a root file system concept for the corresponding root directory. We can mount a certain partition as the root directory file system. For example, in the 6410 public version, mtdblk2 is mounted as the root directory file system. The program can be specified by U-Boot to specify parameters or compile options for Kernel. For example, the current development board uses the following compile options to make the root file system:
CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2"
Simply put, the root directory file system is a directory structure that includes some of the directory structure and important files necessary for Linux to boot.
There are two types of root file systems, one is the virtual root file system, and the other is the real root file system. In general, you will do some work in the virtual root file system first, and then switch to the real root file system.
In general terms, the virtual root file system includes three types, namely Initramfs, cpio-initrd, and image-initrd.
two. Related important concepts
Initrd
Initrd is a technology commonly used in Linux, which is a memory disk loaded by the bootloader. In the process of system startup, the "some file" in Initrd is executed first to complete the task of loading the module, and the second stage will execute /sbin/init in the real root file system. The first phase mentioned here is for the second phase, mainly for the driver of the root file system and the root file system storage medium.
Note: It is called "a file" because the name of the file here is different depending on the version of the operating system.
As mentioned in the data, there are many types of Initrd, including No Initrd, Linux Kernel and Initrd packaging, Linux Kernel and Initrd separation, and RAMDisk Initrd.
At present, the fourth strategy is adopted in the project. At system startup, U-Boot will load the Linux Kernel and Rootfs into memory and jump to the Linux Kernel entry address to execute the program. This article will focus on the analysis of this situation.
three. Root file system loading code analysis
1. Registration of VFS
First of all, I have to start from the function start_kernel() of the old Linux system. The function start_kernel() will call vfs_caches_init() to initialize the VFS.
Let's take a look at the code for the function vfs_caches_init ():
Void __init vfs_caches_init(unsigned long mempages)
{
Unsigned long reserve;
/* Base hash sizes on available memory, with a reserve equal to
150% of current kernel size */
Reserve = min((mempages - nr_free_pages()) * 3/2, mempages - 1);
Mempages -= reserve;
Names_cachep = kmem_cache_create("names_cache", PATH_MAX, 0,
SLAB_HWCACHE_ALIGN|SLAB_PANIC, NULL);
Dcache_init();
Inode_init();
Files_init(mempages);
[1] mnt_init();
Bdev_cache_init();
Chrdev_init();
}
Code [1]: The most important function in vfs_caches_init(). The function mnt_init() creates a rootfs, which is a virtual rootfs, the memory file system, which will later point to the real file system.
Next look at the function mnt_init():
Void __init mnt_init(void)
{
Unsigned u;
Int err;
Init_rwsem(&namespace_sem);
Mnt_cache = kmem_cache_create("mnt_cache", sizeof(struct vfsmount),
0, SLAB_HWCACHE_ALIGN | SLAB_PANIC, NULL);
Mount_hashtable = (struct list_head *)__get_free_page(GFP_ATOMIC);
If (!mount_hashtable)
Panic("Failed to allocate mount hash table/n");
Printk("Mount-cache hash table entries: %lu/n", HASH_SIZE);
For (u = 0; u < HASH_SIZE; u++)
INIT_LIST_HEAD(&mount_hashtable[u]);
Err = sysfs_init();
If (err)
Printk(KERN_WARNING "%s: sysfs_init error: %d/n",
__func__, err);
Fs_kobj = kobject_create_and_add("fs", NULL);
If (!fs_kobj)
Printk(KERN_WARNING "%s: kobj create error/n", __func__);
[1] init_rootfs();
[2] init_mount_tree();
}
Code [1]: Create a virtual root file system;
Code [2]: Register the root file system.
Next, take a look at the code for the function init_mount_tree():
Static void __init init_mount_tree(void)
{
Struct vfsmount *mnt;
Struct mnt_namespace *ns;
Struct path root;
[1] mnt = do_kern_mount("rootfs", 0, "rootfs", NULL);
If (IS_ERR(mnt))
Panic("Can't create rootfs");
Ns = kmalloc(sizeof(*ns), GFP_KERNEL);
If (!ns)
Panic("Can't allocate initial namespace");
Atomic_set(&ns->count, 1);
INIT_LIST_HEAD(&ns->list);
Init_waitqueue_head(&ns->poll);
Ns->event = 0;
List_add(&mnt->mnt_list, &ns->list);
Ns->root = mnt;
Mnt->mnt_ns = ns;
Init_task.nsproxy->mnt_ns = ns;
Get_mnt_ns(ns);
Root.mnt = ns->root;
Root.dentry = ns->root->mnt_root;
Set_fs_pwd(current->fs, &root);
[2] set_fs_root(current->fs, &root);
}
Code [1]: Create a virtual file system;
Code [2]: Configure the current file system as the root file system.
Some people may ask, why not directly configure the real file system as the root file system?
The answer is simple. There are no device drivers for the root file system in the kernel, such as USB device drivers that store the root file system, and even if you compile the device drivers of the root file system into the kernel, they are not yet loaded. In fact, all The Driver is loaded by the Kernel_Init thread at the back. So CPIO Initrd, Initrd and RAMDisk Initrd are required. In addition, our Root devices are specified in the form of device files. If there is no root file system, how can device files exist?
2. Mounting of VFS
Next, Kernel_Start will call rest_init() and will create the first process Kernel_Init in the system, and call the initialization function of all modules, where the ROOTFS initialization function is also called during this period.
The function rest_init code is as follows:
/*
* We need to finalize in a non-__init function or else race conditions
* between the root thread and the init thread may cause start_kernel to
* be reaped by free_initmem before the root thread has proceeded to
* cpu_idle.
*
* gcc-3.4 accidentally inlines this function, so use noinline.
*/
Static noinline void __init_refok rest_init(void)
__releases(kernel_lock)
{
Int pid;
Kernel_thread(kernel_init, NULL, CLONE_FS | CLONE_SIGHAND);
Numa_default_policy();
Pid = kernel_thread(kthreadd, NULL, CLONE_FS | CLONE_FILES);
Kthreadd_task = find_task_by_pid_ns(pid, &init_pid_ns);
Unlock_kernel();
/*
* The boot idle thread must execute schedule()
* at least once to get things moving:
*/
Init_idle_bootup_task(current);
Rcu_scheduler_starting();
Preempt_enable_no_resched();
Schedule();
Preempt_disable();
/* Call into cpu_idle with preempt disabled */
Cpu_idle();
}
The function Kernel_Init code is as follows:
Static int __init kernel_init(void * unused)
{
Lock_kernel();
/*
* init can run on any cpu.
*/
Set_cpus_allowed_ptr(current, CPU_MASK_ALL_PTR);
/*
* Tell the world that we're going to be the grim
* reaper of innocent orphaned children.
*
* We don't want people to have to
* assumptions about where in the task array this
* can be found.
*/
Init_pid_ns.child_reaper = current;
Cad_pid = task_pid(current);
Smp_prepare_cpus(setup_max_cpus);
Do_pre_smp_initcalls();
Start_boot_trace();
Smp_init();
Sched_init_smp();
Cpuset_init_smp();
[1] do_basic_setup();
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
[2] if (!ramdisk_execute_command)
Ramdisk_execute_command = "/init";
[3] if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
Ramdisk_execute_command = NULL;
Prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
Init_post();
Return 0;
}
Code [1]: The function do_basic_setup() calls the initialization functions of all modules, including the initramfs initialization function populate_rootfs. This part of the code is under init/initramfs.c, and the function populate_rootfs is exported as follows:
Rootfs_initcall(populate_rootfs);
Code [2]: The ramdisk_execute_command value is specified by "rdinit=", and if not specified, the default value /init is used.
Code [3]: Check if the file ramdisk_execute_command exists in the root file system, if it exists, execute init_post(), otherwise execute prepare_namespace() to mount the root file system.
It is important to note that the entry function populate_rootfs() of the initramfs.c module depends on Kernel's compile options. Refer to the makefile in the linux/init directory as follows:
#
# Makefile for the linux kernel.
#
Obj-y := main.o version.o mounts.o
Ifneq ($(CONFIG_BLK_DEV_INITRD), y)
Obj-y += noinitramfs.o
Else
Obj-$(CONFIG_BLK_DEV_INITRD) += initramfs.o
Endif
Obj-$(CONFIG_GENERIC_CALIBRATE_DELAY) += calibrate.o
Mounts-y := do_mounts.o
Mounts-$(CONFIG_BLK_DEV_RAM) += do_mounts_rd.o
Mounts-$(CONFIG_BLK_DEV_INITRD) += do_mounts_initrd.o
Mounts-$(CONFIG_BLK_DEV_MD) += do_mounts_md.o
Mainly complete the inspection work of Initrd, check whether CPIO Initrd or Initramfs or Image-Initrd still need to be configured as follows (General setupà Initramfs/initrd support):
The code for this function is as follows:
Static int __init populate_rootfs(void)
{
[1] char *err = unpack_to_rootfs(__initramfs_start,
__initramfs_end - __initramfs_start, 0);
If (err)
Panic(err);
[2] if (initrd_start) {
#ifdef CONFIG_BLK_DEV_RAM
Int fd;
Printk(KERN_INFO "checking if image is initramfs...");
[3] err = unpack_to_rootfs((char *)initrd_start,
Initrd_end - initrd_start, 1);
If (!err) {
Printk(" it is/n");
Unpack_to_rootfs((char *)initrd_start,
Initrd_end - initrd_start, 0);
Free_initrd();
Return 0;
}
Printk("it isn't (%s); looks like an initrd/n", err);
[4] fd = sys_open("/initrd.image", O_WRONLY|O_CREAT, 0700);
If (fd >= 0) {
[5] sys_write(fd, (char *)initrd_start,
Initrd_end - initrd_start);
Sys_close(fd);
[6] free_initrd();
}
#else
Printk(KERN_INFO "Unpacking initramfs...");
[7] err = unpack_to_rootfs((char *)initrd_start,
Initrd_end - initrd_start, 0);
If (err)
Panic(err);
Printk(" done/n");
Free_initrd();
#endif
}
Return 0;
}
Code [1]: unpack_to_rootfs, as its name suggests, is the decompression package to rootfs, which has two functions, one is to detect whether it belongs to the cpio package, the other is to decompress the cpio package, and control through the last parameter. 1: detection, 0: decompression. In fact, Initramfs is also a compressed CPIO file.
The data mentioned that the introduction of initramfs in Linux2.5 must exist in Linux2.6, and it will be compiled into __initramfs_start~__initramfs_end by the connection script arch/arm/kernel/vmlinux.lds after compiling, after executing unpack_to_rootfs Will be copied to the root directory.
Code [2]: Determine if Initrd is loaded, regardless of the format of Initrd, ie CPIO-Initrd or Image-Initrd, U-Boot will copy it to initrd_start. Of course, if it is initramfs, the value is definitely empty.
Code [3]: Determine if the loaded is CPIO-Initrd.
Through the main detection here, if it is compiled to the Linux Kernel CPIO Initrd, __initramfs_end - __initramfs_start should be greater than zero, otherwise zero, in fact, it is here to determine whether it is CPIO Initrd.
Code [4]: ​​If it is not CPIO-Initrd, it is Image-Initrd, save its contents to the file /initrd.image. Create the file /initrd.image in the root file system.
Code [5]: Here is the support for Image-Initrd, the initrd in memory is assigned to initrd.image to free up memory space.
Code [6]: Free up the memory space occupied by Initrd.
In addition, if you want to support Image-Initrd, you must configure CONFIG_BLK_DEV_RAM, the configuration method has been mentioned above.
Next, let's analyze the function kernel_init
Static int __init kernel_init(void * unused)
{
...
Do_basic_setup();
/*
* check if there is an early userspace init. If yes, let it do all
* the work
*/
If (!ramdisk_execute_command)
Ramdisk_execute_command = "/init";
[1] if (sys_access((const char __user *) ramdisk_execute_command, 0) != 0) {
Ramdisk_execute_command = NULL;
Prepare_namespace();
}
/*
* Ok, we have completed the initial bootup, and
* we're essentially up and running. Get rid of the
* initmem segments and start the user-mode stuff..
*/
Init_post();
Return 0;
}
Code [1]: I already knew when analyzing the function populate_rootfs. For initramfs and cpio-initrd, the file system (actually a VFS) is decompressed to the root file system. If the file specified by ramdisk_execute_command exists in the virtual file system, it will directly go to init_post() to execute, otherwise the function prepare_namespace() will be executed.
3. Mounting of the root file system
From the above code analysis, it is known that for the case where the file ramdisk_execute_command does not exist in Image-Initrd or VFS (ie, InitRamfs or CPIO-Initrd), prepare_namespace() is executed.
Next look at the code of the function prepare_namespace():
/*
* Prepare the namespace - decide what/where to mount, load ramdisks, etc.
*/
Void __init prepare_namespace(void)
{
Int is_floppy;
[1] if (root_delay) {
Printk(KERN_INFO "Waiting %dsec before mounting root device.../n",
Root_delay);
Ssleep(root_delay);
}
/*
* wait for the known devices to complete their probing
*
* Note: this is a potential source of long boot delays.
* For example, it is not atypical to wait 5 seconds here
* for the touchpad of a laptop to initialize.
*/
[2] wait_for_device_probe();
Md_run_setup();
[3] if (saved_root_name[0]) {
Root_device_name = saved_root_name;
If (!strncmp(root_device_name, "mtd", 3) ||
!strncmp(root_device_name, "ubi", 3)) {
[4] mount_block_root(root_device_name, root_mountflags);
Goto out;
}
[5] ROOT_DEV = name_to_dev_t(root_device_name);
If (strncmp(root_device_name, "/dev/", 5) == 0)
Root_device_name += 5;
}
[6] if (initrd_load())
Goto out;
[7] /* wait for any asynchronous scanning to complete */
If ((ROOT_DEV == 0) && root_wait) {
Printk(KERN_INFO "Waiting for root device %s.../n",
Saved_root_name);
While (driver_probe_done() != 0 ||
(ROOT_DEV = name_to_dev_t(saved_root_name)) == 0)
Msleep(100);
Async_synchronize_full();
}
Is_floppy = MAJOR(ROOT_DEV) == FLOPPY_MAJOR;
If (is_floppy && rd_doload && rd_load_disk(0))
ROOT_DEV = Root_RAM0;
Mount_root();
Out:
[9] sys_mount(".", "/", NULL, MS_MOVE, NULL);
[10] sys_chroot(".");
}
Code [1]: As mentioned in the data, Kernel needs to wait for these long-time device drivers to load when the root file system is stored on a USB or SCSI device, so there is a Delay.
Code [2]: From the literal point of view, here is also to wait for the completion of the device probe function where the root file system is located.
Code [3]: The parameter saved_root_name stores the device file specified by the Kernel parameter root=, which is not mentioned here. You can refer to the code.
Code [4]: ​​According to the explanation in the data, this is equivalent to loading the device specified by saved_root_nam. The command line passed to the kernel as follows:
CONFIG_CMDLINE="console=ttyS0,115200 mem=108M rdinit=/linuxrc root=/dev/mtdblock2"
In fact, it is loading /dev/mtdblock2.
Code [5]: The parameter ROOT_DEV stores the device node number.
Code [6]: Mounting initrd, the operation here is quite complicated, you can refer to the detailed explanation of the function.
Code [7]: If mount_initrd is specified as true, that is, no mount is specified in the function initrd_load, then the realfs mount operation is re-created here.
Code [9]: Move the mount point from the current directory (the actual current directory is specified in mount_root or in mount_block_root) to the root directory. For the command line above, the current directory is /dev/mtdblock2.
Code [10]: The current directory is treated as the root directory of the system, and the virtual system root file system is switched to the actual root file system.
Next, take a look at the code for the function initrd_load():
Int __init initrd_load(void)
{
[1] if (mount_initrd) {
[2] create_dev("/dev/ram", Root_RAM0);
/*
* Load the initrd data into /dev/ram0. Execute it as initrd
* unless /dev/ram0 is supposed to be our actual root device,
* in that case the ram disk is just set up here, and gets
* mounted in the normal path.
*/
[3] if (rd_load_image("/initrd.image") && ROOT_DEV != Root_RAM0) {
Sys_unlink("/initrd.image");
[4] handle_initrd();
Return 1;
}
}
Sys_unlink("/initrd.image");
Return 0;
}
Code [1]: The value of mount_initrd can be configured by Kernel parameter "noinitrd". The default value is 1. It is rare to see that there is a project area to configure this value, so in general, the value of mount_initrd should be 1.
Code [2]: Create a device node /dev/ram of Root_RAM0;
Code [3]: If the root file device number is not Root_RAM0, the program will execute the code [4]. In other words, the parameter specified for the kernel is not /dev/ram. For example, the /dev/mtdblock2 device node specified above must be Not Root_RAM0.
In addition, this line of code also releases the file initrd.image to the node /dev/ram0, which is the operation corresponding to image-initrd.
Code [4]: ​​The main function of the function handle_initrd is to execute the linuxrc file in Initrd and set the root directory of realfs to the current directory. In fact, as mentioned earlier, these operations will only be performed in the case of image-cpio.
The code for the function handle_initrd is as follows:
Static void __init handle_initrd(void)
{
Int error;
Int pid;
[1] real_root_dev = new_encode_dev(ROOT_DEV);
[2] create_dev("/dev/root.old", Root_RAM0);
/* mount initrd on rootfs' /root */
Mount_block_root("/dev/root.old", root_mountflags & ~MS_RDONLY);
[3] sys_mkdir("/old", 0700);
Root_fd = sys_open("/", 0, 0);
Old_fd = sys_open("/old", 0, 0);
/* move initrd over / and chdir/chroot in initrd root */
[4] sys_chdir("/root");
Sys_mount(".", "/", NULL, MS_MOVE, NULL);
Sys_chroot(".");
/*
* In case that a resume from disk is carried out by linuxrc or one of
* its children, we need to tell the freezer not to wait for us.
*/
Current->flags |= PF_FREEZER_SKIP;
[5] pid = kernel_thread(do_linuxrc, "/linuxrc", SIGCHLD);
If (pid > 0)
While (pid != sys_wait4(-1, NULL, 0, NULL))
Yield();
Current->flags &= ~PF_FREEZER_SKIP;
/* move initrd to rootfs' /old */
Sys_fchdir(old_fd);
Sys_mount("/", ".", NULL, MS_MOVE, NULL);
/* switch root and cwd back to /of rootfs */
[6] sys_fchdir(root_fd);
Sys_chroot(".");
Sys_close(old_fd);
Sys_close(root_fd);
[7] if (new_decode_dev(real_root_dev) == Root_RAM0) {
Sys_chdir("/old");
Return;
}
[8] ROOT_DEV = new_decode_dev(real_root_dev);
Mount_root();
[9] printk(KERN_NOTICE "Trying to move old root to /initrd ... ");
Error = sys_mount("/old", "/root/initrd", NULL, MS_MOVE, NULL);
If (!error)
Printk("okay/n");
Else {
Int fd = sys_open("/dev/root.old", O_RDWR, 0);
If (error == -ENOENT)
Printk("/initrd does not exist. Ignored./n");
Else
Printk("failed/n");
Printk(KERN_NOTICE "Unmounting old root/n");
Sys_umount("/old", MNT_DETACH);
Printk(KERN_NOTICE "Trying to free ramdisk memory ... ");
If (fd < 0) {
Error = fd;
} else {
Error = sys_ioctl(fd, BLKFLSBUF, 0);
Sys_close(fd);
}
Printk(!error ? "okay/n" : "failed/n");
}
}
Code [1]: real_root_dev is a global variable used to store the device number of realfs.
Code [2]: Call mount_block_root to load realfs into /FS under VFS.
Code [3]: Extract the root file descriptor of rootfs and save it to root_fd. The use of the data mentioned in the data is after the subsequent call to sys_chroot to the file system of initrd, after processing the init request, you can also switch back to rootfs again. This can be seen in an IBM official execution flow chart for cpio-initrd and image-initrd, as follows:
Code [4]: ​​sys_chroot to the initrd file system, the initrd has been mounted to the root directory of the VFS;
Code [5]: Execute linuxrc in initrd and wait for execution to end;
Code [6]: After the execution of initrd, cut back to rootfs, do not know why cut directly with the node?
Code [7]: If real_root_dev is directly configured as Root_RAM0, that is, directly use initrd as realfs, change the current directory to initrd and return directly.
Code [8]: After executing Linuxrc, realfs has been determined, then mount mount_root to mount realfs to the /root directory of the VFS and configure the current directory as /root for VFS.
Code [9]: Finishing work, such as freeing memory.
4. Operation after the real root file system is mounted
Let's go back and look at the init_post mentioned above. This function is actually the last function executed in Kernel_init. The code is as follows:
/* This is a non __init function. Force it to be noinline otherwise gcc
* makes it inline to init() and it becomes part of init.text section
*/
Static noinline int init_post(void)
{
/* need to finish all async __init code before freeing the memory */
Async_synchronize_full();
Free_initmem();
Unlock_kernel();
Mark_rodata_ro();
System_state = SYSTEM_RUNNING;
Numa_default_policy();
If (sys_open((const char __user *) "/dev/console", O_RDWR, 0) < 0)
Printk(KERN_WARNING "Warning: unable to open an initial console./n");
(void) sys_dup(0);
(void) sys_dup(0);
Current->signal->flags |= SIGNAL_UNKILLABLE;
If (ramdisk_execute_command) {
Run_init_process(ramdisk_execute_command);
Printk(KERN_WARNING "Failed to execute %s/n",
Ramdisk_execute_command);
}
/*
* We try each of these until one succeeds.
*
* The Bourne shell can be used instead of init if we are
*try to recover a really broken machine.
*/
If (execute_command) {
Run_init_process(execute_command);
Printk(KERN_WARNING "Failed to execute %s. Attempting "
"defaults.../n", execute_command);
}
Run_init_process("/sbin/init");
Run_init_process("/etc/init");
Run_init_process("/bin/init");
Run_init_process("/bin/sh");
Panic("No init found. Try passing init= option to kernel.");
}
As you can see, at the end of the function, you will search for files and execute ramdisk_execute_command, execute_command, /sbin/init, /etc/init, /bin/init, and /bin/sh. If you find that none of these files exist. , then output the wrong command via panic and place the current system Halt there.
Baumüller Servo Motor Replacement
Baumüller Servo Motor Replacement is a high-performance brushless servo product independently developed by Kassel. It can replace Japanese and European servo motors according to user needs, and reduce user costs without affecting the performance of the machine.
Features of product:
1. Excellent control algorithm to ensure the precise operation of the motor,
2. The wiring is simple, which ensures the reliability of the motor during operation and avoids the time cost caused by the motor wiring.
3. Equipped with a variety of safety protection functions to detect faults in time and avoid affecting system operation
4. It has the characteristics of high dynamic response, stable operation and high control precision.
Baumüller Servo Motor Replacement,Baumuller Servo,Baumueller Servo Drive,Permanent Magnet Synchronous Motor
Kassel Machinery (zhejiang) Co., Ltd. , https://www.kasselservo.com