Chinaunix首页 | 论坛 | 博客
  • 博客访问: 26345
  • 博文数量: 47
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 10
  • 用 户 组: 普通用户
  • 注册时间: 2015-07-31 12:10
文章分类
文章存档

2015年(47)

我的朋友

分类: 嵌入式

2015-08-01 09:20:57

摘要:

    本文深入源码分析Coffee文件系统打开文件cfs_open函数技术细节,包括flags、get_available_fd、file_end、find_file、FILE_FREE、read_header、next_file、load_file。


一、cfs_open

    cfs_open用于打开一个文件,成功打开返回文件描述符fd,否则返回-1。如果文件不存在,则试图创建新文件。cfs_open首先获得最小未使用的文件描述符fd(若没有可用的fd,则返回-1),而后查看文件是否已缓存并且对应物理文件有效,若是则打开成功。若物理上没有该文件,则试图创建。值得注意的是,即便文件创建成功也可能返回-1(当coffee_files[COFFEE_MAX_OPEN_FILES]数组没有可用项时)。

cfs_open算法流程图如下:

图1 cfs_open算法流程

cfs_open源代码如下:

  1. int cfs_open(const char *name, int flags) //flags见1.1
  2. {
  3.   int fd;
  4.   struct file_desc *fdp;

  5.   fd = get_available_fd(); //获得最小可用的fd,见1.2
  6.   if (fd < 0)
  7.   {
  8.     PRINTF("Coffee: Failed to allocate a new file descriptor!\n");
  9.     return -1;
  10.   }

  11.   fdp = &coffee_fd_set[fd];
  12.   fdp->flags = 0; //此处的0表示COFFEE_FD_FREE,源码#define COFFEE_FD_FREE 0x0

  13.   fdp->file = find_file(name); //寻找name对应的文件(不存在、在物理FLASH但没缓存、缓存),见二

  14.   /***没有name对应的文件,则试图创建新文件,见1.3***/
  15.   if (fdp->file == NULL)
  16.   {
  17.     if ((flags &(CFS_READ | CFS_WRITE)) == CFS_READ) //若flags是001或101,则返回-1
  18.     {
  19.       return - 1;
  20.     }
  21.     fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0);
  22.     if (fdp->file == NULL)
  23.     {
  24.       return - 1;
  25.     }
  26.     fdp->file->end = 0; //新创建的空文件,文件末尾自然是0
  27.   }
  28.   /*file不为NULL(即找到文件并缓存),寻找文件末尾位置*/
  29.   else if (fdp->file->end == UNKNOWN_OFFSET) //找到文件末尾的位置,见1.4
  30.   {
  31.     fdp->file->end = file_end(fdp->file->page);
  32.   }

  33.   fdp->flags |= flags;
  34.   fdp->offset = flags &CFS_APPEND ? fdp->file->end: 0; //如果文件打开方式是追加APPEND,则将文件偏移量offset设到文件末尾,否则设成0
  35.   fdp->file->references++; //文件引用次数加1

  36.   return fd;
  37. }

1.1 flags

flags表示文件是以什么(只读、写、追加)打开,源码如下(在contiki/core/cfs/cfs.h):

  1. #ifndef CFS_READ
  2.   #define CFS_READ 1
  3. #endif

  4. #ifndef CFS_WRITE
  5.   #define CFS_WRITE 2
  6. #endif

  7. #ifndef CFS_APPEND
  8.   #define CFS_APPEND 4
  9. #endif

    可以单独设CFS_READ、CFS_WRITE、CFS_APPEND其中的一个,也可以用或(即"|")连接设置多个flags(如CFS_READ | CFS_APPEND)。设置了CFS_APPEND意味着CFS_WRITE已被设置(注1),即从文件末尾写。如果设置CFS_WRITE而没有设置 CFS_APPEND,则文件被截断(truncate)为0,即文件大小为0了[2](这点跟Linux不同)。

    注1:尽管[2]ContikiWiki,指出设置了CFS_APPEND意味着CFS_WRITE也被设置了,最近在分析写入文件cfs_write,发现情况并非如此。必须指定CFS_WRITE才能进行写,详情见博文《Contiki学习笔记:Coffee文件系统写入文件cfs_write》,部分源码如下:

  1. if(!(FD_VALID(fd) && FD_WRITABLE(fd)))
  2. {
  3.    return - 1;
  4. }

  5. #define FD_WRITABLE(fd) (coffee_fd_set[(fd)].flags & CFS_WRITE)

