﻿<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:trackback="http://madskills.com/public/xml/rss/module/trackback/" xmlns:wfw="http://wellformedweb.org/CommentAPI/" xmlns:slash="http://purl.org/rss/1.0/modules/slash/"><channel><title>BlogJava-一江春水向东流-文章分类-linux/UNIX 应用开发</title><link>http://www.blogjava.net/huyi2006/category/17677.html</link><description>                            做一个有思想的人,期待与每一位热爱思考的人交流,您的关注是对我最大的支持。</description><language>zh-cn</language><lastBuildDate>Tue, 12 Jun 2012 09:24:05 GMT</lastBuildDate><pubDate>Tue, 12 Jun 2012 09:24:05 GMT</pubDate><ttl>60</ttl><item><title>TCP长连接VS短连接 </title><link>http://www.blogjava.net/huyi2006/articles/380611.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Tue, 12 Jun 2012 07:38:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/380611.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/380611.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/380611.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/380611.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/380611.html</trackback:ping><description><![CDATA[1. TCP连接

当网络通信时采用TCP协议时，在真正的读写操作之前，server与client之间必须建立一个连接，当读写操作完成后，双方不再需要这个连接时它们可以释放这个连接，连接的建立是需要三次握手的，而释放则需要4次握手，所以说每个连接的建立都是需要资源消耗和时间消耗的

经典的三次握手示意图：



经典的四次握手关闭图：



2. TCP短连接

我们模拟一下TCP短连接的情况，client向server发起连接请求，server接到请求，然后双方建立连接。client向server发送消息，server回应client，然后一次读写就完成了，这时候双方任何一个都可以发起close操作，不过一般都是client先发起close操作。为什么呢，一般的server不会回复完client后立即关闭连接的，当然不排除有特殊的情况。从上面的描述看，短连接一般只会在client/server间传递一次读写操作

短连接的优点是：管理起来比较简单，存在的连接都是有用的连接，不需要额外的控制手段

3.TCP长连接

接下来我们再模拟一下长连接的情况，client向server发起连接，server接受client连接，双方建立连接。Client与server完成一次读写之后，它们之间的连接并不会主动关闭，后续的读写操作会继续使用这个连接。

首先说一下TCP/IP详解上讲到的TCP保活功能，保活功能主要为服务器应用提供，服务器应用希望知道客户主机是否崩溃，从而可以代表客户使用资源。如果客户已经消失，使得服务器上保留一个半开放的连接，而服务器又在等待来自客户端的数据，则服务器将应远等待客户端的数据，保活功能就是试图在服务器端检测到这种半开放的连接。

如果一个给定的连接在两小时内没有任何的动作，则服务器就向客户发一个探测报文段，客户主机必须处于以下4个状态之一：

客户主机依然正常运行，并从服务器可达。客户的TCP响应正常，而服务器也知道对方是正常的，服务器在两小时后将保活定时器复位。 
客户主机已经崩溃，并且关闭或者正在重新启动。在任何一种情况下，客户的TCP都没有响应。服务端将不能收到对探测的响应，并在75秒后超时。服务器总共发送10个这样的探测 ，每个间隔75秒。如果服务器没有收到一个响应，它就认为客户主机已经关闭并终止连接。 
客户主机崩溃并已经重新启动。服务器将收到一个对其保活探测的响应，这个响应是一个复位，使得服务器终止这个连接。 
客户机正常运行，但是服务器不可达，这种情况与2类似，TCP能发现的就是没有收到探查的响应。 
从上面可以看出，TCP保活功能主要为探测长连接的存活状况，不过这里存在一个问题，存活功能的探测周期太长，还有就是它只是探测TCP连接的存活，属于比较斯文的做法，遇到恶意的连接时，保活功能就不够使了。

在长连接的应用场景下，client端一般不会主动关闭它们之间的连接，Client与server之间的连接如果一直不关闭的话，会存在一个问题，随着客户端连接越来越多，server早晚有扛不住的时候，这时候server端需要采取一些策略，如关闭一些长时间没有读写事件发生的连接，这样可以避免一些恶意连接导致server端服务受损；如果条件再允许就可以以客户端机器为颗粒度，限制每个客户端的最大长连接数，这样可以完全避免某个蛋疼的客户端连累后端服务。

长连接和短连接的产生在于client和server采取的关闭策略，具体的应用场景采用具体的策略，没有十全十美的选择，只有合适的选择。

