Contiki教程——Coffee文件系统

更多Contiki系列教程请看索引博客:
Contiki教程——索引

介绍

  本教程覆盖了ContikiOS2.7中的Coffee文件系统的主要特性。
  Contiki为资源受限系统上的不同种类的存储设备提供了一套文件系统。所有的这些文件系统实现了一套子Contiki文件系统CFS,且其中两个提供了全功能:CFS-POSIX和Coffee。Coffee用于装备有内存或者EEPROM的设备。Contiki关心潜在的硬件实现。Coffee文件系统提供了类似正常C文件操作的API:打开文件,写文件,关闭文件等。
  由于受到资源和环境的限制,有时将产生数据丢失,因而需要进行重传。而通过无线传感器节点上的文件系统,通过存储数据到节点本地,将使系统变得更加可信赖。由于我们可以存储数据到本地节点,我们可以通过一次就能发送广播数据,这减小了数据传输的数量,也减小了能量消耗。

你将学到

  通过本教程,你将明白Coffee文件系统是如何工作的,并且知道如何利用Coffee文件系统编程。

源代码

  文件系统的源代码位于:

contiki/core/cfs/cfs-coffee.h   
contiki/core/cfs/cfs-coffee.c

Coffee文件系统API

内存管理

/* * The protected memory consists of structures that should not be * overwritten during system checkpointing because they may be used by * the checkpointing implementation. These structures need not be * protected if checkpointing is not used. */
static struct protected_mem_t {
  struct file coffee_files[COFFEE_MAX_OPEN_FILES];
  struct file_desc coffee_fd_set[COFFEE_FD_SET_SIZE];
  coffee_page_t next_free;
  char gc_wait;
} protected_mem;
static struct file * const coffee_files = protected_mem.coffee_files;
static struct file_desc * const coffee_fd_set = protected_mem.coffee_fd_set;
static coffee_page_t * const next_free = &protected_mem.next_free;
static char * const gc_wait = &protected_mem.gc_wait;

Contiki教程——Coffee文件系统_第1张图片
  Coffee文件系统以数组的形式组织file_desc,并将其标记为protected_mem_t中的一个成员。

get_available_fd

static int
get_available_fd(void)
{
  int i;

  for(i = 0; i < COFFEE_FD_SET_SIZE; i++) {
    if(coffee_fd_set[i].flags == COFFEE_FD_FREE) {
      return i;
    }
  }
  return -1;
}

  Contiki与Linux类似,一切皆文件。使用文件描述符表示一个打开的文件。get_available_fd通过升序的方式依次检查file_desc数组,返回第一个flag==0(COFFEE_FD_FREE)的元素(最小元素)。如果所有元素的flag都被设置了,就返回-1。

find_file

static struct file *
find_file(const char *name)
{
  int i;
  struct file_header hdr;
  coffee_page_t page;

  /* First check if the file metadata is cached. */
  for(i = 0; i < COFFEE_MAX_OPEN_FILES; i++) {
    if(FILE_FREE(&coffee_files[i])) {
      continue;
    }

    read_header(&hdr, coffee_files[i].page);
    if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
      return &coffee_files[i];
    }
  }

  /* Scan the flash memory sequentially otherwise. */
  for(page = 0; page < COFFEE_PAGE_COUNT; page = next_file(page, &hdr)) {
    read_header(&hdr, page);
    if(HDR_ACTIVE(hdr) && !HDR_LOG(hdr) && strcmp(name, hdr.name) == 0) {
      return load_file(page, &hdr);
    }
  }

  return NULL;
}

  如果与名字name符合的文件仍然存储在数组coffee_files[COFFEE_MAX_OPEN_FILES]里(即还在内存中),且物理文件仍然是有效的,则返回指向该文件的指针,否则将查找FLASH,并将文件缓存到内存中。

CFS编程接口

cfs_open

  cfs_open用于打开一个文件。如果打开成功,则返回文件描述法fd,否则返回-1。
  

