显示标签为“linux”的博文。显示所有博文
显示标签为“linux”的博文。显示所有博文

2013年1月11日星期五

多线程条件变量和互斥锁的使用

        多线程同步,是多线程编程中肯定会遇到的问题。常见的有pthread_mutex_t、pthread_rwlock_t和pthread_cond_t,前面两个是锁,后面叫条件变量(条件成立时,通知通过阻塞线程的机制;其中条件的修改是需要锁来保护的)。
        条件变量需要和互斥锁以及一个条件结合使用,条件表示某一种情况的达成,比如队列中有元素,可以去取元素了,互斥锁用来使多线程对条件的修改原子化,条件变量用来做通知,向队列放元素的线程通知取元素线程你们可以来取元素了。
        这里有一个令人头疼的问题,就是pthread_cond_signal()和pthread_cond_broadcast()函数是在锁内调用还是在锁外调用,如下:
         1、
         pthread_mutex_lock(&lock);
         pthread_cond_signal(&cond);
         pthread_mutex_unlock(&lock);

         2、
         pthread_mutex_lock(&lock);
         pthread_mutex_unlock(&lock);
         pthread_cond_signal(&cond);

         这个问题,考虑是否浪费了CPU,考虑任务的调度机制,令人头疼;最后在starckoverflow看到一种解释,我觉得很好:不要考虑以上那些不确定的因素,考虑代码正确性就可以,修改条件和通知其它线程条件被修改应该是在一起的,所以放到锁内更好理解。

         另外一点需要注意的是,条件判断使用while循环。
         pthread_mutex_lock(&lock);
         while(cond == NULL) {
                  pthread_cond_wait(&cond, &lock);
         }
         pthread_mutex_unlock(&lock);

2012年12月14日星期五

linux gcc alignment 字节对齐

为什么对齐:
     方便CPU更快速的读取内存数据,Intel CPU也可以识别不对齐的元素,但效率会慢;而RSIC ARM 不能正确识别未正确对齐的变量
     变量对齐并不是C语言定义的,全由编译器根据当前平台的特性确定,所以不能直接确定某个struct的对齐情况。

三个前提:
    struct可以在元素之间和结尾添加填充字节
    数组内元素之间是不能添加填充字节
    可以创建struct类型的数组,要保证数据第二个元素也对齐

结构体不指定__attribute__((aligned()))属性:
       内置类型以自己的长度作为对齐条件,如 short 2字节对齐,int 4字节对齐;这些类型作为struct内元素时,会影响struct的对齐。
       struct内元素按自己的自身长度对齐,struct以最严格的元素的方式对齐(根据CPU字长看)
               32位PC,字长4字节,结构体内有元素大于4字节时,struct也以4字节对齐
               64位PC,字长8字节,struct内元素有大于等于8字节的元素时,struct以8字节对齐

结构体指定 type attribute __attribute__ ((aligned))属性:
        属性是对编译器的建议,编译器会尽量达成
        __attribute__ ((aligned(4)));   建议编译器此结构体以4字节对齐
        __attribute__ ((aligned));        建议编译器使用对此平台maximum useful alignment,有点扯,我平台测试的和手册结果居然不一样
        aligned 指定的长度也会受linker的限制,有些linker有自己的最大对齐值


参考资料:
http://stackoverflow.com/questions/364483/determining-the-alignment-of-c-c-structures-in-relation-to-its-members
http://gcc.gnu.org/onlinedocs/gcc-4.7.2/gcc/Type-Attributes.html#Type-Attributes

2012年3月13日星期二

linux shell中的管道执行

linux shell中管道发挥的作用是文件描述符重定向,例如 prog1 | prog2 | prog3,管道会将prog1的标准输出重定向为prog2的标准输入,将prog2的标准输出重定向为prog3的标准输入,prog1的标准输入和prog3的标准输出并没有改变。比如命令"ps -ef | grep -w "nginx""将ps命令的标准输出内容作为grep的输入,两个命令的组合的只输出关于nginx进程的信息。

这里归档两个平时没有想明白的问题:

1、shell管道中程序按什么顺序执行?进程关系是什么样子?
当前有很多的shell程序,实现各不同,有的支持作业控制,代表有:bash(bourne again shell),有的不支持,代表有bourne shell。

prog1 | prog2 | prog3

1)在不支持作业控制shell中,prog3是shell的子进程,而prog1和prog2为prog3的子进程。执行如下图所示
 
