鸿蒙轻内核M核源码分析系列二一 04 文件系统FatFS 原创 精华

zhushangyuan_
发布于 2022-1-9 17:00
浏览
1收藏

鸿蒙轻内核M核源码分析系列二一 04 文件系统FatFS

【本文正在参与优质创作者激励】

FAT文件系统是File Allocation Table(文件配置表)的简称,主要包括DBR区、FAT区、DATA区三个区域。其中,FAT区各个表项记录存储设备中对应簇的信息,包括簇是否被使用、文件下一个簇的编号、是否文件结尾等。FAT文件系统有FAT12、FAT16、FAT32等多种格式,其中,12、16、32表示对应格式中FAT表项的比特数。FAT文件系统支持多种介质,特别在可移动存储介质(U盘、SD卡、移动硬盘等)上广泛使用,使嵌入式设备和Windows、Linux等桌面系统保持很好的兼容性,方便用户管理操作文件。LiteOS-M内核支持FAT12、FAT16与FAT32三种格式的FAT文件系统,具有代码量小、资源占用小、可裁切、支持多种物理介质等特性,并且与Windows、Linux等系统保持兼容,支持多设备、多分区识别等功能。LiteOS-M内核支持硬盘多分区,可以在主分区以及逻辑分区上创建FAT文件系统。

本文先介绍下FatFS文件系统结构体的结构体和全局变量,然后分析下FatFS文件操作接口。文中所涉及的源码,均可以在开源站点https://gitee.com/openharmony/kernel_liteos_m 获取。


1、FatFS文件系统结构体介绍

会分2部分来介绍结构体部分,先介绍FatFS文件系统的结构体,然后介绍LiteOS-M内核中提供的和FatFS相关的一些结构体。

1.1 FatFS的结构体

在openharmony/third_party/FatFs/source/ff.h头文件中定义FatFS的结构体,我们先简单了解下,后文会使用到的。

先看下相关的宏定义,包含文件访问模式(File access mode),格式化选项(Format options)等等。文件打开方式和POSIX文件打开选项不一致,需要转换,后文会涉及。

/*--------------------------------------------------------------*/
/* Flags and offset address                                     */


/* File access mode and open method flags (3rd argument of f_open) */
#define	FA_READ				0x01
#define	FA_WRITE			0x02
#define	FA_OPEN_EXISTING	0x00
#define	FA_CREATE_NEW		0x04
#define	FA_CREATE_ALWAYS	0x08
#define	FA_OPEN_ALWAYS		0x10
#define	FA_OPEN_APPEND		0x30

/* Fast seek controls (2nd argument of f_lseek) */
#define CREATE_LINKMAP	((FSIZE_t)0 - 1)

/* Format options (2nd argument of f_mkfs) */
#define FM_FAT		0x01
#define FM_FAT32	0x02
#define FM_ANY		0x07
#define FM_SFD		0x08
......

在openharmony/third_party/FatFs/source/ffconf.h头文件中定义FatFS的一些配置信息。如下文的驱动和卷的配置信息等。对于LiteOS-M,默认是支持4个卷。宏定义FS_MAX_SS表示扇区大小sector size。

/*---------------------------------------------------------------------------/
/ Drive/Volume Configurations
/---------------------------------------------------------------------------*/

#ifndef __LITEOS_M__
#define FF_VOLUMES	LOSCFG_FS_FAT_VOLUMES
#else
#define FF_VOLUMES	4
#endif
/* Number of volumes (logical drives) to be used. (1-10) */

#ifdef LOSCFG_FS_FAT_VIRTUAL_PARTITION
#define _DEFAULT_VIRVOLUEMS		4
#define _MIN_CLST				0x4000
#define _FLOAT_ACC				0.00000001
#endif

#ifndef __LITEOS_M__
#define FF_STR_VOLUME_ID	0
#define FF_VOLUME_STRS		"RAM","NAND","CF","SD","SD2","USB","USB2","USB3"
#else
#define FF_STR_VOLUME_ID	2
#endif
#define FF_MULTI_PARTITION	1

#define	FF_MIN_SS		512
#ifndef __LITEOS_M__
#define FF_MAX_SS		4096
#else
#define FF_MAX_SS		FS_MAX_SS
#endif

对于适配LiteOS-M内核的开发板,使用FatFS文件系统时,需要提供头文件liteos_m\board\fs\fs_config.h,例如:openharmony\device\qemu\arm_mps2_an386\liteos_m\board\fs\fs_config.h。

#define FF_VOLUME_STRS "system", "inner", "update", "user"
#define FS_MAX_SS      512

#define FAT_MAX_OPEN_FILES 50

接下来看看重要的结构体。结构体FATFS是FatFS文件系统类型结构体。成员变量BYTE fs_type等0时表示未挂载,挂载后一般取值为FS_FAT12、FS_FAT16或FS_FAT32;WORD id表示卷的挂载编号。 其他成员变量可以暂不了解。

/* Filesystem object structure (FATFS) */

typedef struct {
	BYTE	fs_type;		/* Filesystem type (0:not mounted) */
	BYTE	pdrv;			/* Associated physical drive */
	BYTE	n_fats;			/* Number of FATs (1 or 2) */
	BYTE	wflag;			/* win[] flag (b0:dirty) */
	BYTE	fsi_flag;		/* FSINFO flags (b7:disabled, b0:dirty) */
	WORD	id;				/* Volume mount ID */
	WORD	n_rootdir;		/* Number of root directory entries (FAT12/16) */
	WORD	csize;			/* Cluster size [sectors] */
#if FF_MAX_SS != FF_MIN_SS
	size_t	ssize;			/* Sector size (512, 1024, 2048 or 4096) */
#endif
#if FF_USE_LFN
	WCHAR*	lfnbuf;			/* LFN working buffer */
#endif
#if FF_FS_REENTRANT
	FF_SYNC_t	sobj;		/* Identifier of sync object */
#endif
#if !FF_FS_READONLY
	DWORD	last_clst;		/* Last allocated cluster */
	DWORD	free_clst;		/* Number of free clusters */
#endif
#if FF_FS_RPATH
	DWORD	cdir;			/* Current directory start cluster (0:root) */
#endif
	DWORD	n_fatent;		/* Number of FAT entries, = number of clusters + 2 */
	DWORD	fsize;			/* Sectors per FAT */
	LBA_t	volbase;		/* Volume base sector */
	LBA_t	fatbase;		/* FAT base sector */
	LBA_t	dirbase;		/* Root directory base sector/cluster */
	LBA_t	database;		/* Data base sector */
	LBA_t	winsect;		/* Current sector appearing in the win[] */
	BYTE*	win;			/* Disk access window for Directory, FAT (and file data at tiny cfg) */

#ifdef LOSCFG_FS_FAT_VIRTUAL_PARTITION
	DWORD	st_clst;
	DWORD	ct_clst;
	BYTE	vir_flag;		/* Flag of Virtual Filesystem Object, b0 : 1 for virtual Fatfs object, 0 for reality Fatfs object */
	BYTE	vir_avail;
	DWORD	vir_amount;
	VOID*	parent_fs;		/* Point to the reality Fatfs object, only available in virtual Fatfs object */
	CHAR	namelabel[_MAX_ENTRYLENGTH + 1]; /* The name label point to the each virtual Fatfs object ,only available in virtual Fatfs obj */
	VOID**	child_fs;		/* Point to the child Fatfs object ,only available in reality Fatfs object */
#endif
#ifndef __LITEOS_M__
	int 	fs_uid;
	int 	fs_gid;
	mode_t 	fs_mode;
#endif
	unsigned short fs_dmask;
	unsigned short fs_fmask;
} FATFS;

结构体FIL、DIR分别是FatFS的文件和目录类型结构体,DIR是__dirstream结构体的别名,一般在Musl或Newlib C库的文件dirent.h会有typedef struct __dirstream DIR;。这两个结构体都包含FFOBJID obj这个成员变量,FFOBJID结构体体包含FATFS* fs成员,可以关联文件卷信息。暂不需要关心其他成员变量细节,知道结构体的用途即可。

/* Object ID and allocation information (FFOBJID) */

typedef struct {
	FATFS*	fs;				/* Pointer to the hosting volume of this object */
	WORD	id;				/* Hosting volume mount ID */
	BYTE	attr;			/* Object attribute */
	BYTE	stat;			/* Object chain status (b1-0: =0:not contiguous, =2:contiguous, =3:fragmented in this session, b2:sub-directory stretched) */
	DWORD	sclust;			/* Object data start cluster (0:no cluster or root directory) */
	FSIZE_t	objsize;		/* Object size (valid when sclust != 0) */
#if FF_FS_LOCK
	UINT	lockid;			/* File lock ID origin from 1 (index of file semaphore table Files[]) */
#endif
} FFOBJID;