<img src ="http://www.blogjava.net/huyi2006/aggbug/380611.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2012-06-12 15:38 <a href="http://www.blogjava.net/huyi2006/articles/380611.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux下C语言实现文件传输的简单实例</title><link>http://www.blogjava.net/huyi2006/articles/263836.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 03 Apr 2009 15:06:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/263836.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/263836.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/263836.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/263836.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/263836.html</trackback:ping><description><![CDATA[
		<p>实例来自互联网，这段测试代码实现了基本的文件传输原理，没有实现错误处理。<br /><br />//////////////////////////////////////////////////////////////////////////////////////<br />// file_server.c  文件传输顺序服务器示例<br />//////////////////////////////////////////////////////////////////////////////////////<br />//本文件是服务器的代码<br />#include &lt;netinet/in.h&gt;    // for sockaddr_in<br />#include &lt;sys/types.h&gt;    // for socket<br />#include &lt;sys/socket.h&gt;    // for socket<br />#include &lt;stdio.h&gt;        // for printf<br />#include &lt;stdlib.h&gt;        // for exit<br />#include &lt;string.h&gt;        // for bzero<br />/*<br />#include &lt;sys/types.h&gt;<br />#include &lt;sys/stat.h&gt;<br />#include &lt;fcntl.h&gt;<br />#include &lt;unistd.h&gt;<br />*/<br />#define HELLO_WORLD_SERVER_PORT    6666 <br />#define LENGTH_OF_LISTEN_QUEUE  20<br />#define BUFFER_SIZE 1024<br />#define FILE_NAME_MAX_SIZE 512<br /><br />int main(int argc, char **argv)<br />{<br />    //设置一个socket地址结构server_addr,代表服务器internet地址, 端口<br />    struct sockaddr_in server_addr;<br />    bzero(&amp;server_addr,sizeof(server_addr)); //把一段内存区的内容全部设置为0<br />    server_addr.sin_family = AF_INET;<br />    server_addr.sin_addr.s_addr = htons(INADDR_ANY);<br />    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);<br /><br />    //创建用于internet的流协议(TCP)socket,用server_socket代表服务器socket<br />    int server_socket = socket(PF_INET,SOCK_STREAM,0);<br />    if( server_socket &lt; 0)<br />    {<br />        printf("Create Socket Failed!");<br />        exit(1);<br />    }<br />    <br />    //把socket和socket地址结构联系起来<br />    if( bind(server_socket,(struct sockaddr*)&amp;server_addr,sizeof(server_addr)))<br />    {<br />        printf("Server Bind Port : %d Failed!", HELLO_WORLD_SERVER_PORT); <br />        exit(1);<br />    }<br />    <br />    //server_socket用于监听<br />    if ( listen(server_socket, LENGTH_OF_LISTEN_QUEUE) )<br />    {<br />        printf("Server Listen Failed!"); <br />        exit(1);<br />    }<br />    while (1) //服务器端要一直运行<br />    {<br />        //定义客户端的socket地址结构client_addr<br />        struct sockaddr_in client_addr;<br />        socklen_t length = sizeof(client_addr);<br /><br />        //接受一个到server_socket代表的socket的一个连接<br />        //如果没有连接请求,就等待到有连接请求--这是accept函数的特性<br />        //accept函数返回一个新的socket,这个socket(new_server_socket)用于同连接到的客户的通信<br />        //new_server_socket代表了服务器和客户端之间的一个通信通道<br />        //accept函数把连接到的客户端信息填写到客户端的socket地址结构client_addr中<br />        int new_server_socket = accept(server_socket,(struct sockaddr*)&amp;client_addr,&amp;length);<br />        if ( new_server_socket &lt; 0)<br />        {<br />            printf("Server Accept Failed!\n");<br />            break;<br />        }<br />        <br />        char buffer[BUFFER_SIZE];<br />        bzero(buffer, BUFFER_SIZE);<br />        length = recv(new_server_socket,buffer,BUFFER_SIZE,0);//<font color="#ff0000">这里先接收客户端发来的要获取的文件名</font><br />        if (length &lt; 0)<br />        {<br />            printf("Server Recieve Data Failed!\n");<br />            break;<br />        }<br />        char file_name[FILE_NAME_MAX_SIZE+1];<br />        bzero(file_name, FILE_NAME_MAX_SIZE+1);<br />        strncpy(file_name, buffer, strlen(buffer)&gt;FILE_NAME_MAX_SIZE?FILE_NAME_MAX_SIZE:strlen(buffer));<br />//        int fp = open(file_name, O_RDONLY);<br />//        if( fp &lt; 0 )<br />        FILE * fp = fopen(file_name,"r");<br />        if(NULL == fp )<br />        {<br />            printf("File:\t%s Not Found\n", file_name);<br />        }<br />        else<br />        {<br />            bzero(buffer, BUFFER_SIZE);<br />            int file_block_length = 0;<br />//            while( (file_block_length = read(fp,buffer,BUFFER_SIZE))&gt;0)<br /><font color="#0000cc">            while( (file_block_length = fread(buffer,sizeof(char),BUFFER_SIZE,fp))&gt;0)<br />            {<br />                printf("file_block_length = %d\n",file_block_length);<br />                //发送buffer中的字符串到new_server_socket,实际是给客户端<br />                if(send(new_server_socket,buffer,file_block_length,0)&lt;0)<br />                {<br />                    printf("Send File:\t%s Failed\n", file_name);<br />                    break;<br />                }<br />                bzero(buffer, BUFFER_SIZE);<br />            }                                                                 //这段代码是循环读取文件的一段数据，在循环调用send，发送到客户端，这里强调一点的TCP每次接受最多是1024字节，多了就会分片，因此每次发送时尽量不要超过1024字节。</font><br />//            close(fp);<br />            fclose(fp);<br />            printf("File:\t%s Transfer Finished\n",file_name);<br />        }<br />        //关闭与客户端的连接<br />        close(new_server_socket);<br />    }<br />    //关闭监听用的socket<br />    close(server_socket);<br />    return 0;<br />}<br /><br /><br />//////////////////////////////////////////////////////////////////////////////////////<br />// file_client.c  文件传输客户端程序示例<br />//////////////////////////////////////////////////////////////////////////////////////<br />//本文件是客户机的代码<br />#include &lt;netinet/in.h&gt;    // for sockaddr_in<br />#include &lt;sys/types.h&gt;    // for socket<br />#include &lt;sys/socket.h&gt;    // for socket<br />#include &lt;stdio.h&gt;        // for printf<br />#include &lt;stdlib.h&gt;        // for exit<br />#include &lt;string.h&gt;        // for bzero<br />/*<br />#include &lt;sys/types.h&gt;<br />#include &lt;sys/stat.h&gt;<br />#include &lt;fcntl.h&gt;<br />#include &lt;unistd.h&gt;<br />*/<br /><br />#define HELLO_WORLD_SERVER_PORT    6666 <br />#define BUFFER_SIZE 1024<br />#define FILE_NAME_MAX_SIZE 512<br /><br />int main(int argc, char **argv)<br />{<br />    if (argc != 2)<br />    {<br />        printf("Usage: ./%s ServerIPAddress\n",argv[0]);<br />        exit(1);<br />    }<br /><br />    //设置一个socket地址结构client_addr,代表客户机internet地址, 端口<br />    struct sockaddr_in client_addr;<br />    bzero(&amp;client_addr,sizeof(client_addr)); //把一段内存区的内容全部设置为0<br />    client_addr.sin_family = AF_INET;    //internet协议族<br />    client_addr.sin_addr.s_addr = htons(INADDR_ANY);//INADDR_ANY表示自动获取本机地址<br />    client_addr.sin_port = htons(0);    //0表示让系统自动分配一个空闲端口<br />    //创建用于internet的流协议(TCP)socket,用client_socket代表客户机socket<br />    int client_socket = socket(AF_INET,SOCK_STREAM,0);<br />    if( client_socket &lt; 0)<br />    {<br />        printf("Create Socket Failed!\n");<br />        exit(1);<br />    }<br />    //把客户机的socket和客户机的socket地址结构联系起来<br />    if( bind(client_socket,(struct sockaddr*)&amp;client_addr,sizeof(client_addr)))<br />    {<br />        printf("Client Bind Port Failed!\n"); <br />        exit(1);<br />    }<br /><br />    //设置一个socket地址结构server_addr,代表服务器的internet地址, 端口<br />    struct sockaddr_in server_addr;<br />    bzero(&amp;server_addr,sizeof(server_addr));<br />    server_addr.sin_family = AF_INET;<br />    if(inet_aton(argv[1],&amp;server_addr.sin_addr) == 0) //服务器的IP地址来自程序的参数<br />    {<br />        printf("Server IP Address Error!\n");<br />        exit(1);<br />    }<br />    server_addr.sin_port = htons(HELLO_WORLD_SERVER_PORT);<br />    socklen_t server_addr_length = sizeof(server_addr);<br />    //向服务器发起连接,连接成功后client_socket代表了客户机和服务器的一个socket连接<br />    if(connect(client_socket,(struct sockaddr*)&amp;server_addr, server_addr_length) &lt; 0)<br />    {<br />        printf("Can Not Connect To %s!\n",argv[1]);<br />        exit(1);<br />    }<br /><br />    char file_name[FILE_NAME_MAX_SIZE+1];<br />    bzero(file_name, FILE_NAME_MAX_SIZE+1);<br />    printf("Please Input File Name On Server:\t");<br />    scanf("%s", file_name);<br />    <br />    char buffer[BUFFER_SIZE];<br />    bzero(buffer,BUFFER_SIZE);<br />    strncpy(buffer, file_name, strlen(file_name)&gt;BUFFER_SIZE?BUFFER_SIZE:strlen(file_name));<br />    //向服务器发送buffer中的数据<br />    send(client_socket,buffer,BUFFER_SIZE,0);<br /><br />//    int fp = open(file_name, O_WRONLY|O_CREAT);<br />//    if( fp &lt; 0 )<br />    FILE * fp = fopen(file_name,"w");<br />    if(NULL == fp )<br />    {<br />        printf("File:\t%s Can Not Open To Write\n", file_name);<br />        exit(1);<br />    }<br />    <br />    //从服务器接收数据到buffer中<br />    bzero(buffer,BUFFER_SIZE);<br />    int length = 0;<br /><font color="#0000cc">    while( length = recv(client_socket,buffer,BUFFER_SIZE,0))       //循环接收，再写到文件<br />    {<br />        if(length &lt; 0)<br />        {<br />            printf("Recieve Data From Server %s Failed!\n", argv[1]);<br />            break;<br />        }<br />//        int write_length = write(fp, buffer,length);<br />        int write_length = fwrite(buffer,sizeof(char),length,fp);<br />        if (write_length&lt;length)<br />        {<br />            printf("File:\t%s Write Failed\n", file_name);<br />            break;<br />        }<br />        bzero(buffer,BUFFER_SIZE);    <br />    }</font><br />    printf("Recieve File:\t %s From Server[%s] Finished\n",file_name, argv[1]);<br />    <br />    close(fp);<br />    //关闭socket<br />    close(client_socket);<br />    return 0;<br />}</p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/263836.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2009-04-03 23:06 <a href="http://www.blogjava.net/huyi2006/articles/263836.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下的多线程编程</title><link>http://www.blogjava.net/huyi2006/articles/256617.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Wed, 25 Feb 2009 06:53:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/256617.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/256617.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/256617.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/256617.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/256617.html</trackback:ping><description><![CDATA[一篇介绍Linux下的多线程编程的文章 <br />1 引言<br />　　线程（thread）技术早在60年代就被提出，但真正应用多线程到操作系统中去，是在80年代中期，solaris是这方面的佼佼者。传统的<br />Unix也支持线程的概念，但是在一个进程（process）中只允许有一个线程，这样多线程就意味着多进程。现在，多线程技术已经被许多操作系<br />统所支持，包括Windows/NT，当然，也包括Linux。<br />　　为什么有了进程的概念后，还要再引入线程呢？使用多线程到底有哪些好处？什么的系统应该选用多线程？我们首先必须回答这些问题。<br />　　使用多线程的理由之一是和进程相比，它是一种非常"节俭"的多任务操作方式。我们知道，在Linux系统下，启动一个新的进程必须分配给<br />它独立的地址空间，建立众多的数据表来维护它的代码段、堆栈段和数据段，这是一种"昂贵"的多任务工作方式。而运行于一个进程中的多个<br />线程，它们彼此之间使用相同的地址空间，共享大部分数据，启动一个线程所花费的空间远远小于启动一个进程所花费的空间，而且，线程间<br />彼此切换所需的时间也远远小于进程间切换所需要的时间。据统计，总的说来，一个进程的开销大约是一个线程开销的30倍左右，当然，在具<br />体的系统上，这个数据可能会有较大的区别。<br />　　使用多线程的理由之二是线程间方便的通信机制。对不同进程来说，它们具有独立的数据空间，要进行数据的传递只能通过通信的方式进<br />行，这种方式不仅费时，而且很不方便。线程则不然，由于同一进程下的线程之间共享数据空间，所以一个线程的数据可以直接为其它线程所<br />用，这不仅快捷，而且方便。当然，数据的共享也带来其他一些问题，有的变量不能同时被两个线程所修改，有的子程序中声明为static的数<br />据更有可能给多线程程序带来灾难性的打击，这些正是编写多线程程序时最需要注意的地方。<br />　　除了以上所说的优点外，不和进程比较，多线程程序作为一种多任务、并发的工作方式，当然有以下的优点：<br />　　1)<br />提高应用程序响应。这对图形界面的程序尤其有意义，当一个操作耗时很长时，整个系统都会等待这个操作，此时程序不会响应键盘、鼠标、<br />菜单的操作，而使用多线程技术，将耗时长的操作（time consuming）置于一个新的线程，可以避免这种尴尬的情况。<br />　　2) 使多CPU系统更加有效。操作系统会保证当线程数不大于CPU数目时，不同的线程运行于不同的CPU上。<br />　　3) 改善程序结构。一个既长又复杂的进程可以考虑分为多个线程，成为几个独立或半独立的运行部分，这样的程序会利于理解和修改。<br />　　下面我们先来尝试编写一个简单的多线程程序。<br />2 简单的多线程编程<br />　　Linux系统下的多线程遵循POSIX线程接口，称为pthread。编写Linux下的多线程程序，需要使用头文件pthread.h，连接时需要使用库libp<br />thread.a。顺便说一下，Linux下pthread的实现是通过系统调用clone（）来实现的。clone（）是Linux所特有的系统调用，它的使用方式类似<br />fork，关于clone（）的详细情况，有兴趣的读者可以去查看有关文档说明。下面我们展示一个最简单的多线程程序 example1.c。<br />/* example.c*/<br />#include &lt;stdio.h&gt;<br />#include &lt;pthread.h&gt;<br />void thread(void)<br />{<br />int i;<br />for(i=0;i&lt;3;i++)<br />printf("This is a pthread.n");<br />}<br />int main(void)<br />{<br />pthread_t id;<br />int i,ret;<br />ret=pthread_create(&amp;id,NULL,(void *) thread,NULL);<br />if(ret!=0){<br />printf ("Create pthread error!n");<br />exit (1);<br />}<br />for(i=0;i&lt;3;i++)<br />printf("This is the main process.n");<br />pthread_join(id,NULL);<br />return (0);<br />}<br />我们编译此程序：<br />gcc example1.c -lpthread -o example1<br />运行example1，我们得到如下结果：<br />This is the main process.<br />This is a pthread.<br />This is the main process.<br />This is the main process.<br />This is a pthread.<br />This is a pthread.<br />再次运行，我们可能得到如下结果：<br />This is a pthread.<br />This is the main process.<br />This is a pthread.<br />This is the main process.<br />This is a pthread.<br />This is the main process.<br />　　前后两次结果不一样，这是两个线程争夺CPU资源的结果。上面的示例中，我们使用到了两个函数，　　pthread_create和pthread_join，<br />并声明了一个pthread_t型的变量。<br />　　pthread_t在头文件/usr/include/bits/pthreadtypes.h中定义：<br />　　typedef unsigned long int pthread_t;<br />　　它是一个线程的标识符。函数pthread_create用来创建一个线程，它的原型为：<br />　　extern int pthread_create __P ((pthread_t *__thread, __const pthread_attr_t *__attr,<br />　　void *(*__start_routine) (void *), void *__arg));<br />　　第一个参数为指向线程标识符的指针，第二个参数用来设置线程属性，第三个参数是线程运行函数的起始地址，最后一个参数是运行函数<br />的参数。这里，我们的函数thread不需要参数，所以最后一个参数设为空指针。第二个参数我们也设为空指针，这样将生成默认属性的线程。<br />对线程属性的设定和修改我们将在下一节阐述。当创建线程成功时，函数返回0，若不为0则说明创建线程失败，常见的错误返回代码为EAGAIN<br />和EINVAL。前者表示系统限制创建新的线程，例如线程数目过多了；后者表示第二个参数代表的线程属性值非法。创建线程成功后，新创建的<br />线程则运行参数三和参数四确定的函数，原来的线程则继续运行下一行代码。<br />　　函数pthread_join用来等待一个线程的结束。函数原型为：<br />　　extern int pthread_join __P ((pthread_t __th, void **__thread_return));<br />　　第一个参数为被等待的线程标识符，第二个参数为一个用户定义的指针，它可以用来存储被等待线程的返回值。这个函数是一个线程阻塞<br />的函数，调用它的函数将一直等待到被等待的线程结束为止，当函数返回时，被等待线程的资源被收回。一个线程的结束有两种途径，一种是<br />象我们上面的例子一样，函数结束了，调用它的线程也就结束了；另一种方式是通过函数pthread_exit来实现。它的函数原型为：<br />　　extern void pthread_exit __P ((void *__retval)) __attribute__ ((__noreturn__));<br />　　唯一的参数是函数的返回代码，只要pthread_join中的第二个参数thread_return不是NULL，这个值将被传递给<br />thread_return。最后要说明的是，一个线程不能被多个线程等待，否则第一个接收到信号的线程成功返回，其余调用pthread_join的线程则返<br />回错误代码ESRCH。<br />　　在这一节里，我们编写了一个最简单的线程，并掌握了最常用的三个函数pthread_create，pthread_join和pthread_exit。下面，我们来<br />了解线程的一些常用属性以及如何设置这些属性。<br />3 修改线程的属性<br />　　在上一节的例子里，我们用pthread_create函数创建了一个线程，在这个线程中，我们使用了默认参数，即将该函数的第二个参数设为NUL<br />L。的确，对大多数程序来说，使用默认属性就够了，但我们还是有必要来了解一下线程的有关属性。<br />　　属性结构为pthread_attr_t，它同样在头文件/usr/include/pthread.h中定义，喜欢追根问底的人可以自己去查看。属性值不能直接设置<br />，须使用相关函数进行操作，初始化的函数为pthread_attr_init，这个函数必须在pthread_create函数之前调用。属性对象主要包括是否绑定<br />、是否分离、堆栈地址、堆栈大小、优先级。默认的属性为非绑定、非分离、缺省1M的堆栈、与父进程同样级别的优先级。<br />　　关于线程的绑定，牵涉到另外一个概念：轻进程（LWP：Light Weight<br />Process）。轻进程可以理解为内核线程，它位于用户层和系统层之间。系统对线程资源的分配、对线程的控制是通过轻进程来实现的，一个轻<br />进程可以控制一个或多个线程。默认状况下，启动多少轻进程、哪些轻进程来控制哪些线程是由系统来控制的，这种状况即称为非绑定的。绑<br />定状况下，则顾名思义，即某个线程固定的"绑"在一个轻进程之上。被绑定的线程具有较高的响应速度，这是因为CPU时间片的调度是面向轻进<br />程的，绑定的线程可以保证在需要的时候它总有一个轻进程可用。通过设置被绑定的轻进程的优先级和调度级可以使得绑定的线程满足诸如实<br />时反应之类的要求。<br />　　设置线程绑定状态的函数为pthread_attr_setscope，它有两个参数，第一个是指向属性结构的指针，第二个是绑定类型，它有两个取值：<br />PTHREAD_SCOPE_SYSTEM（绑定的）和PTHREAD_SCOPE_PROCESS（非绑定的）。下面的代码即创建了一个绑定的线程。<br />#include &lt;pthread.h&gt;<br />pthread_attr_t attr;<br />pthread_t tid;<br />/*初始化属性值，均设为默认值*/<br />pthread_attr_init(&amp;attr);<br />pthread_attr_setscope(&amp;attr, PTHREAD_SCOPE_SYSTEM);<br />pthread_create(&amp;tid, &amp;attr, (void *) my_function, NULL);<br />　　线程的分离状态决定一个线程以什么样的方式来终止自己。在上面的例子中，我们采用了线程的默认属性，即为非分离状态，这种情况下<br />，原有的线程等待创建的线程结束。只有当pthread_join（）函数返回时，创建的线程才算终止，才能释放自己占用的系统资源。而分离线程<br />不是这样子的，它没有被其他的线程所等待，自己运行结束了，线程也就终止了，马上释放系统资源。程序员应该根据自己的需要，选择适当<br />的分离状态。设置线程分离状态的函数为 pthread_attr_setdetachstate（pthread_attr_t *attr, int<br />detachstate）。第二个参数可选为PTHREAD_CREATE_DETACHED（分离线程）和 PTHREAD<br />_CREATE_JOINABLE（非分离线程）。这里要注意的一点是，如果设置一个线程为分离线程，而这个线程运行又非常快，它很可能在<br />pthread_create函数返回之前就终止了，它终止以后就可能将线程号和系统资源移交给其他的线程使用，这样调用pthread_create的线程就得<br />到了错误的线程号。要避免这种情况可以采取一定的同步措施，最简单的方法之一是可以在被创建的线程里调用<br />pthread_cond_timewait函数，让这个线程等待一会儿，留出足够的时间让函数pthread_create返回。设置一段等待时间，是在多线程编程里常<br />用的方法。但是注意不要使用诸如wait（）之类的函数，它们是使整个进程睡眠，并不能解决线程同步的问题。<br />　　另外一个可能常用的属性是线程的优先级，它存放在结构sched_param中。用函数pthread_attr_getschedparam和函数<br />pthread_attr_setschedparam进行存放，一般说来，我们总是先取优先级，对取得的值修改后再存放回去。下面即是一段简单的例子。<br />#include &lt;stdio.h&gt;<br />#include &lt;pthread.h&gt;<br />pthread_attr_t attr;<br />pthread_t tid;<br />sched_param param;<br />int newprio=20;<br />pthread_attr_init(&amp;attr);<br />pthread_attr_getschedparam(&amp;attr, &amp;para;m);<br />param.sched_priority=newprio;<br />pthread_attr_setschedparam(&amp;attr, &amp;para;m);<br />pthread_create(&amp;tid, &amp;attr, (void *)myfunction, myarg);<br />　　<br />4 线程的数据处理<br />　　和进程相比，线程的最大优点之一是数据的共享性，各个进程共享父进程处沿袭的数据段，可以方便的获得、修改数据。但这也给多线程<br />编程带来了许多问题。我们必须当心有多个不同的进程访问相同的变量。许多函数是不可重入的，即同时不能运行一个函数的多个拷贝（除非<br />使用不同的数据段）。在函数中声明的静态变量常常带来问题，函数的返回值也会有问题。因为如果返回的是函数内部静态声明的空间的地址<br />，则在一个线程调用该函数得到地址后使用该地址指向的数据时，别的线程可能调用此函数并修改了这一段数据。在进程中共享的变量必须用<br />关键字volatile来定义，这是为了防止编译器在优化时（如gcc中使用-OX参数）改变它们的使用方式。为了保护变量，我们必须使用信号量、<br />互斥等方法来保证我们对变量的正确使用。下面，我们就逐步介绍处理线程数据时的有关知识。<br />4.1 线程数据<br />　　在单线程的程序里，有两种基本的数据：全局变量和局部变量。但在多线程程序里，还有第三种数据类型：线程数据（TSD:<br />Thread-Specific<br />Data）。它和全局变量很象，在线程内部，各个函数可以象使用全局变量一样调用它，但它对线程外部的其它线程是不可见的。这种数据的必<br />要性是显而易见的。例如我们常见的变量errno，它返回标准的出错信息。它显然不能是一个局部变量，几乎每个函数都应该可以调用它；但它<br />又不能是一个全局变量，否则在<br />A线程里输出的很可能是B线程的出错信息。要实现诸如此类的变量，我们就必须使用线程数据。我们为每个线程数据创建一个键，它和这个键<br />相关联，在各个线程里，都使用这个键来指代线程数据，但在不同的线程里，这个键代表的数据是不同的，在同一个线程里，它代表同样的数<br />据内容。<br />　　和线程数据相关的函数主要有4个：创建一个键；为一个键指定线程数据；从一个键读取线程数据；删除键。<br />　　创建键的函数原型为：<br />　　extern int pthread_key_create __P ((pthread_key_t *__key,<br />　　void (*__destr_function) (void *)));<br />　　第一个参数为指向一个键值的指针，第二个参数指明了一个destructor函数，如果这个参数不为空，那么当每个线程结束时，系统将调用<br />这个函数来释放绑定在这个键上的内存块。这个函数常和函数pthread_once ((pthread_once_t*once_control, void (*initroutine)<br />(void)))一起使用，为了让这个键只被创建一次。函数pthread_once声明一个初始化函数，第一次调用pthread_once时它执行这个函数，以后<br />的调用将被它忽略。<br />　　在下面的例子中，我们创建一个键，并将它和某个数据相关联。我们要定义一个函数createWindow，这个函数定义一个图形窗口（数据类<br />型为Fl_Window *，这是图形界面开发工具FLTK中的数据类型）。由于各个线程都会调用这个函数，所以我们使用线程数据。<br />/* 声明一个键*/<br />pthread_key_t myWinKey;<br />/* 函数 createWindow */<br />void createWindow ( void ) {<br />Fl_Window * win;<br />static pthread_once_t once= PTHREAD_ONCE_INIT;<br />/* 调用函数createMyKey，创建键*/<br />pthread_once ( &amp; once, createMyKey) ;<br />/*win指向一个新建立的窗口*/<br />win=new Fl_Window( 0, 0, 100, 100, "MyWindow");<br />/* 对此窗口作一些可能的设置工作，如大小、位置、名称等*/<br />setWindow(win);<br />/* 将窗口指针值绑定在键myWinKey上*/<br />pthread_setpecific ( myWinKey, win);<br />}<br />/* 函数 createMyKey，创建一个键，并指定了destructor */<br />void createMyKey ( void ) {<br />pthread_keycreate(&amp;myWinKey, freeWinKey);<br />}<br />/* 函数 freeWinKey，释放空间*/<br />void freeWinKey ( Fl_Window * win){<br />delete win;<br />}<br />　　这样，在不同的线程中调用函数createMyWin，都可以得到在线程内部均可见的窗口变量，这个变量通过函数<br />pthread_getspecific得到。在上面的例子中，我们已经使用了函数pthread_setspecific来将线程数据和一个键绑定在一起。这两个函数的原<br />型如下：<br />　　extern int pthread_setspecific __P ((pthread_key_t __key,__const void *__pointer));<br />　　extern void *pthread_getspecific __P ((pthread_key_t __key));<br />　　这两个函数的参数意义和使用方法是显而易见的。要注意的是，用pthread_setspecific为一个键指定新的线程数据时，必须自己释放原有<br />的线程数据以回收空间。这个过程函数pthread_key_delete用来删除一个键，这个键占用的内存将被释放，但同样要注意的是，它只释放键占<br />用的内存，并不释放该键关联的线程数据所占用的内存资源，而且它也不会触发函数pthread_key_create中定义的destructor函数。线程数据<br />的释放必须在释放键之前完成。<br />4.2 互斥锁<br />　　互斥锁用来保证一段时间内只有一个线程在执行一段代码。必要性显而易见：假设各个线程向同一个文件顺序写入数据，最后得到的结果<br />一定是灾难性的。<br />　　我们先看下面一段代码。这是一个读/写程序，它们公用一个缓冲区，并且我们假定一个缓冲区只能保存一条信息。即缓冲区只有两个状态<br />：有信息或没有信息。<br />void reader_function ( void );<br />void writer_function ( void );<br />char buffer;<br />int buffer_has_item=0;<br />pthread_mutex_t mutex;<br />struct timespec delay;<br />void main ( void ){<br />pthread_t reader;<br />/* 定义延迟时间*/<br />delay.tv_sec = 2;<br />delay.tv_nec = 0;<br />/* 用默认属性初始化一个互斥锁对象*/<br />pthread_mutex_init (&amp;mutex,NULL);<br />pthread_create(&amp;reader, pthread_attr_default, (void *)&amp;reader_function), NULL);<br />writer_function( );<br />}<br />void writer_function (void){<br />while(1){<br />/* 锁定互斥锁*/<br />pthread_mutex_lock (&amp;mutex);<br />if (buffer_has_item==0){<br />buffer=make_new_item( );<br />buffer_has_item=1;<br />}<br />/* 打开互斥锁*/<br />pthread_mutex_unlock(&amp;mutex);<br />pthread_delay_np(&amp;delay);<br />}<br />}<br />void reader_function(void){<br />while(1){<br />pthread_mutex_lock(&amp;mutex);<br />if(buffer_has_item==1){<br />consume_item(buffer);<br />buffer_has_item=0;<br />}<br />pthread_mutex_unlock(&amp;mutex);<br />pthread_delay_np(&amp;delay);<br />}<br />}<br />　　这里声明了互斥锁变量mutex，结构pthread_mutex_t为不公开的数据类型，其中包含一个系统分配的属性对象。函数<br />pthread_mutex_init用来生成一个互斥锁。NULL参数表明使用默认属性。如果需要声明特定属性的互斥锁，须调用函数<br />pthread_mutexattr_init。函数pthread_mutexattr_setpshared和函数<br />pthread_mutexattr_settype用来设置互斥锁属性。前一个函数设置属性pshared，它有两个取值，<br />PTHREAD_PROCESS_PRIVATE和PTHREAD_PROCESS_SHARED。前者用来不同进程中的线程同步，后者用于同步本进程的不同线程。在上面的例子中，<br />我们使用的是默认属性PTHREAD_PROCESS_<br />PRIVATE。后者用来设置互斥锁类型，可选的类型有PTHREAD_MUTEX_NORMAL、PTHREAD_MUTEX_ERRORCHECK、<br />PTHREAD_MUTEX_RECURSIVE和PTHREAD _MUTEX_DEFAULT。它们分别定义了不同的上所、解锁机制，一般情况下，选用最后一个默认属性。<br />　　pthread_mutex_lock声明开始用互斥锁上锁，此后的代码直至调用pthread_mutex_unlock为止，均被上锁，即同一时间只能被一个线程调<br />用执行。当一个线程执行到pthread_mutex_lock处时，如果该锁此时被另一个线程使用，那此线程被阻塞，即程序将等待到另一个线程释放此<br />互斥锁。在上面的例子中，我们使用了pthread_delay_np函数，让线程睡眠一段时间，就是为了防止一个线程始终占据此函数。<br />　　上面的例子非常简单，就不再介绍了，需要提出的是在使用互斥锁的过程中很有可能会出现死锁：两个线程试图同时占用两个资源，并按<br />不同的次序锁定相应的互斥锁，例如两个线程都需要锁定互斥锁1和互斥锁2，a线程先锁定互斥锁1，b线程先锁定互斥锁2，这时就出现了死锁<br />。此时我们可以使用函数<br />pthread_mutex_trylock，它是函数pthread_mutex_lock的非阻塞版本，当它发现死锁不可避免时，它会返回相应的信息，程序员可以针对死锁<br />做出相应的处理。另外不同的互斥锁类型对死锁的处理不一样，但最主要的还是要程序员自己在程序设计注意这一点。<br />4.3 条件变量<br />　　前一节中我们讲述了如何使用互斥锁来实现线程间数据的共享和通信，互斥锁一个明显的缺点是它只有两种状态：锁定和非锁定。而条件<br />变量通过允许线程阻塞和等待另一个线程发送信号的方法弥补了互斥锁的不足，它常和互斥锁一起使用。使用时，条件变量被用来阻塞一个线<br />程，当条件不满足时，线程往往解开相应的互斥锁并等待条件发生变化。一旦其它的某个线程改变了条件变量，它将通知相应的条件变量唤醒<br />一个或多个正被此条件变量阻塞的线程。这些线程将重新锁定互斥锁并重新测试条件是否满足。一般说来，条件变量被用来进行线承间的同步<br />。<br />　　条件变量的结构为pthread_cond_t，函数pthread_cond_init（）被用来初始化一个条件变量。它的原型为：<br />　　extern int pthread_cond_init __P ((pthread_cond_t *__cond,__const pthread_condattr_t *__cond_attr));<br />　　其中cond是一个指向结构pthread_cond_t的指针，cond_attr是一个指向结构pthread_condattr_t的指针。结构pthread_condattr_t是条件<br />变量的属性结构，和互斥锁一样我们可以用它来设置条件变量是进程内可用还是进程间可用，默认值是 PTHREAD_<br />PROCESS_PRIVATE，即此条件变量被同一进程内的各个线程使用。注意初始化条件变量只有未被使用时才能重新初始化或被释放。释放一个条件<br />变量的函数为pthread_cond_ destroy（pthread_cond_t cond）。　<br />　　函数pthread_cond_wait（）使线程阻塞在一个条件变量上。它的函数原型为：<br />　　extern int pthread_cond_wait __P ((pthread_cond_t *__cond,<br />　　pthread_mutex_t *__mutex));<br />　　线程解开mutex指向的锁并被条件变量cond阻塞。线程可以被函数pthread_cond_signal和函数<br />pthread_cond_broadcast唤醒，但是要注意的是，条件变量只是起阻塞和唤醒线程的作用，具体的判断条件还需用户给出，例如一个变量是否<br />为0等等，这一点我们从后面的例子中可以看到。线程被唤醒后，它将重新检查判断条件是否满足，如果还不满足，一般说来线程应该仍阻塞在<br />这里，被等待被下一次唤醒。这个过程一般用while语句实现。<br />　　另一个用来阻塞线程的函数是pthread_cond_timedwait（），它的原型为：<br />　　extern int pthread_cond_timedwait __P ((pthread_cond_t *__cond,<br />　　pthread_mutex_t *__mutex, __const struct timespec *__abstime));<br />　　它比函数pthread_cond_wait（）多了一个时间参数，经历abstime段时间后，即使条件变量不满足，阻塞也被解除。<br />　　函数pthread_cond_signal（）的原型为：<br />　　extern int pthread_cond_signal __P ((pthread_cond_t *__cond));<br />　　它用来释放被阻塞在条件变量cond上的一个线程。多个线程阻塞在此条件变量上时，哪一个线程被唤醒是由线程的调度策略所决定的。要<br />注意的是，必须用保护条件变量的互斥锁来保护这个函数，否则条件满足信号又可能在测试条件和调用pthread_cond_wait函数之间被发出，从<br />而造成无限制的等待。下面是使用函数pthread_cond_wait（）和函数pthread_cond_signal（）的一个简单的例子。<br />pthread_mutex_t count_lock;<br />pthread_cond_t count_nonzero;<br />unsigned count;<br />decrement_count　() {<br />pthread_mutex_lock (&amp;count_lock);<br />while(count==0)<br />pthread_cond_wait( &amp;count_nonzero, &amp;count_lock);<br />count=count -1;<br />pthread_mutex_unlock (&amp;count_lock);<br />}<br />increment_count(){<br />pthread_mutex_lock(&amp;count_lock);<br />if(count==0)<br />pthread_cond_signal(&amp;count_nonzero);<br />count=count+1;<br />pthread_mutex_unlock(&amp;count_lock);<br />}<br />　　count值为0时， decrement函数在pthread_cond_wait处被阻塞，并打开互斥锁count_lock。此时，当调用到函数<br />increment_count时，pthread_cond_signal（）函数改变条件变量，告知decrement_count（）停止阻塞。读者可以试着让两个线程分别运行这<br />两个函数，看看会出现什么样的结果。<br />　　函数pthread_cond_broadcast（pthread_cond_t<br />*cond）用来唤醒所有被阻塞在条件变量cond上的线程。这些线程被唤醒后将再次竞争相应的互斥锁，所以必须小心使用这个函数。<br />4.4 信号量<br />　　信号量本质上是一个非负的整数计数器，它被用来控制对公共资源的访问。当公共资源增加时，调用函数sem_post（）增加信号量。只有<br />当信号量值大于０时，才能使用公共资源，使用后，函数sem_wait（）减少信号量。函数sem_trywait（）和函数pthread_<br />mutex_trylock（）起同样的作用，它是函数sem_wait（）的非阻塞版本。下面我们逐个介绍和信号量有关的一些函数，它们都在头文件<br />/usr/include/semaphore.h中定义。<br />　　信号量的数据类型为结构sem_t，它本质上是一个长整型的数。函数sem_init（）用来初始化一个信号量。它的原型为：<br />　　extern int sem_init __P ((sem_t *__sem, int __pshared, unsigned int __value));<br />　　sem为指向信号量结构的一个指针；pshared不为０时此信号量在进程间共享，否则只能为当前进程的所有线程共享；value给出了信号量的<br />初始值。<br />　　函数sem_post( sem_t *sem<br />)用来增加信号量的值。当有线程阻塞在这个信号量上时，调用这个函数会使其中的一个线程不在阻塞，选择机制同样是由线程的调度策略决定<br />的。<br />　　函数sem_wait( sem_t *sem<br />)被用来阻塞当前线程直到信号量sem的值大于0，解除阻塞后将sem的值减一，表明公共资源经使用后减少。函数sem_trywait ( sem_t *sem<br />)是函数sem_wait（）的非阻塞版本，它直接将信号量sem的值减一。<br />　　函数sem_destroy(sem_t *sem)用来释放信号量sem。<br />　　下面我们来看一个使用信号量的例子。在这个例子中，一共有4个线程，其中两个线程负责从文件读取数据到公共的缓冲区，另两个线程从<br />缓冲区读取数据作不同的处理（加和乘运算）。<br />/* File sem.c */<br />#include<br />#include<br />#include<br />#define MAXSTACK 100<br />int stack[MAXSTACK][2];<br />int size=0;<br />sem_t sem;<br />/* 从文件1.dat读取数据，每读一次，信号量加一*/<br />void ReadData1(void){<br />FILE *fp=fopen("1.dat","r");<br />while(!feof(fp)){<br />fscanf(fp,"%d %d",&amp;stack[size][0],&amp;stack[size][1]);<br />sem_post(&amp;sem);<br />++size;<br />}<br />fclose(fp);<br />}<br />/*从文件2.dat读取数据*/<br />void ReadData2(void){<br />FILE *fp=fopen("2.dat","r");<br />while(!feof(fp)){<br />fscanf(fp,"%d %d",&amp;stack[size][0],&amp;stack[size][1]);<br />sem_post(&amp;sem);<br />++size;<br />}<br />fclose(fp);<br />}<br />/*阻塞等待缓冲区有数据，读取数据后，释放空间，继续等待*/<br />void HandleData1(void){<br />while(1){<br />sem_wait(&amp;sem);<br />printf("Plus:%d+%d=%dn",stack[size][0],stack[size][1],<br />stack[size][0]+stack[size][1]);<br />--size;<br />}<br />}<br />void HandleData2(void){<br />while(1){<br />sem_wait(&amp;sem);<br />printf("Multiply:%d*%d=%dn",stack[size][0],stack[size][1],<br />stack[size][0]*stack[size][1]);<br />--size;<br />}<br />}<br />int main(void){<br />pthread_t t1,t2,t3,t4;<br />sem_init(&amp;sem,0,0);<br />pthread_create(&amp;t1,NULL,(void *)HandleData1,NULL);<br />pthread_create(&amp;t2,NULL,(void *)HandleData2,NULL);<br />pthread_create(&amp;t3,NULL,(void *)ReadData1,NULL);<br />pthread_create(&amp;t4,NULL,(void *)ReadData2,NULL);<br />/* 防止程序过早退出，让它在此无限期等待*/<br />pthread_join(t1,NULL);<br />}<br />　　在Linux下，我们用命令gcc -lpthread sem.c -o sem生成可执行文件sem。<br />我们事先编辑好数据文件1.dat和2.dat，假设它们的内容分别为1 2 3 4 5 6 7 8 9 10和 -1 -2 -3 -4 -5 -6 -7 -8 -9 -10<br />，我们运行sem，得到如下的结果：<br />Multiply:-1*-2=2<br />Plus:-1+-2=-3<br />Multiply:9*10=90<br />Plus:-9+-10=-19<br />Multiply:-7*-8=56<br />Plus:-5+-6=-11<br />Multiply:-3*-4=12<br />Plus:9+10=19<br />Plus:7+8=15<br />Plus:5+6=11<br />　　从中我们可以看出各个线程间的竞争关系。而数值并未按我们原先的顺序显示出来这是由于size这个数值被各个线程任意修改的缘故。这<br />也往往是多线程编程要注意的问题。<br />5 小结<br />　　多线程编程是一个很有意思也很有用的技术，使用多线程技术的网络蚂蚁是目前最常用的下载工具之一，使用多线程技术的grep比单线程<br />的grep要快上几倍，类似的例子还有很多。希望大家能用多线程技术写出高效实用的好程序来。<br />本文出自:http://www.china-pub.com 作者: 姚继锋 (2001-08-11 09:05:00)<img src ="http://www.blogjava.net/huyi2006/aggbug/256617.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2009-02-25 14:53 <a href="http://www.blogjava.net/huyi2006/articles/256617.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>BT种子文件格式</title><link>http://www.blogjava.net/huyi2006/articles/241632.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Thu, 20 Nov 2008 06:08:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/241632.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/241632.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/241632.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/241632.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/241632.html</trackback:ping><description><![CDATA[
		<p align="left">BT种子文件使用了一种叫bencoding的编码方法来保存数据。<br />bencoding现有四种类型的数据：srings(字符串)，integers(整数)，lists(列表)，dictionaries(字典)<br />编码规则如下：<br />strings(字符串)编码为：&lt;字符串长度&gt;：&lt;字符串&gt;<br />例如： 4:test 表示为字符串"test"<br /> 4:例子 表示为字符串“例子”<br />字符串长度单位为字节<br />没开始或结束标记</p>
		<p>integers(整数)编码为：i&lt;整数&gt;e<br />开始标记i，结束标记为e<br />例如： i1234e 表示为整数1234<br /> i-1234e 表示为整数-1234<br />整数没有大小限制<br /> i0e 表示为整数0<br /> i-0e 为非法<br />以0开头的为非法如： i01234e 为非法</p>
		<p>lists(列表)编码为：l&lt;bencoding编码类型&gt;e<br />开始标记为l,结束标记为e<br />列表里可以包含任何bencoding编码类型，包括整数，字符串，列表，字典。<br />例如： l4:test5abcdee 表示为二个字符串["test","abcde"]</p>
		<p>dictionaries(字典)编码为d&lt;bencoding字符串&gt;&lt;bencoding编码类型&gt;e<br />开始标记为d,结束标记为e<br />关键字必须为bencoding字符串<br />值可以为任何bencoding编码类型<br />例如： d3:agei20ee 表示为{"age"=20}<br /> d4:path3:C:\8:filename8:test.txte 表示为{"path"="C:\","filename"="test.txt"}</p>
		<p>具体文件结构如下：<br />全部内容必须都为bencoding编码类型。<br />整个文件为一个字典结构,包含如下关键字<br />announce:tracker服务器的URL(字符串)<br />announce-list(可选):备用tracker服务器列表(列表)<br />creation date(可选):种子创建的时间，Unix标准时间格式，从1970 1月1日 00:00:00到创建时间的秒数(整数)<br />comment(可选):备注(字符串)<br />created by(可选):创建人或创建程序的信息(字符串)<br />info:一个字典结构，包含文件的主要信息，为分二种情况：单文件结构或多文件结构<br />单文件结构如下：<br />          length:文件长度，单位字节(整数)<br />          md5sum(可选)：长32个字符的文件的MD5校验和，BT不使用这个值，只是为了兼容一些程序所保留!(字符串)<br />          name:文件名(字符串)<br />          piece length:每个块的大小，单位字节(整数)<br />          pieces:每个块的20个字节的SHA1 Hash的值(二进制格式)<br />多文件结构如下：<br />          files:一个字典结构<br />                 length:文件长度，单位字节(整数)<br />                 md5sum(可选):同单文件结构中相同<br />                 path:文件的路径和名字，是一个列表结构，如\test\test.txt 列表为l4:test8test.txte<br />          name:最上层的目录名字(字符串)<br />          piece length:同单文件结构中相同<br />          pieces:同单文件结构中相同 <br />实例：<br />用记事本打开一个.torrent可以看来类似如下内容<br />d8:announce35:http://www.manfen.net:7802/announce13:creation datei1076675108e4:infod6:lengthi17799e4:name62:MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent12:piece lengthi32768e6:pieces20:?W &#x6;?躐?緕排T酆ee</p>
		<p>很容易看出<br />announce＝<a href="http://www.chinaitpower.com/A/2002-03-25/&quot;http://www.manfen.net:7802/announce&quot;">http://www.manfen.net:7802/announce</a><br />creation date＝1076675108秒(02/13/04 20:25:08)<br />文件名=MICROSOFT.WINDOWS.2000.AND.NT4.SOURCE.CODE-SCENELEADER.torrent<br />文件大小＝17799字节<br />文件块大小＝32768字节<br /></p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/241632.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-11-20 14:08 <a href="http://www.blogjava.net/huyi2006/articles/241632.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>函数指针作为结构体成员，实现函数注册</title><link>http://www.blogjava.net/huyi2006/articles/233710.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 11 Oct 2008 02:24:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/233710.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/233710.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/233710.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/233710.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/233710.html</trackback:ping><description><![CDATA[
		<p>有时我们需要将函数作为结构体的成员，模拟C++类的情形，可应用于方法注册。<br />#include &lt;stdio.h&gt;</p>
		<p>struct a<br />{<br />    void (*func)(char *);<br />};</p>
		<p>void hello(char *name)<br />{<br />    printf ("hello %s\n",name);<br />}</p>
		<p>int main()<br />{<br />    struct a a1;<br />    a1.func = hello;<br />    a1.func("illusion");<br />    system("PAUSE");<br />    return 0;<br />}</p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/233710.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-10-11 10:24 <a href="http://www.blogjava.net/huyi2006/articles/233710.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux用Vim+Taglist+Ctags阅读编辑代码</title><link>http://www.blogjava.net/huyi2006/articles/190989.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 05 Apr 2008 15:22:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/190989.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/190989.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/190989.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/190989.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/190989.html</trackback:ping><description><![CDATA[
		<span class="a14c" id="zoom"> 
<p style="TEXT-INDENT: 2em">其实这套组合很实用了 基本上到了不用鼠标的source insight境界了，最重要的是可以在text模式下运行 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">使用的平台是Fedora 8 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">Vim和Ctags在F8安装完后系统已经具备 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">Taglist需要自己下载 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">1.下载一个Taglist的zip文件，然后解压缩，将taglist.vim复制到~/.vim/plugin目录下。 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">2.修改~/.vim/plugin/taglist.vim 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">在 if !exists('loaded_taglist')上面加入 
</p><p style="TEXT-INDENT: 2em">let Tlist_Ctags_Cmd="/usr/bin/ctags" 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">结果为： 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em"></p><center><ccid_nobr><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="black" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid_code>" Line continuation used here
let s:cpo_save = &amp;cpo
set cpo&amp;vim

let Tlist_Ctags_Cmd="/usr/bin/ctags"

if !exists('loaded_taglist')
" First time loading the taglist plugin
"
" To speed up the loading of Vim, the taglist plugin uses autoload
" mechanism to load the taglist functions.
" Only define the configuration variables, user commands and some
" auto-commands and finish sourcing the file

" The taglist plugin requires the built-in Vim system() function. If this
" function is not available, then don't load the plugin.
if !exists('*system')
echomsg 'Taglist: Vim system() built-in function is not available. ' .
\ 'Plugin is not loaded.'
let loaded_taglist = 'no'
let &amp;cpo = s:cpo_save
finish
endif</ccid_code></pre></td></tr></tbody></table></ccid_nobr></center><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">此时Ctags和Taglist已经结合起来。 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">3.在相应的源码目录运行ctags -R产生相应的tags文件 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">4.将tags文件在vim运行时导入。可以修改~/.vimrc，以后每次启动vim将自动导入此tags文件 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">:set tags=/root/develop/honeyids/tags 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">并设置语法高亮 
</p><p style="TEXT-INDENT: 2em">syntax enable 
</p><p style="TEXT-INDENT: 2em">syntax on 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">5.运行vim， 激活Taglist时用:TaglistToggle命令。在左边的tags区域和正常编辑区域切换时用ctrl+2个w。 
</p><p style="TEXT-INDENT: 2em"></p><p style="TEXT-INDENT: 2em">6.使用ctags时， ctrl+]可查看函数的定义。 ctrl+o返回源文件。 </p></span>
<img src ="http://www.blogjava.net/huyi2006/aggbug/190989.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-04-05 23:22 <a href="http://www.blogjava.net/huyi2006/articles/190989.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 共享内存编程</title><link>http://www.blogjava.net/huyi2006/articles/190846.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 04 Apr 2008 15:55:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/190846.html</guid><description><![CDATA[
		<p>共享内存原型<br />shmid_ds结构<br />struct shmid_ds{<br />    struct ipc_perm    shm_perm;<br />    size_t                   shm_segsz;<br />    pid_t                    shm_lpid;<br />    pid_t                    shm_cpid;<br />    shmatt_t               shm_nattch;<br />    time_t                   shm_atime;<br />    time_t                   shm_dtime;<br />    time_t                   shm_ctime;<br />    .<br />};<br /><br />#include &lt;sys/shm.h&gt;<br />int shmget (key_t  key, size_t size, int flag);<br />     成功返回共享存储ID，错误返回-1<br />int shmctl (int shmid, int cmd, struct shmid_ds,*buf);<br />     cmd有 IPC_STAT, IPC_SET, IPC_RMID, SHM_LOCK, SHM_UNLOCK<br /><br />连接到地址空间<br />void *shmat (int shmid ,const void *addr, int flag);<br /><br />对共享内存操作结束时，脱离该段<br />int shmdt (void *addr);<br /><br /></p>
		<p>要运行程序，需要在当前目录下建立一个share文件，share是一个空文件，没有任何意义，只是函数ftok需要一个文件名作参数，ftok另一个参数可以为任何数字。</p>
		<p>程序运行后，分为父子进程，子进程申请共享内存，然后等待父进程继续执行，父进程首先等待子进程申请到共享内存标识，然后输出共享内存中的内容，为了演示共享内存可以随时更新，程序中在子进程里产生随机数写入共享内存供父进程读取。<br />代码如下：</p>
		<p>#include&lt;stdio.h&gt;<br />#include&lt;unistd.h&gt;<br />#include&lt;stdlib.h&gt;<br />#include&lt;string.h&gt;</p>
		<p>#include&lt;time.h&gt;<br />#include&lt;signal.h&gt;<br />#include&lt;sys/ipc.h&gt;<br />#include&lt;sys/shm.h&gt;<br />#include&lt;sys/types.h&gt;</p>
		<p>int shmID;<br />char * buf;<br /> <br />void finalize(int signo)<br />{<br /> shmctl(shmID,IPC_RMID,NULL);<br /> <br /> exit(0);<br />}</p>
		<p>int main()<br />{<br /> int i = 1;  <br /> key_t shmKey;<br /> <br /> signal(SIGINT,finalize);<br /> signal(SIGTERM,finalize);<br /> <br /> if(fork() == 0) //子进程<br /> {  <br />  shmKey = ftok("share",16);  //可以使用任何大于0的数字，如果名字和数相同，则产生的key相同。<br />  if(shmKey == -1)<br />  {<br />   printf("创建key出错\n");<br />   exit(-1);<br />  }<br />  <br />  shmID = shmget(shmKey,20,IPC_CREAT | IPC_EXCL | 0666);<br />  if(shmID == -1)<br />  {<br />   printf("创建共享标识出错\n");<br />   exit(-1);<br />  }<br />  <br />  sleep(2); //等待父进程执行，好显示第一行为空。<br />  while(1)<br />  {<br />   buf = (char *)shmat(shmID,NULL,0);<br />   srandom(time(NULL));<br />   sprintf(buf,"%d",random()); <br />   shmdt(buf);  <br />  }<br /> }<br /> else  //父进程<br /> {<br />  sleep(1); //让子进程先执行，以建立内存映射。<br />  <br />  shmKey = ftok("share",16);  //可以使用任何大于0的数字，如果名字和数相同，则产生的key相同。<br />  if(shmKey == -1)<br />  {<br />   printf("创建key出错\n");<br />   exit(-1);<br />  }<br />  <br />  shmID = shmget(shmKey,20,0); //0表示如果shmKey映射的不存在则报错。<br />  if(shmID == -1)<br />  {<br />   printf("引用共享标识出错\n");<br />   exit(-1);<br />  }<br />  <br />  while(1)<br />  { <br />   buf = (char *)shmat(shmID,NULL,0);<br />   printf("%d. 现在共享内存中的内容为：%s\n",i++,buf);<br />   shmdt(buf);<br />   sleep(1);<br />  }<br /> } </p>
		<p> return 0;<br />}<br /></p>
		<p>
				<br /> </p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/190846.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-04-04 23:55 <a href="http://www.blogjava.net/huyi2006/articles/190846.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 消息队列编程</title><link>http://www.blogjava.net/huyi2006/articles/190843.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 04 Apr 2008 15:39:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/190843.html</guid><description><![CDATA[
		<p>函数原型<br /><br />#include &lt;sys/msg/h&gt;<br />int msgget (key_t key, int flag)<br />int msgctl (int msgid, int cmd , sruct msgid_ds *buf);<br />int msgsnd (int msgid, const void *ptr, size_t nbyes, long type, int flag);<br />int msgrcv (int msgid, void *ptr, size_t nbytes, long type, int flag);<br /><br />文件msg为空文件，可以为任何内容，这里只是为了ftok函数使用。程序通过建立消息队列，完成进程间通信，注意msgrcv的第四个参数为消息类型，他定义了从队列中取消息的类型。</p>
		<p>
				<br />下面是msgLucy.c,是建立消息队列的</p>
		<p>#include&lt;sys/ipc.h&gt;<br />#include&lt;sys/msg.h&gt;<br />#include&lt;sys/stat.h&gt;<br />#include&lt;sys/types.h&gt;</p>
		<p>#include&lt;stdio.h&gt;<br />#include&lt;fcntl.h&gt;<br />#include&lt;signal.h&gt;<br />#include&lt;stdlib.h&gt;<br />#include&lt;string.h&gt;</p>
		<p>#define PROJID 0xFF<br />#define LUCY 1<br />#define PETER 2</p>
		<p>int mqid;</p>
		<p>void terminate_handler(int signo)<br />{<br /> msgctl(mqid,IPC_RMID,NULL);<br /> exit(0);<br />}</p>
		<p>int main()<br />{<br /> char filenm[] = "msg";<br /> key_t mqkey;<br /> struct msgbuf <br /> {<br />      long mtype;      /* message type, must be &gt; 0 */<br />    char mtext[256];  /* message data */<br />   }msg;<br /> int ret;</p>
		<p> mqkey = ftok(filenm,PROJID);<br /> if(mqkey == -1) <br /> {<br />  perror("ftok error: ");<br />  exit(-1);<br /> }</p>
		<p> mqid = msgget(mqkey,IPC_CREAT | IPC_EXCL | 0666);<br /> if(mqid == -1) <br /> {<br />  perror("msgget error: ");<br />  exit(-1);<br /> }</p>
		<p> signal(SIGINT, terminate_handler);<br /> signal(SIGTERM, terminate_handler);</p>
		<p> while(1) <br /> {<br />  printf("Lucy: ");<br />  fgets(msg.mtext, 256, stdin);<br />  if (strncmp("quit", msg.mtext, 4) == 0) <br />  {<br />   msgctl(mqid,IPC_RMID,NULL);<br />   exit(0);<br />  }<br />  msg.mtext[strlen(msg.mtext)-1] = '\0';<br />  msg.mtype = LUCY;<br />  msgsnd(mqid,&amp;msg,strlen(msg.mtext) + 1,0);<br />  msgrcv(mqid,&amp;msg,256,PETER,0);<br />  printf("Peter: %s\n", msg.mtext);  <br /> }  <br />}</p>
		<p>下面的是msgPeter,是和Lucy通信的,程序先运行Lucy,再运行Peter</p>
		<p>#include&lt;sys/ipc.h&gt;<br />#include&lt;sys/msg.h&gt;<br />#include&lt;sys/stat.h&gt;<br />#include&lt;sys/types.h&gt;</p>
		<p>#include&lt;stdio.h&gt;<br />#include&lt;fcntl.h&gt;<br />#include&lt;signal.h&gt;<br />#include&lt;string.h&gt;<br />#include&lt;stdlib.h&gt;</p>
		<p>#define PROJID 0xFF<br />#define LUCY 1<br />#define PETER 2</p>
		<p>int main()<br />{<br /> char filenm[] = "msg";<br /> int mqid;<br /> key_t mqkey;<br /> struct msgbuf <br /> {<br />         long mtype;      /* message type, must be &gt; 0 */<br />         char mtext[256];  /* message data */<br />   }msg;<br /> int ret;</p>
		<p> mqkey = ftok(filenm, PROJID);<br /> if(mqkey == -1) <br /> {<br />  perror("ftok error: ");<br />  exit(-1);<br /> }</p>
		<p> mqid = msgget(mqkey, 0);<br /> if(mqid == -1) <br /> {<br />  perror("msgget error: ");<br />  exit(-1);<br /> }</p>
		<p> while(1) <br /> {<br />  msgrcv(mqid,&amp;msg,256,LUCY,0);<br />  printf("Lucy: %s\n",msg.mtext);<br />  printf("Peter: ");<br />  fgets(msg.mtext,256,stdin);<br />  if(strncmp("quit", msg.mtext, 4) == 0) <br />  {<br />   exit(0);<br />  }<br />  msg.mtext[strlen(msg.mtext)-1] = '\0';<br />  msg.mtype = PETER;<br />  msgsnd(mqid,&amp;msg,strlen(msg.mtext) + 1,0);<br /> } <br />}</p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/190843.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-04-04 23:39 <a href="http://www.blogjava.net/huyi2006/articles/190843.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux信号一览 </title><link>http://www.blogjava.net/huyi2006/articles/188298.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Mon, 24 Mar 2008 09:17:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/188298.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/188298.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/188298.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/188298.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/188298.html</trackback:ping><description><![CDATA[名称                   默认动作                       说明 <br /><br />SIGHUP           终止进程                        终端线路挂断 <br />SIGINT             终止进程                        中断进程 <br />SIGQUIT          建立CORE文件             终止进程，并且生成core文件 <br />SIGILL              建立CORE文件            非法指令 <br />SIGTRAP         建立CORE文件             跟踪自陷 <br />SIGBUS           建立CORE文件             总线错误 <br />SIGSEGV         建立CORE文件             段非法错误 <br />SIGFPE            建立CORE文件             浮点异常 <br />SIGIOT             建立CORE文件             执行I/O自陷 <br />SIGKILL            终止进程                        杀死进程 <br />SIGPIPE           终止进程                        向一个没有读进程的管道写数据 <br />SIGALARM       终止进程                        计时器到时 <br />SIGTERM         终止进程                        软件终止信号 <br />SIGSTOP         停止进程                         终端来的停止信号 <br />SIGCONT         忽略信号                        继续执行一个停止的进程 <br />SIGURG           忽略信号                        I/O紧急信号 <br />SIGIO                忽略信号                        描述符上可以进行I/O <br />SIGCHLD         忽略信号                         当子进程停止或退出时通知父进程 <br />SIGTTOU          停止进程                        后台进程写终端 <br />SIGTTIN            停止进程                        后台进程读终端 <br />SIGXGPU          终止进程                        CPU时限超时 <br />SIGXFSZ           终止进程                         文件长度过长 <br />SIGWINCH       忽略信号                         窗口大小发生变化 <br />SIGPROF          终止进程                         统计分布图用计时器到时 <br />SIGUSR1          终止进程                         用户定义信号1 <br />SIGUSR2          终止进程                         用户定义信号2 <br />SIGVTALRM     终止进程                          虚拟计时器到时<img src ="http://www.blogjava.net/huyi2006/aggbug/188298.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-03-24 17:17 <a href="http://www.blogjava.net/huyi2006/articles/188298.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux 信号处理机制</title><link>http://www.blogjava.net/huyi2006/articles/188278.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Mon, 24 Mar 2008 08:31:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/188278.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/188278.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/188278.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/188278.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/188278.html</trackback:ping><description><![CDATA[
		<p>alarm（设置信号传送闹钟）  <br />相关函数   signal，sleep<br /><br />表头文件   #include&lt;unistd.h&gt;<br /><br />定义函数   unsigned int alarm(unsigned int seconds);<br /><br />函数说明   alarm()用来设置信号SIGALRM在经过参数seconds指定的秒数后传送给目前的进程。如果参数seconds 为0，则之前设置的闹钟会被取消，并将剩下的时间返回。<br /><br />返回值   返回之前闹钟的剩余秒数，如果之前未设闹钟则返回0。<br /><br />范例   #include&lt;unistd.h&gt;<br />#include&lt;signal.h&gt;<br />void handler() {<br />printf(“hello\n”);<br />}<br />main()<br />{<br />int i;<br />signal(SIGALRM,handler);<br />alarm(5);<br />for(i=1;i&lt;7;i++){<br />printf(“sleep %d ...\n”,i);<br />sleep(1);<br />}<br />}<br /><br />执行   sleep 1 ...<br />sleep 2 ...<br />sleep 3 ...<br />sleep 4 ...<br />sleep 5 ...<br />hello<br />sleep 6 ...<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />kill（传送信号给指定的进程）  <br />相关函数   raise，signal<br /><br />表头文件   #include&lt;sys/types.h&gt;<br />#include&lt;signal.h&gt;<br /><br />定义函数   int kill(pid_t pid,int sig);<br /><br />函数说明   kill()可以用来送参数sig指定的信号给参数pid指定的进程。参数pid有几种情况:<br />pid&gt;0 将信号传给进程识别码为pid 的进程。<br />pid=0 将信号传给和目前进程相同进程组的所有进程<br />pid=-1 将信号广播传送给系统内所有的进程<br />pid&lt;0 将信号传给进程组识别码为pid绝对值的所有进程<br />参数sig代表的信号编号可参考附录D<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EINVAL 参数sig 不合法<br />ESRCH 参数pid 所指定的进程或进程组不存在<br />EPERM 权限不够无法传送信号给指定进程<br /><br />范例   #include&lt;unistd.h&gt;<br />#include&lt;signal.h&gt;<br />#include&lt;sys/types.h&gt;<br />#include&lt;sys/wait.h&gt;<br />main()<br />{<br />pid_t pid;<br />int status;<br />if(!(pid= fork())){<br />printf(“Hi I am child process!\n”);<br />sleep(10);<br />return;<br />}<br />else{<br />printf(“send signal to child process (%d) \n”,pid);<br />sleep(1);<br />kill(pid ,SIGABRT);<br />wait(&amp;status);<br />if(WIFSIGNALED(status))<br />printf(“chile process receive signal %d\n”,WTERMSIG(status));<br />}<br />}<br /><br />执行   sen signal to child process(3170)<br />Hi I am child process!<br />child process receive signal 6<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />pause（让进程暂停直到信号出现）  <br />相关函数   kill，signal，sleep<br /><br />表头文件   #include&lt;unistd.h&gt;<br /><br />定义函数   int pause(void);<br /><br />函数说明   pause()会令目前的进程暂停（进入睡眠状态），直到被信号(signal)所中断。<br /><br />返回值   只返回-1。<br /><br />错误代码   EINTR 有信号到达中断了此函数。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigaction（查询或设置信号处理方式）  <br />相关函数   signal，sigprocmask，sigpending，sigsuspend<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigaction(int signum,const struct sigaction *act ,struct sigaction *oldact);<br /><br />函数说明   sigaction()会依参数signum指定的信号编号来设置该信号的处理函数。参数signum可以指定SIGKILL和SIGSTOP以外的所有信号。<br />如参数结构sigaction定义如下<br />struct sigaction<br />{<br />void (*sa_handler) (int);<br />sigset_t sa_mask;<br />int sa_flags;<br />void (*sa_restorer) (void);<br />}<br />sa_handler此参数和signal()的参数handler相同，代表新的信号处理函数，其他意义请参考signal()。<br />sa_mask 用来设置在处理该信号时暂时将sa_mask 指定的信号搁置。<br />sa_restorer 此参数没有使用。<br />sa_flags 用来设置信号处理的其他相关操作，下列的数值可用。<br />OR 运算（|）组合<br />A_NOCLDSTOP : 如果参数signum为SIGCHLD，则当子进程暂停时并不会通知父进程<br />SA_ONESHOT/SA_RESETHAND:当调用新的信号处理函数前，将此信号处理方式改为系统预设的方式。<br />SA_RESTART:被信号中断的系统调用会自行重启<br />SA_NOMASK/SA_NODEFER:在处理此信号未结束前不理会此信号的再次到来。<br />如果参数oldact不是NULL指针，则原来的信号处理方式会由此结构sigaction 返回。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EINVAL 参数signum 不合法， 或是企图拦截SIGKILL/SIGSTOPSIGKILL信号<br />EFAULT 参数act，oldact指针地址无法存取。<br />EINTR 此调用被中断<br /><br />范例   #include&lt;unistd.h&gt;<br />#include&lt;signal.h&gt;<br />void show_handler(struct sigaction * act)<br />{<br />switch (act-&gt;sa_flags)<br />{<br />case SIG_DFL:printf(“Default action\n”);break;<br />case SIG_IGN:printf(“Ignore the signal\n”);break;<br />default: printf(“0x%x\n”,act-&gt;sa_handler);<br />}<br />}<br />main()<br />{<br />int i;<br />struct sigaction act,oldact;<br />act.sa_handler = show_handler;<br />act.sa_flags = SA_ONESHOT|SA_NOMASK;<br />sigaction(SIGUSR1,&amp;act,&amp;oldact);<br />for(i=5;i&lt;15;i++)<br />{<br />printf(“sa_handler of signal %2d =”.i);<br />sigaction(i,NULL,&amp;oldact);<br />}<br />}<br /><br />执行   sa_handler of signal 5 = Default action<br />sa_handler of signal 6= Default action<br />sa_handler of signal 7 = Default action<br />sa_handler of signal 8 = Default action<br />sa_handler of signal 9 = Default action<br />sa_handler of signal 10 = 0x8048400<br />sa_handler of signal 11 = Default action<br />sa_handler of signal 12 = Default action<br />sa_handler of signal 13 = Default action<br />sa_handler of signal 14 = Default action<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigaddset（增加一个信号至信号集）  <br />相关函数   sigemptyset，sigfillset，sigdelset，sigismember<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigaddset(sigset_t *set,int signum);<br /><br />函数说明   sigaddset()用来将参数signum 代表的信号加入至参数set 信号集里。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set指针地址无法存取<br />EINVAL 参数signum非合法的信号编号<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigdelset（从信号集里删除一个信号）  <br />相关函数   sigemptyset，sigfillset，sigaddset，sigismember<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigdelset(sigset_t * set,int signum);<br /><br />函数说明   sigdelset()用来将参数signum代表的信号从参数set信号集里删除。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set指针地址无法存取<br />EINVAL 参数signum非合法的信号编号<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigemptyset（初始化信号集）  <br />相关函数   sigaddset，sigfillset，sigdelset，sigismember<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigemptyset(sigset_t *set);<br /><br />函数说明   sigemptyset()用来将参数set信号集初始化并清空。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set指针地址无法存取<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigfillset（将所有信号加入至信号集）  <br />相关函数   sigempty，sigaddset，sigdelset，sigismember<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigfillset(sigset_t * set);<br /><br />函数说明   sigfillset()用来将参数set信号集初始化，然后把所有的信号加入到此信号集里。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />附加说明   EFAULT 参数set指针地址无法存取<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigismember（测试某个信号是否已加入至信号集里）  <br />相关函数   sigemptyset，sigfillset，sigaddset，sigdelset<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigismember(const sigset_t *set,int signum);<br /><br />函数说明   sigismember()用来测试参数signum 代表的信号是否已加入至参数set信号集里。如果信号集里已有该信号则返回1，否则返回0。<br /><br />返回值   信号集已有该信号则返回1，没有则返回0。如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set指针地址无法存取<br />EINVAL 参数signum 非合法的信号编号<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />signal（设置信号处理方式）  <br />相关函数   sigaction，kill，raise<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   void (*signal(int signum,void(* handler)(int)))(int);<br /><br />函数说明   signal()会依参数signum 指定的信号编号来设置该信号的处理函数。当指定的信号到达时就会跳转到参数handler指定的函数执行。如果参数handler不是函数指针，则必须是下列两个常数之一:<br />SIG_IGN 忽略参数signum指定的信号。<br />SIG_DFL 将参数signum 指定的信号重设为核心预设的信号处理方式。<br />关于信号的编号和说明，请参考附录D<br /><br />返回值   返回先前的信号处理函数指针，如果有错误则返回SIG_ERR(-1)。<br /><br />附加说明   在信号发生跳转到自定的handler处理函数执行后，系统会自动将此处理函数换回原来系统预设的处理方式，如果要改变此操作请改用sigaction()。<br /><br />范例   参考alarm()或raise()。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigpending（查询被搁置的信号）  <br />相关函数   signal，sigaction，sigprocmask，sigsuspend<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigpending(sigset_t *set);<br /><br />函数说明   sigpending()会将被搁置的信号集合由参数set指针返回。<br /><br />返回值执   行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set指针地址无法存取<br />EINTR 此调用被中断。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sigprocmask（查询或设置信号遮罩）  <br />相关函数   signal，sigaction，sigpending，sigsuspend<br /><br />表头文件   #include&lt;signal.h&gt;<br /><br />定义函数   int sigprocmask(int how,const sigset_t *set,sigset_t * oldset);<br /><br />函数说明   sigprocmask()可以用来改变目前的信号遮罩，其操作依参数how来决定<br />SIG_BLOCK 新的信号遮罩由目前的信号遮罩和参数set 指定的信号遮罩作联集<br />SIG_UNBLOCK 将目前的信号遮罩删除掉参数set指定的信号遮罩<br />SIG_SETMASK 将目前的信号遮罩设成参数set指定的信号遮罩。<br />如果参数oldset不是NULL指针，那么目前的信号遮罩会由此指针返回。<br /><br />返回值   执行成功则返回0，如果有错误则返回-1。<br /><br />错误代码   EFAULT 参数set，oldset指针地址无法存取。<br />EINTR 此调用被中断<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />sleep（让进程暂停执行一段时间）  <br />相关函数   signal，alarm<br /><br />表头文件   #include&lt;unistd.h&gt;<br /><br />定义函数   unsigned int sleep(unsigned int seconds);<br /><br />函数说明   sleep()会令目前的进程暂停，直到达到参数seconds 所指定的时间，或是被信号所中断。<br /><br />返回值   若进程暂停到参数seconds 所指定的时间则返回0，若有信号中断则返回剩余秒数。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />ferror（检查文件流是否有错误发生）  <br />相关函数   clearerr，perror<br /><br />表头文件   #include&lt;stdio.h&gt;<br /><br />定义函数   int ferror(FILE *stream);<br /><br />函数说明   ferror()用来检查参数stream所指定的文件流是否发生了错误情况，如有错误发生则返回非0值。<br /><br />返回值   如果文件流有错误发生则返回非0值。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />perror（打印出错误原因信息字符串）  <br />相关函数   strerror<br /><br />表头文件   #include&lt;stdio.h&gt;<br /><br />定义函数   void perror(const char *s);<br /><br />函数说明   perror()用来将上一个函数发生错误的原因输出到标准错误(stderr)。参数s所指的字符串会先打印出，后面再加上错误原因字符串。此错误原因依照全局变量errno的值来决定要输出的字符串。<br /><br />返回值  <br /><br />范例   #include&lt;stdio.h&gt;<br />main()<br />{<br />FILE *fp;<br />fp = fopen(“/tmp/noexist”,”r+”);<br />if(fp = =NULL) perror(“fopen”);<br />}<br /><br />执行   $ ./perror<br />fopen : No such file or diretory<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />strerror（返回错误原因的描述字符串）  <br />相关函数   perror<br /><br />表头文件   #include&lt;string.h&gt;<br /><br />定义函数   char * strerror(int errnum);<br /><br />函数说明   strerror()用来依参数errnum的错误代码来查询其错误原因的描述字符串，然后将该字符串指针返回。<br /><br />返回值   返回描述错误原因的字符串指针。<br /><br />范例   /* 显示错误代码0 至9 的错误原因描述*/<br />#include&lt;string.h&gt;<br />main()<br />{<br />int i;<br />for(i=0;i&lt;10;i++)<br />printf(“%d : %s\n”,i,strerror(i));<br />}<br /><br />执行   0 : Success<br />1 : Operation not permitted<br />2 : No such file or directory<br />3 : No such process<br />4 : Interrupted system call<br />5 : Input/output error<br />6 : Device not configured<br />7 : Argument list too long<br />8 : Exec format error<br />9 : Bad file descriptor<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />mkfifo（建立具名管道）  <br />相关函数   pipe，popen，open，umask<br /><br />表头文件   #include&lt;sys/types.h&gt;<br />#include&lt;sys/stat.h&gt;<br /><br />定义函数   int mkfifo(const char * pathname,mode_t mode);<br /><br />函数说明   mkfifo()会依参数pathname建立特殊的FIFO文件，该文件必须不存在，而参数mode为该文件的权限（mode%~umask），因此umask值也会影响到FIFO文件的权限。Mkfifo()建立的FIFO文件其他进程都可以用读写一般文件的方式存取。当使用open()来打开FIFO文件时，O_NONBLOCK旗标会有影响<br />1、当使用O_NONBLOCK 旗标时，打开FIFO 文件来读取的操作会立刻返回，但是若还没有其他进程打开FIFO 文件来读取，则写入的操作会返回ENXIO 错误代码。<br />2、没有使用O_NONBLOCK 旗标时，打开FIFO 来读取的操作会等到其他进程打开FIFO文件来写入才正常返回。同样地，打开FIFO文件来写入的操作会等到其他进程打开FIFO 文件来读取后才正常返回。<br /><br />返回值   若成功则返回0，否则返回-1，错误原因存于errno中。<br /><br />错误代码   EACCESS 参数pathname所指定的目录路径无可执行的权限<br />EEXIST 参数pathname所指定的文件已存在。<br />ENAMETOOLONG 参数pathname的路径名称太长。<br />ENOENT 参数pathname包含的目录不存在<br />ENOSPC 文件系统的剩余空间不足<br />ENOTDIR 参数pathname路径中的目录存在但却非真正的目录。<br />EROFS 参数pathname指定的文件存在于只读文件系统内。<br /><br />范例   #include&lt;sys/types.h&gt;<br />#include&lt;sys/stat.h&gt;<br />#include&lt;fcntl.h&gt;<br />main()<br />{<br />char buffer[80];<br />int fd;<br />unlink(FIFO);<br />mkfifo(FIFO,0666);<br />if(fork()&gt;0){<br />char s[ ] = “hello!\n”;<br />fd = open (FIFO,O_WRONLY);<br />write(fd,s,sizeof(s));<br />close(fd);<br />}<br />else{<br />fd= open(FIFO,O_RDONLY);<br />read(fd,buffer,80);<br />printf(“%s”,buffer);<br />close(fd);<br />}<br />}<br /><br />执行   hello!<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />pclose（关闭管道I/O）  <br />相关函数   popen<br /><br />表头文件   #include&lt;stdio.h&gt;<br /><br />定义函数   int pclose(FILE * stream);<br /><br />函数说明   pclose()用来关闭由popen所建立的管道及文件指针。参数stream为先前由popen()所返回的文件指针。<br /><br />返回值   返回子进程的结束状态。如果有错误则返回-1，错误原因存于errno中。<br /><br />错误代码   ECHILD pclose()无法取得子进程的结束状态。<br /><br />范例   参考popen()。<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />pipe（建立管道）  <br />相关函数   mkfifo，popen，read，write，fork<br /><br />表头文件   #include&lt;unistd.h&gt;<br /><br />定义函数   int pipe(int filedes[2]);<br /><br />函数说明   pipe()会建立管道，并将文件描述词由参数filedes数组返回。filedes[0]为管道里的读取端，filedes[1]则为管道的写入端。<br /><br />返回值   若成功则返回零，否则返回-1，错误原因存于errno中。<br /><br />错误代码   EMFILE 进程已用完文件描述词最大量。<br />ENFILE 系统已无文件描述词可用。<br />EFAULT 参数filedes数组地址不合法。<br /><br />范例   /* 父进程借管道将字符串“hello!\n”传给子进程并显示*/<br />#include &lt;unistd.h&gt;<br />main()<br />{<br />int filedes[2];<br />char buffer[80];<br />pipe(filedes);<br />if(fork()&gt;0){<br />/* 父进程*/<br />char s[ ] = “hello!\n”;<br />write(filedes[1],s,sizeof(s));<br />}<br />else{<br />/*子进程*/<br />read(filedes[0],buffer,80);<br />printf(“%s”,buffer);<br />}<br />}<br /><br />执行   hello!<br /><br />　 </p>
		<p>
		</p>
		<p>
				<br />popen（建立管道I/O）  <br />相关函数   pipe，mkfifo，pclose，fork，system，fopen<br /><br />表头文件   #include&lt;stdio.h&gt;<br /><br />定义函数   FILE * popen( const char * command,const char * type);<br /><br />函数说明   popen()会调用fork()产生子进程，然后从子进程中调用/bin/sh -c来执行参数command的指令。参数type可使用“r”代表读取，“w”代表写入。依照此type值，popen()会建立管道连到子进程的标准输出设备或标准输入设备，然后返回一个文件指针。随后进程便可利用此文件指针来读取子进程的输出设备或是写入到子进程的标准输入设备中。此外，所有使用文件指针(FILE*)操作的函数也都可以使用，除了fclose()以外。<br /><br />返回值   若成功则返回文件指针，否则返回NULL，错误原因存于errno中。<br /><br />错误代码   EINVAL参数type不合法。<br /><br />注意事项   在编写具SUID/SGID权限的程序时请尽量避免使用popen()，popen()会继承环境变量，通过环境变量可能会造成系统安全的问题。<br /><br />范例   #include&lt;stdio.h&gt;<br />main()<br />{<br />FILE * fp;<br />char buffer[80];<br />fp=popen(“cat /etc/passwd”,”r”);<br />fgets(buffer,sizeof(buffer),fp);<br />printf(“%s”,buffer);<br />pclose(fp);<br />}<br /></p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/188278.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-03-24 16:31 <a href="http://www.blogjava.net/huyi2006/articles/188278.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下automake软件编译与发布之多级目录结构的处理</title><link>http://www.blogjava.net/huyi2006/articles/187908.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 22 Mar 2008 09:20:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/187908.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/187908.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/187908.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/187908.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/187908.html</trackback:ping><description><![CDATA[
		<div class="cnt" id="blog_text">在入门篇我们简单介绍了使用automake自动产生makefile的几个关键步骤，所有文件都在同一个目录下。但在比较大的项目中，很少将所有文件放在一个目录下的。本文针对这种情况做个简单介绍。<br />    <br />    多级目录结构的软件，一般是单个程序、库文件或模块放在各自的目录中。automake要求每个目录都有自己的Makefile.am文件来编译各自目录 下的代码。在顶级的目录中，有一个Makefile.am文件，该文件通过SUBDIRS指明了这个目录下有多少个直接下级目录的代码需要编译。下级目录 的Makefile.am也指明自己需要编译的下级目录。通过这样的层层递归i，从而完成多级目录结构的编译。<br />    <br />    下面看一个具体的示例：<br />    <br />    本例假设目录结构如下：<br />    helloworld    <br />    |src<br />    ||--test.cpp<br />    ||dohello   <br />    |||--dohello.h<br />    |||--dohello.cpp <br />    |doc<br />    ||--userguide<br />    顶级目录helloworld，该目录下有src和doc两个目录。src目录下有一个test.cpp文件，一个dohello目录，dohello目录下又有dohello.h和dohello.cpp两个文件。doc下有一个readme文件。<br />    整个程序中test.cpp是主文件，里面有main函数。test.cpp调用了dohello.h和dohello.cpp中的类。<br />    <br />    下面是编译步骤：<br />    <br /><strong><font color="#0000ff">    1.运行autoscan产生configure.scan</font></strong><br />    <br />    在顶级目录helloworld下运行autoscan命令。产生configure.scan文件内容如下：<br /><font color="#800000">#                -*- Autoconf -*-<br /># Process this file with autoconf to produce a configure script.</font><p><font color="#800000">AC_PREREQ(2.61)<br />AC_INIT(FULL-PACKAGE-NAME, VERSION, BUG-REPORT-ADDRESS)<br />AC_CONFIG_SRCDIR([src/test.cpp])<br />AC_CONFIG_HEADER([config.h])</font></p><p><font color="#800000"># Checks for programs.<br />AC_PROG_CXX<br />AC_PROG_CC</font></p><p><font color="#800000"># Checks for libraries.</font></p><p><font color="#800000"># Checks for header files.</font></p><p><font color="#800000"># Checks for typedefs, structures, and compiler characteristics.</font></p><p><font color="#800000"># Checks for library functions.</font></p><p><font color="#800000">AC_CONFIG_FILES([Makefile<br />                 src/Makefile<br />                 src/dohello/Makefile])                       <br />AC_OUTPUT<br /></font>    <br />    <br /><strong><font color="#0000ff">    2.编辑configure.in</font></strong><br />    <br />    将configure.scan改名成configure.in，然后修改如下：<br /><font color="#800000"># Process this file with autoconf to produce a configure script.</font></p><p><font color="#800000">AC_PREREQ(2.61)<br />AC_INIT(hello,1.0)   #软件包名称和版本号,另一种写法是写在AM_INIT_AUTOMAKE中.<br />AC_CONFIG_SRCDIR([src/test.cpp])<br />#AC_CONFIG_HEADER([config.h])<br />AM_INIT_AUTOMAKE</font></p><p><font color="#800000"># Checks for programs.<br />AC_PROG_CXX<br />AC_PROG_CC<br />AC_PROG_RANLIB       #使用了静态库编译,需要此宏定义</font></p><p><font color="#800000"># Checks for libraries.</font></p><p><font color="#800000"># Checks for header files.</font></p><p><font color="#800000"># Checks for typedefs, structures, and compiler characteristics.</font></p><p><font color="#800000"># Checks for library functions.</font></p><p><font color="#800000">#AC_CONFIG_FILES([src/Makefile src/dohello/Makefile])<br />AC_OUTPUT(Makefile src/Makefile src/dohello/Makefile) #需要生成的Makefile，本例需要生成三个。<br /></font>    <br /><strong><font color="#0000ff">    3.运行aclocal和autoconf</font></strong><br />    <br />    configure.in修改完后则先后执行aclocal和autoconf命令。<br />    <br /><strong><font color="#0000ff">    4.建立各个目录的Makefile.am文件</font></strong><br />    <br />    在顶级目录、src目录和dohello目录下分别建立三个Makefile.am文件。内容分别如下：<br />    <br />    顶级目录Makefile.am：<br /><font color="#800000">#----------------开始------------------------------------------     <br />AUTOMAKE_OPTIONS=foreign<br />SUBDIRS=src   #本目录的直接下级目录src需要编译<br />EXTRA_DIST=doc/userguide #doc/userguide不需要编译，但要发布该文件。如果有多个文件，则用空格分开。<br />#----------------结束------------------------------------------</font><br />    <br />    src目录下的Makefile.am：<br /><font color="#800000">#----------------开始------------------------------------------ <br />AUTOMAKE_OPTIONS=foreign<br />SUBDIRS=dohello #本目录的直接下级目录dohello需要编译<br />bin_PROGRAMS=hello #本目录的文件编译成可执行文件hello。如有多个，用空格分开。然后在下面分别写它们的SOURCE和LDADD。<br />hello_SOURCES=test.cpp #编译hello需要的源文件列表，如有多个，用空格分开。<br />hello_LDADD=dohello/libhello.a #编译hello需要的库文件列表。如有多个，用空格分开。<br />#----------------结束------------------------------------------</font><br />    <br />    dohello目录下的Makefile.am<br /><font color="#800000">#----------------开始------------------------------------------    <br />AUTOMAKE_OPTIONS=foreign<br />noinst_LIBRARIES=libhello.a   #本目录下的代码编译成libhello.a库。不需要发布。如果需要发布，则写成bin_LIBRARIES。注意，库的名称格式必需为 libxxx.a。因为编译静态库，configure.in需要定义AC_PROG_RANLIB宏。<br />libhello_a_SOURCES=dohello.h dohello.cpp #编译libhello.a需要的源文件。注意将库名称中的'.'号改成'_'号。 <br />#----------------结束------------------------------------------</font><br />    <br /><strong><font color="#0000ff">    5.运行automake</font></strong><br />    <br />    以上几个Makefile.am都书写完毕后，运行automake --add-missing。<br />    <br /><strong><font color="#0000ff">    6.运行./configure和make</font></strong><br />    <br />    上面步骤完成后，先后运行./configure和make完成编译。如果编译成功，运行make dist可以将所有文件打包成hello-1.0.tar.gz。</p></div>
		<br />
