在Coffee,没有单独的文件创建函数。在name对应文件不存在的情况下,为cfs_open
指定flags
(取000
或010
或011
或100
或110
或111
)可创建新文件,本文深入源码并用多组示意图分析Coffee创建文件技术细节。包括find_file
、page_count
、reserve
、find_contiguous_pages
、write_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_WRITE
、CFS_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
参数flags
,flags
至少有一项是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
取值为001
、101
,条件为真。也就是说,
图1 cfs_open函数flags判断示意图
当flags
含有CFS_WRITE
或者没有CFS_READ
或者两者皆有之时才有可能创建新文件(CFS_APPEND
可有可无),即flags
取000
、010
、011
、100
、110
、111
。(这样的设计让我很费解)
2.3 page_count
page_count
函数用于算出给定的size
需要多少Coffee页,即size
加上file_header
大小,并且按Coffee页(Coffee页大小与FLASH物理页大小不一定等同,见cfs_coffee_arch.h
文件)对齐,示意图如下:
图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_pages
从next_free
(全局变量,指向下一个空闲页)指向的页面开始查找,若找到start+amount<=next_file()
,则返回start
,否则返回INVALID_PAGE
(说明没有足够空闲页甚至没有空闲页供使用)。
图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_header
将file_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源文件如下: