在嵌入式编程中,有的时候应用程序需要读取配置文件以及一些日志文件,程序能够随时记录这些数据的变化,并且把他们存储到flash中。经常碰到的数据有下面几种:
配置文件
在嵌入式变成中,程序启动的时候往往需要进行配置,比如你应用程序的版本号、 服务器地址等,配置文件一般是key-value值。所以我一般都是采用ini文件来存储配置信息。解析ini文件网上有相应的方法,只需要dictionary.h、dictionary.c、iniparse.h以及iniparse.c文件添加到自己的工程目录中就可以,具体方法可以上网百度下。
日志文件
比如我在做一个电话记录的功能,此时你需要把之前的电话记录记下来,需要记录的项有:电话开始时间、电话号码、通话时间、电话归属地等。此时用上面所说的配置文件格式就不太合适了,一个电话一个section是不合适的。所以这个时候我们可以使用纯文本来记录通话记录。一个通话记录1行,这一行中每一个项目用空格隔开。在读取的时候,通过每一行的格式化读取,我们就可以把这个记录读取出来。
经过思考,文本的存储本质上时文本的读写操作,所以可以转换为文件I/O的操作。由于我们这涉及到文本的操作,所以我们首选的是带缓冲的文件I/O操作,即fopen、fread、fgets等等标准函数,其实这些函数在实现上也是系统调用的,即这些标准的库(fopen、fread等)在实现上也是调用了open、read等API。
对一个日志记录,我们以一台新的手机设备进行类比,假设你现在有一个新的手机,插上了电话卡,你打开你的通话记录,此时你的通话记录是空的,这个时候你所做的操作是去查询电话记录。那么怎么查询呢?当然是去存储器中查询,也就是说此时你需要对磁盘文件进行写。下面这段代码是我读取安防报警txt中的安防报警记录。我们主要关注的是文件I/O的操作。
typedef struct
{
unsigned long timestamp; //报警时间
GuardSensor sensor; //报警传感器, 如烟雾
GuardStatus status; //...
GuardType type; //...
GuardState state; //...
} GuardLog;
static GuardLog *GuardGetLog(int index)
{
return &guardLogArray[index];
}
static void GuardReadLog()
{
FILE *f = NULL;
char buf[LENGTH] = {0};
int i = 0, tmp_sensor = 0, tmp_status = 0;
unsigned long tmp_time = 0;
GuardLog* entry = NULL;
f = fopen(GUARD_RECORD, "r");
if(f != NULL) {
while(fgets(buf, sizeof(buf), f)!=NULL) {
sscanf(buf, "%lu %d %d", &tmp_time, &tmp_sensor, &tmp_status);
entry = &guardLogArray[i];
entry->timestamp = tmp_time;
entry->sensor = tmp_sensor;
entry->status = tmp_status;
i++;
memset(buf, 0, sizeof(buf));
}
if(i!=0) {
guardLogCount = i;
}
}
fclose(f);
}
我们主要分析GuardReadLog()函数中的fgets语句以及sscanf语句,这两个语句是这个函数的精华所在。下面的是对函数fgets函数的定义以及解释。我们的目的是去文件逐行读取。但是我们需要大概清楚一行的长度有多大,这样我们才能确定这个buf多大。而且需要注意的是我们在文件的每一行最后需要加上换行符。这样我们就能够逐行读取没然后进行sscanf操作。
#include
char *fgets(char *buf, int size, FILE *stream);
//从指定的流 stream 读取一行,并把它存储在 str 所指向的字符串内。当读取 (n-1) 个字符时,或者读取到换行符时,或者到达文件末尾时,它会停止。
sscanf是一个非常强大的函数,我们这里用到的是最基本也是最常用:用于格式化字符串输入。下面为这个函数的定义以及解释。在一行中的每个参数都用空格隔开,那么用sscanf读取的时候同样用空格可以把每个参数提取出来。然后存储到相应的数组成员中。
#include
int sscanf(const char *str, const char *fromat, ...);
/*
str是我们需要被分解的字符串,其中format是被分解的格式,转换后的结果存在各个参数内,本文用到的空格分解字符串,如下面两行(带换行符),也是两个字符串。
关关雎鸠 123123123 0 在河之洲
窈窕淑女 124567812 1 君子好逑
*/
总结日志文件的读取:首先日志文件需要有个规范的格式,如每一行的各个参数用空格隔开,在每一行的最后需要一个换行符;之后使用fgets函数进行一行的读,然后使用sscanf函数进行行分解存储到对应的参数中。
将参数赋值到结构体元素中,然后使用SaveGuardRecord()既可以对日志进行保存。
static void GuardWriteLog(GuardSensor sensor, GuardStatus status, unsigned long timestamp)
{
GuardLog* entry = NULL;
pthread_mutex_lock(&guardLogMutex);
if (guardLogCount >= MAX_GUARD_LOG_COUNT)
GuardDeleteLog(guardLogCount - 1);
entry = &guardLogArray[guardLogCount];
memset(entry, 0, sizeof(GuardLog));
entry->timestamp = timestamp;
entry->sensor = sensor;
entry->status = status;
guardLogCount++;
assert(guardLogCount <= MAX_GUARD_LOG_COUNT);
pthread_mutex_unlock(&guardLogMutex);
}
日志的写入是在需要将对应的一行写到文件中,注意行的格式就行,这里我们使用fprintf进行格式化字符串。然后使用fclose将文件流写入磁盘。
static void SaveGuardRecord(void)
{
FILE *f;
int count=0, i=0;
GuardLog* entry = NULL;
f = fopen(GUARD_RECORD, "wb"); //这里一定只用到w,因为我们需要更新文本里面的内容
if (!f)
{
printf("cannot open guard record file: %s\n", GUARD_RECORD);
return;
}
count=GuardGetLogCount();
for(i = count-1; i>= 0; i--)
{
entry = GuardGetLog(i);
fprintf(f, "%lu %d %d\n", entry->timestamp, entry->sensor, entry->status);
}
fclose(f);
}
选定一条记录,然后删除。假设我们有10条(0-9)记录,然后删掉第5(4)条记录,删掉的数组为i = 10 - index(5) - 1 =4;所谓的删除其实是将这个数据从数组中拿掉,然后将下一个元素顶到上一个来。memmove和memcpy功能差不多,但是memmove能够赋值内容重复区域。如果下雨存入flash中,只需要调用上面介绍的SaveGuardRecord()函数即可。
void GuardDeleteLog(int index)
{
int i, endIndex;
if (guardLogCount == 0 || index >= guardLogCount)
return;
pthread_mutex_lock(&guardLogMutex);
assert(guardLogCount <= MAX_GUARD_LOG_COUNT);
assert(index < guardLogCount);
i = guardLogCount - 1 - index;
endIndex = guardLogCount - 1;
assert(i <= endIndex);
memmove(&guardLogArray[i], &guardLogArray[i + 1], sizeof(GuardLog) * (endIndex - i));
guardLogCount--;
assert(guardLogCount >= 0);
pthread_mutex_unlock(&guardLogMutex);
}
将count设置为0,然后使用void SaveGuardRecord(),将txt文件清空。
void GuardClearLog(void)
{
guardLogCount = 0;
}
完整的程序代码可以到csdn下载guard_log.c