在Coffee,没有单独的文件创建函数。在name对应文件不存在的情况下,为cfs_open指定flags(取000010011100110111)可创建新文件,本文深入源码并用多组示意图分析Coffee创建文件技术细节。包括find_filepage_countreservefind_contiguous_pageswrite_header

1. 概述

在Linux,用以下两种方式创建一个新文件(两种方式等价):

int creat(const char *pathname, mode_t mode);   //不足之处是它以只写的方式打开创建的文件
int open(const char *pathname, O_WRONLY|O_CREAT|O_TRUNC, mode_t mode);  //mode指定访问权限

但在Coffee,没有单独的文件创建函数。虽有cfs_open函数,但flags只能是CFS_READ`CFS_WRITECFS_APPEND,没有类似O_CREAT,最开始以为Coffee少了创建文件相关函数(因为Contki源码包的测试例子,有些问题),最近花了不少时间分析Coffee打开文件函数cfs_open,才搞明白。

打开一个物理上不存在的文件,Coffee会试图创建新的文件。之所以说"试图",是因为创建并不一定会成功,得满足以下条件,才会创建成功:

  • cfs_open函数的flags不能是(CFS_READ|CFS_APPEND)或者CFS_READ,见2.2

  • 物理FLASH有连续pages空闲页或者经过垃圾回收后有连续的pages空闲页

值得注意的是,即便文件创建成功也可能返回-1(当coffee_files[COFFEE_MAX_OPEN_FILES]数组没有可用项时,新创建的文件并没有缓存到数组coffee_files)。

2. 源码分析

Coffee打开文件cfs_open函数分析见博文《打开文件cfs_open》,以下选取与文件创建相关代码进行分析,如下:

fdp->file = find_file(name); //见2.1
if (fdp->file == NULL) //物理上没有name对应的文件file,则试图创建之
{
  if ((flags &(CFS_READ | CFS_WRITE)) == CFS_READ)//检查flags是否符合创建文件要求,见2.2
  {
    return -1;
  }

  fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0); //见三
  if (fdp->file == NULL)
  {
    return - 1;
  }

  fdp->file->end = 0; //新创建的空文件,文件末尾自然是0
}

/*此处略去与文件创建无关的4行代码*/

fdp->flags |= flags;
fdp->offset = flags &CFS_APPEND ? fdp->file->end: 0; //如果flags没有设置CFS_APPEND,则文件偏移量设为0
fdp->file->references++; //文件引用次数加1

return fd;

2.1 find_file

执行find_file函数,若name对应的文件file还驻留在内存(即若file还在coffee_files[COFFEE_MAX_OPEN_FILES]数组里),并且对应的物理文件是有效的,则直接返回file,否则扫描整个FLASH,将name对应文件file(在FLASH中但没缓存)缓存到内存(这一步得确保coffee_files数组有可用的项,否则返回空NULL,参见load_file函数)。关于find_file详细分析见《打开文件cfs_open》。显然,这里要处理的情况是后者,即物理上没有name对应的文件file,则试图创建之。

2.2 cfs_open函数flags判断

首先检查cfs_open参数flagsflags至少有一项是CFS_WRITE,才有可能创建新文件,源代码如下:

if ((flags &(CFS_READ | CFS_WRITE)) == CFS_READ) //若flags仅仅是CFS_READ则返回-1,即不创建新文件
{
  return - 1;
}

那么,if条件在什么时候会返回真呢,如下图所示,当flags中没有CFS_WRITE且有CFS_READ时(CFS_APPEND可有可无),即flags取值为001101,条件为真。也就是说,

img

图1 cfs_open函数flags判断示意图

flags含有CFS_WRITE或者没有CFS_READ或者两者皆有之时才有可能创建新文件(CFS_APPEND可有可无),即flags000010011100110111。(这样的设计让我很费解)

2.3 page_count

page_count函数用于算出给定的size需要多少Coffee页,即size加上file_header大小,并且按Coffee页(Coffee页大小与FLASH物理页大小不一定等同,见cfs_coffee_arch.h文件)对齐,示意图如下:

img

图2 page_count函数示意图

在本例中,page_count返回2,即2页Coffee_Page,源代码如下:

//page_count(COFFEE_DYN_SIZE)
#define COFFEE_DYN_SIZE (COFFEE_PAGE_SIZE*1) //在cfs_coffee_arch.h可以配置

static coffee_page_t page_count(cfs_offset_t size)
{
  return (size + sizeof(struct file_header) + COFFEE_PAGE_SIZE - 1) /COFFEE_PAGE_SIZE;
}

3. 创建文件主体函数reserve

reserve是创建新文件的主体函数,首先进行参数验证,接着查看物理FLASH是否有连续pages空闲页,若有,则返回该文件即将占有页的第一页。否则,调用Coffee垃圾回收,再次查看物理FLASH是否有连续pages空闲页,如果还没有就返回NULL

值得注意的是,即便文件创建成功也可能返回-1(当coffee_files[COFFEE_MAX_OPEN_FILES]数组没有可用项时,新创建的文件并没有缓存到数组coffee_files)。源代码如下:

//fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0);
static struct file *reserve(const char *name, coffee_page_t pages,int allow_duplicates,unsigned flags)
{
  struct file_header hdr;
  coffee_page_t page;
  struct file *file;

  if (!allow_duplicates && find_file(name) != NULL) //参数验证,在本例中,!allow_duplicates为0,条件为假
  {
    return NULL;
  }

  page = find_contiguous_pages(pages); //查看物理FLASH是否有连续pages空闲页,若有,则返回该文件即将占有页的第一页。见3.1
  if (page == INVALID_PAGE) //没有可用的页,尝试着调用垃圾回收
  {
    if (*gc_wait)
    {
      return NULL;
    }
    collect_garbage(GC_GREEDY); //垃圾回收
    page = find_contiguous_pages(pages); //再次查看物理FLASH是否有连续pages空闲页,见3.1
    if (page == INVALID_PAGE) //如果垃圾回收后还没找到可用的页,就返回NULL
    {
      *gc_wait = 1;
      return NULL;
    }
  }

  memset(&hdr, 0, sizeof(hdr));                 //将hdr成员变量初始化为0
  memcpy(hdr.name, name, sizeof(hdr.name) - 1); //给file_header的成员变量name赋值
  hdr.max_pages = pages;                        //实参pages,在本例等于2
  hdr.flags = HDR_FLAG_ALLOCATED | flags;       //file_header的flags的A位置1,表示文件正在使用
  write_header(&hdr, page);                     //将file_header写入物理FLASH,见3.2

  PRINTF("Coffee: Reserved %u pages starting from %u for file %s\n", pages, page, name);

  file = load_file(page, &hdr); //如果coffee_files[COFFEE_MAX_OPEN_FILES]数组没有可用项,就返回NULL了(但此时文件已创建完毕)
  if (file != NULL)
  {
    file->end = 0;
  }

  return file;
}

3.1 find_contiguous_pages

find_contiguous_pagesnext_free(全局变量,指向下一个空闲页)指向的页面开始查找,若找到start+amount<=next_file(),则返回start,否则返回INVALID_PAGE(说明没有足够空闲页甚至没有空闲页供使用)。

img

图3 find_contiguous_pages函数示意图

find_contiguous_pages函数源代码如下:

static coffee_page_t find_contiguous_pages(coffee_page_t amount)
{
  coffee_page_t page, start;
  struct file_header hdr;

  start = INVALID_PAGE; //初始化start,#define INVALID_PAGE ((coffee_page_t)-1)

  for (page = *next_free; page < COFFEE_PAGE_COUNT;) //从next_free指向的页面开始查找
  {
    read_header(&hdr, page);
    if (HDR_FREE(hdr)) //判断file_header的flags中A(Allocated)位,若A位为0,则HDR_FREE(hdr)返回真,即页面是空闲的
    {
      if (start == INVALID_PAGE)
      {
        start = page;
        if (start + amount >= COFFEE_PAGE_COUNT) //如果剩下的页面数不足以分配,则跳出循环,直接返回INVALID_PAGE
        {
          break;
        }
      }

      /***若找到start+amount<=next_file(),则返回start***/
      page = next_file(page, &hdr); //返回下一个文件占用Coffee页的第一页(从page页开始)
      if (start + amount <= page)
      {
        if (start == *next_free)
        {
          *next_free = start + amount; //找到空闲页,分配成功。更新next_free
        }
        return start;
      }
    }
    else //如果文件正在使用(即A位为1),跳到下一个文件
    {
      start = INVALID_PAGE;
      page = next_file(page, &hdr);
    }
  }
  return INVALID_PAGE;
}

3.2 write_header

write_headerfile_header写入物理FLASH,源代码如下:

static void write_header(struct file_header *hdr, coffee_page_t page)
{
  hdr->flags |= HDR_FLAG_VALID; //将file_header的flags中的V位置1,即标记文件头是完整的
  COFFEE_WRITE(hdr, sizeof(*hdr), page *COFFEE_PAGE_SIZE);
}

#define HDR_FLAG_VALID 0x1
#define COFFEE_WRITE(buf, size, offset) stm32_flash_write(COFFEE_START + offset, buf, size)

本文图1~3源文件如下:

本文系Spark & Shine原创,转载需注明出处本文最近一次修改时间 2022-03-21 15:34

results matching ""

    No results matching ""