
<?xml version="1.0" encoding="gb2312"?>
<rss version="2.0">

<channel>
<title>52RD Spaces，研发人员自己的博客</title>
<link>http://www.52RD.com/Blog/</link>
<description>52RD Spaces，研发人员自己的博客</description>
<copyright>52RD</copyright>
<generator>52RD Blog</generator>
<webMaster>webmaster_52rd@126.com</webMaster>
<item>
<title><![CDATA[Linux应用程序开发续（转）]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=14668</link>
<author>wangxg97</author>
<pubDate>2008-5-6 15:50:00</pubDate>
<description><![CDATA[<P>termios结构体内容：</P><PRE class=screen>成员            描述
-------------------------------------------
c_cflag         控制模式标志
c_lflag         本地模式标志
c_iflag         输入模式标志
c_oflag         输出模式标志
c_line          line discipline
c_cc[NCCS]      控制字符
c_ispeed        输入波特率
c_ospeed        输出波特率
</PRE>
<P>在termios结构中的四个标志控制了输入输出的四个不同部份。输入模式标志c_iflag决定如何解释和处理接收的字符。输出模式标志c_oflag决定如何解释和处理发送到tty设备的字符。控制模式标志决定设备的一系列协议特征，这一标志只对物理设备有效。本地模式标志c_lflag决定字符在输出前如何收集和处理。</P>
<P>在串口传输中，用波特率来表示传输的速度，1波特表示在1秒钟内可以传输1个码元。波特率设置可以使用cfsetispeed(&amp;new_termios,B19200)和cfsetospeed(&amp;new_termios,B19200)这两个函数来完成，默认的波特率为9600baud。cfsetispeed()函数用来设置输入的波特率，cfsetospeed()函数用来设置输出的波特率。B19200是termios.h头文件里定义的一个宏，表示19200的波特率。</P>
<P>CLOCAL和CREAD是c_cflag成员中与速率相关的标志，在串口编程中，这两个标志一定要有效，以确保程序在突发的作业控制或挂起时，不会成为端口的占有都，同时串口的接收驱动会自动读入数据。设置方法如下：</P><PRE class=screen>termios_new.c_cflag |= CLOCAL;                 //保证程序不会成为端的占有者
termios_new.c_cflag |= CREAD;                  //使端口能读取输入的数据
</PRE>
<P>设置串口属性不能直接赋值，要通过对termios不同成员进行"与"和"或"操作来实现。在termios.h文件，定义了各种常量，如上面介绍的CLOCAL，CREAD。这些常量的值是掩码，通过把这些常量与termios结构成员进行逻辑操作就可实现串口属性的设置。在编程时用"|="来启用属性，用"&amp;=~"来取消属性。</P>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876276></A>9.3.&nbsp;c_iflag输入标志说明</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>BRKINT和IGNBRK</P>
<P>如果设置了IGNBRK，中断条件被忽略。如果没有设置IGNBRK而设置了BRKINT，中断条件清空输入输出队列中所有的数据并且向tty的前台进程组中所有进程发送一个SIGINT信号。如果这两个都没有设置，中断条件会被看作一个0字符。这时，如果设置了PARMRK，当检测到一个帧误差时将会向应用程序发送三个字节'\377''\0''\0'，而不是只发送一个'\0'。</P>
<LI>
<P>PARMRK和IGNPAR</P>
<P>如果设定了IGNPAR，则忽略接收到的数据的奇偶检验错误或帧错误（除了前面提到的中断条件）。如果没有设置IGNPAR而设置了PARMRK，当接收到的字节存在奇偶检验错误或帧错误的时候。将向应用程序发送一个三字节的'\377''\0''\n'错误报告。其中n表示所接收到的字节。如果两者都没有设置，除了接收到的字节存在奇偶检验错误或帧误差之外的中止条件都会向应用程序发送一个单字节（'\0'）的报告。</P>
<LI>
<P>INPCK</P>
<P>如果设置，则进行奇偶校验。如果不进行奇偶检验，PARMRK和IGNPAR将对存在的奇偶校验错误不产生任何的影响。</P>
<LI>
<P>ISTRIP</P>
<P>如果设置，所接收到的所有字节的高位将会被去除，保证它们是一个7位的字符。</P>
<LI>
<P>INLCR</P>
<P>如果设置，所接收到的换行字符（'\n'）将会被转换成回车符（'\r'）。</P>
<LI>
<P>IGNCR</P>
<P>如果设置，则会忽略所有接收的回车符（'\r'）。</P>
<LI>
<P>ICRNL</P>
<P>如果设置，但IGNCR没有设置，接收到的回车符向应用程序发送时会变换成换行符。</P>
<LI>
<P>IUCLC</P>
<P>如果IUCLC和IEXTEN都设置，接收到的所有大写字母发送给应程序时都被转换成小写字母。POSIX中没有定义该标记。</P>
<LI>
<P>IXOFF</P>
<P>如果设置，为避免tty设备的输入缓冲区溢出，tty设备可以向终端发送停止符^S和开始符^Q，要求终端停止或重新开始向计算机发送数据。通过停止符和开始符来控制数据流的方式叫软件流控制，软件流控制方式较少用，我们主要还是用硬件流控制方式。硬件流控制在c_cflag标志中设置。</P>
<LI>
<P>IXON</P>
<P>如果设置，接收到^S后会停止向这个tty设备输出，接收到^Q后会恢复输出。</P>
<LI>
<P>IXANY</P>
<P>如果设置，则接到任何字符都会重新开始输出，而不仅仅是^Q字符。</P>
<LI>
<P>IMAXBEL</P>
<P>如果设置，当输入缓冲区空间满时，再接收到的任何字符就会发出警报符'\a'。POSIX中没有定义该标记。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876510></A>9.4.&nbsp;c_oflag输出标志说明</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>OPOST是POSIX定义的唯一一个标志，只有设置了该标志后，其它非POSIX的输出标记才会生效。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>OPOST</P>
<P>开启该标记，后面的输出标记才会生效。否则，不会对输出数据进行处理。</P>
<LI>
<P>OLCUC</P>
<P>如果设置，大写字母被转换成小写字母输出。</P>
<LI>
<P>ONLCR</P>
<P>如果设置，在发送换行符（'\n'）前先发送回车符（'\r'）。</P>
<LI>
<P>ONOCR</P>
<P>如果设置，当current column为0时，回车符不会被发送也不会被处理。</P>
<LI>
<P>OCRNL</P>
<P>如果设置，回车符会被转换成换行符。另外，如果设置了ONLRET，则current column会被设为0.</P>
<LI>
<P>ONLRET</P>
<P>如果设置，当一个换行符或回车符被发送的时候，current column会被设置为0。</P>
<LI>
<P>OXTABS</P>
<P>如果设置，制表符会被转换成空格符。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876628></A>9.5.&nbsp;c_cflag控制模式标志说明</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>CLOCAL</P>
<P>如果设置，modem的控制线将会被忽略。如果没有设置，则open()函数会阻塞直到载波检测线宣告modem处于摘机状态为止。</P>
<LI>
<P>CREAD</P>
<P>只有设置了才能接收字符，该标记是一定要设置的。</P>
<LI>
<P>CSIZE</P>
<P>设置传输字符的位数。CS5表示每个字符5位，CS6表示每个字符6位，CS7表示每个字符7位，CS8表示每个字符8位。</P>
<LI>
<P>CSTOPB</P>
<P>设置停止位的位数，如果设置，则会在每帧后产生两个停止位，如果没有设置，则产生一个停止位。一般都是使用一位停止位。需要两位停止位的设备已过时了。</P>
<LI>
<P>HUPCL</P>
<P>如果设置，当设备最后打开的文件描述符关闭时，串口上的DTR和RTS线会减弱信号，通知Modem挂断。也就是说，当一个用户通过Modem拔号登录系统，然后注销，这时Modem会自动挂断。</P>
<LI>
<P>PARENB和PARODD</P>
<P>如果设置PARENB，会产生一个奇偶检验位。如果没有设置PARODD，则产生偶校验位，如果设置了PARODD，则产生奇校验位。如果没有设置PARENB，则PARODD的设置会被忽略。</P>
<LI>
<P>CRTSCTS</P>
<P>使用硬件流控制。在高速（19200bps或更高）传输时，使用软件流控制会使效率降低，这个时候必须使用硬件流控制。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876752></A>9.6.&nbsp;c_cc[]控制字符说明</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>只有在本地模式标志c_lflag中设置了IEXITEN时，POSIX没有定义的控制字符才能在Linux中使用。每个控制字符都对应一个按键组合（^C、^H等），但VMIN和VTIME这两个控制字符除外，它们不对应控制符。这两个控制字符只在原始模式下才有效。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>c_cc[VINTR]</P>
<P>默认对应的控制符是^C，作用是清空输入和输出队列的数据并且向tty设备的前台进程组中的每一个程序发送一个SIGINT信号，对SIGINT信号没有定义处理程序的进程会马上退出。</P>
<LI>
<P>c_cc[VQUIT]</P>
<P>默认对应的控制符是^\，作用是清空输入和输出队列的数据并向tty设备的前台进程组中的每一个程序发送一个SIGQUIT信号，对SIGQUIT信号没有定义处理程序的进程会马上退出。</P>
<LI>
<P>c_cc[verase]</P>
<P>默认对应的控制符是^H或^?，作用是在标准模式下，删除本行前一个字符，该字符在原始模式下没有作用。</P>
<LI>
<P>c_cc[VKILL]</P>
<P>默认对应的控制符是^U，在标准模式下，删除整行字符，该字符在原始模式下没有作用。</P>
<LI>
<P>c_cc[VEOF]</P>
<P>默认对应的控制符是^D，在标准模式下，使用read()返回0，标志一个文件结束。</P>
<LI>
<P>c_cc[VSTOP]</P>
<P>默认对应的控制字符是^S，作用是使用tty设备暂停输出直到接收到VSTART控制字符。或者，如果设备了IXANY，则等收到任何字符就开始输出。</P>
<LI>
<P>c_cc[VSTART]</P>
<P>默认对应的控制字符是^Q，作用是重新开始被暂停的tty设备的输出。</P>
<LI>
<P>c_cc[VSUSP]</P>
<P>默认对应的控制字符是^Z，使当前的前台进程接收到一个SIGTSTP信号。</P>
<LI>
<P>c_cc[VEOL]和c_cc[VEOL2]</P>
<P>在标准模式下，这两个下标在行的末尾加上一个换行符（'\n'），标志一个行的结束，从而使用缓冲区中的数据被发送，并开始新的一行。POSIX中没有定义VEOL2。</P>
<LI>
<P>c_cc[VREPRINT]</P>
<P>默认对应的控制符是^R，在标准模式下，如果设置了本地模式标志ECHO，使用VERPRINT对应的控制符和换行符在本地显示，并且重新打印当前缓冲区中的字符。POSIX中没有定义VERPRINT。</P>
<LI>
<P>c_cc[VWERASE]</P>
<P>默认对应的控制字符是^W，在标准模式下，删除缓冲区末端的所有空格符，然后删除与之相邻的非空格符，从而起到在一行中删除前一个单词的效果。POSIX中没有定义VWERASE。</P>
<LI>
<P>c_cc[VLNEXT]</P>
<P>默认对应的控制符是^V，作用是让下一个字符原封不动地进入缓冲区。如果要让^V字符进入缓冲区，需要按两下^V。POSIX中没有定义VLNEXT。</P></LI></UL></DIV>
<P>要禁用某个控制字符，只需把它设置为_POSIX_VDISABLE即可。但该常量只在Linux中有效，所以如果程序要考虑移植性的问题，请不要使用该常量。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876976></A>9.7.&nbsp;c_lflag本地模式标志说明</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>ICANON</P>
<P>如果设置，则启动标准模式，如果没有设置，则启动原始模式。</P>
<LI>
<P>ECHO</P>
<P>如果设置，则启动本地回显。如果没有设置，则除了ECHONL之外，其他以ECHO开头的标记都会失效。</P>
<LI>
<P>ECHOCTL</P>
<P>如果设置，则以^C的形式打印控制字符，如：按Ctrl+C显示^C，按Ctrl+？显示^?。</P>
<LI>
<P>ECHOE</P>
<P>如果在标准模式下设定了ECHOE标志，则当收到一个ERASE控制符时将删除前一个显示字符。</P>
<LI>
<P>ECHOK和ECHOKE</P>
<P>在标准模式下，当接收到一个KILL控制符，则在缓冲区中删除当前行。如果ECHOK、ECHOKE和ECHOE都没有设置，则用ECHOCTL表示的KILL字符（^U）将会在输出终端上显示，表示当前行已经被删除。</P>
<P>如果已经设置了ECHOE和ECHOK，但没有设置ECHOKE，将会在输出终端显示ECHOCTL表示的KILL字符，紧接着是换行，如果设置了OPOST，将会通过OPOST处理程序进行适当的处理。</P>
<P>如果ECHOK、ECHOKE和ECHOE都有设置，则会删除当前行。</P>
<P>在POSIX中没有定义ECHOKE标记，在没有定义ECHOKE标记的系统中，设置ECHOK则表示同时设置了ECHOKE标志。</P>
<LI>
<P>ECHONL</P>
<P>如果在标准模式下设置了该标志，即使没有设置ECHO标志，换行符还是会被显示出来。</P>
<LI>
<P>ECHOPRT</P>
<P>如果设置，则字符会被简单地打印出来，包括各种控制字符。在POSIX中没有定义该标志。</P>
<LI>
<P>ISIG</P>
<P>如果设置，与INTR、QUIT和SUSP相对应的信号SIGINT、SIGQUIT和SIGTSTP会发送到tty设备的前台进程组中的所有进程。</P>
<LI>
<P>NOFLSH</P>
<P>一般情况下，当接收到INTR或QUIT控制符的时候会清空输入输出队列，当接收到SUSP控制符时会清空输入队列。但是如果设置了NOFLUSH标志，则所有队列都不会被清空。</P>
<LI>
<P>TOSTOP</P>
<P>如果设置，则当一个非前台进程组的进程试图向它的控制终端写入数据时，信号SIGTTOU会被被发送到这个进程所在的进程组。默认情况下，这个信号会使进程停止，就像收到SUSP控制符一样。</P>
<LI>
<P>IEXIEN</P>
<P>默认已设置，我们不应修改它。在Linux中IUCLC和几个与删除字符相关的标记都要求在设置了IEXIEN才能正常工作。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877178></A>9.8.&nbsp;下面介绍一些常用串口属性的设置方法。</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>设置流控制</P><PRE class=screen>termios_new.c_cflag &amp;= ~CRTSCTS;            //不使用流控制
termios_new.c_cflag |= CRTSCTS;                 //使用硬件流控制
termios_new.c_iflag |= IXON|IXOFF|IXANY;        //使用软件流控制
</PRE>
<LI>
<P>屏蔽字符大小位</P><PRE class=screen>termios_new.c_cflag &amp;= ~CSIZE; 
</PRE>
<LI>
<P>设置数据位大小</P><PRE class=screen>termios_new.c_cflag |= CS8;         //使用8位数据位
termios_new.c_cflag |= CS7;         //使用7位数据位
termios_new.c_cflag |= CS6;         //使用6位数据位
termios_new.c_cflag |= CS5;         //使用5位数据位
</PRE>
<LI>
<P>设置奇偶校验方式</P><PRE class=screen>termios_new.c_cflag &amp;amp;= ~PARENB;       //无奇偶校验

termios_new.c_cflag |= PARENB;            //奇校验
termios_new.c_cflag &amp;amp;= ~PARODD;       

termios_new.c_cflag |= PARENB;            //偶校验
termios_new.c_cflag &amp;amp;= ~PARODD;       
</PRE>
<LI>
<P>停止位</P><PRE class=screen>termios_new.c_cflag |= CSTOPB;            //2位停止位
termios_new.c_cflag &amp;amp;= ~CSTOPB;       //1位停止位  
</PRE>
<LI>
<P>输出模式</P><PRE class=screen>termios_new.c_cflag &amp;amp;= ~OPOST;        //原始数据（RAW）输出
</PRE>
<LI>
<P>控制字符</P><PRE class=screen>termios_new.c_cc[VMIN] = 1;               //读取字符的最小数量
termios_new.c_cc[VTIME] = 1;              //读取第一个字符的等待时间
</PRE>
<LI>
<P>关闭终端回显，键盘输入的字符不会在终端窗口显示。</P><PRE class=screen>#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;termios.h&gt;
#include &lt;unistd.h&gt;

int main(void)
{
        struct termios ts,ots;
        char passbuf[1024];

        tcgetattr(STDIN_FILENO,&amp;ts);  /* STDIN_FILENO的值是1,表示标准输入的文件描述符 */
        ots = ts;

        ts.c_lflag &amp;= ~ECHO;         /* 关闭回终端回显功能*/
        ts.c_lflag |= ECHONL;
        tcsetattr(STDIN_FILENO,TCSAFLUSH,&amp;ts);  /* 应用新终端设置 */

        fgets(passbuf,1024,stdin);     /* 输入字符不会在终端显示 */
        printf("you input character = %s\n",passbuf);

        tcsetattr(STDIN_FILENO,TCSANOW,&amp;ots);  /* 恢复旧的终端设备 */
}
</PRE></LI></UL></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2877348></A>Chapter&nbsp;10.&nbsp;安全</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877382">10.1. 内核漏洞介绍</A></SPAN></DT></DL></DIV>
<P>Linux内核以稳定和安全著称，但随着Linux使用范围的不断扩展，各种漏洞也慢慢被内核开发人员或黑客发现。这里介绍有关Linux内核和基于Linux的开源软件的安全问题。</P>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877382></A>10.1.&nbsp;内核漏洞介绍</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>权限提升类</P>
<LI>
<P>拒绝服务类</P>
<LI>
<P>溢出类</P>
<LI>
<P>IP地址欺骗类</P></LI></UL></DIV></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2877430></A>Chapter&nbsp;11.&nbsp;数据结构(Data Structure)</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877437">11.1. 基础概念</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877597">11.2. 线性数据结构</A></SPAN></DT></DL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877437></A>11.1.&nbsp;基础概念</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>在实际解决问题的时候，各种数据都不是孤立的，数据之间总是存在关系，这种数据之间的关系叫做数据结构。我们可以把数据结构的形式归并为四种：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>集合：数据之间没有对应关系，但同属于一个集合。如汽车是一个集合，编程语言也是一个集合。</P>
<LI>
<P>线性结构：各数据有一一对应的关系，有前驱也有后续。</P>
<LI>
<P>树形结构：各数据间存在一对多的关系，有一个前驱但有多个后续。</P>
<LI>
<P>图：各数据间有多对多的关系，对前驱和后续没有限制。</P></LI></UL></DIV>
<P>数据类型是一个值的集合和定义在这个值集上的一组操作的总称。</P>
<P>数据类型可分两类，一类是每个对象仅由单值组成，称为原子类型，如整型、字符型等。另一类是由某种结构组成的类型，叫结构类型，如数组、字符串等。</P>
<P>抽象数据结构（Abstract Data Type，ADT）是一种数据类型及在这个类型上定义的一组合法的操作。</P>
<P>算法（Algorithm）是一个有穷规则（或语句、指令）的有序集合。通俗地说，就是计算机解决问题的过程。算法应具备以下几个重要的特性：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>输入：一个算法有零个或多个输入。</P>
<LI>
<P>输出：一个算法至少有一个输出，这种输出是同输入有着某些特定关系的量。没有输出的算法是没有意义的。</P>
<LI>
<P>有穷性：一个算法必须总是在执行有穷步之后结束，且每一步都在有穷时间内完成。</P>
<LI>
<P>确定性：算法中每条指令的含义都必须明确，无二义性。对相同的输入，必须有相同的结果。</P>
<LI>
<P>可行性：算法中的每条指令的执行时间都是有限的。</P></LI></UL></DIV>
<P>描述算法的工具：自然语言、流程图、形式化语言和程序设计语言。</P>
<P>由瑞士科学家Niklaus Wirthrn提出的计算机界公认的公式：算法 + 数据结构 = 程序</P>
<P>算法设计的要求：正确、可读、健壮、快速、节省存储空间。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877597></A>11.2.&nbsp;线性数据结构</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>线性结构中的数据元素之间是一种线性关系，数据元素一个接一个地排列。如排除的队列、表格中一行行的记录等。数据元素可以包含多个数据项（字段），包含多个数据项的数据元素叫做记录。由大量记录组成的线性表又称为文件。</P>
<P>线性表的数学表示模型：a0,a1,a2,...a（n-1）。</P>
<P>顺序连续存放的线性表是最简单的，称为顺序存储结构线性表。它在内存开辟一片连续的存储空间，让线性表的第一个元素存放在内存空间的第一个位置，第二个元素存放在第二个位置，其它元素以此类推。数据元素间的前驱和后继关系表现在存放位置的前后关系上。顺序存储结构线性表算法在插入或删除操作时的效率不高。平均起来，每插入或删除一个元素需要移动一半的元素，最坏的情况更要移动全部的元素。另外，顺序表不利于存储空间的分配。在经常需要进入插入或删除操作的线性表中，使用顺序存储结构线性表是不合适的。所以我们有了链式存储结构线性表。</P>
<DIV class=note style="MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in">
<TABLE summary=Note border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle width=25 rowSpan=2><IMG alt=[Note] src="http://www.ringkee.com/note/opensource/linuxdev/images/note.png"></TD>
<TH align=left></TH></TR>
<TR>
<TD vAlign=top align=left colSpan=2>数组就是顺序存储结构的程序实现。</TD></TR></TBODY></TABLE></DIV>
<P>链式存储结构线性表由结点组成，每个节点由一个数据元素和一个指向下个结点的指针组成。每个结点中如果只有一个指向后续指针的链表，叫单链表。由于链表通过指针指向下一个结点，所以数据元素可以分散存储。</P>
<P>单链表的建立是一种动态内在管理操作，表中的每个节点占用的存储空间无需预先指定，而是在运行时动态申请。</P>
<P>单链表一旦创建就可对链表进行操作。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>查找值为x的节点，并返回该节点地址。算法分析：从单链表的第一个节点开始，判断当前节点的数据域的值是否为x，若是，则返回该节点的指针域，否则，依据指针域内的指针查找下一节点，直至表结束。若找不到，则返回空。</P>
<LI>
<P>查找第i个节点，返回期指针。算法分析：从单链表的第一个节点开始，依次判断当前节点是否为第i个节点，若是则返回其指针，否则，依据指针域内的指针查找下一节点，直至表结束。若找不到，则返回空。</P></LI></UL></DIV></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2877713></A>Chapter&nbsp;12.&nbsp;网络编程</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877720">12.1. TCP/IP协议分析</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877814">12.2. 入门示例程序</A></SPAN></DT></DL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877720></A>12.1.&nbsp;TCP/IP协议分析</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>EthernetII帧的结构（DMAC+SMAC+Type+Data+CRC），EthernetII帧的大小是有限制的，最小不能小于64字节，最大不能超过1518字节，否则帧会被丢弃。一个EthernetII帧包括的内容有：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>DMAC，目的MAC地址，占48个bit，共6个字节。</P>
<LI>
<P>SMAC，源MAC地址，占48个bit，共6个字节。</P>
<LI>
<P>Type，帧类型，如ip,arp等。占16个bit，共2个字节。</P>
<LI>
<P>Data，帧数据，容量是变化的，但最大不能越过1500个字节，最小不能小于46个字节。</P>
<LI>
<P>CRC，校验码，占32个bit，共4个字节。</P></LI></UL></DIV>
<P>IP包结构：</P>
<DIV><IMG src="http://www.ringkee.com/note/opensource/linuxdev/images/ip.jpg"></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2877814></A>12.2.&nbsp;入门示例程序</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>下面我们开发一个模拟Echo服务功能的tcp程序。通过这个简单的程序我们可以学习tcp/ip网络编程的基础结构。</P>
<P>tcpserver.c是服务端程序，运行后会监听一个端口。</P><PRE class=screen>debian:~/c/kernelmodule# cat tcpserver.c
#include &lt;sys/types.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;fcntl.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/socket.h&gt;
#include &lt;netinet/in.h&gt;
#include &lt;arpa/inet.h&gt;
#include &lt;stdio.h&gt;
#include &lt;stdlib.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/time.h&gt;
#include &lt;sys/wait.h&gt;
#include &lt;errno.h&gt;

int main(int argc,char *argv[]){
        int iswork,data,fd1,fd2;
        pid_t pidchild;
        socklen_t clientlen;
        struct sockaddr_in clientaddr;
        struct sockaddr_in serveraddr;
        char buffer[1000];

        if(argc != 2){
                printf("Usage: tcpserver [port number]\n");
                exit(1);
        }

        if((fd1 = socket(AF_INET,SOCK_STREAM,0))&lt;0){
                printf("socket error!\n");
                exit(1);
        }

        memset(&amp;serveraddr,0,sizeof(serveraddr));

        serveraddr.sin_family = AF_INET;
        serveraddr.sin_addr.s_addr = htonl(INADDR_ANY);
        serveraddr.sin_port = htons(atoi(argv[1]));

        if(bind(fd1,(struct sockaddr*)&amp;serveraddr,sizeof(serveraddr))&lt;0){
                printf("bind error!\n");
                exit(1);
        }

        if(listen(fd1,3)&lt;0){
                printf("listen error!\n");
                exit(1);
        }

        iswork = 1;
        while(iswork){
                clientlen = sizeof(clientaddr);
                if((fd2 = accept(fd1,(struct sockaddr *)&amp;clientaddr,&amp;clientlen))&lt;0){
                        printf("accept error!\n");
                        exit(1);
                }

                if((pidchild = fork()) == -1){
                        printf("fork error!\n");
                        exit(1);
                }

                if(pidchild == 0){
                        if(close(fd1) == -1){
                                printf("close error!\n");
                                exit(1);
                }

                printf("Connect from %s\n",inet_ntoa(clientaddr.sin_addr));

                while(1){
                        memset(buffer,0,1000);

                        if(data = read(fd2,buffer,sizeof(buffer))&gt;0){
                                printf("%s",buffer);
                                if(write(fd2,buffer,sizeof(buffer))&lt;0){
                                        printf("send error!\n");
                                        exit(1);
                                }
                        }
                }
                exit(0);
                }

                if(close(fd2) == -1){
                        printf("close error!\n");
                        exit(1);
                }
        }
}
</PRE>
<P>几个主要函数说明：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>socket()函数，创建套接口，返回套接口句柄。</P>
<LI>
<P>bind()函数，</P>
<LI>
<P>listen()函数，</P>
<LI>
<P>htonl()和htons()</P>
<LI>
<P>accept()</P>
<LI>
<P></P>
<LI>
<P></P></LI></UL></DIV>
<P>数据包在应用层称为data，在TCP层称为segment，在IP层称为packet，在数据链路层称为frame。</P></DIV></DIV>]]></description>
</item><item>
<title><![CDATA[Linux应用程序开发（转）]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=14667</link>
<author>wangxg97</author>
<pubDate>2008-5-6 15:46:00</pubDate>
<description><![CDATA[<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2807950">1. C语言基础</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2807957">1.1. 数据类型</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2811802">1.2. 关键字</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2811832">1.3. 变量等级</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2810346">1.4. 特殊字符的表示方法：</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861453">1.5. 格式化字符串</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861522">1.6. 指针与数组</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861616">1.7. 结构体</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861695">1.8. typedef--自定义类型名</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861717">1.9. 函数和宏</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861811">1.10. ANSI标准头文件</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808419">2. 预处理</A></SPAN> 
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808509">3. 使用GCC编译程序</A></SPAN> 
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808555">4. 使用gdb调试程序</A></SPAN> 
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808856">5. Linux程序开发基础</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808864">5.1. 路径</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808991">5.2. 库文件</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809272">5.3. 预处理</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809459">5.4. 系统调用（system call）</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809510">6. 文件处理</A></SPAN> 
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809712">7. Linux环境编程</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809720">7.1. 参数选项</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875409">7.2. 环境变量</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875469">7.3. 时间</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875567">7.4. 临时文件</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875601">7.5. 用户信息</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875678">7.6. 日志信息</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875738">8. 进程</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875927">8.1. 进程状态</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875951">9. 串口编程</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875959">9.1. 常用函数</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876100">9.2. 设置串口属性</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876276">9.3. c_iflag输入标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876510">9.4. c_oflag输出标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876628">9.5. c_cflag控制模式标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876752">9.6. c_cc[]控制字符说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876976">9.7. c_lflag本地模式标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877178">9.8. 下面介绍一些常用串口属性的设置方法。</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877348">10. 安全</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877382">10.1. 内核漏洞介绍</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877430">11. 数据结构(Data Structure)</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877437">11.1. 基础概念</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877597">11.2. 线性数据结构</A></SPAN></DT></DL>
<DT><SPAN class=chapter><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877713">12. 网络编程</A></SPAN> 
<DD>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877720">12.1. TCP/IP协议分析</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877814">12.2. 入门示例程序</A></SPAN></DT></DL></DD></DL></DIV>
<DIV class=list-of-tables>
<P><B>List of Tables</B></P>
<DL>
<DT>1.1. <A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2810354">特殊字符的表示方法</A></DT></DL></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2807950></A>Chapter&nbsp;1.&nbsp;C语言基础</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2807957">1.1. 数据类型</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2811802">1.2. 关键字</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2811832">1.3. 变量等级</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2810346">1.4. 特殊字符的表示方法：</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861453">1.5. 格式化字符串</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861522">1.6. 指针与数组</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861616">1.7. 结构体</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861695">1.8. typedef--自定义类型名</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861717">1.9. 函数和宏</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2861811">1.10. ANSI标准头文件</A></SPAN></DT></DL></DIV>
<P>Linux是使用C语言开发的，基于Linux平台的应用程序开发，C语言是首选的开发语言。本章记录C语言的基本概念和基础知识。</P>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2807957></A>1.1.&nbsp;数据类型</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>整数类型（int），</P>
<P>各种整数数制表示法：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>ddd，十进制表示法，d为0--9的整数，但不能以0开头。如：123，345。</P>
<LI>
<P>0ooo，八进制表示法，以0（数字0）开头，o为0--7的整数。如：010(八进制)=8(十进制)，014(八进制)=12(十进制)。</P>
<LI>
<P>0xhhh，十六进制表示法，以0x或0X开头，h为0--9、A、B、C、D、E、F。如：0x10(十六进制)=16(十进制)，0xA(十六进制)=10(十进制)。</P>
<LI>
<P>以L或l结尾的数表示长整数(long int)，编译器会以32位空间存放此数字，但GCC默认是以32位存放整数，所以此表示法在Linux下没什么作用。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2811802></A>1.2.&nbsp;关键字</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>关键字是C语言本身保留使用的，不能用于变量和函数名。</P><PRE class=screen>auto          double          int          struct
break         else            long         switch
case          enum            register     typedef
char          extern          return       union
const         float           short        unsigned
continue      for             signed       void
default       goto            sizeof       volatile
do            if              static       while
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2811832></A>1.3.&nbsp;变量等级</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>auto，内部变量，在函数内部声明。只能在函数内部使用，它的生命周期从调用函数开始，到函数执行完时消失。内部变量以堆栈存放，必须在函数执行时才会存在，这种方式称为声明。auto可省略。如：</P><PRE class=screen>auto int i = 0;
/* 可写成int i = 0; */
</PRE>
<P>内部变量的优缺点：</P>
<DIV class=itemizedlist>
<UL type=circle>
<LI>
<P>内部变量只在函数内有效，能提高函数的安全。</P>
<LI>
<P>内部变量在函数结束时消失，不会长期占用内存空间，能提高内存的利用率。</P>
<LI>
<P>内部变量的缺点是生命周期短，函数运行结束后不能保留。</P></LI></UL></DIV>
<LI>
<P>static auto，内部静态变量，在函数内部定义，auto也可省略。内部静态变量以固定地址存放，编译时就已分配置内在空间，这种方式称为定义。由于有固定地址，函静态变量不会随函数的结束而消失。static变量会一直保存在内存空间中，当函数再次执行时，上次保留的使用静态变量可以继续使用。如：</P><PRE class=screen>static int i = 0;
</PRE>
<LI>
<P>extern，外部变量，是在函数外定义的变量，可被多个函数存取。在外部变量定义覆盖范围之内的函数内可以自由使用外部变量。不在外部变量定义覆盖范围之内的函数要使用外部变量就要先使用extern关健字来声明外部变量。</P><PRE class=screen>int i;      /* 外部变量定义，在main函数外 */

int main(void)
{
i = 1;            /* main()函数位于外部变量i定义的下面，不用声明可直接使用 */
printf("%d\n", i);
}
</PRE>
<P>不在外部变量定义覆盖范围之内的函数要使用外部变量就要先使用extern关健字来声明外部变量。</P><PRE class=screen>int main(void)
{
extern int i;        /* 外部变量i在main()函数之后定义，需用extern关键字声明后才能使用 */

i = 1;
printf("%d\n",i);
}
int i;
...
</PRE>
<P>在另外的程序文件中我们也可以通过扩展声明使用其它程序文件中的外部变量。</P><PRE class=screen>程序1 hello.c
#include &lt;stdio.h&gt;

int main(void)
{
        extern int i;  //扩展声明外部变量
        i = 333;
        printf("%d\n", i);

        extern des(void);  //扩展声明外部函数
        des();
}
int i;         //外部变量定义

程序2 hello1.c
#include &lt;stdio.h&gt;

extern int i;      //扩展声明其它程序文件中的外部变量

void des()
{
        i++;
        printf("%d\n",i);
}                                               
</PRE>
<P>编译</P><PRE class=screen>debian:~/c# gcc hello.c hello1.c
debian:~/c# ./a.out
333
334
</PRE>
<P>外部变量有效范围总结：</P>
<DIV class=itemizedlist>
<UL type=circle>
<LI>
<P>由外部变量定义的位置开始，至文件结尾。</P>
<LI>
<P>不在有效范围内的函数，也可通过extern扩展声明使用定义的外部变量，且可在多个函数中使用。注意：在各函数中使用的外部变量是一样的，对该变量的修改会影响到其它函数内的同一变量。</P>
<LI>
<P>可用extern扩展声明使用另外一个程序文件中的外部变量。</P></LI></UL></DIV>
<P>外部变量的优点是生命周期长，可在函数间共享数据和传输数据。缺点是变量安全性较低，但可通过合理设置外部变量的有效范围提高安全性。</P>
<LI>
<P>static extern，外部静态变量，在函数外部定义，只供单一程序文件使用，即使其它程序文件定义了同样名称的变量，编译器也把它当成另外一个变量处理。外部静态变量能有效隔离变量在一个程序文件中。</P><PRE class=screen>static int i;
</PRE>
<LI>
<P>register，register变量是以寄存器（register）来存放变量，而不是一般内存。只有内部变量才能使用register类型变量。使用这种变量能加快变量的处理速度。但缺点是要占用CPU寄存器。如：</P><PRE class=screen>register int i;
register int j;
</PRE></LI></UL></DIV>
<P>变量等级的概念也同样适用于函数。若想调用不在有效范围内的函数，则要用extern扩展声明函数的有效范围。</P>
<P>内部变量是以堆栈方式存放的，必须在函数执行时才会存在，所以称为声明（Declaration）。其它如static auto、extern和static extern等级的变量，都是以固定的地址来存放的，而不是以堆栈方式存放的，在程序编译时就已分配了空间，所以称之为定义（Definition）。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2810346></A>1.4.&nbsp;特殊字符的表示方法：</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=table><A name=id2810354></A>
<P class=title><B>Table&nbsp;1.1.&nbsp;特殊字符的表示方法</B></P>
<TABLE summary=特殊字符的表示方法 border=1>
<COLGROUP>
<COL>
<COL>
<COL>
<COL></COLGROUP>
<THEAD>
<TR>
<TH>符号</TH>
<TH>ASCII字符（十六进制）</TH>
<TH>句柄符号</TH>
<TH>作用</TH></TR></THEAD>
<TBODY>
<TR>
<TD>\a</TD>
<TD>07</TD>
<TD>BEL</TD>
<TD>响铃</TD></TR>
<TR>
<TD>\b</TD>
<TD>08</TD>
<TD>BS</TD>
<TD>回格</TD></TR>
<TR>
<TD>\f</TD>
<TD>0C</TD>
<TD>FF</TD>
<TD>换页</TD></TR>
<TR>
<TD>\n</TD>
<TD>0A</TD>
<TD>LF</TD>
<TD>换行</TD></TR>
<TR>
<TD>\r</TD>
<TD>0D</TD>
<TD>CR</TD>
<TD>回车键</TD></TR>
<TR>
<TD>\t</TD>
<TD>09</TD>
<TD>HT</TD>
<TD>[tab]键</TD></TR>
<TR>
<TD>\v</TD>
<TD>0B</TD>
<TD>VT</TD>
<TD>空行</TD></TR>
<TR>
<TD>\0</TD>
<TD>00</TD>
<TD>NUL</TD>
<TD>空字符</TD></TR>
<TR>
<TD>\\</TD>
<TD>5C</TD>
<TD>\</TD>
<TD>反斜杠</TD></TR>
<TR>
<TD>\'</TD>
<TD>2C</TD>
<TD>'</TD>
<TD>单引号</TD></TR>
<TR>
<TD>\"</TD>
<TD>22</TD>
<TD>"</TD>
<TD>双引号</TD></TR>
<TR>
<TD>\?</TD>
<TD>3F</TD>
<TD>?</TD>
<TD>问号</TD></TR></TBODY></TABLE></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861453></A>1.5.&nbsp;格式化字符串</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>%c，表示字符变量。</P>
<LI>
<P>%s，表示字符串变量。</P>
<LI>
<P>%f，表示浮点数变量。</P>
<LI>
<P>%d，表示整数变量。</P>
<LI>
<P>%x，表示十六进制变量。</P>
<LI>
<P>%o，表示八进制变量。</P></LI></UL></DIV></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861522></A>1.6.&nbsp;指针与数组</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>C语言中专门用来存放内存地址的变量叫指针（pointer）变量，简称指针。</P>
<LI>
<P>&amp;运算符用来取得变量地址，</P>
<LI>
<P>"*"运算符用来取得指针变量的值。</P>
<LI>
<P>数组名就是地址变量，指向内存中存放第一个数组元素的地址。数组元素编号从0开始，如a[0]表示数组a的第一个元素。</P></LI></UL></DIV>
<P>数组是内存中的连续区间，可根据声明类型存放多种数值类型。如：</P><PRE class=screen>int a[10];         声明一个有10个int元素的数组
char b[20];        声明一个有20个char元素的数组
</PRE>
<P>指针示例：</P><PRE class=screen>int *p;            /* p是一个指针，p的内容是内存的地址，在这个地址中将存放一个整数。
</PRE>
<P>数组名和指针都是用来存放内存地址的，不过数组名具有固定长度，不可变。而指针与一般变量一样，其值是可变的。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861616></A>1.7.&nbsp;结构体</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>结构体是用户定义的由基本数据类型组成的复合式数据类型。数组也是复合式数据类型，但二者是不同的，数组是相同类型数据的集合，而结构体是不同类型数据的集合。如我们可以把一个人的姓名、性别，年龄组成一个单一结构体。这样在程序处理时就把它当成一个独立对象进行处理。</P>
<P>结构体声明方法有两种，一种是分离式声明，一种是结合式声明。分离式声明是先把声明结构体，在程序中再声明结构体变量。结合式声明是把结构体声明和变量声明同时完成。</P><PRE class=screen>分离式声明示例
struct person{
    char name;
    char sex;
    int age;
};
main(void){
struct person worker;
...
}

结合式声明示例
struct person{
   char name;
   char sex;
   int age;
}worker;
</PRE>
<P>每个结构体可以表示一个工人的信息，如果要表示多个工人的信息，则可以用结构体数组。</P><PRE class=screen>struct person{
    char name;
    char sex;
    int age;
};
main(void){
struct person worker[20];     //表示20个工人
...
}

</PRE>
<P>结构体初始设置。</P><PRE class=screen>struct person{
    char name;
    char sex;
    int age;
}worker={"jims","male",30};
</PRE>
<P>用"."和"-&gt;"运算符存取结构体中的数据。"."是直接存取法，"-&gt;"为间接存取法，用于结构体指针。如果p是一个指向person结构体的指针，则p-&gt;name和(*p).name的结果是一样的。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861695></A>1.8.&nbsp;typedef--自定义类型名</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>结构体可以自定义数据类型，而typedef可以自定义新的类型名。如：</P><PRE class=screen>#include &lt;stdio.h&gt;

typedef char *STRING;  //定义一个新的字符指针类型名STRING

main(void){
        STRING a;

        a = "abc";
        printf("the a value is %s.\n",a);
}
</PRE>
<P>a为字符指针类型，自定义类型名通常以大写方式表示，以示区别。</P>
<P>#define与typedef的区别是：#define只是单纯地进行变量替换，而typedef是创建新的类型名。typedef的一个主要作用是简化声明，提高程序的可读性。如：</P><PRE class=screen>typedef struct person{
   char name;
   char sex;
   int age;
} p
</PRE>
<P>这样我们就定义一个新的结构体类型名p，在程序中我们可以使用它来声明变量。如</P><PRE class=screen>main(void){
   p worker;

  worker = {"jims","male",30};
}
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861717></A>1.9.&nbsp;函数和宏</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>函数是C代码的集合，每个C程序由一个或多个函数组成，main()是一个特殊的函数，是C程序的入口，每个C程序必须有且只能有一个mian()函数。</P>
<P>ANSI函数定义：</P><PRE class=screen>类型 函数名（类型 参数1，类型 参数2, ...）
{
   函数代码；
}

示例：
int func(int i, char c)
{
   ...
}
</PRE>
<P>在程序中要使用我们设计开发的函数，需要先进行声明，函数声明的作用是把函数类型告诉编译器。函数声明与定义差不多，只是不包括程序主体。上面示例的函数在主程序中的声明方式如下：</P><PRE class=screen>void main()
{
   int total;

   int func(int i, char c);            //函数声明
   total = int(xxx,xxx);               //声明后才能调用该函数 
}
</PRE>
<DIV class=note style="MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in">
<TABLE summary=Note border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle width=25 rowSpan=2><IMG alt=[Note] src="http://www.ringkee.com/note/opensource/linuxdev/images/note.png"></TD>
<TH align=left></TH></TR>
<TR>
<TD vAlign=top align=left colSpan=2>定义和声明中的参数类型（int,char）要相同，但名称（i，c）可以不同。</TD></TR></TBODY></TABLE></DIV>
<P>当函数没有返回值时，需声定义成void类型，调用者也要做void声明。</P>
<P>一般我们把函数的声明放在一个统一的文件中，这个文件叫头文件。在程序中用#include命令把头文件包含进来。在程序中调用函数前就不用再进行函数声明了。头文件简化了函数声明的管理并使头文件可被多个程序重复使用。大大提高C程序的开发效率。例如：我们最常使用的printf()函数，在使用前我们不需每次都进行声明操作，直接使用就可以啦。但前提是我们要把stdio.h头文件包含进来。printf()函数声明在stdio.h文件中已进行了声明。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2861811></A>1.10.&nbsp;ANSI标准头文件</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>Linux系统头文件位于/usr/include中。默认情况下编译器只在该目录下搜索头文件。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>assert.h，定义assert宏，可用来检查程序错误。</P>
<LI>
<P>ctype.h，</P>
<LI>
<P>errno.h</P>
<LI>
<P>float.h</P>
<LI>
<P>limits.h</P>
<LI>
<P>locale.h</P>
<LI>
<P>math.h</P>
<LI>
<P>setjmp.h</P>
<LI>
<P>signal.h</P>
<LI>
<P>stdarg.h</P>
<LI>
<P>stddef.h</P>
<LI>
<P>stdio.h</P>
<LI>
<P>stdlib.h</P>
<LI>
<P>string.h</P>
<LI>
<P>time.h</P></LI></UL></DIV></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2808419></A>Chapter&nbsp;2.&nbsp;预处理</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>C语言在程序进行编译之前，会先将程序中以"#"标记的部份进行处理。这种处理叫做预处理。预处理主要的完成以下三个内容：宏处理、头文件和条件式编译。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>宏处理指令语法如下：</P><PRE class=screen>#define 宏名 字符串 

示例：
#define MAX  200           
</PRE>
<DIV class=note style="MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in">
<TABLE summary=Note border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle width=25 rowSpan=2><IMG alt=[Note] src="http://www.ringkee.com/note/opensource/linuxdev/images/note.png"></TD>
<TH align=left></TH></TR>
<TR>
<TD vAlign=top align=left colSpan=2>宏指令语句尾不用加分号(;)</TD></TR></TBODY></TABLE></DIV>
<P>宏定义可以用#undef命令取消，我们可以用该功能进行程序调试。</P>
<LI>
<P>头文件处理是把头文件中的函数声明插入程序中。</P>
<LI>
<P>条件式编译，编译器可根据条件式编译语句有选择地进代码块进行编译。选择式编译指令如下：</P><PRE class=screen>#if 表达式          如果表示式结果不为0,则编译下面的程序
#ifdef 宏名         若宏名已被定义，则编译下面的程序
#ifndef 宏名        若宏名未定义，则编译下面的程序
#else               前面条件不成立时，则编译下面的程序
#endif              结束上列各种条件式编译
</PRE></LI></UL></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2808509></A>Chapter&nbsp;3.&nbsp;使用GCC编译程序</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>直接生成a.out可执行文件</P><PRE class=screen>debian:~/c# gcc hello.c
</PRE>
<P>编译hello.c程序，生成hello可执行文件：</P><PRE class=screen>debian:~/c# gcc -o hello hello.c
</PRE>
<P>生成.s的汇编代码文件。</P><PRE class=screen>debian:~/c# gcc -S hello.c
</PRE></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2808555></A>Chapter&nbsp;4.&nbsp;使用gdb调试程序</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>如果想利用gdb工具来调试程序，在编译程序时要使用-g选项。如：</P><PRE class=screen>debian:~/c# gcc -g serial.c -o serial
</PRE>
<P>调试serial程序。</P><PRE class=screen>debian:~/c# gdb serial
GNU gdb 6.5-debian
Copyright (C) 2006 Free Software Foundation, Inc.
GDB is free software, covered by the GNU General Public License, and you are
welcome to change it and/or distribute copies of it under certain conditions.
Type "show copying" to see the conditions.
There is absolutely no warranty for GDB.  Type "show warranty" for details.
This GDB was configured as "i486-linux-gnu"...Using host libthread_db library "/lib/tls/libthread_db.so.1".

(gdb) list
8       #include     &lt;errno.h&gt;      /*错误号定义*/
9
10      int main(void)
11      {
12              int fd,n,status,buffsize;
13              struct termios a;
14              struct termios *oldtio;
15              char m[255],*comm;
16
17              fd = open("/dev/ttyS0",O_RDWR|O_NOCTTY|O_NDELAY);
(gdb)
</PRE>
<P>gdb的list命令是列出程序源码。下面介绍gdb下的各种操作。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>list，列出程序源代码，一次只列出10行的内容。list命令可以指定范围。如：list 5,10可列出第5行到第10行的内容。</P>
<LI>
<P>run，执行程序。按Ctrl+c可中断程序的执行。</P>
<LI>
<P>shell，暂时退出gdb回到shell环境。在shell环境用exit命令可以返回gdb。</P>
<LI>
<P>break，设置断点，后跟行号则把断点设置在指定的行号，后跟函数名则把断点设置在函数。如break 6，break function。还可根据条件设置断点，如：break 9 if result &gt; 50。这条命令的意思是，当运行到第9行时，如果result变量的值大于50，则中断程序。</P><PRE class=screen>(gdb) break 6
Breakpoint 1 at 0x8048634: file serial.c, line 6.
</PRE>
<LI>
<P>watch，指定条件，如果成立则中断。如：watch result &gt;50。当result的变量大于50时，马上中断程序。</P>
<LI>
<P>print，打印变量值，如：print result。</P>
<LI>
<P>whatis，查看变量类型，如：whatis result。</P>
<LI>
<P>continue，从中断点继续运行程序。</P>
<LI>
<P>step，从中断点开始单步运行，如果遇到函数，则进入函数单步运行。</P>
<LI>
<P>next，从中断点开始单步运行，如果遇到函数，则运行函数，该命令不会进入函数单步运行，而是运行整个函数。</P>
<LI>
<P>info breakpoints，查看程序中所设置的所有中断点信息。</P><PRE class=screen>(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep y   0x08048634 in main at serial.c:6
</PRE>
<P>Enb字段是"y"，表示断点1现正生效。</P>
<LI>
<P>disable/enable，控制中断点失效和启用。如：disable 1。如果disable/enable命令后没有指定断点号，则该命令作用于所有已设置的断点。</P><PRE class=screen>(gdb) disable 1
(gdb) info breakpoints
Num Type           Disp Enb Address    What
1   breakpoint     keep n   0x08048634 in main at serial.c:6
</PRE>
<P>Enb字段由"y"变成"n"，断点1暂时被禁止。</P>
<LI>
<P>enable once，使断点生效一次。</P>
<LI>
<P>delete，删除断点。如：delete 1。delete要指定断点号。</P>
<LI>
<P>clear，删除断点。如：clear 6。clear要指定设置断点的行号或函数名。</P>
<LI>
<P>help all，显示所有gdb环境的命令。</P></LI></UL></DIV>
<DIV class=note style="MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in">
<TABLE summary=Note border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle width=25 rowSpan=2><IMG alt=[Note] src="http://www.ringkee.com/note/opensource/linuxdev/images/note.png"></TD>
<TH align=left></TH></TR>
<TR>
<TD vAlign=top align=left colSpan=2>在gdb环境下，按tab键可自动补全命令。直接按回车键可重复执行上一个操作。按上下光标键可显示历史命令。</TD></TR></TBODY></TABLE></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2808856></A>Chapter&nbsp;5.&nbsp;Linux程序开发基础</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808864">5.1. 路径</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2808991">5.2. 库文件</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809272">5.3. 预处理</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809459">5.4. 系统调用（system call）</A></SPAN></DT></DL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2808864></A>5.1.&nbsp;路径</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>在设置Linux的系统路径时，使用冒号分隔每个路径名。如:</P><PRE class=screen>PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/bin/X11"
</PRE>
<P>在Linux中的程序有两种，一种是可执行程序，与Windows下的.exe文件类似，一种是脚本，与Windows下的.bat文件类似。</P>
<P>Linux中常用的程序存放路径有以下几个：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>/bin，该路径存放系统启动时需要使用的程序。</P>
<LI>
<P>/usr/bin，该路径存放用户需使用的标准程序。</P>
<LI>
<P>/usr/local/bin，该路径存放本地安装的程序。</P>
<LI>
<P>Linux使用斜杠"/"分隔路径名，而不是Windows的反斜杠"\"。</P>
<LI>
<P>Linux下的C编译器使用GCC，由于历史的原因，在POSIX兼容的操作系统中，C编译器都叫cc，所以Linux下也有一个cc命令，它是一个到gcc的软链接。</P></LI></UL></DIV>
<P>开发工具，多数位于/usr/bin或/usr/local/bin目录下。</P>
<P>头文件，位于/usr/include目录。头文件包含有常量定义、系统调用和库函数调用的声明。这是系统默认的头文件存放路径，在编译程序时，编译器会自动查找该目录。gcc编译器在编译程序时也可用-I参数指定另外的头文件路径。如：</P><PRE class=screen>gcc -I/usr/local/myinclude test.c。
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2808991></A>5.2.&nbsp;库文件</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>库文件，库是一组已编译的函数集合，可方便我们重用代码。默认存放在/lib和/usr/lib目录。库文件可分为静态和共享两类。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>.a，静态库文件。使用静态库将会把所有的库代码引入程序，占用更多的磁盘空间和内存空间，所以一般建议使用共享库。</P>
<LI>
<P>.so，共享库文件。使用共享库的程序不包含库代码，只在程序运行才调用共享库中的代码。</P></LI></UL></DIV>
<P>在编译时可用包含路径的库文件名或用-l参数指定使用的库文件，/usr/lib/libm.a等价于-lm。如:</P><PRE class=screen>gcc -o hello hello.c /usr/lib/libm.a  
或用-l参数写成 
gcc -o hello hello.c -lm
</PRE>
<P>如果我们要使用的库文件不在默认位置，在编译程序时可用-L参数指定库文件的路径。下面例子使用了/usr/hello/lib目录下的libhello库文件：</P><PRE class=screen>gcc -o hello -L/usr/hello/lib hello.c -lhello
</PRE>
<P>创建和使用静态库。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>分别创建两个函数，函数a的内容如下：</P><PRE class=screen>#include &lt;stdio.h&gt;

void a(char *arg)
{
        printf("function a,hello world %s\n",arg);
}
</PRE>
<P>函数b的内容如下：</P><PRE class=screen>#include &lt;stdio.h&gt;

void b(int arg)
{
        printf("function b,hello world %d\n",arg);
}
</PRE>
<LI>
<P>接着，生成两个对象文件。</P><PRE class=screen>debian:~/c# gcc -c a.c b.c
debian:~/c# ls *.o
a.o  b.o
</PRE>
<LI>
<P>最后，用ar归档命令把生成的对象文件打包成一个静态库libhello.a。</P><PRE class=screen>debian:~/c# ar crv libhello.a a.o b.o 
r - a.o
r - b.o
</PRE>
<LI>
<P>为我们的静态库定义一个头文件lib.h，包含这两个函数的定义。</P><PRE class=screen>/*
 * this is a header file.
 */
void a(char *arg);
void b(int arg);
}}}
  * 创建jims.c程序，内容如下。{{{#!cplusplus
#include "lib.h"

int main()
{
        a("jims.yang");
        b(3);
        exit(0);
}
</PRE>
<LI>
<P>利用静态链接库编译程序。</P><PRE class=screen>debian:~/c# gcc -c jims.c
debian:~/c# gcc -o jims jims.o libhello.a
debian:~/c# ./jims
function a,hello world jims.yang
function b,hello world 3
debian:~/c#                                       
</PRE>
<DIV class=note style="MARGIN-LEFT: 0.5in; MARGIN-RIGHT: 0.5in">
<TABLE summary=Note border=0>
<TBODY>
<TR>
<TD vAlign=top align=middle width=25 rowSpan=2><IMG alt=[Note] src="http://www.ringkee.com/note/opensource/linuxdev/images/note.png"></TD>
<TH align=left></TH></TR>
<TR>
<TD vAlign=top align=left colSpan=2>gcc -o jims jims.o libhello.a也可以写成gcc -o jims jims.o -L. -lhello。</TD></TR></TBODY></TABLE></DIV></LI></UL></DIV>
<P>共享库比静态库具有以下的优点：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>当多个进程使用同一共享库时，Linux会把共享库中存放可执行代码的内存进行共享。所以共享库可节省内存，提高系统性能。</P>
<LI>
<P>程序可共享代码，减少磁盘空间占用。</P>
<LI>
<P>共享库出错，只要重新编译共享库即可，不用重新编译应用程序。</P></LI></UL></DIV>
<P>ldconfig程序用来安装一个共享库，。</P>
<P>只有在为系统库安装一个库的时候，才需要在/etc/ld.so.conf中创建记录，并运行ldconfig更新共享库的缓存。</P>
<P>LD_LIBRARY_PATH环境变量用来指定附加的库文件路径。系统默认的库文件路径位于/usr/lib和/lib目录下。</P>
<P>LD_PRELOAD环境变量指定提前载入的库，用于替代系统库。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2809272></A>5.3.&nbsp;预处理</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>预处理，在程序开头以“#”开头的命令就是预处理命令，它在语法扫描和分析法时被预处理程序处理。预处理有以下几类：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>宏定义，用#define指令定义。如：#define BUFFER 1024。取消宏定义用#undef指令。宏还可带参数，如：</P><PRE class=screen>#define BUF(x) x*3
</PRE>
<LI>
<P>包含头文件，用#include指令，可把包含的文件代码插入当前位置。如：</P><PRE class=screen>&lt;#include &lt;stdio.h&gt;。
</PRE>
<P>包含的文件可以用尖括号，也可用双引号，如：</P><PRE class=screen>#include "stdio.h"。
</PRE>
<P>不同之处是，使用尖括号表示在系统的包含目录（/usr/include）下查找该文件，而双引号表示在当前目录下查找包含文件。每行只能包含一个包含文件，要包含多个文件要用多个#include指令。</P>
<LI>
<P>条件编译，格式如下：</P><PRE class=screen>格式一，如果定义了标识符，则编译程序段1，否则编译程序段2：
#ifdef 标识符
程序段1
#else
程序段2
#endif

格式二，如果定义了标识符，则编译程序段2，否则编译程序段1，与格式一相反：
#ifndef 标识符
程序段1
#else
程序段2
#endif 

格式三，常量表达式为真则编译程序段1，否则编译程序段2：
#if 常量表达式
程序段1
#else
程序段2
#endif 
</PRE></LI></UL></DIV>
<P>使用gcc编译程序时，要经过四个步骤。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>预处理（Pre-Processing），用-E参数可以生成预处理后的文件。</P><PRE class=screen>debian:~/c# gcc -E hello.c -o hello.i
</PRE>
<LI>
<P>编译（Compiling）</P>
<LI>
<P>汇编（Assembling）</P>
<LI>
<P>链接（Linking）</P></LI></UL></DIV>
<P>GCC默认将.i文件看成是预处理后的C语言源代码，所以我们可以这样把.i文件编译成目标文件。</P><PRE class=screen>debian:~# gcc -c hello.i -o hello.o}}}
</PRE>
<P>在GCC中使用-pedantic选项能够帮助程序员发现一些不符合ANSI/ISO C标准的代码，但不是全部。从程序员的角度看，函数库实际上就是一些头文件（.h）和库文件（.so或者.a）的集合。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2809459></A>5.4.&nbsp;系统调用（system call）</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>要理解系统调用就要先理解程序代码运行的两种模式，一种是用户模式，一种是内核模式。我们编写的应用程序运行在用户模式下，而设备驱动程序和文件系统运行在内核模式。在用户模式下运行的程序受到严格的管理，不会破坏系统级应用。而在内核模式下运行的程序可以对电脑有完全的访问权。系统调用就是运行在内核模式下的代码为运行在用户模式下的代码提供服务。</P>
<P>系统调用的错误返回码是负数，定义在&gt;errno.h&lt;文件中。在系统调用中发生错误，C函数库就会用错误码填充全局变量errno。用perror()和strerror()函数可以输出错误信息。</P>
<P>系统调用多数在&gt;unistd.h&lt;中定义。</P></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2809510></A>Chapter&nbsp;6.&nbsp;文件处理</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>在Linux系统内所有东西都是以文件的形式来表示的，除一般的磁盘文件外，还有设备文件，如硬盘、声卡、串口、打印机等。设备文件又可分为字符设备文件（character devices）和块设备文件（block devices）。使用man hier命令可以查看Linux文件系统的分层结构。文件的处理方法一般有五种，分别是：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>open，打开一个文件或设备。</P>
<LI>
<P>close，关闭一个打开的文件或设备。</P>
<LI>
<P>read，从一个打开的文件或者设备中读取信息。</P>
<LI>
<P>write，写入一个文件或设备。</P>
<LI>
<P>ioctl，把控制信息传递给设备驱动程序。</P></LI></UL></DIV>
<P>open，close，read，write和ioctl都是低级的，没有缓冲的文件操作函数，在实际程序开发中较少使用，一般我们使用标准I/O函数库来处理文件操作。如：fopen，fclose，fread，fwrite，fflush等。在使用标准I/O库时，需用到stdio.h头文件。</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>fopen()这个标准I/O库函数用于打开文件，在Linux中文件要先打开后才能进行读写操作。</P><PRE class=screen>#include &lt;stdio.h&gt;
FILE *fopen(const char *filename, const char *mode);

*mode选项：
"r" 或o"rb"  为读打开文件
"w" 或 "wb" b为写打开文件，如果文件不存在则创建，如果存在则覆盖
"a" 或 "ab" b为追加内容而打开文件
"r+" 或o"rb+" 或 "r+b"r 为更新打开文件，不会覆盖旧文件
"w+" 或 "wb+"b或 "w+b"w 为更新打开文件，会覆盖旧文件
"a+"a或 "ab+" 或 "a+b"  为更新打开文件，更新内容追加到文件末尾
</PRE>
<P></P></LI></UL></DIV>
<P>一些常用的文件和目录维护函数：chmod、chown、unlink、link、symlink、mkdir、rmdir、chdir、getcwd、opendir，closedir、readdir、telldir、seekdir等。</P>
<P>fcntl用于维护文件描述符，mmap用于分享内存。</P>
<P>创建文档并输入信息的示例代码：</P><PRE class=screen>#include &lt;stdio.h&gt;

main(void)
        {
        FILE *fp1;
        char c;

        fp1 = fopen("text.txt","w");
        while ((c = getchar())!= '\n')
                putc(c,fp1);
        fclose(fp1);
        }
</PRE>
<P>显示路径的示例代码</P><PRE class=screen>#include &lt;unistd.h&gt;
#include &lt;stdio.h&gt;
#include &lt;dirent.h&gt;
#include &lt;string.h&gt;
#include &lt;sys/stat.h&gt;
#include &lt;stdlib.h&gt;

int main(int argc, char *argv[])
{
        char *topdir = ".";
        if (argc &gt;= 2)
                topdir = argv[1];

        printf("Directory scan of %s\n", topdir);
        printdir(topdir,0);
        printf("done.\n");

        exit(0);
}

printdir(char *dir, int depth)
{
        DIR *dp;
        struct dirent *entry;
        struct stat statbuf;

        if((dp = opendir(dir)) == NULL)
        {
                fprintf(stderr,"cannot open directory:%s\n",dir);
                return;
        }
        chdir(dir);
        while((entry = readdir(dp)) != NULL)
        {
                lstat(entry-&gt;d_name,&amp;statbuf);
                if(S_ISDIR(statbuf.st_mode))
                {
                        if(strcmp(".",entry-&gt;d_name) == 0 || strcmp("..",entry-&gt;d_name) == 0)
                                continue;
                        printf("%*s%s/\n",depth,"",entry-&gt;d_name);
                        printdir(entry-&gt;d_name,depth+4);
                }
                else printf("%*s%s\n",depth,"",entry-&gt;d_name);
        }
        chdir("..");
        closedir(dp);
}
</PRE></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2809712></A>Chapter&nbsp;7.&nbsp;Linux环境编程</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2809720">7.1. 参数选项</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875409">7.2. 环境变量</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875469">7.3. 时间</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875567">7.4. 临时文件</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875601">7.5. 用户信息</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875678">7.6. 日志信息</A></SPAN></DT></DL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2809720></A>7.1.&nbsp;参数选项</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>void main()表示程序没有参数，int main(int argc, char *argv[])表示程序要带参数，argc保存着参数的个数，argv[]数组保存着参数列表。如：</P><PRE class=screen>debian:~# mytest a b c 
argc: 4
argv: ["mytest","a","b","c"]
</PRE>
<P>getopt()函数和getopt_long()用来处理程序选项。getopt_long()函数可以处理以"--"开头的选项。Gnu官方手册页：<A href="http://www.gnu.org/software/libc/manual/html_node/Getopt.html" target=_top>http://www.gnu.org/software/libc/manual/html_node/Getopt.html</A></P>
<P>获取命令行参数的示例代码：</P><PRE class=screen>#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main(int argc, char *argv[])
{
   int opt;

   while((opt = getopt(argc,argv,"if:lr")) != -1)       /* 返回“-1”表示已没选项需要处理。*/
   {
           switch(opt){
           case 'i':
           case 'l':
           case 'r':
                   printf("option: %c\n", opt);
                   break;
           case 'f':
                   printf("filename: %s\n", optarg);           /*如果选项需要一个参数，则参数存放在外部变量optarg中。*/
                   break;
           case ':':
                   printf("option needs a value \n");          /*“：”表示选项需要参数*/
                   break;
           case '?':
                   printf("unknown option: %c\n", optopt);    /*返回“?”表示无效的选项，并把无效的选项存放在外部变量optopt中。*/
                   break;
           }
   }
   for(; optind &lt; argc; optind++)                      /*外部变量optind指向下一个要处理的选项索引值。*/
           printf("argument: %s\n", argv[optind]);
}
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875409></A>7.2.&nbsp;环境变量</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>在bash shell中使用set命令可以列出Linux系统的环境变量，在C程序中我们也可以用putenv()和getenv()函数来获取Linux系统的环境变量。这两个函数的声明如下：</P><PRE class=screen>char *getenv(const char *name);
int putenv(const char *string);
</PRE>
<P>系统有一个environ变量记录了所有的系统变量。下面的示例代码可把environ的值显示同来。</P><PRE class=screen>#include &lt;stdlib.h&gt;
#include &lt;stdio.h&gt;

extern char **environ;

int main()
{
        char **env = environ;

        while(*env)
        {
                printf("%s\n",*env);
                env++;
        }
}
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875469></A>7.3.&nbsp;时间</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>linux和其它unix一样，使用GMT1970年1月1日子夜作为系统时间的开始，也叫UNIX纪元的开始。现在的时间表示为UNIX纪元至今经过的秒数。</P><PRE class=screen>#include &lt;time.h&gt;
 time_t time(time_t *t);

显示系统时间的示例代码：
#include &lt;time.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main()
{
        int i;
        time_t the_time;

        for(i = 1; i &lt;= 10; i++){
                the_time = time((time_t *)0);
                printf("%d  the time is %ld\n", i, the_time);
                sleep(2);
        }
}
</PRE>
<P>用ctime()函数以友好方式返回当前时间，它的函数声明格式：</P><PRE class=screen>#include &lt;time.h&gt;
char *ctime(const time_t *timeval);

示例：
#include &lt;time.h&gt;
#include &lt;stdio.h&gt;

int main(void)
{
        time_t time1;

        (void)time(&amp;time1);
        printf("The date is: %s\n",ctime(&amp;time1));
}

程序输出：
The date is: Thu Dec  7 09:58:23 2006
</PRE>
<P>用localtime()函数可以返回本地时间，它是一个tm结构，tm结构体的内容如下：</P><PRE class=screen> struct tm
{
int tm_sec;
int tm_min;
int tm_hour;
int tm_mday;
int tm_mon;
int tm_year;
int tm_wday;
int tm_yday;
int tm_isdst;
};

int tm_sec    Seconds, 0-61
int tm_min    Minutes, 0-59
int tm_hour   Hours, 0-23
int tm_mday   Day in the month, 1-31
int tm_mon    Month in the year, 0-11(January= 0)
int tm_year   Years since 1900
int tm_wday   Day in the week, 0-6. (Sunday = 0)
int tm_yday   Day in the year, 0-365
int tm_isdst  Daylight savings in effect
</PRE>
<P>localtime()函数的使用方法如下：</P><PRE class=screen>函数声明：
#include &lt;time.h&gt;
struct tm *localtime(const time_t *timeval);

示例代码：
#include &lt;time.h&gt;
#include &lt;stdio.h&gt;

int main(void)
{
        time_t time1;
        struct tm *p;

        time1 = time(NULL);
        printf("The ctime is: %s\n",ctime(&amp;time1));

        p = localtime(&amp;time1);
        printf("The localtime is:\n tm_year+1900 = %d年\n tm_mon = %d月\n tm_mday = %d日\n wday = %d\n hour = %d 时\n min = %d分\n sec = %d秒\n",p-&gt;tm_year+1900,p-&gt;tm_mon,p-&gt;tm_mday,p-&gt;tm_wday,p-&gt;tm_hour,p-&gt;tm_min,p-&gt;tm_sec);
}
运行结果：
The ctime is: Thu Dec  7 10:31:36 2006

The localtime is:
 tm_year+1900 = 2006年
 tm_mon = 11月
 tm_mday = 7日
 wday = 4
 hour = 10 时
 min = 31分
 sec = 36秒
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875567></A>7.4.&nbsp;临时文件</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>用mkstemp()函数创建临时文件。</P><PRE class=screen>#include&lt;stdlib.h&gt;
int mkstemp(char * template);

示例：
#include &lt;stdio.h&gt;

int main(void)
{
        char template[] = "template-XXXXXX";
        int fp;
        fp = mkstemp(template);
        printf("template = %s\n", template);
        close(fp);
}
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875601></A>7.5.&nbsp;用户信息</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>获取用户信息。</P><PRE class=screen>声明：
#include &lt;sys/types.h&gt;
#include &lt;pwd.h&gt;
struct passwd *getpwuid(uid_t uid);   /* 根据uid返回用户信息 */
struct passwd *getpwnam(const char *name);   /* 根据用户名返回用户信息 */

passwd结构体说明：
passwd Member Description
char *pw_name          The user's login name
uid_t pw_uid             The UID number
gid_t pw_gid             The GID number
char *pw_dir            The user's home directory
char *pw_gecos        The user's full name
char *pw_shell         The user's default shell

示例代码：
#include &lt;stdio.h&gt;
#include &lt;sys/types.h&gt;
#include &lt;stdio.h&gt;
#include &lt;pwd.h&gt;

int main(void)
{
        uid_t uid;
        gid_t gid;
        struct passwd *pw;

        uid = getuid();
        gid = getgid();
        pw = getpwuid(uid);

        printf("User is %s\n", getlogin());
        printf("The uid is:%d\n", uid);
        printf("The gid is:%d\n",gid);
        printf("The pw struct:\n name=%s, uid=%d, gid=%d, home=%s,shell=%s\n", pw-&gt;pw_name, pw-&gt;pw_uid, pw-&gt;pw_gid, pw-&gt;pw_dir, pw-&gt;pw_shell);
}
</PRE>
<P>用gethostname()函数获取主机名。</P><PRE class=screen>函数声明：
#include &lt;unistd.h&gt;
int gethostname(char *name, size_t namelen);    /* 主机名返回给name变量 */

示例代码：
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main(void)
{
        char computer[100];
        int status;

        status = gethostname(computer, 100);
        printf("The status is %d\n", status);
        printf("The hostname is: %s\n", computer);
}
</PRE>
<P>用uname()函数获取主机详细信息，就像shell的uname命令返回的信息一样。</P><PRE class=screen>函数声明：
#include &lt;sys/utsname.h&gt;
int uname(struct utsname *name);

utsname结构体说明：
utsname Member                  Description
char sysname[]                  The operating system name
char nodename[]                 The host name
char release[]                  The release level of the system
char version[]                  The version number of the system
char machine[]                  The hardware type

示例代码：
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;
#include &lt;sys/utsname.h&gt;

int main(void)
{
        char computer[100];
        int status;
        struct utsname uts;

        status = gethostname(computer,100);
        printf("The computer's size is %d\n",sizeof(computer));
        printf("The status is %d\n", status);
        printf("The hostname is: %s\n", computer);

        uname(&amp;uts);
        printf("The uname's information.\n uts.sysname=%s\n uts.machine=%s\n uts.nodename=%s\n uts.release=%s\n uts.version=%s\n", uts.sysname,uts.machine,uts.nodename,uts.release,uts.version);
}
</PRE></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875678></A>7.6.&nbsp;日志信息</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>使用syslog()函数处理日志信息。</P><PRE class=screen>函数声明：
#include &lt;syslog.h&gt;
void syslog(int priority, const char *message, arguments...);

priority参数的格式（severity level|facility code）
示例： 
LOG_ERR|LOG_USER

severity level：
Priority Level               Description
LOG_EMERG                    An emergency situation
LOG_ALERT                    High-priority problem, such as database corruption
LOG_CRIT                     Critical error, such as hardware failure
LOG_ERR                      Errors
LOG_WARNING                  Warning
LOG_NOTICE                   Special conditions requiring attention
LOG_INFO                     Informational messages
LOG_DEBUG                    Debug messages

facility value（转自syslog.h头文件）：
/* facility codes */
#define LOG_KERN        (0&lt;&lt;3)  /* kernel messages */
#define LOG_USER        (1&lt;&lt;3)  /* random user-level messages */
#define LOG_MAIL        (2&lt;&lt;3)  /* mail system */
#define LOG_DAEMON      (3&lt;&lt;3)  /* system daemons */
#define LOG_AUTH        (4&lt;&lt;3)  /* security/authorization messages */
#define LOG_SYSLOG      (5&lt;&lt;3)  /* messages generated internally by syslogd */
#define LOG_LPR         (6&lt;&lt;3)  /* line printer subsystem */
#define LOG_NEWS        (7&lt;&lt;3)  /* network news subsystem */
#define LOG_UUCP        (8&lt;&lt;3)  /* UUCP subsystem */
#define LOG_CRON        (9&lt;&lt;3)  /* clock daemon */
#define LOG_AUTHPRIV    (10&lt;&lt;3) /* security/authorization messages (private) */
#define LOG_FTP         (11&lt;&lt;3) /* ftp daemon */

示例代码：
#include &lt;syslog.h&gt;
#include &lt;stdio.h&gt;

int main(void)
{
        FILE *f;

        f = fopen("abc","r");
        if(!f)                                    
                syslog(LOG_ERR|LOG_USER,"test - %m\n");       
}
</PRE>
<P>上面的日志信息由系统自动给出，我们也可过滤日志信息。用到以下函数：</P><PRE class=screen>#include &lt;syslog.h&gt;
void closelog(void);
void openlog(const char *ident, int logopt, int facility);
int setlogmask(int maskpri);

logopt参数的选项：
logopt Parameter    Description
LOG_PID             Includes the process identifier, a unique number allocated to each process by the system, in the messages.
LOG_CONS            Sends messages to the console if they can’t be logged.
LOG_ODELAY          Opens the log facility at first call to .
LOG_NDELAY          Opens the log facility immediately, rather than at first log.

示例代码：
#include &lt;syslog.h&gt;
#include &lt;stdio.h&gt;
#include &lt;unistd.h&gt;

int main(void)
{
        int logmask;

        openlog("logmask", LOG_PID|LOG_CONS, LOG_USER); /*日志信息会包含进程id。*/
        syslog(LOG_INFO, "informative message, pid=%d", getpid()); 
        syslog(LOG_DEBUG,"debug message, should appear");   /*记录该日志信息。*/
        logmask = setlogmask(LOG_UPTO(LOG_NOTICE));     /*设置屏蔽低于NOTICE级别的日志信息。*/
        syslog(LOG_DEBUG, "debug message, should not appear");  /*该日志信息被屏蔽，不记录。*/
}
</PRE>
<P>不同安全级别的日志信息存放在/var/log目录下的哪个文件中是由/etc/syslog.conf文件控制的，下面是我系统中syslog.conf文件的内容：</P><PRE class=screen>#  /etc/syslog.conf     Configuration file for syslogd.
#
#                       For more information see syslog.conf(5)
#                       manpage.

#
# First some standard logfiles.  Log by facility.
#

auth,authpriv.*                 /var/log/auth.log
*.*;auth,authpriv.none          -/var/log/syslog
#cron.*                         /var/log/cron.log
daemon.*                        -/var/log/daemon.log
kern.*                          -/var/log/kern.log
lpr.*                           -/var/log/lpr.log
mail.*                          -/var/log/mail.log
user.*                          -/var/log/user.log
uucp.*                          /var/log/uucp.log

#
# Logging for the mail system.  Split it up so that
# it is easy to write scripts to parse these files.
#
mail.info                       -/var/log/mail.info
mail.warn                       -/var/log/mail.warn
mail.err                        /var/log/mail.err

# Logging for INN news system
#
news.crit                       /var/log/news/news.crit
news.err                        /var/log/news/news.err
news.notice                     -/var/log/news/news.notice

#
# Some `catch-all' logfiles.
#
*.=debug;\
        auth,authpriv.none;\
        news.none;mail.none     -/var/log/debug
*.=info;*.=notice;*.=warn;\
        auth,authpriv.none;\
        cron,daemon.none;\
        mail,news.none          -/var/log/messages

#
# Emergencies are sent to everybody logged in.
#
*.emerg                         *

#
# I like to have messages displayed on the console, but only on a virtual
# console I usually leave idle.
#
#daemon,mail.*;\
#       news.=crit;news.=err;news.=notice;\
#       *.=debug;*.=info;\
#       *.=notice;*.=warn       /dev/tty8

# The named pipe /dev/xconsole is for the `xconsole' utility.  To use it,
# you must invoke `xconsole' with the `-file' option:
#
#    $ xconsole -file /dev/xconsole [...]
#
# NOTE: adjust the list below, or you'll go crazy if you have a reasonably
#      busy site..
#
daemon.*;mail.*;\
        news.crit;news.err;news.notice;\
        *.=debug;*.=info;\
        *.=notice;*.=warn       |/dev/xconsole
</PRE></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2875738></A>Chapter&nbsp;8.&nbsp;进程</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875927">8.1. 进程状态</A></SPAN></DT></DL></DIV>
<P>进程是任何正在运行的程序代码，它是操作系统的基本调度单位，只有它能在CPU上运行。对于一个进程，内核记录以下信息：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>进程运行的当前位置。</P>
<LI>
<P>进程正在访问的文件。</P>
<LI>
<P>进程的所属的用户和组。</P>
<LI>
<P>进程的当前目录。</P>
<LI>
<P>进程访问的内存空间状况。</P></LI></UL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875927></A>8.1.&nbsp;进程状态</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>pid是进程的标识符，存放在pid_t结构的变量中。在一个进程中创建另一个进程时，这个新进程就是子进程，原来的进程就是父进程。子进程结束时会通知父进程。如果父进程结束而子进程没有结束，则子进程会成为孤儿进程。所有孤儿进程都会变成init进程的子进程。init进程是系统启动的第一个进程，它其中一个主要功能就是收集孤儿进程，以便内核将子进程从进程表中删除。通过getpid()和getppid()函数可以获得进程的pid。</P></DIV></DIV>
<DIV class=chapter lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title><A name=id2875951></A>Chapter&nbsp;9.&nbsp;串口编程</H2></DIV></DIV>
<DIV></DIV></DIV>
<DIV class=toc>
<P><B>Table of Contents</B></P>
<DL>
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2875959">9.1. 常用函数</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876100">9.2. 设置串口属性</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876276">9.3. c_iflag输入标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876510">9.4. c_oflag输出标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876628">9.5. c_cflag控制模式标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876752">9.6. c_cc[]控制字符说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2876976">9.7. c_lflag本地模式标志说明</A></SPAN> 
<DT><SPAN class=sect1><A href="http://www.ringkee.com/note/opensource/linuxdev/linuxdev.htm#id2877178">9.8. 下面介绍一些常用串口属性的设置方法。</A></SPAN></DT></DL></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2875959></A>9.1.&nbsp;常用函数</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>使用open()函数打开串口，open()函数有两个参数，第一个是要打开的设备名（如：/dev/ttyS0）。第二个是打开的方式。打开方式有以下三种：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>O_RDWR，表示以读写方式打开串口。</P>
<LI>
<P>O_NOCTTY，表示不成为端口的控制终端，如果没有这个选项，则任何输入（键盘按键）都会中断程序的执行。</P>
<LI>
<P>O_NDELAY，表示程序不会关注DCD信号线所处的状态，即不管对端设备是运行或挂起。如果没有该选项，则程序会被设置成睡眠状态，直到DCD信号为低为止。</P></LI></UL></DIV>
<P>成功打开串口则会返回文件描述符，打开失败则返回-1。下面是一个打开串口的示例：</P><PRE class=screen>fd = open("/dev/ttyS0",O_RDWR|O_NDELAY|O_NDELAY);
</PRE>
<P>使用close()关闭打开的串口，唯一的参数是打开串口的文件描述符。下面是一个关闭串口的示例：</P><PRE class=screen>close(fd);        //fd是打开串口返回的文件描述符
</PRE>
<P>用write()函数向串口写数据。下面是一个向串口写数据的示例：</P><PRE class=screen>n = write(fd,buff,len);    
/* n表示成功写到串口的字节数，如果写入失败则返回-1
   fd是打开串口返回的文件描述符
   buff表示写入的内容
   len表示写入信息的长度。
*/
</PRE>
<P>用read()函数从串口读取数据。下面是一个从串口读数据的示例：</P><PRE class=screen>n = read(fd,buff,len);
/* n表示从串口读到字节数
   fd是文件描述符
   buff是读入字节存放的缓冲区
   len表示读入的字节数
*/
</PRE>
<P>通过fcntl()函数可以操作文件描述符，用以控制读取数据的状态。fcntl(fd,F_SETFL,0)表示没有数据则阻塞，处于等待状态，直到有数据到来；fcntl(fd,F_SETFL,FNDELAY)表示当端口没有数据时马上返回0。</P></DIV>
<DIV class=sect1 lang=en>
<DIV class=titlepage>
<DIV>
<DIV>
<H2 class=title style="CLEAR: both"><A name=id2876100></A>9.2.&nbsp;设置串口属性</H2></DIV></DIV>
<DIV></DIV></DIV>
<P>所有的串口属性都在一个名为termios的结构体中，要使用该结构体要包含termios.h头文件。在该头文件中还定义两个重要的函数tcgetattr()和tcsetattr()，分别用以获取和设置串口的属性。如：tcgetattr(fd,&amp;old_termios)，tcsetattr(fd,TCSANOW,&amp;new_termios)。old_termios是旧的串口属性，new_termios是重新设置的新串口属性。tcsetattr()函数中常量的意义是：</P>
<DIV class=itemizedlist>
<UL type=disc>
<LI>
<P>TCSANOW表示新设置的串口属性马上生效。</P>
<LI>
<P>TCSADRAIN表示等所有数据传送完成后才生效。</P>
<LI>
<P>TCSAFLUSH表示马上清空输入和输出缓存，然后应用新的串口设置。</P></LI></UL></DIV>
<P>termios结构体内容：</P><PRE class=screen>成员            描述
-------------------------------]]></description>
</item><item>
<title><![CDATA[ARM基础知识二]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6914</link>
<author>wangxg97</author>
<pubDate>2007-3-18 11:41:00</pubDate>
<description><![CDATA[<P align=center><STRONG>ARM基础知识二</STRONG></P>
<P>***************************************************************</P>
<P>程序状态寄存器</P>
<P>***************************************************************<BR>　　CPSR(当前程序状态寄存器)在任何处理器模式下被访问。它包含了条件标志位、中断禁止位、当前处理器模式标志以及其他的一些控制和状态位。每一种处理器</P>
<P>　　模式下都有一个专用的物理状态寄存器，称为SPSR（备份程序状态寄存器）。当特定的异常中断发生时，这个寄存器用于存放当前程序状态寄存器的内容。在异常中断退出时，可以用SPSR来恢复CPSR。由于用户模式和系统模式不是异常</P>
<P>　　中断模式，所以他没有SPSR。当用户在用户模式或系统模式访问SPSR，将产生不可预知的后果。<BR><BR>　　CPSR格式如下所示。SPSR和CPSR格式相同。</P>
<P>&nbsp;31&nbsp; 30&nbsp; 29&nbsp; 28&nbsp; 27&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 26&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 7&nbsp; 6&nbsp; 5&nbsp; 4&nbsp; 3&nbsp; 2&nbsp; 1&nbsp; 0<BR>&nbsp;N&nbsp;&nbsp; Z&nbsp;&nbsp; C&nbsp;&nbsp; V&nbsp;&nbsp; Q&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; DNM(RAZ)&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; I&nbsp; F&nbsp; T&nbsp; M4 M3 M2 M1 M0</P>
<P><BR>　　***条件标志位***<BR><BR>　　N——本位设置成当前指令运算结果的bit[31]的值。当两个表示的有符号整数运算时，n=1表示运算结果为负数，n=0表示结果为正书或零。</P>
<P>　　z——z=1表示运算的结果为零；z=0表示运算的结果不为零。对于CMP指令，Z=1表示进行比较的两个数大小相等。</P>
<P>　　C——下面分四种情况讨论C的设置方法：<BR><BR>　　&nbsp;在加法指令中（包括比较指令CMP），当结果产生了进位,则C=1,表示无符号运算发生上溢出；其他情况C=0。<BR>　　&nbsp;在减法指令中（包括减法指令CMP），当运算中发生错位，则C=0，表示无符号运算数发生下溢出；其他情况下C=1。<BR>　　&nbsp;对于包含移位操作的非加碱运算指令，C中包含最后一次溢出的的位的数值<BR>　　&nbsp;对于其他非加减运算指令，C位的值通常不受影响<BR><BR>　　V——对于加减运算指令，当操作数和运算结果为二进制的补码表示的带符号数时，V=1表示符号为溢出；通常其他指令不影响V位。</P>
<P>　　***Q标识位***<BR><BR>　　在ARM V5的E系列处理器中，CPSR的bit[27]称为q标识位，主要用于指示增强的dsp指令是否发生了溢出。同样的spsr的bit[27]位也称为q标识位，用于在异常中</P>
<P>　　断发生时保存和恢复CPSR中的Q标识位。<BR><BR>　　在ARM V5以前的版本及ARM V5的非E系列的处理器中，Q标识位没有被定义。<BR><BR>　　***CPSR中的控制位***</P>
<P>　　CPSR的低八位I、F、T、M[4:0]统称为控制位。当异常中断发生时这些位发生变化。在特权级的处理器模式下，软件可以修改这些控制位。</P>
<P>　　**中断禁止位：当I=1时禁止IRQ中断，当F=1时禁止FIQ中断</P>
<P>　　**T控制位：T控制位用于控制指令执行的状态，即说明本指令是ARM指令还是Thumb指令。对于ARM V4以更高版本的T系列ARM处理器，T控制位含义如下：<BR><BR>T=0表示执行ARM指令<BR>T=1表示执行Thumb指令<BR><BR>　　对于ARM V5以及更高版本的非T系列处理器，T控制位的含义如下<BR>&nbsp;<BR>T=0表示执行ARM指令<BR>T=1表示强制下一条执行的指令产生未定指令中断</P>
<P>　　***M控制位***<BR>　　M控制位控制处理器模式，具体含义如下：</P>
<P>M[4:0]&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;处理器模式&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; 可访问的寄存器<BR>ob10000&nbsp;&nbsp;user&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; pc,r14~r0,CPSR<BR>0b10001&nbsp;&nbsp;FIQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; PC,R14_FIQ-R8_FIQ,R7~R0,CPSR,SPSR_FIQ<BR>0b10010&nbsp;&nbsp;IRQ&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;PC,R14_IRQ-R13_IRQ,R12~R0,CPSR,SPSR_IRQ<BR>0B10011&nbsp;&nbsp;SUPERVISOR&nbsp;&nbsp; &nbsp; PC,R14_SVC-R13_SVC,R12~R0,CPSR,SPSR_SVC<BR>0b10111&nbsp;&nbsp;ABORT&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;PC,R14_ABT-R13_ABT,R12~R0,CPSR,SPSR_ABT<BR>0b11011&nbsp;&nbsp;UNDEFINEED&nbsp;&nbsp;&nbsp; &nbsp;PC,R14_UND-R8_UND,R12~R0,CPSR,SPSR_UND<BR>0b11111&nbsp;&nbsp;SYSTEM&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;PC,R14-R0,CPSR(ARM V4以及更高版本）</P>
<P>　　***CPSR中的其他位***</P>
<P>　　这些位用于将来扩展。应用软件不要操作这些位。<BR></P></STRONG></FONT>]]></description>
</item><item>
<title><![CDATA[ARM基础知识一]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6913</link>
<author>wangxg97</author>
<pubDate>2007-3-18 11:36:00</pubDate>
<description><![CDATA[<P align=center><STRONG>ARM基础知识一</STRONG></P>
<P align=left>ARM处理器共有37个寄存器。其中包括：</P>
<P>　　**31个通用寄存器，包括程序计数器（PC）在内。这些寄存器都是32位寄存器。</P>
<P>　　**6个状态寄存器。这些寄存器都是32位寄存器。</P>
<P>　　ARM处理器共有7种不同的处理器模式，每一种模式中都有一组相应的寄存器组。在任何时刻，可见的寄存器包括15个通用寄存器（R0-R14）,一个或两个状态寄存器及程序计数器（PC）。在所有的寄存器中，有些是各模式公用一个物理寄存器，有一些寄存器各模式拥有自己独立的物理寄存器。</P>
<P>****************************************************</P>
<P>通用寄存器</P>
<P>***************************************************8</P>
<P>　　通用寄存器分为以下三类：备份寄存器、未备份寄存器、程序计数器PC</P>
<P>　　未备份寄存器</P>
<P>　　未备份寄存器包括R0-R7。对于每一个未备份寄存器来说，所有处理器模式下都是使用同一个物理寄存器。未备份寄存器没有被系统用于特别的用途，任何可采用通用寄存器的场合都可以使用未备份寄存器。</P>
<P>　　备份寄存器</P>
<P>　　对于R8-R12备份寄存器来说，每个寄存器对应两个不同的物理寄存器。系统为将备份寄存器用于任何的特殊用途，但是当中断处理非常简单，仅仅使用R8-R14寄存器时，FIQ处理程序可以不必执行保存和恢复中断现场的指令，从而可以使中断处理非常迅速。</P>
<P>　　对于R13,R14备份寄存器来说，每个寄存器对应六个不同的物理寄存器，其中的一个是系统模式和用户模式共用的；另外的五个对应于其他的五种处理器模式。采用下面的记号来区分各个物理寄存器：</P>
<P>　　R13_&lt;MODE&gt;</P>
<P>　　其中MODE可以是下面几种模式之一：usr,svc,abt,und,irq,fiq</P>
<P>　　程序计数器PC</P>
<P>　　可以作为一般的通用寄存器使用，但有一些指令在使用R15时有一些限制。由于ARM采用了流水线处理器机制，当正确读取了PC的值时，该值为当前指令地址值加上8个字节。也就是说，对于ARM指令集来说，PC指向当前指令的下两条指令的地址。由于ARM指令是字对齐的，PC值的第0位和第一位总为0。</P>
<P>　　需要注意的是，当使用str/stm保存R15时，保存的可能是当前指令地址值加8个字节，也可能保存的是当前指令地址值加12个字节。到底哪种方式取决于芯片的具体设计。对于用户来说，尽量避免使用STR/STM指令来保存R15的值。<BR><BR>　　当成功的向R15写入一个数值时，程序将跳转到该地址执行。由于ARM指令是字对齐的，写入R15的值应满足bits[1:0]为0b00，具体要求arm个版本有所不同：<BR><BR>　　**对于arm3以及更低的版本，写入R15的地址值bits[1:0]被忽略，即写入r15的地址值将与0xFFFF FFFC做与操作。<BR><BR>　　**对于ARM4以及更高的版本，程序必须保证写入R15的地址值bits[1:0]为0b00，否则将产生不可预知的后果。<BR><BR>　　对于Thumb指令集来说，指令是班子对齐的，处理器将忽略bit[0]。</P>
<P>&nbsp;</P>]]></description>
</item><item>
<title><![CDATA[ARM入门]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6912</link>
<author>wangxg97</author>
<pubDate>2007-3-18 11:21:00</pubDate>
<description><![CDATA[<P align=center><STRONG>ARM入门</STRONG></P>
<P align=left>1 ARM 的启动 <BR><BR>　　一般的嵌入式系统在主程序执行之前都需要执行一些初始化的过程以创造嵌入式程序运行的环境，尤其是一些高级的嵌入式系统，由于核心芯片使用内存映射、内存保护等机制以及编程使用高级语言 C,C++ 甚至 JAVA 语言，都需要先创建一个适合程序运行的硬件环境，然后初始化或者配置或者剪裁 run-time library, 这些工作都必须在主程序运行前完成，所以一个 startup 程序或者程序组对于一个嵌入式系统来说是非常重要的。要编写 startup 程序，需要对编译器、链接器和汇编器的细节有一定的了解，同时对 ARM 芯片硬件本身的地址分配以及 memory mapping 机制也需要有一些了解。 <BR><BR>　　2 ARM 程序的工作过程 <BR><BR>　　首先由各种 source file 经过编译产生 object 文件，然后 object 文件经过链接生成 Image 文件，然后通过 ICE 的方法，根据描述文件的指定下载到目标板上的固态存储器指定地址当中，比如 flash ， EEPROM, ROM 等等。在程序执行之前，根据某些描述文件，将需要读写数据的部分读出放入动态存储器比如 RAM 当中，然后程序从 ROM 开始执行。或者有时为了提高程序的运行速度，也可以将所有的程序 ( 有一些 root 的部分除外，以后会提及 ) 通过一个描述文件放入指定的 RAM 当中，然后程序从 RAM 开始执行，但是这样会耗费大量的动态存储器，所以大部分程序会取折中的方法，将需要快速运行的部分和要读写的部分放入 RAM 中 ( 一般读固态存储器的过程和动态存储器的过程是一样的，但是写就不同了，所以读写的部分一定要放到 RAM 中 ) ，而只读的部分和对速度要求不是那么高的部分放入固态存储器。同时 ARM 结构的异常向量表规定放在地址为 0x00000000 开始的地址空间上，而一般的 CPU 为了提高异常相应速度，会将这个向量段 remap 到其他的 RAM 当中，所以在描述文件当中必须精确指定异常向量跳转程序的地址到 remap 的地方。在 application 程序执行前，还需要由一些文件描述 application 程序执行的环境。比如系统工作时钟，总线频率。现在一般嵌入式编程语言为 C,C++ 等。如果在使用它们的时候使用的 runtime-library ，那么在程序执行前还需要为这些库函数初始化 heap 。然后 ARM 可能工作在不同的模式，还需要为不同的工作模式设置 stack 。这样，描述链接地址的文件，以及在 application 运行前所有的初始化程序就是 startup 程序组 <BR><BR>　　3 STARTUP 分类 <BR><BR>　　这样，将 startup 程序所完成的功能分类。一类是链接地址描述，一类是各种初始化的程序。根据不同的应用，描述文件和初始化程序的内容以及结构和复杂程度都会不同。但是基本上，它们都必须实现以下功能。 <BR><BR>　　3.1 描述文件实现功能 <BR>　　描述文件可以是链接命令行上简单的几个字符，也可以是一个非常复杂的文件，但是它必须完成如下功能： <BR>　　; 指定程序下载的地址 <BR>　　; 指定程序执行的地址 <BR><BR>　　3.2 初始化程序实现的功能 <BR>　　初始化程序根据不同的应用，其结构和复杂度也不同，但是它必须完成如下基本功能： <BR>　　; 异常向量初始化 <BR>　　; 内存环境初始化 <BR>　　; 其他硬件环境初始化 <BR></P>]]></description>
</item><item>
<title><![CDATA[ARM微处理器概述]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6911</link>
<author>wangxg97</author>
<pubDate>2007-3-18 11:13:00</pubDate>
<description><![CDATA[<P align=center><SPAN class=style3><STRONG><SPAN class=style3><FONT color=#000000>ARM微处理器概述</FONT></SPAN></STRONG></SPAN></P>
<P align=left><SPAN class=style3><SPAN class=style3><FONT size=2>1&nbsp;&nbsp;ARM－Advanced&nbsp;RISC&nbsp;Machines <BR><BR>　　ARM（Advanced&nbsp;RISC&nbsp;Machines），既可以认为是一个公司的名字，也可以认为是对一类微处理器的通称，还可以认为是一种技术的名字。<BR><BR>　　1991年ARM公司成立于英国剑桥，主要出售芯片设计技术的授权。目前，采用ARM技术知识产权（IP）核的微处理器，即我们通常所说的ARM微处理器，已遍及工业控制、消费类电子产品、通信系统、网络系统、无线系统等各类产品市场，基于ARM技术的微处理器应用约占据了32位RISC微处理器75％以上的市场份额，ARM技术正在逐步渗入到我们生活的各个方面。<BR><BR>　　ARM公司是专门从事基于RISC技术芯片设计开发的公司，作为知识产权供应商，本身不直接从事芯片生产，靠转让设计许可由合作公司生产各具特色的芯片，世界各大半导体生产商从ARM公司购买其设计的ARM微处理器核，根据各自不同的应用领域，加入适当的外围电路，从而形成自己的ARM微处理器芯片进入市场。目前，全世界有几十家大的半导体公司都使用ARM公司的授权，因此既使得ARM技术获得更多的第三方工具、制造、软件的支持，又使整个系统成本降低，使产品更容易进入市场被消费者所接受，更具有竞争力。 <BR><BR>　　2&nbsp;&nbsp;ARM微处理器的应用领域及特点 <BR><BR>　　2.1&nbsp;&nbsp;ARM微处理器的应用领域<BR><BR>　　到目前为止，ARM微处理器及技术的应用几乎已经深入到各个领域：<BR><BR>　　&nbsp;1、工业控制领域：作为32的RISC架构，基于ARM核的微控制器芯片不但占据了高端微控制器市场的大部分市场份额，同时也逐渐向低端微控制器应用领域扩展，ARM微控制器的低功耗、高性价比，向传统的8位/16位微控制器提出了挑战。<BR><BR>　　2、无线通讯领域：目前已有超过85%的无线通讯设备采用了ARM技术，&nbsp;ARM以其高性能和低成本，在该领域的地位日益巩固。<BR><BR>　　3、网络应用：随着宽带技术的推广，采用ARM技术的ADSL芯片正逐步获得竞争优势。此外，ARM在语音及视频处理上行了优化，并获得广泛支持，也对DSP的应用领域提出了挑战。<BR><BR>　　4、消费类电子产品：ARM技术在目前流行的数字音频播放器、数字机顶盒和游戏机中得到广泛采用。<BR><BR>　　5、成像和安全产品：现在流行的数码相机和打印机中绝大部分采用ARM技术。手机中的32位SIM智能卡也采用了ARM技术。<BR><BR>　　除此以外，ARM微处理器及技术还应用到许多不同的领域，并会在将来取得更加广泛的应用。 </FONT></P>
<P><FONT size=2>　　2.2&nbsp;&nbsp;ARM微处理器的特点<BR><BR>　　采用RISC架构的ARM微处理器一般具有如下特点：<BR><BR>　　1、体积小、低功耗、低成本、高性能；<BR>　　2、支持Thumb（16位）/ARM（32位）双指令集，能很好的兼容8位/16位器件；<BR>　　3、大量使用寄存器，指令执行速度更快；<BR>　　4、大多数数据操作都在寄存器中完成；<BR>　　5、寻址方式灵活简单，执行效率高；<BR>　　6、指令长度固定； </FONT></P>
<P><FONT size=2>　　3&nbsp;&nbsp;ARM微处理器系列 <BR></FONT><FONT size=2><BR>　　ARM微处理器目前包括下面几个系列，以及其它厂商基于ARM体系结构的处理器，除了具有ARM体系结构的共同特点以外，每一个系列的ARM微处理器都有各自的特点和应用领域。<BR></FONT><FONT size=2><BR>－&nbsp;ARM7系列<BR>－&nbsp;ARM9系列<BR>－&nbsp;ARM9E系列<BR>－&nbsp;ARM10E系列<BR>－&nbsp;SecurCore系列<BR>－&nbsp;Inter的Xscale<BR>－&nbsp;Inter的StrongARM<BR><BR>　　其中，ARM7、ARM9、ARM9E和ARM10为4个通用处理器系列，每一个系列提供一套相对独特的性能来满足不同应用领域的需求。SecurCore系列专门为安全要求较高的应用而设计。<BR><BR>　　以下我们来详细了解一下各种处理器的特点及应用领域。 <BR></FONT><FONT size=2><BR>　　3.1&nbsp;&nbsp;ARM7微处理器系列<BR><BR>　　ARM7系列微处理器为低功耗的32位RISC处理器，最适合用于对价位和功耗要求较高的消费类应用。ARM7微处理器系列具有如下特点：<BR><BR>－&nbsp;具有嵌入式ICE－RT逻辑，调试开发方便。<BR>－&nbsp;极低的功耗，适合对功耗要求较高的应用，如便携式产品。<BR>－&nbsp;能够提供0.9MIPS/MHz的三级流水线结构。<BR>&nbsp;－&nbsp;代码密度高并兼容16位的Thumb指令集。<BR>&nbsp;－&nbsp;对操作系统的支持广泛，包括Windows&nbsp;CE、Linux、Palm&nbsp;OS等。<BR>－&nbsp;指令系统与ARM9系列、ARM9E系列和ARM10E系列兼容，便于用户的产品升级换代。<BR>&nbsp;－&nbsp;主频最高可达130MIPS，高速的运算处理能力能胜任绝大多数的复杂应用。<BR><BR>　　ARM7系列微处理器的主要应用领域为：工业控制、Internet设备、网络和调制解调器设备、移动电话等多种多媒体和嵌入式应用。<BR><BR>　　ARM7系列微处理器包括如下几种类型的核：ARM7TDMI、ARM7TDMI-S、ARM720T、ARM7EJ。其中，ARM7TMDI是目前使用最广泛的32位嵌入式RISC处理器，属低端ARM处理器核。TDMI的基本含义为：<BR><BR>T：&nbsp;支持16为压缩指令集Thumb；<BR>D：&nbsp;支持片上Debug；<BR>M：内嵌硬件乘法器（Multiplier）<BR>I：&nbsp;嵌入式ICE，支持片上断点和调试点；<BR>本书所介绍的Samsung公司的S3C4510B即属于该系列的处理器。 <BR><BR>　　</FONT><FONT size=2>3.2&nbsp;&nbsp;ARM9微处理器系列<BR><BR>　　ARM9系列微处理器在高性能和低功耗特性方面提供最佳的性能。具有以下特点：<BR><BR>－&nbsp;5级整数流水线，指令执行效率更高。<BR>－&nbsp;提供1.1MIPS/MHz的哈佛结构。<BR>－&nbsp;支持32位ARM指令集和16位Thumb指令集。<BR>－&nbsp;支持32位的高速AMBA总线接口。<BR>－&nbsp;全性能的MMU，支持Windows&nbsp;CE、Linux、Palm&nbsp;OS等多种主流嵌入式操作系统。<BR>－&nbsp;MPU支持实时操作系统。<BR>－&nbsp;支持数据Cache和指令Cache，具有更高的指令和数据处理能力。<BR><BR>　　ARM9系列微处理器主要应用于无线设备、仪器仪表、安全系统、机顶盒、高端打印机、数字照相机和数字摄像机等。<BR><BR>　　ARM9系列微处理器包含ARM920T、ARM922T和ARM940T三种类型，以适用于不同的应用场合。 </FONT></P>
<P><FONT size=2>　　3.3&nbsp;&nbsp;ARM9E微处理器系列<BR><BR>　　ARM9E系列微处理器为可综合处理器，使用单一的处理器内核提供了微控制器、DSP、Java应用系统的解决方案，极大的减少了芯片的面积和系统的复杂程度。ARM9E系列微处理器提供了增强的DSP处理能力，很适合于那些需要同时使用DSP和微控制器的应用场合。<BR><BR>　　ARM9E系列微处理器的主要特点如下：<BR><BR>－&nbsp;支持DSP指令集，适合于需要高速数字信号处理的场合。<BR>－&nbsp;5级整数流水线，指令执行效率更高。<BR>－&nbsp;支持32位ARM指令集和16位Thumb指令集。<BR>－&nbsp;支持32位的高速AMBA总线接口。<BR>－&nbsp;支持VFP9浮点处理协处理器。<BR>－&nbsp;全性能的MMU，支持Windows&nbsp;CE、Linux、Palm&nbsp;OS等多种主流嵌入式操作系统。<BR>－&nbsp;MPU支持实时操作系统。<BR>－&nbsp;支持数据Cache和指令Cache，具有更高的指令和数据处理能力。<BR>－&nbsp;主频最高可达300MIPS。<BR><BR>　　ARM9系列微处理器主要应用于下一代无线设备、数字消费品、成像设备、工业控制、存储设备和网络设备等领域。<BR><BR>　　ARM9E系列微处理器包含ARM926EJ-S、ARM946E-S和ARM966E-S三种类型，以适用于不同的应用场合。 </FONT></P>
<P><FONT size=2>　　3.4&nbsp;&nbsp;ARM10E微处理器系列<BR><BR>　　ARM10E系列微处理器具有高性能、低功耗的特点，由于采用了新的体系结构，与同等的ARM9器件相比较，在同样的时钟频率下，性能提高了近50％，同时，ARM10E系列微处理器采用了两种先进的节能方式，使其功耗极低。<BR><BR>　　ARM10E系列微处理器的主要特点如下：<BR><BR>－&nbsp;支持DSP指令集，适合于需要高速数字信号处理的场合。<BR>－&nbsp;6级整数流水线，指令执行效率更高。<BR>－&nbsp;支持32位ARM指令集和16位Thumb指令集。<BR>－&nbsp;支持32位的高速AMBA总线接口。<BR>－&nbsp;支持VFP10浮点处理协处理器。<BR>－&nbsp;全性能的MMU，支持Windows&nbsp;CE、Linux、Palm&nbsp;OS等多种主流嵌入式操作系统。<BR>－&nbsp;支持数据Cache和指令Cache，具有更高的指令和数据处理能力<BR>－&nbsp;主频最高可达400MIPS。<BR>－&nbsp;内嵌并行读/写操作部件。<BR><BR>　　ARM10E系列微处理器主要应用于下一代无线设备、数字消费品、成像设备、工业控制、通信和信息系统等领域。<BR><BR>　　ARM10E系列微处理器包含ARM1020E、ARM1022E和ARM1026EJ-S三种类型，以适用于不同的应用场合。 </FONT></P>
<P><FONT size=2>　　3.5&nbsp;&nbsp;SecurCore微处理器系列<BR><BR>　　SecurCore系列微处理器专为安全需要而设计，提供了完善的32位RISC技术的安全解决方案，因此，SecurCore系列微处理器除了具有ARM体系结构的低功耗、高性能的特点外，还具有其独特的优势，即提供了对安全解决方案的支持。<BR><BR>　　SecurCore系列微处理器除了具有ARM体系结构各种主要特点外，还在系统安全方面具有如下的特点：<BR><BR>－&nbsp;带有灵活的保护单元，以确保操作系统和应用数据的安全。<BR>－&nbsp;采用软内核技术，防止外部对其进行扫描探测。<BR>－&nbsp;可集成用户自己的安全特性和其他协处理器。<BR><BR>　　SecurCore系列微处理器主要应用于一些对安全性要求较高的应用产品及应用系统，如电子商务、电子政务、电子银行业务、网络和认证系统等领域。<BR><BR>　　SecurCore系列微处理器包含SecurCore&nbsp;SC100、SecurCore&nbsp;SC110、SecurCore&nbsp;SC200和SecurCore&nbsp;SC210四种类型，以适用于不同的应用场合。 </FONT></P>
<P><FONT size=2>　　3.6&nbsp;&nbsp;StrongARM微处理器系列<BR><BR>　　Inter&nbsp;StrongARM&nbsp;SA-1100处理器是采用ARM体系结构高度集成的32位RISC微处理器。它融合了Inter公司的设计和处理技术以及ARM体系结构的电源效率，采用在软件上兼容ARMv4体系结构、同时采用具有Intel技术优点的体系结构。<BR>Intel&nbsp;StrongARM处理器是便携式通讯产品和消费类电子产品的理想选择，已成功应用于多家公司的掌上电脑系列产品。 </FONT></P>
<P><FONT size=2>　　3.7&nbsp;&nbsp;Xscale处理器<BR><BR>　　Xscale&nbsp;处理器是基于ARMv5TE体系结构的解决方案，是一款全性能、高性价比、低功耗的处理器。它支持16位的Thumb指令和DSP指令集，已使用在数字移动电话、个人数字助理和网络产品等场合。<BR>Xscale&nbsp;处理器是Inter目前主要推广的一款ARM微处理器。 </FONT></P>
<P><FONT size=2>　　4&nbsp;&nbsp;ARM微处理器结构 <BR><BR>　　</FONT><FONT size=2>4.1&nbsp;&nbsp;RISC体系结构<BR><BR>　　传统的CISC（Complex&nbsp;Instruction&nbsp;Set&nbsp;Computer，复杂指令集计算机）结构有其固有的缺点，即随着计算机技术的发展而不断引入新的复杂的指令集，为支持这些新增的指令，计算机的体系结构会越来越复杂，然而，在CISC指令集的各种指令中，其使用频率却相差悬殊，大约有20％的指令会被反复使用，占整个程序代码的80％。而余下的80％的指令却不经常使用，在程序设计中只占20％，显然，这种结构是不太合理的。<BR><BR>　　基于以上的不合理性，1979年美国加州大学伯克利分校提出了RISC（Reduced&nbsp;Instruction&nbsp;Set&nbsp;Computer，精简指令集计算机）的概念，RISC并非只是简单地去减少指令，而是把着眼点放在了如何使计算机的结构更加简单合理地提高运算速度上。RISC结构优先选取使用频最高的简单指令，避免复杂指令；将指令长度固定，指令格式和寻地方式种类减少；以控制逻辑为主，不用或少用微码控制等措施来达到上述目的。<BR><BR>　　到目前为止，RISC体系结构也还没有严格的定义，一般认为，RISC体系结构应具有如下特点：<BR><BR>－&nbsp;采用固定长度的指令格式，指令归整、简单、基本寻址方式有2～3种。<BR>－&nbsp;使用单周期指令，便于流水线操作执行。<BR>－&nbsp;大量使用寄存器，数据处理指令只对寄存器进行操作，只有加载/&nbsp;存储指令可以访问存储器，以提高指令的执行效率。<BR><BR>　　除此以外，ARM体系结构还采用了一些特别的技术，在保证高性能的前提下尽量缩小芯片的面积，并降低功耗：<BR><BR>－&nbsp;所有的指令都可根据前面的执行结果决定是否被执行，从而提高指令的执行效率。<BR>－&nbsp;可用加载/存储指令批量传输数据，以提高数据的传输效率。<BR>－&nbsp;可在一条数据处理指令中同时完成逻辑处理和移位处理。<BR>－&nbsp;在循环处理中使用地址的自动增减来提高运行效率。<BR><BR>　　当然，和CISC架构相比较，尽管RISC架构有上述的优点，但决不能认为RISC架构就可以取代CISC架构，事实上，RISC和CISC各有优势，而且界限并不那么明显。现代的CPU往往采用CISC的外围，内部加入了RISC的特性，如超长指令集CPU就是融合了RISC和CISC的优势，成为未来的CPU发展方向之一。 </FONT></P>
<P><FONT size=2>　　4.2&nbsp;&nbsp;ARM微处理器的寄存器结构<BR><BR>　　ARM处理器共有37个寄存器，被分为若干个组（BANK），这些寄存器包括：<BR><BR>－&nbsp;31个通用寄存器，包括程序计数器（PC指针），均为32位的寄存器。<BR>－&nbsp;6个状态寄存器，用以标识CPU的工作状态及程序的运行状态，均为32位，目前只使用了其中的一部分。<BR><BR>　　同时，ARM处理器又有7种不同的处理器模式，在每一种处理器模式下均有一组相应的寄存器与之对应。即在任意一种处理器模式下，可访问的寄存器包括15个通用寄存器（R0～R14）、一至二个状态寄存器和程序计数器。在所有的寄存器中，有些是在7种处理器模式下共用的同一个物理寄存器，而有些寄存器则是在不同的处理器模式下有不同的物理寄存器。<BR>关于ARM处理器的寄存器结构，在后面的相关章节将会详细描述。 <BR></FONT><FONT size=2><BR>　　4.3&nbsp;&nbsp;ARM微处理器的指令结构<BR><BR>　　ARM微处理器的在较新的体系结构中支持两种指令集：ARM指令集和Thumb指令集。其中，ARM指令为32位的长度，Thumb指令为16位长度。Thumb指令集为ARM指令集的功能子集，但与等价的ARM代码相比较，可节省30％～40％以上的存储空间，同时具备32位代码的所有优点。<BR><BR>　　关于ARM处理器的指令结构，在后面的相关章节将会详细描述。<BR>&nbsp;&nbsp;<BR>　　ARM微处理器的应用选型<BR><BR>　　鉴于ARM微处理器的众多优点，随着国内外嵌入式应用领域的逐步发展，ARM微处理器必然会获得广泛的重视和应用。但是，由于ARM微处理器有多达十几种的内核结构，几十个芯片生产厂家，以及千变万化的内部功能配置组合，给开发人员在选择方案时带来一定的困难，所以，对ARM芯片做一些对比研究是十分必要的。<BR><BR>　　以下从应用的角度出发，对在选择ARM微处理器时所应考虑的主要问题做一些简要的探讨。<BR><BR>　　ARM微处理器内核的选择<BR><BR>　　从前面所介绍的内容可知，ARM微处理器包含一系列的内核结构，以适应不同的应用领域，用户如果希望使用WinCE或标准Linux等操作系统以减少软件开发时间，就需要选择ARM720T以上带有MMU（Memory&nbsp;Management&nbsp;Unit）功能的ARM芯片，ARM720T、ARM920T、ARM922T、ARM946T、Strong-ARM都带有MMU功能。而ARM7TDMI则没有MMU，不支持Windows&nbsp;CE和标准Linux，但目前有uCLinux等不需要MMU支持的操作系统可运行于ARM7TDMI硬件平台之上。事实上，uCLinux已经成功移植到多种不带MMU的微处理器平台上，并在稳定性和其他方面都有上佳表现。<BR><BR>　　本书所讨论的S3C4510B即为一款不带MMU的ARM微处理器，可在其上运行uCLinux操作系统。<BR><BR>　　系统的工作频率<BR><BR>　　系统的工作频率在很大程度上决定了ARM微处理器的处理能力。ARM7系列微处理器的典型处理速度为0.9MIPS/MHz，常见的ARM7芯片系统主时钟为20MHz-133MHz，ARM9系列微处理器的典型处理速度为1.1MIPS/MHz，常见的ARM9的系统主时钟频率为100MHz-233MHz，ARM10最高可以达到700MHz。不同芯片对时钟的处理不同，有的芯片只需要一个主时钟频率，有的芯片内部时钟控制器可以分别为ARM核和USB、UART、DSP、音频等功能部件提供不同频率的时钟。<BR><BR>　　芯片内存储器的容量<BR><BR>　　大多数的ARM微处理器片内存储器的容量都不太大，需要用户在设计系统时外扩存储器，但也有部分芯片具有相对较大的片内存储空间，如ATMEL的AT91F40162就具有高达2MB的片内程序存储空间，用户在设计时可考虑选用这种类型，以简化系统的设计。<BR><BR>　　片内外围电路的选择<BR><BR>　　除ARM微处理器核以外，几乎所有的ARM芯片均根据各自不同的应用领域，扩展了相关功能模块，并集成在芯片之中，我们称之为片内外围电路，如USB接口、IIS接口、LCD控制器、键盘接口、RTC、ADC和DAC、DSP协处理器等，设计者应分析系统的需求，尽可能采用片内外围电路完成所需的功能，这样既可简化系统的设计，同时提高系统的可靠性。</FONT></P></SPAN></SPAN>]]></description>
</item><item>
<title><![CDATA[S3c2410软件调试总结]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6909</link>
<author>wangxg97</author>
<pubDate>2007-3-17 11:31:00</pubDate>
<description><![CDATA[<P align=center><STRONG>S3c2410软件调试总结</STRONG></P>
<P><STRONG>S3c2410软件调试总结（一）<BR></P>
<P align=left></STRONG>硬件平台介绍<BR><BR>我的硬件平台主要是 S3c2410 ＋ FPGA（cyclone EP1C6），出于成本的考虑，我买了一块2410的核心板，然后自己画了一块底板，主要的模块有：</P>
<P>S3c2410&nbsp;&nbsp; ARM920 CORE RISC CPU<BR>EP1C6&nbsp;&nbsp; Altera cyclone series Fpga<BR>AT89S52&nbsp;&nbsp; atmel 51 <BR>RTL8019as&nbsp; 10M network device<BR>Sdram&nbsp;&nbsp; x2&nbsp; total: 64M byte<BR>K9F5608&nbsp;&nbsp; Nand flash 32M byte<BR>AM29LV160D&nbsp; x2&nbsp; NOR flash total: 4M byte<BR>64LV25616&nbsp; SRAM&nbsp; 512K byte<BR>UDA1341&nbsp;&nbsp; IIS Interface <BR>24C256&nbsp;&nbsp; EEPROM<BR>USB&nbsp;&nbsp; Host 1, Dev 1<BR>SD interface<BR>UART&nbsp;&nbsp; x2<BR></P><PRE><STRONG>S3c2410软件调试总结（二）<BR></STRONG><BR><P>   ADS下C语言的入口方式和ROM镜像文件的生成</P><BR><P>这部分介绍下ADS下如何生成可以运行的ROM镜像文件，我们知道当程序下载到flash中运行的时候，对于RW、ZI数据就存在着两个环境，一个load环境，一个是exec环境，有时候由于速度的需要RO数据也要重新加载，那么对RO数据也是有两个环境。编译器产生ROM镜像文件时候，这三块数据的存放依次为RO、RW、ZI，并且地址空间时连续的。但是到了运行的时候，RW数据必须被拷贝到SDRAM（SRAM）中以支持读写，这就是我们所谓的运行环境。那么就要有一段代码去完成这个任务，在本章中我们介绍如何生成这段代码。</P><BR><P>玩过2410的朋友都知道2410初始化代码中有一段搬运RW和ZI初始化的代码，没错，它确实能够在一定程度上完成上面所说的任务，只要我们在生成二进制可执行代码的时候在编译器链接项的地方填写正确的RO＆RW地址，（比如RO = 0, RW = 0x30000000), 那么将程序下到 NOR flash的零地址并从nor flash启动，启动代码会将RW＆ZI数据弄到0x30000000，程序就能跑起来了。</P><BR><P>但是各位有没有想过，怎么把RO代码弄到SDRAM中（有时候这是必须的，比方后面我将提到用nor flash的bootloader烧写nor flash）？如果直接设RO＝0x30000000，那么这段代码下载到0地址肯定跑不起来，除非是ROPI，这个要求就高了。这里我们有必要从介绍ADS中规定的C语言入口开始，ADS中从初始化汇编代码跳到main函数有两种方式，main和__main：<BR><BR>1，在__main入口的模式下，汇编代码的指令为 b  __main, 编译器在跳转到main之前还要作一系列的工作，这其中就包括对运行环境的初始化，在&lt;ADS COMPILE GUIDE&gt;中提到： copies nonroot(RO&amp;RW) execution regions from load addr to exec addr, and Zeros ZI region. 借助编译器，我们就可以定义更为复杂的运行环境，这里要用到scatter文件(.scf)，比如我们要的目标运行环境是：将启动代码以外的所有代码都 拷贝到SDRAM的初始地址中运行，比且把RW段设在0x30800000，那么对应的scf文件如下：<BR>   <BR>   FLASH 0x0  0x200000<BR>   {<BR>    EXEC1 0x0 0x200000<BR>    {<BR>     2410init.o(Init, +First)  <BR>     __main.o(+RO)   ; copy code      <BR>     * (Region$$Table)       ; RO/RW addresses to copy<BR>     * (ZISection$$Table)    ; ZI addresses to zero<BR>    }<BR>    EXEC2 0x30000000 0x00800000<BR>    {<BR>     *(+RO)<BR>    }<BR>    SDRAM 0x30800000 0x00800000<BR>    {<BR>     *(+RW,+ZI)<BR>    }<BR>   }<BR>  ;Sections named Region$$Table and ZISection$$Table which contain the addresses of the code/data to be copied. <BR><BR>当然，在这种模式下，有些入口函数必须自己重定义，比如__user_initial_stackheap,具体参见ADS文档。</P><BR><P>2, main入口模式即简单的跳转，这里起始不用“main”这个名字也无所谓。那么编译器不会作任何的初始化，所有运行环境的建立都要* 我们自己，这就是大家看到的那段搬运代码存在的理由。但是它实现一些简单的运行环境是可取的，如果用scf定义的复杂环境，虽然我 相信是可以做到的，但是可能会比较麻烦。我还没深究。<BR><BR>另外，这里提一下semihost，因为我们在看ADS的东西的时候经常出现这个词，我也一直受其困扰。这里我简单说一下自己的见解，semihost 仅仅是一种调试手段，它的机理就是利用MULTI_IDE等工具捕捉目标环境运行过程中产生的值为0x123456的SWI中断，然后向上位机的ADS 软件发送对应的调试信息。对于我们最后的应用代码来说，都是nonsemihost类型的。如果我们在调试中使用semihost，那么只要在最后重定义 ADS中的一些使用到的库函数（比如fputc），代码就可以从semihost向nonsemihost的类型转变。不过到目前为止，我还没体会到semihost的威力。</P><PRE><STRONG></STRONG>&nbsp;</PRE><PRE><STRONG>S3c2410软件调试总结（三）<BR></STRONG><BR></PRE><PRE>2410启动代码分析</PRE><PRE><BR></PRE><PRE>这一章主要对目前广泛流行的2410启动代码进行分析：S3C2410的初始化代码主要涉及到对系统主要模块的配置、运行环境的建立、系统时钟、MMU等模块的配置，下面按执行顺序依次都各个部分进行分析：</PRE><PRE><BR></PRE><PRE>程序入口：（ResetHandler）<BR>  在程序一开始，首先进行的一些操作主要保证初始化程序能够顺利的运行， 因此主要包括关闭WDT、中断，配置锁相环等。</PRE><PRE><BR></PRE><PRE>配置memory接口<BR>   memory接口是确保数据访问正确的基本保障，此处主要配置SFR寄存器中0x48000000开始的memory接口寄存器组，  确保每个bank的位宽、访问类型（waitable）以及时序参数正确。如果没有特别的要求，一般来说时序参数使用默认值即可。<BR><BR>初始化堆栈<BR>   ARM有6种运行模式，必须为每一种模式提供独立的堆栈空间，在堆栈设置之前是不能进行C函数的调用的。ARM的堆栈模式 是从高地址递减的，我的所有代码统一将堆栈的首地址设在0x33ff8000处，往低依次为FIQ、IRQ、Abort、Undef、SVC，其中<BR>  SVC和User模式不予区分。堆栈大小一般可在头文件或者当前文件中修改。<BR><BR>运行空间的初始化<BR>   这段代码主要完成两个功能，一是将RW数据搬运到RW空间（我们生成ROM镜像时，RW数据是跟在RO数据之后的），二是 初始化ZI数据段。当然，这段代码存在的前提是代码的运行环境只是标准的两段式：一段RO空间和一段RW空间；并且在C程序<BR>  入口时没有调用编译器的链接库（__main）。后者已经提供相应的功能，并且支持更加复杂的运行环境定义（使用SCF文件），<BR>  （关于这一点，我在介绍ADS中C代码的启动模式时已经详细介绍）。<BR><BR>__rt_lib_init<BR>   在ADS1.2的环境中，如果在C入口没有调用编译器的链接库（__main），那么在C程序一开始要调用该函数以初始化运行时的函数库，以保证对ADS提供的某些库函数能够正常调用。从这个函数开始，我们已经在C语言环境下了。</PRE><PRE><BR></PRE><PRE>MMU初始化<BR>   2410的MMU支持1级＆2级地址映射，在我们目前大部分应用中均采用1级section模式的地址映射，一个section的大小为1M，也就是说从逻辑地址到物理地址的转变是这样的一个过程：<BR>   一个32位的地址，高12位决定了该地址在页表中的index，这个index的内容决定了该逻辑section对应的物理section；  低20位决定了该地址在section中的偏移（index）。<BR>   因此从0x0～0xffffffff的地址空间总共可以分成0x1000（4K）个section，页表中每项的大小为32个bit，因此页表的大小为0x4000（16K）。在我的代码中所有程序的页表统一存放在地址0x33ff8000。<BR>   每个页表项的内容如下:<BR>  <BR>          bit: 31                              20 19     12 11  10 9 8          5  4  3  2  1  0<BR>   content:  Section对应的物理地址     NULL     AP   0  Domain   1  C B  1  0<BR>   <BR>      最低两位（10）是section分页的标识。<BR>      AP：Access Permission，区分只读、读写、SVC＆其它模式。<BR>   Domain：每个section都属于某个Domain，一个有16个Domain，每个Domain的属性由CP15的R3寄存器控制。 在我得所有程序中，都只包含两个Domain，一个是SFR地址以下（包括SFR）的空间，可访问；  另一个是SFR以上的空间，不可访问。<BR>   C、B：这两位决定了该section的cache＆write buffer属性，这与该段的用途(RO or RW)有密切关系。不同的用途要做不同的设置。<BR><BR>       C     B                         具体含义<BR>        0    0   无cache，无写缓冲，任何对memory的读写都反映到ASB总线上。  </PRE><PRE><BR></PRE><PRE>                   对 memory 的操作过程中CPU需要等待。<BR>        0    1   无cache，有写缓冲，读操作直接反映到ASB总线上。写操作CPU将数据写</PRE><PRE><BR></PRE><PRE>                   入 到写缓冲后继续运行，由写缓冲进行ASB操作。<BR>       1    0    有cache，写通模式，读操作首先考虑cache hit；写操作时直接将数据写入</PRE><PRE><BR></PRE><PRE>                   写缓冲，如果同时出现cache hit，那么也更新cache。<BR>      1    1    有cache，写回模式，读操作首先考虑cache hit；写操作也首先考虑cache，</PRE><PRE><BR></PRE><PRE>                  如果hit，则只修改cache，并将cache对应半行的dirty比特置位；如果miss，</PRE><PRE><BR></PRE><PRE>                   则写入写缓冲，触发ASB总线操作。<BR>               <BR>            在我的程序中内存空间的分配统一采用了文末的MEMORY图。虽然MMU只是使用了逻辑地址到物理地址的linear transfer（值不改变），但是由于MMU能够引入cache＆write buffer，因此系统性能有很大的提高！</PRE><PRE><BR></PRE><PRE>配置时钟比、重新设置PLL<BR>   2410内部有三个时钟：FCLK、HCLK、PCLK，分别供CPU、AHB总线和APB总线使用，为了降低功耗，一般都选择周期比为1：2：4的合理配置。 同时将PLL配置为运行环境时钟，一般都达到最高202M。</PRE><PRE><BR></PRE><PRE>IO初始化<BR>   将IO口配置为对应的功能选项，同时一般会点亮相应的LED灯。<BR>   <BR>中断初始化<BR>   2410的内存空间没有remap的机制，应该中断入口时钟位于零地址。因此中断服务机制可以描述如下：<BR>    首先，不管使用那种启动方式，必须确保一下代码段位于内存的0x0地址：<BR>     b ResetHandler  <BR>     b HandlerUndef ;handler for Undefined mode<BR>     b HandlerSWI ;handler for SWI interrupt<BR>     b HandlerPabort ;handler for PAbort<BR>     b HandlerDabort ;handler for DAbort<BR>     b .   ;reserved<BR>     b HandlerIRQ ;handler for IRQ interrupt <BR>     b HandlerFIQ ;handler for FIQ interrupt<BR>    除ResetHandler外，其余各项都是由如下的宏定义的一段代码：<BR>       HandlerFIQ   HANDLER  HandleFIQ<BR>     MACRO<BR>     $HandlerLabel  HANDLER  $HandleLabel<BR>     $HandlerLabel<BR>      sub sp,sp,#4            ;decrement sp(to store jump address)<BR>      stmfd sp!,{r0}        ;PUSH the work register to stack<BR>      ldr     r0,=$HandleLabel  ;load the address of HandleXXX to r0<BR>      ldr     r0,[r0]            ;load the contents<BR>      str     r0,[sp,#4]     ;store the contents(ISR) of HandleXXX to stack<BR>      ldmfd   sp!,{r0,pc}     ;POP the work register and pc(jump to ISR)<BR>        MEND<BR>      这段代码的含义是通过堆栈将中断向量表中的内容赋给PC指针（如HandleFIQ是存放着FIQ服务程序入口地址的地址），自然程序就跳到相应的入口地址。<BR>   可见，中断向量表存放的是各个中断服务程序的入口地址，它是用来被加载的，而并不是可执行代码。为了统一，所有示例程序都将中断向量表放在0x33ffff00开始的地址，并根据入口地址依次排列。<BR>   需要注意的是如果各种模式的服务程序用C语言定义，那么类型必须用__irq定义，以保证能够正确返回。<BR>   <BR>初始化串口<BR>     串口统一选用UART0，模式采用115200、1bit STOP、No Parity。<BR>  <BR>最后跳转到我们自己的应用程序！</PRE><PRE><BR></PRE><PRE>附：我得程序所使用的地址空间结构以及MMU中C、B的设置：<BR><BR>      Blank Area: RW_FAULT   0x5b000000 ～ 0xffffffff<BR>      <BR>      Sram &amp; SFR: NCNB           0x40000000 ～ 0x4affffff<BR>      <BR>      Blank Area: RW_FAULT    0x34000000 ～ 0x3fffffff</PRE><PRE><BR></PRE><PRE>      Int_Vec, Stack, MTT: CNB  0x33f00000 ～ 0x33ffffff<BR>      <BR>      SDRAM Download: NCNB   0x31000000 ～ 0x33efffff<BR>      <BR>      SDRAM Exec RW: CB         0x30800000 ～ 0x30ffffff<BR>      <BR>      SDRAM Exec R CNB        0x30000000 ～ 0x307fffff<BR>      <BR>      Bank5, FPGA: NCNB          0x28000000 ～ 0x2fffffff<BR>      <BR>      Bank4, FPGA: NCNB            0x20000000 ～ 0x27ffffff<BR>      <BR>      Bank3, Bottom NIC: NCNB   0x18000000 ～ 0x1fffffff<BR>      <BR>      Bank2, Bottom Flash: CNB  0x10000000 ～ 0x17ffffff<BR>      <BR>      Bank1, Bottom Sram: CNB   0x08000000  ～ 0x0fffffff<BR>      <BR>      Bank0, Flash or Sram: CNB  0x00000000 ～ 0x07ffffff<BR></PRE><PRE><STRONG>S3c2410软件调试总结（四）<BR></STRONG><BR><P>Nor Flash Bootloader<BR>   <BR>这是我着手写的第一个程序，我的想法是让这个程序同时支持通过串口对Nand 和 Nor FLASH的烧写，如果不进行任何烧写，那么就跳到Nor Flash的第二个section启动应用程序，这样一来，即使脱离JTGA，我也可以使用串口进行盲调。</P><BR><P>由于有现成的初始化文件和flash烧写的示例程序，开发起来还比较快。当然也遇到了一些问题，一开始连flash的device ID都读不出来，后来发现我指针没有定义成volatile类型，flash的操作时序被编译器优化了；再者，在对Nor Flash进行操作时，bank0在MMU中的类型一定要设为NCNB，这样比较保险。</P><BR><P>遇到最大的问题就是下面的了，一开始我用jtag把程序下载到0x30000000的地方运行，对Nor Flash的烧写完全正常，但是当把程序下载到Nor Flash中启动运行后，再对Nor Flash的section 2进行烧写时，就出现了问题。所幸没多久我就意识到了问题，将程序放在Nor Flash中运行，同时有对同一片flash进行操作，那么操作时序势必会被CPU的指令读取时序所破坏，因此程序必须搬运到SDRAM中运行。</P><BR><P>但是启动地址有必须是零地址，所以我采用了前文提到的scatter文件的方法，将非必要的代码全部搬到sdram中运行，scf文件格式就是前文中的那个。当然采用了__main的入口，调用了ADS的链接库，让它帮忙建立程序的运行环境。</P><BR><P>至此，Nor Flash Bootloader可以顺畅无忧的实现其功能了。</P><P>&nbsp;</P><PRE><STRONG>S3c2410软件调试总结（后记&amp;特别感谢）<BR></STRONG><BR><P>  后记 &amp; 特别感谢<BR>  <BR>第一次写这么洋洋洒洒的文章，想必垃圾成分一定很高，不过能有那么一点点有用的东西，我也就很高兴了。毕竟自己这么一路走过来，真的是满有感触的。</P><BR><P>在此，特别要感谢版上twentyone朋友对我的大力帮助，在高人的指点下，我长进不小，另外我用的他开发的JTAG代理软件XJTAG，个人觉得性能相当不错，各位在用简易JTAG口的，强烈推荐大家尝试：<A href="http://www.twentyone.blogchina.com/" target=_blank><FONT color=#000000>http://www.twentyone.blogchina.com/</FONT></A></P><BR><P>走到这一步，我只能说精彩的生活刚刚开始，接下来我会在linux和FPGA上作一些文章，这两方面的工作我都刚刚开始，远不够深入啊！非常乐意和大家一起探讨、学习、交流，让我们一起进步！</P></PRE></PRE></PRE>]]></description>
</item><item>
<title><![CDATA[SDRAM学习笔记]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6873</link>
<author>wangxg97</author>
<pubDate>2007-3-14 23:15:00</pubDate>
<description><![CDATA[<P align=center>&nbsp;<FONT color=#000063>SDRAM学习笔记</FONT></P>
<P>1.SDRAM的burst mode：SDRAM是一种命令型动作的设备，就算读写资料只有一个也要先下命令才可以用，为了增加工作效率，就产生了一种传送一个命令，写多个数据的模式，这就是burst mode。<BR>burst mode是一种利用内部列地址发生器来工作的高速读写模式，只要设置最开始的列地址，后面的地址就可以通过内部的列地址发生器来自动生成。</P>
<P>2.为什么要做precharge动作？ 关闭正在作用的SDRAM bank，算是一种结束命令，后面可以下新的命令。我想这是和SDRAM内部管理有关。(这是从网上查到的，感觉不够详细，也不太理解)</P>
<P>3.自动刷新功能？动态存储器（Dynamic RAM）都存在刷新问题。这里主要采用自动刷新方式，每隔一段时间向SDRAM发一条刷新命令。</P>
<P>4.SDRAM的地址线:在我们一般用的什么SRAM啊，PSRAM啊，RAM啊，一般而言都是有多少根地址线，然后可以算出寻址空间，比如有11根地址线，那寻址空间就是2的11次方减1。但是SDRAM是分列地址和行地址的，行、列地址线是复用的，所以有时候我们看到说寻址空间有多大多大，但是看看地址线怎么就那么几根啊，呵呵。SDRAM一般还有2根BANK的线，分成4个BANK，在有的处理器的SDRAM控制模块中，这两根线可能映射到地址线的某两根去。一般芯片常按照以下方式写芯片的配置，比如4Meg x 4 x 16，那这个芯片就是256Mbits。其中16指数据线是16根，中间一个4是只分4个BANK， 每个BANK是4Meg。</P>
<P>5.SDRAM的初始化:<BR>SDRAM上电后使用前必须要经过一段初始化操作才可以使用。这个操作过程是标准的过程。这个过程如下 </P>
<P>A:&nbsp;&nbsp; precharge </P>
<P>B:&nbsp;&nbsp; auto-refresh </P>
<P>C:&nbsp;&nbsp; Load Mode Register </P>
<P>D: Normal Read/Write </P>
<P>在上电后输入初始化命令值钱，最少要100us延迟（这个其实很容易满足，呵呵）。 </P>
<P>在输入precharge命令后，因为必须是对所有BANK进行Precharge，所以A10这个管脚要设置成高，因此在Precharge后面要做一个读的操作，这个操作最主要的是在SDRAM的寻址空间里设置的地址必须是A10是高的。 </P>
<P>在输入Auto-refresh命令后，一般要跟几句空操作或者读什么之类的，反正要达到延迟的目的，以使得SDRAM有时间来完成refresh。 </P>
<P>之后就是要设置SDRAM的模式寄存器，这个寄存器里一般设置了burst长度，CAS，burst类型，操作模式，还有是设置SDRAM是工作在单个读写操作还是burst操作下。而这个寄存器的设置也是通过地址线来设置的，所以在发出Load Mode Register命令后要做一个操作可是使得在SDRAM的地址线上出线的值就是你要设置的值。这里很有必要提醒的一下的是，这个操作是8位的操作，切记切记。 </P>
<P>设置完模式寄存器后就进入正常操作模式。 </P>
<P>实际上具体的操作要跟选用的处理器的SDRAM控制模块相结合来设置。对于这些初始化命令比较直观的理解就是拿逻辑分析仪来分析。 </P>
<P>在这里需要提醒一下CAS这个参数很重要。还有SDRAM必须要刷新的，因此刷新频率可以按照手册算出来的，但是设置的高一点也是可以的。常常SDRAM都有工作频率，但是也可以工作在低一点的频率上，比如PC133的，你工作到100也是可以的，设置基本不需要修改。</P>
<P>6.基本读写操作：<BR>SDRAM的基本读操作需要控制线和地址线相配合地发出一系列命令来完成。先发出BANK激活命令（ACTIVE），并锁存相应的BANK地址（BA0、BA1给出）和行地址（A0～A12给出）。BANK激活命令后必须等待大于tRCD(SDRAM的RAS到CAS的延迟指标)时间后，发出读命令字。CL（CAS延迟值）个工作时钟后，读出数据依次出现在数据总线上。在读操作的最后，要向SDRAM发出预充电（PRECHARGE）命令，以关闭已经激活的页。等待tRP时间（PRECHARGE）命令，以关闭已经激活的页。等待tRP时间（PRECHAREG命令后，相隔tRP时间，才可再次访问该行）后，可以开始下一次的读、写操作。SDRAM的读操作只有突发模式（Burst Mode），突发长度为1、2、4、8可选。 </P>
<P>SDRAM的基本写操作也需要控制线和地址线相配合地发出一系列命令来完成。先发出BANK激活命令（ACTIVE），并锁存相应的BANK地址（BA0、BA1给出）和行地址（A0～A12给出）。BANK激活命令后必须等待大于tRCD的时间后，发出写命令字。写命令可以立即写入，需写入数据依次送到DQ（数据线）上。在最后一个数据写入后延迟tWR时间。发出预充电命令，关闭已经激活的页。等待tRP时间后，可以展开下一次操作。写操作可以有突发写和非突发写两种。突发长度同读操作。 </P>
<P>7.其他：我们有时候看到有的原理图上数据线有倒过来接的，其实这个无所谓的，反过接，写进去的就是反的，但是读出又反了一下，反反两次正好没反。 </P>
<P>延伸一下到DDR，其实DDR就是SDRAM外面加了一个乌龟壳。因此初始化是一样的。当然DDR一是多了一个把时钟频率反相的时钟，因此有2个相位差180度的时钟。这两个一般都是用同一个时钟源产生，一致性会比较好。还有多了2个DQS，这个也是一个时序要求，一般CPU的控制模块都有设置好了。如果你使用的CPU不含有控制模块，那用FPGA去做一个控制模块的话，那就要好好研究时序了。 </P>
<P>在有些处理器的控制模块中，由于EMI的设置，地址线映射关系复杂，因此推算会比较麻烦，一般如果没有什么映射的话，还是很容易操作的。</P>]]></description>
</item><item>
<title><![CDATA[ARM启动代码设计]]></title>
<link>http://www.52RD.com/Blog/more.asp?name=wangxg97&amp;id=6869</link>
<author>wangxg97</author>
<pubDate>2007-3-14 20:32:00</pubDate>
<description><![CDATA[<TABLE class=center_tdbgall style="WORD-BREAK: break-all" cellSpacing=0 cellPadding=0 width=760 align=center border=0>
<TBODY>
<TR vAlign=center align=middle>
<TD class=main_ArticleTitle style="WORD-BREAK: break-all" colSpan=2 height=50>
<P>ARM启动代码设计</P></TD></TR>
<TR class=left_tdbgall align=middle>
<TD colSpan=2></TD></TR>
<TR>
<TD class=main_tdbg_760 id=fontzoom style="WORD-BREAK: break-all" vAlign=top colSpan=2 height=300>基于ARM的芯片多数为复杂的片上系统，这种复杂系统里的多数硬件模块都是可配置的，需要由软件来设置其需要的工作状态。因此在用户的应用程序之前，需要由专门的一段代码来完成对系统的初始化。由于这类代码直接面对处理器内核和硬件控制器进行编程，一般都是用汇编语言。一般通用的内容包括： 
<P>中断向量表</P>
<P>初始化存储器系统</P>
<P>初始化堆栈</P>
<P>初始化有特殊要求的断口，设备</P>
<P>初始化用户程序执行环境</P>
<P>改变处理器模式</P>
<P>呼叫主应用程序&nbsp;</P>
<P><STRONG>1. 中断向量表</STRONG></P>
<P>ARM要求中断向量表必须放置在从0地址开始，连续8X4字节的空间内。</P>
<P>每当一个中断发生以后，ARM处理器便强制把PC指针置为向量表中对应中断类型的地址值。因为每个中断只占据向量表中1个字的存储空间，只能放置一条ARM指令，使程序跳转到存储器的其他地方，再执行中断处理。</P>
<P>中断向量表的程序实现通常如下表示：</P>
<P>AREA Boot ,CODE, READONLY</P>
<P>ENTRY</P>
<P>B&nbsp;&nbsp;&nbsp; ResetHandler</P>
<P>B&nbsp;&nbsp;&nbsp; UndefHandler</P>
<P>B&nbsp;&nbsp;&nbsp; SWIHandler</P>
<P>B&nbsp;&nbsp;&nbsp; PreAbortHandler</P>
<P>B&nbsp;&nbsp;&nbsp; DataAbortHandler</P>
<P>B</P>
<P>B&nbsp;&nbsp; &nbsp;IRQHandler</P>
<P>B&nbsp;&nbsp;&nbsp; FIQHandler</P>
<P>其中关键字ENTRY是指定编译器保留这段代码，因为编译器可能会认为这是一段亢余代码而加以优化。链接的时候要确保这段代码被链接在0地址处，并且作为整个程序的入口。&nbsp;</P>
<P><STRONG>2. 初始化存储器系统</STRONG></P>
<P>(1)存储器类型和时序配置</P>
<P>通常Flash和SRAM同属于静态存储器类型，可以合用同一个存储器端口；而DRAM因为有动态刷新和地址线复用等特性，通常配有专用的存储器端口。</P>
<P>存储器端口的接口时序优化是非常重要的，这会影响到整个系统的性能。因为一般系统运行的速度瓶颈都存在于存储器访问，所以存储器访问时序应尽可能的快；而同时又要考虑到由此带来的稳定性问题。</P>
<P>(2)存储器地址分布</P>
<P>一种典型的情况是启动ROM的地址重映射。</P>
<P><STRONG>3. 初始化堆栈</STRONG></P>
<P>因为ARM有7种执行状态，每一种状态的堆栈指针寄存器（SP）都是独立的。因此，对程序中需要用到的每一种模式都要给SP定义一个堆栈地址。方法是改变状态寄存器内的状态位，使处理器切换到不同的状态，让后给SP赋值。注意：不要切换到User模式进行User模式的堆栈设置，因为进入User模式后就不能再操作CPSR回到别的模式了，可能会对接下去的程序执行造成影响。</P>
<P>这是一段堆栈初始化的代码示例，其中只定义了三种模式的SP指针：</P>
<P>MRS&nbsp;&nbsp; R0,CPSR</P>
<P>BIC&nbsp;&nbsp;&nbsp; R0,R0,#MODEMASK&nbsp; 安全起见，屏蔽模式位以外的其他位</P>
<P>ORR&nbsp;&nbsp; R1,R0,#IRQMODE</P>
<P>MSR&nbsp;&nbsp; CPSR_cxfs,R1</P>
<P>LDR&nbsp;&nbsp; SP,=UndefStack</P>
<P>&nbsp;</P>
<P>ORR&nbsp;&nbsp; R1,R0,#FIQMODE</P>
<P>MSR&nbsp;&nbsp; CPSR_cxsf,R1</P>
<P>LDR&nbsp;&nbsp; SP,=FIQStack</P>
<P>&nbsp;</P>
<P>ORR&nbsp;&nbsp; R1,R0,#SVCMODE</P>
<P>MSR&nbsp;&nbsp; CPSR_cxsf,R1</P>
<P>LDR&nbsp;&nbsp; SP,=SVCStack</P>
<P><STRONG>4. 初始化有特殊要求的端口，设备</STRONG></P>
<P><STRONG>5. 初始化应用程序执行环境</STRONG></P>
<P>映像一开始总是存储在ROM／Flash里面的，其RO部分即可以在ROM／Flash里面执行，也可以转移到速度更快的RAM中执行；而RW和ZI这两部分是必须转移到可写的RAM里去。所谓应用程序执行环境的初始化，就是完成必要的从ROM到RAM的数据传输和内容清零。</P>
<P>下面是在ADS下，一种常用存储器模型的直接实现：</P>
<P>LDR&nbsp;&nbsp;&nbsp; r0,=|Image$$RO$$Limit| &nbsp;&nbsp;&nbsp;&nbsp; ;得到RW数据源的起始地址</P>
<P>LDR&nbsp;&nbsp;&nbsp; r1,=|Image$$RW$$Base| &nbsp;&nbsp;&nbsp;&nbsp; ;RW区在RAM里的执行区起始地址</P>
<P>LDR&nbsp;&nbsp;&nbsp; r2,=|Image$$ZI$$Base|&nbsp; &nbsp;&nbsp;&nbsp;&nbsp;&nbsp; ;ZI区在RAM里面的起始地址</P>
<P>CMP&nbsp;&nbsp;&nbsp; r0,r1&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; &nbsp;&nbsp;&nbsp;&nbsp; ;比较它们是否相等</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BEQ&nbsp;&nbsp;&nbsp; %F1</P>
<P>0&nbsp;&nbsp;&nbsp;&nbsp; CMP&nbsp;&nbsp;&nbsp; r1,r3</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; LDRCC&nbsp; r2,[r0],#4</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STRCC&nbsp; r2,[r1],#4</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BCC&nbsp;&nbsp;&nbsp; %B0</P>
<P>1&nbsp;&nbsp;&nbsp;&nbsp; LDR&nbsp;&nbsp;&nbsp; r1,=|Image$$ZI$$Limit|</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; MOV&nbsp;&nbsp; r2,#0</P>
<P>2&nbsp;&nbsp;&nbsp;&nbsp; CMP&nbsp;&nbsp;&nbsp; r3,r1</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; STRCC&nbsp; r2,[r3],#4</P>
<P>&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; BCC&nbsp;&nbsp;&nbsp; %B2</P>
<P>程序实现了RW数据的拷贝和ZI区域的清零功能。其中引用到的4个符号是由链接器第一输出的。</P>
<P>|Image$$RO$$Limit|：表示RO区末地址后面的地址，即RW数据源的起始地址</P>
<P>|Image$$RW$$Base|：RW区在RAM里的执行区起始地址，也就是编译器选项RW_Base指定的地址</P>
<P>|Image$$ZI$$Base|：ZI区在RAM里面的起始地址</P>
<P>|Image$$ZI$$Limit|：ZI区在RAM里面的结束地址后面的一个地址</P>
<P>程序先把ROM里|Image$$RO$$Limt|开始的RW初始数据拷贝到RAM里面|Image$$RW$$Base|开始的地址，当RAM这边的目标地址到达|Image$$ZI$$Base|后就表示RW区的结束和ZI区的开始，接下去就对这片ZI区进行清零操作，直到遇到结束地址|Image$$ZI$$Limit|</P>
<P><STRONG>6. 改变处理器模式</STRONG></P>
<P>因为在初始化过程中，许多操作需要在特权模式下才能进行（比如对CPSR的修改），所以要特别注意不能过早的进入用户模式。</P>
<P>内核级的中断使能也可以考虑在这一步进行。如果系统中另外存在一个专门的中断控制器，这么做总是安全的。</P>
<P><STRONG>7. 呼叫主应用程序</STRONG></P>
<P>当所有的系统初始化工作完成之后，就需要把程序流程转入主应用程序。最简单的一种情况是：</P>
<P>IMPORT main</P>
<P>B&nbsp;&nbsp;&nbsp;&nbsp;&nbsp; main</P>
<P>直接从启动代码跳转到应用程序的主函数入口，当然主函数名字可以由用户随便定义。</P>
<P>在ARM ADS环境中，还另外提供了一套系统级的呼叫机制。</P>
<P>IMPORT __main</P>
<P>B&nbsp;&nbsp;&nbsp;&nbsp; __main</P>
<P>__main()是编译系统提供的一个函数，负责完成库函数的初始化和初始化应用程序执行环境，最后自动跳转到main()函数。</P></TD></TR></TBODY></TABLE>]]></description>
</item>
</channel>
</rss>