/* File object structure (FIL) */

typedef struct {
	FFOBJID	obj;			/* Object identifier (must be the 1st member to detect invalid object pointer) */
	BYTE	flag;			/* File status flags */
	BYTE	err;			/* Abort flag (error code) */
	FSIZE_t	fptr;			/* File read/write pointer (Zeroed on file open) */
	DWORD	clust;			/* Current cluster of fpter (invalid when fptr is 0) */
	LBA_t	sect;			/* Sector number appearing in buf[] (0:invalid) */
#if !FF_FS_READONLY
	LBA_t	dir_sect;		/* Sector number containing the directory entry */
	BYTE*	dir_ptr;		/* Pointer to the directory entry in the win[] */
#endif
#if FF_USE_FASTSEEK
	DWORD*	cltbl;			/* Pointer to the cluster link map table (nulled on open, set by application) */
#endif
#if !FF_FS_TINY
	BYTE*	buf;			/* File private data read/write window */
#endif
#ifndef __LITEOS_M__
	LOS_DL_LIST fp_entry;
#endif
} FIL;


/* Directory object structure (DIR) */

struct __dirstream {
	FFOBJID	obj;			/* Object identifier */
	DWORD	dptr;			/* Current read/write offset */
	DWORD	clust;			/* Current cluster */
	LBA_t	sect;			/* Current sector (0:Read operation has terminated) */
	BYTE*	dir;			/* Pointer to the directory item in the win[] */
	BYTE	fn[12];			/* SFN (in/out) {body[8],ext[3],status[1]} */
#if FF_USE_LFN
	DWORD	blk_ofs;		/* Offset of current entry block being processed (0xFFFFFFFF:Invalid) */
#endif
#if FF_USE_FIND
	const TCHAR* pat;		/* Pointer to the name matching pattern */
#endif
#ifdef LOSCFG_FS_FAT_VIRTUAL_PARTITION
	BYTE			atrootdir;
#endif
};

结构体FILINFO用于维护文件信息,包含文件修改时间,大小和文件名等信息。

/* File information structure (FILINFO) */

typedef struct {
	FSIZE_t	fsize;			/* File size */
	WORD	fdate;			/* Modified date */
	WORD	ftime;			/* Modified time */
	BYTE	fattrib;		/* File attribute */
#if FF_USE_LFN
	TCHAR	altname[FF_SFN_BUF + 1];/* Altenative file name */
	TCHAR	fname[FF_LFN_BUF + 1];	/* Primary file name */
#else
	TCHAR	fname[12 + 1];	/* File name */
#endif
	DWORD	sclst;
#ifndef __LITEOS_M__
	LOS_DL_LIST fp_list;
#endif
} FILINFO;

1.2 LiteOS-M FatFS的结构体

我们来看下在文件components\fs\fatfs\fatfs.c里定义的结构体。结构体FatHandleStruct维护文件相关的信息,该结构体非常简单,在FIL的基础上增加了是否使用成员变量。

typedef struct {
    UINT8 useFlag;
    FIL fil;
} FatHandleStruct;

2、LiteOS-M FatFS的重要全局变量及操作

了解下文件components\fs\fatfs\fatfs.c中定义的常用全局变量。⑴处的g_handle数组维护文件信息,默认支持的文件数目为FAT_MAX_OPEN_FILES;g_dir数组维护目录信息,默认支持的目录数目为FAT_MAX_OPEN_DIRS。 ⑵处的g_fatfs数组维护每个卷的的文件系统信息,默认文件卷数目FF_VOLUMES为4个。和文件卷相关的变量还有g_volPath数组维护每个卷的字符串路径,g_volWriteEnable数组维护每个卷是否可写。⑶处的g_workBuffer维护每个扇区的缓存。⑷处开始的g_fileNum、g_dirNum分别是文件和目录打开的数目;struct dirent g_retValue是目录项结构体变量,用于函数fatfs_readdir();pthread_mutex_t g_fsMutex是互斥锁变量;⑸处开始的挂载操作变量g_fatfsMnt、文件操作操作全局变量g_fatfsFops,在虚拟文件系统中被使用。

⑴  static FatHandleStruct g_handle[FAT_MAX_OPEN_FILES] = {0};
    static DIR g_dir[FAT_MAX_OPEN_DIRS] = {0};
