存档在 2013年5月

valgrind,mudflap检查内存越界

2013年5月31日

我一直使用valgrind这个小工具来检测一些内存问题,虽然该工具检测不是那么准确,但是也可以作为指导。今天发现了另外一个内存检测软件mudflap,决定使用使用。但是很遗憾,项目中代码比较庞大,结果使用该工具后导致“吐核”,查了手册,原来只支持“(C and very simple C++ programs)”。

要使用mudflap要求GCC版本不低于4.0, 而且默认的linux分发版本都没有装这个东东, 需要手动安装:
fedora等使用yum的系统安装命令:
yum install libmudflap libmudflap-devel
测试代码mudflap_test.c如下:

#include <stdlib.h>                                                                                                          
#include <stdio.h>
#include <string.h>
#include <sys/time.h>

int main(void) 
{
    printf( "%s:%d ------------------------------------------------\n", __FILE__, __LINE__ );

    //char p1[256];                     //栈越界测试
    char *p1 = (char *) malloc(256);    //堆越界测试
    p1[256] = '0';                      //下地址越界
    sleep(1);
    printf( "\n%s:%d ------------------------------------------------\n", __FILE__, __LINE__ );
    memset( p1, 0, 257 );               //函数赋值长度过长
    sleep(1);
    printf( "\n%s:%d ------------------------------------------------\n", __FILE__, __LINE__ );
    char *pTest1 = p1 + 256;
    *pTest1 = '0';                      //下地址越界
    sleep(1);
    printf( "\n%s:%d ------------------------------------------------\n", __FILE__, __LINE__ );
    char *pTest2 = p1 - 1;              //上地址越界
    *pTest2 = '0';

    printf( "\n%s:%d ------------------------------------------------\n", __FILE__, __LINE__ );
    sleep(10);
    return 0;
} 

编译以及运行代码如下(堆越界和栈越界输出整体类似, 局部有点小差异):

[mgqw@mgqw memtools]$ gcc mudflap_test.c -fmudflap -lmudflap
[mgqw@mgqw memtools]$ ./a.out 
mudflap_test.c:8 ------------------------------------------------
*******
mudflap violation 1 (check/write): time=1338606358.121363 ptr=0x879da48 size=1
pc=0x1182de location=`mudflap_test.c:12:13 (main)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3e) [0x1182de]
      ./a.out(main+0xa7) [0x804883b]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4a) [0x1176da]
Nearby object 1: checked region begins 1B after and ends 1B after
mudflap object 0x879da70: name=`malloc region'
bounds=[0x879d948,0x879da47] size=256 area=heap check=0r/0w liveness=0
alloc time=1338606358.120588 pc=0x11766e
      /usr/lib/libmudflap.so.0(__mf_register+0x3e) [0x11766e]
      /usr/lib/libmudflap.so.0(__wrap_malloc+0xe3) [0x1187e3]
      ./a.out(main+0x42) [0x80487d6]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4a) [0x1176da]
number of nearby objects: 1

mudflap_test.c:14 ------------------------------------------------
*******
mudflap violation 2 (check/write): time=1338606359.122117 ptr=0x879d948 size=257
pc=0x1182de location=`(memset dest)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3e) [0x1182de]
      /usr/lib/libmudflap.so.0(__mfwrap_memset+0xdf) [0x119d4f]
      ./a.out(main+0xfc) [0x8048890]
Nearby object 1: checked region begins 0B into and ends 1B after
mudflap object 0x879da70: name=`malloc region'
number of nearby objects: 1

mudflap_test.c:17 ------------------------------------------------
*******
mudflap violation 3 (check/write): time=1338606360.122456 ptr=0x879da48 size=1
pc=0x1182de location=`mudflap_test.c:19:13 (main)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3e) [0x1182de]
      ./a.out(main+0x183) [0x8048917]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4a) [0x1176da]
Nearby object 1: checked region begins 1B after and ends 1B after
mudflap object 0x879da70: name=`malloc region'
number of nearby objects: 1

mudflap_test.c:21 ------------------------------------------------
*******
mudflap violation 4 (check/write): time=1338606361.123759 ptr=0x879d947 size=1
pc=0x1182de location=`mudflap_test.c:23:13 (main)'
      /usr/lib/libmudflap.so.0(__mf_check+0x3e) [0x1182de]
      ./a.out(main+0x21c) [0x80489b0]
      /usr/lib/libmudflap.so.0(__wrap_main+0x4a) [0x1176da]
Nearby object 1: checked region begins 1B before and ends 1B before
mudflap object 0x879da70: name=`malloc region'
number of nearby objects: 1

mudflap_test.c:25 ------------------------------------------------

不管是堆还是栈, 不管是上行地址越界还是下行地址越界都能准确定位出来.

关于32位linux下物理内存与虚拟内存的计算

2013年5月31日

今天在遇到一毕业设计同学问到如下这个问题:
cat /proc/pid/stat计算物理内存与虚拟内存时,为什么要如下计算?

tableProcess->setItem(0,3,new QTableWidgetItem(QString("%1M").arg((atof(vmsize)/(1024*1024)))));/*虚拟内存*/
tableProcess->setItem(0,4,new QTableWidgetItem(QString("%1M").arg((atof(rmsize)*4/1024))));/*物理内存*/

其实man以下应该就知道答案,以下是man的结果

vsize %lu
Virtual memory size in bytes.
rss %ld
Resident Set Size: number of pages the process has in real memory, minus 3 for administrative purposes. This is just the pages which count towards text, data, or stack space. This does not include pages which have not been demand-loaded in, or which are swapped out.

可以看出,虚拟内存的值是以字节表示的,而物理内存则时以页数来统计的。
所以对于虚拟内存直接除上1024*1024就可以的出以“M”位单位的答案。
对于物理内存,则需要一些操作系统知识,1个页大小位4K,所以计算方案转换位‘M’为单位时,需要*4/1024.
操作系统原理还是很重要的!!

QString::arg() 函数

2013年5月31日

例:setWindowTitle(tr(“%1[*] – %2”).arg(shownName).arg(tr(“Spreadsheet”)));

QString::arg()函数用第一个arg()调用会替换“%1”,第2个arg()调用会替换“%2”。上面的例子其实可以写作:
setWindowTitle(shownName + tr(“[*] – Spreadsheet”));

epoll事件模型实现原理

2013年5月31日

1. 功能介绍
epoll与select/poll不同的一点是,它是由一组系统调用组成。

int epoll_create(int size);
int epoll_ctl(int epfd, int op, int fd, struct epoll_event *event);
int epoll_wait(int epfd, struct epoll_event *events,int maxevents, int timeout);

epoll相关系统调用是在Linux 2.5.44开始引入的。该系统调用针对传统的select/poll系统调用的不足,设计上作了很大的改动。select/poll的缺点在于:epollnenene
1.每次调用时要重复地从用户态读入参数。
2.每次调用时要重复地扫描文件描述符。
3.每次在调用开始时,要把当前进程放入各个文件描述符的等待队列。在调用结束后,又把进程从各个等待队列中删除。
在实际应用中,select/poll监视的文件描述符可能会非常多,如果每次只是返回一小部分,那么,这种情况下select/poll显得不够高效。 epoll的设计思路,是把
select/poll单个的操作拆分为1个epoll_create+多个epoll_ctrl+一个wait。此外,内核针对epoll操作添加了一个文件系统”eventpollfs”,每一个或者多个要监视的文件描述符都有一个对应的eventpollfs文件系统的inode节点,主要信息保存在eventpoll结构体中。而被监视的文件的重要信息则保存在epitem结构体中。所以他们是一对多的关系。
由于在执行epoll_create和epoll_ctrl时,已经把用户态的信息保存到内核态了,所以之后即使反复地调用epoll_wait,也不会重复地拷贝参数,扫描文件描述符,反复地把当前进程放入/放出等待队列。这样就避免了以上的三个缺点。
接下去看看它们的实现:
2. 关键结构体

/* Wrapper struct used by poll queueing */
struct ep_pqueue {
poll_table pt;
struct epitem *epi;
};

这个结构体类似于select/poll中的struct poll_wqueues。由于epoll需要在内核态保存大量信息,所以光光一个回调函数指针已经不能满足要求,所以在这里引入了一个新的结构体struct epitem。

