Virtual File System

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.binrepresented by , and the files in the 2018 directory f1.binare /data/2018/f1.binrepresented 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:

  1. Initializes the DFS components.

  2. Initialize a file system of a specific type.

  3. Create a block device on the storage.

  4. Format a block device.

  5. Mount the block device to the DFS directory.

  6. 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:

int dfs_register(const struct dfs_filesystem_ops *ops);copymistakeCopy Success

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:

int dfs_mkfs(const char * fs_name, const char * device_name);copymistakeCopy Success

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 mkfsthe command to format the file system. The result of formatting the block device sd0 is as follows:

msh />mkfs sd0                    # sd0 为块设备名称,该命令会默认格式化 sd0 为 elm-FAT 文件系统
msh />
msh />mkfs -t elm sd0             # 使用 -t 参数指定文件系统类型为 elm-FAT 文件系统copymistakeCopy Success

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:

int dfs_mount(const char   *device_name,
              const char   *path,
              const char   *filesystemtype,
              unsigned long rwflag,
              const void   *data);copymistakeCopy Success

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:

int dfs_unmount(const char *specialfile);copymistakeCopy Success

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:

int open(const char *file, int flags, ...);copymistakeCopy Success

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.

int close(int fd);copymistakeCopy Success

parameter

describe

fd

File Descriptors

return

——

0

File closed successfully

-1

File close failed

To read the file contents, use read()the function:

int read(int fd, void *buf, size_t len);copymistakeCopy Success

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:

int write(int fd, const void *buf, size_t len);copymistakeCopy Success

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:

int rename(const char *old, const char *new);copymistakeCopy Success

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:

int stat(const char *file, struct stat *buf);copymistakeCopy Success

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:

int unlink(const char *pathname);copymistakeCopy Success

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:

int fsync(int fildes);copymistakeCopy Success

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:

int statfs(const char *path, struct statfs *buf);copymistakeCopy Success

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:

int select( int nfds,
            fd_set *readfds,
            fd_set *writefds,
            fd_set *exceptfds,
            struct timeval *timeout);copymistakeCopy Success

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:

int mkdir(const char *path, mode_t mode);copymistakeCopy Success

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:

int rmdir(const char *pathname);copymistakeCopy Success

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:

DIR* opendir(const char* name);copymistakeCopy Success

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:

int closedir(DIR* d);copymistakeCopy Success

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:

struct dirent* readdir(DIR *d);copymistakeCopy Success

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:

long telldir(DIR *d);copymistakeCopy Success

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:

void seekdir(DIR *d, off_t offset);copymistakeCopy Success

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:

void rewinddir(DIR *d);copymistakeCopy Success

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:

RT-Thread Components  --->
    Device virtual file system  --->copymistakeCopy Success

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 systemis selected, the corresponding macro will be enabled RT_USING_DFS_MNTTABLEto 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.

const struct dfs_mount_tbl mount_table[] =
{
    {"flash0", "/", "elm", 0, 0},
    {0}
};copymistakeCopy Success

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

Long file names

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)

Encoding

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:

/* This option specifies the OEM code page to be used on the target system.
/  Incorrect setting of the code page can cause a file open failure.
/
/   1   - ASCII (No extended character. Non-LFN cfg. only)
/   437 - U.S.
/   720 - Arabic
/   737 - Greek
/   771 - KBL
/   775 - Baltic
/   850 - Latin 1
/   852 - Latin 2
/   855 - Cyrillic
/   857 - Turkish
/   860 - Portuguese
/   861 - Icelandic
/   862 - Hebrew
/   863 - Canadian French
/   864 - Arabic
/   865 - Nordic
/   866 - Russian
/   869 - Greek 2
/   932 - Japanese (DBCS)
/   936 - Simplified Chinese (DBCS)
/   949 - Korean (DBCS)
/   950 - Traditional Chinese (DBCS)
*/copymistakeCopy Success

File system sector size

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.

Reentrancy

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.

More Configuration

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.

components/dfs/filesystems/elmfat/ffconf.hcopymistakeCopy Success

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 lsthe command to view the current directory information. The running results are as follows:

msh />ls                          # 使用 ls 命令查看文件系统目录信息
Directory /:                      # 可以看到已经存在根目录 /copymistakeCopy Success

Use mkdirthe command to create a folder. The running result is as follows:

msh />mkdir rt-thread             # 创建 rt-thread 文件夹
msh />ls                          # 查看目录信息如下
Directory /:
rt-thread           <DIR>copymistakeCopy Success

Use echothe command to output the input string to the specified output location. The running result is as follows:

msh />echo "hello rt-thread!!!"                 # 将字符串输出到标准输出
hello rt-thread!!!
msh />echo "hello rt-thread!!!" hello.txt      # 将字符串出输出到 hello.txt 文件
msh />ls
Directory /:
rt-thread           <DIR>
hello.txt           18
msh />copymistakeCopy Success

Use catthe command to view the file contents. The running results are as follows:

msh />cat hello.txt                     # 查看 hello.txt 文件的内容并输出
hello rt-thread!!!copymistakeCopy Success

Use rmthe command to delete folders or files. The running results are as follows:

