公 告

欢迎各位网友添加友情链接,在您添加本博客:http://linux0818.blogspot.com/ 做为链接后, E-mail:linux0818@gmail.com给我,我将将您的网址添加到本博客。

2008年10月24日星期五

Pcap编程

Tim Carstens
此文的最近更新见于<http://broker.dhs.org/pcap.htm>

好,让我们从看看这篇文章写给谁开始。显而易见的,需要一些C语言基础知识,除非你只想了解基本的理论。你不必是一个编码专家,因为这个领域只有经验丰富的程序员涉足,而我将尽可能详细的描述这些概念。另外,考虑到这是有关一个包嗅探器的,所以对网络基础知识的理解是有帮助的。所有在此出现的代码示例都已在FreeBSD 4.3平台上测试通过。

开始:pcap应用程序的格式

我们所要理解的第一件事情是一个基于pcap的嗅探器程序的总体布局。流程如下:

1.我们从决定用哪一个接口进行嗅探开始。在Linux中,这可能是eth0,而在BSD系统中则可能是xl1等等。我们也可以用一个字符串来定义这个设备,或者采用pcap提供的接口名来工作。
2.初始化pcap。在这里我们要告诉pcap对什么设备进行嗅探。假如愿意的话,我们还可以嗅探多个设备。怎样区分它们呢?使用 文件句柄。就像打开一个文件进行读写一样,必须命名我们的嗅探“会话”,以此使它们各自区别开来。
3.如果我们只想嗅探特定的传输(如TCP/IP包,发往端口23的包等等),我们必须创建一个规则集合,编译并且使用它。这个过程分为三个相互紧密关联的阶段。规则集合被置于一个字符串内,并且被转换成能被pcap读的格式(因此编译它)。编译实际上就是在我们的程序里调用一个不被外部程序使用的函数。接下来我们要告诉 pcap使用它来过滤出我们想要的那一个会话。
4.最后,我们告诉pcap进入它的主体执行循环。在这个阶段内pcap一直工作到它接收了所有我们想要的包为止。每当它收到一个包就调用另一个已经定义好的函数,这个函数可以做我们想要的任何工作,它可以剖析所部获的包并给用户打印出结果,它可以将结果保存为一个文件,或者什么也不作。
5.在嗅探到所需的数据后,我们要关闭会话并结束。
这是实际上一个很简单的过程。一共五个步骤,其中一个(第3个)是可选的。我们为什么不看一看是怎样实现每一个步骤呢?
设置设备
这是很简单的。有两种方法设置想要嗅探的设备。
第一种,我们可以简单的让用户告诉我们。考察下面的程序:
#include <stdio.h>
#include <pcap.h>
int main(int argc, char *argv[])
{
char *dev = argv[1];
printf("Device: %s\n", dev);
return(0);
}
用户通过传递给程序的第一个参数来指定设备。字符串“dev”以pcap能“理解”的格式保存了我们要嗅探的接口的名字(当然,用户必须给了我们一个真正存在的接口)。
另一种也是同样的简单。来看这段程序:
#include <stdio.h>
#include <pcap.h>
int main()
{
char *dev, errbuf[PCAP_ERRBUF_SIZE];
dev = pcap_lookupdev(errbuf);
printf("Device: %s\n", dev);
return(0);
}
在这个例子里,pcap就自己设置设备。“但是,等一下,Tim”,你会说,“字符串errbuf是做什么的?”大多数的pcap命令允许我们向它们传递字符串作为参数。这个字符串的目的是什么呢?如果命令失败,它将传给这个字符串关于错误的描述。这样,如果pcap_lookupdev()失败,它将在errbuf存储错误信息。很好,是不是?这就是我们怎样去设置设备。

打开设备进行嗅探

创建一个嗅探会话的任务真的非常简单。为此,我们使用pcap_open_live()函数。此函数的原型(根据pcap的手册页)如下:
pcap_t *pcap_open_live(char *device, int snaplen, int promisc, int to_ms, char *ebuf)
其第一个参数是我们在上一节中指定的设备,snaplen是整形的,它定义了将被pcap捕获的最大字节数。当promisc设为true时将置指定接口为混杂模式(然而,当它置为false时接口仍处于混杂模式的特殊情况也是有可能的)。to_ms是读取时的超时值,单位是毫秒(如果为0则一直嗅探直到错误发生,为-1则不确定)。最后,ebuf是一个我们可以存入任何错误信息的字符串(就像上面的errbuf)。此函数返回其会话句柄。

举个例子,考察以下代码片断:
#include <pcap.h>
...
pcap_t *handle;
handle = pcap_open_live(somedev, BUFSIZ, 1, 0, errbuf);

这个代码片断打开字符串somedev的设备,告诉它读取被BUFSIZ指定的字节数(BUFSIZ在pcap.h里定义)。我们告诉它将设备置为混杂模式,一直嗅探到错误发生,如果有了错误,把它存放在字符串errbuf中。

混杂模式与非混杂模式的区别:这两种方式区别很大。一般来说,非混杂模式的嗅探器中,主机仅嗅探那些跟它直接有关的通信,如发向它的,从它发出的,或经它路由的等都会被嗅探器捕获。而在混杂模式中则嗅探传输线路上的所有通信。在非交换式网络中,这将是整个网络的通信。这样做最明显的优点就是使更多的包被嗅探到,它们因你嗅探网络的原因或者对你有帮助,或者没有。但是,混杂模式是可被探测到的。一个主机可以通过高强度的测试判定另一台主机是否正在进行混杂模式的嗅探。其次,它仅在非交换式的网络环境中有效工作(如集线器,或者交换中的ARP层面)。再次,在高负荷的网络中,主机的系统资源将消耗的非常严重。

过滤通信
通常,我们的嗅探器仅对某特定的通信感兴趣。例如,有时我们想嗅探到端口23(telnet)的包以获得密码;或者我们想截获一个正通过端口21(FTP)传送的文件;可能我们仅想要得到DNS的通信(端口53,UDP)。无论哪种情况,我们都很少盲目的嗅探整个网络的通信。下面讨论pcap_compile()与pcap_setfilter()。
这个过程非常简单。当我们已经调用了pcap_open_live()从而建立了一个嗅探会话之后就可以应用我们自己的过滤器了。为什么要用我们自己的过滤器呢?有两个原因。第一,pcap的过滤器太强大了,因为它直接使用BPF过滤器,我们通过使用BPF驱动直接过滤跳过了很多的关节。第二,这样做要容易的多。

在使用我们自己的过滤器前必须编译它。过滤表达式被保存在一个字符串中(字符数组)。其句法在tcpdump的手册页中被证明非常好。我建议你亲自阅读它。但是我们将使用简单的测试表达式,这样你可能很容易理解我的例子。

我们调用pcap_compile()来编译它,其原型是这样定义的:
int pcap_compile(pcap_t *p, struct bpf_program *fp, char *str, int optimize, bpf_u_int32 netmask)
第一个参数是会话句柄(pcap_t *handle在前一节的示例中)。接下来的是我们存储被编译的过滤器版本的地址的引用。再接下来的则是表达式本身,存储在规定的字符串格式里。再下边是一个定义表达式是否被优化的整形量(0为false,1为true,标准规定)。最后,我们必须指定应用此过滤器的网络掩码。函数返回-1为失败,其他的任何值都表明是成功的。

表达式被编译之后就可以使用了。现在进入pcap_setfilter()。仿照我们介绍pcap的格式,先来看一看pcap_setfilter()的原型:

int pcap_setfilter(pcap_t *p, struct bpf_program *fp)

这非常直观,第一个参数是会话句柄,第二个参数是被编译表达式版本的引用(可推测出它与pcap_compile()的第二个参数相同)。
下面的代码示例可能能使你更好的理解:
#include <pcap.h>
...
pcap_t *handle; /* 会话的句柄 */
char dev[] = "rl0"; /* 执行嗅探的设备 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误 信息的字符串 */
struct bpf_program filter; /*已经编译好的过滤表达式*/
char filter_app[] = "port 23"; /* 过滤表达式*/
bpf_u_int32 mask; /* 执行嗅探的设备的网络掩码 */
bpf_u_int32 net; /* 执行嗅探的设备的IP地址 */
pcap_lookupnet(dev, &net, &mask, errbuf);
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);

这个程序使嗅探器嗅探经由端口23的所有通信,使用混杂模式,设备是rl0。

你可能注意到前面的示例包含一个我们还没提到的函数:pcap_lookupnet(),向这个函数提供设备接口名,它将返回其IP和网络掩码,这是很基本的,因为我们需要知道网络掩码以便应用过滤器。此函数在此文最后的miscellaneous一节里还有描述。

据我的经验,这个过滤器在所有的操作系统下都不会工作。在我的测试环境里,我发现OpenBSD 2.9默认内核支持这种过滤器,但FreeBSD 4.3默认内核则不支持。你的情况可能会有变化。

实际的嗅探

到此为止,我们已经学习了如何定义一个设备,让它准备嗅探,还有应用过滤器使我们嗅谈到什么或者不嗅探到什么。现在到了真正去捕获一些数据包的时候了。有两种手段捕获包。我们可以一次只捕获一个包,也可以进入一个循环,等捕获到多个包再进行处理。我们将先看看怎样去捕获单个包,然后再看看使用循环的方法。为此,我们使用函数pcap_next()。
Pcap_next()的原型及其简单:
u_char *pcap_next(pcap_t *p, struct pcap_pkthdr *h)
第一个参数是会话句柄,第二个参数是指向一个包括了当前数据包总体信息(被捕获时的时间,包的长度,其被指定的部分长度)的结构体的指针(在这里只有一个片断,只作为一个示例)。Pcap_next()返回一个u_char指针给被这个结构体描述的包。我们将稍后讨论这种实际读取包本身的手段。

这里有一个演示怎样使用pcap_next()来嗅探一个包的例子:
#include <pcap.h>
#include <stdio.h>
int main()
{
pcap_t *handle; /* 会话句柄 */
char *dev; /* 执行嗅探的设备 */
char errbuf[PCAP_ERRBUF_SIZE]; /* 存储错误信息的字符串 */
struct bpf_program filter; /* 已经编译好的过滤器 */
char filter_app[] = "port 23"; /* 过滤表达式 */
bpf_u_int32 mask; /* 所在网络的掩码 */
bpf_u_int32 net; /* 主机的IP地址 */
struct pcap_pkthdr header; /* 由pcap.h定义 */
const u_char *packet; /* 实际的包 */
/* Define the device */
dev = pcap_lookupdev(errbuf);
/* 探查设备属性 */
pcap_lookupnet(dev, &net, &mask, errbuf);
/* 以混杂模式打开会话 */
handle = pcap_open_live(dev, BUFSIZ, 1, 0, errbuf);
/* 编译并应用过滤器 */
pcap_compile(handle, &filter, filter_app, 0, net);
pcap_setfilter(handle, &filter);
/* 截获一个包 */
packet = pcap_next(handle, &header);
/* 打印它的长度 */
printf("Jacked a packet with length of [%d]\n", header.len);
/* 关闭会话 */
pcap_close(handle);
return(0);
}
这个程序嗅探被pcap_lookupdev()返回的设备并将它置为混杂模式。它发现第一个包经过端口23(telnet)并且告诉用户此包的大小(以字节为单位)。这个程序又包含了一个新的调用pcap_close(),我们将在后面讨论(尽管它的名字就足够证明它自己的作用)。
我们可以使用的另一种手段则要复杂的多,并且可能也更为有用。很少有(如果有的话)嗅探器真正的使用pcap_next()。通常,它们使用pcap_loop()或者pcap_dispatch()(它就是用了pcap_loop())。为了理解这两个函数的用法,你必须理解回调函数的思想。

回调函数并不是什么新东西,它在许多API里面非常普遍。回调函数的概念极其简单。设想我有一个程序正等待某种排序的事件。为了达到这个例子的目的,让我们假象我的程序想让用户在键盘上按下一个键,每当他们按下了一个键,我就想调用一个作相应处理的函数。我所用的函数就是一个回调函数。用户每按一个键一次,我的程序就调用回调函数一次。回调函数在应用在pcap里,取代当用户按下键时被调用的函数的是当pcap嗅探到一个数据包时所调用的函数。可以定义它们的回调函数的两个函数就是pcap_loop()和pcap_dispatch()。此二者在它们的回调函数的使用上非常的相似。它们都是每当捕获到一个符合我们过滤器的包时调用器回调函数(当然是存在一个过滤器时,如果不存在则所有被嗅探到的包都被送到会调函数处理)。

Pcap_loop()的原型如下:

int pcap_loop(pcap_t *p, int cnt, pcap_handler callback, u_char *user)

第一个参数是会话句柄,接下来是一个整型,它告诉pcap_loop()在返回前应捕获多少个数据包(若为负值则表示应该一直工作直至错误发生)。第三个参数是回调函数的名称(正像其标识符所指,无括号)。最后一个参数在有些应用里有用,但更多时候则置为NULL。假设我们有我们自己的想送往回调函数的参数,另外还有pcap_loop()发送的参数,这就需要用到它。很明显,必须是一个u_char类型的指针以确保结果正确;正像我们稍后见到的,pcap使用了很有意思的方法以u_char指针的形势传递信息。在我们展示了一个pcap是怎样做的例子之后就很容易去做了。若是还不行就参考你的本地的C引用文本,作为一个指针的解释那就超出了本文的范围。 Pcap_dispatch()的用法几乎相同。 唯一不同的是它们如何处理超时(还记得在调用pcap_open_live()时怎样设置超时吗?这就是它起作用的地方)。Pcap_loop()忽略超时而pcap_dispatch()则不。关于它们之间区别的更深入的讨论请参见pcap的手册页。

