In-Depth Analysis of Linux SquashFS: From Principles to Practice
1 Overview of SquashFS: The Read-Only Compressed File System for Linux
SquashFS (.sfs) is a GPL open-source licensed read-only compressed file system used by the Linux kernel, designed specifically for general read-only file system use cases. This file system excels in data backup and environments with limited system resources, significantly reducing storage space usage through efficient compression algorithms. The first version of SquashFS used gzip for data compression, and as technology evolved, subsequent versions gradually added support for more compression algorithms. In Linux kernels after version 2.6.34, SquashFS added support for LZMA and LZO compression algorithms, while the 2.6.38 kernel version further added support for LZMA2 (which is also the compression algorithm used by xz).
The innovation of SquashFS lies in its ability to compress not only file data but also the metadata (including inodes and directory structures) of the file system. This comprehensive compression strategy allows it to achieve higher compression ratios than traditional file systems. For this reason, SquashFS is often used in various Linux distributions’ LiveCD environments and is widely applied in router firmware such as OpenWrt and DD-WRT. Notably, even Google’s Chromecast uses SquashFS as its file system. In LiveCD application scenarios, SquashFS is typically combined with union file systems like UnionFS, OverlayFS, and aufs to provide read-write support on a read-only basis, effectively balancing the needs for compression ratio and system functionality.
With the rise of container technology, SquashFS has also found new application scenarios. The AppImage project uses SquashFS as its image format, allowing applications to be packaged in a compressed read-only file system and run directly on various Linux distributions without installation. This self-contained application distribution method greatly simplifies the complexity of software distribution on the Linux platform.
2 SquashFS Architecture Design: Layers and Components
2.1 Overall Architecture Overview
The architecture design of SquashFS adopts a typical layered architecture pattern, with each layer having clear responsibilities. This layered design enables SquashFS to efficiently handle the compression and read-only access requirements of the file system. As shown in the diagram below, the overall architecture of SquashFS consists of the following core layers:
User Application
VFS Virtual File System Layer
SquashFS File System Layer
Compression/Decompression Layer
Block Device Driver Layer
Physical Storage Device
Metadata Management
Data Block Management
Fragment Management
Directory Management
Gzip Compressor
LZO Compressor
XZ Compressor
LZ4 Compressor
ZSTD Compressor
In the architecture of SquashFS, the VFS Virtual File System Layer serves as the abstract interface for the Linux file system, providing a unified file operation API for upper-layer user applications. When a user program initiates a file access request, the VFS routes the call to the SquashFS file system layer, where the specific file read/write logic is handled by SquashFS’s implementation.
SquashFS File System Layer is the core of the entire architecture, responsible for parsing the disk format of SquashFS, managing the file system’s metadata and data blocks. This layer includes several subcomponents: metadata management handles the parsing and caching of superblocks, inodes, and other metadata; data block management organizes and manages file data blocks; fragment management is an optimization technique used to merge multiple small files into a single block, improving compression ratio and access efficiency; directory management specifically handles the storage and access of directory structures.
The bottom Compression/Decompression Layer provides support for multiple compression algorithms. One of the clever designs of SquashFS is that it supports various compression algorithms, allowing users to choose the most suitable algorithm when creating the file system based on their needs. For example, the gzip compressor provides a balance between compression speed and ratio, the lzo compressor focuses on fast decompression, while the xz compressor aims for higher compression ratios, albeit at a slower compression speed. This flexibility allows SquashFS to adapt to different usage scenarios, from extremely storage-constrained embedded devices to LiveCD environments that require fast booting.
2.2 Metadata Organization Structure
The metadata organization of SquashFS adopts a highly compressed compact structure, which is one of the key factors enabling its high compression ratio. In SquashFS, not only file data is compressed, but all metadata, including inodes and directory entries, is also processed for compression. The organization of metadata on disk is as follows:
SquashFS Image
Superblock
Data Block
Fragment
Inode Table
Directory Table
Export Table
UID/GID Lookup Table
File System Basic Information
Compression Method
Block Size
Regular File Inode
Directory Inode
Symbolic Link Inode
Device File Inode
Superblock is the starting point of the SquashFS file system, containing basic information about the file system, such as the magic number (used to identify the SquashFS format), file system creation time, block size, compression method, and the locations of various metadata tables within the file system. The superblock is located at the beginning of the file system, and the kernel first reads and verifies the superblock when mounting the SquashFS file system.
Inode Table stores the metadata information of all files. The inode design of SquashFS is very compact, with the structure and fields of the inode varying based on the file type. Unlike conventional file systems, the inodes in SquashFS are highly compressed, with an average length of about 8 bytes. This compact design further enhances the storage efficiency of SquashFS.
Data Blocks are where the actual file content is stored. SquashFS divides file content into multiple blocks, each compressed separately and then stored sequentially on disk. This block-wise compression strategy has a significant advantage: when accessing a part of a file, only the corresponding data block needs to be decompressed, rather than decompressing the entire file, greatly reducing memory usage and access latency.
Fragments are an innovative feature introduced by SquashFS for efficiently storing small files. In traditional file systems, even very small files occupy an entire disk block, leading to wasted storage space. The fragment mechanism of SquashFS allows multiple small files to be packed into a single fragment block, with compression and storage occurring only when the fragment block is filled. This mechanism significantly improves storage efficiency in scenarios with many small files.
3 Core Data Structure Analysis
3.1 Superblock
The superblock is the core metadata structure of the SquashFS file system, recording global information and layout of the entire file system. In the Linux kernel source code, the structure of the SquashFS superblock is defined as <span>struct squashfs_super_block</span>, which contains the following key fields:
struct squashfs_super_block {
__le32 s_magic; /* Magic number, fixed value is SQUASHFS_MAGIC */
__le32 inodes; /* Total number of inodes in the file system */
__le32 mkfs_time; /* File system creation time */
__le32 block_size; /* Data block size */
__le32 fragments; /* Number of fragments */
__le16 compression; /* Compression algorithm ID */
__le16 block_log; /* Log representation of block size */
__le16 flags; /* File system flags */
__le16 no_ids; /* Unique UID/GID count */
__le16 s_major; /* Major version number */
__le16 s_minor; /* Minor version number */
__le64 root_inode; /* Location of root directory inode */
__le64 bytes_used; /* Actual bytes used by the file system */
__le64 id_table_start; /* Location of ID table */
__le64 xattr_id_table_start; /* Location of extended attribute table */
__le64 inode_table_start; /* Starting location of inode table */
__le64 directory_table_start; /* Starting location of directory table */
__le64 fragment_table_start; /* Starting location of fragment table */
__le64 lookup_table_start; /* Starting location of export table */
};
The <span>s_magic</span> field in the superblock is a special identifier used to confirm that the file system is indeed in SquashFS format, with a fixed value of <span>0x73717368</span> (the ASCII representation of “sqsh”). When the kernel mounts a SquashFS image, it first checks this magic number to verify the correctness of the file system type.
<span>compression</span> field specifies the compression algorithm used by the file system. SquashFS supports multiple compression algorithms, each with a unique ID. For example, the ID for gzip compression is 1, LZMA is 2, LZO is 3, XZ is 4, lz4 is 5, and zstd is 6. This multi-algorithm support allows users to choose the most suitable compression method based on specific needs—XZ or zstd for high compression ratios, or lzo or lz4 for fast decompression.
<span>block_size</span> field defines the block size of the file system, typically defaulting to 128KB but adjustable as needed, with a maximum supported block size of 1MB. Larger block sizes generally achieve better compression ratios but also require more memory to cache and decompress data blocks. Therefore, in memory-constrained environments, smaller block sizes may need to be chosen to balance performance and resource consumption.
3.2 Inode Structure
Inodes are the core structures in SquashFS that describe file metadata. Unlike traditional file systems, SquashFS inodes are highly compressed and designed with different structures for different file types. This type-specific inode design avoids maintaining a large unified structure for all file types, further enhancing storage efficiency.
The basic structure of several main inode types in SquashFS is as follows:
/* Basic inode structure */
struct squashfs_base_inode {
__le16 inode_type; /* Inode type */
__le16 mode; /* File mode and permissions */
__le16 uid; /* Owner ID index */
__le16 guid; /* Group ID index */
__le32 mtime; /* Modification time */
__le32 inode_number; /* Inode number */
};
/* Regular file inode */
struct squashfs_reg_inode {
__le16 inode_type; /* Fixed as SQUASHFS_FILE_TYPE */
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le64 start_block; /* Starting block position of the file */
__le64 file_size; /* File size */
__le32 fragment; /* Fragment index */
__le32 offset; /* Offset within the fragment */
__le32 block_list[]; /* List of data block positions */
};
/* Directory inode */
struct squashfs_dir_inode {
__le16 inode_type; /* Fixed as SQUASHFS_DIR_TYPE */
__le16 mode;
__le16 uid;
__le16 guid;
__le32 mtime;
__le32 inode_number;
__le32 start_block; /* Starting block position of the directory */
__le32 nlink; /* Link count */
__le16 file_size; /* Directory size */
__le16 offset; /* Offset of the first directory entry */
__le32 parent_inode; /* Parent directory inode number */
};
The inode design of SquashFS reflects its pursuit of efficient compression. Unlike conventional file systems, the length of SquashFS inodes is not fixed but varies dynamically based on file type and storage needs. For example, the symbolic link inode directly stores the link path within the inode, while the device file inode includes device number information.
<span>inode_type</span> field identifies the specific type of inode. In addition to the regular file and directory inodes mentioned above, there are also symbolic link types (SQUASHFS_SYMLINK_TYPE), block device types (SQUASHFS_BLKDEV_TYPE), character device types (SQUASHFS_CHRDEV_TYPE), etc. This type differentiation allows SquashFS to optimize storage structures for different file types.
For regular files, <span>start_block</span> points to the starting block position of the file data, while <span>file_size</span> indicates the actual size of the file. If the file is small enough to fit entirely within a fragment, the <span>fragment</span> and <span>offset</span> fields specify the file’s position and offset in the fragment table, while <span>start_block</span> is not used. This flexible data storage strategy allows SquashFS to efficiently handle files of various sizes.
3.3 Directory Structure
The organization of directories in SquashFS is also carefully designed to achieve efficient storage and fast path lookup. Directories in SquashFS are stored as a series of directory entries, each containing a name, inode number, and other necessary information.
The basic structure of a directory entry is as follows:
struct squashfs_dir_entry {
__le16 offset; /* Offset of the directory entry within the block */
__le16 inode_number; /* Offset of the inode number */
__le16 inode_type; /* Inode type */
__le16 size; /* Length of the directory entry name */
char name[]; /* Directory entry name (non-null-terminated string) */
};
In SquashFS, directory entries do not store the complete inode number directly but rather store the offset of the inode number relative to the <span>start_inode</span> field in the directory inode. This relative addressing method reduces storage space requirements, as smaller offset values can be represented with fewer bytes.
Another characteristic of directory entry organization is that they are grouped and stored in compressed data blocks, with each directory block containing multiple directory entries. When searching for a specific entry in a directory, SquashFS decompresses the entire directory block and then performs a linear search in memory for the matching directory entry. Since directory blocks typically contain multiple directory entries, this batch processing approach improves directory lookup efficiency.
3.4 Core Data Structure Relationship Diagram
The core data structures of SquashFS are interconnected through pointers and positional references, forming an efficient overall architecture. The following Mermaid diagram illustrates the logical relationships between these data structures:
SquashFS Superblock
Inode Table
Directory Table
Fragment Table
Data Blocks
ID Lookup Table
Regular File Inode
Directory Inode
Symbolic Link Inode
Device File Inode
Directory Entry 1
Directory Entry 2
Directory Entry ...
Fragment Block
Data Block 1
Data Block 2
Data Block ...
UID List
GID List
From the diagram, it can be seen that the Superblock serves as the root structure of the file system, containing pointers to various metadata tables. The Inode Table stores the metadata of all files, with inodes pointing to different data areas based on file type: regular file inodes point to data blocks and possibly fragment blocks, while directory inodes point to a location in the directory table, which in turn contains directory entries pointing to other inodes.
Fragment Table is a special data structure used to manage small files that are packed together. When a file is too small to fill an entire data block, SquashFS attempts to pack it with other small files into a fragment block. The fragment table records the locations and allocation status of these fragment blocks.
ID Lookup Table is an optimized design for efficiently storing UID and GID information. Since files in a typical file system usually belong to only a few users and groups, SquashFS centralizes all unique UIDs and GIDs in this table, allowing inodes to store only the index of the UID/GID in the table rather than the complete ID values. This method significantly reduces storage overhead, especially when the file system contains a large number of files.
4 Code Framework Analysis
4.1 Image Creation Process: Workflow of mksquashfs
mksquashfs is the tool for creating SquashFS images, and its workflow involves several complex steps, including source directory scanning, data block compression, metadata construction, and final image assembly. Understanding this process helps us grasp where the efficiency of SquashFS comes from.
The core workflow of mksquashfs can be represented by the following Mermaid sequence diagram:
Image Writer Fragment Manager Metadata Builder Compressor Data Block Processor Directory Scanner mksquashfs Image Writer Fragment Manager Metadata Builder Compressor Data Block Processor Directory Scanner mksquashfs Scan source directory structure Generate inodes and directory entries Read file data Check small file fragmentation Merge small files into fragment blocks Compress data blocks Write compressed data Write metadata Assemble complete image
Directory scanning phase, mksquashfs recursively traverses the source directory, assigning inode numbers to each file and constructing an in-memory representation of the directory tree. This process requires careful handling of file permissions, owners, and timestamps to ensure they are preserved in the generated SquashFS image.
Data compression phase is the core step of mksquashfs. For each regular file, mksquashfs splits it into multiple data blocks, with each block size specified by the <span>-b</span> parameter (default is 128KB). Each data block is then processed individually by the compressor. The compressor compresses the data block based on the compression algorithm selected by the user (specified by the <span>-comp</span> parameter, such as gzip, xz, lzo, etc.).
One important feature of mksquashfs is its support for multi-processor parallel compression. By using the <span>-processors</span> parameter, users can specify the number of CPU cores to be used for compression. When using multiple processors, mksquashfs creates multiple compression worker threads, each handling different data blocks, significantly speeding up the image creation process, especially when using high compression ratio algorithms (like xz).
Fragment processing is an optimization step of mksquashfs. The tool detects small files smaller than the block size and merges them into dedicated fragment blocks. The fragment manager is responsible for tracking the fill status of these fragment blocks and triggering the compression and writing of the fragment block at the appropriate time (when the fragment block is filled or when there are no more small files).
In the image assembly phase, mksquashfs organizes all compressed data blocks, fragment blocks, and metadata according to the disk layout of SquashFS, writing them into the final image file. The metadata includes the superblock, inode table, directory table, fragment table, etc. This phase requires careful calculation of the positions and offsets of various tables to ensure that the generated image complies with the SquashFS format specification.
4.2 Mounting and Reading Process: Mechanism of the Kernel Module
When a SquashFS image is mounted, the SquashFS file system module in the Linux kernel is responsible for parsing the image format and providing access interfaces to the files. This process involves complex interactions between the VFS (Virtual File System) layer, the SquashFS implementation, and the block device driver.
The core function call relationships of the SquashFS mounting process are as follows:
// Mount entry point
static struct dentry *squashfs_mount(struct file_system_type *fs_type,
int flags, const char *dev_name, void *data)
// Main mount function
static int squashfs_fill_super(struct super_block *sb, void *data, int silent)
{
// Read and parse the superblock
struct squashfs_super_block *sblk = read_superblock(sb);
// Set block size
sb->s_blocksize = sblk->block_size;
sb->s_blocksize_bits = ilog2(sblk->block_size);
// Read compressed metadata blocks
read_ids(sb, sblk->no_ids, &sblk->id_table_start);
read_fragment_index_table(sb, sblk->fragments, &sblk->fragment_table_start);
read_inode_table(sb, sblk->inode_table_start);
read_directory_table(sb, sblk->directory_table_start);
// Create root inode
struct inode *root_inode = squashfs_iget(sb, sblk->root_inode);
// Create root dentry
sb->s_root = d_make_root(root_inode);
}
When a user space executes the command <span>mount -t squashfs /dev/device /mount/point</span>, the kernel calls the SquashFS mount entry point function <span>squashfs_mount</span>. This function initializes a <span>super_block</span> structure and then calls <span>squashfs_fill_super</span> to complete the actual superblock filling and file system initialization work.
In the <span>squashfs_fill_super</span> function, the kernel first reads and verifies the superblock of the SquashFS image. Checking the magic number is a critical step to ensure that the mounted image is indeed a valid SquashFS image. Once the superblock verification passes, the kernel sets the file system’s block size based on the information in the superblock and locates the positions of various metadata tables.
Next, the kernel reads and decompresses the key metadata tables, including the ID table, fragment index table, inode table, and directory table. These tables are typically stored in compressed form, so the reading process involves decompression. SquashFS uses a unified metadata decompression routine to handle these compressed metadata blocks.
Once all necessary metadata structures are ready, the kernel creates inode and dentry structures for the root directory. An inode is the standard structure representing files in the VFS, while a dentry (directory entry) represents the mapping from path names to inodes. At this point, the SquashFS file system is mounted, and users can access the files in the image through the mount point.
4.3 File Reading Process
When an application attempts to read a file from a SquashFS image, a series of kernel operations are triggered, ultimately handled by the SquashFS file system module. The file reading process involves collaboration between the VFS layer, the SquashFS implementation, and the page cache.
The core flow of file reading is as follows:
- 1. Path Lookup: When an application opens a file using a path name, the VFS looks up the path component by component. For each path component, the VFS calls SquashFS’s
<span>lookup</span>function, which searches for the matching directory entry in the directory and returns the corresponding inode. - 2. Inode Creation: SquashFS’s
<span>lookup</span>function calls<span>squashfs_iget</span>to create or retrieve the inode corresponding to the directory entry. This function reads the compressed inode data from SquashFS’s inode table and decompresses it into the kernel’s<span>inode</span>structure. - 3. File Opening: Once the target file’s inode is obtained, the VFS calls SquashFS’s
<span>open</span>function. For SquashFS, this operation is relatively simple, as read-only file systems do not need to handle complex situations like write permissions or file locks. - 4. Data Reading: When the application reads the file content, the VFS calls SquashFS’s
<span>readpage</span>function to read a specific file page. This function is the core of SquashFS data reading:
static int squashfs_readpage(struct file *file, struct page *page)
{
struct inode *inode = page->mapping->host;
struct squashfs_inode_info *ino = inode->i_private;
// Calculate the position of the requested data in the file
u64 block = page->index << (PAGE_SHIFT - inode->i_blkbits);
u64 offset = block & (sbi->block_size - 1);
// Find the position of the data block
u64 start_block = find_data_block(inode, block, &offset);
// Read and decompress the data block
struct buffer_head *bh = get_block_bh(sb, start_block, offset);
int err = squashfs_read_data(sb, bh->b_data, start_block, offset,
PAGE_SIZE, &page->page);
// Copy the decompressed data to the page cache
memcpy(page_address(page), bh->b_data, PAGE_SIZE);
mark_page_accessed(page);
kunmap(bh);
brelse(bh);
return err;
}
- 5. Data Block Decompression: The
<span>squashfs_read_data</span>function is responsible for the actual decompression work. It first checks whether the requested data is in a fragment—if so, it retrieves the fragment block from the fragment table; otherwise, it retrieves it from the regular data block. It then calls the appropriate decompressor (based on the compression algorithm specified in the superblock) to decompress the data. - 6. Page Cache: The decompressed data is stored in the Linux page cache, allowing subsequent reads of the same page to be retrieved directly from the cache, avoiding repeated decompression operations and improving read performance.
The reading process of SquashFS reflects its efficient design: data is decompressed on demand, with only the data blocks that are actually accessed being decompressed into memory; the use of page cache avoids repeated decompression; and support for multiple compression algorithms meets the needs of different scenarios.
5 Simple Example Demonstration
5.1 Creating a SquashFS Image
We will demonstrate how to create and use a SquashFS image through a complete example. This example is based on a practical application scenario, showcasing the entire process from source directory preparation to image mounting.
First, we need to prepare a source directory containing the files we want to compress. Suppose we have a simple application to deploy, with the following directory structure:
/myapp/
├── bin/
│ ├── app_binary
│ └── helper_script
├── etc/
│ └── config.conf
├── lib/
│ ├── lib1.so
│ └── lib2.so
└── share/
└── data.bin
We can use the mksquashfs tool to compress this directory tree into a SquashFS image. mksquashfs provides rich options that allow us to finely control the image creation process:
# Basic command format
mksquashfs /myapp myapp.squashfs
# Complete command with advanced options
mksquashfs /myapp myapp.squashfs \
-comp xz \ # Use XZ compression algorithm
-b 256k \ # Set block size to 256KB
-noappend \ # Do not append to existing image
-root-owned \ # All files owned by root
-processors 4 \ # Use 4 processor cores for parallel compression
-p '/dev d 755 0 0' \ # Create device directory
-p '/dev/console c 600 0 0 5 1' # Create device node
This command uses several important parameters:
- •
<span>-comp xz</span>: Specifies the use of the XZ compression algorithm, which typically provides a higher compression ratio but is relatively slower. - •
<span>-b 256k</span>: Sets the block size to 256KB, which is a balanced choice between compression ratio and memory usage. - •
<span>-noappend</span>: Ensures that a new image file is created, overwriting the target file if it already exists. - •
<span>-root-owned</span>: Forces all files and directories to have UID/GID of 0 (root user). - •
<span>-processors 4</span>: Uses 4 CPU cores for parallel compression, which can significantly speed up the image creation process. - •
<span>-p</span>parameter: Used to create device nodes in the image, which is a common requirement for root file system images.
5.2 Mounting and Using the SquashFS Image
After creating the SquashFS image, we can mount it into the file system tree and access the files within it like a regular directory. There are several methods to mount a SquashFS image:
Directly Mounting the Image File:
# Create mount point
mkdir /mnt/myapp
# Mount SquashFS image
mount -t squashfs myapp.squashfs /mnt/myapp -o loop,ro
# Access image content
ls -l /mnt/myapp
cat /mnt/myapp/etc/config.conf
# Unmount when done
umount /mnt/myapp
This method simulates the image file as a block device through the <span>loop</span> device and then mounts it as a read-only file system. It is important to note that SquashFS is inherently read-only, so the <span>ro</span> (read-only) option must be specified when mounting, or no options can be specified (the default is read-only).
Burning the Image to a Device and Mounting: In embedded systems and other scenarios, SquashFS images are often burned to specific partitions of Flash storage and then mounted from that partition:
# Burn image to MTD partition
flashcp myapp.squashfs /dev/mtd4
# Mount MTD block device
mount -t squashfs /dev/mtdblock4 /mnt/myapp
This method is common in router firmware and embedded Linux systems. If mounting fails (e.g., with an “Invalid argument” error), it may be necessary to check whether the kernel configuration supports SquashFS and whether the image has been correctly burned to the storage device.
Automatically Mounting at System Startup: To automatically mount a SquashFS image at system startup, we can add the mount information to the <span>/etc/fstab</span><span> file:</span>
# /etc/fstab entry
/myapp.squashfs /mnt/myapp squashfs loop,ro 0 0
/dev/mtdblock4 /mnt/myapp squashfs ro 0 0
5.3 Extracting Content from a SquashFS Image
In addition to mounting and accessing, we can also use the unsquashfs tool to extract content from a SquashFS image, which is very useful when needing to modify or analyze the image content:
# Extract entire image content to current directory
unsquashfs myapp.squashfs
# Extract specific file or directory
unsquashfs -f myapp.squashfs etc/config.conf
unsquashfs -f myapp.squashfs bin/
# Specify extraction target directory
unsquashfs -d /extract/target myapp.squashfs
The unsquashfs tool preserves the original file permissions, timestamps, and other attributes, ensuring that the extracted content is identical to the original directory. This tool is very useful for debugging SquashFS images or recovering specific files from them.
6 Debugging and Performance Analysis Tools
6.1 Common Debugging Tools and Techniques
When developing and maintaining systems based on SquashFS, effective debugging tools and techniques are essential. Here are some commonly used SquashFS debugging methods and tools:
mksquashfs Debugging Options: mksquashfs provides several debugging options that can help us understand the details of the image creation process:
# Display detailed creation process information
mksquashfs /myapp myapp.squashfs -info
# Display more detailed debugging information
mksquashfs /myapp myapp.squashfs -debug
# Statistics on compression effects
mksquashfs /myapp myapp.squashfs -stat
# Save complete file list
mksquashfs /myapp myapp.squashfs -keep-as-directory -ef files.list
<span>-info</span> option displays detailed information for each processed file, including its size before and after compression, compression ratio, etc. The <span>-stat</span> option displays overall statistics after the image creation is complete, including total compression ratio and the number of each file type.
unsquashfs Debugging Features: The unsquashfs tool can also be used for debugging SquashFS images:
# Display detailed information about the image
unsquashfs -s myapp.squashfs
# Display the directory tree of the image
unsquashfs -ll myapp.squashfs
# Display detailed extraction process
unsquashfs -v myapp.squashfs
# List contents without actually extracting
unsquashfs -l myapp.squashfs
Kernel Debugging Techniques: When encountering SquashFS mounting or access issues, kernel debugging tools can be helpful:
# Enable SquashFS kernel debugging information
echo 8 > /proc/sys/kernel/printk
# Use strace to trace mount calls
strace mount -t squashfs /dev/mtdblock4 /mnt/myapp
# Use ftrace to trace kernel function calls
echo function > /sys/kernel/debug/tracing/current_tracer
echo squashfs_get_tree > /sys/kernel/debug/tracing/set_ftrace_filter
echo 1 > /sys/kernel/debug/tracing/tracing_on
mount -t squashfs /dev/mtdblock4 /mnt/myapp
cat /sys/kernel/debug/tracing/trace
When encountering errors like “mount: mounting /dev/mtdblock23 on /rootfs failed: Invalid argument,” using strace and ftrace can help pinpoint the root of the problem. Strace can determine which system call returned an error, while ftrace can trace the relationships of kernel function calls to help identify where the error occurred in a specific kernel function.
Image Integrity Check: Sometimes, the SquashFS image itself may be corrupted, leading to mounting failures. We can use the following methods to check the integrity of the image:
# Attempt to mount the image to a temporary location
mkdir -p /tmp/test_mount
mount -t squashfs myapp.squashfs /tmp/test_mount -o loop,ro && umount /tmp/test_mount
# Use unsquashfs to test extraction
unsquashfs -t myapp.squashfs
# Check the magic number of the image
hexdump -C myapp.squashfs | head -n 5
A valid SquashFS image should start with the “sqsh” magic number (in hexadecimal: 73 71 73 68), which is an important marker for identifying the SquashFS format.
6.2 Performance Analysis and Optimization
The performance characteristics of SquashFS make it an ideal choice in many scenarios, but to achieve optimal performance, we need to understand how to analyze and optimize the use of SquashFS.
Compression Algorithm Comparison: SquashFS supports multiple compression algorithms, each with different characteristics in terms of compression ratio, compression speed, and decompression speed. The following table compares the characteristics of the main compression algorithms:
| Compression Algorithm | Compression Ratio | Compression Speed | Decompression Speed | Memory Usage | Applicable Scenarios |
|---|---|---|---|---|---|
| gzip | Medium | Medium | Fast | Low | General scenarios, balanced performance |
| lzo | Lower | Very fast | Very fast | Low | Real-time applications, quick startup |
| xz | High | Slow | Medium | High | Scenarios with limited storage space |
| lz4 | Lower | Extremely fast | Extremely fast | Low | High-performance requirement scenarios |
| zstd | High | Medium | Fast | Medium | Modern applications, balanced choice |
When choosing a compression algorithm, the characteristics of the target device need to be considered. For embedded devices with weak CPUs but precious storage space, xz may be a better choice; while for real-time systems requiring quick startup, lzo or lz4 may be more suitable.
Block Size Optimization: Block size has a significant impact on the performance and compression ratio of SquashFS. Larger blocks typically achieve better compression ratios but require more memory to cache and decompress data. We can experiment to find the most suitable block size for specific application scenarios:
# Test the effects of different block sizes
for bs in 64k 128k 256k 512k 1024k; do
echo "Testing block size: $bs"
mksquashfs /myapp test_$bs.squashfs -b $bs -no-recovery -no-progress
echo "Size: $(du -h test_$bs.squashfs | cut -f1)"
done
Multi-Processor Optimization: On multi-core systems, using multiple processor cores can significantly accelerate the image creation process of SquashFS:
# Use all available processor cores
mksquashfs /myapp myapp.squashfs -processors $(nproc)
# Or explicitly specify the number of processors
mksquashfs /myapp myapp.squashfs -processors 4
When using multi-processor parallel compression, mksquashfs creates multiple worker threads, each handling different data blocks. This is particularly effective when using high compression ratio algorithms (like xz), significantly reducing image creation time.
Read Performance Analysis: We can use various tools to analyze the read performance of SquashFS:
# Measure mount time
time mount -t squashfs myapp.squashfs /mnt/myapp -o loop
# Use fio for read performance testing
fio --name=seqread --rw=read --bs=256k --size=100M --filename=/mnt/myapp/share/data.bin
# Use systemtap for deeper kernel-level performance analysis
stap -e 'probe kernel.function("squashfs_read_data") {
times[tid()] = gettimeofday_us()
}
probe kernel.function("squashfs_read_data").return {
t = gettimeofday_us() - times[tid()]
printf("squashfs_read_data took %d us\n", t)
}'
7 Conclusion
As a read-only compressed file system in the Linux ecosystem, SquashFS has developed into a mature, stable, and efficient technical solution over the years. Its unique design philosophy and technical characteristics demonstrate significant advantages in multiple application scenarios.
The core advantage of SquashFS lies in its comprehensive compression strategy—not only compressing file data but also compressing metadata such as inodes and directories. This all-encompassing compression method allows SquashFS to achieve higher compression ratios than other file systems. Compared to earlier compressed file systems like cramfs, SquashFS supports larger file systems (up to 2^64 bytes), complete 32-bit UID/GID, file creation timestamps, and more flexible block size configurations (up to 1MB).
The block compression architecture of SquashFS is one of the key factors for its success. By dividing files into independent compressed blocks, SquashFS achieves efficient random access capability—only the data blocks that are actually accessed need to be decompressed, rather than decompressing the entire file. This design is particularly suitable for scenarios involving partial reads of large files, significantly reducing memory usage and access latency.
Another important feature is the fragment mechanism, which addresses the inefficiency of small file storage. By packing multiple small files into a single block, SquashFS avoids the space wastage caused by allocating an entire block for each small file. This optimization is particularly valuable in applications containing a large number of small files (such as root file systems for embedded systems).
From a performance perspective, SquashFS offers flexible compression algorithm choices, allowing users to balance between compression ratio, compression speed, and decompression speed based on specific needs. Whether pursuing high compression ratios with the xz algorithm, focusing on fast decompression with the lzo algorithm, or balancing performance with the zstd algorithm, SquashFS can provide support.