1.2 get_available_fd

    类似于Linux,所有打开文件都通过文件描述符fd引用,函数get_available_fd返回最小未使用的描述符(即缓存file_desc数组coffee_fd_set[]首个flags为COFFEE_FD_FREE),没有可用的fd则返回-1。源码如下:

  1. static int get_available_fd(void)
  2. {
  3.   int i;
  4.   for (i = 0; i < COFFEE_FD_SET_SIZE; i++)
  5.   {
  6.     if (coffee_fd_set[i].flags == COFFEE_FD_FREE)
  7.     {
  8.       return i;
  9.     }
  10.   }
  11.   return - 1;
  12. }

    Coffee将文件描述符file_desc组织成一个数组,作为protected_mem_t的一个成员变量,系统缓存file_desc示意图如下,详情见博文《Contiki学习笔记:Coffee文件组织及若干数据结构》。

图2 Coffee缓存file_desc示意图

1.3 创建文件

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

    首先检查cfs_open参数flags,若flags是001(CFS_READ)或101(CFS_APPEND|CFS_READ),则返回-1,即创建不成功。否则调用 reserve为其分配新页面,并对file_header作一些设置。其中page_count(COFFEE_DYN_SIZE)求得COFFEE_DYN_SIZE字节需要分配多少Coffee页(因为还要包括file_header大小且需考虑页对齐)。详情见博文《Contiki学习笔记:Coffee文件系统创建文件》。

1.4 找到文件末尾位置file_end

    find_file(const char *name),如果name对应的文件没有被缓存或者是缓存了但物理文件失效,则调用load_file将对应的物理FLASH文件缓存到RAM(物理上有这个文件且coffee_files有可用的项),此时file_end是UNKNOWN_OFFSET。因为Coffee没有在file_header存储文件末尾的位置(因为文件长度经常改变,加之FLASH先擦后写特性),所以打开一个文件时必须找到文件末尾的位置。

    如图2所示,cfs_offset_t file_end从文件的最后一页max_pages-1往前找,直到找到空字节(即0x00),就找到file_end了,否则返回0。返回0有两种情况,一种是

图3 file_end函数示意图

file_end源代码如下:

  1. //fdp->file->end = file_end(fdp->file->page);
  2. static cfs_offset_t file_end(coffee_page_t start)
  3. {
  4.   struct file_header hdr;
  5.   unsigned char buf[COFFEE_PAGE_SIZE];
  6.   coffee_page_t page;
  7.   int i;

  8.   read_header(&hdr, start); //读取文件头file_header

  9.   /* An important implication of this is that if the last written bytes are zeroes, then these are skipped from the calculation.*/
  10.   for (page = hdr.max_pages - 1; page >= 0; page--) //从文件最后一页(预留的页而不是实际占有)往前找
  11.   {
  12.     COFFEE_READ(buf, sizeof(buf), (start + page) *COFFEE_PAGE_SIZE); //将Coffee页读入buf
  13.     for (i = COFFEE_PAGE_SIZE - 1; i >= 0; i--)
  14.     {
  15.       if (buf[i] != 0)
  16.       {
  17.         if (page == 0 && i < sizeof(hdr)) //没必要检查file_header
  18.         {
  19.           return 0;
  20.         }

  21.         return 1+i + (page *COFFEE_PAGE_SIZE) - sizeof(hdr);
  22.       }
  23.     }
  24.   }

  25.   return 0; //All bytes are writable
  26. }