在提供使用pcap_loop()的示例之前,我们必须检查我们的回调函数的格式。我们不能武断的定义回调函数的原型,否则pcap_loop()将会不知道如何去使用它。因此我们使用这样的格式作为我们的回调函数的原型:
void got_packet(u_char *args, const struct pcap_pkthdr *header, const u_char *packet);
让我们更细致的考察它。首先,你会注意到该函数返回void类型,这是符合逻辑的,因为pcap_loop()不知道如何去处理一个回调返回值。第一个参数相应于pcap_loop()的最后一个参数。每当回调函数被调用时,无论最后一个参数传给pcap_loop()什么值,这个值都会传给我们回调函数的第一个参数。第二个参数是pcap头文件定义的,它包括数据包被嗅探的时间、大小等信息。结构体pcap_pkhdr在pcap.h中定义如下:
struct pcap_pkthdr {
struct timeval ts; /* 时间戳 */
bpf_u_int32 caplen; /* 已捕获部分的长度 */
bpf_u_int32 len; /* 该包的脱机长度 */
};
这些量都相当明了。最后一个参数在它们中是最有意思的,也最让pcap程序新手感到迷惑。这又是一个u_char指针,它包含了被pcap_loop()嗅探到的所有包。

但是你怎样使用这个我们在原型里称为packet的变量呢?一个数据包包含许多属性,因此你可以想象它不只是一个字符串,而实质上是一个结构体的集合(比如,一个TCP/IP包会有一个以太网的头部,一个IP头部,一个TCP头部,还有此包的有效载荷)。这个u_char就是这些结构体的串联版本。为了使用它,我们必须作一些有趣的匹配工作。

首先,在匹配它们之前必须定义这些实际的结构体。下面就是我用来描述一个通过以太网的TCP/IP包的结构体的定义。我使用的所有这些定义都是直接从POSIX库中提取的。通常,我只简单的使用那些库中的定义即可,但据我的经验不同平台的库之间有轻微的差别,这使得它实现起来变得混乱。因此,为达到示例的目的,我就避免那些混乱而简单的复制这些有关的结构体。所有这些都能在你的本地unix系统中的include/netinet中找到。下面就是这些结构体:
/* 以太网帧头部 */
struct sniff_ethernet {
u_char ether_dhost[ETHER_ADDR_LEN]; /* 目的主机的地址 */
u_char ether_shost[ETHER_ADDR_LEN]; /* 源主机的地址 */
u_short ether_type; /* IP? ARP? RARP? etc */
};
/* IP数据包的头部 */
struct sniff_ip {
#if BYTE_ORDER == LITTLE_ENDIAN
u_int ip_hl:4, /* 头部长度 */
ip_v:4; /* 版本号 */
#if BYTE_ORDER == BIG_ENDIAN
u_int ip_v:4, /* 版本号 */
ip_hl:4; /* 头部长度 */
#endif
#endif /* not _IP_VHL */
u_char ip_tos; /* 服务的类型 */
u_short ip_len; /* 总长度 */
u_short ip_id; /*包标志号 */
u_short ip_off; /* 碎片偏移 */
#define IP_RF 0x8000 /* 保留的碎片标志 */
#define IP_DF 0x4000 /* dont fragment flag */
#define IP_MF 0x2000 /* 多碎片标志*/
#define IP_OFFMASK 0x1fff /*分段位 */
u_char ip_ttl; /* 数据包的生存时间 */
u_char ip_p; /* 所使用的协议 */
u_short ip_sum; /* 校验和 */
struct in_addr ip_src,ip_dst; /* 源地址、目的地址*/
};
/* TCP 数据包的头部 */
struct sniff_tcp {
u_short th_sport; /* 源端口 */
u_short th_dport; /* 目的端口 */
tcp_seq th_seq; /* 包序号 */
tcp_seq th_ack; /* 确认序号 */
#if BYTE_ORDER == LITTLE_ENDIAN
u_int th_x2:4, /* 还没有用到 */
th_off:4; /* 数据偏移 */
#endif
#if BYTE_ORDER == BIG_ENDIAN
u_int th_off:4, /* 数据偏移*/
th_x2:4; /*还没有用到 */
#endif
u_char th_flags;
#define TH_FIN 0x01
#define TH_SYN 0x02
#define TH_RST 0x04
#define TH_PUSH 0x08
#define TH_ACK 0x10
#define TH_URG 0x20
#define TH_ECE 0x40
#define TH_CWR 0x80
#define TH_FLAGS (TH_FIN|TH_SYN|TH_RST|TH_ACK|TH_URG|TH_ECE|TH_CWR)
u_short th_win; /* TCP滑动窗口 */
u_short th_sum; /* 头部校验和 */
u_short th_urp; /* 紧急服务位 */
};

注:在Slackware Linux 8(内核版本2.2.19)上我发现使用以上结构体的代码将不能通过编译。后来证明问题在于include/fearures.h,它只实现了一个POSIX接口,除非定义BSD_SOURCE。如果它没有被定义,我就只能使用一个不同的结构体去定义TCP头部。使它们工作在FreeBSD或OpenBSD系统上的更为通用的解决方法如下:
#define _BSD_SOURCE 1
事先要包含你自己的所有头文件。这将确保正常使用BSD风格的API。如果不想这样做,那你可以改变TCP头结构(点此链接即可,内含注释)。

那么所有这些与pcap还有神秘的u_char是怎么关联的呢?看,幸运的是pcap嗅探数据包时正是使用的这些结构。接下来,它简单的创建一个u_char字符串并且将这些结构体填入。那么我们怎样才能区分它们呢?准备好见证指针最实用的好处之一吧(在此,我可要刺激刺激那些坚持说指针无用的C程序新手了)。

我们再一次假定要对以太网上的TCP/IP包进行处理。同样的手段可以应用于任何数据包,唯一的区别是你实际所使用的结构体的类型。让我们从声明分解u_char包的变量开始:
const struct sniff_ethernet *ethernet; /* 以太网帧头部*/
const struct sniff_ip *ip; /* IP包头部 */
const struct sniff_tcp *tcp; /* TCP包头部 */
const char *payload; /* 数据包的有效载荷*/
/*为了让它的可读性好,我们计算每个结构体中的变量大小*/
int size_ethernet = sizeof(struct sniff_ethernet);
int size_ip = sizeof(struct sniff_ip);
int size_tcp = sizeof(struct sniff_tcp);
现在我们开始让人感到有些神秘的匹配:
ethernet = (struct sniff_ethernet*)(packet);
ip = (struct sniff_ip*)(packet + size_ethernet);
tcp = (struct sniff_tcp*)(packet + size_ethernet + size_ip);
payload = (u_char *)(packet + size_ethernet + size_ip + size_tcp);

此处如何工作?考虑u_char在内存中的层次。基本的,当pcap将这些结构体填入u_char的时候是将这些数据存入一个字符串中,那个字符串将被送入我们的会调函数中。反向转换是这样的,不考虑这些结构体制中的值,它们的大小将是一致的。例如在我的平台上,一个sniff_ethernet结构体的大小是14字节。一个sniff_ip结构体是20字节,一个sniff_tcp结构体也是20字节。 u_char指针正是包含了内存地址的一个变量,这也是指针的实质,它指向内存的一个区域。简单而言,我们说指针指向的地址为x,如果三个结构体恰好线性排列,第一个(sniff_ethernet)被装载到内存地址的x处则我们很容易的发现其他结构体的地址,让我们以表格显示之:

Variable Location (in bytes)
sniff_ethernet X
sniff_ip X + 14
sniff_tcp X + 14 + 20
payload X + 14 + 20 + 20

结构体sniff_ethernet正好在x处,紧接着它的sniff_ip则位于x加上它本身占用的空间(此例为14字节),依此类推可得全部地址。

注意:你没有假定你的变量也是同样大小是很重要的。你应该总是使用sizeof()来确保尺寸的正确。这是因为这些结构体中的每个成员在不同平台下可以有不同的尺寸。

到现在,我们已经知道了怎样设置回调函数,调用它,弄清被嗅探到的数据包的属性。你可能正期待着写出一个可用的包嗅探器。因为代码的长度关系,我不想列在这篇文章里。你可以点击这里下载并测试它。

结束语
到此为止,你应该可以写出一个基于pcap的包嗅探器了。你已经学习了基本的概念:打开一个pcap会话,有关它的全体属性,嗅探数据包,使用过滤器,使用回调函数,等等。现在是进行数据包嗅探的时候了。
website: http://blog.csdn.net/plowboy/archive/2002/12/02/16398.aspx#345091


--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

2008年10月23日星期四

FC3下DirectFB的安装

1、下载下面两个包并解压:

tar -xzf DirectFB-1.0.0.tar.gz 
tar -xzf DirectFB-examples-1.0.0.tar.gz

2、安装DirectFB-1.0.0

cd /home/DirectFB-1.0.0
./configure
make
make install

3、安装DirectFB-examples-1.0.0

a).vi /boot/grub/grub.conf在kernel后面加上vga=0x0317把frambuffer启动,如:

#****************start*************#
title Fedora Core (2.6.9-1.667)
root (hd0,0)
kernel /vmlinuz-2.6.9-1.667 ro root=LABEL=/ rhgb quiet vga=0x0317
initrd /initrd-2.6.9-1.667.img
#****************stop**************#

b).updatedb #更新数据库

c).locate directfb.pc

找到directfb.pc的路径,例如我的是这样的:

/usr/local/lib/pkgconfig/directfb.pc
/home/DirectFB-1.0.0/directfb.pc
/home/DirectFB-1.0.0/directfb.pc.in

d).接着设置环境,将第3步得到的pkgconfig库目录加入环境之中:

export PKG_CONFIG_PATH=/usr/local/lib/pkgconfig
(这样只能在当前终端可以运行,关机后还得自已运行些命令,才能运行dfb程序,如要永久性加入可这样:
如你用的是root用户,可以vi /root/.bash_profile中加入:
PKG_CONFIG_PATH=/usr/local/lib/pkgconfig:/usr/lib/pkgconfig )

e).make

f).make install

g).将指定directfb的库目录:
    vi /etc/ld.so.conf在最后一行加入:/usr/local/lib,保存退出。
    ldconfig

4、测试例子,cd src/ 下,运行:

./df_andi



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

FC3下五笔和eva的安装

 五笔安装

1、解包

tar -xjf fcitx-3.0.2.tar.bz2
cd fcitx-3.0.2

2、安装

./configure
make
make install

3、修改配置文件

mv /etc/X11/xinit/xinput.d/zh_CN /etc/X11/xinit/xinput.d/fcitx
将fcix的内容改为下面的内容:
#******************fcix start**********#
XIM=fcitx
XIM_PROGRAM=fcitx
GTK_IM_MODULE=fcitx
gnome-im-settings-daemon >/dev/null
#******************fcix stop***********#

eva安装

1、解包

 tar -xjf eva.bz2
 cd eva/

2、安装