<img src ="http://www.blogjava.net/huyi2006/aggbug/187908.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-03-22 17:20 <a href="http://www.blogjava.net/huyi2006/articles/187908.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下Makefile的automake生成全攻略</title><link>http://www.blogjava.net/huyi2006/articles/187907.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 22 Mar 2008 09:19:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/187907.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/187907.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/187907.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/187907.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/187907.html</trackback:ping><description><![CDATA[作为Linux下的程序开发人员，大家一定都遇到过Makefile，用make命令来编译自己写的程序确实是很方便。一般情况下，大家都是手工写一个简单Makefile，如果要想写出一个符合自由软件惯例的Makefile就不那么容易了。 
<p>　　在本文中，将给大家介绍如何使用autoconf和automake两个工具来帮助我们自动地生成符合自由软件惯例的Makefile，这样就 可以象常见的GNU程序一样，只要使用“./configure”，“make”，“make instal”就可以把程序安装到Linux系统中去了。这将特别适合想做开放源代码软件的程序开发人员，又或如果你只是自己写些小的Toy程序，那么这 个文章对你也会有很大的帮助。</p><p>　　一、Makefile介绍</p><p>　　Makefile是用于自动编译和链接的，一个工程有很多文件组成，每一个文件的改变都会导致工程的重新链接，但是不是所有的文件都需要重新编译，Makefile中纪录有文件的信息，在make时会决定在链接的时候需要重新编译哪些文件。</p><p>　　Makefile的宗旨就是：让编译器知道要编译一个文件需要依赖其他的哪些文件。当那些依赖文件有了改变，编译器会自动的发现最终的生成文件已经过时，而重新编译相应的模块。</p><p>　　Makefile的基本结构不是很复杂，但当一个程序开发人员开始写Makefile时，经常会怀疑自己写的是否符合惯例，而且自己写的 Makefile经常和自己的开发环境相关联，当系统环境变量或路径发生了变化后，Makefile可能还要跟着修改。这样就造成了手工书写 Makefile的诸多问题，automake恰好能很好地帮助我们解决这些问题。</p><p>　　使用automake，程序开发人员只需要写一些简单的含有预定义宏的文件，由autoconf根据一个宏文件生成configure，由 automake根据另一个宏文件生成Makefile.in，再使用configure依据Makefile.in来生成一个符合惯例的 Makefile。下面我们将详细介绍Makefile的automake生成方法。</p><p>　　二、使用的环境</p><p>　　本文所提到的程序是基于Linux发行版本：Fedora Core release 1，它包含了我们要用到的autoconf，automake。</p><p>　　三、从helloworld入手</p><p>　　我们从大家最常使用的例子程序helloworld开始。</p><p>　　下面的过程如果简单地说来就是：</p><p>　　新建三个文件：</p><p>　　　helloworld.c<br />configure.in<br />Makefile.am</p><p>　　然后执行：<br />   aclocal<br />   autoconf<br />   automake --add-missing<br />   ./configure<br />   make<br />   ./helloworld</p><p>　　就可以看到Makefile被产生出来，而且可以将helloworld.c编译通过。</p><p>　　很简单吧，几条命令就可以做出一个符合惯例的Makefile，感觉如何呀。</p><p>　　现在开始介绍详细的过程：</p><p>　　1、建目录</p><p>　　在你的工作目录下建一个helloworld目录，我们用它来存放helloworld程序及相关文件，如在/home/my/build下：</p><p>$ mkdir helloword<br />$ cd helloworld</p><p>　　2、 helloworld.c</p><p>　　然后用你自己最喜欢的编辑器写一个hellowrold.c文件，如命令：vi helloworld.c。使用下面的代码作为helloworld.c的内容。</p><p>int main(int argc, char** argv)<br />{<br />printf("Hello, Linux World!\n");<br />return 0;<br />}</p><p>　　完成后保存退出。</p><p>　　现在在helloworld目录下就应该有一个你自己写的helloworld.c了。</p><p>　　3、生成configure</p><p>　　我们使用autoscan命令来帮助我们根据目录下的源代码生成一个configure.in的模板文件。</p><p>　　命令：</p><p>$ autoscan<br />$ ls<br />configure.scan helloworld.c</p><p>　　执行后在hellowrold目录下会生成一个文件：configure.scan，我们可以拿它作为configure.in的蓝本。</p><p>　　现在将configure.scan改名为configure.in，并且编辑它，按下面的内容修改，去掉无关的语句：</p><p>============================configure.in内容开始=========================================<br /># -*- Autoconf -*-<br /># Process this file with autoconf to produce a configure script.</p><p>AC_INIT(helloworld.c)<br />AM_INIT_AUTOMAKE(helloworld, 1.0)</p><p># Checks for programs.<br />AC_PROG_CC</p><p># Checks for libraries.</p><p># Checks for header files.</p><p># Checks for typedefs, structures, and compiler characteristics.</p><p># Checks for library functions.<br />AC_OUTPUT(Makefile)<br />============================configure.in内容结束=========================================</p><p>　　然后执行命令aclocal和autoconf，分别会产生aclocal.m4及configure两个文件：</p><p>$ aclocal <br />$ls <br />aclocal.m4 configure.in helloworld.c <br />$ autoconf <br />$ ls <br />aclocal.m4 autom4te.cache configure configure.in helloworld.c</p><p><br />大家可以看到configure.in内容是一些宏定义，这些宏经autoconf处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。</p><p>　　autoconf 是用来生成自动配置软件源代码脚本（configure）的工具。configure脚本能独立于autoconf运行，且在运行的过程中，不需要用户的干预。</p><p>　　要生成configure文件，你必须告诉autoconf如何找到你所用的宏。方式是使用aclocal程序来生成你的aclocal.m4。</p><p>　　aclocal根据configure.in文件的内容，自动生成aclocal.m4文件。aclocal是一个perl 脚本程序，它的定义是：“aclocal - create aclocal.m4 by scanning configure.ac”。</p><p>　　autoconf从configure.in这个列举编译软件时所需要各种参数的模板文件中创建configure。</p><p>　　autoconf需要GNU m4宏处理器来处理aclocal.m4，生成configure脚本。</p><p>　　m4是一个宏处理器。将输入拷贝到输出，同时将宏展开。宏可以是内嵌的，也可以是用户定义的。除了可以展开宏，m4还有一些内建的函数，用来引用文件，执行命令，整数运算，文本操作，循环等。m4既可以作为编译器的前端，也可以单独作为一个宏处理器。<br />  <br />   4、新建Makefile.am</p><p>　　新建Makefile.am文件，命令：<br />$ vi Makefile.am <br />内容如下:<br />AUTOMAKE_OPTIONS=foreign<br />bin_PROGRAMS=helloworld<br />helloworld_SOURCES=helloworld.c <br />automake会根据你写的Makefile.am来自动生成Makefile.in。</p><p>　　Makefile.am中定义的宏和目标,会指导automake生成指定的代码。例如，宏bin_PROGRAMS将导致编译和连接的目标被生成。</p><p>　　5、运行automake<br />命令：<br />$ automake --add-missing<br />configure.in: installing `./install-sh'<br />configure.in: installing `./mkinstalldirs'<br />configure.in: installing `./missing'<br />Makefile.am: installing `./depcomp'</p><p>　　automake会根据Makefile.am文件产生一些文件，包含最重要的Makefile.in。</p><p>　　6、执行configure生成Makefile</p><p>$ ./configure <br />checking for a BSD-compatible install... /usr/bin/install -c<br />checking whether build environment is sane... yes<br />checking for gawk... gawk<br />checking whether make sets $(MAKE)... yes<br />checking for gcc... gcc<br />checking for C compiler default output... a.out<br />checking whether the C compiler works... yes<br />checking whether we are cross compiling... no<br />checking for suffix of executables... <br />checking for suffix of object files... o<br />checking whether we are using the GNU C compiler... yes<br />checking whether gcc accepts -g... yes<br />checking for gcc option to accept ANSI C... none needed<br />checking for style of include used by make... GNU<br />checking dependency style of gcc... gcc3<br />configure: creating ./config.status<br />config.status: creating Makefile<br />config.status: executing depfiles commands<br />$ ls -l Makefile<br />-rw-rw-r-- 1 yutao yutao 15035 Oct 15 10:40 Makefile</p><p>　　你可以看到，此时Makefile已经产生出来了。</p><p>　　7、使用Makefile编译代码<br />$ make<br />if gcc -DPACKAGE_NAME="" -DPACKAGE_TARNAME="" -DPACKAGE_VERSION="" -</p><p>DPACKAGE_STRING="" -DPACKAGE_BUGREPORT="" -DPACKAGE="helloworld" -DVERSION="1.0"</p><p>-I. -I. -g -O2 -MT helloworld.o -MD -MP -MF ".deps/helloworld.Tpo" \<br />-c -o helloworld.o `test -f 'helloworld.c' || echo './'`helloworld.c; \<br />then mv -f ".deps/helloworld.Tpo" ".deps/helloworld.Po"; \<br />else rm -f ".deps/helloworld.Tpo"; exit 1; \<br />fi<br />gcc -g -O2 -o helloworld helloworld.o</p><p>　　运行helloworld<br />$ ./helloworld <br />Hello, Linux World! <br />这样helloworld就编译出来了，你如果按上面的步骤来做的话，应该也会很容易地编译出正确的helloworld文件。你还可以试着使用一些 其他的make命令，如make clean，make install，make dist，看看它们会给你什么样的效果。感觉如何？自己也能写出这么专业的Makefile，老板一定会对你刮目相看。</p><p><br />四、深入浅出</p><p>　　针对上面提到的各个命令，我们再做些详细的介绍。</p><p>　　1、 autoscan</p><p>　　autoscan是用来扫描源代码目录生成configure.scan文件的。autoscan可以用目录名做为参数，但如果你不使用参数的 话，那么autoscan将认为使用的是当前目录。autoscan将扫描你所指定目录中的源文件，并创建configure.scan文件。</p><p>　　2、 configure.scan</p><p>　　configure.scan包含了系统配置的基本选项，里面都是一些宏定义。我们需要将它改名为configure.in</p><p>　　3、 aclocal</p><p>　　aclocal是一个perl 脚本程序。aclocal根据configure.in文件的内容，自动生成aclocal.m4文件。aclocal的定义是：“aclocal - create aclocal.m4 by scanning configure.ac”。</p><p>　　4、 autoconf</p><p>　　autoconf是用来产生configure文件的。configure是一个脚本，它能设置源程序来适应各种不同的操作系统平台，并且根据不同的系统来产生合适的Makefile，从而可以使你的源代码能在不同的操作系统平台上被编译出来。</p><p>　　configure.in文件的内容是一些宏，这些宏经过autoconf 处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。configure.in文件中的宏的顺序并没有规定，但是你必须在所有宏的最前 面和最后面分别加上AC_INIT宏和AC_OUTPUT宏。</p><p>　　在configure.in中：</p><p>　　#号表示注释，这个宏后面的内容将被忽略。</p><p>　　AC_INIT(FILE)</p><p>　　这个宏用来检查源代码所在的路径。</p><p>AM_INIT_AUTOMAKE(PACKAGE, VERSION)</p><p>　　 这个宏是必须的，它描述了我们将要生成的软件包的名字及其版本号：PACKAGE是软件包的名字，VERSION是版本号。当你使用make dist命令时，它会给你生成一个类似helloworld-1.0.tar.gz的软件发行包，其中就有对应的软件包的名字和版本号。</p><p>AC_PROG_CC</p><p>　　这个宏将检查系统所用的C编译器。</p><p>AC_OUTPUT(FILE)</p><p>　　这个宏是我们要输出的Makefile的名字。</p><p>　　我们在使用automake时，实际上还需要用到其他的一些宏，但我们可以用aclocal 来帮我们自动产生。执行aclocal后我们会得到aclocal.m4文件。</p><p>　　产生了configure.in和aclocal.m4 两个宏文件后，我们就可以使用autoconf来产生configure文件了。</p><p>　　5、 Makefile.am</p><p>　　Makefile.am是用来生成Makefile.in的，需要你手工书写。Makefile.am中定义了一些内容：</p><p>AUTOMAKE_OPTIONS</p><p>　　这个是automake的选项。在执行automake时，它会检查目录下是否存在标准GNU软件包中应具备的各种文件，例如AUTHORS、ChangeLog、NEWS等文件。我们将其设置成foreign时，automake会改用一般软件包的标准来检查。</p><p>bin_PROGRAMS</p><p>　　这个是指定我们所要产生的可执行文件的文件名。如果你要产生多个可执行文件，那么在各个名字间用空格隔开。</p><p>helloworld_SOURCES</p><p>　　这个是指定产生“helloworld”时所需要的源代码。如果它用到了多个源文件，那么请使用空格符号将它们隔开。比如需要 helloworld.h，helloworld.c那么请写成helloworld_SOURCES= helloworld.h helloworld.c。</p><p>　　如果你在bin_PROGRAMS定义了多个可执行文件，则对应每个可执行文件都要定义相对的filename_SOURCES。</p><p>LIBS<br />   这个用来指定链接的程序库。如LIBS += -lpthread，指定链接pthread库。</p><p>　　6、 automake</p><p>　　我们使用automake --add-missing来产生Makefile.in。</p><p>　　选项--add-missing的定义是“add missing standard files to package”，它会让automake加入一个标准的软件包所必须的一些文件。</p><p>　　我们用automake产生出来的Makefile.in文件是符合GNU Makefile惯例的，接下来我们只要执行configure这个shell 脚本就可以产生合适的 Makefile 文件了。</p><p>　　7、 Makefile</p><p>　　在符合GNU Makefiel惯例的Makefile中，包含了一些基本的预先定义的操作：<br />make<br />根据Makefile编译源代码，连接，生成目标文件，可执行文件。<br />make clean<br />清除上次的make命令所产生的object文件（后缀为“.o”的文件）及可执行文件。<br />make install<br />将编译成功的可执行文件安装到系统目录中，一般为/usr/local/bin目录。<br />make dist<br />产生发布软件包文件（即distribution package）。这个命令将会将可执行文件及相关文件打包成一个tar.gz压缩的文件用来作为发布软件的软件包。<br />它会在当前目录下生成一个名字类似“PACKAGE-VERSION.tar.gz”的文件。PACKAGE和VERSION，是我们在configure.in中定义的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。</p><p>make distcheck</p><p>　　生成发布软件包并对其进行测试检查，以确定发布包的正确性。这个操作将自动把压缩包文件解开，然后执行configure命令，并且执行make，来确认编译不出现错误，最后提示你软件包已经准备好，可以发布了。</p><p>===============================================<br />helloworld-1.0.tar.gz is ready for distribution<br />===============================================<br />make distclean</p><p>　　类似make clean，但同时也将configure生成的文件全部删除掉，包括Makefile。</p><p>　　五、结束语</p><p>　　通过上面的介绍，你应该可以很容易地生成一个你自己的符合GNU惯例的Makefile文件及对应的项目文件。</p><p>　　如果你想写出更复杂的且符合惯例的Makefile，你可以参考一些开放代码的项目中的configure.in和Makefile.am文件，比如：嵌入式数据库sqlite，单元测试cppunit。</p><img src ="http://www.blogjava.net/huyi2006/aggbug/187907.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-03-22 17:19 <a href="http://www.blogjava.net/huyi2006/articles/187907.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>STDIN和STDIN_FILENO的区别</title><link>http://www.blogjava.net/huyi2006/articles/179096.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 02 Feb 2008 14:45:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/179096.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/179096.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/179096.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/179096.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/179096.html</trackback:ping><description><![CDATA[
		<p>我个人理解STDIN应该是指针吧。像通过fopen 打开的是文件指针，通过fgets,fwrite操作，是文件流方式。属于高级IO，带缓冲的。<br />而STDIN_FILENO则是文件描述符，是个整型，open,read,write用到的是文件描述符。属于低级IO，要自己处理缓冲。</p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/179096.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-02-02 22:45 <a href="http://www.blogjava.net/huyi2006/articles/179096.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>ps 命令中stat字段的含义</title><link>http://www.blogjava.net/huyi2006/articles/175405.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Tue, 15 Jan 2008 03:11:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/175405.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/175405.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/175405.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/175405.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/175405.html</trackback:ping><description><![CDATA[
		<div class="tit">ps 命令中stat字段的含义</div>
		<div class="date"> </div>
		<table style="TABLE-LAYOUT: fixed">
				<tbody>
						<tr>
								<td>
										<div class="cnt">
												<p style="TEXT-INDENT: 2em">D 无法中断的休眠状态（通常 IO 的进程）；</p>
												<p style="TEXT-INDENT: 2em">R 正在运行可中在队列中可过行的；</p>
												<p style="TEXT-INDENT: 2em">S 处于休眠状态；</p>
												<p style="TEXT-INDENT: 2em">T 停止或被追踪；</p>
												<p style="TEXT-INDENT: 2em">W 进入内存交换（从内核2.6开始无效）；</p>
												<p style="TEXT-INDENT: 2em">X 死掉的进程（从来没见过）；</p>
												<p style="TEXT-INDENT: 2em">Z 僵尸进程；</p>
												<p style="TEXT-INDENT: 2em">
												</p>
												<p style="TEXT-INDENT: 2em">&lt; 优先级高的进程</p>
												<p style="TEXT-INDENT: 2em">N 优先级较低的进程</p>
												<p style="TEXT-INDENT: 2em">L 有些页被锁进内存；</p>
												<p style="TEXT-INDENT: 2em">s 进程的领导者（在它之下有子进程）；</p>
												<p style="TEXT-INDENT: 2em">l 多进程的（使用 CLONE_THREAD, 类似 NPTL pthreads）；</p>
												<p style="TEXT-INDENT: 2em">+ 位于后台的进程组；</p>
										</div>
								</td>
						</tr>
				</tbody>
		</table>
