Chinaunix首页 | 论坛 | 博客
  • 博客访问: 42890
  • 博文数量: 2
  • 博客积分: 0
  • 博客等级: 民兵
  • 技术积分: 25
  • 用 户 组: 普通用户
  • 注册时间: 2017-10-30 16:54
文章分类

全部博文(2)

文章存档

2017年(2)

我的朋友

分类: 嵌入式

2017-11-21 21:42:23

摘要:

    本文分析了Coffee文件系统读取文件cfs_read技术细节,包括FD_VALID、FD_READABLE、FILE_MODIFIED、absolute_offset、COFFEE_READ、COFFEE_MICRO、log_param、read_header、read_log_page、adjust_log_config、modify_log_buffer、get_record_index。cfs_read,当没有微日志文件时,cfs_read直接从原始文件读取,否则从微日志文件读取。


一、cfs_read

1.1 概述

    cfs_read从打开文件fd读取数据,存放在buf,读取的字节数是size,返回实际读取的字节数。从预定义宏指令可以看出,cfs_read总体上包括两种情况:配置了微日志与没有配置微日志。但都必须进行参数验证。cfs_read先进行参数验证(fd有效性、读取权限、size),而后再读取(无微日志文件的读和有微日志文件的读),源码如下:

  1. int cfs_read(int fd, void *buf, unsigned size)
  2. {
  3.   struct file_desc *fdp;
  4.   struct file *file;

  5.   #if COFFEE_MICRO_LOGS
  6.     struct file_header hdr;
  7.     struct log_param lp;
  8.     unsigned bytes_left;
  9.     int r;
  10.   #endif

  11.   /*****fd有效性检查及权限判断,见1.2********/
  12.   if (!(FD_VALID(fd) && FD_READABLE(fd)))
  13.   {
  14.     return - 1;
  15.   }

  16.   /********************************/
  17.   fdp = &coffee_fd_set[fd];
  18.   file = fdp->file;

  19.   /******size参数调整,见1.3********/
  20.   if (fdp->offset + size > file->end)
  21.   {
  22.     size = file->end - fdp->offset;
  23.   }


  24.   /******没有微日志文件读,见二***********/
  25.   if (!FILE_MODIFIED(file))
  26.   {
  27.     COFFEE_READ(buf, size, absolute_offset(file->page, fdp->offset));

  28.     fdp->offset += size;
  29.     return size;
  30.   }

  31.   /******有微日志文件读,见三***********/
  32.   #if COFFEE_MICRO_LOGS
  33.     read_header(&hdr, file->page);
  34.     for (bytes_left = size; bytes_left > 0; bytes_left -= r)
  35.     {
  36.       r = - 1;
  37.       lp.offset = fdp->offset;
  38.       lp.buf = buf;
  39.       lp.size = bytes_left;

  40.       r = read_log_page(&hdr, file->record_count, &lp);

  41.       if (r < 0)
  42.       {
  43.         COFFEE_READ(buf, lp.size, absolute_offset(file->page, fdp->offset));
  44.         r = lp.size;
  45.       }
  46.       fdp->offset += r;
  47.       buf = (char*)buf + r;
  48.     }
  49.   #endif

  50.   return size;
  51. }

1.2 FD_VALID宏和FD_READABLE宏

(1)FD_VALID

    FD_VALID宏用于判断cfs_read函数传进来的fd是否有效,不能小于0(如,-1是get_available_fd函数分配不到fd的返回值),也不能大于FD的上限(即COFFEE_FD_SET_SIZE),其对应的file_desc的标志flags不能为空闲(COFFEE_FD_FREE),源码如下:

  1. #define FD_VALID(fd) ((fd)>=0 && (fd)<COFFEE_FD_SET_SIZE && coffee_fd_set[(fd)].flags!=COFFEE_FD_FREE)

(2)FD_READABLE

FD_READABLE宏用于打开的文件有读权限,源码如下:

  1. #define FD_READABLE(fd) (coffee_fd_set[(fd)].flags & CFS_READ)

1.3 size参数调整

    如果文件欲读取的字节数(size)超过文件末尾,则将读取字节数设为最大能读取的字节数(即最多只能读到文件末尾),源码如下:

  1. if (fdp->offset + size > file->end)
  2. {
  3.   size = file->end - fdp->offset;
  4. }


二、没有微日志的读