./configure --prefix=`kde -config--prefix` (注意这不是单引号,是与 ~ 共用一键的 `)
make clean
make
make install

3、运行

在终端输入:eva

--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

linux下VNC服务的安装和使用(windows,linux)

#***********************************#
系统:FC3

kernel: 2.6.9-1.667smp
VNC服务器: vnc-4.0-8
VNC客户端:windowns下:realVNC,  linux下:vnc-4.0-8
#***********************************#

步骤:
1。安装VNC服务器:

#tar zxvf vnc-3.3.6-x86_linux.tar.gz
#cd vnc-3.3.6
# ./vncinstall /usr/local/bin
# ./vncinstall /usr/local/bin /usr/local/man
#mkdir -p /usr/local/vnc/classes
#cp classes/* /usr/local/vnc/classes

2。运行VNC服务器

a).#vncserver
 passwd: //如果这个帐号是第一次运行vncserver,则会要求你输入密码,这个密码就是连接你的VNC密码。
 verify:
然后会生成一个图形端口编号,记住。如:
#*****************stop***********************#
New 'localhost.localdomain:1 (root)' desktop is localhost.localdomain:1

Starting applications specified in /root/.vnc/xstartup
Log file is /root/.vnc/localhost.localdomain:1.log
#*****************stop***********************#
像其中的"1"就是图形编号.

b).#vi $HOME/.vnc/xstartup 删除里面的所有内容后输入下面的代码。

unset SESSION_MANAGER
exec /etc/X11/xinit/xinitrc

保存退出.

备注:
如果不改这个文件,进去的只是灰色界面,什么都没有。

c). 重新起动vncserver

 /etc/init.d/vncserver restart

 3。VNC客户端:
   a).windows下登陆
      在WINDOWS下安装RealVNC后,运行RealVNC,输入VNC服务器的IP和图形编号,如:
      192.168.0.222:1
      然后输入密码(上面你启动vncserver时的密码)即可登陆X界面。
   b).linux下登陆
      在终端输入:{ vncview 服务器IP:图形编号 },输入密码就可以进去了.
      如: vncview 192.168.0.222:1
-----------------------------END-------------------------


--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

linux系统怎样对grub进行md5加密和明文加密


linux系统怎样对grub进行md5加密和明文加密。步骤如下:
一、md5加密:

default=0
timeout=5
splashimage=(hd0,5)/grub/splash.xpm.gz
hiddenmenu

1、md5密码由系统命令grub-md5-crypt生成,在终端敲入此命令后,根据提示输入你的密码:如:$1$/kFox1$3noAc7RZjdUTVjdKaT2r40

2、然后在/boot/grub/grub.conf下加入下面的粗体部分,注意不要写错了,不然你边系统的单用户模式都进不去了。ubuntu下面是/boot/grub/menu.lst文件。


#****************grub.conf部分 start*************************#
password --md5 $1$/kFox1$3noAc7RZjdUTVjdKaT2r40
title Welcome To My Fedora Core (2.6.9-1.667smp)
        root (hd0,5)
        kernel /vmlinuz-2.6.9-1.667smp ro root=LABEL=/1 rhgb quiet vga=0x0317
        initrd /initrd-2.6.9-1.667smp.img
        lock
title Fedora Core-up (2.6.9-1.667)
        root (hd0,5)
        kernel /vmlinuz-2.6.9-1.667 ro root=LABEL=/1 rhgb quiet
        initrd /initrd-2.6.9-1.667.img
title Welcome To My Windows XP
        rootnoverify (hd0,0)
        chainloader +1
#****************grub.conf部分 stop*************************#


3、保存退出,重启就可以看见你的grub已经加密了,这样的话,你的linux就有双重密码保护了。


二、明文加密:

在/boot/grub/grub.conf里加入粗体部分就可以啦,如密码是:123456。不过这样密码容易被看见,不够安全哦。

 #****************grub.conf部分 start*************************#
password=123456
title Welcome To My Fedora Core (2.6.9-1.667smp)
        root (hd0,5)
        kernel /vmlinuz-2.6.9-1.667smp ro root=LABEL=/1 rhgb quiet vga=0x0317
        initrd /initrd-2.6.9-1.667smp.img
        lock
title Fedora Core-up (2.6.9-1.667)
        root (hd0,5)
        kernel /vmlinuz-2.6.9-1.667 ro root=LABEL=/1 rhgb quiet
        initrd /initrd-2.6.9-1.667.img
title Welcome To My Windows XP
        rootnoverify (hd0,0)
        chainloader +1
#****************grub.conf部分 stop*************************#



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

用autoconf,automake自动生成Makefile

使用 automake之前系统需要的安装的包:
GNU automake
GNU autoconf
GNU m4
perl
GNU libtool(如果你需要产生 shared library)
gcc
Automake 依赖于: Autoconf, Bash, Coreutils, Diffutils, Grep, M4, Make, Perl, Sed.

注:下面讲的是将所有文件放在一个文件夹里,而一些大项目往往是将源文件放在一个src/目录下,头文件放在一个include/ 目录下的,这样只要将下面的稍做修改就可以啦,我已经试过了,其目录结构是:

test/--------------src/
                  |__________include/
                  |__________lib/
                  |__________other  files(ex: configure,aclocal.m4,Makefile,Makefile.am,Makefile.in )

1、首先写好一个.c文件,如helloworld.c:(下面执行的命令都在此目录下)
-------------start-------------------
#include <stdio.h>

int main(int argc,char **argv)
{
printf("hello,xiongfeng!\n");
return 0;
}
--------------end--------------------

2、生成configure
  我们使用autoscan命令来帮助我们根据目录下的源代码生成一个configure.in的模板文件。
  命令:
   # autoscan
    # ls

ls后显示的文件:
    configure.scan helloworld.c

执行后在hellowrold目录下会生成一个文件:configure.scan,我们可以拿它作为configure.in的蓝本。
configure.scan文件改名为configure.in并编辑:
    #mv configure.scan configure.in
    #vi configure.in
configure.in修改成下面的内容并保存:
--------configure.in start-----------------

#                                               -*- Autoconf -*-
# Process this file with autoconf to produce a configure script.

#AC_PREREQ(2.60)
AC_INIT(helloworld.c)   #这个宏用来检查源代码所在的路径
AM_INIT_AUTOMAKE(helloworld,1.0)  #这个宏是必须的,描述了我们将要生成的软件包的名字及其版本号
#AC_CONFIG_SRCDIR([helloworld.c])
#AC_CONFIG_HEADER([config.h])

# Checks for programs.
AC_PROG_CC   #这个宏将检查系统所用的C编译器。

# Checks for libraries.

# Checks for header files.

# Checks for typedefs, structures, and compiler characteristics.

# Checks for library functions.

AC_OUTPUT(Makefile)  #这个宏是我们要输出的Makefile的名字

--------configure.in stop-------------------
然后执行命令aclocal和autoconf,分别会产生aclocal.m4及configure两个文件:

# aclocal
#ls

ls后显示的文件:
aclocal.m4 configure.in helloworld.c

# autoconf
# ls

ls后显示的文件:
aclocal.m4 autom4te.cache configure configure.in helloworld.c

        大家可以看到configure.in内容是一些宏定义,这些宏经autoconf处理后会变成检查系统特性、环境变
量、软件必须的参数的shell脚本。autoconf 是用来生成自动配置软件源代码脚本(configure)的工
具。configure脚本能独立于autoconf运行,且在运行的过程中,不需要用户的干预。要生成configure
文件,你必须告诉autoconf如何找到你所用的宏。方式是使用aclocal程序来生成你的aclocal.m4。
aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal是一个perl 脚本程序,它
的定义是:"aclocal - create aclocal.m4 by scanning configure.ac"。autoconf从
configure.in这个列举编译软件时所需要各种参数的模板文件中创建configure。autoconf需要GNU m4宏
处理器来处理aclocal.m4,生成configure脚本。m4是一个宏处理器。将输入拷贝到输出,同时将宏展开。
宏可以是内嵌的,也可以是用户定义的。除了可以展开宏,m4还有一些内建的函数,用来引用文件,执行命令,
整数运算,文本操作,循环等。m4既可以作为编译器的前端,也可以单独作为一个宏处理器。

3、编辑Makefile.am

# vi Makefile.am
内容如下:
----------Makefile.am start------------

AUTOMAKE_OPTIONS=foreign  #它会检查目录下是否存在标准GNU软件包中应具备的各种文件
bin_PROGRAMS=helloworld   #指定我们所要产生的可执行文件的文件名
helloworld_SOURCES=helloworld.c #指定产生"helloworld"时所需要的源代码和头文件

----------Makefile.am stop-------------
   automake会根据你写的Makefile.am来自动生成Makefile.in。Makefile.am中定义的宏和目标,会指导
automake生成指定的代码。例如,宏bin_PROGRAMS将导致编译和连接的目标被生成。

4、运行automake

# automake --add-missing  #让automake加入一个标准的软件包所必须的一些文件
运行后显示:
-----------------start-------------------------
configure.in: installing `./install-sh'
configure.in: installing `./mkinstalldirs'
configure.in: installing `./missing'
Makefile.am: installing `./depcomp'
-----------------stop--------------------------
  automake会根据Makefile.am文件产生一些文件,包含最重要的Makefile.in。

5、执行configure生成Makefile

# ./configure

运行后显示:
------------------------start---------------------------------
checking for a BSD-compatible install... /usr/bin/install -c
checking whether build environment is sane... yes
checking for gawk... gawk
checking whether make sets $(MAKE)... yes
checking for gcc... gcc
checking for C compiler default output... a.out
checking whether the C compiler works... yes
checking whether we are cross compiling... no
checking for suffix of executables...
checking for suffix of object files... o
checking whether we are using the GNU C compiler... yes
checking whether gcc accepts -g... yes
checking for gcc option to accept ANSI C... none needed
checking for style of include used by make... GNU
checking dependency style of gcc... gcc3
configure: creating ./config.status
config.status: creating Makefile
config.status: executing depfiles commands
------------------------stop-----------------------------------
# ls -l Makefile
-rw-r--r-- 1 root root 16687 Apr 21 18:37 Makefile
这样,你的Makefile已经产生出来了。

6、使用Makefile编译代码

# make

运行后显示:
------------------------start-----------------------------------
if gcc -DPACKAGE_NAME=\"\" -DPACKAGE_TARNAME=\"\" -DPACKAGE_VERSION=\"\"
-DPACKAGE_STRING=\"\" -DPACKAGE_BUGREPORT=\"\" -DPACKAGE=\"hello\" -DVER
SION=\"1.0\" -I. -I.     -g -O2 -MT helloworld.o -MD -MP -MF ".deps/hell
oworld.Tpo" -c -o helloworld.o helloworld.c;then mv -f ".deps/helloworl
d.Tpo" ".deps/helloworld.Po"; else rm -f ".deps/helloworld.Tpo"; exit 1;
fi gcc  -g -O2   -o helloworld  helloworld.o 
------------------------stop-------------------------------------

7、运行可执行文件helloworld

#./helloworld

运行后显示:
------------------------start-----------------------------------
hello,xiongfeng!   
------------------------stop-------------------------------------

深入浅出

  针对上面提到的各个命令,我们再做些详细的介绍。

  1、 autoscan

  autoscan是用来扫描源代码目录生成configure.scan文件的。autoscan可以用目录名做为参数,但如果你不使用参数的话,那么autoscan将认为使用的是当前目录。autoscan将扫描你所指定目录中的源文件,并创建configure.scan文件。

  2、 configure.scan

  configure.scan包含了系统配置的基本选项,里面都是一些宏定义。我们需要将它改名为configure.in

  3、 aclocal

  aclocal是一个perl 脚本程序。aclocal根据configure.in文件的内容,自动生成aclocal.m4文件。aclocal的定义是:"aclocal - create aclocal.m4 by scanning configure.ac"。

  4、 autoconf

  autoconf是用来产生configure文件的。configure是一个脚本,它能设置源程序来适应各种不同的操作系统平台,并且根据不同的系统来产生合适的Makefile,从而可以使你的源代码能在不同的操作系统平台上被编译出来。

  configure.in文件的内容是一些宏,这些宏经过autoconf 处理后会变成检查系统特性、环境变量、软件必须的参数的shell脚本。configure.in文件中的宏的顺序并没有规定,但是你必须在所有宏的最前面和最后面分别加上AC_INIT宏和AC_OUTPUT宏。

  在configure.ini中:

  #号表示注释,这个宏后面的内容将被忽略。

  AC_INIT(FILE)

  这个宏用来检查源代码所在的路径。

AM_INIT_AUTOMAKE(PACKAGE, VERSION)

  这个宏是必须的,它描述了我们将要生成的软件包的名字及其版本号:PACKAGE是软件包的名字,VERSION是版本号。当你使用make dist命令时,它会给你生成一个类似helloworld-1.0.tar.gz的软件发行包,其中就有对应的软件包的名字和版本号。

AC_PROG_CC

  这个宏将检查系统所用的C编译器。

AC_OUTPUT(FILE)

  这个宏是我们要输出的Makefile的名字。

  我们在使用automake时,实际上还需要用到其他的一些宏,但我们可以用aclocal 来帮我们自动产生。执行aclocal后我们会得到aclocal.m4文件。

  产生了configure.in和aclocal.m4 两个宏文件后,我们就可以使用autoconf来产生configure文件了。

  5、 Makefile.am

  Makefile.am是用来生成Makefile.in的,需要你手工书写。Makefile.am中定义了一些内容:

AUTOMAKE_OPTIONS

  这个是automake的选项。在执行automake时,它会检查目录下是否存在标准GNU软件包中应具备的各种文件,例如AUTHORS、ChangeLog、NEWS等文件。我们将其设置成foreign时,automake会改用一般软件包的标准来检查。

bin_PROGRAMS

  这个是指定我们所要产生的可执行文件的文件名。如果你要产生多个可执行文件,那么在各个名字间用空格隔开。

helloworld_SOURCES

  这个是指定产生"helloworld"时所需要的源代码。如果它用到了多个源文件,那么请使用空格符号将它们隔开。比如需要 helloworld.h,helloworld.c那么请写成helloworld_SOURCES= helloworld.h helloworld.c。

  如果你在bin_PROGRAMS定义了多个可执行文件,则对应每个可执行文件都要定义相对的filename_SOURCES。

  6、 automake

  我们使用automake --add-missing来产生Makefile.in。

  选项--add-missing的定义是"add missing standard files to package",它会让automake加入一个标准的软件包所必须的一些文件。

  我们用automake产生出来的Makefile.in文件是符合GNU Makefile惯例的,接下来我们只要执行configure这个shell 脚本就可以产生合适的 Makefile 文件了。

  7、 Makefile

  在符合GNU Makefiel惯例的Makefile中,包含了一些基本的预先定义的操作:

make

  根据Makefile编译源代码,连接,生成目标文件,可执行文件。

make clean

  清除上次的make命令所产生的object文件(后缀为".o"的文件)及可执行文件。

make install

  将编译成功的可执行文件安装到系统目录中,一般为/usr/local/bin目录。

make dist

  产生发布软件包文件(即distribution package)。这个命令将会将可执行文件及相关文件打包成一个tar.gz压缩的文件用来作为发布软件的软件包。

  它会在当前目录下生成一个名字类似"PACKAGE-VERSION.tar.gz"的文件。PACKAGE和VERSION,是我们在configure.in中定义的AM_INIT_AUTOMAKE(PACKAGE, VERSION)。

make distcheck

  生成发布软件包并对其进行测试检查,以确定发布包的正确性。这个操作将自动把压缩包文件解开,然后执行configure命令,并且执行make,来确认编译不出现错误,最后提示你软件包已经准备好,可以发布了。

===============================================
helloworld-1.0.tar.gz is ready for distribution
===============================================
make distclean

  类似make clean,但同时也将configure生成的文件全部删除掉,包括Makefile。

 结束语

  通过上面的介绍,你应该可以很容易地生成一个你自己的符合GNU惯例的Makefile文件及对应的项目文件。

  如果你想写出更复杂的且符合惯例的Makefile,你可以参考一些开放代码的项目中的configure.in和Makefile.am文件,比如:嵌入式数据库sqlite,单元测试cppunit。
GNU Makefile详细说明可到GNU官方网站上查阅: http://www.gnu.org/
Automake下载官方网站:ftp://ftp.gnu.org/gnu/automake/

--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

FC3中插入U盘时无法打开的解决方法

 在FC3中插入U盘的时候会出现桌面假死,或都打不开的情况的解决方法:
解决的办法是在挂载时,根据语言环境 locale 的值,使用挂载参数。如果是 UTF-8 那么就用
mount -o utf8,如果是 GB* 那么就用 mount -o iocharset=cp936。但是这样繁琐地手动挂载
就失掉了 FC3 的好处。正确的解决办法,是添加自己的配置文件:

vi /usr/share/hal/fdi/95userpolicy/storage-policy.fdi 文件加入下面的内容;
#*************************开始*************************
<?xml version="1.0" encoding="ISO-8859-1"?> <!-- -*- SGML -*- -->

<deviceinfo version="0.2">
<device>
<match key="@block.storage_device:storage.removable" bool="true">
<merge key="volume.policy.mount_option.fmask=111" type="bool">true</merge>
<merge key="volume.policy.mount_option.dmask=0" type="bool">true</merge>
<merge key="volume.policy.mount_option.users" type="bool">true</merge>
<merge key="volume.policy.mount_option.utf8" type="bool">true</merge>
</match>
<match key="@block.storage_device:storage.hotpluggable" bool="true">
<merge key="volume.policy.mount_option.fmask=111" type="bool">true</merge>
<merge key="volume.policy.mount_option.dmask=0" type="bool">true</merge>
<merge key="volume.policy.mount_option.users" type="bool">true</merge>
<merge key="volume.policy.mount_option.utf8" type="bool">true</merge>
</match>
</device>
</deviceinfo>
#*************************结束*************************

原理正如上面所说,hal 在收到优盘插入的通知时,将读取这些 storage policy 配置文件。
在改写 /etc/fstab 时,将用到配置文件中的数据,将这些 mount_option 全部加入 fstab 中去。


如何测试效果
测试效果的办法简单得很,但是不要急着插入优盘,而是应当先运行
代码:
service haldaemon restart

然后插入优盘。几秒钟之后,桌面上会出现优盘图标。看看你的 fstab,如果多了这样一行
代码:
/dev/sda1 /media/YUAN vfat pamconsole,utf8,users,dmask=0,fmask=111,noatime,sync,fscontext=system_u:object_r
                                            :removable_t,exec,noauto,managed 0 0
那么说明没什么问题。也许你的系统中 selinux 已经被关闭了,所以参数没有这么长。访问优盘,应该不会再出错了。

可能的问题
如果你的语言环境是 gb2312,那么要把上面的配置文件稍微修改一下,把两次出现的 utf8 修改为 iocharset=cp936
小心配置文件中的拼写错误
如果仍然会挂起或死机,那么可能 haldaemon 没有重启动,甚至可能被关闭了。运行
代码:
chkconfig haldaemon on
chkconfig messagebus on
service messagebus restart
service haldaemon restart



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

minigui 2.0.3的安装及其中遇到的一些错误解决方法

 minigui有两种版本:
MiniGUI  threads版,直接运行程序即可。
MiniGUI  lite版,则需要启动服务程序再运行程序,服务程序为mginit。该命令在目录mde-2.0.3/mginit/ 下面可以找到。
安装:
1、下载软件开发包:
   开发包名称为:
minigui-dev-2.0.3-linux.tar.gz:这是针对 Linux (i386)平台的 MiniGUI V2.0.3 开发包(MiniGUI-Processes 运行模 式)。关于安装和使用方法,请阅读该软件包中的 README 文件。
mde-2.0.3.tar.gz: MDE 是 MiniGUI 的综合演示程序。主要的演示程序有:虚拟控制台程序、控件演示程序、对话框演示程序、字体演示程序、GDI 接口演示程序、看图程序、记事本程序、绘图程序、扫雷游戏、推箱子游戏、俄罗斯方块游戏、合并同类项游戏等。
mg-samples-2.0.3.tar.gz: mg-samples 是《MiniGUI 编程指南》的配套示例程序包。
   下载地址:http://www.minigui.com/download/cindex.shtml
qvfb-1.0.tar.gz:由飞漫打包的 QVFB 程序,可在 Red Hat Linux 上直接编译。使用该程序可在 X Window 上运行和调试 MiniGUI 应用程序。
下载地址:http://www.minigui.com/download/cdevtool.shtml
2、将下载后的开发包解压缩:
  (1)$ tar  -zxvf   minigui-dev-2.0.3-linux.tar.gz
  (2)解压缩后的文件共两个:minigui-dev-2.0.3-linux.i386.rpm  readme
        rpm包为MiniGUI的安装程序。
        readme是MiniGUI的安装说明文件。
3、安装minigui:
   # rpm -ivh minigui-dev-2.0.3-linux.i386.rpm
4、确保已经将/usr/local/lib目录添加到/etc/ld.so.conf文件中,运行 ldconfig命令刷新系统的共享库搜索缓存:
   $ sudo ldconfig
5、修改minigui.cfg文件:
    # vi /usr/local/etc/MiniGUI.cfg ,把输入fbcon输出console都改为qvfb 然后保 存退出
6、安装mde-2.0.3.tar.gz
(1) $ tar  -zxvf  mde-2.0.3.tar.gz    并  cd  mde-2.0.3
(2)  . /configure
(3)  make
(4)  make install
具体看它的README
7、安装qvfb
(1) tar -xzf qvfb-1.0.tar.gz  并  cd  qvfb-1.0
(2) . /configure
(3) make
(4) make install

若检测不到3.0.3版本以上的qt库,请检查是否已经安装。为使qvfb适合以后的开发,可修改qvfb/main.cpp中的代码(line:39开始),将分辨率设为800x600:16位色

8、运行示例程序:

(1) tar -xzf  mg-samples-2.0.3.tar.gz    并  cd  mg-samples-2.0.3

(2) . /configure

(3) make

(4) cd  ./src
(5) 在终端运行:qvfb &
(6)  cd  mde-2.0.3/mginit   并运行 . /mginit  &    (安装的是minigui-lite版,所以需要运行mginit程序)
(7)运行其中任何一个可执行文件,如:. /helloworld
/***********如何顺利的话,那么你的minigui-lite版的环境就搭建完毕了**************/

下面是安装中常遇到的一些错误:
------------------------------------------------------------------------

安装后,没有生成/usr/local/etc/MiniGUI.cfg文件,应该如何解决呢?
安装后,执行./mginit,提示:
NEWGAL: No available video device.
NEWGAL: Does not find matched engine: qvfb.
...

答:
这个要先启动qvfb(在飞漫的网站上有下)
按照官方文档的说法,1.6.8和2.0是不需要minigui.cfg这个文件的,要改配置的话用export key=value设置环境变量即可 请看其中的readme文件!
-------------------------------------------------------------------------

mginit跑出来过一次,但现在运行它总是
Error in step 3: There is already an instance of 'mginit'!
怎么回事?

答:
只需要将/var/tmp目录下的2个minigui的文件删掉就可以了
-------------------------------------------------------------------------
main.o: In function `MiniGUIAppMain':/home/mde-2.0.3/notebook/main.c:1023:对'GetLayerInfo'未定义的 引用
:/home/mde-2.0.3/notebook/main.c:1024:对'JoinLayer'未定义的引用
collect2: ld 返回 1
make[2]: *** [notebook] 错误 1
make[2]: Leaving directory `/home/mde-2.0.3/notebook'
make[1]: *** [all-recursive] 错误 1
make[1]: Leaving directory `/home/mde-2.0.3/notebook'
make: *** [all-recursive] 错误 1

答:�载minigui的库包:rpm -e minigui-dev-2.0.3  然后再重新安装
----------------------------------------------------------------------------
运行./mginit时出现这样的错误:
LoadSharedResource: File exists
Error in step 8: Can not load shared resource!

答:
第一种方法:reboot
第二种方法:
先用 ipcs 看系统中存在的 IPC 对象,确定 MiniGUI 所使用的共享内存及信号量
对象的 ID 号。
然后用 ipcrm sem xxx 和 ipcrm shm xxx 命令删除这些对象即可。



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

一步一步学习C++:cout/cin/函数的重载/引用/内置函数/new/delete/作用域运算符

 一、cout/cin的应用:
源代码(cout_cin.cpp):
#include <iostream>
#include <iomanip>
using namespace std;

int main(int argc, char **argv)
{
    int i = 0;
    int a = 3;
    int x = 0;
    char b = 'b';
    long c = 34567;
    cout << "请输入一个整数:" << endl;
    cin >> x;
    cout << "该整数为:" << x << endl;

    /* 下面第一条和第三条语句功能一样,endl代表着'\n' */
    cout << "hello,xiongfeng!\n";
    cout << "hello,world!";
    cout << "hello,world!" << endl;

    cout << "a = " << a << "," << "b = " << b << "," << "c = " << c <<
        endl;
    cout << "a = " << a << "\n" << "b = " << b << "\n" << "c = " << c <<
        endl;
    /* setw(num)中num表示需留出几个空格,右对齐 */
    cout << "a = " << setw(10) << a << "\n" << "b = " << setw(10) << b <<
        "\n" << "c = " << setw(10) << c << endl;

    for (i = 0; i < 3; i++) {
        cout << "hello = " << i << endl;
    }
    return 0;
}
编译:g++  cout_cin.cpp _Wall
运行:. /a.out
结果:
请输入一个整数:
123
该整数为:123
hello,xiongfeng!
hello,world!hello,world!
a = 3,b = b,c = 34567
a = 3
b = b
c = 34567
a =          3
b =          b
c =      34567
hello = 0
hello = 1
hello = 2
二、函数的重载
源代码(overloading):
/*
 * 重载:允许一个运算符用于不同场合,有不同的含义。也可以说一物多用
 *C语言中在同一作用域中不能有同名的函数
 *C++允许在同一作用域中用同一函数名定义多个函数,这些函数的参数个
 *数和参数类型不同。这就是函数的重载。即一个函数名多用
 */
#include <stdio.h>
#include <iostream>
using namespace std;

int max(int a, int b, int c);
float max(float a, float b, float c);
char max(char a, char b, char c);
int main(int argc, char **argv)
{
    int max_int = 0;
    float max_float;
    char max_char;
    int a = 0, b = 0, c = 0;
    float d = 0, e = 0, f = 0;
    char g, h, i;

    cout << "请输入三个整数:" << endl;
    cin >> a >> b >> c;
    max_int = max(a, b, c);
    cout << "三个中最大数为:" << max_int << endl;

    cout << "请输入三个浮点数:" << endl;
    cin >> d >> e >> f;
    max_float = max(d, e, f);
    cout << "三个中最大数为:" << max_float << endl;

    cout << "请输入三个字符:" << endl;
    cin >> g >> h >> i;
    max_char = max(g, h, i);
    cout << "三个中最大数为:" << max_char << endl;
    return 0;
}

int max(int a, int b, int c = 6)
{
    if (b > a)
        a = b;
    if (c > a)
        a = c;
    return a;
}

float max(float a, float b, float c)
{
    if (b > a)
        a = b;
    if (c > a)
        a = c;
    return a;
}

char max(char a, char b, char c)
{
    if (b > a)
        a = b;
    if (c > a)
        a = c;
    return a;
}
编译:g++ overloading.cpp _Wall
运行:. /a.out
结果:
请输入三个整数:
1 2 3
三个中最大数为:3
请输入三个浮点数:
3.4 4.5 6.3
三个中最大数为:6.3
请输入三个字符:
s c d
三个中最大数为:s
三、变量的引用类型
源代码(reference_function.cpp):
#include <iostream>
using namespace std;
void swap(int &a, int &b);

int main(int argc, char **argv)
{
    int i = 3;
    int j = 4;
    cout << "i = " << i << "\n" << "j = " << j << endl;
    swap(i, j);
    cout << "i = " << i << "\n" << "j = " << j << endl;
    return 0;
}

void swap(int &a, int &b)       //引用做为函数参数
{
    int tmp = 0;
    tmp = a;
    a = b;
    b = tmp;
}
编译:g++  reference_function.cpp  -Wall
运行:. /a.out
结果:
i = 3
j = 4
i = 4
j = 3
四、内置函数
源代码(inline_function.cpp):
/*
 *  内置函数:在编译时将所调用函数的代码嵌入到主调函数中
 *  使用方法:在函数的首行的左端加一个关键字inline即可
 *  作用:使有些频繁调用的函数被调用的更快,加快程序的执行效率
 *        因为调用函数时需要一定的时间.
 */
#include <iostream>
using namespace std;
inline int func(void);

int main(int argc, char **argv)
{
    int a = 0;
    int i = 0;
    for (i = 0; i < 10000; i++) {
        a = func();             //编译时将func函数体的代码代替func()
    }
    cout << "a = " << a << endl;

    return 0;
}

inline int func(void)
{
    int i = 0;
    for (i = 0; i < 10; i++) {
        ;
        ;
        ;
    }
    return i;
}
编译:g++  inline_function.cpp  -Wall
运行:. /a.out
结果:
a = 10
五、C++动态分配内存(new/delete)
原代码(new_delete.cpp):
#include <iostream>
#include <string.h>
using namespace std;

struct message {
    char name[12];
    int num;
    char sex;
};

int main(int argc, char **argv)
{
    message *p;
    p = new message;
    if(p==NULL){
        cout << "内存分配失败!" << endl;
    }
    strcpy(p->name, "xiongfeng");
    p->num = 1000;
    p->sex = 'm';
    cout << "name:" << p->name << endl;
    cout << "num:" << p->num << endl;
    cout << "sex:" << p->sex << endl;
    delete p;
    return 0;
}
编译:g++ new_delete.cpp  -Wall
运行:. /a.out
结果:
name:xiongfeng
num:1000
sex:m
六、作用域运算符::
源代码(scope.cpp):
#include <iostream>
using namespace std;

/* 作用域运算符:: */
char a = 'b';
int main(int argc, char **argv)
{
    int a = 2;
/*
 * 在C/C++语言中,局部变量a会屏蔽全局变量a的值
 * 所以在下行输出局部变量a的值为2,但使用作用
 * 域运算符::后会输出全局变量a的值为'b'
 */
    cout << "a = " << a << endl;
    cout << "a = " <<::a << endl;
    return 0;
}
编译:g++  scope.cpp  -Wall
运行:. /a.out
结果:
a = 2
a = b
/*************************结束****************************/


--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

C/C++中堆和栈的区别(转载)

因为经典,所以拿来,并非本人所写!
堆和栈的区别
一、预备知识―程序的内存分配
一个由c/C++编译的程序占用的内存分为以下几个部分
1、栈区(stack)― 由编译器自动分配释放 ,存放函数的参数值,局部变量的值等。其操作方式类似于数据结构中的栈。
2、堆区(heap) ― 一般由程序员分配释放, 若程序员不释放,程序结束时可能由OS回收 。注意它与数据结构中的堆是两回事,分配方式倒是类似于链表,呵呵。
3、全局区(静态区)(static)―,全局变量和静态变量的存储是放在一块的,初始化的全局变量和静态变量在一块区域, 未初始化的全局变量和未初始化的静态变量在相邻的另一块区域。 - 程序结束后有系统释放
4、文字常量区―常量字符串就是放在这里的。 程序结束后由系统释放
5、程序代码区―存放函数体的二进制代码。
二、例子程序
这是一个前辈写的,非常详细
//main.cpp
int a = 0; 全局初始化区
char *p1; 全局未初始化区
main()
{
int b; 栈
char s[] = "abc"; 栈
char *p2; 栈
char *p3 = "123456"; 123456\0在常量区,p3在栈上。
static int c =0; 全局(静态)初始化区
p1 = (char *)malloc(10);
p2 = (char *)malloc(20);
分配得来得10和20字节的区域就在堆区。
strcpy(p1, "123456"); 123456\0放在常量区,编译器可能会将它与p3所指向的"123456"优化成一个地方。


二、堆和栈的理论知识
2.1申请方式
stack:
由系统自动分配。 例如,声明在函数中一个局部变量 int b; 系统自动在栈中为b开辟空间
heap:
需要程序员自己申请,并指明大小,在c中malloc函数
如p1 = (char *)malloc(10);
在C++中用new运算符
如p2 = (char *)malloc(10);
但是注意p1、p2本身是在栈中的。


2.2
申请后系统的响应
栈:只要栈的剩余空间大于所申请空间,系统将为程序提供内存,否则将报异常提示栈溢出。
堆:首先应该知道操作系统有一个记录空闲内存地址的链表,当系统收到程序的申请时,
会遍历该链表,寻找第一个空间大于所申请空间的堆结点,然后将该结点从空闲结点链表中删除,并将该结点的空间分配给程序,另外,对于大多数系统,会在这块内存空间中的首地址处记录本次分配的大小,这样,代码中的delete语句才能正确的释放本内存空间。另外,由于找到的堆结点的大小不一定正好等于申请的大小,系统会自动的将多余的那部分重新放入空闲链表中。

2.3申请大小的限制
栈:在Windows下,栈是向低地址扩展的数据结构,是一块连续的内存的区域。这句话的意思是栈顶的地址和栈的最大容量是系统预先规定好的,在WINDOWS下,栈的大小是2M(也有的说是1M,总之是一个编译时就确定的常数),如果申请的空间超过栈的剩余空间时,将提示overflow。因此,能从栈获得的空间较小。
堆:堆是向高地址扩展的数据结构,是不连续的内存区域。这是由于系统是用链表来存储的空闲内存地址的,自然是不连续的,而链表的遍历方向是由低地址向高地址。堆的大小受限于计算机系统中有效的虚拟内存。由此可见,堆获得的空间比较灵活,也比较大。


2.4申请效率的比较:
栈由系统自动分配,速度较快。但程序员是无法控制的。
堆是由new分配的内存,一般速度比较慢,而且容易产生内存碎片,不过用起来最方便.
另外,在WINDOWS下,最好的方式是用VirtualAlloc分配内存,他不是在堆,也不是在栈是直接在进程的地址空间中保留一快内存,虽然用起来最不方便。但是速度快,也最灵活。

2.5堆和栈中的存储内容
栈: 在函数调用时,第一个进栈的是主函数中后的下一条指令(函数调用语句的下一条可执行语句)的地址,然后是函数的各个参数,在大多数的C编译器中,参数是由右往左入栈的,然后是函数中的局部变量。注意静态变量是不入栈的。
当本次函数调用结束后,局部变量先出栈,然后是参数,最后栈顶指针指向最开始存的地址,也就是主函数中的下一条指令,程序由该点继续运行。
堆:一般是在堆的头部用一个字节存放堆的大小。堆中的具体内容有程序员安排。

2.6存取效率的比较

char s1[] = "aaaaaaaaaaaaaaa";
char *s2 = "bbbbbbbbbbbbbbbbb";
aaaaaaaaaaa是在运行时刻赋值的;
而bbbbbbbbbbb是在编译时就确定的;
但是,在以后的存取中,在栈上的数组比指针所指向的字符串(例如堆)快。
比如:
#include
void main()
{
char a = 1;
char c[] = "1234567890";
char *p ="1234567890";
a = c[1];
a = p[1];
return;
}
对应的汇编代码
10: a = c[1];
00401067 8A 4D F1 mov cl,byte ptr [ebp-0Fh]
0040106A 88 4D FC mov byte ptr [ebp-4],cl
11: a = p[1];
0040106D 8B 55 EC mov edx,dword ptr [ebp-14h]
00401070 8A 42 01 mov al,byte ptr [edx+1]
00401073 88 45 FC mov byte ptr [ebp-4],al
第一种在读取时直接就把字符串中的元素读到寄存器cl中,而第二种则要先把指针值读到edx中,在根据edx读取字符,显然慢了。


2.7小结:
堆和栈的区别可以用如下的比喻来看出:
使用栈就象我们去饭馆里吃饭,只管点菜(发出申请)、付钱、和吃(使用),吃饱了就走,不必理会切菜、洗菜等准备工作和洗碗、刷锅等扫尾工作,他的好处是快捷,但是自由度小。
使用堆就象是自己动手做喜欢吃的菜肴,比较麻烦,但是比较符合自己的口味,而且自由度大。


windows进程中的内存结构


在阅读本文之前,如果你连堆栈是什么多不知道的话,请先阅读文章后面的基础知识。

接触过编程的人都知道,高级语言都能通过变量名来访问内存中的数据。那么这些变量在内存中是如何存放的呢?程序又是如何使用这些变量的呢?下面就会对此进行深入的讨论。下文中的C语言代码如没有特别声明,默认都使用VC编译的release版。

首先,来了解一下 C 语言的变量是如何在内存分部的。C 语言有全局变量(Global)、本地变量(Local),静态变量(Static)、寄存器变量(Regeister)。每种变量都有不同的分配方式。先来看下面这段代码:

#include <stdio.h>

int g1=0, g2=0, g3=0;

int main()
{
static int s1=0, s2=0, s3=0;
int v1=0, v2=0, v3=0;

//打印出各个变量的内存地址

printf("0x%08x\n",&v1); //打印各本地变量的内存地址
printf("0x%08x\n",&v2);
printf("0x%08x\n\n",&v3);
printf("0x%08x\n",&g1); //打印各全局变量的内存地址
printf("0x%08x\n",&g2);
printf("0x%08x\n\n",&g3);
printf("0x%08x\n",&s1); //打印各静态变量的内存地址
printf("0x%08x\n",&s2);
printf("0x%08x\n\n",&s3);
return 0;
}

编译后的执行结果是:

0x0012ff78
0x0012ff7c
0x0012ff80

0x004068d0
0x004068d4
0x004068d8

0x004068dc
0x004068e0
0x004068e4

输出的结果就是变量的内存地址。其中v1,v2,v3是本地变量,g1,g2,g3是全局变量,s1,s2,s3是静态变量。你可以看到这些变量在内存是连续分布的,但是本地变量和全局变量分配的内存地址差了十万八千里,而全局变量和静态变量分配的内存是连续的。这是因为本地变量和全局/静态变量是分配在不同类型的内存区域中的结果。对于一个进程的内存空间而言,可以在逻辑上分成3个部份:代码区,静态数据区和动态数据区。动态数据区一般就是"堆栈"。"栈(stack)"和"堆(heap)"是两种不同的动态数据区,栈是一种线性结构,堆是一种链式结构。进程的每个线程都有私有的"栈",所以每个线程虽然代码一样,但本地变量的数据都是互不干扰。一个堆栈可以通过"基地址"和"栈顶"地址来描述。全局变量和静态变量分配在静态数据区,本地变量分配在动态数据区,即堆栈中。程序通过堆栈的基地址和偏移量来访问本地变量。


├―――――――┤低端内存区域
│ …… │
├―――――――┤
│ 动态数据区 │
├―――――――┤
│ …… │
├―――――――┤
│ 代码区 │
├―――――――┤
│ 静态数据区 │
├―――――――┤
│ …… │
├―――――――┤高端内存区域


堆栈是一个先进后出的数据结构,栈顶地址总是小于等于栈的基地址。我们可以先了解一下函数调用的过程,以便对堆栈在程序中的作用有更深入的了解。不同的语言有不同的函数调用规定,这些因素有参数的压入规则和堆栈的平衡。windows API的调用规则和ANSI C的函数调用规则是不一样的,前者由被调函数调整堆栈,后者由调用者调整堆栈。两者通过"__stdcall"和"__cdecl"前缀区分。先看下面这段代码:

#include <stdio.h>

void __stdcall func(int param1,int param2,int param3)
{
int var1=param1;
int var2=param2;
int var3=param3;
printf("0x%08x\n",?m1); //打印出各个变量的内存地址
printf("0x%08x\n",?m2);
printf("0x%08x\n\n",?m3);
printf("0x%08x\n",&var1);
printf("0x%08x\n",&var2);
printf("0x%08x\n\n",&var3);
return;
}

int main()
{
func(1,2,3);
return 0;
}

编译后的执行结果是:

0x0012ff78
0x0012ff7c
0x0012ff80

0x0012ff68
0x0012ff6c
0x0012ff70


├―――――――┤<―函数执行时的栈顶(ESP)、低端内存区域
│ …… │
├―――――――┤
│ var 1 │
├―――――――┤
│ var 2 │
├―――――――┤
│ var 3 │
├―――――――┤
│ RET │
├―――――――┤<―"__cdecl"函数返回后的栈顶(ESP)
│ parameter 1 │
├―――――――┤
│ parameter 2 │
├―――――――┤
│ parameter 3 │
├―――――――┤<―"__stdcall"函数返回后的栈顶(ESP)
│ …… │
├―――――――┤<―栈底(基地址 EBP)、高端内存区域


上图就是函数调用过程中堆栈的样子了。首先,三个参数以从又到左的次序压入堆栈,先压"param3",再压"param2",最后压入"param1";然后压入函数的返回地址(RET),接着跳转到函数地址接着执行(这里要补充一点,介绍UNIX下的缓冲溢出原理的文章中都提到在压入RET后,继续压入当前EBP,然后用当前ESP代替EBP。然而,有一篇介绍windows下函数调用的文章中说,在windows下的函数调用也有这一步骤,但根据我的实际调试,并未发现这一步,这还可以从param3和var1之间只有4字节的间隙这点看出来);第三步,将栈顶(ESP)减去一个数,为本地变量分配内存空间,上例中是减去12字节(ESP=ESP-3*4,每个int变量占用4个字节);接着就初始化本地变量的内存空间。由于"__stdcall"调用由被调函数调整堆栈,所以在函数返回前要恢复堆栈,先回收本地变量占用的内存(ESP=ESP+3*4),然后取出返回地址,填入EIP寄存器,回收先前压入参数占用的内存(ESP=ESP+3*4),继续执行调用者的代码。参见下列汇编代码:

;--------------func 函数的汇编代码-------------------

:00401000 83EC0C sub esp, 0000000C //创建本地变量的内存空间
:00401003 8B442410 mov eax, dword ptr [esp+10]
:00401007 8B4C2414 mov ecx, dword ptr [esp+14]
:0040100B 8B542418 mov edx, dword ptr [esp+18]
:0040100F 89442400 mov dword ptr [esp], eax
:00401013 8D442410 lea eax, dword ptr [esp+10]
:00401017 894C2404 mov dword ptr [esp+04], ecx

……………………(省略若干代码)

:00401075 83C43C add esp, 0000003C ;恢复堆栈,回收本地变量的内存空间
:00401078 C3 ret 000C ;函数返回,恢复参数占用的内存空间
;如果是"__cdecl"的话,这里是"ret",堆栈将由调用者恢复

;-------------------函数结束-------------------------


;--------------主程序调用func函数的代码--------------

:00401080 6A03 push 00000003 //压入参数param3
:00401082 6A02 push 00000002 //压入参数param2
:00401084 6A01 push 00000001 //压入参数param1
:00401086 E875FFFFFF call 00401000 //调用func函数
;如果是"__cdecl"的话,将在这里恢复堆栈,"add esp, 0000000C"

聪明的读者看到这里,差不多就明白缓冲溢出的原理了。先来看下面的代码:

#include <stdio.h>
#include <string.h>

void __stdcall func()
{
char lpBuff[8]="\0";
strcat(lpBuff,"AAAAAAAAAAA");
return;
}

int main()
{
func();
return 0;
}

编译后执行一下回怎么样?哈,""0x00414141"指令引用的"0x00000000"内存。该内存不能为"read"。","非法操作"喽!"41"就是"A"的16进制的ASCII码了,那明显就是strcat这句出的问题了。"lpBuff"的大小只有8字节,算进结尾的\0,那strcat最多只能写入7个"A",但程序实际写入了11个"A"外加1个\0。再来看看上面那幅图,多出来的4个字节正好覆盖了RET的所在的内存空间,导致函数返回到一个错误的内存地址,执行了错误的指令。如果能精心构造这个字符串,使它分成三部分,前一部份仅仅是填充的无意义数据以达到溢出的目的,接着是一个覆盖RET的数据,紧接着是一段shellcode,那只要着个RET地址能指向这段shellcode的第一个指令,那函数返回时就能执行shellcode了。但是软件的不同版本和不同的运行环境都可能影响这段shellcode在内存中的位置,那么要构造这个RET是十分困难的。一般都在RET和shellcode之间填充大量的NOP指令,使得exploit有更强的通用性。


├―――――――┤<―低端内存区域
│ …… │
├―――――――┤<―由exploit填入数据的开始
│ │
│ buffer │<―填入无用的数据
│ │
├―――――――┤
│ RET │<―指向shellcode,或NOP指令的范围
├―――――――┤
│ NOP │
│ …… │<―填入的NOP指令,是RET可指向的范围
│ NOP │
├―――――――┤
│ │
│ shellcode │
│ │
├―――――――┤<―由exploit填入数据的结束
│ …… │
├―――――――┤<―高端内存区域


windows下的动态数据除了可存放在栈中,还可以存放在堆中。了解C++的朋友都知道,C++可以使用new关键字来动态分配内存。来看下面的C++代码:

#include <stdio.h>
#include <iostream.h>
#include <windows.h>

void func()
{
char *buffer=new char[128];
char bufflocal[128];
static char buffstatic[128];
printf("0x%08x\n",buffer); //打印堆中变量的内存地址
printf("0x%08x\n",bufflocal); //打印本地变量的内存地址
printf("0x%08x\n",buffstatic); //打印静态变量的内存地址
}

void main()
{
func();
return;
}

程序执行结果为:

0x004107d0
0x0012ff04
0x004068c0

可以发现用new关键字分配的内存即不在栈中,也不在静态数据区。VC编译器是通过windows下的"堆(heap)"来实现new关键字的内存动态分配。在讲"堆"之前,先来了解一下和"堆"有关的几个API函数:

HeapAlloc 在堆中申请内存空间
HeapCreate 创建一个新的堆对象
HeapDestroy 销毁一个堆对象
HeapFree 释放申请的内存
HeapWalk 枚举堆对象的所有内存块
GetProcessHeap 取得进程的默认堆对象
GetProcessHeaps 取得进程所有的堆对象
LocalAlloc
GlobalAlloc

当进程初始化时,系统会自动为进程创建一个默认堆,这个堆默认所占内存的大小为1M。堆对象由系统进行管理,它在内存中以链式结构存在。通过下面的代码可以通过堆动态申请内存空间:

HANDLE hHeap=GetProcessHeap();
char *buff=HeapAlloc(hHeap,0,8);

其中hHeap是堆对象的句柄,buff是指向申请的内存空间的地址。那这个hHeap究竟是什么呢?它的值有什么意义吗?看看下面这段代码吧:

#pragma comment(linker,"/entry:main") //定义程序的入口
#include <windows.h>

_CRTIMP int (__cdecl *printf)(const char *, ...); //定义STL函数printf
/*---------------------------------------------------------------------------
写到这里,我们顺便来复习一下前面所讲的知识:
(*注)printf函数是C语言的标准函数库中函数,VC的标准函数库由msvcrt.dll模块实现。
由函数定义可见,printf的参数个数是可变的,函数内部无法预先知道调用者压入的参数个数,函数只能通过分析第一个参数字符串的格式来获得压入参数的信息,由于这里参数的个数是动态的,所以必须由调用者来平衡堆栈,这里便使用了__cdecl调用规则。BTW,Windows系统的API函数基本上是__stdcall调用形式,只有一个API例外,那就是wsprintf,它使用__cdecl调用规则,同printf函数一样,这是由于它的参数个数是可变的缘故。
---------------------------------------------------------------------------*/
void main()
{
HANDLE hHeap=GetProcessHeap();
char *buff=HeapAlloc(hHeap,0,0x10);
char *buff2=HeapAlloc(hHeap,0,0x10);
HMODULE hMsvcrt=LoadLibrary("msvcrt.dll");
printf=(void *)GetProcAddress(hMsvcrt,"printf");
printf("0x%08x\n",hHeap);
printf("0x%08x\n",buff);
printf("0x%08x\n\n",buff2);
}

执行结果为:

0x00130000
0x00133100
0x00133118

hHeap的值怎么和那个buff的值那么接近呢?其实hHeap这个句柄就是指向HEAP首部的地址。在进程的用户区存着一个叫PEB(进程环境块)的结构,这个结构中存放着一些有关进程的重要信息,其中在PEB首地址偏移0x18处存放的ProcessHeap就是进程默认堆的地址,而偏移0x90处存放了指向进程所有堆的地址列表的指针。windows有很多API都使用进程的默认堆来存放动态数据,如windows 2000下的所有ANSI版本的函数都是在默认堆中申请内存来转换ANSI字符串到Unicode字符串的。对一个堆的访问是顺序进行的,同一时刻只能有一个线程访问堆中的数据,当多个线程同时有访问要求时,只能排队等待,这样便造成程序执行效率下降。

最后来说说内存中的数据对齐。所位数据对齐,是指数据所在的内存地址必须是该数据长度的整数倍,DWORD数据的内存起始地址能被4除尽,WORD数据的内存起始地址能被2除尽,x86 CPU能直接访问对齐的数据,当他试图访问一个未对齐的数据时,会在内部进行一系列的调整,这些调整对于程序来说是透明的,但是会降低运行速度,所以编译器在编译程序时会尽量保证数据对齐。同样一段代码,我们来看看用VC、Dev-C++和lcc三个不同编译器编译出来的程序的执行结果:

#include <stdio.h>

int main()
{
int a;
char b;
int c;
printf("0x%08x\n",&a);
printf("0x%08x\n",&b);
printf("0x%08x\n",&c);
return 0;
}

这是用VC编译后的执行结果:
0x0012ff7c
0x0012ff7b
0x0012ff80
变量在内存中的顺序:b(1字节)-a(4字节)-c(4字节)。

这是用Dev-C++编译后的执行结果:
0x0022ff7c
0x0022ff7b
0x0022ff74
变量在内存中的顺序:c(4字节)-中间相隔3字节-b(占1字节)-a(4字节)。

这是用lcc编译后的执行结果:
0x0012ff6c
0x0012ff6b
0x0012ff64
变量在内存中的顺序:同上。

三个编译器都做到了数据对齐,但是后两个编译器显然没VC"聪明",让一个char占了4字节,浪费内存哦。


基础知识:
堆栈是一种简单的数据结构,是一种只允许在其一端进行插入或删除的线性表。允许插入或删除操作的一端称为栈顶,另一端称为栈底,对堆栈的插入和删除操作被称为入栈和出栈。有一组CPU指令可以实现对进程的内存实现堆栈访问。其中,POP指令实现出栈操作,PUSH指令实现入栈操作。CPU的ESP寄存器存放当前线程的栈顶指针,EBP寄存器中保存当前线程的栈底指针。CPU的EIP寄存器存放下一个CPU指令存放的内存地址,当CPU执行完当前的指令后,从EIP寄存器中读取下一条指令的内存地址,然后继续执行。


参考:《Windows下的HEAP溢出及其利用》by: isno
《windows核心编程》by: Jeffrey Richter


摘要: 讨论常见的堆性能问题以及如何防范它们。(共 9 页)

前言
您是否是动态分配的 C/C++ 对象忠实且幸运的用户?您是否在模块间的往返通信中频繁地使用了"自动化"?您的程序是否因堆分配而运行起来很慢?不仅仅您遇到这样的问题。几乎所有项目迟早都会遇到堆问题。大家都想说,"我的代码真正好,只是堆太慢"。那只是部分正确。更深入理解堆及其用法、以及会发生什么问题,是很有用的。

什么是堆?
(如果您已经知道什么是堆,可以跳到"什么是常见的堆性能问题?"部分)

在程序中,使用堆来动态分配和释放对象。在下列情况下,调用堆操作:

事先不知道程序所需对象的数量和大小。


对象太大而不适合堆栈分配程序。
堆使用了在运行时分配给代码和堆栈的内存之外的部分内存。下图给出了堆分配程序的不同层。
(图略)

GlobalAlloc/GlobalFree:Microsoft Win32 堆调用,这些调用直接与每个进程的默认堆进行对话。

LocalAlloc/LocalFree:Win32 堆调用(为了与 Microsoft Windows NT 兼容),这些调用直接与每个进程的默认堆进行对话。

COM 的 IMalloc 分配程序(或 CoTaskMemAlloc / CoTaskMemFree):函数使用每个进程的默认堆。自动化程序使用"组件对象模型 (COM)"的分配程序,而申请的程序使用每个进程堆。

C/C++ 运行时 (CRT) 分配程序:提供了 malloc() 和 free() 以及 new 和 delete 操作符。如 Microsoft Visual Basic 和 Java 等语言也提供了新的操作符并使用垃圾收集来代替堆。CRT 创建自己的私有堆,驻留在 Win32 堆的顶部。

Windows NT 中,Win32 堆是 Windows NT 运行时分配程序周围的薄层。所有 API 转发它们的请求给 NTDLL。

Windows NT 运行时分配程序提供 Windows NT 内的核心堆分配程序。它由具有 128 个大小从 8 到 1,024 字节的空闲列表的前端分配程序组成。后端分配程序使用虚拟内存来保留和提交页。

在图表的底部是"虚拟内存分配程序",操作系统使用它来保留和提交页。所有分配程序使用虚拟内存进行数据的存取。

分配和释放块不就那么简单吗?为何花费这么长时间?

堆实现的注意事项
传统上,操作系统和运行时库是与堆的实现共存的。在一个进程的开始,操作系统创建一个默认堆,叫做"进程堆"。如果没有其他堆可使用,则块的分配使用"进程堆"。语言运行时也能在进程内创建单独的堆。(例如,C 运行时创建它自己的堆。)除这些专用的堆外,应用程序或许多已载入的动态链接库 (DLL) 之一可以创建和使用单独的堆。Win32 提供一整套 API 来创建和使用私有堆。有关堆函数(英文)的详尽指导,请参见 MSDN。

当应用程序或 DLL 创建私有堆时,这些堆存在于进程空间,并且在进程内是可访问的。从给定堆分配的数据将在同一个堆上释放。(不能从一个堆分配而在另一个堆释放。)

在所有虚拟内存系统中,堆驻留在操作系统的"虚拟内存管理器"的顶部。语言运行时堆也驻留在虚拟内存顶部。某些情况下,这些堆是操作系统堆中的层,而语言运行时堆则通过大块的分配来执行自己的内存管理。不使用操作系统堆,而使用虚拟内存函数更利于堆的分配和块的使用。

典型的堆实现由前、后端分配程序组成。前端分配程序维持固定大小块的空闲列表。对于一次分配调用,堆尝试从前端列表找到一个自由块。如果失败,堆被迫从后端(保留和提交虚拟内存)分配一个大块来满足请求。通用的实现有每块分配的开销,这将耗费执行周期,也减少了可使用的存储空间。

Knowledge Base 文章 Q10758,"用 calloc() 和 malloc() 管理内存" (搜索文章编号), 包含了有关这些主题的更多背景知识。另外,有关堆实现和设计的详细讨论也可在下列著作中找到:"Dynamic Storage Allocation: A Survey and Critical Review",作者 Paul R. Wilson、Mark S. Johnstone、Michael Neely 和 David Boles;"International Workshop on Memory Management", 作者 Kinross, Scotland, UK, 1995 年 9 月(http://www.cs.utexas.edu/users/oops/papers.html)(英文)。

Windows NT 的实现(Windows NT 版本 4.0 和更新版本) 使用了 127 个大小从 8 到 1,024 字节的 8 字节对齐块空闲列表和一个"大块"列表。"大块"列表(空闲列表[0]) 保存大于 1,024 字节的块。空闲列表容纳了用双向链表链接在一起的对象。默认情况下,"进程堆"执行收集操作。(收集是将相邻空闲块合并成一个大块的操作。)收集耗费了额外的周期,但减少了堆块的内部碎片。

单一全局锁保护堆,防止多线程式的使用。(请参见"Server Performance and Scalability Killers"中的第一个注意事项, George Reilly 所著,在 "MSDN Online Web Workshop"上(站点:http://msdn.microsoft.com/workshop/server/iis/tencom.asp(英文)。)单一全局锁本质上是用来保护堆数据结构,防止跨多线程的随机存取。若堆操作太频繁,单一全局锁会对性能有不利的影响。

什么是常见的堆性能问题?
以下是您使用堆时会遇到的最常见问题:

分配操作造成的速度减慢。光分配就耗费很长时间。最可能导致运行速度减慢原因是空闲列表没有块,所以运行时分配程序代码会耗费周期寻找较大的空闲块,或从后端分配程序分配新块。


释放操作造成的速度减慢。释放操作耗费较多周期,主要是启用了收集操作。收集期间,每个释放操作"查找"它的相邻块,取出它们并构造成较大块,然后再把此较大块插入空闲列表。在查找期间,内存可能会随机碰到,从而导致高速缓存不能命中,性能降低。


堆竞争造成的速度减慢。当两个或多个线程同时访问数据,而且一个线程继续进行之前必须等待另一个线程完成时就发生竞争。竞争总是导致麻烦;这也是目前多处理器系统遇到的最大问题。当大量使用内存块的应用程序或 DLL 以多线程方式运行(或运行于多处理器系统上)时将导致速度减慢。单一锁定的使用―常用的解决方案―意味着使用堆的所有操作是序列化的。当等待锁定时序列化会引起线程切换上下文。可以想象交叉路口闪烁的红灯处走走停停导致的速度减慢。
竞争通常会导致线程和进程的上下文切换。上下文切换的开销是很大的,但开销更大的是数据从处理器高速缓存中丢失,以及后来线程复活时的数据重建。

堆破坏造成的速度减慢。造成堆破坏的原因是应用程序对堆块的不正确使用。通常情形包括释放已释放的堆块或使用已释放的堆块,以及块的越界重写等明显问题。(破坏不在本文讨论范围之内。有关内存重写和泄漏等其他细节,请参见 Microsoft Visual C++(R) 调试文档 。)


频繁的分配和重分配造成的速度减慢。这是使用脚本语言时非常普遍的现象。如字符串被反复分配,随重分配增长和释放。不要这样做,如果可能,尽量分配大字符串和使用缓冲区。另一种方法就是尽量少用连接操作。
竞争是在分配和释放操作中导致速度减慢的问题。理想情况下,希望使用没有竞争和快速分配/释放的堆。可惜,现在还没有这样的通用堆,也许将来会有。

在所有的服务器系统中(如 IIS、MSProxy、DatabaseStacks、网络服务器、 Exchange 和其他), 堆锁定实在是个大瓶颈。处理器数越多,竞争就越会恶化。

尽量减少堆的使用
现在您明白使用堆时存在的问题了,难道您不想拥有能解决这些问题的超级魔棒吗?我可希望有。但没有魔法能使堆运行加快―因此不要期望在产品出货之前的最后一星期能够大为改观。如果提前规划堆策略,情况将会大大好转。调整使用堆的方法,减少对堆的操作是提高性能的良方。

如何减少使用堆操作?通过利用数据结构内的位置可减少堆操作的次数。请考虑下列实例:

struct ObjectA {
   // objectA 的数据
}

struct ObjectB {
   // objectB 的数据
}

// 同时使用 objectA 和 objectB

//
// 使用指针
//
struct ObjectB {
   struct ObjectA * pObjA;
   // objectB 的数据
}

//
// 使用嵌入
//
struct ObjectB {
   struct ObjectA pObjA;
   // objectB 的数据
}

//
// 集合 � 在另一对象内使用 objectA 和 objectB
//

struct ObjectX {
   struct ObjectA  objA;
   struct ObjectB  objB;
}

避免使用指针关联两个数据结构。如果使用指针关联两个数据结构,前面实例中的对象 A 和 B 将被分别分配和释放。这会增加额外开销―我们要避免这种做法。


把带指针的子对象嵌入父对象。当对象中有指针时,则意味着对象中有动态元素(百分之八十)和没有引用的新位置。嵌入增加了位置从而减少了进一步分配/释放的需求。这将提高应用程序的性能。


合并小对象形成大对象(聚合)。聚合减少分配和释放的块的数量。如果有几个开发者,各自开发设计的不同部分,则最终会有许多小对象需要合并。集成的挑战就是要找到正确的聚合边界。


内联缓冲区能够满足百分之八十的需要(aka 80-20 规则)。个别情况下,需要内存缓冲区来保存字符串/二进制数据,但事先不知道总字节数。估计并内联一个大小能满足百分之八十需要的缓冲区。对剩余的百分之二十,可以分配一个新的缓冲区和指向这个缓冲区的指针。这样,就减少分配和释放调用并增加数据的位置空间,从根本上提高代码的性能。


在块中分配对象(块化)。块化是以组的方式一次分配多个对象的方法。如果对列表的项连续跟踪,例如对一个 {名称,值} 对的列表,有两种选择:选择一是为每一个"名称-值"对分配一个节点;选择二是分配一个能容纳(如五个)"名称-值"对的结构。例如,一般情况下,如果存储四对,就可减少节点的数量,如果需要额外的空间数量,则使用附加的链表指针。
块化是友好的处理器高速缓存,特别是对于 L1-高速缓存,因为它提供了增加的位置 ―不用说对于块分配,很多数据块会在同一个虚拟页中。

正确使用 _amblksiz。C 运行时 (CRT) 有它的自定义前端分配程序,该分配程序从后端(Win32 堆)分配大小为 _amblksiz 的块。将 _amblksiz 设置为较高的值能潜在地减少对后端的调用次数。这只对广泛使用 CRT 的程序适用。
使用上述技术将获得的好处会因对象类型、大小及工作量而有所不同。但总能在性能和可升缩性方面有所收获。另一方面,代码会有点特殊,但如果经过深思熟虑,代码还是很容易管理的。

其他提高性能的技术
下面是一些提高速度的技术:

使用 Windows NT5 堆
由于几个同事的努力和辛勤工作,1998 年初 Microsoft Windows(R) 2000 中有了几个重大改进:

改进了堆代码内的锁定。堆代码对每堆一个锁。全局锁保护堆数据结构,防止多线程式的使用。但不幸的是,在高通信量的情况下,堆仍受困于全局锁,导致高竞争和低性能。Windows 2000 中,锁内代码的临界区将竞争的可能性减到最小,从而提高了可伸缩性。


使用 "Lookaside"列表。堆数据结构对块的所有空闲项使用了大小在 8 到 1,024 字节(以 8-字节递增)的快速高速缓存。快速高速缓存最初保护在全局锁内。现在,使用 lookaside 列表来访问这些快速高速缓存空闲列表。这些列表不要求锁定,而是使用 64 位的互锁操作,因此提高了性能。


内部数据结构算法也得到改进。
这些改进避免了对分配高速缓存的需求,但不排除其他的优化。使用 Windows NT5 堆评估您的代码;它对小于 1,024 字节 (1 KB) 的块(来自前端分配程序的块)是最佳的。GlobalAlloc() 和 LocalAlloc() 建立在同一堆上,是存取每个进程堆的通用机制。如果希望获得高的局部性能,则使用 Heap(R) API 来存取每个进程堆,或为分配操作创建自己的堆。如果需要对大块操作,也可以直接使用 VirtualAlloc() / VirtualFree() 操作。

上述改进已在 Windows 2000 beta 2 和 Windows NT 4.0 SP4 中使用。改进后,堆锁的竞争率显著降低。这使所有 Win32 堆的直接用户受益。CRT 堆建立于 Win32 堆的顶部,但它使用自己的小块堆,因而不能从 Windows NT 改进中受益。(Visual C++ 版本 6.0 也有改进的堆分配程序。)

使用分配高速缓存
分配高速缓存允许高速缓存分配的块,以便将来重用。这能够减少对进程堆(或全局堆)的分配/释放调用的次数,也允许最大限度的重用曾经分配的块。另外,分配高速缓存允许收集统计信息,以便较好地理解对象在较高层次上的使用。

典型地,自定义堆分配程序在进程堆的顶部实现。自定义堆分配程序与系统堆的行为很相似。主要的差别是它在进程堆的顶部为分配的对象提供高速缓存。高速缓存设计成一套固定大小(如 32 字节、64 字节、128 字节等)。这一个很好的策略,但这种自定义堆分配程序丢失与分配和释放的对象相关的"语义信息"。

与自定义堆分配程序相反,"分配高速缓存"作为每类分配高速缓存来实现。除能够提供自定义堆分配程序的所有好处之外,它们还能够保留大量语义信息。每个分配高速缓存处理程序与一个目标二进制对象关联。它能够使用一套参数进行初始化,这些参数表示并发级别、对象大小和保持在空闲列表中的元素的数量等。分配高速缓存处理程序对象维持自己的私有空闲实体池(不超过指定的阀值)并使用私有保护锁。合在一起,分配高速缓存和私有锁减少了与主系统堆的通信量,因而提供了增加的并发、最大限度的重用和较高的可伸缩性。

需要使用清理程序来定期检查所有分配高速缓存处理程序的活动情况并回收未用的资源。如果发现没有活动,将释放分配对象的池,从而提高性能。

可以审核每个分配/释放活动。第一级信息包括对象、分配和释放调用的总数。通过查看它们的统计信息可以得出各个对象之间的语义关系。利用以上介绍的许多技术之一,这种关系可以用来减少内存分配。

分配高速缓存也起到了调试助手的作用,帮助您跟踪没有完全清除的对象数量。通过查看动态堆栈返回踪迹和除没有清除的对象之外的签名,甚至能够找到确切的失败的调用者。

MP 堆
MP 堆是对多处理器友好的分布式分配的程序包,在 Win32 SDK(Windows NT 4.0 和更新版本)中可以得到。最初由 JVert 实现,此处堆抽象建立在 Win32 堆程序包的顶部。MP 堆创建多个 Win32 堆,并试图将分配调用分布到不同堆,以减少在所有单一锁上的竞争。

本程序包是好的步骤 ―一种改进的 MP-友好的自定义堆分配程序。但是,它不提供语义信息和缺乏统计功能。通常将 MP 堆作为 SDK 库来使用。如果使用这个 SDK 创建可重用组件,您将大大受益。但是,如果在每个 DLL 中建立这个 SDK 库,将增加工作设置。

重新思考算法和数据结构
要在多处理器机器上伸缩,则算法、实现、数据结构和硬件必须动态伸缩。请看最经常分配和释放的数据结构。试问,"我能用不同的数据结构完成此工作吗?"例如,如果在应用程序初始化时加载了只读项的列表,这个列表不必是线性链接的列表。如果是动态分配的数组就非常好。动态分配的数组将减少内存中的堆块和碎片,从而增强性能。

减少需要的小对象的数量减少堆分配程序的负载。例如,我们在服务器的关键处理路径上使用五个不同的对象,每个对象单独分配和释放。一起高速缓存这些对象,把堆调用从五个减少到一个,显著减少了堆的负载,特别当每秒钟处理 1,000 个以上的请求时。

如果大量使用"Automation"结构,请考虑从主线代码中删除"Automation BSTR",或至少避免重复的 BSTR 操作。(BSTR 连接导致过多的重分配和分配/释放操作。)

 

摘要
对所有平台往往都存在堆实现,因此有巨大的开销。每个单独代码都有特定的要求,但设计能采用本文讨论的基本理论来减少堆之间的相互作用。

评价您的代码中堆的使用。
改进您的代码,以使用较少的堆调用:分析关键路径和固定数据结构。
在实现自定义的包装程序之前使用量化堆调用成本的方法。
如果对性能不满意,请要求 OS 组改进堆。更多这类请求意味着对改进堆的更多关注。
要求 C 运行时组针对 OS 所提供的堆制作小巧的分配包装程序。随着 OS 堆的改进,C 运行时堆调用的成本将减小。
操作系统(Windows NT 家族)正在不断改进堆。请随时关注和利用这些改进。
Murali Krishnan 是 Internet Information Server (IIS) 组的首席软件设计工程师。从 1.0 版本开始他就设计 IIS,并成功发行了 1.0 版本到 4.0 版本。Murali 组织并领导 IIS 性能组三年 (1995-1998), 从一开始就影响 IIS 性能。他拥有威斯康星州 Madison 大学的 M.S.和印度 Anna 大学的 B.S.。


--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

linux中C++编译中出现的一个警告解决方法

原始代码class.cpp:
#include <stdio.h>
#include <string.h>
#include <iostream.h>

int main(int argc, char **argv)
{
    class stud {
      private:
        int num;
        char name[10];
        char sex;
      public:
         stud() {
            num = 1010;
            strcpy(name, "xiongfeng");
            sex = 'm';
        } void display() {
            cout << "num:" << num << endl;
            cout << "name:" << name << endl;
            cout << "sex:" << sex << endl;
        }
    };
    stud stud1;
    stud1.display();
    return 0;
}
编译:g++  class.cpp -Wall
出现的警告:
g++ class.cpp -Wall
In file included from /usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/backward/iostream.h:31,
                 from class.cpp:3:
/usr/lib/gcc/i486-linux-gnu/4.1.2/../../../../include/c++/4.1.2/backward/backward_warning.h:32:2: warning: #warning This file includes at least one deprecated or antiquated header. Please consider using one of the 32 headers found in section 17.4.1.2 of the C++ standard. Examples include substituting the <X> header for the <X.h> header for C++ includes, or <iostream> instead of the deprecated header <iostream.h>. To disable this warning use -Wno-deprecated.
解决方法:

将头文件#include <iostream.h>改为#include <iostream>,并在下一行加上using name space std; 其原因我在网上找了下,有些网友这样说:
1、iostream.h是旧的头文件了,iostream里使用了namespace,支持C99标准
2、. h格式的头文件早在98年9月份就被标准委员会抛弃了,我们应该紧跟标准,以适合时代的发展。
3、其次,iostream.h只支持窄字符集,iostream则支持窄/宽字符集。
4、标准对iostream作了很多的改动,接口和实现都有了变化。
5、iostream组件全部放入namespace std中,防止了名字污染。
仅供参考。

#include <stdio.h>
#include <string.h>
#include <iostream>

using namespace std;
int main(int argc, char **argv)
{
    class stud {
      private:
        int num;
        char name[10];
        char sex;
      public:
         stud() {
            num = 1010;
            strcpy(name, "xiongfeng");
            sex = 'm';
        } void display() {
            cout << "num:" << num << endl;
            cout << "name:" << name << endl;
            cout << "sex:" << sex << endl;
        }
    };
    stud stud1;
    stud1.display();
    return 0;
}
编译:g++  class.cpp  -Wall         这样改后编译就没警告出现了!
运行:. /a.out
结果:
num:1010
name:xiongfeng
sex:m

 



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

在linux中用gcc时switch语句的另一种简洁写法

 源代码:
#include <stdio.h>
int main(int argc, char **argv)
{
    int word = 0;
    while (word != 1111) {
        printf("Please input a integer:\n");
        scanf("%d", &word);
        printf("word = %d\n", word);
        switch (word) {
        case 0...20:            //注意格式是:0+空格+...+空格+20
            printf("The word(0-20) is %d\n", word);
            break;
        case 21...40:
            printf("The word(21-40) is %d\n", word);
            break;
        case 41...60:
            printf("The word(41-60) is %d\n", word);
            break;
        case 61...80:
            printf("The word(61-80) is %d\n", word);
            break;
        case 81...100:
            printf("The word(81-100) is %d\n", word);
            break;
        default:
            printf("The word > 100\n");
            break;
        }
    }
    return 0;
}
编译:gcc -Wall switchcase.c
运行:. /a.out
结果:
Please input a integer:
12
word = 12
The word(0-20) is 12
Please input a integer:
34
word = 34
The word(21-40) is 34
Please input a integer:
78
word = 78
The word(61-80) is 78
Please input a integer:
120
word = 120
The word > 100
Please input a integer:
1111
word = 1111
The word > 100



--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

一道对文件件加密题目

 前几天去面试,碰到一道文件加密题目,供网友参考,其要求如下:
1)对文件的内容取反
2)  如果文件大小为奇数则在文件后面追加一个'B'字符。
3)以文件的每两个字节为一组,对每一组的字节交换。

前提:
/*******************************************************************
文件名:file_ope.c   
原始文件:xiong.c         设文件内容为:1234567890
加密后存入文件:feng.c
编辑环境:ubuntu 6.10    vim
*******************************************************************/
源代码:
#include <stdio.h>
#include <string.h>
int readfile(char *filename, char *buf);
int writefile(char *filename, char *buf, int length);
long filelength(char *filename);
int appendfile(char *filename, char *buf);

int main(int argc, char **argv)
{
    int ret = 0;
    int ret1 = 0;
    int i = 0;
    int len = 0;
    char b = 'B';
    char swap;
    long length = 0;
    char buf[1024] = "";

/*****对文件xiong.c的每一个字节取反,并写入文件feng.c中*****/
    ret = readfile("xiong.c", buf);
    if (!ret) {
        printf("\tread file xiong.c content sucess!\n");
    } else {
        printf("\tread file xiong.c content fail!\n");
    }
    printf("\tbuf = %s", buf);     /*********????************/
    while (buf[i] != '\0') {
        buf[i] = ~buf[i];
        i++;
    }
    len = strlen(buf);
    ret1 = writefile("feng.c", buf, len);
    if (ret1) {
        printf("\twrite file feng.c content sucess!\n");
    } else {
        printf("\twrite file feng.c content fail!\n");
    }

/**判断文件feng.c的大小是偶数还是奇数,如是奇数则在文件feng.c中追加'B'**/
    length = filelength("feng.c");      //通过fseek和ftell函数求出文件的大小
    printf("\tfeng.c length = %ld\n", length);
    if (length % 2 == 0) {
        printf("\tThe length is ou shu!\n");
    } else {
        if (appendfile("feng.c", &b)) {
            printf("\twrite 'B' sucess\n");
        } else {
            printf("\twrite 'B' fail\n");
        }
    }
    length = filelength("feng.c");      //测试加'B'后文件feng.c是否为偶数
    printf("\tAfter append 'B' of the file feng.c'length = %ld\n", length);

/**以每两个字节为一组,使这组中的两个字节互换,再写入feng.c中**/
    memset(buf, 0, sizeof(buf));
    ret = 1;
    ret = readfile("feng.c", buf);
    if (!ret) {
        printf("\tread file xiong.c content sucess!\n");
    } else {
        printf("\tread file xiong.c content fail!\n");
    }
    printf("\tbuf = %s\n", buf);   /******????******/
    while (length!=0)
    {
        i = 0;
        swap = buf[i];
        buf[i] = buf[i + 1];
        buf[i + 1] = swap;
        i = i + 2;
        length--;
    }
    ret1 = 0;
    len = strlen(buf);
    ret1 = writefile("feng.c", buf, len);
    if (ret1) {
        printf("\twrite file feng.c content sucess!\n");
    } else {
        printf("\twrite file feng.c content fail!\n");
    }
    return 0;
}

/**************************读文件函数****************************/
int readfile(char *filename, char *buf)
{
    FILE *fp;
    int j = 0;
    size_t i;
    fp = fopen(filename, "r");
    if (fp == NULL) {
        printf("\topen file error!\n");
    } else {
        while (1) {
            i = fread(&buf[j], 1, 1, fp);
            if (i == 0)
                break;
            j++;
        }
    }
    fclose(fp);
    return i;
}

/**************************写文件函数****************************/
int writefile(char *filename, char *buf,int length)
{
    FILE *fp;
    int j = 0;
    size_t i = 0;
    fp = fopen(filename, "w");
    if (fp == NULL) {
        printf("\topen file error!\n");
    } else {
        while (length != 0) {
            i = fwrite(&buf[j], 1, 1, fp);
            length--;
            j++;
        }
    }
    fclose(fp);
    return i;
}

/***************************追加字符'B'函数*********************/
int appendfile(char *filename, char *buf)
{
    FILE *fp;
    size_t i = 0;
    fp = fopen(filename, "a");
    if (fp == NULL) {
        printf("\topen file error!\n");
    } else {
        i = fwrite(buf, 1, 1, fp);
    }
    fclose(fp);
    return i;
}

/***************************求文件大小函数*********************/
long filelength(char *filename)
{
    FILE *fp;
    long len = 0;
    fp = fopen(filename, "rb");
    if (fp == NULL) {
        printf("\topen file error!\n");
    } else {
        len = fseek(fp, 0L, SEEK_END);
printf("1len=%ld\n", len);
        len = ftell(fp);
printf("2len=%ld\n", len);
    }
    fclose(fp);
    return len;
}
/********************************结束************************/
编译:gcc  -Wall  file_ope.c
运行:. /a.out
结果:
        read file xiong.c content sucess!
        buf = 1234567890
        write file feng.c content sucess!
1len=0
2len=11
        feng.c length = 11
        write 'B' sucess
1len=0
2len=12
        After append 'B' of the file feng.c'length = 12
        read file xiong.c content sucess!
        buf = ??????????
        write file feng.c content sucess!

疑问:代码中有两个加了/**********************????******************/的printf语句,为什么一个要'\n',一个不要'\n',我找了好久的原因都没找到,有知道的麻烦告诉我下哦!:)

还有一道关于内存改错的题目,问在主函数中运行是否有错,错在哪?
前题:
/************************************************************************
文件名:memory_error.c  
编辑环境:ubuntu 6.10    vim
************************************************************************/
源代码:
void func(void)
{
char a[]="abcdef";
a[0] = 'X';
char *p="123456";
p[0] = 'X';
printf("p = %s\n",p);
printf("a = %s\n",a);
}
调试程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(void);
int main(int argc,char **argv)
{
func();
return 0;
}

void func(void)
{
char a[]="abcdef";
printf("before a[]\n");
a[0] = 'X';
printf("before *p\n");
char *p="123456";
printf("after *p\n");
p[0] = 'X'; //p指向的是一个常量,不能改变它的值
printf("before printf\n");
printf("p = %s\n",p);
printf("a = %s\n",a);
}
编译:gcc -Wall memory_error.c
运行:. /a.out
结果:
before a[]
before *p
after *p
段错误 (core dumped)

错误出在  p[0] = 'X';    处,上面已经解释了原因。
我改后的程序:
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
void func(void);
int main(int argc,char **argv)
{
func();
return 0;
}

void func(void)
{
char a[]="abcdef";
printf("before a[]\n");
a[0] = 'X';
printf("before *p\n");
char *p = NULL;
p = malloc(10);
strcpy(p,"123456");
printf("after *p\n");
p[0] = 'X';//p指向的是一个常量,不能改变它的值
printf("before printf\n");
printf("p = %s\n",p);
printf("a = %s\n",a);
}
编译:gcc -Wall memory_error.c
运行:. /a.out
结果:
before a[]
before *p
after *p
before printf
p = X23456
a = Xbcdef
/********************End************************/




--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/

Linux下对MYSQL数据库的操作一

Linux下对MYSQL数据库的操作一

   首先在ubuntu/debian上安装下列软件包:mysql-client-5.0     mysql-server-5.0   
 在Redhat上安装下列软件包:mysql-3.23.54a-11.i386.rpm  mysql-server-3.23.54a-11.i386.rpm
 和mysql-devel-3.23.54a-11.i386.rmp   (可以装上更高的版本,本文在ubuntu下试验)

 1、在终端运行:mysql_install_db  (这一步好像可以不要)
     /*启动mysqld服务器并且建立初始MySQL授权表,包含决定用户如何被允许连接服务器的权限*/
2、启动数据库
      方法一:终端运行:sudo mymysqld_safe  &       //启动mysql数据库
      方法二:终端运行:sudo /etc/init.d/mysql start                 
3、检查MYSQL是否被启动
    方法一:终端运行:pstree | grep mysqld
    出现如下结果则表明已经启动:
     | -mysqld_safe-+-logger
     |             `-mysqld---9*[{mysqld}]
    方法二:终端运行:sudo /etc/init.d/mysql  status
    出现如下结果则表明已经启动:(不同的系统版本略有差异)
      * /usr/bin/mysqladmin  Ver 8.41 Distrib 5.0.24a, for pc-linux-gnu on i486
    Copyright (C) 2000 MySQL AB & MySQL Finland AB & TCX DataKonsult AB
    This software comes with ABSOLUTELY NO WARRANTY. This is free software,
    and you are welcome to modify and redistribute it under the GPL license
    Server version          5.0.24a-Debian_9-log
    Protocol version        10
    Connection              Localhost via UNIX socket
    UNIX socket             /var/run/mysqld/mysqld.sock
    Uptime:                 1 min 23 sec
    Threads: 1  Questions: 147  Slow queries: 0  Opens: 98  Flush tables: 1  Open tables: 14  Queries per second avg: 1.771
另:如想开机后自动启动,可下载一个sysvconfig命令:sudo apt-get install sysvconfig  (Redhat里有这个命令可用:ntsysv)   然后在终端运行:sudo sysvconfig  在menu中进入Enable/Disable,将mysql选中保存即可。
4、关闭MYSQL数据库
    方法一、sudo /etc/init.d/mysql  stop
    方法二、sudo mysqladmin  shutdown
5、进入数据库
    终端运行:mysql -u root -p     //进入密码为空,mysql第一次默认root用户密码为空,敲回车即可
6、创建数据库
      mysql> create database test;   //创建test数据库
      输出结果:Query OK, 1 row affected (0.00 sec)
      mysql> show databases;     //显示所有的数据库
    输出结果:
    +-----------------------------+
    | Database                 |
    +------------------------------+
    | information_schema  |
    | bear                         |
    | mysql                       |
    | test                          |
    +------------------------------+
    4 rows in set (0.00 sec)
7、删除数据库
    mysql> drop database test;   //删除test数据库
    输出结果:
    Query OK, 0 rows affected (0.00 sec)
    mysql> show databases;    //显示所有的数据库
    输出结果:
    +---------------------------------------+
    | Database                          |
    +---------------------------------------+
    | information_schema           |
    | bear                                  |        
    | mysql                                |
    +---------------------------------------+
    3 rows in set (0.01 sec)
8、创建表
    mysql> create database test;   //创建test数据库
    mysql> use test;  //进入到test数据库中
    mysql> show tables ;  //查看所有的表
    输出显示结果:Empty set (0.00 sec)
    mysql> create table people(  //创建一个表名为people的表,四列分别是:
    -> people_id char(6) primary key,  //people_id    为主键
    -> name char(10),                             //name
    -> sex char(2),                                //sex
    -> birthday date);                          //birthday
    输出结果:
    Query OK, 0 rows affected (0.02 sec)  
    mysql> show tables ;  //显示所有的表
    输出结果:
    +----------------------+
    | Tables_in_test  |
    +----------------------+
    | people              |
    +----------------------+
    1 row in set (0.00 sec)
9、删除表
    /* 这一步可到最后做,因为下面还要用到表people */
     mysql> drop  table  people;   //删除表格
     mysql> show tables ;  //显示所有的表
10、查看表结构
    mysql> use test;    //进入要查看表结构的数据库
    mysql> describe people;    //查看表结构
    显示结果:
    +--------+-------+-------------+---------+--------+-----------+-----------+
    |  Field          |  Type     |  Null |  Key   |  Default |  Extra  |
    +--------+-------+-------------+--- -----+--------+-----------+-----------+
    | people_id   |  char(6)   |  NO    |  PRI  |            |             |
    | name         |  char(10) |  YES  |         | NULL   |             |
    | sex            |  char(2)  |  YES  |          | NULL   |            |
    | birthday      |  date      |  YES  |          |  NULL |             |
    +--------+-------+------------+----------+--------+-----------+-----------+
   4 rows in set (0.00 sec)
11、向表中添加和查看数据
    mysql> use test;      //进入test数据库
    mysql> insert into people values         //向people表中添加两组数据
        -> ('200701','熊锋','m','1983/04/23');
    mysql> insert into people values
        ->('200702','郑玲','w','1986-08-18');
   mysql> select * from people;          //查看people表中的数据
   显示结果:
    +-----------+--------+------+------------+------------+-----------------+
    |     people_id     |     name        |     sex  |  birthday     |
    +-----------+--------+------+------------+------------+-----------------+
    |     200701         |       熊锋      |       m  |  1983-04-23 | 
    |     200702         |       郑玲       |        w  |  1986-08-18 |
    +-----------+--------+------+------------+------------+---------------+
2 rows in set (0.00 sec)
mysql> select * from people where people_id='200701';
//查看people表中主键值为200701的数据
   显示结果:
+-----------+--------+------+---------+
| people_id         | name           |      sex    | birthday      |
+-----------+--------+------+---------+
| 200701                | 熊锋           |      m        | 1983-04-23 |
+-----------+--------+------+---------+
1 row in set (0.00 sec)
12、修改表结构
mysql> alter table people add score int(3) not null;
显示结果:
Query OK, 2 rows affected (0.01 sec)
Records: 2  Duplicates: 0  Warnings: 0
mysql> describe people;  //显示表结构
显示结果:
+--------+-------+----+----+------+----+
|  Field          |  Type       |  Null |  Key  |  Default |  Extra |
+--------+-------+----+---+-------+----+
| people_id  |  char(6)   |  NO    |  PRI |                  |            |
| name           |  char(10)  |  YES  |         |      NULL   |            |
| sex             |  char(2)    |  YES  |         |     NULL    |            |
| birthday   |  date         |  YES  |         |     NULL    |            |
| core           |  int(11)      |  YES  |         |     NULL    |            |
| score         |  int(3)      |   NO    |        |                   |            |
+--------+-------+----+--+--------+----+
6 rows in set (0.00 sec)

注:MYSQL中的数据类型
Data Type                          Default Value
char/varchar/binary         ""
date                                      0000-00-00
time                                      00:00:00
datetime                          0000-00-00 00:00:00
timestamp                       2005-06-21 12:01:00(now()!)
year                                  0000
enum('a','b','c')                   'a'
set('a','b','c')                     ()(empty set)

Linux下对MYSQL数据库的操作待续......


--
/**************************************/
Name: Xiong Feng
E-mail:linux0818@gmail.com
MSN:linux0818@hotmail.com
QQ:23562033
Address: GuangZhou.China
/**************************************/