int
cfs_open(const char *name, int flags)
{
  int fd;
  struct file_desc *fdp;

  fd = get_available_fd(); //find the smallest available fd, see section 4.2
  if(fd < 0) {
    PRINTF("Coffee: Failed to allocate a new file descriptor!\n");
    return -1;
  }

  fdp = &coffee_fd_set[fd];
  fdp->flags = 0; //set the fd to FREE

  fdp->file = find_file(name); //find the file corresponding to name(not exist, In flash but not cached, cached)

  /*** if there isn't any corresponding file, then try to create new file ***/
  if(fdp->file == NULL) {
    if((flags & (CFS_READ | CFS_WRITE)) == CFS_READ) {
      return -1;
    }
    fdp->file = reserve(name, page_count(COFFEE_DYN_SIZE), 1, 0);
    if(fdp->file == NULL) {
      return -1;
    }
    fdp->file->end = 0; // Since it's a new created file, the end will be set to 0
  }
   /*** find the file,seek for the end of the file***/ 
   else if(fdp->file->end == UNKNOWN_OFFSET) {
    fdp->file->end = file_end(fdp->file->page);
  }

  fdp->flags |= flags;
  fdp->offset = flags & CFS_APPEND ? fdp->file->end : 0; //if the flag is set to APPEND, then the offset is set to the end of the file, otherwise set to 0
  fdp->file->references++; //reference count will increment

  return fd;
}

Contiki教程——Coffee文件系统_第2张图片

cfs_close

void cfs_close(int fd)
{
  if (FD_VALID(fd))
  {
    coffee_fd_set[fd].flags = COFFEE_FD_FREE;
    coffee_fd_set[fd].file->references--;
    coffee_fd_set[fd].file = NULL;
  }
}

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

  当一个被打开的文件不再需要时,应用程序应该使用cfs_close关闭它。通过关闭文件,文件系统能重新分配文件持有的内部资源,并将缓存数据提交到永久存储设备。
  文件系统先确定fd是有效的,接着将fd_desc中的flag设置为COFFEE_FD_FREE,再减小该文件的引用计数,最后将file_desc指向NULL。

cfs_read

  cfs_read()从存储在文件描述符中的当前位置处开始填充len字节到buf。它返回读取到的字节数,当发生错误时返回-1。

cfs_write

  cfs_write()从文件的文件描述符中的当前位置处开始写入len字节内存buf数据。被写的文件在打开时必须带有CFS_WRITE标志。它返回实际写入文件的字节数,当发生错误是返回-1。

cfs_seek

  cfs_seek()移动当前文件位置到由offsetwhence共同决定的另一个位置。CFS_SEEK_SET表明offset是从文件开始处计算的偏移,即绝对偏移。CFS_SEEK_CUR表明offset是从文件的当前位置开始计算的相对偏移。CFS_SEEK_END 表明offset是从文件的结尾处计算的相对偏移,且如果文件系统的实现允许的话,可以移动到文件尾的后面。因为程序可能希望从参数whence指定的基本地址处向后移动,所以CFS_SEEK_CUR和CFS_SEEK_END都允许offset值为负。如果执行成功,cfs_seek返回新的文件绝对地址,如果文件指针不能移动到所指定的位置,则返回-1.

cfs_remove

  cfs_remove删除与名字name相符的文件。它先通过find_file(name)
找到文件,然后通过remove_by_page删除文件。

使用Coffee API编码

  修改Contiki提供的代码。
  打开contiki/examples/sky/example-coffee.c,重写file_test函数。
  

/*
 * Copyright (c) 2011, Swedish Institute of Computer Science.
 * All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions
 * are met:
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 * 3. Neither the name of the Institute nor the names of its contributors
 *    may be used to endorse or promote products derived from this software
 *    without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE INSTITUTE AND CONTRIBUTORS ``AS IS'' AND
 * ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
 * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
 * ARE DISCLAIMED.  IN NO EVENT SHALL THE INSTITUTE OR CONTRIBUTORS BE LIABLE
 * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
 * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
 * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
 * SUCH DAMAGE.
 *
 * This file is part of the Contiki operating system.
 */

#include <stdio.h>
#include "contiki.h"
#include "cfs/cfs.h"
#include "cfs/cfs-coffee.h"
#include <string.h>

PROCESS(example_coffee_process, "Coffee example");
AUTOSTART_PROCESSES(&example_coffee_process);
#define FILENAME "test"
/* Formatting is needed if the storage device is in an unknown state; 
   e.g., when using Coffee on the storage device for the first time. */