没有微日志情况下的读,略去参数验证及与本节无关代码如下:

  1. struct file_desc *fdp;
  2. struct file *file;

  3. fdp = &coffee_fd_set[fd];
  4. file = fdp->file;

  5. if (!FILE_MODIFIED(file)) //判断该文件是否只是原始文件(意味着没有修改过,没有微日志文件),详情见2.1
  6. {
  7.   COFFEE_READ(buf, size, absolute_offset(file->page, fdp->offset)); //absolute_offset见2.2,COFFEE_READ见2.3
  8.   fdp->offset += size; //更新偏移量
  9.   return size; //返回实际读写字节数
  10. }

2.1 FILE_MODIFIED

    file的flags只有两种取值:0和COFFEE_FILE_MODIFIED,将物理文件缓存时(load_file函数,详情见博文《Contiki学习笔记:Coffee文件系统打开文件cfs_open》2.5),如果物理文件元数据file_header的flags的M位为1的话(即物理文件被修改,日志存在),则file->flags设为COFFEE_FILE_MODIFIED,否则设为0。部分代码如下:

  1. //load_file函数部分代码
  2. file->flags = 0;

  3. if (HDR_MODIFIED(*hdr))
  4. {
  5.   file->flags |= COFFEE_FILE_MODIFIED; //如果文件被修改(表示日志存在),#define COFFEE_FILE_MODIFIED 0x1
  6. }

再看下面的代码就很清楚了,如果file->flags为COFFEE_FILE_MODIFIED,则返回真。

  1. #define FILE_MODIFIED(file) ((file)->flags & COFFEE_FILE_MODIFIED)

  2. #define COFFEE_FILE_MODIFIED 0x1

2.2 absolute_offset

    absolute_offset函数返回文件系统的绝对偏移量(但不是物理偏移量),得出的是从COFFEE_START处的偏移量,而不是从FLASH开始外的偏移量,因为COFFEE_START往往不等于FLASH_START(因为会拿出FLASH一部分空间用于存放代码,不受文件系统管理)。absolute_offset源码如下:

  1. //absolute_offset(file->page, fdp->offset)
  2. static cfs_offset_t absolute_offset(coffee_page_t page, cfs_offset_t offset)
  3. {
  4.   return page *COFFEE_PAGE_SIZE + sizeof(struct file_header) + offset;
  5. }

2.3 COFFEE_READ宏

    COFFEE_READ宏直接定位到硬件相关的读函数,移植Coffee文件的时候需要映射过去(在cfs-coffee-arch.h),源码如下:

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

    COFFEE_READ与stm32_flash_read映射关系如下图,在原有的offset加上COFFEE_START,就得到了从FLASH_START处的偏移量,即实际物理FLASH位置。

图1 COFFEE_READ与stm32_flash_read映射关系


三、有微日志文件的读取

有微日志情况下的读,略去参数验证及与本节无关代码如下:

  1. //int cfs_read(int fd, void *buf, unsigned size)
  2. struct file_desc *fdp;
  3. struct file *file;

  4. #if COFFEE_MICRO_LOGS //见3.1
  5.   struct file_header hdr;
  6.   struct log_param lp; //见3.2
  7.   unsigned bytes_left;
  8.   int r;
  9. #endif

  10. fdp = &coffee_fd_set[fd];
  11. file = fdp->file;

  12. #if COFFEE_MICRO_LOGS
  13.   read_header(&hdr, file->page); //读取物理文件的元数据file_header,见3.3
  14.   /*如果微日志文件有数据,直接从微日志文件读取数据,否则从原始文件读取*/
  15.   for (bytes_left = size; bytes_left > 0; bytes_left -= r) //读取日志记录,当size跨越多个日志记录时,则需要逐个记录读取
  16.   {
  17.     r = - 1;
  18.     /*初始化结构体log_param类型的lp*/
  19.     lp.offset = fdp->offset;
  20.     lp.buf = buf;
  21.     lp.size = bytes_left;

  22.     r = read_log_page(&hdr, file->record_count, &lp); //见四

  23.     /*如果在微日志文件找不到数据,就直接从原始文件读取*/
  24.     if (r < 0)
  25.     {
  26.       COFFEE_READ(buf, lp.size, absolute_offset(file->page, fdp->offset)); //详情见1.3
  27.       r = lp.size;
  28.     } 
  29.     fdp->offset += r;
  30.     buf = (char*)buf + r;
  31.   }
  32. #endif

  33. return size;

