Virtual File System
Last updated
Last updated
Assoc. Prof. Wiroon Sriborrirux, Founder of Advance Innovation Center (AIC) and Bangsaen Design House (BDH), Electrical Engineering Department, Faculty of Engineering, Burapha University
In early embedded systems, the data that needed to be stored was relatively small and the data type was relatively simple. The method of directly writing data to the specified address in the storage device was often used to store data. However, with the development of embedded device functions, the data that needs to be stored is increasing and becoming more and more complex. At this time, it becomes very cumbersome and difficult to use the old method to store and manage data. Therefore, we need a new data management method to simplify the organization of stored data. This method is the file system that we will introduce next.
The file system is an abstract data type that implements data storage, hierarchical organization, access, and retrieval. It is a mechanism for providing users with low-level data access. The basic unit of storage in a file system is usually a file, that is, data is organized in a file-by-file manner. When there are many files, there will be a large number of files that are difficult to classify and have duplicate names. A folder exists as a container for multiple files.
This chapter explains the RT-Thread file system and takes you to understand the architecture, functional features and usage of the RT-Thread virtual file system.
DFS is a virtual file system component provided by RT-Thread. Its full name is Device File System, which is a device virtual file system. The name of the file system uses a style similar to UNIX files and folders. The directory structure is shown in the following figure:
In RT-Thread DFS, the file system has a unified root directory, which /
is represented by . The f1.bin file in the root directory is /f1.bin
represented by , and the files in the 2018 directory f1.bin
are /data/2018/f1.bin
represented by . That is, the directory separator is /
, which is exactly the same as UNIX/Linux, but different from Windows (Windows operating system uses \
as the directory separator).
The main features of the RT-Thread DFS component are:
Provides a unified POSIX file and directory operation interface for applications: read, write, poll/select, etc.
It supports multiple types of file systems, such as FatFS, RomFS, DevFS, etc., and provides management of common files, device files, and network file descriptors.
Supports various types of storage devices, such as SD Card, SPI Flash, Nand Flash, etc.
The hierarchical architecture of DFS is shown in the figure below, which is mainly divided into POSIX interface layer, virtual file system layer and device abstraction layer.
POSIX stands for Portable Operating System Interface of UNIX (POSIX for short). The POSIX standard defines the interface standards that the operating system should provide to applications. It is the general name for a series of API standards defined by IEEE for software to run on various UNIX operating systems.
The POSIX standard is intended to achieve software portability at the source code level. In other words, a program written for a POSIX-compatible operating system should be compilable and executable on any other POSIX operating system (even if it comes from another manufacturer). RT-Thread supports the POSIX standard interface, so it is easy to port Linux/Unix programs to the RT-Thread operating system.
In Unix-like systems, ordinary files, device files, and network file descriptors are the same file descriptors. In the RT-Thread operating system, DFS is used to achieve this unification. With this unification of file descriptors, we can use the poll/select interface to uniformly poll these descriptors, which brings convenience to the implementation of program functions.
The poll/select interface can be used to block and simultaneously detect whether a group of I/O devices that support non-blocking have events (such as readable, writable, high-priority error output, error, etc.) until a device triggers an event or exceeds the specified waiting time. This mechanism can help the caller find the currently ready device and reduce the complexity of programming.
Users can register specific file systems to DFS, such as FatFS, RomFS, DevFS, etc. The following are some common file system types:
FatFS is a file system compatible with Microsoft FAT format developed specifically for small embedded devices. It is written in ANSI C and has good hardware independence and portability. It is the most commonly used file system type in RT-Thread.
The traditional RomFS file system is a simple, compact, read-only file system that does not support dynamic erasure and storage and stores data in sequence. It supports applications running in XIP (execute in place) mode, saving RAM space when the system is running.
Jffs2 file system is a log flash file system. It is mainly used for NOR flash memory, based on MTD driver layer, and has the following features: readable and writable, supports data compression, is a log file system based on hash table, provides crash/power-off safety protection, provides write balancing support, etc.
DevFS is the device file system. After enabling this function in the RT-Thread operating system, the devices in the system can be virtualized into files in the /dev folder, so that the devices can be operated using read, write and other interfaces in the same way as files.
NFS Network File System (NFS) is a technology for sharing files between different machines and different operating systems over the network. During the development and debugging phase of the operating system, this technology can be used to create an NFS-based root file system on the host and mount it on an embedded device, so that the content of the root file system can be easily modified.
UFFS is the abbreviation of Ultra-low-cost Flash File System. It is an open source file system developed by Chinese people and is specially designed for using Nand Flash in small memory environments such as embedded devices. Compared with the Yaffs file system commonly used in embedded systems, it has the advantages of less resource usage, fast startup speed, and free of charge.
The device abstraction layer abstracts physical devices such as SD Card, SPI Flash, and Nand Flash into devices that can be accessed by the file system. For example, the FAT file system requires that storage devices must be block device types.
Different file system types are implemented independently of storage device drivers. Therefore, the file system function can only be used correctly after the driver interface of the underlying storage device is connected with the file system.
The initialization process of the file system is generally divided into the following steps:
Initializes the DFS components.
Initialize a file system of a specific type.
Create a block device on the storage.
Format a block device.
Mount the block device to the DFS directory.
When a file system is no longer in use, it can be unmounted.
The initialization of the DFS component is completed by the dfs_init() function. The dfs_init() function will initialize the relevant resources required by DFS and create some key data structures. With these data structures, DFS can find a specific file system in the system and obtain the operation method for files in a specific storage device. If automatic initialization is enabled (enabled by default), this function will be called automatically.
After the DFS component is initialized, the specific type of file system to be used needs to be initialized, that is, the specific type of file system needs to be registered in DFS. The interface for registering a file system is as follows:
parameter
describe
ops
A collection of file system operation functions
return
——
0
File registration successful
-1
File registration failed
This function does not need to be called by the user, it will be called by the initialization function of different file systems, such as the initialization function of the elm-FAT file system elm_init()
. After the corresponding file system is enabled, if automatic initialization is enabled (enabled by default), the file system initialization function will also be automatically called.
elm_init()
The function will initialize the elm-FAT file system. This function will call dfs_register(
the function to register the elm-FAT file system to DFS. The file system registration process is shown in the figure below:
Because only block devices can be mounted on the file system, it is necessary to create the required block devices on the storage device. If the storage device is SPI Flash, you can use the "Serial Flash Universal Driver Library SFUD" component, which provides various SPI Flash drivers and abstracts SPI Flash into block devices for mounting. The process of registering a block device is shown in the following figure:
After registering the block device, you need to create a file system of the specified type on the block device, that is, format the file system. You can use dfs_mkfs()
the function to format the specified storage device and create a file system. The interface for formatting the file system is as follows:
parameter
describe
fs_name
File system type
device_name
Block device name
return
——
0
The file system was formatted successfully
-1
File system formatting failed
The possible values of the file system type (fs_name) and the corresponding file systems are shown in the following table:
Value
File system type
elm
elm-FAT file system
jffs2
jffs2 Journaling Flash File System
nfs
NFS Network File System
ram
RamFS File System
rom
RomFS read-only file system
uffs
uffs file system
lfs
littlefs file system
Taking the elm-FAT file system to format a block device as an example, the formatting process is shown in the following figure:
You can also use mkfs
the command to format the file system. The result of formatting the block device sd0 is as follows:
In RT-Thread, mounting means attaching a storage device to an existing path. To access files in a storage device, we must mount the partition where the file is located to an existing path, and then access the storage device through this path. The interface for mounting a file system is as follows:
parameter
describe
device_name
The name of the formatted block device
path
Mount path, that is, mount point
filesystemtype
The type of file system to be mounted. For possible values, see the dfs_mkfs() function description.
rwflag
Read and write flag
data
Private data for a specific file system
return
——
0
The file system is mounted successfully
-1
File system mount failed
If there is only one storage device, you can mount it directly to the root directory /
.
When a file system is no longer needed, it can be uninstalled. The interface for uninstalling a file system is as follows:
parameter
describe
specialfile
Mount path
return
——
0
Unmounting the file system successfully
-1
Failed to unmount the file system
This section introduces the functions related to file operations. File operations are generally based on the file descriptor fd, as shown in the following figure:
To open or create a file, you can call the following open() function:
parameter
describe
file
The name of the file to open or create
flags
Specify the method to open the file. The values can be found in the following table.
return
——
File Descriptors
File opened successfully
-1
File open failed
A file can be opened in multiple ways, and multiple opening modes can be specified at the same time. For example, if a file is opened in O_RDONLY and O_CREAT modes, then if the specified file does not exist, the file will be created first and then opened in read-only mode. The file opening modes are shown in the following table:
parameter
describe
O_RDONLY
Open the file in read-only mode
O_WRONLY
Open the file for writing only
O_RDWR
Open the file for reading and writing
O_CREAT
If the file to be opened does not exist, it is created.
O_APPEND
When reading and writing files, they start moving from the end of the file, that is, the written data is added to the end of the file in an appended manner.
O_TRUNC
If the file already exists, clear the contents of the file
When you have finished using a file and no longer need to use it, you can use close()
the function to close the file, which close()
will write the data back to the disk and release the resources occupied by the file.
parameter
describe
fd
File Descriptors
return
——
0
File closed successfully
-1
File close failed
To read the file contents, use read()
the function:
parameter
describe
fd
File Descriptors
buf
Buffer pointer
len
The number of bytes read from the file
return
——
int
The actual number of bytes read
0
The read data has reached the end of the file or there is no more data to read
-1
Read error, error code see errno of the current thread
This function will read len bytes of the file pointed to by the parameter fd into the memory pointed to by the buf pointer. In addition, the file's read and write position pointer will move with the bytes read.
To write data to a file, use write()
the function:
parameter
describe
F
File Descriptors
buf
Buffer pointer
len
The number of bytes written to the file
return
——
int
The actual number of bytes written
-1
Writing error, error code see errno of the current thread
This function will write len bytes in the memory pointed to by the buf pointer to the file pointed to by the parameter fd. In addition, the file's read and write position pointers will move with the bytes written.
To rename a file, use rename()
the function:
parameter
describe
old
Old file name
new
New File Name
return
——
0
Name changed successfully
-1
Change name failed
This function will change the file name specified by the parameter old to the file name specified by the parameter new. If the file specified by new already exists, it will be overwritten.
To get the file status, use the following stat()
function:
parameter
describe
file
file name
buf
Structure pointer, pointing to a structure that stores file status information
return
——
0
Get status successfully
-1
Failed to get status
To delete files in a specified directory, you can use unlink()
the function:
parameter
describe
pathname
Specify the absolute path of the file to be deleted
return
——
0
Delete file successfully
-1
Failed to delete file
To synchronize all modified file data in memory to the storage device, you can use fsync()
the function:
parameter
describe
Fildes
File Descriptors
return
——
0
Synchronize files successfully
-1
Failed to sync files
To query the file system related information, you can use statfs()
the function:
parameter
describe
path
The mount path of the file system
buf
Pointer to a structure used to store file system information
return
——
0
Query file system information successfully
-1
Failed to query file system information
To monitor whether an I/O device has an event, you can use select()
the function:
parameter
describe
nfds
The range of all file descriptors in the set, that is, the maximum value of all file descriptors plus 1
readfds
The set of file descriptors that need to be monitored for read changes
writefds
The set of file descriptors that need to be monitored for write changes
exceptfds
Need to monitor the set of file descriptors that have abnormalities
timeout
select timeout
return
——
Positive
The monitored file collection has a read/write event or an error
0
Waiting timed out, no files to read or write or error
Negative Values
Error
The interface can be used to select()
block and simultaneously detect whether a group of I/O devices that support non-blocking have events (such as readable, writable, high-priority error output, error, etc.) until a device triggers an event or exceeds the specified waiting time.
This section introduces the functions commonly used in directory management. The operations on directories are generally based on the directory address, as shown in the following figure:
To create a directory, use mkdir()
the function:
parameter
describe
path
The absolute address of the directory
mode
Create a schema
return
——
0
Directory created successfully
-1
Failed to create directory
This function is used to create a directory or folder. The parameter path is the absolute path of the directory. The parameter mode is not enabled in the current version, so fill in the default parameter 0x777.
To delete a directory, use rmdir()
the function:
parameter
describe
pathname
The absolute path of the directory to be deleted
return
——
0
Directory deleted successfully
-1
Directory deletion error
To open a directory, use opendir()
the function:
parameter
describe
name
The absolute address of the directory
return
——
DIR
Open the directory successfully and return a pointer to the directory stream
NULL
Open Failed
To close a directory, you can use closedir()
the function:
parameter
describe
d
Directory stream pointer
return
——
0
Directory closed successfully
-1
Directory Close Error
This function is used to close a directory and must opendir()
be used in conjunction with the function.
To read the directory, use readdir()
the function:
parameter
describe
d
Directory stream pointer
return
——
dirent
A successful read returns a structure pointer to the directory entry
NULL
End of directory read
This function is used to read a directory. The parameter d is the directory stream pointer. In addition, each time a directory is read, the pointer position of the directory stream will automatically move back by 1 position.
To get the read position of the directory stream, use telldir()
the function:
parameter
describe
d
Directory stream pointer
return
——
long
Read the offset of the position
The return value of this function records the current position of a directory stream. This return value represents the offset from the beginning of the directory file . You can use this value in subsequent seekdir()
function calls to reset the directory to the current position. In other words, telldir()
the function can seekdir()
be used in conjunction with the function to reset the reading position of the directory stream to the specified offset.
To set the location of the directory to be read next time, you can use seekdir()
the function:
parameter
describe
d
Directory stream pointer
offset
Offset value, the displacement from this directory
This is used to set the reading position of the directory stream parameter d. When calling readdir(), reading starts from this new position.
To reset the directory stream's reading position to the beginning, use rewinddir()
the function:
parameter
describe
d
Directory stream pointer
This function can be used to set the current reading position of the directory stream d to the initial position of the directory stream.
The specific configuration path of the file system in menuconfig is as follows:
The configuration menu description and corresponding macro definition are shown in the following table:
Configuration options
Corresponding macro definition
describe
[*] Using device virtual file system
RT_USING_DFS
Enable DFS virtual file system
[*] Using working directory
DFS_USING_WORKDIR
Enable relative paths
(2) The maximum number of mounted file system
DFS_FILESYSTEMS_MAX
Maximum number of mounted file systems
(2) The maximum number of file system type
DFS_FILESYSTEM_TYPES_MAX
Maximum number of supported file systems
(4) The maximum number of opened files
DFS_FD_MAX
Maximum number of open files
[ ] Using mount table for file system
RT_USING_DFS_MNTTABLE
Enable automatic mount table
[*] Enable elm-chan fatfs
RT_USING_DFS_ELMFAT
Enable elm-FatFs file system
[*] Using devfs for device objects
RT_USING_DFS_DEVFS
Enable DevFS device file system
[ ] Enable ReadOnly file system on flash
RT_USING_DFS_ROMFS
Enable RomFS file system
[ ] Enable RAM file system
RT_USING_DFS_RAMFS
Enable RamFS file system
[ ] Enable UFFS file system: Ultra-low-cost Flash File System
RT_USING_DFS_UFFS
Enable UFFS file system
[ ] Enable JFFS2 file system
RT_USING_DFS_JFFS2
Enable JFFS2 file system
[ ] Using NFS v3 client file system
RT_USING_DFS_NFS
Enable NFS file system
By default, the RT-Thread operating system does not enable the relative path function in order to obtain a smaller memory footprint. When the relative path support option is not enabled, you should use absolute directories when using file and directory interfaces for operations (because the current working directory does not exist in the system at this time). If you need to use the current working directory and relative directories, you can enable the relative path function in the file system configuration item.
After the option [*] Using mount table for file system
is selected, the corresponding macro will be enabled RT_USING_DFS_MNTTABLE
to turn on the automatic mount table function. The automatic mount table mount_table[]
is provided by the user in the application code. The user needs to specify the device name, mount path, file system type, read-write flags and private data in the table. After that, the system will traverse the mount table to perform the mount. It should be noted that the mount table must end with {0} to determine the end of the table.
An example of the automatic mount table mount_table[]
is shown below, where mount_table[0]
the five members are dfs_mount()
the five parameters of the function, which means mounting the elm file system to the / path of the flash0 device, with rwflag being 0 and data being 0; mount_table[1]
and ending with {0} to determine the end of the table.
After enabling the elm-FatFs file system in menuconfig, you can further configure elm-FatFs. The configuration menu description and corresponding macro definitions are shown in the following table:
Configuration options
Corresponding macro definition
describe
(437) OEM code page
RT_DFS_ELM_CODE_PAGE
Encoding
[*] Using RT_DFS_ELM_WORD_ACCESS
RT_DFS_ELM_WORD_ACCESS
Support long file name (0: LFN disable) --->
RT_DFS_ELM_USE_LFN
Open the long file name submenu
(255) Maximal size of file name length
RT_DFS_ELM_MAX_LFN
Maximum file name length
(2) Number of volumes (logical drives) to be used.
RT_DFS_ELM_DRIVES
Number of devices mounted with FatFs
(4096) Maximum sector size to be handled.
RT_DFS_ELM_MAX_SECTOR_SIZE
File system sector size
[ ] Enable sector erase feature
RT_DFS_ELM_USE_ERASE
[*] Enable the reentrancy (thread safe) of the FatFs module
RT_DFS_ELM_REENTRANT
Enable reentrancy
By default, FatFs file naming has the following disadvantages:
The file name (excluding the suffix) cannot be longer than 8 characters, and the suffix cannot be longer than 3 characters. The file name and suffix will be truncated if they exceed the limit.
File names are not case sensitive (displayed as uppercase letters)
If you need to support long file names, you need to turn on the long file name support option. The long file name submenu description is as follows:
Configuration options
Corresponding macro definition
describe
( ) 0: LFN disable
RT_DFS_ELM_USE_LFN_0
Turn off long file names
( ) 1: LFN with static LFN working buffer
RT_DFS_ELM_USE_LFN_1
Use static buffer to support long file names, which will cause reentrancy problems when multi-threaded file name operations
( ) 2: LFN with dynamic LFN working buffer on the stack
RT_DFS_ELM_USE_LFN_2
Uses a temporary buffer in the stack to support long file names. Large stack space requirements
(X) 3: LFN with dynamic LFN working buffer on the heap
RT_DFS_ELM_USE_LFN_3
Use heap (malloc) buffer to store long file names, which is the safest (default mode)
When long file name support is enabled, you can set the file name encoding. RT-Thread/FatFs uses 437 encoding (American English) by default. If you need to store Chinese file names, you can use 936 encoding (GBK encoding). 936 encoding requires a font library of about 180KB. If you only use English characters as files, it is recommended to use 437 encoding (American English), which can save 180KB of Flash space.
The file encodings supported by FatFs are as follows:
Specify the internal sector size of FatFs, which needs to be greater than or equal to the sector size of the actual hardware driver. For example, if the sector size of a spi flash chip is 4096 bytes, the above macro needs to be modified to 4096, otherwise the array will be out of bounds when FatFs reads data from the driver, causing the system to crash (the new version will give a warning message when the system is executed).
Generally, the sector size of Flash devices can be set to 4096, and the sector size of common TF cards and SD cards is set to 512.
FatFs takes into full consideration the safety of multi-threaded reading and writing. When reading and writing FatFs in multiple threads, in order to avoid the problem of reentry, the above macro needs to be turned on. If the system has only one thread operating the file system and there will be no reentry problem, this function can be turned off to save resources.
FatFs itself supports a lot of configuration options and is very flexible. The following file is the configuration file of FatFs, which can be modified to customize FatFs.
After the file system is mounted successfully, you can operate files and directories. The commonly used FinSH commands for file system operations are shown in the following table:
FinSH Commands
describe
ls
Display information about files and directories
cd
Enter the specified directory
cp
Copying Files
rm
Deleting a file or directory
mv
Move or rename the file
echo
Write the specified content to the specified file. If the file exists, write it to the file. If the file does not exist, create a new file and write it to it.
cat
Display the contents of a file
pwd
Print out the current directory address
mkdir
Create a folder
mkfs
Format the file system
Use ls
the command to view the current directory information. The running results are as follows:
Use mkdir
the command to create a folder. The running result is as follows:
Use echo
the command to output the input string to the specified output location. The running result is as follows:
Use cat
the command to view the file contents. The running results are as follows:
Use rm
the command to delete folders or files. The running results are as follows:
After the file system works properly, you can run the application example. In this sample code, you will first use open()
the function to create a file text.txt, use the write() function to write a string to the file “RT-Thread Programmer!\n”
, and then close the file. Use the function again open()
to open the text.txt file, read out the content and print it out, and finally close the file.
The sample code is as follows:
The sample code in this section shows how to modify the file name. The program will create a function to operate the file rename_sample()
and export it to the msh command list. This function will call rename()
the function to rename the file named text.txt to text1.txt. The sample code is as follows:
Run this example in the FinSH console, and the results are as follows:
In the example display process, we first use the echo command to create a file named text.txt, and then run the sample code to change the file name of the text.txt file to text1.txt.
The sample code in this section shows how to get the file status. The program will create a function to operate the file stat_sample()
and export it to the msh command list. This function will call stat()
the function to get the file size information of the text.txt file. The sample code is as follows:
Run this example in the FinSH console, and the results are as follows:
When the example is running, echo
the command is used to create the file text.txt first, and then the example code is run to print out the file size information of the file text.txt.
The sample code in this section shows how to create a directory. The program will create a function to operate files mkdir_sample()
and export it to the msh command list. This function will call mkdir()
the function to create a folder named dir_test. The sample code is as follows:
Run this example in the FinSH console, and the results are as follows:
This example demonstrates creating a folder named dir_test in the root directory.
The sample code in this section shows how to read a directory. The program will create a function to operate files readdir_sample()
and export it to the msh command list. The function will call readdir()
the function to obtain the content information of the dir_test folder and print it out. The sample code is as follows:
Run this example in the FinSH console, and the results are as follows:
In this example, we first enter the dir_test folder to create the hello.txt file, and then exit the dir_test folder. Now run the sample program to print out the contents of the dir_test folder.
The sample code in this section shows how to set the location of the next directory read. The program will create a function to operate the file telldir_sample()
and export it to the msh command list. The function will first open the root directory, then read all the directory information under the root directory and print out the directory information. At the same time, telldir()
the function is used to record the location information of the third directory item. Before reading the directory information under the root directory for the second time, seekdir()
the function is used to set the reading position to the address of the third directory item previously recorded. At this time, the information under the root directory is read again and the directory information is printed out. The sample code is as follows:
In this demonstration, you need to manually mkdir
create five folders from hello_1 to hello_5 in the root directory using the command to ensure that the root directory contains the folders required to run the example.
Run this example in the FinSH console, and the results are as follows:
After running the sample program, you can see that the first time the root directory information is read, it starts from the first folder and prints out all the directory information under the root directory. When the directory information is printed for the second time, the seekdir()
function is used to set the starting position of the read to the position of the third folder. Therefore, when the root directory is read for the second time, it starts from the third folder to the last folder and only prints out the directory information from hello_3 to hello_5.
A: Check whether long file name support is enabled in the DFS function configuration section.
A: Check whether the types and number of file systems allowed to be mounted in the file system configuration items are sufficient.
A: Check whether the storage device exists. If it exists, check whether the device driver can pass the functional test. If not, check the driver error. Check whether the libc function is enabled.
A:
Check whether the specified mount path exists. The file system can be mounted directly to the root directory ("/"), but if you want to mount it to another path, such as ("/sdcard"), you need to ensure that the ("/sdcard") path exists, otherwise you need to create sdcard
a folder in the root directory before mounting successfully.
Check whether a file system has been created on the storage device. If no file system has been created on the storage device, you need to use mkfs
the command to create a file system on the storage device.
A:
Check whether the hardware pin settings are correct.
Whether the SPI device has been registered.
Whether the SPI device has been mounted on the bus.
Check whether the and configuration items RT-Thread Components → Device Drivers -> Using SPI Bus/Device device drivers -> Using Serial Flash Universal Driver
under the menu are selected. If not, you need to enable these two options.Using auto probe flash JEDEC SFDP parameterUsing defined supported flash chip information table
If the storage device is still not recognized after enabling the above options, you can file an issue in the SFUD project.
A:
You can compare the benchmark test datasystem tick
when is 1000 and the time required for this test. If the time difference is too large, it can be considered that the test work is not running normally.
Check the system tick settings, because some delay operations are determined by the tick time, so you need to set a suitable system tick
value according to the system situation. If the system system tick
value is not less than 1000, you need to use a logic analyzer to check the waveform to confirm that the communication rate is normal.
A: You can use the partition tool package provided by RT-Thread to create multiple block devices for the entire storage device, and assign different functions to the created multiple block devices.
A: Try to use a debugger or print some necessary debugging information to determine where the program is stuck before asking the question.
A:
You can use a bottom-up approach to gradually troubleshoot the problem.
First, check whether the storage device is successfully registered and functions normally.
Check whether a file system has been created on the storage device.
Checks whether the specified file system type is registered with the DFS framework, and always checks whether the allowed file system types and number are sufficient.
Check whether DFS is initialized successfully. This step of initialization is purely software-based, so the possibility of error is low. It should be noted that if automatic component initialization is enabled, there is no need to manually initialize it again.