#ifndef NEED_FORMATTING
#define NEED_FORMATTING 0
#endif

static int
file_test(const char *filename, char *msg)
{
    int fd;
    int r;
    char message[32];
    char buf[64];
    strncpy(message, "First Message", sizeof(message) - 1);
    message[sizeof(message) - 1] = '\0';
    strcpy(buf,message);
    /*First message is to test if the write will succeed*/
    printf("Write Test: Will write \"%s\" to file \"%s\"\n",buf,FILENAME);

  /* Obtain a file descriptor for the file, capable of handling both reads and writes. */
    fd = cfs_open(FILENAME, CFS_WRITE | CFS_APPEND | CFS_READ);
    if(fd < 0) {
        printf("failed to open %s\n", FILENAME);
        return 0;
    }
  /*Write message to Filesystem*/
    r = cfs_write(fd, message, sizeof(message));
    if(r != sizeof(message)) {
        printf("failed to write %d bytes to %s\n",
                (int)sizeof(message), FILENAME);
        cfs_close(fd);
        return 0;
    }
    cfs_close(fd);
    printf("Write Test: Successfully wrote \"%s\" to \"%s\" wrote %d bytes\n ",message,FILENAME,r);

    strcpy(buf,"fail");
    fd = cfs_open(FILENAME, CFS_READ);
    if(fd < 0) {
        printf("failed to open %s\n", FILENAME);
        return 0;
    }
    r = cfs_read(fd, buf, sizeof(message));
    if(r != sizeof(message)) {
        printf("failed to write %d bytes to %s\n",(int)sizeof(message), FILENAME);
        cfs_close(fd);
        return 0;
    }
      /* compare with the original message to see if the message was read 
      correctly, if it reads fail then it will print fail*/
    printf("Read Test: Read \"%s\" from \"%s\"\n",buf, FILENAME);
    cfs_close(fd);
     /*Append test */
    strcpy(message,"Append Something");
    fd = cfs_open(FILENAME, CFS_WRITE | CFS_APPEND | CFS_READ);
    if(fd < 0) {
        printf("failed to open %s\n", FILENAME);
        return 0;
    }
    r = cfs_write(fd, message, sizeof(message));
    cfs_close(fd);
    printf("Append Test: Successfully \"%s\" to \"%s\" \n ",message,FILENAME);
    strcpy(buf,"fail");
    fd = cfs_open(FILENAME, CFS_READ);
    if(fd < 0) {
        printf("failed to open %s\n", FILENAME);
        return 0;
    }
    cfs_read(fd,buf,sizeof(message));
    printf("Read First Part \"%s\"\n",buf);
       /*seek test*/
    if(cfs_seek(fd, sizeof(message), CFS_SEEK_SET) == -1) {
        printf("seek failed\n");
        cfs_close(fd);
        return 0;
    }
  //cfs_seek(fd, sizeof(message), CFS_SEEK_SET);
    /*if the seek fails then the second message will not be the same as the last message write to the file*/
    cfs_read(fd,buf,sizeof(message));
    printf("Read Second Part: \"%s\"\n",buf);
    cfs_close(fd);
  /* Release the internal resources held by Coffee for the file descriptor. */
    cfs_remove(FILENAME);
    fd = cfs_open(FILENAME, CFS_READ);
    if(fd != -1) {
        printf("ERROR: could read from memory\n");
        return 0;
    }
    printf("Successfully removed file\n");
    return 1;
}


PROCESS_THREAD(example_coffee_process, ev, data)
{
  PROCESS_BEGIN();

#if NEED_FORMATTING
  cfs_coffee_format();
#endif

  /* Ensure that we will be working with a new file. */
  cfs_remove(FILENAME);

  if(file_test(FILENAME, "The first test") == 0) {
    printf("file test 1 failed\n");
  }
  printf("test succeed\n");

  PROCESS_END();
}

仿真结果


  我们可以看到,Coffee文件系统工作正常。

常见问题

  1.在strncpy()函数中,使用sizeof()而不是strlen()。
  2.记得包含string.h,否则编译时将产生警告。

参考文档

  Zane D. Purvis, “Coffee filesystem”
  Zane D. Purvis, “Coffee filesystem example”

你可能感兴趣的:(IoT,物联网,contiki,CFS,Coffee文件系统)