二、find_file 

    find_file函数用于找到文件名name对应的file指针。若name对应的文件file是还驻留在内存(即还在coffee_files[COFFEE_MAX_OPEN_FILES]数组里),并且对应的物理文件是有效的,则直接返回file指针,否则扫描整个FLASH,将name对应文件file(在FLASH中但没缓存)缓存到内存(这一步得确保coffee_files数组有可用的项,否则返回空NULL,参见load_file函数)。file可以理解成是file_header的缓存,但不等同(可参见博文《Contiki学习笔记:Coffee文件组织及若干数据结构》)。源代码如下:

  1. static struct file *find_file(const char *name)
  2. {
  3.   int i;
  4.   struct file_header hdr;
  5.   coffee_page_t page;

  6.   /*首先检查name对应的文件file是否还驻留在内存(是否缓存了file_header),即是否还在coffee_files[COFFEE_MAX_OPEN_FILES]数组里,若有,则直接返回file*/
  7.   for (i = 0; i < COFFEE_MAX_OPEN_FILES; i++) //遍历整个coffee_files[COFFEE_MAX_OPEN_FILES]数组
  8.   {
  9.     /*若没有max_pages不为0的文件file,则扫描整个FLASH(即跳出for循环,执行后面内容)*/
  10.     if (FILE_FREE(&coffee_files[i])) //file的max_pages是否为0,见2.1
  11.     {
  12.       continue;
  13.     }

  14.     /*如果找到了RAM缓存的file数组coffee_files[COFFEE_MAX_OPEN_FILES]中有空闲的项*/
  15.     read_header(&hdr, coffee_files[i].page); //读取文件的元数据,即file_header,详情见2.2
  16.     if (HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) //通过文件头file_header判断物理文件是否有效,见2.3
  17.     {
  18.       return &coffee_files[i];//文件元数据file_header被缓存且物理文件有效,则直接返回file
  19.     }
  20.   }

  21.   /*否则顺序扫描整个FLASH(没有缓存该文件file)*/
  22.   for (page = 0; page < COFFEE_PAGE_COUNT; page = next_file(page, &hdr))//next_file见2.4
  23.   {
  24.     read_header(&hdr, page);
  25.     if (HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name)==0) //见2.3
  26.     {
  27.       return load_file(page, &hdr); //见2.5
  28.     }
  29.   }

  30.   return NULL; //如果物理FLASH没有该name对应的文件,也返回NULL
  31. }

    典型的情况,系统启动后,第一次打开文件,Coffee文件系统需要顺序扫描整个FLASH,因为Coffee格式化将coffee_files[COFFEE_MAX_OPEN_FILES]数组的结构成员变量都初始化为0(当然也包括max_pags),详情见博文《Contiki学习笔记:Coffee文件系统格式化cfs_coffee_format》。

2.1 FILE_FREE

    max_pages是file的成员变量,用来表示文件预设的最大页面数(所有的Coffee文件创建时都会被预先设置)。在Coffee格式化时(参见博文《Contiki学习笔记:Coffee文件系统格式化cfs_coffee_format》),max_pages被置为0。当删除一个文件cfs_remove时,max_pages也被置为0。

  1. #define FILE_FREE(file) ((file)->max_pages == 0)

2.2 read_header

