01dxwlm
发表于 2008-8-1 19:35
我对如何编写高质量的程序的看法
<br />我对嵌入式软件开发的时间也不是很长,仅仅只有5年,算不上高手,也不是老手只能算是5岁的老菜鸟<br /><br />,在前面的3年里没有高人指点,靠着自己摸索也写了不少的程序吧,那时侯我没有想过要写出容易理解<br />和维护以及移植的问题,仅仅想着如何把这个功能实现出来,所以在我工作的的第一个项目GSM汽车防盗<br /><br />器,我用汇编写到5000行的时候逻辑就开始乱了,记得在快要完成所有功能的时候老板要我再增加一个<br /><br />小功能时我不能决定在原有的程序的基础上哪个地方下手来增加,因为程序的逻辑关系太乱了,修改一<br /><br />个地方会牵涉到很多功能,经过那一次以后我就下决心下次再写程序的时候一定要避免这个问题,一定<br /><br />要作到容易修改,逻辑关系清晰。<br /> 后面有幸到一个外资公司做单片机软件开发工作,一个工程师给了我一本台湾出版的关于C语言面向<br /><br />对象的编程的书(书名字忘记了),我在那本书里面找到了我为什么我第一个项目GSM汽车防盗器程序逻辑<br /><br />乱的原因,我当时认认真真地读了那本书,真的感觉很好,在那本书里我学到了一个重要的概念---模块<br /><br />化编程,加上那位台湾的工程师的指导,我觉得我的单片机编程水平有了质的飞跃,在以后的编程里我<br /><br />不会再因为偷懒而不写注释,不会因为仅仅简单而放弃可维护,可移植,易懂的方案,这样慢慢养成了<br /><br />习惯,在第二年我就用C写出了大约80KBYTE 的产品程序,而且自认为逻辑清晰,只要客户说需要修改哪<br /><br />里我能马上作出响应。<br /><br /> 现在我已经习惯模块化程序设计,不管是8位的单片机还是32位的MPU, 都会按照这样的思路去做,我<br /><br />觉得写出好的,高质量的程序,<br />1.不要怕麻烦,该写的就要写,该做的就要做。<br />2.尽量模块化设计,虽然这样做在写小程序时会浪费时间,可这样会养成模块化程序的习惯。<br />3.最好多看看老外写的关于编程的书籍,你会发现很多东西你以前见到的。<br />4.学习下PSP,CMMI的课程.<br /><br />下面是我用队列来实现CPU SCI 底层驱动程序的一个文件,希望能起到点点引用的作用。<br />最早是在freescale的CPU上使用,后来移植到了51,h8/3062,凌阳等MCU上,表现性能相当地好,在开发<br />产品节约了大把的时间,老板大大开心,我也大大开心。。<br />#define SCI_DRIVER<br />/*****************************************************************************************<br />*Copyright: xxxxxCorporation <br /><br />*<br />*File name: SciDriver.c <br /><br /> *<br />*Author: Kenny_wang <br /><br /> *<br />*Version: V1.0 <br /><br /> *<br />*Date: *<br />******************************************************************************************/<br /><br />#include "SciDriverSciDriver.h"<br />#include "SciDriverSourceFunLst.h"<br />#include "SciDriverPortsSciconfig.h"<br /><br /><br />/********************************************************************************<br />* Constant Define *<br />********************************************************************************/<br />#define cQBufNormal 0<br />#define cQBufFull 1<br />#define cQBufEmpty 2<br /><br />/********************************************************************************<br />* Queue structure *<br />********************************************************************************/<br />typedef struct{<br /> unsigned char *pIn;<br /> unsigned char *pOut;<br /> unsigned char *pStart;<br /> unsigned int bLength;<br /> unsigned int wSize;<br /> }QUEUE;<br /><br />/********************************************************************************<br />* Sci structure *<br />* Including tranmit and receive queue structure and Tx,Rx threshold variabls *<br />********************************************************************************/<br />typedef struct{<br /> unsigned char bTxStatus;<br /> unsigned int wTxLength;<br /> unsigned char *pbTx;<br /> QUEUE *pqRx;<br /> unsigned char bSciType;<br /> }SciStruct;<br /><br />/********************************************************************************<br />* List of Sci structure and queue *<br />********************************************************************************/<br />SciStruct SciList;<br />SciStruct *pSciIndex;<br />QUEUE QList;<br />unsigned char bSciRxBuf;<br />unsigned char *pSciBuf = bSciRxBuf;<br />unsigned char bSciNo = 0;<br /><br />/********************************************************************************<br />* Internal Function Declaration *<br />********************************************************************************/<br />void sQInit(QUEUE *pq,unsigned char *pStart,unsigned int wSize);<br />unsigned char sQDataIn(QUEUE *pq,unsigned char bData); <br />unsigned char sQDataOut(QUEUE *pq,unsigned char *pData);<br /><br />/********************************************************************************<br />*Function name: sQInit *<br />*Parameters: pq: pointer to queue structure to be initialized *<br />* start: start address of ring buffer *<br />* size: the size of the ring buffer *<br />*Description: initialize a queue structure *<br />********************************************************************************/<br />void sQInit(QUEUE *pq,unsigned char *pStart,unsigned int wSize)<br />{<br /> pq->pIn = pStart;<br /> pq->pOut = pStart;<br /> pq->pStart = pStart;<br /> pq->bLength = 0;<br /> pq->wSize = wSize;<br />}<br /><br />/********************************************************************************<br />*Function name: sQDataIn *<br />*Parameters: pq: pointer to queue structure to be initialized *<br />* data: the data to be inserted into the queue *<br />*Returns: cQBufNormal: data has been inserted into the queue *<br />* cQBufFull: the buffer is full *<br />*Description: insert a data into the queue *<br />********************************************************************************/<br />unsigned char sQDataIn(QUEUE *pq,unsigned char bData)<br />{<br /> if(pq->bLength == pq->wSize)<br /> {<br /> if(pq->pIn == pq->pStart)<br /> {<br /> *(pq->pStart + pq->wSize) = bData;<br /> }<br /> else<br /> {<br /> *(pq->pIn-1) = bData;<br /> }<br /> return(cQBufFull);<br /> }<br /> else<br /> {<br /> *(pq->pIn) = bData;<br /> pq->bLength++;<br /> if(pq->pIn == pq->pStart + pq->wSize - 1)<br /> {<br /> pq->pIn = pq->pStart;<br /> }<br /> else<br /> {<br /> pq->pIn++;<br /> }<br /> return(cQBufNormal);<br /> }<br />}<br /><br />/********************************************************************************<br />*Function name: sQDataOut *<br />*Parameters: pq: pointer to queue structure to be initialized *<br />* pdata: the address to save the data *<br />*Returns: cQBufNormal: data has been inserted into the queue *<br />* cQBufEmpty: the buffer is empty *<br />*Description: Get a data from the queue *<br />********************************************************************************/<br />unsigned char sQDataOut(QUEUE *pq,unsigned char *pData)<br />{<br /> if(pq->bLength == 0)<br /> {<br /> return(cQBufEmpty);<br /> }<br /> *pData = *(pq->pOut);<br /> pq->bLength--;<br /> if(pq->pOut == pq->pStart + pq->wSize - 1)<br /> {<br /> pq->pOut = pq->pStart;<br /> }<br /> else<br /> {<br /> pq->pOut++;<br /> }<br /> return(cQBufNormal);<br />}<br /><br />/********************************************************************************<br />*Function Name: sInitialSci *<br />*Parameters: bSciId: sci id *<br />* *bRxBuf: receive buffer start address *<br />* wRxSize: receive buffer length *<br />* bTxBuf: transmit buffer start address *<br />* wTxSize: transmit buffer length *<br />* type: sci type *<br />*Descriptions: assign and initialize the sci control struct to sci *<br />********************************************************************************/<br />void sInitialSci(unsigned int wRxSize,unsigned char bType)<br />{<br /> QUEUE *pq;<br /> SciStruct *pSci;<br /><br /> pSci = &SciList;<br /> pSciIndex = pSci;<br /><br /> pSci->pqRx = &QList;<br /> pq = pSci->pqRx;<br /> sQInit(pq,pSciBuf,wRxSize);<br /> pSciBuf += wRxSize;<br /> bSciNo++;<br /><br /> pSci->bTxStatus = cSciTxRdy;<br /> pSci->wTxLength = 0;<br /><br /> pSci->bSciType = bType;<br />}<br /><br />/********************************************************************************<br />*Function Name: sSciRxISR *<br />*Parameters: bSciId: sci id *<br />*Description: This function is executed in Sci rx interrupt io2sci rx compare *<br />* interrupt. *<br />********************************************************************************/<br />void sSciRxISR(void)<br />{<br /> unsigned char bData;<br /> QUEUE *pq;<br /> SciStruct *pSci;<br /><br /> pSci = pSciIndex;<br /> pq = pSci->pqRx;<br /><br /> if(sbGetSciRxRdy() == cSciRxRdy)<br /> {<br /> sSciResetRx();<br /> bData = sbGetSciRxData();<br /> sQDataIn(pq,bData);<br /> }<br />}<br /><br />/********************************************************************************<br />*Function Name: sSciRead *<br />*Parameters: bSciId: sci id *<br />* *pBuf: address to save data received *<br />*Returns: cSciRxBufEmpty: receive buffer is empty *<br />* cSciRxRdy: get one byte data successfully *<br />*Description: This function is executed in AP *<br />********************************************************************************/<br />unsigned char sSciRead(unsigned char *pBuf)<br />{<br /> QUEUE *pq;<br /> unsigned char bTemp;<br /> SciStruct *pSci;<br /><br /> pSci = pSciIndex;<br /> pq = pSci->pqRx;<br /><br /> OS_ENTER_CRITICAL();<br /> bTemp = sQDataOut(pq,pBuf);<br /> OS_EXIT_CRITICAL();<br /><br /> if(bTemp == cQBufEmpty)<br /> {<br /> return(cSciRxBufEmpty);<br /> }<br /> else <br /> {<br /> return(cSciRxRdy);<br /> }<br />}<br /><br />/********************************************************************************<br />*Function Name: sSciTxISR *<br />*Parameters: bSciId: sci id *<br />*Description: This function is executed in Sci Tx interrupt io2sci Tx compare *<br />* interrupt. *<br />********************************************************************************/<br />void sSciTxISR(void)<br />{<br /> SciStruct *pSci;<br /><br /> pSci = pSciIndex;<br /><br /><br /> if(sbGetSciTxRdy() == cSciTxRdy)<br /> {<br /> if(pSci->wTxLength == 0)<br /> {<br /> pSci->bTxStatus = cSciTxRdy;<br /> sSciResetTx();<br /> }<br /> else<br /> {<br /> sSciTxData(*(pSci->pbTx));<br /> (pSci->pbTx)++;<br /> (pSci->wTxLength)--;<br /> sSciResetTx();<br /> }<br /> }<br />}<br /><br />/********************************************************************************<br />*Function Name: sSciWrite *<br />*Parameters: bSciId: sci id *<br />* *pstart: start address of data to be sent *<br />* wLength: the length of data to be send *<br />*Returns: cSciTxBufFull: transmit buffer is empty *<br />* cSciTxRdy: send one byte data successfully *<br />*Description: This function is executed in AP *<br />********************************************************************************/<br />unsigned char sSciWrite(unsigned char *pStart,unsigned int wLength)<br />{<br /> SciStruct *pSci;<br /><br /> pSci = pSciIndex;<br /><br /> if(pSci->bTxStatus == cSciTxBusy)<br /> {<br /> return(cSciTxBusy);<br /> }<br /><br /> OS_ENTER_CRITICAL();<br /> pSci->pbTx = pStart;<br /> pSci->wTxLength = wLength;<br /> pSci->bTxStatus = cSciTxBusy;<br /><br /> sSciTxData(*(pSci->pbTx));<br /> (pSci->pbTx)++;<br /> (pSci->wTxLength)--;<br /><br /> OS_EXIT_CRITICAL();<br /><br /> return(cSciTxRdy);<br />}<br /><br />/********************************************************************************<br />*Function Name: sbGetSciTxStatus *<br />*Parameters: bSciId: sci id *<br />*Returns: sci tx status cSciTxRdy *<br />* cSciTxBusy *<br />*Description: Get the sci trasmit status *<br />********************************************************************************/<br />unsigned char sbGetSciTxStatus(void)<br />{<br /> SciStruct *pSci;<br /><br /> pSci = pSciIndex;<br /><br /> return(pSci->bTxStatus);<br />}<br /><br />/********************************************************************************<br />*Function Name: sSetSciBaudRate *<br />*Parameters: bSciId: sci id *<br />*Returns: bBaudrate Sci Baudrate *<br />*Description: Set the sci baudrate *<br />********************************************************************************/<br />void sSetSciBaudRate(unsigned char bBaudrate)<br />{<br /> sSciChangeBaudRate(bBaudrate);<br />}<br />
db10
发表于 2008-8-2 04:02
LZ
虽然有人指出不是,但是感觉还是不错得,给你裤子了。
01dxwlm
发表于 2008-8-2 20:44
我自己当然知道我的不足
我的目的只是在一般MCU上 实现,上边的文件和另外一个文件配合就不需要修改任何地方,我会继续努力的。
jy6715
发表于 2008-8-7 09:15
看看先
crypt.wind
发表于 2008-8-8 23:31
上面那位贴出一大版linux代码的,不知道为什么说别人的代码
上面那位贴出一大版linux代码的,不知道为什么说别人的代码不好。<br />如果不好得指出哪里要改进。<br />楼主的代码对普通mcu很合适。涉及除了本身的功能,也就依赖了一下临界区代码保护。有os的就不说了,没os的也很方便处理。你看看你那linux代码,又是spinlock又是内核里的动态内存分配。<br />这种有可比性吗?
lczsx2000
发表于 2008-8-9 14:25
楼上的难道看不出来?
楼主的意思就是要编写高质量的代码,我赞的也是这个,楼主有这个意识了,非常的不错。同时觉得不足的也是这点。<br /> 首先,写的代码要在你自己的模块库里面应该能得到重用,这也是楼主的意思。但是我们可不可以写得更好一点呢?例如上面的两个fifo代码,你认为哪段代码容易让人理解,容易让人也愿意使用呢?当然是linux kfifo的代码,它更容易让人接受,浅显易懂,而且对各种可能出现的情况考虑的很全面:__kfifo_len求fifo空余容量。<br /> 再就是,你没发现,kfifo代码提供了两种代码,一种是临界区代码保护的(kfifo.h,不带下划线的函数),另一种是不需要保护的(kfifo.c,带双下划线的)。而且你没发现,spinlock_irqsave等临界保护资源需要用你自己的cpu来提供的吗?还有就是带下划线的fifo操作函数可以工作在一个线程只写,另一个线程只读的设计里面,减少关中断而导致的实时性能下降带来的整个系统性能下降吗?至于动态内存分配,难道你不会自己替换成你自己cpu提供的更合适的方式吗?<br /> 最后,写代码不能随心所欲,楼主的代码把sci收发和fifo代码整合在一起,难以区分,难不成下次你只需要fifo代码的时候,重写一份新的fifo?还是从这段代码里面摘取?这是个问题。<br /> 没事的时候多看看人家写的代码,有些代码实现是非常经典的。花了时间,哪怕我们只是学到了一丁点对我们有益的东西,日后能把这些东西应用到我的系统里面,也是值得的,你说是吧。好了就说这么多了,看代码去了。
crypt.wind
发表于 2008-8-9 21:00
楼上的终于出来说说如何改进了,不过呢。。
看来你没明白我的意思,代码好不好,要看适用的环境。我说楼主那个代码适合普通mcu,甚至没有os的情况。我说没有可比性,还得好好详细说明说明。<br /><br />自旋锁这样的机制,本身也是一个轻量级锁,对于一些简单的mcu和os,如果不想通过开关中断去实现,使用其他方法也完全可行。适用自旋锁的情况你既然在看linux代码你应该也明白。如果楼主的代码不需要用到保护临界区。undef OS_ENTER_CRITICAL就是了。<br />动态分配内存,简单mcu很少去做这种事情。所以楼主才会用外部静态变量分配buffer。你说的前两点我觉得是根据不同系统去做的一个选择,没有什么优劣的比较。<br />如果说代码的重用,你提的代码仅仅是kfifo的实现。你难道没注意楼主的代码也仅仅三个接口而已。想提出来不是难事,当然楼主把这些搞在一起看起来让你觉得混乱了。可是我觉得不要提取比较好。<br />仔细看楼主的代码,每个调用进出fifo是一个字节。而你提的代码是一次调用copy一段buffer。想想吧,在某些应用场合,你能不能每次提交一段buffer?如果仅仅是一个字节为什么要去做那些多余的事情。<br />你觉得linux那段fifo代码适用楼主的应用场合吗?请注意看看楼主使用fifo接口是在什么情况。为什么楼主要把sci收发和fifo代码整合在一起。<br /><br />脱离应用实际去谈代码的优美与否是没有意义的。
lczsx2000
发表于 2008-8-10 10:05
贴上图
从你描述的自旋锁来看,你还没完全理解它。注意上面的程序,isr写数据,主程序读数据,kfifo程序在这种情况下是不需要临界段保护的,而楼主的代码在这种应用中需要保护一大段代码,首先执行效率会比kfifo的代码低,再次就是系统中断响应时间的延长导致整体性能的下降。<br />为什么必须要malloc,看测试程序吧,直接初始化全局数组<br />我一直在linux下工作学习,也是靠他吃饭的,而不是一时心血来潮看了点linux的代码。经典的代码还是挺感触的https://bbs.21ic.com/upfiles/img/20079/200792510103182.jpg
lczsx2000
发表于 2008-8-10 10:06
再图
https://bbs.21ic.com/upfiles/img/20079/2007925102222481.jpg
crypt.wind
发表于 2008-8-10 13:34
对你的务实非常佩服,不过我还有意见,我们再探讨探讨
你比较的那个例子,其实从两个定义就可以看出端倪:<br />struct kfifo {<br /> unsigned char *buffer; /* the buffer holding the data */<br /> size_tt size; /* the size of the allocated buffer */<br /> size_tt in; /* data is added at offset (in % size) */<br /> size_tt out; /* data is extracted from off. (out % size) */<br />};<br />typedef struct{<br /> unsigned char *pIn;<br /> unsigned char *pOut;<br /> unsigned char *pStart;<br /> unsigned int bLength;<br /> unsigned int wSize;<br />}QUEUE;<br />两个接口并不一样。kfifo还没有反应fifo的状态,可是楼主的代码有。<br />因此kfifo要改成单字节进出,并且提供和楼主代码一致的接口。那么,要增加在put和out时,fifo是否满和空的状态反应。对于单字节复制,memcpy这样的函数调用实在没必要。然后struct里的spinlock,我想还是去掉比较方便吧。<br /><br />好了,到这里这个kfifo才得到了重用。那么你觉得这个重用的kfifo和QUEUE比较,差别在哪?一个使用在buffer中的偏移量作为游标,一个使用的是指针作为游标。你所比较的是这两种方式的优劣。在实际使用过程中,要选择哪一个,好像有点过于计较。要我来说是偏移量做游标还是指针做游标很多时候是一个个人喜好。<br /><br />现在问题在于,楼主的代码进出fifo是单字节。我认可你的说法,如果硬件允许让你一次读写多个字节,那么一般会要优于一次读写一个字节。我上面的回复,就是基于如果只能一次往硬件读写一个字节的情况。那么你的kfifo比起来没什么改善。我说不要提取QUEUE代码实现,也是这个考虑。因为这个QUEUE<br />的实现只适应这种特殊情况。如果想傻事不要做太多,那么好吧,我们提取出来。楼主的实现很清晰。<br /><br />还有一个问题就是要不要保护临界区,我们知道优良的设计可以减少临界区的存在。那么仔细看看,如果要和楼主的接口一样,提供fifo状态的返回,那么你觉得要不要保护临界区?这里引出kfifo的一个问题是,in/out游标不会自己回滚,如果一直put,虽然数据仍然在分配的buffer里,可是fifo->in会一直增加。而楼主的接口是要返回fifo满的。再者对于unsigned int来说这个值大到了目前没有这么大的RAM计数。可是你的例子里的unsigned char呢?怎么办?<br /><br />kfifo为什么这么实现?这个是有限制的,在linux代码的情况下,fifo->in的值就算会很大,但是fifo还是有reset的时候,并且也不会大到溢出的地步。而楼主的代码没有这个假设。所以为了在每次put和out后返回fifo的状态,就要保护好临界区。<br /><br />你提的那些看法我认可,但是,太理想化啦,代码优劣与否要看实际应用场合,和不同的设计考虑。基于楼主的设计考虑,kfifo并不是那么优势明显。
lczsx2000
发表于 2008-8-10 15:50
谢谢!我务实了,你呢?
工程师还是务实的比较好,以事实说话!<br /> 我敢肯定你没仔细看前面的kfifo代码!要探讨,如果水平不够,我希望你至少仔细分析一下两个代码再来论证,而不是臆断。至少是对我工作的尊重,也能说明你还是很有涵养的,你说是吧!<br /> 查看kfifo的状态,有kfifo_len()函数,而且还有kfifo_reset()函数,这些难道你都没看见吗?编写函数有一个最基本的原则:一个函数只干一件事。你看楼主的代码,入队岀队的同时还干了什么?<br /> memcpy虽然性能消耗比直接赋值稍多,但他却为软件fifo的通用化提供了最有力的保障!struct里面的spinlock在这里用不上,难道你不会把它去掉?人家不是为你的设计定制的代码。我的测试程序也是把它去掉了的。<br /> 至于使用游标还是指针,这个不重要,借句你的话,看个人喜好。非要理论优劣,那是钻牛角尖了。<br /> 写代码,没有假设。不能假设我们的代码工作在哪种理想的情况下,否则,代码一多,时间一长,环境变化,系统出错了,鬼才知道岀在哪里。至于提取还是不提取代码,对执行效率没什么影响。但是对结构化还是影响比较大的。例外,一个函数中if else太多的话,这个代码的清晰度是直线下降的,之所以你还认为比较清晰,那是因为这个代码还是太简单。<br /> 至于临界区保护的这段,上面已经回复了大部分。kfifo的游标确实不回滚,只是在memcpy的时候进行了取余处理。至于现在空满状态,kfifo_put()和kfifo_get()函数返回值你看到了吗?那才是代表fifo真正的状态,以及成功与否。而楼主的代码会给人歧义的感觉(返回满状态,我刚才的值到底写进fifo没呢?这是个问题,看代码吧实现吧,正常状态下也是一样的),需要仔细分析才能明白,尤其对于刚看到这段代码的程序员。你说是吧,而kfifo返回你真正写进去了多少个字节。至于你说的unsigned char 计数,我的原则是:处理器多少位的,就用多少位的计数,32的处理器,它将是unsigned int。8位的处理器,大部分情况下,软件fifo的容量用unsigned char类型的就能足够的去描述了。<br /> kfifo没有假设游标溢出的情况,要溢出就让他溢出吧,对我没什么影响。这里唯一需要保证的就是fifo的容量需要是2的指数倍。<br /> 你说kfifo代码太理想化,那只能说明你没有对它进行仔细分析,或者认真的看过这里面到底实现了什么函数。你说是吧!linux里面没有假设,这也是它的哲学。<br /> 还有重提一下,即使在51这样的处理器上,kfifo的实现也只比楼主的实现在单字节入队岀队的情况下,降低3%不到的执行性能。而少占用了超过20%的代码空间,你来评价一下综合效率,如何取舍?3%效率的下降是memcpy带来的,但是它也带来了代码的通用性的显著增强,代码重用率大幅提升。在上两个帖子提到的两种需要一次性多字节入队岀队的情况下,楼主的代码连执行效率都占不到任何便宜。<br /> 时间、空间都输给了kfifo,代码可读性也输了,通用性还是输了,我不明白,楼上你还有什么理由为楼主的这段代码叫好呢?唯一欣赏的是楼主有了编写高质量程序的意识,这难能可贵。也是我一开篇对他比较赞赏的原因。希望楼主能吸取经验,更上一层楼,切莫被蒙蔽了双眼。
01dxwlm
发表于 2008-8-10 16:14
lczsx2000,我佩服你的务实精神
首先我承认我的代码肯定并不适合任何情况,对于linux代码那是全世界人民的结晶,我是不能比的,我的代码在51上肯定是不好的,占用的资源太多,不过在大ROM的系统中我想占用FLASH的问题就不算大问题了,我在日立的h8/300的32位芯片上使用,freescale 的AW60上使用,感觉非常适合我应用的特定场合。下面是我在52上使用的例子(在我的AT89S52学习板,2KRAM,8KFLASH上测试)的。<br /> <br /> 相关链接:<a href='https://bbs.21ic.com/upfiles/img/20079/200792516306632.rar'>https://bbs.21ic.com/upfiles/img/20079/200792516306632.rar</a>
lczsx2000
发表于 2008-8-10 16:30
楼主,我没有丝毫贬低你的意思。
只是希望共同进步,更上一层楼!<br />linux也是人写出来的,人家老外能写出优秀的代码,我们一定也能,你说是吧。我现在在线,可以查我个人资料,加qq聊聊
crypt.wind
发表于 2008-8-11 16:18
临界区这一段有误
lczsx2000
发表于 2008-8-11 22:17
楼上能讲详细点吗?
xwj
发表于 2008-8-11 23:01
呵呵,生命不止,优化不息! 俺滴55555积分终于完成!
01dxwlm
发表于 2008-8-12 12:44
等我有空的时候我来优化下
最早是在AW60上使用,后来在32位上使用,所以可能在KEIL C下并不是合适的.<br />最近比较忙,等有空了我来优化下..
lczsx2000
发表于 2008-8-17 13:30
楼上的楼上不厚道
说临界区有误的兄弟怎么不出来指出错误在哪了呢
ko_wangph
发表于 2008-8-21 12:16
不懂
顶
Cowherd
发表于 2008-8-21 12:41
佩服,佩服
我菜菜鸟一个,老是着急。根本不懂得去注释。去模块化。以后一定学习