一、前言
最近开发一基于嵌入式零碎下的“文件系统”,前期测试呈现一些离奇的 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)
如果当产生写操作时,数据曾经在 CPU Cache 里的话,则把数据更新到 CPU Cache 里,同时标记 CPU Cache 里的这个 Cache Block 为脏 (Cache Block 的数据和内存是不统一);
如果当产生写操作时,数据所对应的 Cache Block 里寄存的是「别的内存地址的数据」的话,就要检 查这个 Cache Block 里的数据有没有被标记为脏的,如果是脏的话,咱们就要把这个 Cache Block 里的数据写回到内存,而后再把以后要写入的数据,写入到这个 Cache Block 里,同时也把它标记为 脏的; 如果 Cache Block 外面的数据没有被标记为脏,则就间接将数据写入到这个 Cache Block 里,而后再把这个 Cache Block 标记为脏。
- 读贯通(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;
}