read_header用于读取物理文件的元数据(即file_header),源码如下: 

  1. static void read_header(struct file_header *hdr, coffee_page_t page)
  2. {
  3.   COFFEE_READ(hdr, sizeof(*hdr), page *COFFEE_PAGE_SIZE);
  4.   #if DEBUG
  5.     if (HDR_ACTIVE(*hdr) && !HDR_VALID(*hdr))
  6.     {
  7.       PRINTF("Invalid header at page %u!\n", (unsigned)page);
  8.     }
  9.   #endif
  10. }

    宏COFFEE_READ直接映射到平台相关的FLASH读函数(如stm32_flash_read),从offset处读取size字节存放到buf。其中offset是指从文件系统管理的FLASH处(即COFFEE_START,因为有些NOR FLASH将一部分空间用于存放代码)开始算的偏移量。

  1. #define COFFEE_READ(buf, size, offset) stm32_flash_read(COFFEE_START+offset, buf, size)

  2. void stm32_flash_read(u32_t address, void *data, u32_t length)
  3. {
  4.   u8_t *pdata = (u8_t*)address;
  5.   ENERGEST_ON(ENERGEST_TYPE_FLASH_READ);
  6.   memcpy(data, pdata, length);
  7.   ENERGEST_OFF(ENERGEST_TYPE_FLASH_READ);
  8. }

2.3 file_header的flags

    文件头file_header(文件的元数据)的flags,为方便操作,Coffee将这些flags单独定义成宏,源码如下,各个标志位的含义见博文《 Contiki学习笔记:Coffee文件组织及若干数据结构 》。

  1. /* File header flags. */
  2. #define HDR_FLAG_VALID 0x1 /* Completely written header. */
  3. #define HDR_FLAG_ALLOCATED 0x2 /* Allocated file. */
  4. #define HDR_FLAG_OBSOLETE 0x4 /* File marked for GC. */
  5. #define HDR_FLAG_MODIFIED 0x8 /* Modified file, log exists. */
  6. #define HDR_FLAG_LOG 0x10 /* Log file. */
  7. #define HDR_FLAG_ISOLATED 0x20 /* Isolated page. */

Coffee定义了若干宏来判断文件的状态(依据file_header中的flags),如下:

  1. /* File header macros. */
  2. #define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))

  3. #define HDR_VALID(hdr) CHECK_FLAG(hdr, HDR_FLAG_VALID) //若V标志置1,则返回真
  4. #define HDR_ALLOCATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ALLOCATED) //若A标志置1,则返回真
  5. #define HDR_OBSOLETE(hdr) CHECK_FLAG(hdr, HDR_FLAG_OBSOLETE) //若O标志置1,则返回真
  6. #define HDR_MODIFIED(hdr) CHECK_FLAG(hdr, HDR_FLAG_MODIFIED) //若M标志置1,则返回真
  7. #define HDR_LOG(hdr) CHECK_FLAG(hdr, HDR_FLAG_LOG) //若L标志置1,则返回真 
  8. #define HDR_ISOLATED(hdr) CHECK_FLAG(hdr, HDR_FLAG_ISOLATED) //若I标志置1,则返回真 

  9. #define HDR_FREE(hdr) !HDR_ALLOCATED(hdr) //若A标志置0,则返回真
  10. #define HDR_ACTIVE(hdr) (HDR_ALLOCATED(hdr) && !HDR_OBSOLETE(hdr) && !HDR_ISOLATED(hdr))//若A为1,O为0,I为0,则返回真

    从以上分析可以看出,(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name)==0)含义就是文件正在使用(A),并且不是孤立(I)和无效(O)页,微日志文件没有被修改(?),且文件名相同,只有这样该文件的元数据才算被缓存,够复杂的吧:-) 事实上,就是判断物理文件有效性。考虑这样的情况(RAM确实缓存了文件元数据file_header,但物理文件已失效),写入文件数据超过文件预留的大小,此时,Coffee会新建一个文件,将原始文件和微日志文件拷贝到新文件,并将原来的文件标记为失效。

2.4 next_file函数

    Coffee用快速跳跃(quick-skip)算法,快速跳过空闲的区域,以快速寻找下一个文件。算法最坏情况是遇到多个长连续的孤立页(multiple long sequences of isolated pages),但这种情况并不常见[3]。源码如下:

  1. static coffee_page_t next_file(coffee_page_t page, struct file_header *hdr)
  2. {
  3.   if (HDR_FREE(*hdr))
  4.   {
  5.     return (page + COFFEE_PAGES_PER_SECTOR) &~(COFFEE_PAGES_PER_SECTOR - 1);
  6.   }
  7.   else if (HDR_ISOLATED(*hdr))
  8.   {
  9.     return page + 1;
  10.   }
  11.   return page + hdr->max_pages;
  12. }

