Monday, April 4, 2011

Qualcomm Onsite 之一

今天是来美国后的第一个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

No comments:

Post a Comment