/*
* Each file descriptor added to the eventpoll interface will
* have an entry of this type linked to the hash.
*/
struct epitem {
/* RB-Tree node used to link this structure to the eventpoll rb-tree */
struct rb_node rbn;
红黑树,用来保存eventpoll
/* List header used to link this structure to the eventpoll ready list */
struct list_head rdllink;
双向链表,用来保存已经完成的eventpoll
/* The file descriptor information this item refers to */
struct epoll_filefd ffd;
这个结构体对应的被监听的文件描述符信息
/* Number of active wait queue attached to poll operations */
int nwait;
poll操作中事件的个数
/* List containing poll wait queues */
struct list_head pwqlist;
双向链表,保存着被监视文件的等待队列,功能类似于select/poll中的poll_table
/* The "container" of this item */
struct eventpoll *ep;
指向eventpoll,多个epitem对应一个eventpoll
/* The structure that describe the interested events and the source fd */
struct epoll_event event;
记录发生的事件和对应的fd
/*
* Used to keep track of the usage count of the structure. This avoids
* that the structure will desappear from underneath our processing.
*/
atomic_t usecnt;
引用计数
/* List header used to link this item to the "struct file" items list */
struct list_head fllink;
双向链表,用来链接被监视的文件描述符对应的struct file。因为file里有f_ep_link,
用来保存所有监视这个文件的epoll节点
/* List header used to link the item to the transfer list */
struct list_head txlink;
双向链表,用来保存传输队列
/*
* This is used during the collection/transfer of events to userspace
* to pin items empty events set.
*/
unsigned int revents;
文件描述符的状态,在收集和传输时用来锁住空的事件集合
};
该结构体用来保存与epoll节点关联的多个文件描述符,保存的方式是使用红黑树实现的hash表.
至于为什么要保存,下文有详细解释。它与被监听的文件描述符一一对应.
struct eventpoll {
/* Protect the this structure access */
rwlock_t lock;
读写锁
/*
* This semaphore is used to ensure that files are not removed
* while epoll is using them. This is read-held during the event
* collection loop and it is write-held during the file cleanup
* path, the epoll file exit code and the ctl operations.
*/
struct rw_semaphore sem;
读写信号量
/* Wait queue used by sys_epoll_wait() */
wait_queue_head_t wq;
/* Wait queue used by file-&gt;poll() */
wait_queue_head_t poll_wait;
/* List of ready file descriptors */
struct list_head rdllist;
已经完成的操作事件的队列。
/* RB-Tree root used to store monitored fd structs */
struct rb_root rbr;
保存epoll监视的文件描述符
};

这个结构体保存了epoll文件描述符的扩展信息,它被保存在file结构体的private_data
中。它与epoll文件节点一一对应。通常一个epoll文件节点对应多个被监视的文件描述符。
所以一个eventpoll结构体会对应多个epitem结构体。
那么,epoll中的等待事件放在哪里呢?见下面

/* Wait structure used by the poll hooks */
struct eppoll_entry {
/* List header used to link this structure to the "struct epitem" */
struct list_head llink;
/* The "base" pointer is set to the container "struct epitem" */
void *base;
/*
* Wait queue item that will be linked to the target file wait
* queue head.
*/
wait_queue_t wait;
/* The wait queue head that linked the "wait" wait queue item */
wait_queue_head_t *whead;
};

与select/poll的struct poll_table_entry相比,epoll的表示等待队列节点的结构体只是稍有不同,与struct poll_table_entry比较一下。

struct poll_table_entry {
struct file * filp;
wait_queue_t wait;
wait_queue_head_t * wait_address;
};