2.5 load_file

    load_file将文件的元数据file_header缓存到RAM的file,如果能在coffee_files[COFFEE_MAX_OPEN_FILES]数组中找到可用项(即是否有文件file的max_pages为0或者引用次数为0的项),就缓存,否则返回空NULL。源代码如下:

  1. static struct file *load_file(coffee_page_t start, struct file_header *hdr)
  2. {
  3.   int i, unreferenced, free;
  4.   struct file *file;

  5.   /*首先检查内存是否缓存了file,即是否在coffee_files[COFFEE_MAX_OPEN_FILES]数组中*/
  6.   for (i = 0, unreferenced = free = - 1; i < COFFEE_MAX_OPEN_FILES; i++) //遍历整个coffee_files[COFFEE_MAX_OPEN_FILES]数组
  7.   {
  8.     /*试图从coffee_files[COFFEE_MAX_OPEN_FILES]数组找到第一个max_pages为0的file(表示该项空闲,可使用)*/
  9.     if (FILE_FREE(&coffee_files[i]))
  10.     {
  11.       free = i;
  12.       break;
  13.     }
  14.     /*退而求其次,查看coffee_files[COFFEE_MAX_OPEN_FILES]数组是否有引用次数为0的项*/
  15.     else if (FILE_UNREFERENCED(&coffee_files[i]))
  16.     {
  17.       unreferenced = i;
  18.     }
  19.   }

  20.   /*如果coffee_files[COFFEE_MAX_OPEN_FILES]数组中的file,即没有max_pages为0,也没有引用次数为0的,那么就返回空*/
  21.   if (free == - 1)
  22.   {
  23.     if (unreferenced != - 1)
  24.     {
  25.       i = unreferenced;
  26.     }
  27.     else
  28.     {
  29.       return NULL;
  30.     }
  31.   }

  32.   /*将物理上文件的file_header(存放在文件的开始处)缓存到内存的file*/
  33.   file = &coffee_files[i];
  34.   file->page = start;
  35.   file->end = UNKNOWN_OFFSET; //因为文件大小经常变化,所以在file_header不存储文件末尾位置
  36.   file->max_pages = hdr->max_pages;
  37.   file->flags = 0;
  38.   if (HDR_MODIFIED(*hdr))
  39.   {
  40.     file->flags |= COFFEE_FILE_MODIFIED; //如果文件被修改(表示日志存在),#define COFFEE_FILE_MODIFIED 0x1
  41.   }
  42.   file->record_count = - 1; //此时还不知道日志数量

  43.   return file;
  44. }

FILE_UNREFERENCED宏定义如下,即判断文件引用次数是否为0,源代码如下:

  1. #define FILE_UNREFERENCED(file) ((file)->references == 0)

HDR_MODIFIED宏,源代码如下:

  1. #define HDR_MODIFIED(hdr) CHECK_FLAG(hdr, HDR_FLAG_MODIFIED)

  2. #define CHECK_FLAG(hdr, flag) ((hdr).flags & (flag))

莫非内存file的flags与file_header的flags各位含义不同???源码如下:

  1. #define COFFEE_FILE_MODIFIED 0x1
  2. #define HDR_FLAG_MODIFIED 0x8


更多Contiki学习笔记可通过博文《Contiki学习笔记:目录》索引访问。


上文图1~2源文件如下:

 Coffee缓存fd示意图.rar  

 file_end函数示意图.rar 

 cfs_open算法流程图.rar   


参考资料:

[1] Enabling Large-Scale Storage in Sensor Networks with the Coffee File System.pdf

[2]

[3] Contiki源代码

阅读(529) | 评论(0) | 转发(0) |
给主人留下些什么吧!~~