一、前言
最近开发一基于嵌入式系统下的“文件系统”,后期测试出现一些离奇的BUG,经审查和排故发现是开启了缓存加上DMA搬运数据导致的CACHE不一致问题。现在就来分享一下。
二、缓存
改文件系统的应用场景是基于嵌入式操作系统,实现数据的有效记录。硬件设计基于Z7,BSP默认打开了CPU和主存之间的cache, 且写数据方式为写贯穿。应用运行中,CPU通过cache访问操作主存,而读写电子盘的驱动默认采用DMA搬运数据到主存,这是cache不一致的根本原因。
本文先介绍一下几个概念:
- cache line
CPU cache的结构是由很多Cache Line组成的。每条cache line都有两个标志位。 - 有效位(valid bit)
表示cache line的数据是否有效。系统刚启动时,数据都置无效。 - 脏位(dirty bit)
表示cache line的数据是否和下一级缓存一致。0一致,1不一致 - 命中(Hit)
CPU要访问的数据在Cache中有缓存。成为命中(Hit),反之为缺失(Miss) - DMA(Direct Memory Acess)
直接存储器访问,是一种不经过CPU而直接从内存存取数据的数据交换方式。否则,CPU需要从来源把每一片段的资料复制到暂存器,然后把它们再次写回新的地方。
三、缓存的读写方式
- 写直达(write through)
任一从CPU发出的写信号送到cache的同时,也写入主存,以保证主存的数据能够同步更新。 写回(write back)
- 读贯穿(read through)
CPU的所有对主存的数据请求都先送到cache,如果命中,则不请求访问主存,并将数据送出;如果不命中,则向主存请求数据。 - 读旁路(read aside)
CPU发出数据请求时,并不是单通道地穿过Cache。而是向Cache和主存同时发出请求。由于Cache速度更快,如果命中,则Cache在将数据回送给CPU的同时,还来得及中断CPU对主存的请求;不命中。则Cache不做任何动作。由CPU直接访问主存。
四、应用场景
文件系统是基于vxWorks开发的。针对CACHE不一致问题,分析vxWorks系统提供的cacheLib,提出两种解决方法:
- 所有经过DMA操作的数据都用cacheDmaMalloc申请内存空间
默认用malloc申请的内存不是缓存安全的。用cacheDmaMalloc可以为DMA设备和驱动分配缓存安全的内存缓冲。 - 调用cacheFlush和cacheInvalidate解决问题
cacheFlush强制将缓冲的数据更新到内存。对于写贯穿类型,cacheFlush什么都不需要做因为内存和缓存条目是匹配的。cacheInvalidate将所有的缓冲条目都设置为无效,完全切断内存和缓冲之间的联系。
本项目采用的是第二种方式,直接在调用磁盘的读写驱动处增加cacheFlush和cacheInvalidate。在调用写驱动之前,调用cacheFlush强制将缓冲的数据更新到内存,保证写入磁盘的数据是从cache中拿到的最新的数据。在调用读驱动后,调用cacheInvalidate切断当前内存区域和cache的联系,保证后续CPU访问该区域的时候,能够直接访问内存而不是缓存中可能存在的旧数据。
int rawFsBlkWrt(unsigned int startsector,int nsectors,char *pdata, const char *devname)
{
STATUS stats = ERROR;
BLK_DEV * pdev = NULL;
int ldrs_num = -1;
int i = 0;
for(i=0;i<LDRS_NUM;i++)
{
if(ldrs_handle[i].ldrs_valid_flag == LDRS_HANDLE_VALID_FLAG)
{
if(0==strcmp(devname,ldrs_handle[i].ldrs_name))
{
ldrs_num = i;
break;
}
}
}
if(ldrs_num<0)
{
ldrs_errno = ERR_BLKDEV_INVALID;
printf("ERR_BLKDEV_INVALID %s",(char *)devname);
return -1;
}
if(pdata == NULL)
{
ldrs_errno = ERR_ADDR_IS_NULL;
return ldrs_errno;
}
if(ERROR == semTake(ldrs_handle[ldrs_num].sem_blkdev,sysClkRateGet()*30))
{
ldrs_errno = ERR_SEMPHONE_TAKE;
return ldrs_errno;
}
cacheFlush(DATA_CACHE, (void*)pdata, nsectors);
stats = fsBlkWrt(pdev,startsector,nsectors,pdata);
semGive(ldrs_handle[ldrs_num].sem_blkdev);
if(stats == ERROR)
{
ldrs_errno = ERR_BLK_WRITE;
return ldrs_errno;
}
return DISC_OK;
}
int rawFsBlkRd(unsigned int startsector,int nsectors,char *pdata, const char *devname)
{
STATUS stats = ERROR;
BLK_DEV * pdev = NULL;
int ldrs_num = -1;
int i = 0;
for(i=0;i<LDRS_NUM;i++)
{
if(0==strcmp(devname,ldrs_handle[i].ldrs_name))
{
ldrs_num = i;
break;
}
}
if(ldrs_num<0)
{
ldrs_errno = ERR_BLKDEV_INVALID;
printf("ERR_BLKDEV_INVALID %s",(char *)devname);
return -1;
}
if(pdata == NULL)
{
ldrs_errno = ERR_ADDR_IS_NULL;
return ldrs_errno;
}
if(ERROR == semTake(ldrs_handle[ldrs_num].sem_blkdev,sysClkRateGet()*30))
{
ldrs_errno = ERR_SEMPHONE_TAKE;
return ldrs_errno;
}
stats = fsBlkRd(pdev,startsector,nsectors,pdata);
cacheInvalidate(DATA_CACHE, pdata, nsectors);
semGive(ldrs_handle[ldrs_num].sem_blkdev);
if(stats == ERROR)
{
ldrs_errno = ERR_BLK_READ;
return ldrs_errno;
}
return DISC_OK;
}