⑵  static FATFS g_fatfs[FF_VOLUMES] = {0};
⑶  static UINT8 g_workBuffer[FF_MAX_SS];
⑷  static UINT32 g_fileNum = 0;
    static UINT32 g_dirNum = 0;
    static struct dirent g_retValue;
    static pthread_mutex_t g_fsMutex = PTHREAD_MUTEX_INITIALIZER;

    static const char * const g_volPath[FF_VOLUMES] = {FF_VOLUME_STRS};
    static BOOL g_volWriteEnable[FF_VOLUMES] = {FALSE};
        ......
⑸  struct MountOps g_fatfsMnt = {
        .Mount = fatfs_mount,
        .Umount = fatfs_umount,
        .Umount2 = fatfs_umount2,
        .Statfs = fatfs_statfs,
    };

    struct FileOps g_fatfsFops = {
        .Mkdir = fatfs_mkdir,
        .Unlink = fatfs_unlink,
        .Rmdir = fatfs_rmdir,
        .Opendir = fatfs_opendir,
        .Readdir = fatfs_readdir,
        .Closedir = fatfs_closedir,
        .Open = fatfs_open,
        .Close = fatfs_close,
        .Write = fatfs_write,
        .Read = fatfs_read,
        .Seek = fatfs_lseek,
        .Rename = fatfs_rename,
        .Getattr = fatfs_stat,
        .Fsync = fatfs_fsync,
        .Fstat = fatfs_fstat,
    };

下文继续介绍下和这些变量相关的内部操作接口。

2.1 文件系统互斥锁

FatFS文件系统使用的是超时加锁。函数FsLock()中,⑴处获取系统实时时间,⑵处设置15秒超时,FS_LOCK_TIMEOUT_SEC默认为15秒。⑶处对互斥量进行加锁,超时后不会再对互斥量加锁。函数FsUnlock()用于解锁。

static int FsLock(void)
{
    INT32 ret = 0;
    struct timespec absTimeout = {0};
    if (osKernelGetState() != osKernelRunning) {
        return ret;
    }
⑴  ret = clock_gettime(CLOCK_REALTIME, &absTimeout);
    if (ret != 0) {
        PRINTK("clock gettime err 0x%x!\r\n", errno);
        return errno;
    }
⑵  absTimeout.tv_sec += FS_LOCK_TIMEOUT_SEC;
⑶  ret = pthread_mutex_timedlock(&g_fsMutex, &absTimeout);
    return ret;
}

static void FsUnlock(void)
{
    if (osKernelGetState() != osKernelRunning) {
        return;
    }
    (void)pthread_mutex_unlock(&g_fsMutex);
}

2.2 判断文件描述符有效性

函数IsValidFd()用于判断文件描述符的是否有效,如果文件描述符超出有效范围,或者文件未使用状态,返回false,否则返回true。

static bool IsValidFd(int fd)
{
    if ((fd < 0) || (fd >= FAT_MAX_OPEN_FILES) || (g_handle[fd].useFlag == 0)) {
        return false;
    }
    return true;
}

2.3 切换驱动器

函数FsChangeDrive()根据传入的路径切换驱动器(盘符)。字符串数组tmpPath用于保存驱动器名称,其中驱动器名称最大值FS_DRIVE_NAME_MAX_LEN。⑵处处理路径长度大于驱动器名称长度的情况。⑶处从路径中获取驱动器名称,然后调用接口f_chdrive()切换驱动器。在文件操作接口中,会调用该函数来切换驱动器,如fatfs_open、fatfs_unlink、fatfs_stat、fatfs_mkdir、fatfs_opendir、fatfs_rmdir、fatfs_rename和fatfs_statfs,这些函数的参数涉及文件路径char *path或者目录char *dirName。

static int FsChangeDrive(const char *path)
{
    INT32 res;
⑴  CHAR tmpPath[FS_DRIVE_NAME_MAX_LEN] = { "/" }; /* the max name length of different parts is 16 */
    errno_t retErr;
    UINT16 pathLen;
    pathLen = strlen((char const *)path);
    /* make sure the path begin with "/", the path like /xxx/yyy/... */
⑵  if (pathLen >= (FS_DRIVE_NAME_MAX_LEN - 1)) {
        /* 2: except first flag "/" and last end flag */
        pathLen = FS_DRIVE_NAME_MAX_LEN - 2;
    }

⑶  retErr = strncpy_s(tmpPath + 1, (FS_DRIVE_NAME_MAX_LEN - 1), (char const *)path, pathLen);
    if (retErr != EOK) {
        return FS_FAILURE;
    }

    res = f_chdrive(tmpPath);
    if (res != FR_OK) {
        return FS_FAILURE;
    }

    return FS_SUCCESS;
}