msh />ls                                # 查看当前目录信息
Directory /:
rt-thread           <DIR>
hello.txt           18
msh />rm rt-thread                      # 删除 rt-thread 文件夹
msh />ls
Directory /:
hello.txt           18
msh />rm hello.txt                      # 删除 hello.txt 文件
msh />ls
Directory /:
msh />copymistakeCopy Success

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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void readwrite_sample(void)
{
    int fd, size;
    char s[] = "RT-Thread Programmer!", buffer[80];

    rt_kprintf("Write string %s to test.txt.\n", s);

    /* 以创建和读写模式打开 /text.txt 文件,如果该文件不存在则创建该文件 */
    fd = open("/text.txt", O_WRONLY | O_CREAT);
    if (fd>= 0)
    {
        write(fd, s, sizeof(s));
        close(fd);
        rt_kprintf("Write done.\n");
    }

      /* 以只读模式打开 /text.txt 文件 */
    fd = open("/text.txt", O_RDONLY);
    if (fd>= 0)
    {
        size = read(fd, buffer, sizeof(buffer));
        close(fd);
        rt_kprintf("Read from file test.txt : %s \n", buffer);
        if (size < 0)
            return ;
    }
  }
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(readwrite_sample, readwrite sample);
copymistakeCopy Success

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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void rename_sample(void)
{
    rt_kprintf("%s => %s", "/text.txt", "/text1.txt");

    if (rename("/text.txt", "/text1.txt") < 0)
        rt_kprintf("[error!]\n");
    else
        rt_kprintf("[ok!]\n");
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(rename_sample, rename sample);copymistakeCopy Success

Run this example in the FinSH console, and the results are as follows:

msh />echo "hello" text.txt
msh />ls
Directory /:
text.txt           5
msh />rename_sample
/text.txt => /text1.txt [ok!]
msh />ls
Directory /:
text1.txt           5copymistakeCopy Success

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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void stat_sample(void)
{
    int ret;
     struct stat buf;
     ret = stat("/text.txt", &buf);
    if(ret == 0)
    rt_kprintf("text.txt file size = %d\n", buf.st_size);
    else
    rt_kprintf("text.txt file not fonud\n");
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(stat_sample, show text.txt stat sample);copymistakeCopy Success

Run this example in the FinSH console, and the results are as follows:

msh />echo "hello" text.txt
msh />stat_sample
text.txt file size = 5copymistakeCopy Success

When the example is running, echothe 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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void mkdir_sample(void)
{
    int ret;

    /* 创建目录 */
    ret = mkdir("/dir_test", 0x777);
    if (ret < 0)
    {
        /* 创建目录失败 */
        rt_kprintf("dir error!\n");
    }
    else
    {
        /* 创建目录成功 */
        rt_kprintf("mkdir ok!\n");
    }
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(mkdir_sample, mkdir sample);copymistakeCopy Success

Run this example in the FinSH console, and the results are as follows:

msh />mkdir_sample
mkdir ok!
msh />ls
Directory /:
dir_test                 <DIR>    # <DIR> 表示该目录的类型是文件夹copymistakeCopy Success

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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

static void readdir_sample(void)
{
    DIR *dirp;
    struct dirent *d;

    /* 打开 / dir_test 目录 */
    dirp = opendir("/dir_test");
    if (dirp == RT_NULL)
    {
        rt_kprintf("open directory error!\n");
    }
    else
    {
        /* 读取目录 */
        while ((d = readdir(dirp)) != RT_NULL)
        {
            rt_kprintf("found %s\n", d->d_name);
        }

        /* 关闭目录 */
        closedir(dirp);
    }
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(readdir_sample, readdir sample);copymistakeCopy Success

Run this example in the FinSH console, and the results are as follows:

msh />ls
Directory /:
dir_test                 <DIR>
msh />cd dir_test
msh /dir_test>echo "hello" hello.txt       # 创建一个 hello.txt 文件
msh /dir_test>cd ..                        # 切换到上级文件夹
msh />readdir_sample
found hello.txtcopymistakeCopy Success

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:

#include <rtthread.h>
#include <dfs_posix.h> /* 当需要使用文件操作时,需要包含这个头文件 */

/* 假设文件操作是在一个线程中完成 */
static void telldir_sample(void)
{
    DIR *dirp;
    int save3 = 0;
    int cur;
    int i = 0;
    struct dirent *dp;

    /* 打开根目录 */
    rt_kprintf("the directory is:\n");
    dirp = opendir("/");

    for (dp = readdir(dirp); dp != RT_NULL; dp = readdir(dirp))
    {
        /* 保存第三个目录项的目录指针 */
        i++;
        if (i == 3)
            save3 = telldir(dirp);

        rt_kprintf("%s\n", dp->d_name);
    }

    /* 回到刚才保存的第三个目录项的目录指针 */
    seekdir(dirp, save3);

    /* 检查当前目录指针是否等于保存过的第三个目录项的指针. */
    cur = telldir(dirp);
    if (cur != save3)
    {
        rt_kprintf("seekdir (d, %ld); telldir (d) == %ld\n", save3, cur);
    }

    /* 从第三个目录项开始打印 */
    rt_kprintf("the result of tell_seek_dir is:\n");
    for (dp = readdir(dirp); dp != NULL; dp = readdir(dirp))
    {
         rt_kprintf("%s\n", dp->d_name);
    }

    /* 关闭目录 */
    closedir(dirp);
}
/* 导出到 msh 命令列表中 */
MSH_CMD_EXPORT(telldir_sample, telldir sample);copymistakeCopy Success

In this demonstration, you need to manually mkdircreate 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:

msh />ls
Directory /:
hello_1             <DIR>
hello_2             <DIR>
hello_3             <DIR>
hello_4             <DIR>
hello_5             <DIR>
msh />telldir_sample
the directory is:
hello_1
hello_2
hello_3
hello_4
hello_5
the result of tell_seek_dir is:
hello_3
hello_4
hello_5copymistakeCopy Success

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 sdcarda 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 mkfsthe 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 Driverunder 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 tickvalue according to the system situation. If the system system tickvalue 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.

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