3.1 COFFEE_MICRO_LOGS

    微日志是Coffee文件系统的一大亮点,当文件需要修改时,创建微日志结构并链接到原始文件,而不是创建新的文件。系统默认是配置微日志,源码如下:

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

不过也可以不配置微日志,在cfs_coffee_arch.h文件将COFFEE_MICRO_LOGS定义为0,如下:

  1. #define COFFEE_MICRO_LOGS 0

3.2 log_param

    在Google搜没找到buggy compiler相关资料,不晓得是什么东西。通过源码分析,当欲读取大小跨越两个日志记录(甚至更多)时,log_param用于辅助分开读取,即先读前一个日志记录包含的内容,再读取后一个日志记录包含的内容。源码如下:

  1. /* This is needed because of a buggy compiler. */
  2. struct log_param
  3. {
  4.   cfs_offset_t offset;
  5.   const char *buf;
  6.   uint16_t size;
  7. };

3.3 read_header

read_header将物理文件的元数据file_header读出来放在hdr结构体,源代码如下:

  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. }


四、read_log_page

    read_log_page将日志记录读到lp->buffer中,返回实际读取的大小lp->size。首先检查一些参(log_record_size、log_records、search_records),必要时进行一些调整,而后求得欲读取位置对应的索引表项,进而求得偏移量,最后调用COFFEE_READ读取数据。源代码如下:

  1. //r = read_log_page(&hdr, file->record_count, &lp);
  2. #if COFFEE_MICRO_LOGS
  3.   static int read_log_page(struct file_header *hdr, int16_t record_count, struct log_param *lp)
  4.   {
  5.     uint16_t region;
  6.     int16_t match_index;
  7.     uint16_t log_record_size;
  8.     uint16_t log_records;
  9.     cfs_offset_t base;
  10.     uint16_t search_records;

  11.     adjust_log_config(hdr, &log_record_size, &log_records); //若log_record_size和log_records为0,则设成默认值,详情见4.1
  12.     region = modify_log_buffer(log_record_size, &lp->offset, &lp->size); //设置offset,必要时调整size,返回region,见4.2

  13.     search_records = record_count < 0 ? log_records : record_count; //需要搜索的记录数,record_count是file的成员变量

  14.     match_index = get_record_index(hdr->log_page, search_records, region); //见4.3
  15.     if (match_index < 0)
  16.     {
  17.       return - 1;
  18.     }

  19.     /***求出文件欲读取的Coffee偏移量(即从COFFEE_START开始处的偏移量),见4.5***/
  20.     base = absolute_offset(hdr->log_page, log_records *sizeof(region)); //索引表大小
  21.     base += (cfs_offset_t)match_index *log_record_size; //跳过match_index个记录
  22.     base += lp->offset;
  23.     
  24.     COFFEE_READ(lp->buf, lp->size, base);

  25.     return lp->size;
  26.   }
  27. #endif

4.1 adjust_log_config

    在博文《Contiki学习笔记:Coffee文件组织及若干数据结构》分析结构体file_header成员变量log_record_size,说如果该项为0,则会被设成默认值(即COFFEE_PAGE_SIZE ),就是在这里设置的。log_records表示日志可以容纳的记录数量(log records denotes the number of records that the log can hold),如果该项为0,则设成(COFFEE_LOG_SIZE)/(*log_record_size)。

  1. //adjust_log_config(hdr, &log_record_size, &log_records);
  2. #if COFFEE_MICRO_LOGS
  3.   static void adjust_log_config(struct file_header *hdr, uint16_t *log_record_size, uint16_t *log_records)
  4.   {
  5.     *log_record_size = hdr->log_record_size == 0 ? COFFEE_PAGE_SIZE : hdr->log_record_size;
  6.     *log_records = hdr->log_records == 0 ? COFFEE_LOG_SIZE / *log_record_size: hdr->log_records;
  7.   }
  8. #endif

4.2 modify_log_buffer

    modify_log_buffer将偏移量(全局的)调整为日志记录内部的偏移量,如果欲读取的大小跨越两个日志记录(甚至更多),则需调整size为从offset到第一日志记录末尾,最后返回region。modify_log_buffer函数示意图如下:

图2 modify_log_buffer函数示意图