(摘自APUE)
 2)对于支持作业控制的shell,prog1、prog2、prog3都为shell的子进程,在bash中的执行顺序为 prog1、prog2、prog3,具体和shell的实现有关。

[root@zhangst ~]# uname -a
Linux zhangst.F14 2.6.35.6-45.fc14.i686 #1 SMP Mon Oct 18 23:56:17 UTC 2010 i686 i686 i386 GNU/Linux
[root@zhangst ~]# ps -o pid,ppid,pgid,session,tpgid,tty,comm | cat | grep -v "*"
  PID  PPID  PGID  SESS  TPGID TT       COMMAND
 2667  2664  2667  2667  2689 pts/0    bash
 2689  2667  2689  2667  2689 pts/0    ps
 2690  2667  2689  2667  2689 pts/0    cat
 2691  2667  2689  2667  2689 pts/0    grep

上面的例子中ps、cat、grep三个进程都为bash的子进程,ps进程最先执行,并且三个进程在一个进程组中,ps为组长进程,这个组为前台进程组(TPGID),而bash为后台进程。


2、如何判断管道中程序是否成功执行?某个程序执行失败是否影响其它程序的执行?

1)shell中有一个变量数组PIPESTATUS,保存了上一个执行管道的状态。
echo ${PIPESTATUS[*]};就可以输出所有管道进程的执行状态。

[root@zhangst test]# ./0 | ./1 | ./2
[root@zhangst test]# echo ${PIPESTATUS[*]}
0 1 2 


0、1、2三个程序main函数中只包含一条return代码,分别返回 0,1,2。


2)bash中管道程序的执行相互不影响的,参考下面的例子:

[root@zhangst test]# ./0 | grep -v | ./2
用法: grep [选项]... PATTERN [FILE]...
试用‘grep --help’来获得更多信息。
2
0

[root@zhangst test]# echo ${PIPESTATUS[*]}
0 2 2


0、2三个程序分别在标准错误输出输出0、2,然后返回0、2
grep -v是不正确的,不能正确执行,但是0和2都正确执行完毕了。



2012年2月4日星期六

awk & sed命令总结


awk

file          整个文件
record     文件中一行转为一个record,awk每次处理一个record
field        record由field组成,如$1,$2,$0表示真个record

例子:
file s.c
1 2
3 4
awk '{d=($1 + $2); print d}' s.c
输出:
3
7

awk匹配模式从流中提取自己需要的内容:
awk -F "\"" '{printf "msgid ""\""$2"\"""\n""msgstr \"\"\n\n"}' pc_err.c

在线资料
http://sed.sourceforge.net/sed1line_zh-CN.html



sed(stream editor)
commands = pattern + action.
$          matches the end of line
^          matches the beginning of the line


sed -e '1,10d'
删除stdin输入中的前10行,并将其余输出stdout
-e          把 '1,10d'当作sed语句来执行
1,10       pattern
d            action

sed -n -e '/1/p' z.c
输出z.c中匹配1的行
-n     不输出test.txt原文的内容

sed -e '3,$d'
sed -n -e '1,2p'

sed -e '2q'
$表示文本的最后一样,d表示delete
q表示quit
输出流的前两行

sed -e '1,2d' -e '4,5d' z.c
同时指定多个-e

grep 'foo' log | grep -v 'debug' == sed -n -e '/debug/d' -e '/foo/p' log

sed -e 's/PC_NO_MATCH) goto ret;/PC_NO_MATCH) {ret = PC_ERR_RECORD_REPEAT; goto ret;}/' -i *.c

sed -e 's/str1/str2/' -i  *.c
把当前目录下所有c文件中的str1替换为str2

在线资料
http://www.tsnc.edu.cn/default/tsnc_wgrj/doc/abs-3.9.1_cn/html/sedawk.html











2012年2月3日星期五

TCP/IP详解卷一摘抄

算下来,这是我第四次读《TCP/IP详解-卷一》了,读大学时读过一遍,那时懵懵懂懂,工作后又读过三遍,加上工作中的实践,理解渐渐加深。我想这样的经典读十遍也不为过。

UDP

11.10 限制最大UDP长度的因素
          1、IP数据包长度字段,16位的限制。最大为65535 - 20 - 8
          2、sockt API接口限制,用户可以修改此值,APUE上有讲
          3、内核TCP/IP协议栈的实现。