<img src ="http://www.blogjava.net/huyi2006/aggbug/175405.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2008-01-15 11:11 <a href="http://www.blogjava.net/huyi2006/articles/175405.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>理解Proc文件系统</title><link>http://www.blogjava.net/huyi2006/articles/121838.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Mon, 04 Jun 2007 06:53:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/121838.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/121838.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/121838.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/121838.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/121838.html</trackback:ping><description><![CDATA[
		<br />
		<div id="article">
				<table cellspacing="0" cellpadding="0" width="560" border="0">
						<tbody>
								<tr>
										<th class="f24">
												<font color="#05006c">
														<h1>理解 Proc 文件系统</h1>
												</font>
										</th>
								</tr>
						</tbody>
				</table>
		</div>
		<em>目录</em>: 
<ul><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex0">/proc --- 一个虚拟文件系统</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex1">加载 proc 文件系统</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex2">察看 /proc 的文件</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex3">得到有用的系统/内核信息</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex4">有关运行中的进程的信息</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex5">通过 /proc 与内核交互</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex6">结论</a></li><li><a href="http://linux.chinaunix.net/doc/2004-10-05/16.shtml#324lfindex7">参考文献</a></li></ul><!-- HEAD OF THE ARTICLE --><br />  
<table border="0"><tbody><tr><td><!-- ABSTRACT OF THE ARTICLE --><p><i>摘要</i>: 
</p><p><!-- articleabstract_start --></p><p>Linux 内核提供了一种通过 /proc 文件系统，在运行时访问内核内部数据结构、改变内核设置的机制。尽管在各种硬件平台上的 Linux 系统的 /proc 文件系统的基本概念都是相同的，但本文只讨论基于 intel x86 架构的 Linux /proc 文件系统。 </p><!-- articleabstract_stop --><br /><!-- HR divider --><center><font color="#8282e0"><b>_________________ _________________ _________________</b></font></center><br /></td></tr></tbody></table><!-- BODY OF THE ARTICLE --><a name="324lfindex0"> </a><h2>/proc --- 一个虚拟文件系统</h2><p>/proc 文件系统是一种内核和内核模块用来向进程 (process) 发送信息的机制 (所以叫做 /proc)。这个伪文件系统让你可以和内核内部数据结构进行交互，获取 有关进程的有用信息，在运行中 (on the fly) 改变设置 (通过改变内核参数)。 与其他文件系统不同，/proc 存在于内存之中而不是硬盘上。如果你察看文件 /proc/mounts (和 mount 命令一样列出所有已经加载的文件系统)，你会看到其中 一行是这样的： </p><br clear="all" /><pre class="code">grep proc /proc/mounts
/proc /proc proc rw 0 0
</pre><p>/proc 由内核控制，没有承载 /proc 的设备。因为 /proc 主要存放由内核控制的状态信息，所以大部分这些信息的逻辑位置位于内核控制的内存。对 /proc 进行一次 'ls -l' 可以看到大部分文件都是 0 字节大的；不过察看这些文件的时候，确实可以看到一些信息。这怎么可能？这是因为 /proc 文件系统和其他常规的文件系统一样把自己注册到虚拟文件系统层 (VFS) 了。然而，直到当 VFS 调用它，请求文件、目录的 i-node 的时候，/proc 文件系统才根据内核中的信息建立相应的文件和目录。 </p><a name="324lfindex1"> </a><h2>加载 proc 文件系统</h2><p>如果系统中还没有加载 proc 文件系统，可以通过如下命令加载 proc 文件系统： <br /></p><p class="code">mount -t proc proc /proc </p>上述命令将成功加载你的 proc 文件系统。更多细节请阅读 mount 命令的 man page。 
<p></p><a name="324lfindex2"> </a><h2>察看 /proc 的文件</h2><p>/proc 的文件可以用于访问有关内核的状态、计算机的属性、正在运行的进程的状态等信息。大部分 /proc 中的文件和目录提供系统物理环境最新的信息。尽管 /proc 中的文件是虚拟的，但它们仍可以使用任何文件编辑器或像'more', 'less'或 'cat'这样的程序来查看。当编辑程序试图打开一个虚拟文件时，这个文件就通过内核中的信息被凭空地 (on the fly) 创建了。这是一些我从我的系统中得到的一些有趣结果： </p><pre class="code">$ ls -l /proc/cpuinfo
-r--r--r-- 1 root root 0 Dec 25 11:01 /proc/cpuinfo

$ file /proc/cpuinfo
/proc/cpuinfo: empty

$ cat /proc/cpuinfo

processor       : 0
vendor_id       : GenuineIntel
cpu family      : 6
model           : 8
model name      : Pentium III (Coppermine)
stepping        : 6
cpu MHz         : 1000.119
cache size      : 256 KB
fdiv_bug        : no
hlt_bug         : no
sep_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca
cmov pat pse36 mmx fxsr xmm
bogomips        : 1998.85

processor       : 3
vendor_id       : GenuineIntel
cpu family      : 6
model           : 8
model name      : Pentium III (Coppermine)
stepping        : 6
cpu MHz         : 1000.119
cache size      : 256 KB
fdiv_bug        : no
hlt_bug         : no
sep_bug         : no
f00f_bug        : no
coma_bug        : no
fpu             : yes
fpu_exception   : yes
cpuid level     : 2
wp              : yes
flags           : fpu vme de pse tsc msr pae mce cx8 apic sep mtrr pge mca
cmov pat pse36 mmx fxsr xmm
bogomips        : 1992.29
</pre><br />这是一个从双 CPU 的系统中得到的结果，上述大部分的信息十分清楚地给出了这个系统的有用的硬件信息。有些 /proc 的文件是经过编码的，不同的工具可以被用来解释这些编码过的信息并输出成可读的形式。这样的工具包括：'top', 'ps', 'apm' 等。 <br /><p></p><a name="324lfindex3"> </a><h2>得到有用的系统/内核信息</h2><p><br />proc 文件系统可以被用于收集有用的关于系统和运行中的内核的信息。下面是一些重要的文件： 
</p><ul><li>/proc/cpuinfo - CPU 的信息 (型号, 家族, 缓存大小等) 
</li><li>/proc/meminfo - 物理内存、交换空间等的信息 
</li><li>/proc/mounts - 已加载的文件系统的列表 
</li><li>/proc/devices - 可用设备的列表 
</li><li>/proc/filesystems - 被支持的文件系统 
</li><li>/proc/modules - 已加载的模块 
</li><li>/proc/version - 内核版本 
</li><li>/proc/cmdline - 系统启动时输入的内核命令行参数 </li></ul>proc 中的文件远不止上面列出的这么多。想要进一步了解的读者可以对 /proc 的每一个文件都'more'一下或读参考文献[1]获取更多的有关 /proc 目录中的文件的信息。我建议使用'more'而不是'cat'，除非你知道这个文件很小，因为有些文件 (比如 kcore) 可能会非常长。 
<p></p><a name="324lfindex4"> </a><h2>有关运行中的进程的信息</h2><p>/proc 文件系统可以用于获取运行中的进程的信息。在 /proc 中有一些编号的子目录。每个编号的目录对应一个进程 id (PID)。这样，每一个运行中的进程 /proc 中都有一个用它的 PID 命名的目录。这些子目录中包含可以提供有关进程的状态和环境的重要细节信息的文件。让我们试着查找一个运行中的进程。 </p><pre class="code">$ ps -aef | grep mozilla
root 32558 32425 8  22:53 pts/1  00:01:23  /usr/bin/mozilla
</pre>上述命令显示有一个正在运行的 mozilla 进程的 PID 是 32558。相对应的，/proc 中应该有一个名叫 32558 的目录<br /><br /><pre class="code">$ ls -l /proc/32558
total 0
-r--r--r--    1 root  root            0 Dec 25 22:59 cmdline
-r--r--r--    1 root  root            0 Dec 25 22:59 cpu
lrwxrwxrwx    1 root  root            0 Dec 25 22:59 cwd -&gt; /proc/
-r--------    1 root  root            0 Dec 25 22:59 environ
lrwxrwxrwx    1 root  root            0 Dec 25 22:59 exe -&gt; /usr/bin/mozilla*
dr-x------    2 root  root            0 Dec 25 22:59 fd/
-r--r--r--    1 root  root            0 Dec 25 22:59 maps
-rw-------    1 root  root            0 Dec 25 22:59 mem
-r--r--r--    1 root  root            0 Dec 25 22:59 mounts
lrwxrwxrwx    1 root  root            0 Dec 25 22:59 root -&gt; //
-r--r--r--    1 root  root            0 Dec 25 22:59 stat
-r--r--r--    1 root  root            0 Dec 25 22:59 statm
-r--r--r--    1 root  root            0 Dec 25 22:59 status
</pre>文件 "cmdline" 包含启动进程时调用的命令行。"envir" 进程的环境变两。 "status" 是进程的状态信息，包括启动进程的用户的用户ID (UID) 和组ID(GID) ，父进程ID (PPID)，还有进程当前的状态，比如"Sleelping"和"Running"。每个进程的目录都有几个符号链接，"cwd"是指向进程当前工作目录的符号链接，"exe"指向运行的进程的可执行程序，"root"指向被这个进程看作是根目录的目录 (通常是"/")。目录"fd"包含指向进程使用的文件描述符的链接。 "cpu"仅在运行 SMP 内核时出现，里面是按 CPU 划分的进程时间。 
<p></p><p><tt>/proc/self</tt> 是一个有趣的子目录，它使得程序可以方便地使用 /proc 查找本进程地信息。/proc/self 是一个链接到 /proc 中访问 /proc 的进程所对应的 PID 的目录的符号链接。<br /></p><a name="324lfindex5"> </a><h2>通过 /proc 与内核交互</h2><p><br />上面讨论的大部分 /proc 的文件是只读的。而实际上 /proc 文件系统通过 /proc 中可读写的文件提供了对内核的交互机制。写这些文件可以改变内核的状态，因而要慎重改动这些文件。/proc/sys 目录存放所有可读写的文件的目录，可以被用于改变内核行为。</p><p><tt>/proc/sys/kernel</tt> - 这个目录包含反通用内核行为的信息。 /proc/sys/kernel/{domainname, hostname} 存放着机器/网络的域名和主机名。这些文件可以用于修改这些名字。<br /><br /></p><pre class="code">$ hostname
machinename.domainname.com

$ cat /proc/sys/kernel/domainname
domainname.com

$ cat /proc/sys/kernel/hostname
machinename

$ echo "new-machinename"  &gt; /proc/sys/kernel/hostname

$ hostname
new-machinename.domainname.com


</pre>这样，通过修改 /proc 文件系统中的文件，我们可以修改主机名。很多其他可配置的文件存在于 /proc/sys/kernel/。这里不可能列出所有这些文件，读者可以自己去这个目录查看以得到更多细节信息。<br />另一个可配置的目录是 <tt>/proc/sys/net</tt>。这个目录中的文件可以用于修改机器/网络的网络属性。比如，简单修改一个文件，你可以在网络上瘾藏匿的计算机。<br /><br /><pre class="code">$ echo 1 &gt; /proc/sys/net/ipv4/icmp_echo_ignore_all
</pre>这将在网络上瘾藏你的机器，因为它不响应 icmp_echo。主机将不会响应其他主机发出的 ping 查询。<br /><br /><pre class="code">$ ping machinename.domainname.com
no answer from machinename.domainname.com
</pre>要改回缺省设置，只要<br /><pre class="code">$ echo 0 &gt; /proc/sys/net/ipv4/icmp_echo_ignore_all
</pre>/proc/sys 下还有许多其它可以用于改变内核属性。读者可以通过参考文献 [1], [2] 获取更多信息。 
<p></p><a name="324lfindex6"> </a><h2>结论</h2><p>/proc 文件系统提供了一个基于文件的 Linux 内部接口。它可以用于确定系统的各种不同设备和进程的状态。对他们进行配置。因而，理解和应用有关这个文件系统的知识是理解你的 Linux 系统的关键。<br /><br /></p><a name="324lfindex7"> </a><h2>参考文献</h2><p><br /></p><ul><li>[1] 有关Linux proc 文件系统的文档位于: /usr/src/linux/Documentation/filesystems/proc.txt<br /></li><li>[2] RedHat Guide: The /proc File System: <a href="http://www.redhat.com/docs/manuals/linux/RHL-7.3-Manual/ref-guide/ch-proc.html">http://www.redhat.com/docs/manuals/linux/RHL-7.3-Manual/ref-guide/ch-proc.html</a></li></ul><img src ="http://www.blogjava.net/huyi2006/aggbug/121838.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-06-04 14:53 <a href="http://www.blogjava.net/huyi2006/articles/121838.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux Deamon编程方法</title><link>http://www.blogjava.net/huyi2006/articles/117860.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Wed, 16 May 2007 07:41:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/117860.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/117860.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/117860.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/117860.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/117860.html</trackback:ping><description><![CDATA[
		<span class="oblog_text">
				<div>Linux Deamon编程方法</div>
				<div>守护进程（Daemon）是运行在后台的一种特殊进程。它独立于控制终端并且周期性地执行某种任务或等待处理某些发生的事件。守护进程是一种很
有用的进程。
Linux的大多数服务器就是用守护进程实现的。比如，Internet服务器inetd，Web服务器httpd等。同时，守护进程完成许多系统任务。
比如，作业规划进程crond，打印进程lpd等。 </div>
				<p>守护进程的编程本身并不复杂，复杂的是各种版本的Unix的实现机制不尽相同，造成不同
Unix环境下守护进程的编程规则并不一致。需要注意，照搬某些书上的规则（特别是BSD4.3和低版本的System
V）到Linux会出现错误的。下面将给出Linux下守护进程的编程要点和详细实例。 </p>
				<p>一． 守护进程及其特性 </p>
				<p>守护进程最重要的特性是后台运行。在这一点上DOS下的常驻内存程序TSR与之相似。其次，守护进程必须与其运行前的环境隔离开来。这些环境包括未
关闭的文件描述符，控制终端，会话和进程组，工作目录以及文件创建掩模等。这些环境通常是守护进程从执行它的父进程（特别是shell）中继承下来的。最
后，守护进程的启动方式有其特殊之处。它可以在Linux系统启动时从启动脚本/etc/rc.d中启动，可以由作业规划进程crond启动，还可以由用
户终端（通常是 shell）执行。 </p>
				<p>总之，除开这些特殊性以外，守护进程与普通进程基本上没有什么区别。因此，编写守护进程实际上是把一个普通进程按照上述的守护进程的特性改造成为守护进程。如果对进程有比较深入的认识就更容易理解和编程了。 </p>
				<p>二． 守护进程的编程要点 </p>
				<p>前面讲过，不同Unix环境下守护进程的编程规则并不一致。所幸的是守护进程的编程原则其实都一样，区别在于具体的实现细节不同。这个原则就是要满
足守护进程的特性。同时，Linux是基于Syetem V的SVR4并遵循Posix标准，实现起来与BSD4相比更方便。编程要点如下； </p>
				<p>1. 在后台运行。 </p>
				<p>为避免挂起控制终端将Daemon放入后台执行。方法是在进程中调用fork使父进程终止，让Daemon在子进程中后台执行。 </p>
				<div>
						<pre>if(pid=fork())<br />exit(0); //是父进程，结束父进程，子进程继续<br /></pre>
				</div>
				<p>2. 脱离控制终端，登录会话和进程组 </p>
				<p>有必要先介绍一下Linux中的进程与控制终端，登录会话和进程组之间的关系：进程属于一个进程组，进程组号（GID）就是进程组长的进程号