modify_log_buffer源代码如下:

  1. //region = modify_log_buffer(log_record_size, &lp->offset, &lp->size),其中lp.offset被初始化为fdp->offset
  2. #if COFFEE_MICRO_LOGS
  3.   static uint16_t modify_log_buffer(uint16_t log_record_size, cfs_offset_t *offset, uint16_t *size)
  4.   {
  5.     uint16_t region;

  6.     region = *offset / log_record_size; //算出从第几个region开始
  7.     *offset %= log_record_size;

  8.     if (*size > log_record_size - *offset) //当size跨越两个(甚至更多)日志记录时,需调整size
  9.     {
  10.       *size = log_record_size - *offset;
  11.     }

  12.     return region;
  13.   }
  14. #endif

4.3 log_records & record_count & search_records

    log_records是file_header的成员变量,表示日志可以容纳的记录数量(log records denotes the number of records that the log can hold)。如果为0,会自动调节成COFFEE_LOG_SIZE/*log_record_size,而log_record_size若为0,则会自动调成成COFFEE_PAGE_SIZE,这些是在adjust_logo_config函数完成的(见4.1)。

    record_count是file的成员变量,表示文件实际的日志记录数量,加载文件load_file将record-count设为-1(源码注释-We don't know the amount of records yet)。

    search_records是read_log_page函数定义的一个成员变量,表示需要搜索的记录数,若已有record_count值,则用record_count初始化search_records,否则用log_records,源码如下:

  1. //static int read_log_page(struct file_header *hdr, int16_t record_count, struct log_param *lp)
  2. search_records = record_count < 0 ? log_records : record_count;

4.4 get_record_index

get_record_index求得region日志记录对应的索引表项,源代码如下:

  1. //match_index = get_record_index(hdr->log_page, search_records, region);
  2. #if COFFEE_MICRO_LOGS
  3.   static int get_record_index(coffee_page_t log_page, uint16_t search_records, uint16_t region)
  4.   {
  5.     cfs_offset_t base;
  6.     uint16_t processed;
  7.     uint16_t batch_size;
  8.     int16_t match_index, i;

  9.     base = absolute_offset(log_page, sizeof(uint16_t) *search_records); //返回索引表最后一个字节处,见4.4.1
  10.     batch_size = search_records > COFFEE_LOG_TABLE_LIMIT ? COFFEE_LOG_TABLE_LIMIT: search_records; //调整search_records,不能超过COFFEE_LOG_TABLE_LIMIT
  11.     processed = 0; //正在读取的记录
  12.     match_index = - 1;

  13.     {
  14.       uint16_t indices[batch_size]; //存放索引表

  15.       while (processed < search_records && match_index < 0)
  16.       {
  17.         if (batch_size + processed > search_records) //调整batch_size大小,不能超过search_records
  18.         {
  19.           batch_size = search_records - processed;
  20.         }

  21.         base -= batch_size * sizeof(indices[0]); //base从索引表末尾移到索引表开始处,见4.4.1
  22.         COFFEE_READ(&indices, sizeof(indices[0]) *batch_size, base); //读取索引表放入indices[batch_size],见4.4.1

  23.         for (i = batch_size - 1; i >= 0; i--) //索引表从后向前扫描
  24.         {
  25.           /***索引表项正好指向region记录(即找到索引表项),返回match_index***/
  26.           if (indices[i] - 1 == region)
  27.           {
  28.             match_index = search_records - processed - (batch_size - i);
  29.             break;
  30.           }
  31.         }

  32.         processed += batch_size;
  33.       }
  34.     }

  35.     return match_index; //没找到region日录对应的索引项,则返回-1
  36.   }
  37. #endif

4.4.1 读取索引表

    通过absolute_offset求得索引表末尾位置base(如下图红色箭头所示),再通过base -= batch_size * sizeof(indices[0])求得索引表开始位置(如下图绿色箭头所示),最后通过COFFEE_READ将索引表读入indices[]数组。整个过程示意图如下:

图3 读取索引表示意图

4.5 read_log_page中求偏移量

    得到文件欲读取的索引表项后,接下来,求得偏移量(从COFFEE_START开始处的偏移量),结合源码和下图理解,很快就会懂的,如下:

  1. base = absolute_offset(hdr->log_page, log_records *sizeof(region)); //索引表大小
  2. base += (cfs_offset_t)match_index *log_record_size; //跳过match_index个记录
  3. base += lp->offset;

图4 read_log_page求偏移量示意图


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

上述图1-4源文件如下:

 get_record_index函数示意图.rar   

 modify_log_buffer函数示意图.rar   

 read_log_page求偏移量示意图.rar   

 COFFEE_READ与stm32_flash_read映射关系.rar   

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