由于epitem对应一个被监视的文件,所以通过base可以方便地得到被监视的文件信息。
又因为一个文件可能有多个事件发生,所以用llink链接这些事件。
3. epoll_create的实现
epoll_create()的功能是创建一个eventpollfs文件系统的inode节点。具体由ep_getfd()完成。ep_getfd()先调用ep_eventpoll_inode()创建一个inode节点,然后调用d_alloc()为inode分配一个dentry。最后把file,dentry,inode三者关联起来。在执行了ep_getfd()之后,它又调用了ep_file_init(),分配了eventpoll结构体,并把eventpoll的指针赋给file结构体,这样eventpoll就与file结构体关联起来了。需要注意的是epoll_create()的参数size实际上只是起参考作用,只要它不小于等于0,就并不限制这个epoll inode关联的文件描述符数量。
4. epoll_ctl的实现
epoll_ctl的功能是实现一系列操作,如把文件与eventpollfs文件系统的inode节点关联起来。这里要介绍一下eventpoll结构体,它保存在file->f_private中,记录了eventpollfs文件系统的inode节点的重要信息,其中成员rbr保存了该epoll文件节点监视的所有文件描述符。组织的方式是一棵红黑树,这种结构体在查找节点时非常
高效。
首先它调用ep_find()从eventpoll中的红黑树获得epitem结构体。然后根据op参数的不同而选择不同的操作。如果op为EPOLL_CTL_ADD,那么正常情况下epitem是不可能在eventpoll的红黑树中找到的,所以调用ep_insert创建一个epitem结构体并插入到对应的红黑树中。ep_insert()首先分配一个epitem对象,对它初始化后,把它放入对应的红黑树。此外,这个函数还要作一个操作,就是把当前进程放入对应文件操作的等待队列。这一步是由下面的代码完成的。
init_poll_funcptr(&epq.pt, ep_ptable_queue_proc);
。。。
revents = tfile->f_op->poll(tfile, &epq.pt);
函数先调用init_poll_funcptr注册了一个回调函数 ep_ptable_queue_proc,这个函数会在调用f_op->poll时被执行。该函数分配一个epoll等待队列结点eppoll_entry:一方面把它挂到文件操作的等待队列中,另一方面把它挂到epitem的队列中。此外,它还注册了一个等待队列的回调函数ep_poll_callback。当文件操作完成,唤醒当前进程之前,
会调用ep_poll_callback(),把eventpoll放到epitem的完成队列中,并唤醒等待进程。如果在执行f_op->poll以后,发现被监视的文件操作已经完成了,那么把它放在完成
队列中了,并立即把等待操作的那些进程唤醒。
5. epoll_wait的实现
epoll_wait的工作是等待文件操作完成并返回。
它的主体是ep_poll(),该函数在for循环中检查epitem中有没有已经完成的事件
,有的话就把结果返回。没有的话调用schedule_timeout()进入休眠,直到进程被再
度唤醒或者超时。
6. 性能分析
epoll机制是针对select/poll的缺陷设计的。通过新引入的eventpollfs文件系统
,epoll把参数拷贝到内核态,在每次轮询时不会重复拷贝。通过把操作拆分为epol
l_create,epoll_ctl,epoll_wait,避免了重复地遍历要监视的文件描述符。此外,
由于调用epoll的进程被唤醒后,只要直接从epitem的完成队列中找出完成的事件,
找出完成事件的复杂度由O(N)降到了O(1)。
但是epoll的性能提高是有前提的,那就是监视的文件描述符非常多,而且每次完
成操作的文件非常少。所以,epoll能否显著提高效率,取决于实际的应用场景。这
方面需要进一步测试。

密码保护:epoll资料连接

2013年5月31日

这是一篇受密码保护的文章,您需要提供访问密码:

Linux下epoll的EPOLLHUP事件

2013年5月31日

当客户端使用ctrl+c强制中断或者强制退出时,服务器端希望能够检测到这一变化,我一般是使用检测read是否返回0来判断。但是会出现这样的情况,当使用ctrl+c时,epoll可以收到EPOLLIN事件,并用read返回0;但是当客户端正常退出时,则检测不到该变化。tcpip

有人试图使用EPOLLHUP来完成这一检测,实际上EPOLLHUP不能用于服务器来检测客户端出现的异常,但EPOLLRDHUP是可以的。

在使用 epoll 时,对端正常断开连接(调用 close()),在服务器端会触发一个 epoll 事件。在低于 2.6.17 版本的内核中,这个 epoll 事件一般是 EPOLLIN,即 0x1,代表连接可读。连接池检测到某个连接发生 EPOLLIN 事件且没有错误后,会认为有请求到来,将连接交给上层进行处理。这样一来,上层尝试在对端已经 close() 的连接上读取请求,只能读到 EOF,会认为发生异常,报告一个错误。
因此在使用 2.6.17 之前版本内核的系统中,我们无法依赖封装 epoll 的底层连接库来实现对对端关闭连接事件的检测,只能通过上层读取数据时进行区分处理。不过,2.6.17 版本内核中增加了 EPOLLRDHUP 事件,代表对端断开连接,。
在使用 2.6.17 之后版本内核的服务器系统中,对端连接断开触发的 epoll 事件会包含 EPOLLIN | EPOLLRDHUP,即 0x2001。有了这个事件,对端断开连接的异常就可以在底层进行处理了,不用再移交到上层。重现这个现象的方法很简单,首先 telnet 到 server,然后什么都不做直接退出,查看在不同系统中触发的事件码。注意,在使用 2.6.17 之前版本内核的系统中,sys/epoll.h 的 EPOLL_EVENTS 枚举类型中是没有 EPOLLRDHUP 事件的,所以带 EPOLLRDHUP 的程序无法编译通过。

