今天是来美国后的第一个onsite,但是感觉很多技术问题没有答上来。准备的很多算法方面的东西,比如bitwise operation都没有问。
提的问题大多是比较有实战意义的。比如第一个比较绕一点的技术题是这样的:
有一块physical memory area、DMA、ARM以及介于ARM和physical memory之间的cache,其中DMA是直接向physical memory里面写数据的,而ARM则是通过cache从memory里面读数据,再把读出来的数据传给DMA,如果传给DMA的数据和DMA写入的数据不相符合,那么上传数据的function会hang掉。另外:当DMA向memory里面写数据的时候,ARM上传的函数会被block住。只有当DMA完成写的动作,ARM才有可能把从cache/memory里面读出来的数据写上去。
流程图如下:
------------------------------------- DMA
| /\
| |
| |
\/ |
memory |
| |
| |
| |
-------------> cache ---------------> ARM
现在有这样一段程序:
void arm_send_data(char *mem_data)
{
char result = 0;
check_sync_dma_arm();
result = *mem_data;
send_data_to_dma_and_check(result);
...
check_sync_dma_arm();
result = *mem_data;
send_data_to_dma_and_check(result);
return;
}
其中check_sync_dma_arm()是个blocking function,知道DMA完成向内存的写操作才得以返回。
当时Interviewer跟我说了一下有反复作两到三次的必要,但没有解释为什么。在上面的代码中,就简化为两次好了。
问题来了:这段代码在实际运行的时候发现会hang在send_data_to_dma_and_check(result)这个函数;也就是说,从memory中读取再传回DMA的数据与DMA向memory写入的数据不匹配。问为什么会出现这种情况?
我问会不会有别的thread在此期间也写入内存,导致内存值被改动?Interviewer否定了这个可能,并且确认physical memory里面这个值只有DMA可以写,写操作也成功地完成了。
我绞尽脑汁,也没想出来是为什么。后来Interview提示光看code是看不出问题的,问题出在这个模块的架构上。因为ARM是通过cache从memory读取的,什么是cache呢?就是暂存前一次从memory里面读取的data的地方。如果memory没有改动,那么下一次读数据的时候,ARM就不需要去memory操作了,直接从cache拿可以了。这就是cache加速的原理。
导致上面的代码出现问题的原因在于,当blocking call跳出来的时候,说明memory的值已经被写入了,而这时候ARM上传的值却是从cache里面读取的,是旧的、在写入之前的值。
Debug,第一轮:
void arm_send_data(char *mem_data)
{
char result = 0;
check_sync_dma_arm();
cache_invalidated(); // fix
result = *mem_data;
send_data_to_dma_and_check(result);
...
check_sync_dma_arm();
cache_invalidated(); // fix
result = *mem_data;
send_data_to_dma_and_check(result);
return;
}
cache_invalidated()的作用是使得下一次从内存读数据的时候(result = *medm_data),ARM不再从cache里面读值,而直接去physical memory里面去拿,同时更新cache的内容。
OK,这个问题我想明白了,但是还没完。上面的代码中,读数据和传回给DMA的动作进行了两轮。但fix却只对第一轮起作用。第二轮的操作还是hang掉了。为什么呢?
还是想不出来,Interview再次给出答案:因为一模一样的result = *mem_data的语句出现了两次,所以在compiler作optimization的时候,把第二条赋值语句给省略了,没有编译。
我想了两个办法:
1. Turn off compiler's optimization -显然是不行的,不能因噎废食,对吗?
2. 再加一个变量的声明:char result1; 之后在第二次读取数据和传数据给DMA的时候,用result1 = *mem_data和send_data_to_dma_and_check(result1)就可以了。
第二个办法虽然可行,但显然比较麻烦,而且确实也没必要多开销一个变量。
其实最佳的解决办法很简单,就是对函数的形参mem_data做一个volatile的声明:
void arm_send_data(volatile char *mem_data)
在K&R的Bible的附录A.8.2里面,提到了这么一段:
The purpose of volatile is to force an implementation to suppress optimization that could otherwise occur. For example, for a machine with memory-mapped input/output, a pointer to a device register might be declared as a pointer to volatile, in order to prevent the compiler from removing apparently redundant references through the pointer.
另外,相关的资料也可参见下面的link:
http://en.wikibooks.org/wiki/Embedded_Systems/C_Programming
http://vault.embedded.com/story/OEG20010615S0107
http://publications.gbdirect.co.uk/c_book/chapter8/const_and_volatile.html
Subscribe to:
Post Comments (Atom)
No comments:
Post a Comment