更多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
/* * 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;
Coffee文件系统以数组的形式组织file_desc,并将其标记为protected_mem_t中的一个成员。
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。
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_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;
}
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()从存储在文件描述符中的当前位置处开始填充len字节到buf。它返回读取到的字节数,当发生错误时返回-1。
cfs_write()从文件的文件描述符中的当前位置处开始写入len字节内存buf数据。被写的文件在打开时必须带有CFS_WRITE标志。它返回实际写入文件的字节数,当发生错误是返回-1。
cfs_seek()移动当前文件位置到由offset
和whence
共同决定的另一个位置。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删除与名字name相符的文件。它先通过find_file(name)
找到文件,然后通过remove_by_page删除文件。
修改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”