UDP为数据报协议,需要由应用层控制UDP单包大小,避免IP分片提高效率。


TCP

半关闭:TCP提供一种结束一端发送,但还可以继续接收数据的能力。
半打开:如果一端已经关闭或异常终结,而另一端还不知道,则称这种状态为半打开。


呼入连接请求队列(listen)
     1、完成三次握手后,TCP会把连接放入请求队列,应用层从队列中取连接。
     2、当队列满之后,TCP不会理会新到的SYN请求,且不发送RST回复。


交互数据包特性(小包)
     1、经受时延的确认ACK;实现数据包捎带ACK,减少包数量。
     2、Nagle算法;算法要求一个TCP连接上最多只能有一个未被确认的未完成的小分组,该分组的确认到达之前不能发送其它小分组。这样,TCP可以收集多个小分组,等到确认ACK到达时,一并发出去。这样减少了网络上小分组的数量,减轻了慢速网络的压力。算法自适应特点:确认到达的越快,数据发送的也越快。这是一种流量控制的算法。
          Nagle算法并不适用所有环境,如X环境下需要关闭Nagle算法。
    

成块数据包特性(大包)
滑动窗口(通告窗口):
          是TCP的一种流量控制方法,其中窗口的大小由应用程序控制。

慢启动:
          增加拥塞窗口,简称cwnd;一种在连接上发起数据流的方法。窗口初始化为1个报文单位,收到一个ack则增加1个报文单位,发送窗口取拥塞窗口和通告窗口(滑动窗口)的最小值。
          拥塞窗口是发送方的流量控制,通告窗口是接收方的拥塞控制。

超时与重传:
          发送包后设置一个定时器(重传定时器),若定时器超时时间内未收到ACK确认,则重传数据包。
          当定时器超时、收到重复的ACK时,则认为网络发生了拥塞,就需要启动拥塞避免算法。

拥塞避免算法:
          一种处理丢失分组的方法。算法假定分组受损坏而丢失的可能性是非常少的,分组丢失就意味着在源目的地址中间发生了拥塞。
          拥塞避免算法和慢启动算法维护两个变量,一个拥塞窗口cwnd,一个慢启动门限ssthresh。
          1)对一个新连接,cwnd初始化为1个报文段,ssthresh初始化为65535字节。
          2)TCP的输出不能超过拥塞窗口cwnd和接收方的通告窗口。
          3)当拥塞发生时,ssthresh被置为当前窗口的一半(cwnd和通告窗口的最小值,不小于两个报文段),当因为超时引起拥塞时,将cwnd置为1个报文段(启动慢启动)。
          4)当收到新的数据的确认ACK时则增加cwnd。如果cwnd小于等于ssthresh,则执行慢启动,每个ACK确认cwnd增1;当cwnd大于ssthresh时,则执行拥塞避免每个ACK确认cwnd增加1/cwnd。

Jacobson快速重传算法:
          当收到3个ACK时,就假定那个报文已经丢失,并重传ACK序号开始的那个报文段。
Jacobson快速重传算法、快速恢复算法(两个算法,常配合使用)
          当收到3个重复的ACK时,不等待超时,马上重传丢失报文,这是快速重传算法。
          重传之后,不执行慢启动而执行拥塞避免算法,这称为快速恢复算法。
          1)当收到3个重复ACK后,将ssthresh设置为拥塞窗口cwnd的一半,重传丢失的报文。设置cwnd为ssthresh加三倍报文段大小。(根据图21-10、21-11并没有将cwnd设置为ssthresh加三倍报文?)
          2)每收到一个重复的ACK,则cwnd加1并发送1个分组(如果新的cwnd允许发送)。
          3)当下一个确认新数据的ACK达到时,设置cwnd为ssthresh(第1步中设置的值)。这一步采用的拥塞避免,因为分组丢失时我们将当前速率减半。

          ssthresh立即设置为当重传发生 时正在起作用的窗口大小的一半,但是在接收到重复 ACK的过程中cwnd允许保持增加,这是 因为每个重复的 ACK表示1个报文段已离开了网络(接收 TCP已缓存了这个报文段,等待所缺 数据的到达)。这就是快速恢复算法。

坚持定时器
          能够处理TCP打开窗口ACK丢失的情况。由发送方来维护坚持定时器,来周期性的查询(使用指数退避算法)窗口大小。TCP从不放弃窗口探测。