常用正则表达式

2013年5月30日

^                                 行首

$                                 行尾

^ [ t h e ]                      以t h e开头行

[ S s ] i g n a [ l L ]              匹配单词s i g n a l、s i g n a L、S i g n a l、S i g n a L

[Ss]igna[lL]”.                同上,但加一句点

[ m a y M A Y ]             包含m a y大写或小写字母的行

^ U S E R $                  只包含U S E R的行

[tty]$                           以t t y结尾的行

” .                                带句点的行

^ d . . x . . x . . x          对用户、用户组及其他用户组成员有可执行权限的目录

^ [ ^ l ]                        排除关联目录的目录列表

^[^d]                ls –l | grep ^[^d] 只显示非文件夹的文件

[ . * 0 ]                       0之前或之后加任意字符

[ 0 0 0 * ]                  0 0 0或更多个

[ iI]                             大写或小写I

[ i I ] [ n N ]                大写或小写i或n

[ ^ $ ]                         空行

[ ^ . * $ ]                     匹配行中任意字符串

^ . . . . . . $                  包括6个字符的行

[a- zA-Z]                     任意单字符

[ a – z ] [ a – z ] *         至少一个小写字母

[ ^ 0 – 9 ” $ ]                非数字或美元标识

[ ^ 0 – 0 A – Z a – z ]     非数字或字母

[ 1 2 3 ]                       1到3中一个数字

[ D d ] e v i c e            单词d e v i c e或D e v i c e

D e . . c e                    前两个字母为D e,后跟两个任意字符,最后为c e

” ^ q                            以^ q开始行

^ . $                            仅有一个字符的行

^”.[0-9][0-9]                以一个句点和两个数字开始的行

‘ ” D e v i c e ” ‘            单词d e v i c e

D e [ V v ] i c e ” .               单词D e v i c e或d e v i c e

[ 0 – 9 ] ” { 2 ” } – [ 0 – 9 ] ” { 2 ” } – [ 0 – 9 ] ” { 4 ” }      对日期格式d d – m m – y y y y

[ 0 – 9 ] ” { 3 ” } ” . [ 0 – 9 ] ” { 3 ” } ” . [ 0 – 9 ] ” { 3 ” } ” . [ 0 – 9 ] ” { 3 ” } I P地址格式

[ ^ . * $ ]                     匹配任意行

[A-Za-z]*            匹配所有单词

 

Vim/GVim+cscope搭配实现无缝跳转

2013年5月29日

vim各种操作命令已经很熟悉了,但有些插件的魅力还没有接触,下面我先来尝试下cscope。
1.是否安装cscope检测,fedora17已经默认安装。

cscope -V

2.生成索引,在源代码目录下

cscope -Rb

b选项为仅仅建立交叉引用,-R是递归子目录
3.如何使用
打开一个文件,比如我打开的Robot.cpp,输入以下vim指令

cscope的用法 :cs find {querytype} {name} 
{querytype} 为下面的命令字 
0 或 s: 查找该C语言符号 
1 或 g: 查找定义 
2 或 d: 查找本函数调用的函数 
3 或 c: 查找调用指定函数的函数 
4 或 t: 查找字符串 
6 或 e: 查找 egrep 模式 
7 或 f: 查找文件 
8 或 i: 查找包含指定文件的文件 

4.上边第3条的每次输入命令很不方便,我们改成快捷键来操作,下载cscope_map.vim
把cscope_map.vim里从 if has(“cscope”) 到 endif里边的内容复制到/etc/vimrc里边去。
5.现在可以使用快捷键了
ctrl + \ 然后加上上边的命令字就可以了。