2.4 匹配文件卷

函数FsPartitionMatch()根据传入的文件路径获取对应的卷索引,在挂载、卸载、格式化等接口中使用。⑴处如果传入的是卷名称,获取顶级目录名称,即路径的第一级目录,前后不包含路径分隔符/。⑵如果传入的是路径名称,获取顶级路径,截止到第一个分隔符/。否则执行⑶,赋值路径中的名称。然后遍历每一个卷,如果获取的顶级目录名称等于卷名称,则返回对应的卷数组索引。否则返回FS_FAILURE。

static int FsPartitionMatch(const char *path, int flag)
{
    INT32 ret;
    UINT32 index;
    CHAR tmpName[FF_MAX_LFN] = {0};

    if (path == NULL) {
        return FS_FAILURE;
    }

    switch ((UINT32)flag & NAME_MASK) {
        case VOLUME_NAME:
⑴          ret = sscanf_s(path, "/%[^/]", tmpName, FF_MAX_LFN);
            if (ret <= 0) {
                return FS_FAILURE;
            }
            break;
        case PATH_NAME:
⑵          ret = sscanf_s(path, "%[^/]", tmpName, FF_MAX_LFN);
            if (ret <= 0) {
                return FS_FAILURE;
            }
            break;
        case PART_NAME:
        default:
⑶          ret = strcpy_s(tmpName, FF_MAX_LFN, path);
            if (ret != EOK) {
                return FS_FAILURE;
            }
    }

    for (index = 0; index < FF_VOLUMES; index++) {
⑷      if (strcmp(tmpName, g_volPath[index]) == 0) {
            return index;
        }
    }
    return FS_FAILURE;
}

2.4 判断文件卷是否可写

函数FsCheckByPath()、FsCheckByID()用于判断文件卷是否可写,传递参数不同。前者传入的是文件路径,转化为卷索引后判断。后者传入的是挂载编号,遍历每一个卷,判断相应卷的编号与传入参数是否相等。

static bool FsCheckByPath(const char *path)
{
    INT32 index;

    index = FsPartitionMatch(path, PATH_NAME);
    if (index == FS_FAILURE) {
        return FS_FAILURE;
    }

    return g_volWriteEnable[index];
}

static bool FsCheckByID(int id)
{
    INT32 index;

    for (index = 0; index < FF_VOLUMES; index++) {
        if (g_fatfs[index].id == id) {
            return g_volWriteEnable[index];
        }
    }
    return false;
}

2.5 标签转换

函数FatFsGetMode()用于把POSIX格式的文件打开标签转换为FatFS文件系统格式的文件打开标签。FatfsErrno()把FatFS文件系统格式的错误号转换为POSIX格式的错误号。

static unsigned int FatFsGetMode(int oflags)
{
    UINT32 fmode = FA_READ;

    if ((UINT32)oflags & O_WRONLY) {
        fmode |= FA_WRITE;
    }

    if (((UINT32)oflags & O_ACCMODE) & O_RDWR) {
        fmode |= FA_WRITE;
    }
    /* Creates a new file if the file is not existing, otherwise, just open it. */
    if ((UINT32)oflags & O_CREAT) {
        fmode |= FA_OPEN_ALWAYS;
        /* Creates a new file. If the file already exists, the function shall fail. */
        if ((UINT32)oflags & O_EXCL) {
            fmode |= FA_CREATE_NEW;
        }
    }
    /* Creates a new file. If the file already exists, its length shall be truncated to 0. */
    if ((UINT32)oflags & O_TRUNC) {
        fmode |= FA_CREATE_ALWAYS;
    }

    return fmode;
}

static int FatfsErrno(int result)
{
    INT32 status = 0;

    if (result < 0) {
        return result;
    }

    /* FatFs errno to Libc errno */
    switch (result) {
        case FR_OK:
            break;

        case FR_NO_FILE:
        case FR_NO_PATH:
        case FR_NO_FILESYSTEM:
            status = ENOENT;
            break;
        ......
        default:
            status = result;
            break;
    }

    return status;
}

©著作权归作者所有,如需转载,请注明出处,否则将追究法律责任
3
收藏 1
回复
举报
回复
    相关推荐