糊涂窗口综合征
          接收方通告一个小的窗口,而发送方也可以发送小量的数据。造成频繁发送小包。

保活定时器
          发现一个TCP连接何时断掉。主要用在服务器。
         


TCP特性:
     1、流量控制;Nagle算法、滑动窗口、慢启动
     2、拥塞控制;慢启动、拥塞避免、快速重传


问题:
Nagle算法和滑动窗口的选择
          TCP内核有mytcp_nagle_testv函数,此函数来判断这个数据包是否使用nagle算法,之后再调用tcp_cwnd_test判断是否使用滑动窗口算法。
          是否使用nagle算法和包的属性(SYN、FIN),包的大小有关。一半认为小于MSS的数据包都属于小包。
http://blog.sina.com.cn/s/blog_54b5ea250100g2xu.html

LAST_ACK 状态有超时机制吗?这个状态合适消失?
          这个和普通数据包的超时机制相同,发送FIN包之后进入LAST_ACK状态,如果超时之前没有收到ACK,则会重发FIN包。


留下一个疑问没有解决,上文标红快速恢复算法图21-10问题,不急,等下次重读再说。


2012年1月10日星期二

c语言inline函数的使用

大学在教科书上学习过inline函数,定义为inline函数之后,会省去函数调用的开销,直接嵌套汇编代码,取代函数调用,提高效率。工作后项目中也很少用到inline来定义函数,近几天在研读google的google c++ style guide,发现之前自己对inline函数的认识太过肤浅了,这里学习总结一下。

1、inline函数不要超过10行代码,且不能包含循环、switch、if语句
2、在一个c文件中定义的inline函数是不能在其它c文件中直接使用,google推荐把inline函数定义在**-inl.h头文件中。
3、不要过度使用inline函数定义,尤其对大函数来说


上面三点说明如何正确的使用inline函数,我以前的时候对inline理解不透彻,使用inline的方式不正确,但现在编译器够先进,能保证错误的inline定义也可以正确编译、运行。可能会有性能的缺失。

我在F14(gcc 版本 4.5.1 20100924 (Red Hat 4.5.1-4) (GCC))上做了个实验:

1、当inline函数超过10行,并且包含了循环、switch语句后gcc会执行inline语义,将inline函数汇编嵌入到main函数中。
//gcc n.c -O2 -S
#include <stdio.h>
#include <string.h>

//inline int inc_inline(volatile int *j);
inline int inc_inline(volatile int *j)
{
    for (;*j < 100; (*j)++)
    {
        *j += 2;
        (*j)++;
    }
    switch (*j)
    {
    case 1:
        (*j)++;
        break;
    case 2:
        (*j)++;
        break;
    default:
        break;
    }
   
    return (*j)++;
}


int main(int argc, char *argv[])
{
    volatile int i = 0;
   
    inc_inline(&i);

    printf("i;%d\n", i);
   
    return 0;
}


2、当我把inline函数的定义放到另一个c文件中,在main函数文件中声明此函数,此时inline函数不生效,gcc编译出的汇编使用call进行正常的函数调用。
//gcc n.c a.c -O2 -S


3、当我们过度使用inline函数,会造成程序文件变大,性能降低。程序文件变大是肯定的,但为什么性能会降低呢,inline不是为了提高性能吗?使用的方式不正确性能不能提高,反而会下降。现在的CPU上都有cache,紧凑的代码在chache中保存的时间更长,这样cache命中的机会更高。
如果某个A函数未定义为inline,并且被很多其它函数调用,那个这个A函数很大的可能会长期被保存在cahe中,这样CPU对代码的执行速度会提高很多。如果A函数被定义为了inline函数,代码分散各个调用函数中,这样每次指定都不命中都需要去内存把代码拷贝到cache中,然后执行,造成很大的抖动。



更深一层的理解,当函数整个函数编译为的汇编代码,函数调用的上下文切换占用了大多的时间的时候,可以考虑把此函数定义为inline函数。


2011年2月25日星期五

SVN服务器的搭建

SVN是一种版本控制系统,工作需要自己搭建了一台SVN服务器,在这里总结一下。我使用的是F8操作系统,安装完成后,自带有SVN服务器。主要说一下配置过程。