（PID）。登录会话可以包含多个进程组。这些进程组共享一个控制终端。这个控制终端通常是创建进程的登录终端。控制终端，登录会话和进程组通常是从父进
程继承下来的。我们的目的就是要摆脱它们，使之不受它们的影响。方法是在第1点的基础上，调用setsid()使进程成为会话组长： </p>
				<p>setsid(); </p>
				<p>说明：当进程是会话组长时setsid()调用失败。但第一点已经保证进程不是会话组长。setsid()调用成功后，进程成为新的会话组长和新的进程组长，并与原来的登录会话和进程组脱离。由于会话过程对控制终端的独占性，进程同时与控制终端脱离。 </p>
				<p>3. 禁止进程重新打开控制终端 </p>
				<p>现在，进程已经成为无终端的会话组长。但它可以重新申请打开一个控制终端。可以通过使进程不再成为会话组长来禁止进程重新打开控制终端： </p>
				<p>if(pid=fork()) exit(0); //结束第一子进程，第二子进程继续（第二子进程不再是会话组长） </p>
				<p>4. 关闭打开的文件描述符 </p>
				<p>进程从创建它的父进程那里继承了打开的文件描述符。如不关闭，将会浪费系统资源，造成进程所在的文件系统无法卸下以及引起无法预料的错误。按如下方法关闭它们： </p>
				<p>for(i=0;i 关闭打开的文件描述符close(i);&gt; </p>
				<p>5. 改变当前工作目录 </p>
				<p>进程活动时，其工作目录所在的文件系统不能卸下。一般需要将工作目录改变到根目录。对于需要转储核心，写运行日志的进程将工作目录改变到特定目录如 /tmpchdir("/") </p>
				<p>6. 重设文件创建掩模 </p>
				<p>进程从创建它的父进程那里继承了文件创建掩模。它可能修改守护进程所创建的文件的存取位。为防止这一点，将文件创建掩模清除：umask(0); </p>
				<p>7. 处理SIGCHLD信号 </p>
				<p>处理SIGCHLD信号并不是必须的。但对于某些进程，特别是服务器进程往往在请求到来时生成子进程处理请求。如果父进程不等待子进程结束，子进程
将成为僵尸进程（zombie）从而占用系统资源。如果父进程等待子进程结束，将增加父进程的负担，影响服务器进程的并发性能。在Linux下可以简单地
将 SIGCHLD信号的操作设为SIG_IGN。 </p>
				<p>signal(SIGCHLD,SIG_IGN); </p>
				<p>这样，内核在子进程结束时不会产生僵尸进程。这一点与BSD4不同，BSD4下必须显式等待子进程结束才能释放僵尸进程。 </p>
				<p>三． 守护进程实例 </p>
				<p>守护进程实例包括两部分：主程序test.c和初始化程序init.c。主程序每隔一分钟向/tmp目录中的日志test.log报告运行状态。初始化程序中的init_daemon函数负责生成守护进程。读者可以利用init_daemon函数生成自己的守护进程。 </p>
				<p>1． init.c清单 </p>
				<div>
						<pre>＃i nclude &lt; unistd.h &gt;<br />＃i nclude &lt; signal.h &gt;<br />＃i nclude &lt; sys/param.h &gt;<br />＃i nclude &lt; sys/types.h &gt;<br />＃i nclude &lt; sys/stat.h &gt;<br /><br />void init_daemon(void)<br />{<br />int pid;<br />int i;<br />if(pid=fork())<br />exit(0);//是父进程，结束父进程<br />else if(pid&lt; 0)<br />exit(1);//fork失败，退出<br />//是第一子进程，后台继续执行<br />setsid();//第一子进程成为新的会话组长和进程组长<br />//并与控制终端分离<br />if(pid=fork())<br />exit(0);//是第一子进程，结束第一子进程<br />else if(pid&lt; 0)<br />exit(1);//fork失败，退出<br />//是第二子进程，继续<br />//第二子进程不再是会话组长<br /><br />for(i=0;i&lt; NOFILE;++i)//关闭打开的文件描述符<br />close(i);<br />chdir("/tmp");//改变工作目录到/tmp<br />umask(0);//重设文件创建掩模<br />return;<br />}<br /></pre>
				</div>
				<p>2． test.c清单 </p>
				<div>
						<pre>＃i nclude &lt; stdio.h &gt;<br />＃i nclude &lt; time.h &gt;<br /><br />void init_daemon(void);//守护进程初始化函数<br /><br />main()<br />{<br />FILE *fp;<br />time_t t;<br />init_daemon();//初始化为Daemon<br /><br />while(1)//每隔一分钟向test.log报告运行状态<br />{<br />sleep(60);//睡眠一分钟<br />if((fp=fopen("test.log","a")) &gt;=0)<br />{<br />t=time(0);<br />fprintf(fp,"Im here at %sn",asctime(localtime(&amp;t)) );<br />fclose(fp);<br />}<br />}<br />}<br /></pre>
				</div>
				<p>以上程序在RedHat Linux6.0下编译通过。步骤如下： </p>
				<p>编译：gcc -g -o test init.c test.c </p>
				<p>执行：./test </p>
				<p>查看进程：ps -ef </p>
				<p>从输出可以发现test守护进程的各种特性满足上面的要求。 </p>
		</span>
<img src ="http://www.blogjava.net/huyi2006/aggbug/117860.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-05-16 15:41 <a href="http://www.blogjava.net/huyi2006/articles/117860.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux启动过程综述</title><link>http://www.blogjava.net/huyi2006/articles/116947.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 12 May 2007 03:22:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/116947.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/116947.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/116947.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/116947.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/116947.html</trackback:ping><description><![CDATA[
		<blockquote>本文以Redhat 6.0 Linux 2.2.19 for Alpha/AXP为平台，描述了从开机到登录的 Linux 启动全过程。该文对i386平台同样适用。</blockquote>
		<!--START RESERVED FOR FUTURE USE INCLUDE FILES-->
		<!-- include java script once we verify teams wants to use this and it will work on dbcs and cyrillic characters -->
		<!--END RESERVED FOR FUTURE USE INCLUDE FILES-->
		<p>
				<a name="1">
						<span class="atitle">Bootloader</span>
				</a>
		</p>
		<p>在Alpha/AXP平台上引导Linux通常有两种方法，一种是由MILO及其他类似的引导程序引导，另一种是由Firmware直接引导。MILO功能与i386平台的LILO相近，但内置有基本的磁盘驱动程序（如IDE、SCSI等），以及常见的文件系统驱动程序（如ext2，iso9660等）， firmware有ARC、SRM两种形式，ARC具有类BIOS界面，甚至还有多重引导的设置；而SRM则具有功能强大的命令行界面，用户可以在控制台上使用boot等命令引导系统。ARC有分区（Partition）的概念，因此可以访问到分区的首扇区；而SRM只能将控制转给磁盘的首扇区。两种firmware都可以通过引导MILO来引导Linux，也可以直接引导Linux的引导代码。</p>
		<p>“arch/alpha/boot”下就是制作Linux Bootloader的文件。“head.S”文件提供了对 OSF PAL/1的调用入口，它将被编译后置于引导扇区（ARC的分区首扇区或SRM的磁盘0扇区），得到控制后初始化一些数据结构，再将控制转给“main.c”中的start_kernel()， start_kernel()向控制台输出一些提示，调用pal_init()初始化PAL代码，调用openboot() 打开引导设备（通过读取Firmware环境），调用load()将核心代码加载到START_ADDR（见 “include/asm-alpha/system.h”），再将Firmware中的核心引导参数加载到ZERO_PAGE(0) 中，最后调用runkernel()将控制转给0x100000的kernel，bootloader部分结束。</p>
		<p>Bootloader中使用的所有“srm_”函数在“arch/alpha/lib/”中定义。</p>
		<p>以上这种Boot方式是一种最简单的方式，即不需其他工具就能引导Kernel，前提是按照 Makefile的指导，生成bootimage文件，内含以上提到的bootloader以及vmlinux，然后将 bootimage写入自磁盘引导扇区始的位置中。</p>
		<p>当采用MILO这样的引导程序来引导Linux时，不需要上面所说的Bootloader，而只需要 vmlinux或vmlinux.gz，引导程序会主动解压加载内核到0x1000（小内核）或0x100000（大内核），并直接进入内核引导部分，即本文的第二节。</p>
		<blockquote>
				<b>对于I386平台</b>
				<br />i386系统中一般都有BIOS做最初的引导工作，那就是将四个主分区表中的第一个可引导分区的第一个扇区加载到实模式地址0x7c00上，然后将控制转交给它。 </blockquote>
		<blockquote>在“arch/i386/boot”目录下，bootsect.S是生成引导扇区的汇编源码，它首先将自己拷贝到0x90000上，然后将紧接其后的setup部分（第二扇区）拷贝到0x90200，将真正的内核代码拷贝到0x100000。以上这些拷贝动作都是以bootsect.S、setup.S以及vmlinux在磁盘上连续存放为前提的，也就是说，我们的bzImage文件或者zImage文件是按照bootsect，setup， vmlinux这样的顺序组织，并存放于始于引导分区的首扇区的连续磁盘扇区之中。</blockquote>
		<blockquote>bootsect.S完成加载动作后，就直接跳转到0x90200，这里正是setup.S的程序入口。 setup.S的主要功能就是将系统参数（包括内存、磁盘等，由BIOS返回）拷贝到 0x90000-0x901FF内存中，这个地方正是bootsect.S存放的地方，这时它将被系统参数覆盖。以后这些参数将由保护模式下的代码来读取。</blockquote>
		<blockquote>除此之外，setup.S还将video.S中的代码包含进来，检测和设置显示器和显示模式。最后，setup.S将系统转换到保护模式，并跳转到0x100000（对于bzImage格式的大内核是 0x100000，对于zImage格式的是0x1000）的内核引导代码，Bootloader过程结束。</blockquote>
		<blockquote>
				<b>对于2.4.x版内核</b>
				<br />没有什么变化。 </blockquote>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="2">
						<span class="atitle">Kernel引导入口</span>
				</a>
		</p>
		<p>
		</p>
		<blockquote>
				<b>对于I386平台</b>
				<br />在i386体系结构中，因为i386本身的问题，在"arch/alpha/kernel/head.S"中需要更多的设置，但最终也是通过call SYMBOL_NAME(start_kernel)转到start_kernel()这个体系结构无关的函数中去执行了。 </blockquote>
		<blockquote>所不同的是，在i386系统中，当内核以bzImage的形式压缩，即大内核方式（__BIG_KERNEL__）压缩时就需要预先处理bootsect.S和setup.S，按照大核模式使用$(CPP) 处理生成bbootsect.S和bsetup.S，然后再编译生成相应的.o文件，并使用 "arch/i386/boot/compressed/build.c"生成的build工具，将实际的内核（未压缩的，含 kernel中的head.S代码）与"arch/i386/boot/compressed"下的head.S和misc.c合成到一起，其中的head.S代替了"arch/i386/kernel/head.S"的位置，由Bootloader引导执行（startup_32入口），然后它调用misc.c中定义的decompress_kernel()函数，使用 "lib/inflate.c"中定义的gunzip()将内核解压到0x100000，再转到其上执行 "arch/i386/kernel/head.S"中的startup_32代码。</blockquote>
		<blockquote>
				<b>对于2.4.x版内核</b>
				<br />没有变化。 </blockquote>
		<br />
		<table cellspacing="0" cellpadding="0" width="100%" border="0">
				<tbody>
						<tr>
								<td>
										<img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" />
										<br />
										<img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" />
								</td>
						</tr>
				</tbody>
		</table>
		<table class="no-print" cellspacing="0" cellpadding="0" align="right">
				<tbody>
						<tr align="right">
								<td>
										<img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" />
										<br />
										<table cellspacing="0" cellpadding="0" border="0">
												<tbody>
														<tr>
																<td valign="center">
																		<img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" />
																		<br />
																</td>
																<td valign="top" align="right">
																		<a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main">
																				<b>
																						<font color="#996699">回页首</font>
																				</b>
																		</a>
																</td>
														</tr>
												</tbody>
										</table>
								</td>
						</tr>
				</tbody>
		</table>
		<br />
		<br />
		<p>
				<a name="3">
						<span class="atitle">核心数据结构初始化--内核引导第一部分</span>
				</a>
		</p>
		<p>
		</p>
		<p>start_kernel()中调用了一系列初始化函数，以完成kernel本身的设置。这些动作有的是公共的，有的则是需要配置的才会执行的。</p>
		<blockquote>在start_kernel()函数中，</blockquote>
		<p>
		</p>
		<ul>
				<li>输出Linux版本信息（printk(linux_banner)） 
</li>
				<li>设置与体系结构相关的环境（setup_arch()） 
</li>
				<li>页表结构初始化（paging_init()） 
</li>
				<li>使用"arch/alpha/kernel/entry.S"中的入口点设置系统自陷入口（trap_init()） 
</li>
				<li>使用alpha_mv结构和entry.S入口初始化系统IRQ（init_IRQ()） 
</li>
				<li>核心进程调度器初始化（包括初始化几个缺省的Bottom-half，sched_init()） 
</li>
				<li>时间、定时器初始化（包括读取CMOS时钟、估测主频、初始化定时器中断等，time_init()） 
</li>
				<li>提取并分析核心启动参数（从环境变量中读取参数，设置相应标志位等待处理，（parse_options()） 
</li>
				<li>控制台初始化（为输出信息而先于PCI初始化，console_init()） 
</li>
				<li>剖析器数据结构初始化（prof_buffer和prof_len变量） 
</li>
				<li>核心Cache初始化（描述Cache信息的Cache，kmem_cache_init()） 
</li>
				<li>延迟校准（获得时钟jiffies与CPU主频ticks的延迟，calibrate_delay()） 
</li>
				<li>内存初始化（设置内存上下界和页表项初始值，mem_init()） 
</li>
				<li>创建和设置内部及通用cache（"slab_cache"，kmem_cache_sizes_init()） 
</li>
				<li>创建uid taskcount SLAB cache（"uid_cache"，uidcache_init()） 
</li>
				<li>创建文件cache（"files_cache"，filescache_init()） 
</li>
				<li>创建目录cache（"dentry_cache"，dcache_init()） 
</li>
				<li>创建与虚存相关的cache（"vm_area_struct"，"mm_struct"，vma_init()） 
</li>
				<li>块设备读写缓冲区初始化（同时创建"buffer_head"cache用户加速访问，buffer_init()） 
</li>
				<li>创建页cache（内存页hash表初始化，page_cache_init()） 
</li>
				<li>创建信号队列cache（"signal_queue"，signals_init()） 
</li>
				<li>初始化内存inode表（inode_init()） 
</li>
				<li>创建内存文件描述符表（"filp_cache"，file_table_init()） 
</li>
				<li>检查体系结构漏洞（对于alpha，此函数为空，check_bugs()） 
</li>
				<li>SMP机器其余CPU（除当前引导CPU）初始化（对于没有配置SMP的内核，此函数为空，smp_init()） 
</li>
				<li>启动init过程（创建第一个核心线程，调用init()函数，原执行序列调用cpu_idle() 等待调度，init()） </li>
		</ul>至此start_kernel()结束，基本的核心环境已经建立起来了。 
<p></p><blockquote><b>对于I386平台</b><br />i386平台上的内核启动过程与此基本相同，所不同的主要是实现方式。 </blockquote><blockquote><b>对于2.4.x版内核</b><br />2.4.x中变化比较大，但基本过程没变，变动的是各个数据结构的具体实现，比如Cache。 </blockquote><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="4"><span class="atitle">外设初始化--内核引导第二部分</span></a></p><p>init()函数作为核心线程，首先锁定内核（仅对SMP机器有效），然后调用 do_basic_setup()完成外设及其驱动程序的加载和初始化。过程如下：</p><p></p><ul><li>总线初始化（比如pci_init()） 
</li><li>网络初始化（初始化网络数据结构，包括sk_init()、skb_init()和proto_init()三部分，在proto_init()中，将调用protocols结构中包含的所有协议的初始化过程，sock_init()） 
</li><li>创建bdflush核心线程（bdflush()过程常驻核心空间，由核心唤醒来清理被写过的内存缓冲区，当bdflush()由kernel_thread()启动后，它将自己命名为kflushd） 
</li><li>创建kupdate核心线程（kupdate()过程常驻核心空间，由核心按时调度执行，将内存缓冲区中的信息更新到磁盘中，更新的内容包括超级块和inode表） 
</li><li>设置并启动核心调页线程kswapd（为了防止kswapd启动时将版本信息输出到其他信息中间，核心线调用kswapd_setup()设置kswapd运行所要求的环境，然后再创建 kswapd核心线程） 
</li><li>创建事件管理核心线程（start_context_thread()函数启动context_thread()过程，并重命名为keventd） 
</li><li>设备初始化（包括并口parport_init()、字符设备chr_dev_init()、块设备 blk_dev_init()、SCSI设备scsi_dev_init()、网络设备net_dev_init()、磁盘初始化及分区检查等等，device_setup()） 
</li><li>执行文件格式设置（binfmt_setup()） 
</li><li>启动任何使用__initcall标识的函数（方便核心开发者添加启动函数，do_initcalls()） 
</li><li>文件系统初始化（filesystem_setup()） 
</li><li>安装root文件系统（mount_root()） </li></ul>至此do_basic_setup()函数返回init()，在释放启动内存段（free_initmem()）并给内核解锁以后，init()打开/dev/console设备，重定向stdin、stdout和stderr到控制台，最后，搜索文件系统中的init程序（或者由init=命令行参数指定的程序），并使用 execve()系统调用加载执行init程序。 
<p></p><p>init()函数到此结束，内核的引导部分也到此结束了，这个由start_kernel()创建的第一个线程已经成为一个用户模式下的进程了。此时系统中存在着六个运行实体：</p><ul><li>start_kernel()本身所在的执行体，这其实是一个"手工"创建的线程，它在创建了init()线程以后就进入cpu_idle()循环了，它不会在进程（线程）列表中出现 
</li><li>init线程，由start_kernel()创建，当前处于用户态，加载了init程序 
</li><li>kflushd核心线程，由init线程创建，在核心态运行bdflush()函数 
</li><li>kupdate核心线程，由init线程创建，在核心态运行kupdate()函数 
</li><li>kswapd核心线程，由init线程创建，在核心态运行kswapd()函数 
</li><li>keventd核心线程，由init线程创建，在核心态运行context_thread()函数 </li></ul><blockquote><b>对于I386平台</b><br />基本相同。 </blockquote><blockquote><b>对于2.4.x版内核</b><br />这一部分的启动过程在2.4.x内核中简化了不少，缺省的独立初始化过程只剩下网络（sock_init()）和创建事件管理核心线程，而其他所需要的初始化都使用__initcall()宏包含在do_initcalls()函数中启动执行。 </blockquote><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="5"><span class="atitle">init进程和inittab引导指令</span></a></p><p>init进程是系统所有进程的起点，内核在完成核内引导以后，即在本线程（进程）空间内加载init程序，它的进程号是1。</p><p>init程序需要读取/etc/inittab文件作为其行为指针，inittab是以行为单位的描述性（非执行性）文本，每一个指令行都具有以下格式：</p><p>id:runlevel:action:process其中id为入口标识符，runlevel为运行级别，action为动作代号，process为具体的执行程序。</p><p>id一般要求4个字符以内，对于getty或其他login程序项，要求id与tty的编号相同，否则getty程序将不能正常工作。</p><p>runlevel是init所处于的运行级别的标识，一般使用0－6以及S或s。0、1、6运行级别被系统保留，0作为shutdown动作，1作为重启至单用户模式，6为重启；S和s意义相同，表示单用户模式，且无需inittab文件，因此也不在inittab中出现，实际上，进入单用户模式时，init直接在控制台（/dev/console）上运行/sbin/sulogin。</p><p>在一般的系统实现中，都使用了2、3、4、5几个级别，在Redhat系统中，2表示无NFS支持的多用户模式，3表示完全多用户模式（也是最常用的级别），4保留给用户自定义，5表示XDM图形登录方式。7－9级别也是可以使用的，传统的Unix系统没有定义这几个级别。runlevel可以是并列的多个值，以匹配多个运行级别，对大多数action来说，仅当runlevel与当前运行级别匹配成功才会执行。</p><p>initdefault是一个特殊的action值，用于标识缺省的启动级别；当init由核心激活以后，它将读取inittab中的initdefault项，取得其中的runlevel，并作为当前的运行级别。如果没有inittab文件，或者其中没有initdefault项，init将在控制台上请求输入 runlevel。</p><p>sysinit、boot、bootwait等action将在系统启动时无条件运行，而忽略其中的runlevel，其余的action（不含initdefault）都与某个runlevel相关。各个action的定义在inittab的man手册中有详细的描述。</p><p>在Redhat系统中，一般情况下inittab都会有如下几项：</p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td class="code-outline"><pre class="displaycode">id:3:initdefault:
#表示当前缺省运行级别为3--完全多任务模式；
si::sysinit:/etc/rc.d/rc.sysinit
#启动时自动执行/etc/rc.d/rc.sysinit脚本
l3:3:wait:/etc/rc.d/rc 3    
#当运行级别为3时，以3为参数运行/etc/rc.d/rc脚本，init将等待其返回
0:12345:respawn:/sbin/mingetty tty0 
#在1－5各个级别上以tty0为参数执行/sbin/mingetty程序，打开tty0终端用于
#用户登录，如果进程退出则再次运行mingetty程序
x:5:respawn:/usr/bin/X11/xdm -nodaemon
#在5级别上运行xdm程序，提供xdm图形方式登录界面，并在退出时重新执行
</pre></td></tr></tbody></table><br /><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="6"><span class="atitle">rc启动脚本</span></a></p><p>上一节已经提到init进程将启动运行rc脚本，这一节将介绍rc脚本具体的工作。</p><p>一般情况下，rc启动脚本都位于/etc/rc.d目录下，rc.sysinit中最常见的动作就是激活交换分区，检查磁盘，加载硬件模块，这些动作无论哪个运行级别都是需要优先执行的。仅当rc.sysinit执行完以后init才会执行其他的boot或bootwait动作。</p><p>如果没有其他boot、bootwait动作，在运行级别3下，/etc/rc.d/rc将会得到执行，命令行参数为3，即执行/etc/rc.d/rc3.d/目录下的所有文件。rc3.d下的文件都是指向/etc/rc.d/init.d/目录下各个Shell脚本的符号连接，而这些脚本一般能接受start、stop、restart、status等参数。rc脚本以start参数启动所有以S开头的脚本，在此之前，如果相应的脚本也存在K打头的链接，而且已经处于运行态了（以/var/lock/subsys/下的文件作为标志），则将首先启动K开头的脚本，以stop作为参数停止这些已经启动了的服务，然后再重新运行。显然，这样做的直接目的就是当init改变运行级别时，所有相关的服务都将重启，即使是同一个级别。</p><p>rc程序执行完毕后，系统环境已经设置好了，下面就该用户登录系统了。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="7"><span class="atitle">getty和login</span></a></p><p>在rc返回后，init将得到控制，并启动mingetty（见第五节）。mingetty是getty的简化，不能处理串口操作。getty的功能一般包括：</p><ul><li>打开终端线，并设置模式 
</li><li>输出登录界面及提示，接受用户名的输入 
</li><li>以该用户名作为login的参数，加载login程序 </li></ul><p>注：用于远程登录的提示信息位于/etc/issue.net中。</p><p>login程序在getty的同一个进程空间中运行，接受getty传来的用户名参数作为登录的用户名。</p><p>如果用户名不是root，且存在/etc/nologin文件，login将输出nologin文件的内容，然后退出。这通常用来系统维护时防止非root用户登录。</p><p>只有/etc/securetty中登记了的终端才允许root用户登录，如果不存在这个文件，则root可以在任何终端上登录。/etc/usertty文件用于对用户作出附加访问限制，如果不存在这个文件，则没有其他限制。</p><p>当用户登录通过了这些检查后，login将搜索/etc/passwd文件（必要时搜索 /etc/shadow文件）用于匹配密码、设置主目录和加载shell。如果没有指定主目录，将默认为根目录；如果没有指定shell，将默认为/bin/sh。在将控制转交给shell以前， getty将输出/var/log/lastlog中记录的上次登录系统的信息，然后检查用户是否有新邮件（/usr/spool/mail/{username}）。在设置好shell的uid、gid，以及TERM，PATH 等环境变量以后，进程加载shell，login的任务也就完成了。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="8"><span class="atitle">bash</span></a></p><p>运行级别3下的用户login以后，将启动一个用户指定的shell，以下以/bin/bash为例继续我们的启动过程。</p><p>bash是Bourne Shell的GNU扩展，除了继承了sh的所有特点以外，还增加了很多特性和功能。由login启动的bash是作为一个登录shell启动的，它继承了getty设置的TERM、PATH等环境变量，其中PATH对于普通用户为"/bin:/usr/bin:/usr/local/bin"，对于root 为"/sbin:/bin:/usr/sbin:/usr/bin"。作为登录shell，它将首先寻找/etc/profile 脚本文件，并执行它；然后如果存在~/.bash_profile，则执行它，否则执行 ~/.bash_login，如果该文件也不存在，则执行~/.profile文件。然后bash将作为一个交互式shell执行~/.bashrc文件（如果存在的话），很多系统中，~/.bashrc都将启动 /etc/bashrc作为系统范围内的配置文件。</p><p>当显示出命令行提示符的时候，整个启动过程就结束了。此时的系统，运行着内核，运行着几个核心线程，运行着init进程，运行着一批由rc启动脚本激活的守护进程（如 inetd等），运行着一个bash作为用户的命令解释器。</p><br /><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td><img height="1" alt="" src="http://www.ibm.com/i/v14/rules/blue_rule.gif" width="100%" /><br /><img height="6" alt="" src="http://www.ibm.com/i/c.gif" width="8" border="0" /></td></tr></tbody></table><table class="no-print" cellspacing="0" cellpadding="0" align="right"><tbody><tr align="right"><td><img height="4" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /><br /><table cellspacing="0" cellpadding="0" border="0"><tbody><tr><td valign="center"><img height="16" alt="" src="http://www.ibm.com/i/v14/icons/u_bold.gif" width="16" border="0" /><br /></td><td valign="top" align="right"><a class="fbox" href="http://www.ibm.com/developerworks/cn/linux/kernel/startup/#main"><b><font color="#996699">回页首</font></b></a></td></tr></tbody></table></td></tr></tbody></table><br /><br /><p><a name="9"><span class="atitle">附：XDM方式登录</span></a></p><p>如果缺省运行级别设为5，则系统中不光有1-6个getty监听着文本终端，还有启动了一个XDM的图形登录窗口。登录过程和文本方式差不多，也需要提供用户名和口令，XDM 的配置文件缺省为/usr/X11R6/lib/X11/xdm/xdm-config文件，其中指定了 /usr/X11R6/lib/X11/xdm/xsession作为XDM的会话描述脚本。登录成功后，XDM将执行这个脚本以运行一个会话管理器，比如gnome-session等。</p><p>除了XDM以外，不同的窗口管理系统（如KDE和GNOME）都提供了一个XDM的替代品，如gdm和kdm，这些程序的功能和XDM都差不多。<br /><br /></p><p><a name="author"><span class="atitle">关于作者</span></a></p><p></p><table cellspacing="0" cellpadding="0" width="100%" border="0"><tbody><tr><td colspan="3"><img height="5" alt="" src="http://www.ibm.com/i/c.gif" width="100%" /></td></tr><tr valign="top" align="left"><td><p></p></td><td><img height="5" alt="" src="http://www.ibm.com/i/c.gif" width="4" /></td><td width="100%"><p><b>杨沙洲，</b>男，现攻读国防科大计算机学院计算机软件方向博士学位。您可以通过电子邮件 <a href="mailto:pubb@163.net?cc=pubb@163.net"><font color="#5c81a7">pubb@163.net</font></a>跟他联系。 </p></td></tr></tbody></table><img src ="http://www.blogjava.net/huyi2006/aggbug/116947.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-05-12 11:22 <a href="http://www.blogjava.net/huyi2006/articles/116947.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>IPC之共享存储区</title><link>http://www.blogjava.net/huyi2006/articles/115339.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 04 May 2007 09:27:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/115339.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/115339.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/115339.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/115339.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/115339.html</trackback:ping><description><![CDATA[
		<p>IPC之共享存储区<br />用共享存储区进行进程间通信主要有以下步骤组成<br /><strong>1. Create shared memory</strong> <br />int shmget(key_t key, int size, int shmflg); <br />if ((shm_id = shmget (mykey, sizeof (struct sharedbuf), 0600 | IFLAGS)) &lt; 0)<br />    perror ("shmget");</p>
		<p>
				<strong>2. Attach shared memory</strong> <br />char *buf = shmat (shm_id, 0, 0);<br /> <br /><strong>3. Read / Write shared memory</strong> <br />sharedbuf-&gt;size = size_; <br />memcpy(sharedbuf-&gt;buf, mybuf, size_); <br />memcpy(mybuf, sharedbuf-&gt;buf, sharedbuf-&gt;size);  <br /> <br /><strong>3. Detach shared memory (optional)</strong> <br />shmdt (buf);<br /> <br /><strong>4. Remove shared memory</strong> <br />if (shmctl (shm_id, IPC_RMID, (struct shmid_ds *)0) &lt; 0)  <br />    perror ("shmctl");</p>
		<p>一个测试过的实例<br />#include &lt;stdio.h&gt;<br />#include &lt;sys/types.h&gt;<br />#include &lt;sys/shm.h&gt;<br />#include &lt;sys/ipc.h&gt;<br />#define SHM_MODE (SHM_R | SHM_W)<br />#define SHM_SIZE 2048</p>
		<p>int main()<br />{<br /> int segment_id, segment_size;<br /> char *shared_memory;<br /> pid_t pid;<br /> <br /> if((segment_id = shmget(IPC_PRIVATE, SHM_SIZE, SHM_MODE)) &lt; 0)/*获得共享内存标识符*/<br />  perror("shmget error!\n");<br /> if((shared_memory = shmat(segment_id, 0, 0)) == (void *)-1)/*进程和共享内存段相连接*/<br />  perror("shmat error!\n");<br /> printf("test1 send a message to share memory.\n");<br /> sprintf(shared_memory, "Hello test2\n");<br /> shmdt(shared_memory);/*脱离链接*/<br /> pid = fork();<br /> if(pid &lt; 0)<br />  perror("Creating process error!\n");<br /> else if(pid &gt; 0)<br /> {<br />  wait(NULL); /*父进程等待子进程结束*/<br />  shmctl(segment_id, IPC_RMID, 0);/*子进程结束，父进程将共享内存删除*/<br />  exit(0);<br /> }<br /> else<br /> {                                                                                                                                                                        <br />  if((shared_memory = shmat(segment_id, 0, 0)) == (void*)-1)/*子进程和共享内存连接*/<br />   perror("shmat error!\n");<br />  printf("test2 get a message form share memory:%s",shared_memory);<br />  shmdt(shared_memory);<br /> }<br />}     <br /><br />相关参考：<a href="http://blog.csdn.net/Apollo_zhc/archive/2006/06/01/768694.aspx">http://blog.csdn.net/Apollo_zhc/archive/2006/06/01/768694.aspx</a></p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/115339.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-05-04 17:27 <a href="http://www.blogjava.net/huyi2006/articles/115339.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux的c编程技巧</title><link>http://www.blogjava.net/huyi2006/articles/110693.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Sat, 14 Apr 2007 13:03:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/110693.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/110693.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/110693.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/110693.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/110693.html</trackback:ping><description><![CDATA[
		<p>linux的c编程技巧</p>
		<p>1. 获取文件的信息：<br />stat(char* filename, struct stat* buf);<br />struct stat { <br />dev_t st_dev; /* 设备 */ <br />ino_t st_ino; /* 节点 */ <br />mode_t st_mode; /* 模式 */ <br />nlink_t st_nlink; /* 硬连接 */ <br />uid_t st_uid; /* 用户ID */ <br />gid_t st_gid; /* 组ID */ <br />dev_t st_rdev; /* 设备类型 */ <br />off_t st_off; /* 文件字节数 */ <br />unsigned long st_blksize; /* 块大小 */ <br />unsigned long st_blocks; /* 块数 */ <br />time_t st_atime; /* 最后一次访问时间 */ <br />time_t st_mtime; /* 最后一次修改时间 */ <br />time_t st_ctime; /* 最后一次改变时间(指属性) */ <br />};</p>
		<p>struct statfs <br />{<br />long f_type; /* 文件系统类型 */<br />long f_bsize; /* 块大小*/<br />long f_blocks; /* 块多少*/<br />long f_bfree; /* 空闲的块（）*/<br />long f_bavail; /* 可用块 */<br />long f_files; /* 总文件节点 */<br />long f_ffree; /* 空闲文件节点 */<br />fsid_t f_fsid; /* 文件系统id */<br />long f_namelen; /* 文件名的最大长度 */<br />long f_spare[6]; /* spare for later */<br />};</p>
		<p>2. 获取文件访问权限或者判断文件是否存在：<br />int access(char* filename, int mode);</p>
		<p>3. 获取当前时间：<br />time_t t;char* asctime(localtime(&amp;t));<br />或者<br />time(&amp;t);char* ctime(&amp;t);<br />得到的字符串形式为：Wed Mar 12 10:07:53 2003</p>
		<p>4. 计算两个时刻之间的时间差<br />double difftime(time_t time2, time_t time1);</p>
		<p>5. 删除某文件：<br />int unlink(char* pathname);<br />int remove(char* pathname);</p>
		<p>6. 删除某目录：<br />int rmdir(const char* pathname);</p>
		<p>7. 获得当前所在目录名：<br />char * getcwd(char *buf,size_t size); buf将会返回目前路径名称。 </p>
		<p>8. 获取目录信息：<br />DIR * opendir(const char * pathname);<br />int closedir(DIR *dir);<br />struct dirent * readdir(DIR *dir);<br />struct dirent <br />{ <br />long d_ino; /* inode number */ <br />off_t d_off; /* offset to this dirent */ <br />unsigned short d_reclen; /* length of this d_name */ <br />char d_name [NAME_MAX+1]; /* file name (null-terminated) */ <br />};</p>
		<p>9. strerror(errno);函数会返回一个指定的错误号的错误信息的字符串. </p>
		<p>10.得到当前路径下面所有的文件(包含目录)的个数 <br />struct dirent **namelist; <br />int num = scandir(".",&amp;namelist,0,alphasort)</p>
		<p>11./etc/ld.so.conf：包含共享库的搜索位置 <br />查看执行文件调用了哪些共享库<br />shell&gt;ldd a.out<br />共享库管理工具，一般在更新了共享库之后要运行该命令 <br />shell&gt;ldconfig</p>
		<p>12.查看文件执行的速度<br />shell&gt;time ./a.out</p>
		<p>13.改变文件访问权限<br />int chmod(const char* path, mode_t mode);</p>
		<p>14.改变文件大小 <br />int chsize(int handle, long size);</p>
		<p>15.把一个浮点数转换为字符串 <br />char ecvt(double value, int ndigit, int *decpt, int *sign);</p>
		<p>16.检测文件结束<br />int eof(int *handle);</p>
		<p>17.检测流上的文件结束符 <br />int feof(FILE *stream);</p>
		<p>18.检测流上的错误<br />int ferror(FILE *stream);</p>
		<p>19.装入并运行其它程序的函数<br />int execl(char *pathname, char *arg0, arg1, ..., argn, NULL);<br />int execle(char *pathname, char *arg0, arg1, ..., argn, NULL,<br />char *envp[]);<br />int execlp(char *pathname, char *arg0, arg1, .., NULL); <br />int execple(char *pathname, char *arg0, arg1, ..., NULL, <br />char *envp[]); <br />int execv(char *pathname, char *argv[]); <br />int execve(char *pathname, char *argv[], char *envp[]); <br />int execvp(char *pathname, char *argv[]); <br />int execvpe(char *pathname, char *argv[], char *envp[]);</p>
		<p>20.指数函数 <br />double exp(double x);</p>
		<p>21. struct sockaddr <br />{<br />unsigned short sa_family; /* address family, AF_xxx */<br />char sa_data[14]; /* 14 bytes of protocol address */<br />};<br />struct sockaddr_in<br />{<br />short int sin_family; /* Address family */<br />unsigned short int sin_port; /* Port number */<br />struct in_addr sin_addr; /* Internet address */<br />unsigned char sin_zero[8]; /* Same size as struct sockaddr */<br />};<br />struct in_addr <br />{<br />unsigned long s_addr;<br />};<br />s_addr按照网络字节顺序存储IP地址<br />sin_zero是为了让sockaddr与sockaddr_in两个数据结构保持大小相同而保留的空字节。</p>
		<p>使用的例子：<br />struct sockaddr_in sa;<br />sa.sin_family = AF_INET;<br />sa.sin_port = htons(3490);<br />sa.sin_addr.s_addr = inet_addr("132.241.5.10");<br />bzero(&amp;(sa.sin_zero), 8);<br />注意：如果sa.sin_addr.s_addr ＝ INADDR_ANY，则不指定IP地址（用于Server程序）</p>
		<p>22. #define UNIX_PATH_MAX 108<br />struct sockaddr_un <br />{<br />sa_family_t sun_family; /* AF_UNIX */<br />char sun_path[UNIX_PATH_MAX]; /* 路径名 */<br />};</p>
		<p>23. IP地址转换函数:<br />unsigned long inet_addr (const char *cp);<br />inet_addr将一个点分十进制IP地址字符串转换成32位数字表示的IP地址（网络字节顺序）</p>
		<p>char* inet_ntoa (struct in_addr in);<br />inet_ntoa将一个32位数字表示的IP地址转换成点分十进制IP地址字符串。</p>
		<p>这两个函数互为反函数</p>
		<p>字节顺序转换:<br />htons()--"Host to Network Short"<br />htonl()--"Host to Network Long"<br />ntohs()--"Network to Host Short"<br />ntohl()--"Network to Host Long"　</p>
		<p>24. 获取当前机器的CPU、内存使用情况<br />getrusage </p>
		<p>25. open的使用中常用的flag和mode参数<br />int FILE_FLAG = O_WRONLY|O_APPEND|O_CREAT;<br />int FILE_MODE = S_IRUSR|S_IWUSR|S_IRGRP|S_IROTH;</p>
		<p>26. makefile中常用的符号：<br />预定义变量 含义<br />$* 不包含扩展名的目标文件名称。<br />$@ 目标的完整名称<br />$% 如果目标是归档成员，则该变量表示目标的归档成员名称。例如，如果目标名称<br />为 mytarget.so(image.o)，则 $@ 为 mytarget.so，而 $% 为 image.o。</p>
		<p>$+ 所有的依赖文件，以空格分开，并以出现的先后为序，可能包含重复的依赖文件。<br />$&lt; 第一个依赖文件的名称。<br />$? 所有的依赖文件，以空格分开，这些依赖文件的修改日期比目标的创建日期晚<br />$^ 所有的依赖文件，以空格分开，不包含重复的依赖文件。</p>
		<p>AR 归档维护程序的名称，默认值为 ar。<br />ARFLAGS 归档维护程序的选项。<br />AS 汇编程序的名称，默认值为 as。<br />ASFLAGS 汇编程序的选项。<br />CC C 编译器的名称，默认值为 cc。<br />CCFLAGS C 编译器的选项。<br />CPP C 预编译器的名称，默认值为 $(CC) -E。<br />CPPFLAGS C 预编译的选项。<br />CXX C++ 编译器的名称，默认值为 g++。<br />CXXFLAGS C++ 编译器的选项。<br />FC FORTRAN 编译器的名称，默认值为 f77。<br />FFLAGS FORTRAN 编译器的选项。</p>
		<p>用变量object表示所有的.o文件：<br />objects := $(wildcard *.o)</p>
		<p>make -n或者--just-print表示只是显示命令，但不会执行命令<br />make -s或者--slient表示全面禁止命令的显示<br />make -i或者--ignore-errors表示Makefile中所有命令都会忽略错误<br />make -k或者--keep-going表示如果某规则中的命令出错了，那么就终止该规则的执行，但继续执行其它规则</p>
		<p>在makefile中直接利用shell获取变量PLAT<br />使用make中的一种用变量来定义变量的方法。这种方法使用的是“:=”操作符<br />PLAT := $(shell uname -a)</p>
		<p>我们要定义一个变量，其值是一个空格，那么我们可以这样来：<br />nullstring :=<br />space := $(nullstring) # end of the line</p>
		<p>FOO ?= bar含义是:<br />如果FOO没有被定义过，那么变量FOO的值就是“bar”，如果FOO先前被定义过，那么这条语将什么也不做，其等价于：<br />ifeq ($(origin FOO), undefined)<br />FOO = bar<br />endif</p>
		<p>foo := a.o b.o c.o<br />bar := $(foo:.o=.c)<br />我们先定义了一个“$(foo)”变量，而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”，所以我们的“$(bar)”的值就是“a.c b.c c.c”。</p>
		<p>
				<br />你想在Makefile中设置参数值，你可以使用“override”指示符。其语法是：<br />override &lt;variable&gt; = &lt;value&gt;<br />override &lt;variable&gt; := &lt;value&gt;<br />当然，你还可以追加：<br />override &lt;variable&gt; += &lt;more text&gt;</p>
		<p>make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中，<br />但是如果Makefile中已定义了这个变量，或是这个变量由make命令行带入，<br />那么系统的环境变量的值将被覆盖。<br />如果make指定了“-e”参数，那么，系统环境变量将覆盖Makefile中定义的变量<br /></p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/110693.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-04-14 21:03 <a href="http://www.blogjava.net/huyi2006/articles/110693.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>编写Linux/Unix守护进程 [zz]</title><link>http://www.blogjava.net/huyi2006/articles/110431.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 13 Apr 2007 06:04:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/110431.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/110431.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/110431.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/110431.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/110431.html</trackback:ping><description><![CDATA[
		<div class="blogbody">
				<h2 class="title">编写Linux/Unix守护进程 [zz]</h2>
				<div class="posted">作者 liubin 16:19 | <img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="11" alt="Permalink" src="http://blog.itpub.net//templates/blueish/post.gif" width="10" /><a href="http://liubin.itpub.net/post/325/24427"><font color="#999966">静态链接网址</font></a> | <img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="11" alt="Comments" src="http://blog.itpub.net//templates/blueish/bubble.gif" width="11" /><a href="http://liubin.itpub.net/post/325/24427"><font color="#999966">最新回复 (1)</font></a> | <img style="BORDER-TOP-WIDTH: 0px; BORDER-LEFT-WIDTH: 0px; BORDER-BOTTOM-WIDTH: 0px; BORDER-RIGHT-WIDTH: 0px" height="11" alt="Trackback" src="http://blog.itpub.net//templates/blueish/trackback.gif" width="16" /><a href="http://liubin.itpub.net/trackbacks/325/24427"><font color="#999966">引用 (0)</font></a> | <a href="http://liubin.itpub.net/category/325/7256"><font color="#999966">C++</font></a></div>
		</div>守护进程在Linux/Unix系统中有着广泛的应用。有时，开发人员也想把自己的程序变成守护进程。在创建一个守护进程的时候，要接触到子进程、进程组、会晤期、信号机制、文件、目录和控制终端等多个概念。因此守护进程还是比较复杂的，在这里详细地讨论Linux/Unix的守护进程的编写，总结出八条经验，并给出应用范例。 <br /><br /><font size="2"><font face="Helvetica"> <b>编程要点</b><br /><br />    1.屏蔽一些有关控制终端操作的信号。防止在守护进程没有正常运转起来时，控制终端受到干扰退出或挂起。示例如下： <br /><br /><ccid /></font></font><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />signal(SIGTTOU,SIG_IGN); 
signal(SIGTTIN,SIG_IGN); 
signal(SIGTSTP,SIG_IGN); 
signal(SIGHUP ,SIG_IGN);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    所有的信号都有自己的名字。这些名字都以“SIG”开头，只是后面有所不同。开发人员可以通过这些名字了解到系统中发生了什么事。当信号出现时，开发人员可以要求系统进行以下三种操作：<br />    ◆ 忽略信号。大多数信号都是采取这种方式进行处理的，这里就采用了这种用法。但值得注意的是对SIGKILL和SIGSTOP信号不能做忽略处理。<br />    ◆ 捕捉信号。最常见的情况就是，如果捕捉到SIGCHID信号，则表示子进程已经终止。然后可在此信号的捕捉函数中调用waitpid()函数取得该子进程的进程ID和它的终止状态。另外，如果进程创建了临时文件，那么就要为进程终止信号SIGTERM编写一个信号捕捉函数来清除这些临时文件。<br />    ◆ 执行系统的默认动作。对绝大多数信号而言，系统的默认动作都是终止该进程。 <br /><br />    对这些有关终端的信号，一般采用忽略处理，从而保障了终端免受干扰。 <br /><br />    这类信号分别是，SIGTTOU（表示后台进程写控制终端）、SIGTTIN（表示后台进程读控制终端）、SIGTSTP（表示终端挂起）和SIGHUP（进程组长退出时向所有会议成员发出的）。 <br /><br />    2.将程序进入后台执行。由于守护进程最终脱离控制终端，到后台去运行。方法是在进程中调用fork使父进程终止，让Daemon在子进程中后台执行。这就是常说的“脱壳”。子进程继续函数fork()的定义如下： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />#include &lt;sys/types.h&gt;
#include &lt;unistd.h&gt;
 pid_t fork(void);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    该函数是Linux/Unix编程中非常重要的函数。它被调用一次，但返回两次。这两次返回的区别是子进程的返回值为“0”，而父进程的返回值为子进程的ID。如果出错则返回“-1”。 <br /><br />    3.脱离控制终端、登录会话和进程组。开发人员如果要摆脱它们，不受它们的影响，一般使用 setsid() 设置新会话的领头进程，并与原来的登录会话和进程组脱离。这只是其中的一种方法，也有如下处理的办法： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />if  ((fd = open("/dev/tty",O_RDWR)) &gt;= 0) { 
ioctl(fd,TIOCNOTTY,NULL); 
close(fd); 
}/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    其中/dev/tty是一个流设备，也是终端映射，调用close()函数将终端关闭。 <br /><br />    4.禁止进程重新打开控制终端。进程已经成为无终端的会话组长，但它可以重新申请打开一个控制终端。开发人员可以通过不再让进程成为会话组长的方式来禁止进程重新打开控制终端，需要再次调用fork函数。<br />    上面的程序代码表示结束第一子进程，第二子进程继续（第二子进程不再是会话组长）。 <br /><br />    5. 关闭打开的文件描述符，并重定向标准输入、标准输出和标准错误输出的文件描述符。进程从创建它的父进程那里继承了打开的文件描述符。如果不关闭，将会浪费系统资源，引起无法预料的错误。关闭三者的代码如下： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />for (fd = 0, fdtablesize = getdtablesize(); 
 fd &lt; fdtablesize; fd++) 
  close(fd);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    但标准输入、标准输出和标准错误输出的重定向是可选的。也许有的程序想保留标准输入（0）、标准输出（1）和标准错误输出（2），那么循环应绕过这三者。代码如下： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />for (fd =3, fdtablesize = getdtablesize();
fd &lt; fdtablesize; fd++) 
  close(fd);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    有的程序有些特殊的需求，还需要将这三者重新定向。示例如下： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />error=open("/tmp/error",O_WRONLY|O_CREAT,
0600);
  dup2(error,2);
 close(error);
 in=open("/tmp/in",O_RDONLY|O_CREAT,0600);
 if(dup2(in,0)==-1)  perror("in");
 close(in);
out=open("/tmp/out",O_WRONLY|O_CREAT,0600);
 if(dup2(out,1)==-1) perror("out");
 close(out);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    6.改变工作目录到根目录或特定目录进程活动时，其工作目录所在的文件系统不能卸下。 <br /><br />    一般需要将工作目录改变到根目录或特定目录，注意用户对此目录需要有读写权。防止超级用户卸载设备时系统报告设备忙。 <br /><br />    7.处理SIGCHLD信号。SIGCHLD信号是子进程结束时，向内核发送的信号。 <br /><br />如果父进程不等待子进程结束，子进程将成为僵尸进程（zombie）从而占用系统资源。因此需要对SIGCHLD信号做出处理，回收僵尸进程的资源，避免造成不必要的资源浪费。可以用如下语句：<br />    signal(SIGCHLD,(void *)reap_status); <br /><br />    捕捉信号SIGCHLD，用下面的函数进行处理： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />void reap_status() 
 { int pid; 
   union wait status; 
   while ((pid = wait3(&amp;status,WNOHANG,NULL)) &gt; 0) 
  …… }/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    8.在Linux/Unix下有个syslogd的守护进程，向用户提供了syslog()系统调用。任何程序都可以通过syslog记录事件。 <br /><br />    由于syslog非常好用和易配置，所以很多程序都使用syslog来发送它们的记录信息。一般守护进程也使用syslog向系统输出信息。syslog有三个函数，一般只需要用syslog(...)函数，openlog()/closelog()可有可无。syslog()在shslog.h定义如下： <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />#include &lt;syslog.h&gt;
void syslog(int priority,char *format,...);/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    其中参数priority指明了进程要写入信息的等级和用途。第二个参数是一个格式串，指定了记录输出的格式。在这个串的最后需要指定一个%m，对应errno错误码。 <br /><br />    <b>应用范例</b><br /><br />    下面给出Linux下编程的守护进程的应用范例，在UNIX中，不同版本实现的细节可能不一致，但其实现的原则是与Linux一致的。 <br /><br /><ccid /><table cellspacing="0" bordercolordark="#ffffff" cellpadding="2" width="400" align="center" bordercolorlight="#000000" border="1"><tbody><tr><td class="code" style="FONT-SIZE: 9pt" bgcolor="#e6e6e6"><pre><ccid />#include &lt;stdio.h&gt; 
#include &lt;signal.h&gt; 
#include &lt;sys/file.h&gt; 
main(int argc,char **argv)
{
  time_t now;
  int childpid,fd,fdtablesize;
  int error,in,out;
  /* 忽略终端 I/O信号,STOP信号 */
 signal(SIGTTOU,SIG_IGN);
 signal(SIGTTIN,SIG_IGN);
  signal(SIGTSTP,SIG_IGN); 
  signal(SIGHUP ,SIG_IGN);
  /* 父进程退出,程序进入后台运行 */
  if(fork()!=0) exit(1);
   if(setsid()&lt;0)exit(1);/* 创建一个新的会议组 */ 
  /* 子进程退出,孙进程没有控制终端了 */  
  if(fork()!=0) exit(1);
  if(chdir("/tmp")==-1)exit(1);
/* 关闭打开的文件描述符,包括标准输入、标准输出和标准错误输出 */ 
 for (fd = 0, fdtablesize = getdtablesize(); fd &lt; fdtablesize; fd++) 
   close(fd);
   umask(0);/*重设文件创建掩模 */ 
   signal(SIGCHLD,SIG_IGN);/* 忽略SIGCHLD信号 */ 
/*打开log系统*/
  syslog(LOG_USER|LOG_INFO,"守护进程测试!n");  
   while(1)  
   {  
    time(&amp;now);
   syslog(LOG_USER|LOG_INFO,"当前时间:t%sttn",ctime(&amp;now));
    sleep(6);
     }  
 }/PRE&gt;</pre></td></tr></tbody></table>BR&gt;<br />    此程序在Turbo Linux 4.0下编译通过。这个程序比较简单，但基本体现了守护进程的编程要点。读者针对实际应用中不同的需要，还可以做相应的调整。 <!-- comment these out if you want to see an example of custom fields, but remember to name the fields
     in the same way they are named here: 'imfeeling' (livejournal.com style), 'listening' and 'new_field' 
<p>
 <b></b> <br/>
 <b>:</b> <br/> 
 <b></b>?</p> 
--><img src ="http://www.blogjava.net/huyi2006/aggbug/110431.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-04-13 14:04 <a href="http://www.blogjava.net/huyi2006/articles/110431.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>SIGCHLD的一个处理函数[c/c++] </title><link>http://www.blogjava.net/huyi2006/articles/110430.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 13 Apr 2007 05:56:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/110430.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/110430.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/110430.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/110430.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/110430.html</trackback:ping><description><![CDATA[//防止僵尸进程的产生<br /><br /><table style="BORDER-COLLAPSE: collapse" bordercolor="#999999" cellspacing="0" cellpadding="0" width="95%" bgcolor="#f1f1f1" border="1"><tbody><tr><td><p style="MARGIN: 5px; LINE-HEIGHT: 150%"><code><span style="COLOR: rgb(0,0,0)"><span style="COLOR: rgb(0,0,255)">void</span> sig_chld<span style="COLOR: rgb(0,0,204)">(</span><span style="COLOR: rgb(0,0,255)">int</span> sig<span style="COLOR: rgb(0,0,204)">)</span><br /><span style="COLOR: rgb(0,0,204)">{</span><br />   <span style="COLOR: rgb(255,0,0)">pid_t</span> pid<span style="COLOR: rgb(0,0,204)">;</span><br />   <span style="COLOR: rgb(0,0,255)">int</span> stat<span style="COLOR: rgb(0,0,204)">;</span><br /><br />   <span style="COLOR: rgb(0,0,255)">while</span><span style="COLOR: rgb(0,0,204)">(</span>1<span style="COLOR: rgb(0,0,204)">)</span><br />   <span style="COLOR: rgb(0,0,204)">{</span><br />     pid <span style="COLOR: rgb(0,0,204)">=</span> waitpid<span style="COLOR: rgb(0,0,204)">(</span><span style="COLOR: rgb(0,0,204)">-</span>1<span style="COLOR: rgb(0,0,204)">,</span><span style="COLOR: rgb(0,0,204)">&amp;</span>stat<span style="COLOR: rgb(0,0,204)">,</span> WNOHANG<span style="COLOR: rgb(0,0,204)">)</span><span style="COLOR: rgb(0,0,204)">;//</span></span></code><span style="FONT-SIZE: 13px">WNOHANG 非阻塞</span><code><span style="COLOR: rgb(0,0,0)"><span style="COLOR: rgb(0,0,204)"></span>方式<br />     <span style="COLOR: rgb(0,0,255)">if</span><span style="COLOR: rgb(0,0,204)">(</span>pid <span style="COLOR: rgb(0,0,204)">=</span><span style="COLOR: rgb(0,0,204)">=</span> 0 <span style="COLOR: rgb(0,0,204)">|</span><span style="COLOR: rgb(0,0,204)">|</span><span style="COLOR: rgb(0,0,204)">(</span>pid <span style="COLOR: rgb(0,0,204)">=</span><span style="COLOR: rgb(0,0,204)">=</span><span style="COLOR: rgb(0,0,204)">-</span>1 <span style="COLOR: rgb(0,0,204)">&amp;</span><span style="COLOR: rgb(0,0,204)">&amp;</span><span style="COLOR: rgb(255,0,0)">errno</span><span style="COLOR: rgb(0,0,204)">!</span><span style="COLOR: rgb(0,0,204)">=</span> EINTR<span style="COLOR: rgb(0,0,204)">)</span><span style="COLOR: rgb(0,0,204)">)</span><br />     <span style="COLOR: rgb(0,0,204)">{</span><br />         <span style="COLOR: rgb(0,0,255)">break</span><span style="COLOR: rgb(0,0,204)">;</span><br />     <span style="COLOR: rgb(0,0,204)">}</span><br />   <span style="COLOR: rgb(0,0,204)">}</span><br />   <br />   <span style="COLOR: rgb(0,0,255)">return</span><span style="COLOR: rgb(0,0,204)">;</span><br /><span style="COLOR: rgb(0,0,204)">}</span></span></code></p></td></tr></tbody></table><img src ="http://www.blogjava.net/huyi2006/aggbug/110430.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-04-13 13:56 <a href="http://www.blogjava.net/huyi2006/articles/110430.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux下定时器使用 </title><link>http://www.blogjava.net/huyi2006/articles/110356.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 13 Apr 2007 01:39:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/110356.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/110356.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/110356.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/110356.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/110356.html</trackback:ping><description><![CDATA[
		<font color="#ff9900">Linux下的定时器有两种，以下分别介绍： <br /><br />       <strong>1、alarm</strong><br />       如果不要求很精确的话，用 alarm() 和 signal() 就够了 <br />           unsigned int alarm(unsigned int seconds) <br />       专门为SIGALRM信号而设，在指定的时间seconds秒后，将向进程本身发送SIGALRM信号，又称为闹钟时间。进程调用alarm后，任何以前的alarm()调用都将无效。如果参数seconds为零，那么进程内将不再包含任何闹钟时间。如果调用alarm（）前，进程中已经设置了闹钟时间，则返回上一个闹钟时间的剩余时间，否则返回0。 <br /><br />       示例： <br />       #include &lt;stdio.h&gt; <br />       #include &lt;unistd.h&gt; <br />       #include &lt;signal.h&gt; <br /><br />       void sigalrm_fn(int sig) <br />       { <br />               /* Do something */ <br />               printf("alarm!\n"); <br /><br />               alarm(2); <br />               return; <br />       } <br /><br />       int main(void) <br />       { <br />               signal(SIGALRM, sigalrm_fn); <br />               alarm(2); <br /><br />               /* Do someting */ <br />               while(1) pause(); <br />       } <br /><br /><br />       <strong>2、setitimer</strong><br />       int setitimer(int which, const struct itimerval *value, struct itimerval *ovalue)); <br />       setitimer()比alarm功能强大，支持3种类型的定时器： <br /><br />       ITIMER_REAL :  以系统真实的时间来计算，它送出SIGALRM信号。   <br />       ITIMER_VIRTUAL :  以该行程真正有执行的时间来计算，它送出SIGVTALRM信号。   <br />       ITIMER_PROF :  以行程真正有执行及在核心中所费的时间来计算，它送出SIGPROF信号。   <br />       Setitimer()第一个参数which指定定时器类型（上面三种之一）；第二个参数是结构itimerval的一个实例；第三个参数可不做处理。 <br />       Setitimer()调用成功返回0，否则返回-1。 <br /><br />       下面是关于setitimer调用的一个简单示范，在该例子中，每隔一秒发出一个SIGALRM，每隔0.5秒发出一个SIGVTALRM信号：： <br />       #include &lt;stdio.h&gt; <br />       #include &lt;stdlib.h&gt; <br />       #include &lt;unistd.h&gt; <br />       #include &lt;signal.h&gt; <br />       #include &lt;time.h&gt; <br />       #include &lt;sys/time.h&gt; <br /><br />       int sec; <br />       void sigroutine(int signo){ <br /><br />           switch (signo){ <br />           case SIGALRM: <br />               printf("Catch a signal -- SIGALRM \n"); <br />               signal(SIGALRM, sigroutine); <br />               break; <br />           case SIGVTALRM: <br />               printf("Catch a signal -- SIGVTALRM \n"); <br />               signal(SIGVTALRM, sigroutine); <br />               break; <br />           } <br />           return; <br />       } <br /><br />       int main() <br />       { <br />           struct itimerval value, ovalue, value2; <br />    <br />           sec = 5; <br />           printf("process id is %d ", getpid()); <br />           signal(SIGALRM, sigroutine); <br />           signal(SIGVTALRM, sigroutine); <br />           value.it_value.tv_sec = 1; <br />           value.it_value.tv_usec = 0; <br />           value.it_interval.tv_sec = 1; <br />           value.it_interval.tv_usec = 0; <br />           setitimer(ITIMER_REAL, &amp;value, &amp;ovalue); <br /><br />           value2.it_value.tv_sec = 0; <br />           value2.it_value.tv_usec = 500000; <br />           value2.it_interval.tv_sec = 0; <br />           value2.it_interval.tv_usec = 500000; <br />           setitimer(ITIMER_VIRTUAL, &amp;value2, &amp;ovalue); <br />           for(;;) <br />               ; <br />       } <br /><br /><br />       　该例子的屏幕拷贝如下： <br /><br />       localhost:~$ ./timer_test <br />       process id is 579 <br />       Catch a signal – SIGVTALRM <br />       Catch a signal – SIGALRM <br />       Catch a signal – SIGVTALRM <br />       Catch a signal – SIGVTALRM <br />       Catch a signal – SIGALRM <br />       Catch a signal –GVTALRM <br /><br /><br />       注意：Linux信号机制基本上是从Unix系统中继承过来的。早期Unix系统中的信号机制比较简单和原始，后来在实践中暴露出一些问题，因此，把那些建立在早期机制上的信号叫做"不可靠信号"，信号值小于SIGRTMIN(Red hat 7.2中，SIGRTMIN=32，SIGRTMAX=63)的信号都是不可靠信号。这就是"不可靠信号"的来源。它的主要问题是：进程每次处理信号后，就将对信号的响应设置为默认动作。在某些情况下，将导致对信号的错误处理；因此，用户如果不希望这样的操作，那么就要在信号处理函数结尾再一次调用signal()，重新安装该信号。</font> <br /><img src ="http://www.blogjava.net/huyi2006/aggbug/110356.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-04-13 09:39 <a href="http://www.blogjava.net/huyi2006/articles/110356.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>Linux环境下的Socket编程[学习笔记摘]</title><link>http://www.blogjava.net/huyi2006/articles/110316.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Thu, 12 Apr 2007 23:13:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/110316.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/110316.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/110316.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/110316.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/110316.html</trackback:ping><description><![CDATA[
		<span class="pg" id="xydwtext">什么是Socket <br />　　Socket接口是TCP/IP网络的API，Socket接口定义了许多函数或例程，程序员可以用它们来开发TCP/IP网络上的应用程序。要学Internet上的TCP/IP网络编程，必须理解Socket接口。 <br />　　Socket接口设计者最先是将接口放在Unix操作系统里面的。如果了解Unix系统的输入和输出的话，就很容易了解Socket了。网络的Socket数据传输是一种特殊的I/O，Socket也是一种文件描述符。Socket也具有一个类似于打开文件的函数调用Socket()，该函数返回一个整型的Socket描述符，随后的连接建立、数据传输等操作都是通过该Socket实现的。常用的Socket类型有两种：流式Socket（SOCK_STREAM）和数据报式Socket（SOCK_DGRAM）。流式是一种面向连接的Socket，针对于面向连接的TCP服务应用；数据报式Socket是一种无连接的Socket，对应于无连接的UDP服务应用。 <br /><br />Socket建立 <br />　　为了建立Socket，程序可以调用Socket函数，该函数返回一个类似于文件描述符的句柄。socket函数原型为： <br />　　int socket(int domain, int type, int protocol); <br />　　domain指明所使用的协议族，通常为PF_INET，表示互联网协议族（TCP/IP协议族）；type参数指定socket的类型：SOCK_STREAM 或SOCK_DGRAM，Socket接口还定义了原始Socket（SOCK_RAW），允许程序使用低层协议；protocol通常赋值"0"。Socket()调用返回一个整型socket描述符，你可以在后面的调用使用它。 <br />　　Socket描述符是一个指向内部数据结构的指针，它指向描述符表入口。调用Socket函数时，socket执行体将建立一个Socket，实际上"建立一个Socket"意味着为一个Socket数据结构分配存储空间。Socket执行体为你管理描述符表。 <br />　　两个网络程序之间的一个网络连接包括五种信息：通信协议、本地协议地址、本地主机端口、远端主机地址和远端协议端口。Socket数据结构中包含这五种信息。 <br /><br />Socket配置 <br />　　通过socket调用返回一个socket描述符后，在使用socket进行网络传输以前，必须配置该socket。面向连接的socket客户端通过调用Connect函数在socket数据结构中保存本地和远端信息。无连接socket的客户端和服务端以及面向连接socket的服务端通过调用bind函数来配置本地信息。 <br />Bind函数将socket与本机上的一个端口相关联，随后你就可以在该端口监听服务请求。Bind函数原型为： <br />　　int bind(int sockfd,struct sockaddr *my_addr, int addrlen); <br />　　Sockfd是调用socket函数返回的socket描述符,my_addr是一个指向包含有本机IP地址及端口号等信息的sockaddr类型的指针；addrlen常被设置为sizeof(struct sockaddr)。 <br />　　struct sockaddr结构类型是用来保存socket信息的： <br />　　struct sockaddr { <br />　　 unsigned short sa_family; /* 地址族， AF_xxx */ <br />char sa_data[14]; /* 14 字节的协议地址 */ <br />}; <br />　　sa_family一般为AF_INET，代表Internet（TCP/IP）地址族；sa_data则包含该socket的IP地址和端口号。 <br />　　另外还有一种结构类型： <br />　　struct sockaddr_in { <br />　　 short int sin_family; /* 地址族 */ <br />　　 unsigned short int sin_port; /* 端口号 */ <br />　　 struct in_addr sin_addr; /* IP地址 */ <br />　　 unsigned char sin_zero[8]; /* 填充0 以保持与struct sockaddr同样大小 */ <br />　　}; <br />　　这个结构更方便使用。sin_zero用来将sockaddr_in结构填充到与struct sockaddr同样的长度，可以用bzero()或memset()函数将其置为零。指向sockaddr_in 的指针和指向sockaddr的指针可以相互转换，这意味着如果一个函数所需参数类型是sockaddr时，你可以在函数调用的时候将一个指向sockaddr_in的指针转换为指向sockaddr的指针；或者相反。 <br />　　使用bind函数时，可以用下面的赋值实现自动获得本机IP地址和随机获取一个没有被占用的端口号： <br />　　my_addr.sin_port = 0; /* 系统随机选择一个未被使用的端口号 */ <br />　　my_addr.sin_addr.s_addr = INADDR_ANY; /* 填入本机IP地址 */ <br />通过将my_addr.sin_port置为0，函数会自动为你选择一个未占用的端口来使用。同样，通过将my_addr.sin_addr.s_addr置为INADDR_ANY，系统会自动填入本机IP地址。 <br />注意在使用bind函数是需要将sin_port和sin_addr转换成为网络字节优先顺序；而sin_addr则不需要转换。 <br />　　计算机数据存储有两种字节优先顺序：高位字节优先和低位字节优先。Internet上数据以高位字节优先顺序在网络上传输，所以对于在内部是以低位字节优先方式存储数据的机器，在Internet上传输数据时就需要进行转换，否则就会出现数据不一致。 <br />　　下面是几个字节顺序转换函数： <br />·htonl()：把32位值从主机字节序转换成网络字节序 <br />·htons()：把16位值从主机字节序转换成网络字节序 <br />·ntohl()：把32位值从网络字节序转换成主机字节序 <br />·ntohs()：把16位值从网络字节序转换成主机字节序 <br />　　Bind()函数在成功被调用时返回0；出现错误时返回"-1"并将errno置为相应的错误号。需要注意的是，在调用bind函数时一般不要将端口号置为小于1024的值，因为1到1024是保留端口号，你可以选择大于1024中的任何一个没有被占用的端口号。 <br /><br />连接建立 <br />　　面向连接的客户程序使用Connect函数来配置socket并与远端服务器建立一个TCP连接，其函数原型为： <br />　　int connect(int sockfd, struct sockaddr *serv_addr,int addrlen); <br />Sockfd是socket函数返回的socket描述符；serv_addr是包含远端主机IP地址和端口号的指针；addrlen是远端地质结构的长度。Connect函数在出现错误时返回-1，并且设置errno为相应的错误码。进行客户端程序设计无须调用bind()，因为这种情况下只需知道目的机器的IP地址，而客户通过哪个端口与服务器建立连接并不需要关心，socket执行体为你的程序自动选择一个未被占用的端口，并通知你的程序数据什么时候到打断口。 <br />　　Connect函数启动和远端主机的直接连接。只有面向连接的客户程序使用socket时才需要将此socket与远端主机相连。无连接协议从不建立直接连接。面向连接的服务器也从不启动一个连接，它只是被动的在协议端口监听客户的请求。 <br />　　Listen函数使socket处于被动的监听模式，并为该socket建立一个输入数据队列，将到达的服务请求保存在此队列中，直到程序处理它们。 <br />　　int listen(int sockfd， int backlog); <br />Sockfd是Socket系统调用返回的socket 描述符；backlog指定在请求队列中允许的最大请求数，进入的连接请求将在队列中等待accept()它们（参考下文）。Backlog对队列中等待服务的请求的数目进行了限制，大多数系统缺省值为20。如果一个服务请求到来时，输入队列已满，该socket将拒绝连接请求，客户将收到一个出错信息。 <br />当出现错误时listen函数返回-1，并置相应的errno错误码。 <br />　　accept()函数让服务器接收客户的连接请求。在建立好输入队列后，服务器就调用accept函数，然后睡眠并等待客户的连接请求。 <br />　　int accept(int sockfd, void *addr, int *addrlen); <br />　　sockfd是被监听的socket描述符，addr通常是一个指向sockaddr_in变量的指针，该变量用来存放提出连接请求服务的主机的信息（某台主机从某个端口发出该请求）；addrten通常为一个指向值为sizeof(struct sockaddr_in)的整型指针变量。出现错误时accept函数返回-1并置相应的errno值。 <br />　　首先，当accept函数监视的socket收到连接请求时，socket执行体将建立一个新的socket，执行体将这个新socket和请求连接进程的地址联系起来，收到服务请求的初始socket仍可以继续在以前的 socket上监听，同时可以在新的socket描述符上进行数据传输操作。 <br /><br />数据传输 <br />　　Send()和recv()这两个函数用于面向连接的socket上进行数据传输。 <br />　　Send()函数原型为： <br />　　int send(int sockfd, const void *msg, int len, int flags); <br />Sockfd是你想用来传输数据的socket描述符；msg是一个指向要发送数据的指针；Len是以字节为单位的数据的长度；flags一般情况下置为0（关于该参数的用法可参照man手册）。 <br />　　Send()函数返回实际上发送出的字节数，可能会少于你希望发送的数据。在程序中应该将send()的返回值与欲发送的字节数进行比较。当send()返回值与len不匹配时，应该对这种情况进行处理。 <br />char *msg = "Hello!"; <br />int len, bytes_sent; <br />…… <br />len = strlen(msg); <br />bytes_sent = send(sockfd, msg,len,0); <br />…… <br />　　recv()函数原型为： <br />　　int recv(int sockfd,void *buf,int len,unsigned int flags); <br />　　Sockfd是接受数据的socket描述符；buf 是存放接收数据的缓冲区；len是缓冲的长度。Flags也被置为0。Recv()返回实际上接收的字节数，当出现错误时，返回-1并置相应的errno值。 <br />Sendto()和recvfrom()用于在无连接的数据报socket方式下进行数据传输。由于本地socket并没有与远端机器建立连接，所以在发送数据时应指明目的地址。 <br />sendto()函数原型为： <br />　　int sendto(int sockfd, const void *msg,int len,unsigned int flags,const struct sockaddr *to, int tolen); <br />　　该函数比send()函数多了两个参数，to表示目地机的IP地址和端口号信息，而tolen常常被赋值为sizeof (struct sockaddr)。Sendto 函数也返回实际发送的数据字节长度或在出现发送错误时返回-1。 <br />　　Recvfrom()函数原型为： <br />　　int recvfrom(int sockfd,void *buf,int len,unsigned int flags,struct sockaddr *from,int *fromlen); <br />　　from是一个struct sockaddr类型的变量，该变量保存源机的IP地址及端口号。fromlen常置为sizeof (struct sockaddr)。当recvfrom()返回时，fromlen包含实际存入from中的数据字节数。Recvfrom()函数返回接收到的字节数或当出现错误时返回-1，并置相应的errno。 <br />如果你对数据报socket调用了connect()函数时，你也可以利用send()和recv()进行数据传输，但该socket仍然是数据报socket，并且利用传输层的UDP服务。但在发送或接收数据报时，内核会自动为之加上目地和源地址信息。 <br /><br />结束传输 <br />　　当所有的数据操作结束以后，你可以调用close()函数来释放该socket，从而停止在该socket上的任何数据操作： <br />close(sockfd); <br />　　你也可以调用shutdown()函数来关闭该socket。该函数允许你只停止在某个方向上的数据传输，而一个方向上的数据传输继续进行。如你可以关闭某socket的写操作而允许继续在该socket上接受数据，直至读入所有数据。 <br />　　int shutdown(int sockfd,int how); <br />　　Sockfd是需要关闭的socket的描述符。参数 how允许为shutdown操作选择以下几种方式： <br />　　·0-------不允许继续接收数据 <br />　　·1-------不允许继续发送数据 <br />·2-------不允许继续发送和接收数据， <br />·均为允许则调用close () <br />　　shutdown在操作成功时返回0，在出现错误时返回-1并置相应errno。 <br /><br />面向连接的Socket实例 <br />　　代码实例中的服务器通过socket连接向客户端发送字符串"Hello, you are connected!"。只要在服务器上运行该服务器软件，在客户端运行客户软件，客户端就会收到该字符串。 <br />　　该服务器软件代码如下： <br />#include &lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#include &lt;sys/wait.h&gt; <br />#define SERVPORT 3333 /*服务器监听端口号 */ <br />#define BACKLOG 10 /* 最大同时连接请求数 */ <br />main() <br />{ <br />int sockfd,client_fd; /*sock_fd：监听socket；client_fd：数据传输socket */ <br />　struct sockaddr_in my_addr; /* 本机地址信息 */ <br />　struct sockaddr_in remote_addr; /* 客户端地址信息 */ <br />if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1) { <br />　　perror("socket创建出错！"); exit(1); <br />} <br />my_addr.sin_family=AF_INET; <br />　my_addr.sin_port=htons(SERVPORT); <br />　my_addr.sin_addr.s_addr = INADDR_ANY; <br />bzero(&amp;(my_addr.sin_zero),8); <br />　if (bind(sockfd, (struct sockaddr *)&amp;my_addr, sizeof(struct sockaddr)) \ <br />　　 == -1) { <br />perror("bind出错！"); <br />exit(1); <br />} <br />　if (listen(sockfd, BACKLOG) == -1) { <br />perror("listen出错！"); <br />exit(1); <br />} <br />while(1) { <br />　　sin_size = sizeof(struct sockaddr_in); <br />　　if ((client_fd = accept(sockfd, (struct sockaddr *)&amp;remote_addr, \ <br />　　&amp;sin_size)) == -1) { <br />perror("accept出错"); <br />continue; <br />} <br />　　printf("received a connection from %s\n", inet_ntoa(remote_addr.sin_addr)); <br />　 if (!fork()) { /* 子进程代码段 */ <br />　　 if (send(client_fd, "Hello, you are connected!\n", 26, 0) == -1) <br />　　 perror("send出错！"); <br />close(client_fd); <br />exit(0); <br />} <br />　　close(client_fd); <br />　　} <br />　} <br />} <br />　　服务器的工作流程是这样的：首先调用socket函数创建一个Socket，然后调用bind函数将其与本机地址以及一个本地端口号绑定，然后调用listen在相应的socket上监听，当accpet接收到一个连接服务请求时，将生成一个新的socket。服务器显示该客户机的IP地址，并通过新的socket向客户端发送字符串"Hello，you are connected!"。最后关闭该socket。 <br />　　代码实例中的fork()函数生成一个子进程来处理数据传输部分，fork()语句对于子进程返回的值为0。所以包含fork函数的if语句是子进程代码部分，它与if语句后面的父进程代码部分是并发执行的。 <br /><br />客户端程序代码如下： <br />#include&lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;netdb.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#define SERVPORT 3333 <br />#define MAXDATASIZE 100 /*每次最大数据传输量 */ <br />main(int argc, char *argv[]){ <br />　int sockfd, recvbytes; <br />　char buf[MAXDATASIZE]; <br />　struct hostent *host; <br />　struct sockaddr_in serv_addr; <br />　if (argc &lt; 2) { <br />fprintf(stderr,"Please enter the server's hostname!\n"); <br />exit(1); <br />} <br />　if((host=gethostbyname(argv[1]))==NULL) { <br />herror("gethostbyname出错！"); <br />exit(1); <br />} <br />　if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ <br />perror("socket创建出错！"); <br />exit(1); <br />} <br />　serv_addr.sin_family=AF_INET; <br />　serv_addr.sin_port=htons(SERVPORT); <br />　serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr); <br />　bzero(&amp;(serv_addr.sin_zero),8); <br />　if (connect(sockfd, (struct sockaddr *)&amp;serv_addr, \ <br />　　 sizeof(struct sockaddr)) == -1) { <br />perror("connect出错！"); <br />exit(1); <br />} <br />　if ((recvbytes=recv(sockfd, buf, MAXDATASIZE, 0)) ==-1) { <br />perror("recv出错！"); <br />exit(1); <br />} <br />　buf[recvbytes] = '\0'; <br />　printf("Received: %s",buf); <br />　close(sockfd); <br />} <br />　　客户端程序首先通过服务器域名获得服务器的IP地址，然后创建一个socket，调用connect函数与服务器建立连接，连接成功之后接收从服务器发送过来的数据，最后关闭socket。 <br />　　函数gethostbyname()是完成域名转换的。由于IP地址难以记忆和读写，所以为了方便，人们常常用域名来表示主机，这就需要进行域名和IP地址的转换。函数原型为： <br />　　struct hostent *gethostbyname(const char *name); <br />　　函数返回为hosten的结构类型，它的定义如下： <br />　　struct hostent { <br />　 char *h_name; /* 主机的官方域名 */ <br />　　 char **h_aliases; /* 一个以NULL结尾的主机别名数组 */ <br />　　 int h_addrtype; /* 返回的地址类型，在Internet环境下为AF-INET */ <br />　　int h_length; /* 地址的字节长度 */ <br />　　 char **h_addr_list; /* 一个以0结尾的数组，包含该主机的所有地址*/ <br />　　}; <br />　　#define h_addr h_addr_list[0] /*在h-addr-list中的第一个地址*/ <br />　　当 gethostname()调用成功时，返回指向struct hosten的指针，当调用失败时返回-1。当调用gethostbyname时，你不能使用perror()函数来输出错误信息，而应该使用herror()函数来输出。 <br /><br />　　无连接的客户/服务器程序的在原理上和连接的客户/服务器是一样的，两者的区别在于无连接的客户/服务器中的客户一般不需要建立连接，而且在发送接收数据时，需要指定远端机的地址。 <br /><br />阻塞和非阻塞 <br />　　阻塞函数在完成其指定的任务以前不允许程序调用另一个函数。例如，程序执行一个读数据的函数调用时，在此函数完成读操作以前将不会执行下一程序语句。当服务器运行到accept语句时，而没有客户连接服务请求到来，服务器就会停止在accept语句上等待连接服务请求的到来。这种情况称为阻塞（blocking）。而非阻塞操作则可以立即完成。比如，如果你希望服务器仅仅注意检查是否有客户在等待连接，有就接受连接，否则就继续做其他事情，则可以通过将Socket设置为非阻塞方式来实现。非阻塞socket在没有客户在等待时就使accept调用立即返回。 <br />　　#include &lt;unistd.h&gt; <br />　　#include &lt;fcntl.h&gt; <br />　　…… <br />sockfd = socket(AF_INET,SOCK_STREAM,0); <br />fcntl(sockfd,F_SETFL,O_NONBLOCK)； <br />…… <br />　　通过设置socket为非阻塞方式，可以实现"轮询"若干Socket。当企图从一个没有数据等待处理的非阻塞Socket读入数据时，函数将立即返回，返回值为-1，并置errno值为EWOULDBLOCK。但是这种"轮询"会使CPU处于忙等待方式，从而降低性能，浪费系统资源。而调用select()会有效地解决这个问题，它允许你把进程本身挂起来，而同时使系统内核监听所要求的一组文件描述符的任何活动，只要确认在任何被监控的文件描述符上出现活动，select()调用将返回指示该文件描述符已准备好的信息，从而实现了为进程选出随机的变化，而不必由进程本身对输入进行测试而浪费CPU开销。Select函数原型为: <br />int select(int numfds,fd_set *readfds,fd_set *writefds， <br />fd_set *exceptfds,struct timeval *timeout); <br />　　其中readfds、writefds、exceptfds分别是被select()监视的读、写和异常处理的文件描述符集合。如果你希望确定是否可以从标准输入和某个socket描述符读取数据，你只需要将标准输入的文件描述符0和相应的sockdtfd加入到readfds集合中；numfds的值是需要检查的号码最高的文件描述符加1，这个例子中numfds的值应为sockfd+1；当select返回时，readfds将被修改，指示某个文件描述符已经准备被读取，你可以通过FD_ISSSET()来测试。为了实现fd_set中对应的文件描述符的设置、复位和测试，它提供了一组宏： <br />　　FD_ZERO(fd_set *set)----清除一个文件描述符集； <br />　　FD_SET(int fd,fd_set *set)----将一个文件描述符加入文件描述符集中； <br />　　FD_CLR(int fd,fd_set *set)----将一个文件描述符从文件描述符集中清除； <br />　　FD_ISSET(int fd,fd_set *set)----试判断是否文件描述符被置位。 <br />　　Timeout参数是一个指向struct timeval类型的指针，它可以使select()在等待timeout长时间后没有文件描述符准备好即返回。struct timeval数据结构为： <br />　　struct timeval { <br />　　 int tv_sec; /* seconds */ <br />　　 int tv_usec; /* microseconds */ <br />}; <br /><br />POP3客户端实例 <br />　　下面的代码实例基于POP3的客户协议，与邮件服务器连接并取回指定用户帐号的邮件。与邮件服务器交互的命令存储在字符串数组POPMessage中，程序通过一个do-while循环依次发送这些命令。 <br />#include&lt;stdio.h&gt; <br />#include &lt;stdlib.h&gt; <br />#include &lt;errno.h&gt; <br />#include &lt;string.h&gt; <br />#include &lt;netdb.h&gt; <br />#include &lt;sys/types.h&gt; <br />#include &lt;netinet/in.h&gt; <br />#include &lt;sys/socket.h&gt; <br />#define POP3SERVPORT 110 <br />#define MAXDATASIZE 4096 <br /><br />main(int argc, char *argv[]){ <br />int sockfd; <br />struct hostent *host; <br />struct sockaddr_in serv_addr; <br />char *POPMessage[]={ <br />"USER userid\r\n", <br />"PASS password\r\n", <br />"STAT\r\n", <br />"LIST\r\n", <br />"RETR 1\r\n", <br />"DELE 1\r\n", <br />"QUIT\r\n", <br />NULL <br />}; <br />int iLength; <br />int iMsg=0; <br />int iEnd=0; <br />char buf[MAXDATASIZE]; <br /><br />if((host=gethostbyname("your.server"))==NULL) { <br />perror("gethostbyname error"); <br />exit(1); <br />} <br />if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) == -1){ <br />perror("socket error"); <br />exit(1); <br />} <br />serv_addr.sin_family=AF_INET; <br />serv_addr.sin_port=htons(POP3SERVPORT); <br />serv_addr.sin_addr = *((struct in_addr *)host-&gt;h_addr); <br />bzero(&amp;(serv_addr.sin_zero),8); <br />if (connect(sockfd, (struct sockaddr *)&amp;serv_addr,sizeof(struct sockaddr))==-1){ <br />perror("connect error"); <br />exit(1); <br />} <br /><br />do { <br />send(sockfd,POPMessage[iMsg],strlen(POPMessage[iMsg]),0); <br />printf("have sent: %s",POPMessage[iMsg]); <br /><br />iLength=recv(sockfd,buf+iEnd,sizeof(buf)-iEnd,0); <br />iEnd+=iLength; <br />buf[iEnd]='\0'; <br />printf("received: %s,%d\n",buf,iMsg); <br /><br />iMsg++; <br />} while (POPMessage[iMsg]); <br /><br />close(sockfd); <br />}</span>
		<br />
		<br />
		<img height="415" alt="r_4883a52702000h0d.jpg" src="http://www.blogjava.net/images/blogjava_net/huyi2006/17681/r_4883a52702000h0d.jpg" width="388" border="0" />
<img src ="http://www.blogjava.net/huyi2006/aggbug/110316.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-04-13 07:13 <a href="http://www.blogjava.net/huyi2006/articles/110316.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>linux软件工程师之基本题</title><link>http://www.blogjava.net/huyi2006/articles/106469.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Mon, 26 Mar 2007 09:30:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/106469.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/106469.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/106469.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/106469.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/106469.html</trackback:ping><description><![CDATA[
		<p>Linux软件工程师测试</p>
		<p>一、基础总分：<br />1．GCC <br />用哪个参数可以产生obj文件：B<br />(A) -c      (B)-o      (C)-share      (D)-static</p>
		<p>
				<br />2、GDB <br />在main函数处设置断点的命令是：A<br />(A)b main      (B)set main      (C)set 0      (D)b 0</p>
		<p>
				<br />3、UNP <br />下列函数中可以将主机字节序转换成网络字节序的是：D<br />(A)convert()      (B)sprintf()      (C)ntonl()      (D)htonl()</p>
		<p>
				<br />4、man <br />查阅"read系统调用"man手册的命令是：B<br />(A)man 1 read      (B)man 2 read      (C)man 3 read      (D)man read</p>
		<p>
				<br />5、shell <br />删除"/tmp/prj/"目录下（及所有子目录下）所有名字以".o"结尾的文件：A<br />(A)find /tmp/prj/ -name "*.o" | xargs -i rm {}<br />(B)rm -rf /tmp/prj/*.o<br />(C)find /tmp/prj/ -name "*.o" -rm {} \;<br />(D)find /tmp/prj/*.o -name "*.o" | xargs -i rm {}</p>
		<p>
				<br />6、IPC <br />下列哪些属于IPC范畴(多选)：A,C,D,E,G,H<br />(A)信号<br />(B)文件监视<br />(C)管道<br />(D)消息队列<br />(E)信号灯<br />(F)odbc<br />(G)共享内存<br />(H)UNIX域套接字</p>
		<p> </p>
		<p>7、Signal <br />下列那个信号不可以被捕获或阻塞：A<br />(A)SIGKILL      (B)SIGINT      (C)SIGCHILD      (D)SIGUSR1</p>
		<p> </p>
		<p>8、Thread<br />下列那一项描述是错误的：B<br />(A)进程拥有独立的内存空间，而线程之间却共享内存空间。<br />(B)进程可以使用libc库，而线程不可以<br />(C)进程和线程在Linux内核中都使用clone()来实现<br />(D)信号量也可以作为线程间的通讯手段</p>
		<p>
				<br />二、C/C++部分：<br />1、以下为HP-UX下的64位应用程序，请写出其运行结果。 <br />void func(char *ptr)<br />{<br />        printf("%d\n", sizeof(ptr));<br />}</p>
		<p>int main()<br />{<br />        char buf[1024];<br />        char *buf_p = buf;</p>
		<p>        printf("%d\n", sizeof(char));<br />        printf("%d\n", sizeof(int *));<br />        printf("%d\n", sizeof(buf));<br />        func(buf);<br />        func(buf_p);<br />}</p>
		<p>答案：<br />1<br />8<br />1024<br />8<br />8</p>
		<p> </p>
		<p>
				<br />2、请分析以下程序，并写出其运行结果。<br />char *get_memory(void)<br />{<br />        char p[] = "hello world";</p>
		<p>        return p;<br />}</p>
		<p>int main()<br />{<br />        char *str = NULL;</p>
		<p>        str = get_memory();<br />        printf(str);<br />}</p>
		<p>答案：(这是个典型有内存错误的程序，回答出有内存错误算对，最好能回答哪里有错)</p>
		<p>
				<br />3、请编写下面的C函数<br />/*<br />功能：在堆上分配一块指定大小的内存，并且全部清0，如果出错则返回一个空指针<br />lens：请求分配内存的尺寸<br />*/<br />void *get_mem(int lens)<br />{<br />        ...<br />}</p>
		<p>
				<br />答案：(考察编程风格和细心程度，不一定要和下面的程序一样，注意出错处理和返回值控制)<br />void *get_mem(int lens)<br />{<br />        char *ret;<br />        if (lens &lt;= 0)<br />                return NULL;<br />        ret = malloc(lens);<br />        if (ret == NULL)<br />                return NULL;<br />        bzero(ret, lens);<br />        return ret;<br />}</p>
		<p> </p>
		<p>4、Makefile<br />假设有一个小型web服务器程序名叫"miniweb",它的源代码包含三个源文件: utils.c, lib.c, main.c<br />请为这个程序编写一个Makefile。</p>
		<p>答案：(考察是否会写Makefile，可能很多人都不会写，如果此题没回答出来，面试的时候需要再次询问是否使会用make和Makefile)<br />一个最基本的例子：<br />miniweb: main.o lib.o utils.o<br />        cc -o $@ $&lt;</p>
		<p>*.o: *.c<br />        cc -c $&lt;</p>
		<p>clean:<br />        rm -f *.o rm miniweb<br /></p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/106469.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-03-26 17:30 <a href="http://www.blogjava.net/huyi2006/articles/106469.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>UNIX常用命令</title><link>http://www.blogjava.net/huyi2006/articles/106371.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Mon, 26 Mar 2007 05:07:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/106371.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/106371.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/106371.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/106371.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/106371.html</trackback:ping><description><![CDATA[
		<p>1.1 ls </p>
		<p>
				<br />[语法]： ls [-RadCxmlnogrtucpFbqisf1] [目录或文件......] </p>
		<p>[说明]： ls 命令列出指定目录下的文件，缺省目录为当前目录 ./，缺省输出顺序为纵向按字符顺序排列。<br /> <br />-R 递归地列出每个子目录的内容</p>
		<p>-a 列出所有文件，包括第一个字符为“.”的隐藏文件</p>
		<p>-d 若后面参数是目录，则只列出目录名而不列出目录内容，常与-l选项连<br />用以显示目录状态。</p>
		<p>-C 输出时多列显示 </p>
		<p>-x 横向按字符顺序排列 </p>
		<p>-m 输出按流式格式横向排列，文件名之间用逗号(，)分隔 </p>
		<p>-l 长列表输出，显示文件详细信息，每行一个文件，从左至右依次是： </p>
		<p>文件存取模式 链接数 文件主 文件组 文件字节数 上次修改时间 </p>
		<p>其中文件存取模式用10个字母表示，从左至右的意义如下： </p>
		<p>第一个字母表示文件种类，可以是以下几种情况： </p>
		<p>d 为目录文件 </p>
		<p>l 为链接 </p>
		<p>b 为块文件 </p>
		<p>c 为字符型文件 </p>
		<p>p 为命名管道（FIFO) </p>
		<p>- 为普通文件 </p>
		<p>后面9个字母分别表示文件主、同组用户、其他用户对文件的权力，用r表示可读，w 表示可写，x 表示可执行。如果是设备文件，则在文件字节数处显示：主设备 从设备。 </p>
		<p>-n 与-l选项相同，只是文件主用数字(即UID)显示，文件组用数字 </p>
		<p>(即GID)表示 </p>
		<p>-o 与-l选项相同，只是不显示文件组 </p>
		<p>-g 与-l选项相同，只是不显示文件主 </p>
		<p>-r 逆序排列 </p>
		<p>-t 按时间顺序排列而非按名字 </p>
		<p>-u 显示时间时使用上次访问时间而非上次修改时间 </p>
		<p>-c 显示时间时使用上次修改i节点时间而非上次修改时间 </p>
		<p>-p 若所列文件是目录文件，则在其后显示斜杠(/) </p>
		<p>-F 在目录文件后加'/'，在可执行文件后加'*' </p>
		<p>-b 文件名中若有非打印字符，则用八进制显示该字符 </p>
		<p>-q 文件名中的打印字符用'?'表示 </p>
		<p>-i 显示节点号 </p>
		<p>-s 显示文件长度时使用块长度而非字节长度 </p>
		<p>-f 将后面的参数解释为目录并列出其中的每一项 </p>
		<p>-1 每行仅列一项 </p>
		<p>[例子]: </p>
		<p>ls 列出当前目录下的文件 </p>
		<p>ls -al /bin 以长列表的形式列出目录 /bin 下的所有文件，包括隐藏文件 </p>
		<p>1.2 pwd <br />[语法]: pwd </p>
		<p>[说明]： 本命令用于显示当前的工作目录 </p>
		<p>[例子]: </p>
		<p>pwd 显示出当前的工作目录 </p>
		<p>1.3 cd <br />[语法]: cd [目录] </p>
		<p>[说明]：本命令用于改变当前的工作目录，无参数时使用环境变量$HOME 作为其参数，$HOME 一般为注册时进入的路径。 </p>
		<p>[例子]： </p>
		<p>cd 回到注册进入时的目录 </p>
		<p>cd /tmp 进入 /tmp 目录 </p>
		<p>cd ../ 进入上级目录 </p>
		<p>1.4 mkdir </p>
		<p> </p>
		<p>[语法]: mkdir [-m 模式] [-p] 目录名 </p>
		<p>[说明]: 本命令用于建立目录，目录的存取模式由掩码（umask)决定，要求对其父目录具有写权限，目录的UID和GID为实际UID和GID </p>
		<p>-m 按指定存取模式建立目录 </p>
		<p>-p 建立目录时建立其所有不存在的父目录 </p>
		<p>[例子]: </p>
		<p>mkdir tmp 在当前目录下建立子目录 tmp </p>
		<p>mkdir -m 777 /tmp/abc 用所有用户可读可写可执行的存取模式 </p>
		<p>建立目录 /tmp/aaa ，存取模式参看命令 chmod </p>
		<p>mkdir -p /tmp/a/b/c 建立目录 /tmp/a/b/c ，若不存在目录 /tmp/a </p>
		<p>及/tmp/a/b 则建立之 </p>
		<p>1.5 rmdir </p>
		<p> </p>
		<p>[语法]: rmdir [-p] [-s] 目录名 </p>
		<p>[说明]: 本命令用于删除目录 </p>
		<p>-p 删除所有已经为空的父目录 </p>
		<p>-s 当使用-p 选项时，出现错误不提示 </p>
		<p>[例子]: </p>
		<p>rmdir /tmp/abc 删除目录 /tmp/abc </p>
		<p>rmdir -p /tmp/a/b/c 删除目录 /tmp/a/b/c ，若目录 /tmp/a /b </p>
		<p>及/tmp/a 空，则删除 </p>
		<p>
				<br />1.6 cat </p>
		<p> </p>
		<p>[语法]: cat [-u] [-s] [-v[-t] [-e]] 文件... </p>
		<p>[说明]: 显示和连接一个或多个文件至标准输出 </p>
		<p>-u 无缓冲的输出(缺省为有缓冲输出) </p>
		<p>-s 对不存在的文件不作提示 </p>
		<p>-v 显示出文件中的非打印字符，控制字符显示成^n ，n为八进制数字， </p>
		<p>其他非打印字符显示成M-x ， x 为该字符低7位的8进制数值 </p>
		<p>-t 在使用-v 选项时，将制表符（tab） 显示成 ^I，将换页符 </p>
		<p>（formfeed）显示成 ^ L </p>
		<p>-e 在使用-v 选项时，在每一行的行尾显示 $ </p>
		<p>[例子]: </p>
		<p>cat file 显示文件 </p>
		<p>cat -s -v -e file1 file2 file3 逐个显示文件 file1 file2 file3 </p>
		<p>1.7 head </p>
		<p> </p>
		<p>[语法]: head [-n] [文件 ...] </p>
		<p>[说明]: 将文件的头n 行显示输出,缺省值为 10 行，显示多个文件时，在每个文件的前面加上 ==&gt; 文件名 &lt;== </p>
		<p>[例子]： </p>
		<p>head -9999 file1 file2 显示文件 file1 和 file2 的头 9999 行 </p>
		<p> </p>
		<p> </p>
		<p>1.8 more </p>
		<p> </p>
		<p>[语法]: more [-cdflrsuw] [－ 行数] [+ 行数] [+ / 模式 ] [ 文件 ... ] </p>
		<p>[说明]: 将文件显示在终端上，每次一屏，在左下部显示－－more－－，若是从文件读出而非从管道，则在后面显示百分比，表示已显示的部分，按回车键则上滚一行，按空格键则上滚一屏，未显示完时可以使用more 命令中的子命令。 </p>
		<p>-c 显示文件之前先清屏 </p>
		<p>-d 当输错命令时显示错误信息而不是响铃(bell) </p>
		<p>-f 不折叠显示长的行 </p>
		<p>-l 不将分页控制符(CTRL D)当作页结束 </p>
		<p>-r 一般情况下，more 不显示控制符，本选项使more 显示控制符， </p>
		<p>例如，将 (CTRL C) 显示成 ^ C </p>
		<p>-s 将多个空行转换成一个空行显示 </p>
		<p>-u 禁止产生下划线序列 </p>
		<p>-w 一般情况下 more 显示完后立即推出，本选项在显示完后作提 </p>
		<p>示，敲任意键后推出 </p>
		<p>-n 行数 指定每屏显示的行数 </p>
		<p>+ 行号 从指定行号开始显示 </p>
		<p>+/模式 在文件中搜索指定模式，从模式出现行的上两行开始显示 文件未显示完时，可以使用more 命令中的子命令，命令中除了! 和 / 以外均不回显，也不用敲回车，当命令破坏 more 提示行时，可用退格键恢复提示行。在以下子命令操作中，i 表示数字，缺省值为 1。 </p>
		<p>i 空格 上滚一屏多 i 行 </p>
		<p>i 回车 上滚 i 行 </p>
		<p>i CTRL+D i 缺省时上滚 11 行，否则上滚 i 行 </p>
		<p>id i 缺省时上滚 11 行，否则上滚 i 行 </p>
		<p>iz i 缺省时上滚一屏，否则定义每屏为 i 行 </p>
		<p>is 跳过 i 行后显示一屏 </p>
		<p>if 跳过 i 屏后显示一屏 </p>
		<p>i CTRL+B 跳回 i 屏后显示一屏 </p>
		<p>b 跳回 一屏后显示一屏 </p>
		<p>q 或 Q 推出 more </p>
		<p>= 显示当前行号 </p>
		<p>v 从当前行开始编辑当前文件编辑器由环境变量 </p>
		<p>$EDITOR定义 </p>
		<p>h 显示帮助信息 </p>
		<p>i / 模式 向前搜索，直至模式的第 i 次出现 ， 从该行的上 两行开始显示一屏 </p>
		<p>in 向前搜索，直至上一模式的第 i 次出现 ， 从该行 的上两行开始显示一屏 </p>
		<p>单引号 回到上次搜索的出发点，若无搜索则回到开始位置 </p>
		<p>! 命令 激活一个sh 去执行指定的命令 </p>
		<p>i ： n 跳到后面第 i 个文件，若不存在则跳到最后一个文件 </p>
		<p>：f 显示当前文件名和行号 </p>
		<p>：q 或 ：Q 推出 more </p>
		<p>. (点) 重复上次命令 </p>
		<p>[ 例子]: </p>
		<p>more -c +50 file 清屏后，从第50行开始显示文件 file </p>
		<p>more -s -w file1 file2 file3 显示文件 file1 file2 file3 </p>
		<p> </p>
		<p> </p>
		<p>1.9 cp </p>
		<p> </p>
		<p>[语法]: cp [ -p ] [ -r ] 文件 1 [ 文件 2 ...] 目标 </p>
		<p>[说明]: 将文件1(文件2 ...)拷贝到目标上，目标不能与文件同名， 若目标是文件名，则拷贝的文件只能有一个，若目标是目录，则拷贝的文件可以有多个，若目标文件不存在，则建立这个文件，若存在，则覆盖其以前的内容，若目标是目录，则将文件拷贝到这个目录下。 </p>
		<p>- i 在覆盖已存在文件时作提示，若回答 y 则覆盖，其他则中止 </p>
		<p>- p 不仅拷贝文件内容，还有修改时间，存取模式，存取控制表， 但不拷贝 </p>
		<p>UID 及 GID </p>
		<p>- r 若文件名为目录，则拷贝目录下所有文件及子目录和它们的文件，此时 </p>
		<p>目标必须为目录 </p>
		<p>[例子]: </p>
		<p>cp file1 file2 将文件 file1 拷贝到文件 file2 </p>
		<p>cp file1 file2 /tmp 将文件 file1 和文件 file2 拷贝到目录 /tmp 下 </p>
		<p>cp -r /tmp /mytmp 将目录 /tmp 下所有文件及其子目录拷贝至目录/mytmp </p>
		<p> </p>
		<p>1.10 mv </p>
		<p> </p>
		<p>[语法]: mv [-f] [-i] 文件1 [文件2...] 目标 </p>
		<p>[说明]: 将文件移动至目标，若目标是文件名，则相当于文件改名 </p>
		<p>- i 在覆盖已存在文件时作提示，若回答 y 则覆盖，其他则中止 </p>
		<p>- f 覆盖前不作任何提示 </p>
		<p>[例子]: </p>
		<p>mv file1 file2 将文件 file1 改名为 file2 </p>
		<p>mv file1 file2 /tmp 将文件 file1 和文件 file2 移动到目录 /tmp 下 </p>
		<p> </p>
		<p> </p>
		<p>1.11 rm </p>
		<p> </p>
		<p>[语法]: rm [-f] [-i] 文件... </p>
		<p>或 rm -r [-f] [-i] 目录名... [文件] </p>
		<p>[说明]: 用来删除文件或目录 </p>
		<p>- f 删除文件时不作提示 </p>
		<p>- r 递归地删除目录及其所有子目录 </p>
		<p>- i 删除文件之前先作提示 </p>
		<p>[例子]: </p>
		<p>rm file1 删除文件 file1 </p>
		<p>rm -i /tmp/* 删除目录 /tmp 下的所有文件 </p>
		<p>rm -r /mytmp 递归地删除目录 /mytmp </p>
		<p> </p>
		<p> </p>
		<p>1.12 chmod </p>
		<p> </p>
		<p>[语法]: chmod [-R] 模式 文件... </p>
		<p>或 chmod [ugoa] {+|-|=} [rwxst] 文件... </p>
		<p>[说明]: 改变文件的存取模式，存取模式可表示为数字或符号串，例如： </p>
		<p>chmod nnnn file ， n为0-7的数字，意义如下: </p>
		<p>4000 运行时可改变UID </p>
		<p>2000 运行时可改变GID </p>
		<p>1000 置粘着位 </p>
		<p>0400 文件主可读 </p>
		<p>0200 文件主可写 </p>
		<p>0100 文件主可执行 </p>
		<p>0040 同组用户可读 </p>
		<p>0020 同组用户可写 </p>
		<p>0010 同组用户可执行 </p>
		<p>0004 其他用户可读 </p>
		<p>0002 其他用户可写 </p>
		<p>0001 其他用户可执行 </p>
		<p>nnnn 就是上列数字相加得到的，例如 chmod 0777 file 是指将文件 file 存取权限置为所有用户可读可写可执行。 </p>
		<p>-R 递归地改变所有子目录下所有文件的存取模式 </p>
		<p>u 文件主 </p>
		<p>g 同组用户 </p>
		<p>o 其他用户 </p>
		<p>a 所有用户 </p>
		<p>+ 增加后列权限 </p>
		<p>- 取消后列权限 </p>
		<p>= 置成后列权限 </p>
		<p>r 可读 </p>
		<p>w 可写 </p>
		<p>x 可执行 </p>
		<p>s 运行时可置UID </p>
		<p>t 运行时可置GID </p>
		<p>[例子]: </p>
		<p>chmod 0666 file1 file2 将文件 file1 及 file2 置为所有用户可读可写 </p>
		<p>chmod u+x file 对文件 file 增加文件主可执行权限 </p>
		<p>chmod o-rwx 对文件file 取消其他用户的所有权限 </p>
		<p> </p>
		<p> </p>
		<p>1.13 chown </p>
		<p> </p>
		<p>[语法]: chown [-R] 文件主 文件... </p>
		<p>[说明]: 文件的UID表示文件的文件主，文件主可用数字表示， 也可用一个有效的用户名表示，此命令改变一个文件的UID，仅当此文件的文件主或超级用户可使用。 </p>
		<p>-R 递归地改变所有子目录下所有文件的存取模式 </p>
		<p>[例子]: </p>
		<p>chown mary file 将文件 file 的文件主改为 mary </p>
		<p>chown 150 file 将文件 file 的UID改为150 </p>
		<p> </p>
		<p> </p>
		<p>1.14 chgrp </p>
		<p> </p>
		<p>[语法]: chgrp [-R] 文件组 文件... </p>
		<p>[说明]： 文件的GID表示文件的文件组，文件组可用数字表示，也可用一个有效的组名表示，此命令改变一个文件的GID，可参看chown。 </p>
		<p>-R 递归地改变所有子目录下所有文件的存取模式 </p>
		<p>[例子]: </p>
		<p>chgrp group file 将文件 file 的文件组改为 group </p>
		<p> </p>
		<p> </p>
		<p>1.15 cmp </p>
		<p> </p>
		<p>[语法]: cmp [-l] [-s] 文件1 文件2 </p>
		<p>[说明]: 比较两个文件，若文件1 为 "-" ，则使用标准输入， 两个文件相同则无提示，不同则显示出现第一个不同时的字符数和行号。 </p>
		<p>-l 显示每个不同处的字节数(10进制)和不同的字节(8进制) </p>
		<p>-s 不作任何提示，只返回码 </p>
		<p>[例子]: </p>
		<p>cmp file1 file2 比较文件 file1 和 file2 </p>
		<p>cmp -l file1 file2 比较文件file1 和 file2 的每处不同 </p>
		<p> </p>
		<p>1.16 diff </p>
		<p> </p>
		<p>[语法]: diff [-be] 文件1 文件2 </p>
		<p>[说明]: 本命令比较两个文本文件，将不同的行列出来 </p>
		<p>-b 将一串空格或TAB转换成一个空格或TAB </p>
		<p>-e 生成一个编辑角本，作为ex或ed的输入可将文件1转换成文件2 </p>
		<p>[例子]: </p>
		<p>diff file1 file2 </p>
		<p>diff -b file1 file2 </p>
		<p>diff -e file1 file2 &gt;edscript </p>
		<p> </p>
		<p> </p>
		<p>1.17 wc </p>
		<p> </p>
		<p>[语法]: wc [-lwc] 文件... </p>
		<p>[说明]: 统计文件的行、字、字符数，若无指定文件，则统计标准输入 </p>
		<p>-l 只统计行数 </p>
		<p>-w 只统计字数 </p>
		<p>-c 只统计字符数 </p>
		<p>[例子]: </p>
		<p>wc -l file1 file2 统计文件file1和file2 的行数 </p>
		<p> </p>
		<p> </p>
		<p>1.18 split </p>
		<p> </p>
		<p>[语法]: split [-n] [ 文件 [名字]] </p>
		<p>[说明]: split 将指定大文件分解为若干个小文件，每个文件长度为n行(n 缺省时为1000)，第一个小文件名为指定的名字后跟aa，直至zz，名字缺省值为x，若未指定大文件名，则使用标准输入 </p>
		<p>[例子]: </p>
		<p>split -500 largefile little </p>
		<p>将文件largefile 每500行写入一个文件，第一个文件名为littleaa </p>
		<p> </p>
		<p> </p>
		<p>1.19 touch </p>
		<p> </p>
		<p>[语法]: touch [-amc] [mmddhhmm[yy]] 文件... </p>
		<p>[说明]: 将指定文件的访问时间和修改时间改变，若指定文件不存在则创建之，若无指定时间，则使用当前时间，返回值是未成功改变时间的文件个数，包括不存在而又未能创建的文件。 </p>
		<p>-a 只改变访问时间 </p>
		<p>-m 只改变修改时间 </p>
		<p>-c 若文件不存在，不创建它且不作提示 </p>
		<p>mmddhhmm[yy] 两位表示 月日时分[年] </p>
		<p>[例子]: </p>
		<p>touch file </p>
		<p>更新文件file的时间 </p>
		<p>touch 0701000097 HongKong </p>
		<p>将文件HongKong的时间改为97年7月1日0时0分 </p>
		<p> </p>
		<p> </p>
		<p>1.20 file </p>
		<p> </p>
		<p>[语法]: file [-f 文件名文件] 文件... </p>
		<p>[说明]: file 对指定文件进行测试，尽量猜测出文件类型并显示出来 </p>
		<p>-f 文件名文件 文件名文件是一个包含了文件名的文本文件， -f 选项测试 </p>
		<p>文件名文件中所列出的文件 </p>
		<p>[例子]: </p>
		<p>file * 显示当前目录下所有文件的类型 </p>
		<p> </p>
		<p> </p>
		<p>1.21 pack </p>
		<p> </p>
		<p>[语法]: pack 文件... </p>
		<p>[说明]: pack 将指定文件转储为压缩格式，文件名后加 ".z "， 文件存取模式，访问时间，修改时间等均不变 </p>
		<p>[例子]: </p>
		<p>pack largefile 将largefile 压缩后转储为largefile.z </p>
		<p> </p>
		<p> </p>
		<p>1.22 pcat 显示压缩文件 </p>
		<p> </p>
		<p>[语法]: pcat 文件... </p>
		<p>[说明]: pcat 显示输出压缩文件 </p>
		<p> </p>
		<p>[例子]: </p>
		<p>pcat largefile.z 显示压缩前的largefile </p>
		<p>pcat largefile.z &gt; oldfile 显示压缩前的laregfile，并将其重定向到 </p>
		<p>文件oldfile中 </p>
		<p> </p>
		<p> </p>
		<p>1.23 unpack </p>
		<p> </p>
		<p>[语法]: unpack 文件... </p>
		<p>[说明]: 将压缩后的文件解压后转储为压缩前的格式 </p>
		<p>[例子]: </p>
		<p>unpack largefile.z 将压缩文件largefile.z解压后转储为largefile </p>
		<p> </p>
		<p> </p>
		<p>1.24 find </p>
		<p> </p>
		<p>[语法]: find 路径名... 表达式 </p>
		<p>[说明]: find 命令递归地遍历指定路径下的每个文件和子目录，看该文件是否能使表达式值为真，以下 n 代表一个十进制整数，+n 代表打印 n ， -n 代表小于 n ，下面是合法表达式说明： </p>
		<p>-name 模式 文件名与模式匹配则为真，(\ 为转意符) </p>
		<p>-perm [-]八进制数 文件存取模式与八进制数相同则为真若有- 选项，则文件存 </p>
		<p>取模式含有八进制数规定模式即为真 </p>
		<p>-size n[c] 文件块长度为 n 则真(一块为512字节)，若 </p>
		<p>有c 选项，则文件字节长度为 n 则真 </p>
		<p>-atime n 若文件的最近访问时间为 n 天前则为真， </p>
		<p>find 命令将改变其访问的目录的访问时间 </p>
		<p>-mtime n 若文件的最近修改时间为 n 天前则为真 </p>
		<p>-ctime n 若文件状态为 n 天前改变则为真 </p>
		<p>-exec 命令 { }\; 若命令返回值为0则真，{ }内为命令参数， </p>
		<p>此命令必须以 \; 为结束 </p>
		<p>-ok 命令 { }\; 与 exec 相同，只是在命令执行前先提示，若 </p>
		<p>回答 y 则执行命令 </p>
		<p>-print 显示输出使表达式为真的文件名 </p>
		<p>-newer 文件 若文件的访问时间比newer 指定的文件新则真 </p>
		<p>-depth 先下降到搜索目录的子目录，然后才至其自身 </p>
		<p>-mount 仅查找包含指定目录的文件系统 </p>
		<p>-local 文件在当前文件系统时为真 </p>
		<p>-type c 文件类型为 c 则真，c 取值可为 b(块文件) c (字符文件) </p>
		<p>d(目录) l (符号链接) p (命名管道) f (普通文件) </p>
		<p>\( 表达式 \) 表达式为真则真 </p>
		<p>-links n 文件链接数为 n 时为真 </p>
		<p>-user 用户 当文件属于用户时为真，用户可用数字表示UID </p>
		<p>-nouser 当文件不属于 /etc/passwd 中的一个用户时为真 </p>
		<p>-group 文件组 当文件属于文件组时为真，文件组可用数字表示GID </p>
		<p>-nogroup 当文件不属于 /etc/group 中的一个组时为真 </p>
		<p>-fstype 类型 当文件所属文件系统类型为指定类型时真 </p>
		<p>-inum n 当文件 i 节点号为 n 时为真 </p>
		<p>-prune 当目录名与模式匹配时，不再搜索其子目录 </p>
		<p>可以用逻辑操作符将简单表达式连接成复杂表达式 </p>
		<p>逻辑操作符有 ! 表示非操作， -o 表示或操作，两个表达式并列则表示 </p>
		<p>与操作 </p>
		<p>[例子]: </p>
		<p>find / -name find* -print </p>
		<p>从根目录开始搜索文件名如 find* 的文件并显示之 </p>
		<p>find ./ -exec sleep{1}\; -print </p>
		<p>每秒显示一个当前目录下的文件 </p>
		<p>find $HOME \(-name a.out -o -name '*.o' \) -atime +7 -exec rm {} \; </p>
		<p>从$HOME目录开始搜索，删除所有文件名为a.out 或 *.o 且访问时间在7天前的文件 </p>
		<p> </p>
		<p> </p>
		<p>1.25 grep </p>
		<p> </p>
		<p>[语法]: grep [选项] 模式 [文件...] </p>
		<p>[说明]: 在指定的文件中搜索模式，并显示所有包含模式的行，模式是一个正规表达式，在使用正规表达式时，最好将其引在单引号(') 中，若指定文件为缺省，则使用标准输入，正规表达式可以是： </p>
		<p>. 匹配任意一个字符 </p>
		<p>* 匹配0个或多个*前的字符 </p>
		<p>^ 匹配行开头 </p>
		<p>$ 匹配行结尾 </p>
		<p>[] 匹配[ ]中的任意一个字符，[]中可用 - 表示范围， </p>
		<p>例如[a-z]表示字母a 至z 中的任意一个 </p>
		<p>\ 转意字符 </p>
		<p>命令中的选项为： </p>
		<p>-b 显示块号 </p>
		<p>-c 仅显示各指定文件中包含模式的总行数 </p>
		<p>-i 模式中字母不区分大小写 </p>
		<p>-h 不将包含模式的文件名显示在该行上 </p>
		<p>-l 仅显示包含模式的文件名 </p>
		<p>-n 显示模式所在行的行号 </p>
		<p>-s 指定文件若不存在或不可读，不提示错误信息 </p>
		<p>-v 显示所有不包含模式的行 </p>
		<p>[例子]: </p>
		<p>grep 'good' * 在所有文件中搜索含有字符串 good 的行 </p>
		<p>grep '^myline' mytext 在文件mytext中搜索行首出现myline字符串的行 </p>
		<p> </p>
		<p> </p>
		<p>1.26 vi </p>
		<p> </p>
		<p>[语法]：vi [-wn] [-R] 文件... </p>
		<p>[说明]: vi 是一个基于行编辑器 ex 上的全屏幕编辑器，可以在vi 中使用 ex，ed的全部命令，vi选项中 -wn 指将编辑窗口大小置为n行，-R 为将编辑的文件置为只读模式， vi 工作模式分为命令模式和输入模式，一般情况下在命令模式下，可敲入vi命令，进入输入模式下时可以编辑要编辑的文本，命令 a A i I o O c C s S R 可进入输入模式，在输入模式下按 ESC 键可推出输入模式，回到命令模式，在命令模式中敲入：命令，则可进入ex方式，在屏幕底部出现提示符 ： ，此时可使用任意ex命令，屏幕底行也用来作/ ? ! 命令的提示行，大多数命令可以在其前面加数字，表示命令执行的重复次数，下面简单介绍一下vi 的命令集，^ 表示(CTRL)键 </p>
		<p>^B 退回前一页，前面加数字表示重复次数，每次换页时 </p>
		<p>保留上一页的两行 </p>
		<p>^D 在命令模式下，表示下滚屏幕的一半，在输入模式下，表示回退至 </p>
		<p>左边的自动缩进处 </p>
		<p>^E 显示屏幕底线之下的一行 </p>
		<p>^F 前进一页，前面加数字表示重复次数，每次换页时 </p>
		<p>保留上一页的两行 </p>
		<p>^G 显示当前文件名，当前行号和文件总行数，并用百分号当前行在 </p>
		<p>整个文件中的位置 </p>
		<p>^H(退格) 在命令模式下，光标左移一格；在输入模式下，删去前面的字符 </p>
		<p>^I(TAB) 在输入模式下，产生一串空格 </p>
		<p>^J(LF) 光标下移一行 </p>
		<p>^L 刷新屏幕，即将屏幕重新显示 </p>
		<p>^M(回车) 在命令模式下，光标移动至下行开头 </p>
		<p>在输入模式下，开辟一新行 </p>
		<p>^N 光标下移一行 </p>
		<p>^P 光标上移一行 </p>
		<p>^Q 在输入模式下，将其后的非打印字符作为正文插入 </p>
		<p>^R 刷新屏幕 </p>
		<p>^U 屏幕上滚一半，前面加数字时表示上滚的行数，此数字对 </p>
		<p>以后的^D ^U 命令有效 </p>
		<p>^V 在输入模式下，将其后的非打印字符作为正文插入 </p>
		<p>^W 在输入模式下，使光标回退一个字 </p>
		<p>^Y 显示屏幕底线之上的一行 </p>
		<p>^Z 暂停编辑，退回上层Shell </p>
		<p>^[(ESC) 退出输入模式，回到命令模式 </p>
		<p>! 暂时退出编辑，执行Shell命令 </p>
		<p>"(双引号) 用于标志有名缓冲区，编号缓冲区1-9用于保存被删去的正文，字 </p>
		<p>母名缓冲区a-z供用户存放自定义的正文 </p>
		<p>$ 将光标移动到当前行尾，前加数字则表示前移行数，如2$表示移动 </p>
		<p>到下一行行尾 </p>
		<p>% 将光标移动到配对的小括号()或大括号{}上去 </p>
		<p>( 退回句子开头 </p>
		<p>) 前移到句子开头 </p>
		<p>- 退到上一行第一个非空格字符 </p>
		<p>. 重复上一次改变缓冲区内容的命令 </p>
		<p>/ 模式 向前搜索模式，将光标移动到模式出现处，模式是一个正规 </p>
		<p>表达式，(参看 grep) </p>
		<p>： 在屏幕底部提示：，其后可使用ex命令 </p>
		<p>? 功能同 / ，但方向是向前查找 </p>
		<p>[[ 光标回退至前一节分界处 </p>
		<p>\ 转意符 </p>
		<p>]] 光标前移至节分界处 </p>
		<p>^(不是CTRL) 光标移至当前行第一个非空字符上 </p>
		<p>' 连续两个''表示将光标移至其移动前的位置，'后跟字母表示光标字 </p>
		<p>母标记的行首(参看 m 命令) </p>
		<p>A 在行尾插入正文，进入输入模式 </p>
		<p>B 光标回退一个字 </p>
		<p>C 替换光标后的内容 </p>
		<p>D 删除光标后的内容 </p>
		<p>E 光标前移到字尾 </p>
		<p>F 字符 在当前行向左查找指定字符 </p>
		<p>G 光标移动到其前面数字指定的行，若未指定则移动到最后一行 </p>
		<p>H 光标移动到屏幕顶行，若前面有数字，则移动到屏幕上该数字 </p>
		<p>指定的行 </p>
		<p>I 在行开头插入正文 </p>
		<p>J 连接两行，若前面有数字则连接数字指定的行 </p>
		<p>L 光标移动到屏幕底行，若前面有数字，则移动到屏幕底线往上数该 </p>
		<p>数字指定的行 </p>
		<p>M 光标移动到屏幕中线 </p>
		<p>N 使用模式查找/或?时，重复找下一个匹配的模式，但方向与上次相 </p>
		<p>反，其功能同 n ，但方向相反 </p>
		<p>O 在当前行上开辟一新行 </p>
		<p>P 将上次被删除的正文插入光标前面，可在其前面加缓冲区编号，编 </p>
		<p>号1-9用于保存被删去的正文，字母名缓冲区a-z供用户存放自定 </p>
		<p>义的正文 </p>
		<p>Q 从vi 推出进入ex命令状态 </p>
		<p>R 替换字符串 </p>
		<p>S 替换整行 </p>
		<p>T 字符 向左查找字符 </p>
		<p>U 将当前行恢复至第一次修改前的状态 </p>
		<p>W 光标移至下一个字首 </p>
		<p>X 删除光标前的字符 </p>
		<p>Y 将当前行存入无名缓冲区，前面加数字表示存入的行数，也可用有 </p>
		<p>名缓冲区来保存，以后可用命令p或P将其取出 </p>
		<p>ZZ 存盘退出vi </p>
		<p>a 光标后插入正文 </p>
		<p>b 光标回退至上一个字首 </p>
		<p>cw 替换当前字 </p>
		<p>c) 替换当前句子 </p>
		<p>dw 删除一个字 </p>
		<p>dd 删除一行 </p>
		<p>e 光标移到下一个字末 </p>
		<p>f 字符 在当前行向前查找字符 </p>
		<p>h 光标左移一格 </p>
		<p>i 在光标前插入正文 </p>
		<p>j 光标下移一行 </p>
		<p>k 光标上移一行 </p>
		<p>l 光标右移一格 </p>
		<p>m 字母 用字母标记当前行，以后可用 '字母使光标移动到当前行， </p>
		<p>(参看'命令) </p>
		<p>n 重复上次 / 或 ? 命令 </p>
		<p>o 在当前行下开辟一新行 </p>
		<p>p 将用户缓冲区内容放到光标位置(参看P命令) </p>
		<p>r 替换当前字符 </p>
		<p>s 用一串字符替换当前字符 </p>
		<p>t 字符 光标移动至字符前 </p>
		<p>u 取消上次操作 </p>
		<p>w 光标移至下一字首 </p>
		<p>x 删除当前字符 </p>
		<p>yw 将当前字存入无名缓冲区，前面可加"x，表示存入名字为x的有名 </p>
		<p>缓冲区(x为a-z)，也可加数字表示存入的字数，以后可用P或p命 </p>
		<p>令取出 </p>
		<p>yy 将当前行存入无名缓冲区，用法参看yw </p>
		<p>{ 光标移动至前一段开头 </p>
		<p>| 光标移至行首，若前面加数字，则移到数字指定行的行首 </p>
		<p>} 光标移至下一段开头 </p>
		<p>在：提示符下，常用命令如下: </p>
		<p>：w 当前文件存盘 </p>
		<p>：w! 强制存盘 </p>
		<p>：w 文件 将内容写入指定文件 </p>
		<p>：w! 文件 强制写入指定文件 </p>
		<p>：x，y w 文件 将 x至 y 行写入指定文件中 </p>
		<p>：r 文件 将文件读到光标位置 </p>
		<p>：r ! 命令 将系统命令的输出读到光标位置 </p>
		<p>：q 退出编辑 </p>
		<p>：q! 强制退出 </p>
		<p>：x 与命令ZZ相同 </p>
		<p>：e 文件名 编辑另一文件 </p>
		<p>：e ! 重新编辑文件，放弃任何改变 </p>
		<p>：sh 执行sh，结束后回到编辑 </p>
		<p>：! 命令 执行命令后回到编辑 </p>
		<p>：n 编辑下一文件 </p>
		<p>：n 文件表 重新定义待编辑文件表 </p>
		<p>：set 设置 vi 的选项，例如 set nu 表示每行前显示行号，在选项前 </p>
		<p>加no则表示清除该选项，例如 set nonu 表示每行前不显示行 </p>
		<p>号，下面是一些常用的选项: </p>
		<p>ai 自动缩进 </p>
		<p>aw 编辑下一文件前自动存盘 </p>
		<p>ic 查找字符串时不区分大小写 </p>
		<p>nu 每行前显示行号 </p>
		<p>sm 输入)及}时显示与之配对的( 或 { </p>
		<p>slow 插入时延迟屏幕刷新 </p>
		<p>ws 使查找能绕过文件尾从头进行 </p>
		<p>wa 写文件之前不作对文件的检查</p>
<img src ="http://www.blogjava.net/huyi2006/aggbug/106371.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-03-26 13:07 <a href="http://www.blogjava.net/huyi2006/articles/106371.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item><item><title>gcc的使用</title><link>http://www.blogjava.net/huyi2006/articles/94880.html</link><dc:creator>allic</dc:creator><author>allic</author><pubDate>Fri, 19 Jan 2007 02:56:00 GMT</pubDate><guid>http://www.blogjava.net/huyi2006/articles/94880.html</guid><wfw:comment>http://www.blogjava.net/huyi2006/comments/94880.html</wfw:comment><comments>http://www.blogjava.net/huyi2006/articles/94880.html#Feedback</comments><slash:comments>0</slash:comments><wfw:commentRss>http://www.blogjava.net/huyi2006/comments/commentRss/94880.html</wfw:commentRss><trackback:ping>http://www.blogjava.net/huyi2006/services/trackbacks/94880.html</trackback:ping><description><![CDATA[
		<div class="postText">要想读懂本文，你需要对C语言有基本的了解，本文将介绍如何使用gcc编译器。首先，我们介绍如何在命令行方式下使用编译器编译简单的C源代码。然后，我们简要介绍一下编译器究竟作了那些工作，以及如何控制编译过程。我们也简要介绍了调试器的使用方法。 <br /><br />GCC rules <br /><br />你能想象使用封闭源代码的私有编译器编译自由软件吗？你怎么知道编译器在你的可执行文件中加入了什么？可能会加入各种后门和木马。Ken Thompson是一个著名的黑客，他编写了一个编译器，当编译器编译自己时，就在'login'程序中留下后门和永久的木马。请到 这里 阅读他对这个杰作的描述。幸运的是，我们有了gcc。当你进行 configure; make; make install 时， gcc在幕后做了很多繁重的工作。如何才能让gcc为我们工作呢？我们将开始编写一个纸牌游戏，不过我们只是为了演示编译器的功能，所以尽可能地精简了代码。我们将从头开始一步一步地做，以便理解编译过程，了解为了制作可执行文件需要做些什么，按什么顺序做。我们将看看如何编译C程序，以及如何使用编译选项让gcc按照我们的要求工作。步骤（以及所用工具）如下： 预编译 (gcc -E)， 编译 (gcc)， 汇编 (as)，和 连接 (ld)。 <br /><br />开始... <br /><br />首先，我们应该知道如何调用编译器。实际上，这很简单。我们将从那个著名的第一个C程序开始。（各位老前辈，请原谅我）。 <br /><br />＃i nclude <stdio.h></stdio.h><br /><br />int main() <br /><br />{ <br />printf("Hello World! <br />"); <br />} <br /><br />把这个文件保存为 game.c。 你可以在命令行下编译它： <br /><br />gcc game.c <br /><br />在默认情况下，C编译器将生成一个名为 a.out 的可执行文件。你可以键入如下命令运行它： <br /><br />a.out <br />Hello World <br /><br />每一次编译程序时，新的 a.out 将覆盖原来的程序。你无法知道是哪个程序创建了 a.out。我们可以通过使用 -o 编译选项，告诉 gcc我们想把可执行文件叫什么名字。我们将把这个程序叫做 game，我们可以使用任何名字，因为C没有Java那样的命名限制。 <br /><br />gcc -o game game.c <br /><br />game <br />Hello World <br /><br />到现在为止，我们离一个有用的程序还差得很远。如果你觉得沮丧，你可以想一想我们已经编译并运行了一个程序。因为我们将一点一点为这个程序添加功能，所以我们必须保证让它能够运行。似乎每个刚开始学编程的程序员都想一下子编一个1000行的程序，然后一次修改所有的错误。没有人，我是说没有人，能做到这个。你应该先编一个可以运行的小程序，修改它，然后再次让它运行。这可以限制你一次修改的错误数量。另外，你知道刚才做了哪些修改使程序无法运行，因此你知道应该把注意力放在哪里。这可以防止这样的情况出现：你认为你编写的东西应该能够工作，它也能通过编译，但它就是不能运行。请切记，能够通过编译的程序并不意味着它是正确的。 <br /><br />下一步为我们的游戏编写一个头文件。头文件把数据类型和函数声明集中到了一处。这可以保证数据结构定义的一致性，以便程序的每一部分都能以同样的方式看待一切事情。 <br /><br />#ifndef DECK_H <br />#define DECK_H <br /><br />#define DECKSIZE 52 <br /><br />typedef struct deck_t <br />{ <br />int card[DECKSIZE]; <br />/* number of cards used */ <br />int dealt; <br />}deck_t; <br /><br />#endif /* DECK_H */ <br /><br />把这个文件保存为 deck.h。只能编译 .c 文件，所以我们必须修改 game.c。在game.c的第2行，写上 ＃i nclude "deck.h"。在第5行写上 deck_t deck;。为了保证我们没有搞错，把它重新编译一次。 <br /><br />gcc -o game game.c <br /><br />如果没有错误，就没有问题。如果编译不能通过，那么就修改它直到能通过为止。 <br /><br />预编译 <br /><br />编译器是怎么知道 deck_t 类型是什么的呢？因为在预编译期间，它实际上把"deck.h"文件复制到了"game.c"文件中。源代码中的预编译指示以"#"为前缀。你可以通过在gcc后加上 -E 选项来调用预编译器。 <br /><br />gcc -E -o game_precompile.txt game.c <br />wc -l game_precompile.txt <br />3199 game_precompile.txt <br /><br />几乎有3200行的输出！其中大多数来自 stdio.h 包含文件，但是如果你查看这个文件的话，我们的声明也在那里。如果你不用 -o 选项指定输出文件名的话，它就输出到控制台。预编译过程通过完成三个主要任务给了代码很大的灵活性。 <br /><br />1. 把"include"的文件拷贝到要编译的源文件中。 <br />2. 用实际值替代"define"的文本。 <br />3. 在调用宏的地方进行宏替换。 <br /><br />这就使你能够在整个源文件中使用符号常量（即用DECKSIZE表示一付牌中的纸牌数量），而符号常量是在一个地方定义的，如果它的值发生了变化，所有使用符号常量的地方都能自动更新。在实践中，你几乎不需要单独使用 -E 选项，而是让它把输出传送给编译器。 <br /><br /><br />编译 <br /><br />作为一个中间步骤，gcc把你的代码翻译成汇编语言。它一定要这样做，它必须通过分析你的代码搞清楚你究竟想要做什么。如果你犯了语法错误，它就会告诉你，这样编译就失败了。人们有时会把这一步误解为整个过程。但是，实际上还有许多工作要gcc去做呢。 <br /><br />汇编 <br /><br />as 把汇编语言代码转换为目标代码。事实上目标代码并不能在CPU上运行，但它离完成已经很近了。编译器选项 -c 把 .c 文件转换为以 .o 为扩展名的目标文件。 如果我们运行 <br /><br />gcc -c game.c <br /><br />我们就自动创建了一个名为game.o的文件。这里我们碰到了一个重要的问题。我们可以用任意一个 .c 文件创建一个目标文件。正如我们在下面所看到的，在连接步骤中我们可以把这些目标文件组合成可执行文件。让我们继续介绍我们的例子。因为我们正在编写一个纸牌游戏，我们已经把一付牌定义为 deck_t，我们将编写一个洗牌函数。这个函数接受一个指向deck类型的指针，并把一付随机的牌装入deck类型。它使用'drawn' 数组跟踪记录那些牌已经用过了。这个具有DECKSIZE个元素的数组可以防止我们重复使用一张牌。 <br /><br />＃i nclude <stdlib.h></stdlib.h><br />＃i nclude <stdio.h></stdio.h><br />＃i nclude <time.h></time.h><br />＃i nclude "deck.h" <br /><br />static time_t seed = 0; <br /><br />void shuffle(deck_t *pdeck) <br />{ <br />/* Keeps track of what numbers have been used */ <br />int drawn[DECKSIZE] = {0}; <br />int i; <br /><br />/* One time initialization of rand */ <br />if(0 == seed) <br />{ <br />seed = time(NULL); <br />srand(seed); <br />} <br />for(i = 0; i &lt; DECKSIZE; i++) <br />{ <br />int value = -1; <br />do <br />{ <br />value = rand() % DECKSIZE; <br />} <br />while(drawn[value] != 0); <br /><br />/* mark value as used */ <br />drawn[value] = 1; <br /><br />/* debug statement */ <br />printf("%i <br />", value); <br />pdeck-&gt;card[i] = value; <br />} <br />pdeck-&gt;dealt = 0; <br />return; <br />} <br /><br />把这个文件保存为 shuffle.c。我们在这个代码中加入了一条调试语句，以便运行时，能输出所产生的牌号。这并没有为我们的程序添加功能，但是现在到了关键时刻，我们看看究竟发生了什么。因为我们的游戏还在初级阶段，我们没有别的办法确定我们的函数是否实现了我们要求的功能。使用那条printf语句，我们就能准确地知道现在究竟发生了什么，以便在开始下一阶段之前我们知道牌已经洗好了。在我们对它的工作感到满意之后，我们可以把那一行语句从代码中删掉。这种调试程序的技术看起来很粗糙，但它使用最少的语句完成了调试任务。以后我们再介绍更复杂的调试器。 <br />请注意两个问题。 <br /><br />1. 我们用传址方式传递参数，你可以从'&amp;'（取地址）操作符看出来。这把变量的机器地址传递给了函数，因此函数自己就能改变变量的值。也可以使用全局变量编写程序，但是应该尽量少使用全局变量。指针是C的一个重要组成部分，你应该充分地理解它。 <br />2. 我们在一个新的 .c 文件中使用函数调用。操作系统总是寻找名为'main'的函数，并从那里开始执行。 shuffle.c 中没有'main'函数，因此不能编译为独立的可执行文件。我们必须把它与另一个具有'main'函数并调用'shuffle'的程序组合起来。 <br /><br />运行命令 <br /><br />gcc -c shuffle.c <br /><br />并确定它创建了一个名为 shuffle.o 的新文件。编辑game.c文件，在第7行，在 deck_t类型的变量 deck 声明之后，加上下面这一行： <br /><br />shuffle(&amp;deck); <br /><br />现在，如果我们还象以前一样创建可执行文件，我们就会得到一个错误 <br /><br />gcc -o game game.c <br /><br />/tmp/ccmiHnJX.o: In function `main': <br />/tmp/ccmiHnJX.o(.text+0xf): undefined reference to `shuffle' <br />collect2: ld returned 1 exit status <br /><br />编译成功了，因为我们的语法是正确的。但是连接步骤却失败了，因为我们没有告诉编译器'shuffle'函数在哪里。那么，到底什么是连接？我们怎样告诉编译器到哪里寻找这个函数呢？ <br /><br /><br />连接 <br /><br />连接器ld，使用下面的命令，接受前面由 as 创建的目标文件并把它转换为可执行文件 <br /><br />gcc -o game game.o shuffle.o <br /><br />这将把两个目标文件组合起来并创建可执行文件 game。 <br /><br />连接器从shuffle.o目标文件中找到 shuffle 函数，并把它包括进可执行文件。目标文件的真正好处在于，如果我们想再次使用那个函数，我们所要做的就是包含"deck.h" 文件并把 shuffle.o 目标文件连接到新的可执行文件中。 <br /><br />象这样的代码重用是经常发生的。虽然我们并没有编写前面作为调试语句调用的 printf 函数，连接器却能从我们用 ＃i nclude <stdlib.h></stdlib.h>语句包含的文件中找到它的声明，并把存储在C库（/lib/libc.so.6）中的目标代码连接进来。这种方式使我们可以使用已能正确工作的其他人的函数，只关心我们所要解决的问题。这就是为什么头文件中一般只含有数据和函数声明，而没有函数体。一般，你可以为连接器创建目标文件或函数库，以便连接进可执行文件。我们的代码可能产生问题，因为在头文件中我们没有放入任何函数声明。为了确保一切顺利，我们还能做什么呢？ <br /><br />另外两个重要选项 <br /><br />-Wall 选项可以打开所有类型的语法警告，以便帮助我们确定代码是正确的，并且尽可能实现可移植性。当我们使用这个选项编译我们的代码时，我们将看到下述警告： <br /><br />game.c:9: warning: implicit declaration of function `shuffle' <br /><br />这让我们知道还有一些工作要做。我们需要在头文件中加入一行代码，以便告诉编译器有关 shuffle 函数的一切，让它可以做必要的检查。听起来象是一种狡辩，但这样做 可以把函数的定义与实现分离开来，使我们能在任何地方使用我们的函数，只要包含新的头文件 并把它连接到我们的目标文件中就可以了。下面我们就把这一行加入deck.h中。 <br /><br />void shuffle(deck_t *pdeck); <br /><br />这就可以消除那个警告信息了。 <br /><br />另一个常用编译器选项是优化选项 -O# (即 -O2)。 这是告诉编译器你需要什么级别的优化。编译器具有一整套技巧可以使你的代码运行得更快一点。对于象我们这种小程序，你可能注意不到差别，但对于大型程序来说，它可以大幅度提高运行速度。你会经常碰到它，所以你应该知道它的意思。 <br /><br />调试 <br /><br />我们都知道，代码通过了编译并不意味着它按我们得要求工作了。你可以使用下面的命令验证是否所有的号码都被使用了 <br /><br />game | sort - n | less <br /><br />并且检查有没有遗漏。如果有问题我们该怎么办？我们如何才能深入底层查找错误呢？ <br /><br />你可以使用调试器检查你的代码。大多数发行版都提供著名的调试器：gdb。如果那些众多的命令行选项让你感到无所适从，那么你可以使用KDE提供的一个很好的前端工具 KDbg。还有一些其它的前端工具，它们都很相似。要开始调试，你可以选择 File-&gt;Executable 然后找到你的 game 程序。当你按下F5键或选择 Execution-&gt;从菜单运行时，你可以在另一个窗口中看到输出。怎么回事？在那个窗口中我们什么也看不到。不要担心，KDbg没有出问题。问题在于我们在可执行文件中没有加入任何调试信息，所以KDbg不能告诉我们内部发生了什么。编译器选项 -g 可以把必要的调试信息加入目标文件。你必须用这个选项编译目标文件（扩展名为.o），所以命令行成了： <br /><br />gcc -g -c shuffle.c game.c <br />gcc -g -o game game.o shuffle.o <br /><br />这就把钩子放入了可执行文件，使gdb和KDbg能指出运行情况。调试是一种很重要的技术，很值得你花时间学习如何使用。调试器帮助程序员的方法是它能在源代码中设置“断点”。现在你可以用右键单击调用 shuffle 函数的那行代码，试着设置断点。那一行边上会出现一个红色的小圆圈。现在当你按下F5键时，程序就会在那一行停止执行。按F8可以跳入shuffle函数。呵，我们现在可以看到 shuffle.c 中的代码了！我们可以控制程序一步一步地执行，并看到究竟发生了什么事。如果你把光标暂停在局部变量上，你将能看到变量的内容。太好了。这比那条 printf 语句好多了，是不是？ <br /><br /><br />小结 <br /><br />本文大体介绍了编译和调试C程序的方法。我们讨论了编译器走过的步骤，以及为了让编译器做这些工作应该给gcc传递哪些选项。我们简述了有关连接共享函数库的问题，最后介绍了调试器。真正了解你所从事的工作还需要付出许多努力，但我希望本文能让你正确地起步。你可以在 gcc、 as 和 ld的 man 和 info page中找到更多的信息。 <br /><br />自己编写代码可以让你学到更多的东西。作为练习你可以以本文的纸牌游戏为基础，编写一个21点游戏。那时你可以学学如何使用调试器。使用GUI的KDbg开始可以更容易一些。如果你每次只加入一点点功能，那么很快就能完成。切记，一定要保持程序一直能运行！ <br /><br />要想编写一个完整的游戏，你需要下面这些内容： <br /><br />* 一个纸牌玩家的定义（即，你可以把deck_t定义为player_t）。 <br />* 一个给指定玩家发一定数量牌的函数。记住在纸牌中要增加“已发牌”的数量，以便能知道还有那些牌可发。还要记住玩家手中还有多少牌。 <br />* 一些与用户的交互，问问玩家是否还要另一张牌。 <br />* 一个能打印玩家手中的牌的函数。 card 等于value % 13 （得数为0到12），suit 等于 value / 13 （得数为0到3）。 <br />* 一个能确定玩家手中的value的函数。Ace的value为零并且可以等于1或11。King的value为12并且可以等于10。 <br /><br /><br /><p id="TBPingURL">Trackback: http://tb.blog.csdn.net/TrackBack.aspx?PostId=742217</p><br /></div>
<img src ="http://www.blogjava.net/huyi2006/aggbug/94880.html" width = "1" height = "1" /><br><br><div align=right><a style="text-decoration:none;" href="http://www.blogjava.net/huyi2006/" target="_blank">allic</a> 2007-01-19 10:56 <a href="http://www.blogjava.net/huyi2006/articles/94880.html#Feedback" target="_blank" style="text-decoration:none;">发表评论</a></div>]]></description></item></channel></rss>