1、创建项目目录
mkdir /svn
2、创建SVN项目
svnadmin create –fs-type fsfs /svn
命令执行完后,会在/svn下创建一个SVN项目,/svn目录下包含conf、dav、db、hooks、locks目录以及format、README.txt文件。
3、将最初代码导入到项目中
svn import /root/svn file:///svn –message “import initial code”
本命令将/root/svn目录下所有的文件和目录导入到/svn这个项目中,注释为message内容。本中方法是在服务器机器上直接操作,也可以使用SVN客户端工具将客户端机器上的代码发到SVN服务器上。
svn import –m “” /root/svn svn://10.50.10.233/trunk
这样/root/svn目录下的所有代码,会被上传到SVN服务器的本项目的trunk目录下。
4、修改SVN服务器配置文件
修改svnserve.conf:将anon-access、auth-access注释去掉,并将内容改为none、write;password-db为密码文件路径、authz-db为权限文件路径,修改为本项目的配置文件路径,我这里的路径为/svn/conf/passwd和/svn/conf/authz。
修改/svn/conf/passwd:添加用户,如test1 = 111111。用户名 = 密码格式。
修改/svn/conf/authz:按照示例,在[groups]中添加组合本组的成员,然后添加访问目录和那些组可以访问这个目录。
5、将svnserve程序设置为开机启动。
将/usr/bin/svnserve –d –r /svn 添加到/etc/rc.local文件最后。

到这里配置就完成了。我们可以使用SVN客户端软件来测试一下。


SVN主干(trunk)、分支(branches)、标记(tags)的意义
三个词的含义是我们认为加给SVN服务器的,在SVN服务器的程序设计中并没有这些概念。
主干:代码的主线,可读可修改。
分支:开发中在某一点遇到了不同或临时的需求,就建立一个分支,同时在主干和分支上做开发,分支可以由多个,是可读可修改。
标记:标记是代码进程中的一个镜像。如在某个时刻,项目完成了一个里程碑,这时在SVN服务器上打一个标记,给一个名字。以后就可以获取这个里程碑时的代码了。标记是可读不可修改的。
使用svn cp主干路径 分支或标记路径 -m命令即可建立分支和标记

2011年2月12日星期六

man手册数字的意义

1、Standard commands (标准命令)
2、System calls (系统调用)
3、Library functions (库函数)
4、Special devices (设备说明)
5、File formats (文件格式)
6、Games and toys (游戏和娱乐)
7、Miscellaneous (杂项)
8、Administrative Commands (管理员命令)
***************************************************************************

1 用户命令, 可由任何人启动的。

2 系统调用, 即由内核提供的函数。

3 例程, 即库函数。

4 设备, 即/dev目录下的特殊文件。

5 文件格式描述, 例如/etc/passwd。

6 游戏, 不用解释啦!

7 杂项, 例如宏命令包、惯例等。

8 系统管理员工具, 只能由root启动。

9 其他(Linux特定的), 用来存放内核例行程序的文档。

转自:
http://hi.baidu.com/bihailan/blog/item/c9af7813b608a4095baf538c.html

2010年12月7日星期二

log4c库使用和配置文件的写法

1、log4c_category_get函数
     get特征,可从xml中获得多个category,如:
     (1)搜six13log.log.app.application1会包含xml中有的six13log.log.app.application1、six13log.log.app、six13log.log等category,以.为分隔符号,向上包含。
     (2)搜six13log.log.app,并不会包含six13log.log.app.application1,也就是不向下包含。

2、log4c有自己的文件输出方式,可指定目录、文件个数、文件大小、文件名前缀、输出方式等。如果大小完了,log4c会删除全部,然后重新开始写。

3、用户可以写自己的append和format。官网的程序包中有两个例子,这样我们可以将日志输出到syslog和socket中。

4、const log4c_location_info_t locinfo = LOG4C_LOCATION_INFO_INITIALIZER(NULL); 来定义日志中的函数名、行数信息。

log4c配置文件:

2010年12月6日星期一

linux动态链接库使用方法

linux环境下动态库的默认搜索路径是/lib和/usr/lib。动态库创建后一般被放到这两个目录中,当然也可以放到我们自己建立的目录中,这时我们就需要做一些其它操作,有三种方法可实现:

1、修改/etc/ld.so.conf文件,将你自己放动态库的路径加入这个文件,运行命令ldconfig然系统从新读取配置。

2、修改LD_LIBRARY_PATH环境变量,将你自己放动态库的路径加入这个环境变量。
export LD_LIBRARY_PATH=LD_LIBRARY_PATH:/root/test/env/lib

3、编译程序时指定程序动态库搜索路径,gcc -Wl,-rpath,./

linux系统动态库搜索顺序:编译指定路径-->LD_LIBRARY_PATH路径-->/etc/ld.so.conf路径-->/lib目录-->/usr/lib目录

2010年11月15日星期一

Quoted-printable邮件编码及编码实现

    Quoted-printable是众多邮件编码协议中的一种,与BASE64作用相同。为了表示传统的NVT ASCII不能表示的字符(128-254)。对于大多可读的ASCII不进行编码,所以QP编码有很多的部分是可读的。
    编码规则如下:

  1. ASCII中,[33,126]之间除了'=',在QP中都可以直接显示无需编码。0-32 + '=' + 127-254这些字符是需要进行QP编码的。
  2. 编码方法:如空格在ASCII中用0x20十六进制表示,QP编码将其十六进制用字符表示,在其前加'='号,为=20。QP编码中,'='符号后必须为大写。
  3. 普通文本中有'\r\n'换行符号,在QP中,编码为=0D=0A,但不换行。
  4. QP编码每行的长度限制为最多76个字符,到达后需要添加QP中的软换行(=\r\n),直接将软换行加入QP编码中,其中'='算在76字符内,而'\r\n'不算。
  5. QP编码的所有行不能以空格或TAB结尾,必须为软换行(=\r\n)或可打印的字符。
  6. 代码中最好加入异常处理:QP编码不为大写,结尾为空格等。

2010年11月12日星期五

Linux启动过程之inittab脚本流程分析

系统从我们按下计算机的电源键到机器可以使用,执行流程如下:
power on–>BIOS–>Grub–>Kernerl boot–>init(rc.sysinit, rc)–>mingetty(login)–>Shell—->
Init进程进程号为1,是linux内核引导完之后运行的第一个进程,init进程运行初会去读取配置文件/etc/inittab。Inittab是一个不可执行的文本文件,由若干指令组成。这里总结一下init进程开始的系统脚本执行顺序,以及各个脚本的含义。
脚本/etc/inittab中所有的记录都以以下的格式呈现:
id:runlevel:action:process
id是入口标示符号,为一个字符串,对于getty或mingetty等login程序项,要求id与tty的编号相同,否则getty程序将不能正常工作。
runlevel是级别标志,init进程启动时会有一个运行级别,/etc/inittab脚本中和init运行级别匹配的记录会被执行。
Action表述后面的process的运行方式。action的取值包括initdefault、sysinit、boot 、bootwait。
Initdefalut是一个特殊的action值,用于标示缺省的启动级别。当init进程启动后会去读取initdefault的值,如果没有此值,则会在控制台请求用户输入runlevel值。
Process为具体的执行程序,程序可以带参数。
脚本中记录的含义:
id:5:initdefault:
用于指定当前的runlevel,5标示运行于界面模式。
si::sysinit:/etc/rc.d/rc.sysinit
此脚本主要用于完成系统初始化工作,它是每一个级别都要首先运行的重要脚本。rc.sysinit程序执行完后,将返回init继续执行。
l2:2:wait:/etc/rc.d/rc2
各守护进程的启动,rc2为一个shell脚本,在rc2中回去循环执行/etc/rc.d/rc2.d目录下的所有脚本,这些脚本一般都是符号链接,链接到别的位置文件,这些脚本一般都会接受start、stop、restart、status等参数。
1:2345:respawn:/sbin/getty 38400 tty1
用于启动终端

linux系统下栈大小的计算

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
#include <stdio.h>
 
static int i = 0;
static int c = 0;
int r1 = 0, r2 = 0;
 
static void
func_stack()
{
 
    int ret;
    /* 输出函数的%ebp的地址
     */
    if (c < 2) {
 asm("movl %%ebp, %%eax; movl %%eax, %0;"
     :"=r" (ret)
     :
     :"%eax");
 
 if (c == 0) {
     r1 = ret;
 } else if (c == 1) {
     r2 = ret;
 }
 c++;
    }
 
 
    while (1) {
 printf("i=%d stack:%d all:%ld\n", i, r1 - r2, (long )(r1-r2)*i);
 i++;
 func_stack();
    }
}
 
int
main(void)
{
    func_stack();
    return 0;
}
代码计算出的数字和ulimit -a得出的结果相同。8M