免费注册 查看新帖 |

Chinaunix

  平台 论坛 博客 文库
最近访问板块 发新帖
楼主: compare2000
打印 上一主题 下一主题

linux git常见命令整理 [复制链接]

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
51 [报告]
发表于 2014-05-24 00:17 |只看该作者
Linux Shell常用技巧(九) 系统运行进程    1.  进程监控命令(ps):
    要对进程进行监测和控制,首先必须要了解当前进程的情况,也就是需要查看当前进程,而ps命令就是最基本同时也是非常强大的进程查看命令。使用该命令可以 确定有哪些进程正在运行和运行的状态、进程是否结束、进程有没有僵死、哪些进程占用了过多的资源等等。总之大部分信息都是可以通过执行该命令得到的。
    ps命令存在很多的命令行选项和参数,然而我们最为常用只有两种形式,这里先给出与它们相关的选项和参数的含义:选项 说明
a 显示终端上的所有进程,包括其他用户的进程。
u 以用户为主的格式来显示程序状况。
x 显示所有程序,不以终端来区分。
-e 显示所有进程。
o 其后指定要输出的列,如user,pid等,多个列之间用逗号分隔。
-p 后面跟着一组pid的列表,用逗号分隔,该命令将只是输出这些pid的相关数据。

    /> ps aux

    root         1  0.0  0.1   2828  1400 ?        Ss   09:51   0:02 /sbin/init
    root         2  0.0  0.0      0          0 ?        S    09:51   0:00 [kthreadd]
    root         3  0.0  0.0      0          0 ?        S    09:51   0:00 [migration/0]
    ... ...   
    /> ps -eo user,pid,%cpu,%mem,start,time,command | head -n 4
    USER       PID %CPU %MEM  STARTED     TIME        COMMAND
    root         1         0.0    0.1   09:51:08     00:00:02  /sbin/init
    root         2         0.0    0.0   09:51:08     00:00:00  [kthreadd]
    root         3         0.0    0.0   09:51:08     00:00:00  [migration/0]
    这里需要说明的是,ps中存在很多和进程性能相关的参数,它们均以输出表格中的列的方式显示出来,在这里我们只是给出了非常常用的几个参数,至于更多参数,我们则需要根据自己应用的实际情况去看ps的man手册。
    #以完整的格式显示pid为1(init)的进程的相关数据
    /> ps -fp 1
    UID        PID  PPID  C STIME TTY          TIME   CMD
    root         1        0  0 05:16   ?        00:00:03 /sbin/init
   
    2.  改变进程优先级的命令(nice和renice):
    该Shell命令最常用的使用方式为:nice [-n <优先等级>][执行指令],其中优先等级的范围从-20-19,其中-20最高,19最低,只有系统管理者可以设置负数的等级。
    #后台执行sleep 100秒,同时在启动时将其nice值置为19
    /> nice -n 19 sleep 100 &
    [1] 4661
    #后台执行sleep 100秒,同时在启动时将其nice值置为-19
    /> nice -n -19 sleep 100 &
    [2] 4664
    #关注ps -l输出中用黄色高亮的两行,它们的NI值和我们执行是设置的值一致。
    /> ps -l
    F S   UID   PID  PPID  C PRI  NI  ADDR  SZ    WCHAN  TTY       TIME        CMD
    4 S     0  2833  2829  0  80   0     -      1739     -         pts/2    00:00:00  bash
    0 S     0  4661  2833  0  99  19    -      1066     -         pts/2    00:00:00  sleep
    4 S     0  4664  2833  0  61 -19    -      1066     -         pts/2    00:00:00  sleep
    4 R     0  4665  2833  1  80   0     -      1231     -         pts/2    00:00:00  ps
   
    renice命令主要用于为已经执行的进程重新设定nice值,该命令包含以下几个常用选项:

选项 说明
-g 使用程序群组名称,修改所有隶属于该程序群组的程序的优先权。
-p 改变该程序的优先权等级,此参数为预设值。
-u 指定用户名称,修改所有隶属于该用户的程序的优先权。

    #切换到stephen用户下执行一个后台进程,这里sleep进程将在后台睡眠1000秒。
    /> su stephen
    /> sleep 1000&  
    [1] 4812
    /> exit   #退回到切换前的root用户
    #查看已经启动的后台sleep进程,其ni值为0,宿主用户为stephen
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812   0 sleep 1000
    root        4821    0 grep  stephen
    #以指定用户的方式修改该用户下所有进程的nice值
    /> renice -n 5 -u stephen
    500: old priority 0, new priority 5
    #从再次执行ps的输出结果可以看出,该sleep后台进程的nice值已经调成了5
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812   5 sleep 1000
    root         4826   0 grep  stephen
    #以指定进程pid的方式修改该进程的nice值
    /> renice -n 10 -p 4812
    4812: old priority 5, new priority 10
    #再次执行ps,该sleep后台进程的nice值已经从5变成了10
    /> ps -eo user,pid,ni,command | grep stephen
    stephen   4812  10 sleep 1000
    root        4829   0 grep  stephen


    3.  列出当前系统打开文件的工具(lsof):
    lsof(list opened files),其重要功能为列举系统中已经被打开的文件,如果没有指定任何选项或参数,lsof则列出所有活动进程打开的所有文件。众所周知,linux环境中任何事物都是文件,如设备、目录、sockets等。所以,用好lsof命令,对日常的linux管理非常有帮助。下面先给出该命令的常用选项:

选项 说明
-a 该选项会使后面选项选出的结果列表进行and操作。
-c command_prefix 显示以command_prefix开头的进程打开的文件。
-p PID 显示指定PID已打开文件的信息
+d directory 从文件夹directory来搜寻(不考虑子目录),列出该目录下打开的文件信息。
+D directory 从文件夹directory来搜寻(考虑子目录),列出该目录下打开的文件信息。
-d num_of_fd 以File Descriptor的信息进行匹配,可使用3-10,表示范围,3,10表示某些值。
-u user 显示某用户的已经打开的文件,其中user可以使用正则表达式。
-i 监听指定的协议、端口、主机等的网络信息,格式为:[proto][@host|addr][:svc_list|port_list]

    #查看打开/dev/null文件的进程。
    /> lsof /dev/null | head -n 5
    COMMAND    PID      USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    init         1      root    0u   CHR    1,3      0t0 3671 /dev/null
    init         1      root    1u   CHR    1,3      0t0 3671 /dev/null
    init         1      root    2u   CHR    1,3      0t0 3671 /dev/null
    udevd 397      root    0u   CHR    1,3      0t0 3671 /dev/null

    #查看打开22端口的进程
    /> lsof -i:22
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    sshd    1582 root    3u  IPv4  11989      0t0  TCP *:ssh (LISTEN)
    sshd    1582 root    4u  IPv6  11991      0t0  TCP *:ssh (LISTEN)
    sshd    2829 root    3r   IPv4  19635      0t0  TCP bogon:ssh->bogon:15264 (ESTABLISHED)

    #查看init进程打开的文件
    />  lsof -c init
    COMMAND PID USER   FD   TYPE     DEVICE   SIZE/OFF   NODE    NAME
    init               1 root  cwd      DIR        8,2     4096              2        /
    init               1 root  rtd       DIR        8,2     4096              2        /
    init               1 root  txt       REG       8,2   136068       148567     /sbin/init
    init               1 root  mem    REG        8,2    58536      137507     /lib/libnss_files-2.12.so
    init               1 root  mem    REG        8,2   122232     186675     /lib/libgcc_s-4.4.4-20100726.so.1
    init               1 root  mem    REG        8,2   141492     186436     /lib/ld-2.12.so
    init               1 root  mem    REG        8,2  1855584    186631     /lib/libc-2.12.so
    init               1 root  mem    REG        8,2   133136     186632     /lib/libpthread-2.12.so
    init               1 root  mem    REG        8,2    99020      180422     /lib/libnih.so.1.0.0
    init               1 root  mem    REG        8,2    37304      186773     /lib/libnih-dbus.so.1.0.0
    init               1 root  mem    REG        8,2    41728      186633     /lib/librt-2.12.so
    init               1 root  mem    REG        8,2   286380     186634     /lib/libdbus-1.so.3.4.0
    init               1 root    0u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    1u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    2u     CHR        1,3      0t0           3671      /dev/null
    init               1 root    3r      FIFO       0,8      0t0           7969      pipe
    init               1 root    4w     FIFO       0,8      0t0           7969      pipe
    init               1 root    5r      DIR        0,10        0             1         inotify
    init               1 root    6r      DIR        0,10        0             1         inotify
    init               1 root    7u     unix   0xf61e3840  0t0       7970      socket
    init               1 root    9u     unix   0xf3bab280  0t0      11211     socket
    在上面输出的FD列中,显示的是文件的File Descriptor number,或者如下的内容:
    cwd:  current working directory;
    mem:  memory-mapped file;
    mmap: memory-mapped device;
    pd:   parent directory;
    rtd:  root directory;
    txt:  program text (code and data);
    文件的File Descriptor number显示模式有:
    r for read access;
    w for write access;
    u for read and write access;

    在上面输出的TYPE列中,显示的是文件类型,如:
    DIR:  目录
    LINK: 链接文件
    REG:  普通文件


    #查看pid为1的进程(init)打开的文件,其输出结果等同于上面的命令,他们都是init。
    /> lsof -p 1
    #查看owner为root的进程打开的文件。
    /> lsof -u root
    #查看owner不为root的进程打开的文件。
    /> lsof -u ^root
    #查看打开协议为tcp,ip为192.168.220.134,端口为22的进程。
    /> lsof -i tcp@192.168.220.134:22
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF NODE NAME
    sshd        2829 root     3r    IPv4  19635      0t0      TCP    bogon:ssh->bogon:15264 (ESTABLISHED)   
    #查看打开/root文件夹,但不考虑目录搜寻
    /> lsof +d /root
    #查看打开/root文件夹以及其子目录搜寻
    /> lsof +D /root
    #查看打开FD(0-3)文件的所有进程
    /> lsof -d 0-3
    #-a选项会将+d选项和-c选项的选择结果进行and操作,并输出合并后的结果。
    /> lsof +d .
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
    bash       9707  root  cwd    DIR    8,1     4096         39887 .
    lsof         9791  root  cwd    DIR    8,1     4096         39887 .
    lsof         9792  root  cwd    DIR    8,1     4096         39887 .
    /> lsof -a -c bash +d .
    COMMAND  PID USER   FD   TYPE DEVICE SIZE/OFF  NODE NAME
    bash        9707 root  cwd    DIR    8,1     4096         39887 .
    最后需要额外说明的是,如果在文件名的末尾存在(delete),则说明该文件已经被删除,只是还存留在cache中。


    4.  进程查找/杀掉命令(pgrep/pkill):
    查找和杀死指定的进程, 他们的选项和参数完全相同, 这里只是介绍pgrep。下面是常用的命令行选项:

选项 说明
-d 定义多个进程之间的分隔符, 如果不定义则使用换行符。
-n 表示如果该程序有多个进程正在运行,则仅查找最新的,即最后启动的。
-o 表示如果该程序有多个进程正在运行,则仅查找最老的,即最先启动的。
-G 其后跟着一组group id,该命令在搜索时,仅考虑group列表中的进程。
-u 其后跟着一组有效用户ID(effetive user id),该命令在搜索时,仅考虑该effective user列表中的进程。
-U 其后跟着一组实际用户ID(real user id),该命令在搜索时,仅考虑该real user列表中的进程。
-x 表示进程的名字必须完全匹配, 以上的选项均可以部分匹配。
-l 将不仅打印pid,也打印进程名。
-f 一般与-l合用, 将打印进程的参数。
    #手工创建两个后台进程
    /> sleep 1000&
    3456
    /> sleep 1000&
    3457

    #查找进程名为sleep的进程,同时输出所有找到的pid
    /> pgrep sleep
    3456
    3457
    #查找进程名为sleep的进程pid,如果存在多个,他们之间使用:分隔,而不是换行符分隔。
    /> pgrep -d: sleep
    3456:3457
    #查找进程名为sleep的进程pid,如果存在多个,这里只是输出最后启动的那一个。
    /> pgrep -n sleep
    3457
    #查找进程名为sleep的进程pid,如果存在多个,这里只是输出最先启动的那一个。
    /> pgrep -o  sleep
    3456
    #查找进程名为sleep,同时这个正在运行的进程的组为root和stephen。
    /> pgrep -G root,stephen sleep
    3456
    3457
    #查找有效用户ID为root和oracle,进程名为sleep的进程。
    /> pgrep -u root,oracle sleep
    3456
    3457
    #查找实际用户ID为root和oracle,进程名为sleep的进程。
    /> pgrep -U root,oracle sleep
    3456
    3457
    #查找进程名为sleep的进程,注意这里找到的进程名必须和参数中的完全匹配。
    /> pgrep -x sleep
    3456
    3457
    #-x不支持部分匹配,sleep进程将不会被查出,因此下面的命令没有结果。
    /> pgrep -x sle
    #查找进程名为sleep的进程,同时输出所有找到的pid和进程名。   
    /> pgrep -l sleep
    3456 sleep
    3457 sleep
    #查找进程名为sleep的进程,同时输出所有找到的pid、进程名和启动时的参数。
    /> pgrep -lf sleep
    3456 sleep 1000
    3457 sleep 1000
    #查找进程名为sleep的进程,同时以逗号为分隔符输出他们的pid,在将结果传给ps命令,-f表示显示完整格式,-p显示pid列表,ps将只是输出该列表内的进程数据。
    /> pgrep -f sleep -d, | xargs ps -fp
    UID        PID  PPID  C STIME TTY          TIME CMD
    root      3456  2138  0 06:11 pts/5    00:00:00 sleep 1000
    root      3457  2138  0 06:11 pts/5    00:00:00 sleep 1000

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
52 [报告]
发表于 2014-05-26 09:45 |只看该作者
细说Linux系统优化-实践篇 .  作为一名linux系统管理员,最主要的工作是优化系统配置,使应用在系统上以最优的状态运行,但是由于硬件问题、软件问题、网络环境等的复杂性 和多变性,导致对系统的优化变得异常复杂,如何定位性能问题出在哪个方面,是性能优化的一大难题, 本章从系统入手,重点讲述由于系统软、硬件配置不当可能造成的性能问题,并且给出了检测系统故障和优化性能的一般方法和流程。
1 cpu性能评估
Cpu是影响Linux性能的主要因素之一,下面先介绍几个查看CPU性能的命令。
1.1 vmstat命令
该命令可以显示关于系统各种资源之间相关性能的简要信息,这里我们主要用它来看CPU的一个负载情况。
下面是vmstat命令在某个系统的输出结果:


点击(此处)折叠或打开
1.[root@node1 ~]# vmstat 2 3
2.procs -----------memory---------- ---swap-- -----io---- --system-- -----cpu------
3.r b swpd free buff cache si so bi bo in cs us sy id wa st
4.0 0 0 162240 8304 67032 0 0 13 21 1007 23 0 1 98 0 0
5.0 0 0 162240 8304 67032 0 0 1 0 1010 20 0 1 100 0 0
6.0 0 0 162240 8304 67032 0 0 1 1 1009 18 0 1 99 0 0

对上面每项的输出解释如下:
? procs
? r列表示运行和等待cpu时间片的进程数,这个值如果长期大于系统CPU的个数,说明CPU不足,需要增加CPU。
? b列表示在等待资源的进程数,比如正在等待I/O、或者内存交换等。
? memory
? swpd列表示切换到内存交换区的内存数量(以k为单位)。如果swpd的值不为0,或者比较大,只要si、so的值长期为0,这种情况下一般不用担心,不会影响系统性能。
? free列表示当前空闲的物理内存数量(以k为单位)
? buff列表示buffers cache的内存数量,一般对块设备的读写才需要缓冲。
? cache列表示page cached的内存数量,一般作为文件系统cached,频繁访问的文件都会被cached,如果cache值较大,说明cached的文件数较多,如果此时IO中bi比较小,说明文件系统效率比较好。
? swap
? si列表示由磁盘调入内存,也就是内存进入内存交换区的数量。
? so列表示由内存调入磁盘,也就是内存交换区进入内存的数量。
一般情况下,si、so的值都为0,如果si、so的值长期不为0,则表示系统内存不足。需要增加系统内存。
? IO项显示磁盘读写状况
? Bi列表示从块设备读入数据的总量(即读磁盘)(每秒kb)。
? Bo列表示写入到块设备的数据总量(即写磁盘)(每秒kb)
这里我们设置的bi+bo参考值为1000,如果超过1000,而且wa值较大,则表示系统磁盘IO有问题,应该考虑提高磁盘的读写性能。
? system 显示采集间隔内发生的中断数
? in列表示在某一时间间隔中观测到的每秒设备中断数。
? cs列表示每秒产生的上下文切换次数。
上面这2个值越大,会看到由内核消耗的CPU时间会越多。
? CPU项显示了CPU的使用状态,此列是我们关注的重点。
? us列显示了用户进程消耗的CPU 时间百分比。us的值比较高时,说明用户进程消耗的cpu时间多,但是如果长期大于50%,就需要考虑优化程序或算法。
? sy列显示了内核进程消耗的CPU时间百分比。Sy的值较高时,说明内核消耗的CPU资源很多。
根据经验,us+sy的参考值为80%,如果us+sy大于 80%说明可能存在CPU资源不足。
? id 列显示了CPU处在空闲状态的时间百分比。
? wa列显示了IO等待所占用的CPU时间百分比。wa值越高,说明IO等待越严重,根据经验,wa的参考值为20%,如果wa超过20%,说明IO等待严重,引起IO等待的原因可能是磁盘大量随机读写造成的,也可能是磁盘或者磁盘控制器的带宽瓶颈造成的(主要是块操作)。
综上所述,在对CPU的评估中,需要重点注意的是procs项r列的值和CPU项中us、sy和id列的值。
1.2  sar命令
检查CPU性能的第二个工具是sar,sar功能很强大,可以对系统的每个方面进行单独的统计,但是使用sar命令会增加系统开销,不过这些开销是可以评估的,对系统的统计结果不会有很大影响。
下面是sar命令对某个系统的CPU统计输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -u 3 5
2.Linux 2.6.9-42.ELsmp (webserver) 11/28/2008 _i686_ (8 CPU)
3.11:41:24 AM CPU %user %nice %system %iowait %steal %idle
4.11:41:27 AM all 0.88 0.00 0.29 0.00 0.00 98.83
5.11:41:30 AM all 0.13 0.00 0.17 0.21 0.00 99.50
6.11:41:33 AM all 0.04 0.00 0.04 0.00 0.00 99.92
7.11:41:36 AM all 0.29 0.00 0.13 0.00 0.00 99.58
8.11:41:39 AM all 0.38 0.00 0.17 0.04 0.00 99.41
9.Average: all 0.34 0.00 0.16 0.05 0.00 99.45

对上面每项的输出解释如下:
? %user列显示了用户进程消耗的CPU 时间百分比。
? %nice列显示了运行正常进程所消耗的CPU 时间百分比。
? %system列显示了系统进程消耗的CPU时间百分比。
? %iowait列显示了IO等待所占用的CPU时间百分比
? %steal列显示了在内存相对紧张的环境下pagein强制对不同的页面进行的steal操作 。
? %idle列显示了CPU处在空闲状态的时间百分比。
这个输出是对系统整体CPU使用状况的统计,每项的输出都非常直观,并且最后一行Average是个汇总行,是上面统计信息的一个平均值。
需要注意的一点是:第一行的统计信息中包含了sar本身的统计消耗,所以%user列的值会偏高一点,不过,这不会对统计结果产生多大影响。
在一个多CPU的系统中,如果程序使用了单线程,会出现这么一个现象,CPU的整体使用率不高,但是系统应用却响应缓慢,这可能是由于程序使用单线程的原因,单线程只使用一个CPU,导致这个CPU占用率为100%,无法处理其它请求,而其它的CPU却闲置,这就导致 了整体CPU使用率不高,而应用缓慢 现象的发生 。
针对这个问题,可以对系统的每个CPU分开查询,统计每个CPU的使用情况:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -P 0 3 5
2.Linux 2.6.9-42.ELsmp (webserver) 11/29/2008 _i686_ (8 CPU)
3.06:29:33 PM CPU %user %nice %system %iowait %steal %idle
4.06:29:36 PM 0 3.00 0.00 0.33 0.00 0.00 96.67
5.06:29:39 PM 0 0.67 0.00 0.33 0.00 0.00 99.00
6.06:29:42 PM 0 0.00 0.00 0.33 0.00 0.00 99.67
7.06:29:45 PM 0 0.67 0.00 0.33 0.00 0.00 99.00
8.06:29:48 PM 0 1.00 0.00 0.33 0.33 0.00 98.34
9.Average: 0 1.07 0.00 0.33 0.07 0.00 98.53
这个输出是对系统的第一颗CPU的信息统计,需要注意的是,sar中对CPU的计数是从0开始的,因此,“sar -P 0 3 5”表示对系统的第一颗CPU进行信息统计,“sar -P 4 3 5”则表示对系统的第五颗CPU进行统计。依次类推。可以看出,上面的系统有八颗CPU。
1.3 iostat命令
iostat指令主要用于统计磁盘IO状态,但是也能查看CPU的使用信息,它的局限性是只能显示系统所有CPU的平均信息,看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# iostat -c
2.Linux 2.6.9-42.ELsmp (webserver) 11/29/2008 _i686_ (8 CPU)
3.avg-cpu: %user %nice %system %iowait %steal %idle
4.2.52 0.00 0.30 0.24 0.00 96.96
在这里,我们使用了“-c”参数,只显示系统CPU的统计信息,输出中每项代表的含义与sar命令的输出项完全相同,不再详述。
1.4 uptime命令
uptime是监控系统性能最常用的一个命令,主要用来统计系统当前的运行状况,输出的信息依次为:系统现在的时间、系统从上次开机到现在运行了多长时间、系统目前有多少登陆用户、系统在一分钟内、五分钟内、十五分钟内的平均负载。看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# uptime
2.18:52:11 up 27 days, 19:44, 2 users, load average: 0.12, 0.08, 0.08

这里需要注意的是load average这个输出值,这三个值的大小一般不能大于系统CPU的个数,例如,本输出中系统有8个CPU,如果load average的三个值长期大于8时,说明CPU很繁忙,负载很高,可能会影响系统性能,但是偶尔大于8时,倒不用担心,一般不会影响系统性能。相反,如果load average的输出值小于CPU的个数,则表示CPU还有空闲的时间片,比如本例中的输出,CPU是非常空闲的。
1.5 本节小结
上面介绍了检查CPU使用状况的四个命令,通过这些命令需要了解的是:系统CPU是否出现性能瓶颈,也就是说,以上这些命令只能查看CPU是否繁忙,负载是否过大,但是无法知道CPU为何负载过大,因而,判断系统CPU出现问题后,要结合top、ps等命令进一步检查是由那些进程导致CPU负载过大的。引起CPU资源紧缺的原因可能是应用程序不合理造成的,也可能是硬件资源匮乏引起的,所以,要具体问题具体分析,或者优化应用程序,或者增加系统CPU资源。
2 内存性能评估
内存的管理和优化是系统性能优化的一个重要部分,内存资源的充足与否直接影响应用系统的使用性能,在进行内存优化之前,一定要熟悉linux的内存管理机制,这一点我们在前面的章节已经有深入讲述,本节的重点是如何通过系统命令监控linux系统的内存使用状况。
2.1 free 命令
free是监控linux内存使用状况最常用的指令,看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# free -m
2.total used free shared buffers cached
3.Mem: 8111 7185 925 0 243 6299
4.-/+ buffers/cache: 643 7468
5.Swap: 8189 0 8189
  “free –m”表示以M为单位查看内存使用情况,在这个输出中,我们重点关注的应该是free列与cached列的输出值,由输出可知,此系统共8G内存,系统空闲内存还有925M,其中,Buffer Cache占用了243M,Page Cache占用了6299M,由此可知系统缓存了很多的文件和目录,而对于应用程序来说,可以使用的内存还有7468M,当然这个7468M包含了Buffer Cache和Page Cache的值。在swap项可以看出,交换分区还未使用。所以从应用的角度来说,此系统内存资源还非常充足。
  一般有这样一个经验公式:应用程序可用内存/系统物理内存>70%时,表示系统内存资源非常充足,不影响系统性能,应用程序可用内存/系统物理内存<20%时,表示系统内存资源紧缺,需要增加系统内存,20%<应用程序可用内存/系统物理内存<70%时,表示系统内存资源基本能满足应用需求,暂时不影响系统性能。
  free命令还可以适时的监控内存的使用状况,使用“-s”参数可以在指定的时间段内不间断的监控内存的使用情况:


点击(此处)折叠或打开
1.[root@webserver ~]# free -b -s 5
2.total used free shared buffers cached
3.Mem: 8505901056 7528706048 977195008 0 260112384 6601158656
4.-/+ buffers/cache: 667435008 7838466048
5.Swap: 8587149312 163840 8586985472
6.total used free shared buffers cached
7.Mem: 8505901056 7526936576 978964480 0 260128768 6601142272
8.-/+ buffers/cache: 665665536 7840235520
9.Swap: 8587149312 163840 8586985472
10.total used free shared buffers cached
11.Mem: 8505901056 7523987456 981913600 0 260141056 6601129984
12.-/+ buffers/cache: 662716416 7843184640
13.Swap: 8587149312 163840 8586985472
  其中,“-b”表示以千字节(也就是1024字节为单位)来显示内存使用情况。
2.2 通过watch与free相结合动态监控内存状况
watch是一个非常有用的命令,几乎每个linux发行版都带有这个工具,通过watch,可以动态的监控命令的运行结果,省去手动执行的麻烦。
  可以在watch后面跟上需要运行的命令,watch就会自动重复去运行这个命令,默认是2秒钟执行一次,并把执行的结果更新在屏幕上。例如:


点击(此处)折叠或打开
1.[root@webserver ~]# watch -n 3 -d free
2.Every 3.0s: free Sun Nov 30 16:23:20 2008
3.total used free shared buffers cached
4.Mem: 8306544 7349548 956996 0 203296 6500024
5.-/+ buffers/cache: 646228 7660316
6.Swap: 8385888 160 8385728

其中,“-n”指定重复执行的时间,“-d”表示高亮显示变动。
2.3 vmstat命令监控内存
vmstat命令在监控系统内存方面功能强大,请看下面的一个输出:


点击(此处)折叠或打开
1.procs -----------memory---------- ---swap-- -----io---- --system-- ----cpu----
2.r b swpd free buff cache si so bi bo in cs us sy id wa
3.0 0 906440 22796 155616 1325496 340 180 2 4 1 4 80 0 10 10
4.0 0 906440 42796 155616 1325496 320 289 0 54 1095 287 70 15 0 15
5.0 0 906440 42884 155624 1325748 236 387 2 102 1064 276 78 2 5 15

对于内存的监控,在vmstat中重点关注的是swpd、si和so行,从这个输出可以看出,此系统内存资源紧缺,swpd占用了900M左右内存,si和so占用很大,而由于系统内存的紧缺,导致出现15%左右的系统等待,此时增加系统的内存是必须要做的。
2.4 sar -r命令组合
sar命令也可以监控linux的内存使用状况,可以通过“sar –r”组合查看系统内存和交换空间的使用率。请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -r 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 11/30/2008 _i686_ (8 CPU)
3.09:57:33 PM kbmemfree kbmemused %memused kbbuffers kbcached kbcommit %commit
4.09:57:35 PM 897988 7408556 89.19 249428 6496532 786556 4.71
5.09:57:37 PM 898564 7407980 89.18 249428 6496532 784276 4.70
6.09:57:39 PM 899196 7407348 89.17 249440 6496520 782132 4.69
7.Average: 898583 7407961 89.18 249432 6496528 784321 4.70

其中:
Kbmemfree表示空闲物理内存大小,kbmemused表示已使用的物理内存空间大小,%memused表示已使用内存占总内存大小的百分比,kbbuffers和kbcached分别表示Buffer Cache和Page Cache的大小,kbcommit和%commit分别表示应用程序当前使用的内存大小和使用百分比。
可以看出sar的输出其实与free的输出完全对应,不过sar更加人性化,不但给出了内存使用量,还给出了内存使用的百分比以及统计的平均值。从%commit项可知,此系统目前内存资源充足。
2.5 本节小结
上面介绍了内存监控常用的几个指令以及一些经验规则,其实现在的系统在内存方面出现的瓶颈已经很少,因为内存价格很低,充足的内存已经完全能满足应用程序和系统本身的需要,如果系统在内存方面出现瓶颈,很大的可能是应用程序本身的问题造成的。
3 磁盘I/O性能评估
在对磁盘I/O性能做评估之前,必须知道的几个方面是:
? 熟悉RAID存储方式,可以根据应用的不同,选择不同的RAID方式,例如,如果一个应用经常有大量的读操作,可以选择RAID5方式构建磁盘阵列存储数据,如果应用有大量的、频繁的写操作,可以选择raid0存取方式,如果应用对数据安全要求很高,同时对读写也有要求的话,可以考虑raid01存取方式等等。
? 尽可能用内存的读写代替直接磁盘I/O,使频繁访问的文件或数据放入内存中进行操作处理,因为内存读写操作比直接磁盘读写的效率要高千倍。
? 将经常进行读写的文件与长期不变的文件独立出来,分别放置到不同的磁盘设备上。
? 对于写操作频繁的数据,可以考虑使用裸设备代替文件系统。这里简要讲述下文件系统与裸设备的对比:
使用裸设备的优点有:
? 数据可以直接读写,不需要经过操作系统级的缓存,节省了内存资源,避免了内存资源争用。
? 避免了文件系统级的维护开销,比如文件系统需要维护超级块、I-node等。
? 避免了操作系统的cache预读功能,减少了I/O请求。
使用裸设备的缺点是:
? 数据管理、空间管理不灵活,需要很专业的人来操作。
其实裸设备的优点就是文件系统的缺点,反之也是如此,这就需要我们做出合理的规划和衡量,根据应用的需求,做出对应的策略。
下面接着介绍对磁盘IO的评估标准。
3.1 sar -d命令组合
通过“sar –d”组合,可以对系统的磁盘IO做一个基本的统计,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -d 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 11/30/2008 _i686_ (8 CPU)
3.11:09:33 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
4.11:09:35 PM dev8-0 0.00 0.00 0.00 0.00 0.00 0.00 0.00 0.00
5.11:09:35 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
6.11:09:37 PM dev8-0 1.00 0.00 12.00 12.00 0.00 0.00 0.00 0.00
7.11:09:37 PM DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
8.11:09:39 PM dev8-0 1.99 0.00 47.76 24.00 0.00 0.50 0.25 0.05
9.Average: DEV tps rd_sec/s wr_sec/s avgrq-sz avgqu-sz await svctm %util
10.Average: dev8-0 1.00 0.00 19.97 20.00 0.00 0.33 0.17 0.02

对上面每项的输出解释如下:
? DEV表示磁盘设备名称。
? tps表示每秒到物理磁盘的传送数,也就是每秒的I/O流量。一个传送就是一个I/O请求,多个逻辑请求可以被合并为一个物理I/O请求。
? rd_sec/s表示每秒从设备读取的扇区数(1扇区=512字节)。
? wr_sec/s表示每秒写入设备的扇区数目。
? avgrq-sz表示平均每次设备I/O操作的数据大小(以扇区为单位)。
? avgqu-sz表示平均I/O队列长度。
? await表示平均每次设备I/O操作的等待时间(以毫秒为单位)。
? svctm表示平均每次设备I/O操作的服务时间(以毫秒为单位)。
? %util表示一秒中有百分之几的时间用于I/O操作。
Linux中I/O请求系统与现实生活中超市购物排队系统有很多类似的地方,通过对超市购物排队系统的理解,可以很快掌握linux中I/O运行机制。比如:
avgrq-sz类似与超市排队中每人所买东西的多少。
avgqu-sz类似与超市排队中单位时间内平均排队的人数。
await类似与超市排队中每人的等待时间。
svctm类似与超市排队中收银员的收款速度。
%util类似与超市收银台前有人排队的时间比例。
对以磁盘IO性能,一般有如下评判标准:
正常情况下svctm应该是小于await值的,而svctm的大小和磁盘性能有关,CPU、内存的负荷也会对svctm值造成影响,过多的请求也会间接的导致svctm值的增加。
await值的大小一般取决与svctm的值和I/O队列长度以及I/O请求模式,如果svctm的值与await很接近,表示几乎没有I/O等待,磁盘性能很好,如果await的值远高于svctm的值,则表示I/O队列等待太长,系统上运行的应用程序将变慢,此时可以通过更换更快的硬盘来解决问题。
%util项的值也是衡量磁盘I/O的一个重要指标,如果%util接近100%,表示磁盘产生的I/O请求太多,I/O系统已经满负荷的在工作,该磁盘可能存在瓶颈。长期下去,势必影响系统的性能,可以通过优化程序或者通过更换更高、更快的磁盘来解决此问题。
3.2 iostat –d命令组合
通过“iostat –d”命令组合也可以查看系统磁盘的使用状况,请看如下输出:




点击(此处)折叠或打开
1.[root@webserver ~]# iostat -d 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
4.sda 1.87 2.58 114.12 6479462 286537372
5.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
6.sda 0.00 0.00 0.00 0 0
7.Device: tps Blk_read/s Blk_wrtn/s Blk_read Blk_wrtn
8.sda 1.00 0.00 12.00 0 24

对上面每项的输出解释如下:
? Blk_read/s表示每秒读取的数据块数。
? Blk_wrtn/s表示每秒写入的数据块数。
? Blk_read表示读取的所有块数
? Blk_wrtn表示写入的所有块数。
这里需要注意的一点是:上面输出的第一项是系统从启动以来到统计时的所有传输信息,从第二次输出的数据才代表在检测的时间段内系统的传输值。
可以通过Blk_read/s和Blk_wrtn/s的值对磁盘的读写性能有一个基本的了解,如果Blk_wrtn/s值很大,表示磁盘的写操作很频繁,可以考虑优化磁盘或者优化程序,如果Blk_read/s值很大,表示磁盘直接读取操作很多,可以将读取的数据放入内存中进行操作。对于这两个选项的值没有一个固定的大小,根据系统应用的不同,会有不同的值,但是有一个规则还是可以遵循的:长期的、超大的数据读写,肯定是不正常的,这种情况一定会影响系统性能。
“iostat –x”组合还提供了对每个磁盘的单独统计,如果不指定磁盘,默认是对所有磁盘进行统计,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# iostat -x /dev/sda 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.avg-cpu: %user %nice %system %iowait %steal %idle
4.2.45 0.00 0.30 0.24 0.00 97.03
5.Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
6.sda 0.01 12.48 0.10 1.78 2.58 114.03 62.33 0.07 38.39 1.30 0.24
7.avg-cpu: %user %nice %system %iowait %steal %idle
8.3.97 0.00 1.83 8.19 0.00 86.14
9.Device:rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
10.sda 0.00 195.00 0.00 18.00 0.00 1704.00 94.67 0.04 2.50 0.11 0.20
11.avg-cpu: %user %nice %system %iowait %steal %idle
12.4.04 0.00 1.83 8.01 0.00 86.18
13.Device: rrqm/s wrqm/s r/s w/s rsec/s wsec/s avgrq-sz avgqu-sz await svctm %util
14.sda 0.00 4.50 0.00 7.00 0.00 92.00 13.14 0.01 0.79 0.14 0.10

这个输出基本与“sar –d”相同,需要说明的几个选项的含义为:
? rrqm/s表示每秒进行merged的读操作数目。
? wrqm/s表示每秒进行 merge 的写操作数目。
? r/s表示每秒完成读I/O设备的次数。
? w/s表示每秒完成写I/O设备的次数。
? rsec/s表示每秒读取的扇区数。
? wsec/s表示每秒写入的扇区数。
3.3 vmstat –d组合
通过“vmstat –d”组合也可以查看磁盘的统计数据,情况下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# vmstat -d 3 2|grep sda
2.disk- ------------reads------------ ------------writes----------- -----IO------
3.total merged sectors ms total merged sectors ms cur sec
4.sda 239588 29282 6481862 1044442 4538678 32387680 295410812 186025580 0 6179
5.disk- ------------reads------------ ------------writes----------- -----IO------
6.total merged sectors ms total merged sectors ms cur sec
7.sda 239588 29282 6481862 1044442 4538680 32387690 295410908 186025581 0 6179

这个输出显示了磁盘的reads、writes和IO的使用状况。
3.4 本节小结
上面主要讲解了对磁盘I/O的性能评估,其实衡量磁盘I/O好坏是多方面的,有应用程序本身的,也有硬件设计上的,还有系统自身配置的问题等,要解决I/O的瓶颈,关键是要提高I/O子系统的执行效率。例如,首要要从应用程序上对磁盘读写进行优化,能够放到内存执行的操作,尽量不要放到磁盘,同时对磁盘存储方式进行合理规划,选择适合自己的RAID存取方式,最后,在系统级别上,可以选择适合自身应用的文件系统,必要时使用裸设备提高读写性能。
4 网络性能评估
网络性能的好坏直接影响应用程序对外提供服务的稳定性和可靠性,监控网络性能,可以从以下几个方面进行管理和优化。
4.1 通过ping命令检测网络的连通性
如果发现网络反应 缓慢,或者连接中断,可以通过ping来测试网络的连通情况,请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# ping 10.10.1.254
2.PING 10.10.1.254 (10.10.1.254) 56(84) bytes of data.
3.64 bytes from 10.10.1.254: icmp_seq=0 ttl=64 time=0.235 ms
4.64 bytes from 10.10.1.254: icmp_seq=1 ttl=64 time=0.164 ms
5.64 bytes from 10.10.1.254: icmp_seq=2 ttl=64 time=0.210 ms
6.64 bytes from 10.10.1.254: icmp_seq=3 ttl=64 time=0.178 ms
7.64 bytes from 10.10.1.254: icmp_seq=4 ttl=64 time=0.525 ms
8.64 bytes from 10.10.1.254: icmp_seq=5 ttl=64 time=0.571 ms
9.64 bytes from 10.10.1.254: icmp_seq=6 ttl=64 time=0.220 ms
10.--- 10.10.1.254 ping statistics ---
11.7 packets transmitted, 7 received, 0% packet loss, time 6000ms
12.rtt min/avg/max/mdev = 0.164/0.300/0.571/0.159 ms, pipe 2



在这个输出中,time值显示了两台主机之间的网络延时情况,如果此值很大,则表示网络的延时很大,单位为毫秒。在这个输出的最后,是对上面输出信息的一个总结,packet loss表示网络的丢包率,此值越小,表示网络的质量越高。
4.2 通过netstat –i组合检测网络接口状况
netstat命令提供了网络接口的详细信息,请看下面的输出:


点击(此处)折叠或打开
1.[root@webserver ~]# netstat -i
2.Kernel Interface table
3.Iface MTU Met RX-OK RX-ERR RX-DRP RX-OVR TX-OK TX-ERR TX-DRP TX-OVR Flg
4.eth0 1500 0 1313129253 0 0 0 1320686497 0 0 0 BMRU
5.eth1 1500 0 494902025 0 0 0 292358810 0 0 0 BMRU
6.lo 16436 0 41901601 0 0 0 41901601 0 0 0 LRU

对上面每项的输出解释如下:
? Iface表示网络设备的接口名称。
? MTU表示最大传输单元,单位字节。
? RX-OK/TX-OK表示已经准确无误的接收/发送了多少数据包。
? RX-ERR/TX-ERR表示接收/发送数据包时产生了多少错误。
? RX-DRP/TX-DRP表示接收/发送数据包时丢弃了多少数据包。
? RX-OVR/TX-OVR表示由于误差而遗失了多少数据包。
? Flg表示接口标记,其中:
? L:表示该接口是个回环设备。
? B:表示设置了广播地址。
? M:表示接收所有数据包。
? R:表示接口正在运行。
? U:表示接口处于活动状态。
? O:表示在该接口上禁用arp。
? P:表示一个点到点的连接。
正常情况下,RX-ERR/TX-ERR、RX-DRP/TX-DRP和RX-OVR/TX-OVR的值都应该为0,如果这几个选项的值不为0,并且很大,那么网络质量肯定有问题,网络传输性能也一定会下降。
当网络传输存在问题是,可以检测网卡设备是否存在故障,如果可能,可以升级为千兆网卡或者光纤网络,还可以检查网络部署环境是否合理。
4.3 通过netstat –r组合检测系统的路由表信息
在网络不通,或者网络异常时,首先想到的就是检查系统的路由表信息,“netstat –r”的输出结果与route命令的输出完全相同,请看下面的一个实例:


点击(此处)折叠或打开
1.[root@webserver ~]# netstat -r
2.Kernel IP routing table
3.Destination Gateway Genmask Flags MSS Window irtt Iface
4.10.10.1.0 * 255.255.255.0 U 0 0 0 eth0
5.192.168.200.0 * 255.255.255.0 U 0 0 0 eth1
6.169.254.0.0 * 255.255.0.0 U 0 0 0 eth1
7.default 10.10.1.254 0.0.0.0 UG 0 0 0 eth0

关于输出中每项的具体含义,已经在前面章节进行过详细介绍,这里不再多讲,这里我们重点关注的是default行对应的值,default项表示系统的默认路由,对应的网络接口为eth0。

4.4 通过sar –n组合显示系统的网络运行状态
sar提供四种不同的选项来显示网络统计信息,通过“-n”选项可以指定4个不同类型的开关:DEV、EDEV、SOCK和FULL。DEV显示网络接口信息,EDEV显示关于网络错误的统计数据,SOCK显示套接字信息,FULL显示所有三个开关。请看下面的一个输出:


点击(此处)折叠或打开
1.[root@webserver ~]# sar -n DEV 2 3
2.Linux 2.6.9-42.ELsmp (webserver) 12/01/2008 _i686_ (8 CPU)
3.02:22:31 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
4.02:22:33 PM lo 31.34 31.34 37.53 37.53 0.00 0.00 0.00
5.02:22:33 PM eth0 199.50 279.60 17.29 344.12 0.00 0.00 0.00
6.02:22:33 PM eth1 5.47 4.98 7.03 0.36 0.00 0.00 0.00
7.02:22:33 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
8.02:22:33 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
9.02:22:35 PM lo 67.66 67.66 74.34 74.34 0.00 0.00 0.00
10.02:22:35 PM eth0 159.70 222.39 19.74 217.16 0.00 0.00 0.00
11.02:22:35 PM eth1 3.48 4.48 0.44 0.51 0.00 0.00 0.00
12.02:22:35 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
13.02:22:35 PM IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
14.02:22:37 PM lo 4.52 4.52 9.25 9.25 0.00 0.00 0.00
15.02:22:37 PM eth0 102.51 133.67 20.67 116.14 0.00 0.00 0.00
16.02:22:37 PM eth1 27.14 67.34 2.42 89.26 0.00 0.00 0.00
17.02:22:37 PM sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00
18.Average: IFACE rxpck/s txpck/s rxkB/s txkB/s rxcmp/s txcmp/s rxmcst/s
19.Average: lo 34.61 34.61 40.48 40.48 0.00 0.00 0.00
20.Average: eth0 154.08 212.15 19.23 226.17 0.00 0.00 0.00
21.Average: eth1 11.98 25.46 3.30 29.85 0.00 0.00 0.00
22.Average: sit0 0.00 0.00 0.00 0.00 0.00 0.00 0.00

对上面每项的输出解释如下:
? IFACE表示网络接口设备。
? rxpck/s表示每秒钟接收的数据包大小。
? txpck/s表示每秒钟发送的数据包大小。
? rxkB/s表示每秒钟接收的字节数。
? txkB/s表示每秒钟发送的字节数。
? rxcmp/s表示每秒钟接收的压缩数据包。
? txcmp/s表示每秒钟发送的压缩数据包。
? rxmcst/s表示每秒钟接收的多播数据包。
通过“sar –n”的输出,可以清楚的显示网络接口发送、接收数据的统计信息。此外还可以通过“sar -n EDEV 2 3”来统计网络错误信息等。
4.5 小结
本节通过几个常用的网络命令介绍了对网络性能的评估,事实上,网络问题是简单而且容易处理的,只要我们根据上面给出的命令,一般都能迅速定位问题。解决问题的方法一般是增加网络带宽,或者优化网络部署环境。
除了上面介绍的几个命令外,排查网络问题经常用到的命令还有traceroute,主要用于跟踪数据包的传输路径,还有nslookup命令,主要用于判断DNS解析信息。

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
53 [报告]
发表于 2014-05-27 11:11 |只看该作者
本帖最后由 compare2000 于 2014-05-28 11:09 编辑

reset命令有3种方式:


1.git reset –mixed:此为默认方式,不带任何参数的git reset,即时这种方式,它回退到某个版本,只保留源码,回退commit和index信息

2.git reset –soft:回退到某个版本,只回退了commit的信息,不会恢复到index file一级。如果还要提交,直接commit即可

3.git reset –hard:彻底回退到某个版本,本地的源码也会变为上一个版本的内容




以下是一些reset的示例:

(1) 回退所有内容到上一个版本   
git reset HEAD^   
(2) 回退a.py这个文件的版本到上一个版本   
git reset HEAD^ a.py   
(3) 向前回退到第3个版本   
git reset –soft HEAD~3   
(4) 将本地的状态回退到和远程的一样   
git reset –hard origin/master   
(5) 回退到某个版本   
git reset 057d   
(7) 回退到上一次提交的状态,按照某一次的commit完全反向的进行一次commit   
git revert HEAD   



如果我们某次修改了某些内容,并且已经commit到本地仓库,而且已经push到远程仓库了


这种情况下,我们想把本地和远程仓库都回退到某个版本,该怎么做呢?


前面讲到的git reset只是在本地仓库中回退版本,而远程仓库的版本不会变化


这样,即时本地reset了,但如果再git pull,那么,远程仓库的内容又会和本地之前版本的内容进行merge


这并不是我们想要的东西,这时可以有2种办法来解决这个问题:


1.直接在远程server的仓库目录下,执行git reset –soft 10efa来回退。注意:在远程不能使用mixed或hard参数

2.在本地直接把远程的master分支给删除,然后再把reset后的分支内容给push上去,如下:
        (1)新建old_master分支做备份   
            git branch old_master   
        (2)push到远程   
            git push origin old_masterld_master   
        (3)本地仓库回退到某个版本   
             git reset –hard bae168   
        (4)删除远程的master分支   
             git push origin :master   
        (5)重新创建master分支   
           git push origin master   

在删除远程master分支时,可能会有问题,见下:

        $ git push origin :master  


        error: By default, deleting the current branch is denied, because the next   


        error: 'git clone' won't result in any file checked out, causing confusion.   


        error:   


         error: You can set 'receive.denyDeleteCurrent' configuration variable to   


         error: 'warn' or 'ignore' in the remote repository to allow deleting the   


         error: current branch, with or without a warning message.   


         error:   


         t">error: To squelch this message, you can set it to 'refuse'.   


        error: refusing to delete the current branch: refs/heads/master   


        To git@xx.sohu.com:gitosis_test   


        ! [remote rejected] master (deletion of the current branch prohibited)   


        error: failed to push some refs to 'git@xx.sohu.com:gitosis_test'   


这时需要在远程仓库目录下,设置git的receive.denyDeleteCurrent参数

git receive.denyDeleteCurrent warn   



然后,就可以删除远程的master分支了


虽然说有以上2种方法可以回退远程分支的版本,但这2种方式,都挺危险的,需要谨慎操作……





reference:http://blog.163.com/jianlizhao@1 ... 163201183045856216/

我们都是打tag,没那么做过,这个命令应该能做到repo forall -c 'HAHA=`git log --before="3 days" -1 --pretty=format:"%H"`;git reset --hard $HAHA'  

http://www.csdn.net/article/2014-05-27/2819954
很幸运自己能够在不同类型的单位工作过,也因此接触了很多不同背景、不同思维习惯、不同性格类型的人,积累了人脉,同时也丰富了自己的人生阅历。中小型私企是我职业生涯的第一步,我打下了坚实的基础,不仅仅是技术,更重要的是使自己的综合素质得到了提高。而大型外企的管理等各个方面相对比较正规,有一整套的流程,我懂得了如何带领和管理一个大型团队。而在事业单位,我学会了如何跟不同类型的人沟通交流。这些年以来,我渐渐感悟出一点对生活的理解:程序员不仅要关注自己所处领域的技术,更要关注我们所处的环境。技术可能不会做一辈子,锻炼自己的生存能力则是最现实的话题。
编程是个技术活,既然是技术活就需要不断的在实践当中加以练习。因为我之前自学过C语言,算是有一点基础,但是这与真正的软件开发还是有相当的差距。为了能够尽快的掌握C#开发,我买了很多相关的技术书籍。入门经典、高级编程之类的书都买了,一开始的时候就跟着上面一个一个例子不断在VS编译器中调试、运行,监控每一步值的变化,慢慢培养编程的感觉。掌握了C#语言之后,后来我喜欢做一些小工具之类练习,比如我的浏览器(Webbrowser)、记账单(单机版Access应用)等等。通过这些一个又一个的小工具开发练习,我慢慢的掌握了软件开发的技巧。更重要的是通过这些编程练习,我建立起了肯定能学好编程的信心,并从中找到了乐趣以及成就感

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
54 [报告]
发表于 2014-06-24 16:50 |只看该作者
跟我一起写 Makefile


陈皓 (CSDN)

概述
——

什么是makefile?或许很多Winodws的程序员都不知道这个东西,因为那些Windows的IDE都为你做了这个工作,但我觉得要作一个好的和professional的程序员,makefile还是要懂。这就好像现在有这么多的HTML的编辑器,但如果你想成为一个专业人士,你还是要了解HTML的标识的含义。特别在Unix下的软件编译,你就不能不自己写makefile了,会不会写makefile,从一个侧面说明了一个人是否具备完成大型工程的能力。

因为,makefile关系到了整个工程的编译规则。一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,哪些文件需要重新编译,甚至于进行更复杂的功能操作,因为makefile就像一个Shell脚本一样,其中也可以执行操作系统的命令。

makefile带来的好处就是——“自动化编译”,一旦写好,只需要一个make命令,整个工程完全自动编译,极大的提高了软件开发的效率。make是一个命令工具,是一个解释makefile中指令的命令工具,一般来说,大多数的IDE都有这个命令,比如:Delphi的make,Visual C++的nmake,Linux下GNU的make。可见,makefile都成为了一种在工程方面的编译方法。

现在讲述如何写makefile的文章比较少,这是我想写这篇文章的原因。当然,不同产商的make各不相同,也有不同的语法,但其本质都是在“文件依赖性”上做文章,这里,我仅对GNU的make进行讲述,我的环境是RedHat Linux 8.0,make的版本是3.80。必竟,这个make是应用最为广泛的,也是用得最多的。而且其还是最遵循于IEEE 1003.2-1992 标准的(POSIX.2)。

在这篇文档中,将以C/C++的源码作为我们基础,所以必然涉及一些关于C/C++的编译的知识,相关于这方面的内容,还请各位查看相关的编译器的文档。这里所默认的编译器是UNIX下的GCC和CC。



关于程序的编译和链接
——————————

在此,我想多说关于程序编译的一些规范和方法,一般来说,无论是C、C++、还是pas,首先要把源文件编译成中间代码文件,在Windows下也就是 .obj 文件,UNIX下是 .o 文件,即 Object File,这个动作叫做编译(compile)。然后再把大量的Object File合成执行文件,这个动作叫作链接(link)。

编译时,编译器需要的是语法的正确,函数与变量的声明的正确。对于后者,通常是你需要告诉编译器头文件的所在位置(头文件中应该只是声明,而定义应该放在C/C++文件中),只要所有的语法正确,编译器就可以编译出中间目标文件。一般来说,每个源文件都应该对应于一个中间目标文件(O文件或是OBJ文件)。

链接时,主要是链接函数和全局变量,所以,我们可以使用这些中间目标文件(O文件或是OBJ文件)来链接我们的应用程序。链接器并不管函数所在的源文件,只管函数的中间目标文件(Object File),在大多数时候,由于源文件太多,编译生成的中间目标文件太多,而在链接时需要明显地指出中间目标文件名,这对于编译很不方便,所以,我们要给中间目标文件打个包,在Windows下这种包叫“库文件”(Library File),也就是 .lib 文件,在UNIX下,是Archive File,也就是 .a 文件。

总结一下,源文件首先会生成中间目标文件,再由中间目标文件生成执行文件。在编译时,编译器只检测程序语法,和函数、变量是否被声明。如果函数未被声明,编译器会给出一个警告,但可以生成Object File。而在链接程序时,链接器会在所有的Object File中找寻函数的实现,如果找不到,那到就会报链接错误码(Linker Error),在VC下,这种错误一般是:Link 2001错误,意思说是说,链接器未能找到函数的实现。你需要指定函数的Object File.

好,言归正传,GNU的make有许多的内容,闲言少叙,还是让我们开始吧。



Makefile 介绍
———————

make命令执行时,需要一个 Makefile 文件,以告诉make命令需要怎么样的去编译和链接程序。

首先,我们用一个示例来说明Makefile的书写规则。以便给大家一个感兴认识。这个示例来源于GNU的make使用手册,在这个示例中,我们的工程有8个C文件,和3个头文件,我们要写一个Makefile来告诉make命令如何编译和链接这几个文件。我们的规则是:
1)如果这个工程没有编译过,那么我们的所有C文件都要编译并被链接。
2)如果这个工程的某几个C文件被修改,那么我们只编译被修改的C文件,并链接目标程序。
3)如果这个工程的头文件被改变了,那么我们需要编译引用了这几个头文件的C文件,并链接目标程序。

只要我们的Makefile写得够好,所有的这一切,我们只用一个make命令就可以完成,make命令会自动智能地根据当前的文件修改的情况来确定哪些文件需要重编译,从而自己编译所需要的文件和链接目标程序。


一、Makefile的规则

在讲述这个Makefile之前,还是让我们先来粗略地看一看Makefile的规则。

target ... : prerequisites ...
command
...
...

target也就是一个目标文件,可以是Object File,也可以是执行文件。还可以是一个标签(Label),对于标签这种特性,在后续的“伪目标”章节中会有叙述。

prerequisites就是,要生成那个target所需要的文件或是目标。

command也就是make需要执行的命令。(任意的Shell命令)

这是一个文件的依赖关系,也就是说,target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。说白一点就是说,prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行。这就是Makefile的规则。也就是Makefile中最核心的内容。

说到底,Makefile的东西就是这样一点,好像我的这篇文档也该结束了。呵呵。还不尽然,这是Makefile的主线和核心,但要写好一个Makefile还不够,我会以后面一点一点地结合我的工作经验给你慢慢到来。内容还多着呢。:)


二、一个示例

正如前面所说的,如果一个工程有3个头文件,和8个C文件,我们为了完成前面所述的那三个规则,我们的Makefile应该是下面的这个样子的。

edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

反斜杠(/)是换行符的意思。这样比较便于Makefile的易读。我们可以把这个内容保存在文件为“Makefile”或“makefile”的文件中,然后在该目录下直接输入命令“make”就可以生成执行文件edit。如果要删除执行文件和所有的中间目标文件,那么,只要简单地执行一下“make clean”就可以了。

在这个makefile中,目标文件(target)包含:执行文件edit和中间目标文件(*.o),依赖文件(prerequisites)就是冒号后面的那些 .c 文件和 .h文件。每一个 .o 文件都有一组依赖文件,而这些 .o 文件又是执行文件 edit 的依赖文件。依赖关系的实质上就是说明了目标文件是由哪些文件生成的,换言之,目标文件是哪些文件更新的。

在定义好依赖关系后,后续的那一行定义了如何生成目标文件的操作系统命令,一定要以一个Tab键作为开头。记住,make并不管命令是怎么工作的,他只管执行所定义的命令。make会比较targets文件和prerequisites文件的修改日期,如果prerequisites文件的日期要比targets文件的日期要新,或者target不存在的话,那么,make就会执行后续定义的命令。

这里要说明一点的是,clean不是一个文件,它只不过是一个动作名字,有点像C语言中的lable一样,其冒号后什么也没有,那么,make就不会自动去找文件的依赖性,也就不会自动执行其后所定义的命令。要执行其后的命令,就要在make命令后明显得指出这个lable的名字。这样的方法非常有用,我们可以在一个makefile中定义不用的编译或是和编译无关的命令,比如程序的打包,程序的备份,等等。



三、make是如何工作的

在默认的方式下,也就是我们只输入make命令。那么,

1、make会在当前目录下找名字叫“Makefile”或“makefile”的文件。
2、如果找到,它会找文件中的第一个目标文件(target),在上面的例子中,他会找到“edit”这个文件,并把这个文件作为最终的目标文件。
3、如果edit文件不存在,或是edit所依赖的后面的 .o 文件的文件修改时间要比edit这个文件新,那么,他就会执行后面所定义的命令来生成edit这个文件。
4、如果edit所依赖的.o文件也不存在,那么make会在当前文件中找目标为.o文件的依赖性,如果找到则再根据那一个规则生成.o文件。(这有点像一个堆栈的过程)
5、当然,你的C文件和H文件是存在的啦,于是make会生成 .o 文件,然后再用 .o 文件生命make的终极任务,也就是执行文件edit了。

这就是整个make的依赖性,make会一层又一层地去找文件的依赖关系,直到最终编译出第一个目标文件。在找寻的过程中,如果出现错误,比如最后被依赖的文件找不到,那么make就会直接退出,并报错,而对于所定义的命令的错误,或是编译不成功,make根本不理。make只管文件的依赖性,即,如果在我找了依赖关系之后,冒号后面的文件还是不在,那么对不起,我就不工作啦。

通过上述分析,我们知道,像clean这种,没有被第一个目标文件直接或间接关联,那么它后面所定义的命令将不会被自动执行,不过,我们可以显示要make执行。即命令——“make clean”,以此来清除所有的目标文件,以便重编译。

于是在我们编程中,如果这个工程已被编译过了,当我们修改了其中一个源文件,比如file.c,那么根据我们的依赖性,我们的目标file.o会被重编译(也就是在这个依性关系后面所定义的命令),于是file.o的文件也是最新的啦,于是file.o的文件修改时间要比edit要新,所以edit也会被重新链接了(详见edit目标文件后定义的命令)。

而如果我们改变了“command.h”,那么,kdb.o、command.o和files.o都会被重编译,并且,edit会被重链接。


四、makefile中使用变量

在上面的例子中,先让我们看看edit的规则:

edit : main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o
cc -o edit main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

我们可以看到[.o]文件的字符串被重复了两次,如果我们的工程需要加入一个新的[.o]文件,那么我们需要在两个地方加(应该是三个地方,还有一个地方在clean中)。当然,我们的makefile并不复杂,所以在两个地方加也不累,但如果makefile变得复杂,那么我们就有可能会忘掉一个需要加入的地方,而导致编译失败。所以,为了makefile的易维护,在makefile中我们可以使用变量。makefile的变量也就是一个字符串,理解成C语言中的宏可能会更好。

比如,我们声明一个变量,叫objects, OBJECTS, objs, OBJS, obj, 或是 OBJ,反正不管什么啦,只要能够表示obj文件就行了。我们在makefile一开始就这样定义:

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

于是,我们就可以很方便地在我们的makefile中以“$(objects)”的方式来使用这个变量了,于是我们的改良版makefile就变成下面这个样子:

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)
main.o : main.c defs.h
cc -c main.c
kbd.o : kbd.c defs.h command.h
cc -c kbd.c
command.o : command.c defs.h command.h
cc -c command.c
display.o : display.c defs.h buffer.h
cc -c display.c
insert.o : insert.c defs.h buffer.h
cc -c insert.c
search.o : search.c defs.h buffer.h
cc -c search.c
files.o : files.c defs.h buffer.h command.h
cc -c files.c
utils.o : utils.c defs.h
cc -c utils.c
clean :
rm edit $(objects)


于是如果有新的 .o 文件加入,我们只需简单地修改一下 objects 变量就可以了。

关于变量更多的话题,我会在后续给你一一道来。


五、让make自动推导

GNU的make很强大,它可以自动推导文件以及文件依赖关系后面的命令,于是我们就没必要去在每一个[.o]文件后都写上类似的命令,因为,我们的make会自动识别,并自己推导命令。

只要make看到一个[.o]文件,它就会自动的把[.c]文件加在依赖关系中,如果make找到一个whatever.o,那么whatever.c,就会是whatever.o的依赖文件。并且 cc -c whatever.c 也会被推导出来,于是,我们的makefile再也不用写得这么复杂。我们的是新的makefile又出炉了。


objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

main.o : defs.h
kbd.o : defs.h command.h
command.o : defs.h command.h
display.o : defs.h buffer.h
insert.o : defs.h buffer.h
search.o : defs.h buffer.h
files.o : defs.h buffer.h command.h
utils.o : defs.h

.PHONY : clean
clean :
rm edit $(objects)

这种方法,也就是make的“隐晦规则”。上面文件内容中,“.PHONY”表示,clean是个伪目标文件。

关于更为详细的“隐晦规则”和“伪目标文件”,我会在后续给你一一道来。


六、另类风格的makefile

即然我们的make可以自动推导命令,那么我看到那堆[.o]和[.h]的依赖就有点不爽,那么多的重复的[.h],能不能把其收拢起来,好吧,没有问题,这个对于make来说很容易,谁叫它提供了自动推导命令和文件的功能呢?来看看最新风格的makefile吧。

objects = main.o kbd.o command.o display.o /
insert.o search.o files.o utils.o

edit : $(objects)
cc -o edit $(objects)

$(objects) : defs.h
kbd.o command.o files.o : command.h
display.o insert.o search.o files.o : buffer.h

.PHONY : clean
clean :
rm edit $(objects)

这种风格,让我们的makefile变得很简单,但我们的文件依赖关系就显得有点凌乱了。鱼和熊掌不可兼得。还看你的喜好了。我是不喜欢这种风格的,一是文件的依赖关系看不清楚,二是如果文件一多,要加入几个新的.o文件,那就理不清楚了。

七、清空目标文件的规则

每个Makefile中都应该写一个清空目标文件(.o和执行文件)的规则,这不仅便于重编译,也很利于保持文件的清洁。这是一个“修养”(呵呵,还记得我的《编程修养》吗)。一般的风格都是:

clean:
rm edit $(objects)

更为稳健的做法是:

.PHONY : clean
clean :
-rm edit $(objects)

前面说过,.PHONY意思表示clean是一个“伪目标”,。而在rm命令前面加了一个小减号的意思就是,也许某些文件出现问题,但不要管,继续做后面的事。当然,clean的规则不要放在文件的开头,不然,这就会变成make的默认目标,相信谁也不愿意这样。不成文的规矩是——“clean从来都是放在文件的最后”。


上面就是一个makefile的概貌,也是makefile的基础,下面还有很多makefile的相关细节,准备好了吗?准备好了就来。

Makefile 总述
———————

一、Makefile里有什么?

Makefile里主要包含了五个东西:显式规则、隐晦规则、变量定义、文件指示和注释。

1、显式规则。显式规则说明了,如何生成一个或多的的目标文件。这是由Makefile的书写者明显指出,要生成的文件,文件的依赖文件,生成的命令。

2、隐晦规则。由于我们的make有自动推导的功能,所以隐晦的规则可以让我们比较粗糙地简略地书写Makefile,这是由make所支持的。

3、变量的定义。在Makefile中我们要定义一系列的变量,变量一般都是字符串,这个有点你C语言中的宏,当Makefile被执行时,其中的变量都会被扩展到相应的引用位置上。

4、文件指示。其包括了三个部分,一个是在一个Makefile中引用另一个Makefile,就像C语言中的include一样;另一个是指根据某些情况指定Makefile中的有效部分,就像C语言中的预编译#if一样;还有就是定义一个多行的命令。有关这一部分的内容,我会在后续的部分中讲述。

5、注释。Makefile中只有行注释,和UNIX的Shell脚本一样,其注释是用“#”字符,这个就像C/C++中的“//”一样。如果你要在你的Makefile中使用“#”字符,可以用反斜框进行转义,如:“/#”。

最后,还值得一提的是,在Makefile中的命令,必须要以[Tab]键开始。


二、Makefile的文件名

默认的情况下,make命令会在当前目录下按顺序找寻文件名为“GNUmakefile”、“makefile”、“Makefile”的文件,找到了解释这个文件。在这三个文件名中,最好使用“Makefile”这个文件名,因为,这个文件名第一个字符为大写,这样有一种显目的感觉。最好不要用“GNUmakefile”,这个文件是GNU的make识别的。有另外一些make只对全小写的“makefile”文件名敏感,但是基本上来说,大多数的make都支持“makefile”和“Makefile”这两种默认文件名。

当然,你可以使用别的文件名来书写Makefile,比如:“Make.Linux”,“Make.Solaris”,“Make.AIX”等,如果要指定特定的Makefile,你可以使用make的“-f”和“--file”参数,如:make -f Make.Linux或make --file Make.AIX。


三、引用其它的Makefile

在Makefile使用include关键字可以把别的Makefile包含进来,这很像C语言的#include,被包含的文件会原模原样的放在当前文件的包含位置。include的语法是:

include <filename>

filename可以是当前操作系统Shell的文件模式(可以保含路径和通配符)

在include前面可以有一些空字符,但是绝不能是[Tab]键开始。include和<filename>可以用一个或多个空格隔开。举个例子,你有这样几个Makefile:a.mk、b.mk、c.mk,还有一个文件叫foo.make,以及一个变量$(bar),其包含了e.mk和f.mk,那么,下面的语句:

include foo.make *.mk $(bar)

等价于:

include foo.make a.mk b.mk c.mk e.mk f.mk

make命令开始时,会把找寻include所指出的其它Makefile,并把其内容安置在当前的位置。就好像C/C++的#include指令一样。如果文件都没有指定绝对路径或是相对路径的话,make会在当前目录下首先寻找,如果当前目录下没有找到,那么,make还会在下面的几个目录下找:

1、如果make执行时,有“-I”或“--include-dir”参数,那么make就会在这个参数所指定的目录下去寻找。
2、如果目录<prefix>/include(一般是:/usr/local/bin或/usr/include)存在的话,make也会去找。

如果有文件没有找到的话,make会生成一条警告信息,但不会马上出现致命错误。它会继续载入其它的文件,一旦完成makefile的读取,make会再重试这些没有找到,或是不能读取的文件,如果还是不行,make才会出现一条致命信息。如果你想让make不理那些无法读取的文件,而继续执行,你可以在include前加一个减号“-”。如:

-include <filename>
其表示,无论include过程中出现什么错误,都不要报错继续执行。和其它版本make兼容的相关命令是sinclude,其作用和这一个是一样的。


四、环境变量 MAKEFILES

如果你的当前环境中定义了环境变量MAKEFILES,那么,make会把这个变量中的值做一个类似于include的动作。这个变量中的值是其它的Makefile,用空格分隔。只是,它和include不同的是,从这个环境变中引入的Makefile的“目标”不会起作用,如果环境变量中定义的文件发现错误,make也会不理。

但是在这里我还是建议不要使用这个环境变量,因为只要这个变量一被定义,那么当你使用make时,所有的Makefile都会受到它的影响,这绝不是你想看到的。在这里提这个事,只是为了告诉大家,也许有时候你的Makefile出现了怪事,那么你可以看看当前环境中有没有定义这个变量。


五、make的工作方式

GNU的make工作时的执行步骤入下:(想来其它的make也是类似)

1、读入所有的Makefile。
2、读入被include的其它Makefile。
3、初始化文件中的变量。
4、推导隐晦规则,并分析所有规则。
5、为所有的目标文件创建依赖关系链。
6、根据依赖关系,决定哪些目标要重新生成。
7、执行生成命令。

1-5步为第一个阶段,6-7为第二个阶段。第一个阶段中,如果定义的变量被使用了,那么,make会把其展开在使用的位置。但make并不会完全马上展开,make使用的是拖延战术,如果变量出现在依赖关系的规则中,那么仅当这条依赖被决定要使用了,变量才会在其内部展开。

当然,这个工作方式你不一定要清楚,但是知道这个方式你也会对make更为熟悉。有了这个基础,后续部分也就容易看懂了。



书写规则
————

规则包含两个部分,一个是依赖关系,一个是生成目标的方法。

在Makefile中,规则的顺序是很重要的,因为,Makefile中只应该有一个最终目标,其它的目标都是被这个目标所连带出来的,所以一定要让make知道你的最终目标是什么。一般来说,定义在Makefile中的目标可能会有很多,但是第一条规则中的目标将被确立为最终的目标。如果第一条规则中的目标有很多个,那么,第一个目标会成为最终的目标。make所完成的也就是这个目标。

好了,还是让我们来看一看如何书写规则。


一、规则举例

foo.o : foo.c defs.h # foo模块
cc -c -g foo.c

看到这个例子,各位应该不是很陌生了,前面也已说过,foo.o是我们的目标,foo.c和defs.h是目标所依赖的源文件,而只有一个命令“cc -c -g foo.c”(以Tab键开头)。这个规则告诉我们两件事:

1、文件的依赖关系,foo.o依赖于foo.c和defs.h的文件,如果foo.c和defs.h的文件日期要比foo.o文件日期要新,或是foo.o不存在,那么依赖关系发生。
2、如果生成(或更新)foo.o文件。也就是那个cc命令,其说明了,如何生成foo.o这个文件。(当然foo.c文件include了defs.h文件)


二、规则的语法

targets : prerequisites
command
...

或是这样:

targets : prerequisites ; command
command
...

targets是文件名,以空格分开,可以使用通配符。一般来说,我们的目标基本上是一个文件,但也有可能是多个文件。

command是命令行,如果其不与“targetrerequisites”在一行,那么,必须以[Tab键]开头,如果和prerequisites在一行,那么可以用分号做为分隔。(见上)

prerequisites也就是目标所依赖的文件(或依赖目标)。如果其中的某个文件要比目标文件要新,那么,目标就被认为是“过时的”,被认为是需要重生成的。这个在前面已经讲过了。

如果命令太长,你可以使用反斜框(‘/’)作为换行符。make对一行上有多少个字符没有限制。规则告诉make两件事,文件的依赖关系和如何成成目标文件。

一般来说,make会以UNIX的标准Shell,也就是/bin/sh来执行命令。


三、在规则中使用通配符

如果我们想定义一系列比较类似的文件,我们很自然地就想起使用通配符。make支持三各通配符:“*”,“?”和“[...]”。这是和Unix的B-Shell是相同的。

波浪号(“~”)字符在文件名中也有比较特殊的用途。如果是“~/test”,这就表示当前用户的$HOME目录下的test目录。而“~hchen/test”则表示用户hchen的宿主目录下的test目录。(这些都是Unix下的小知识了,make也支持)而在Windows或是MS-DOS下,用户没有宿主目录,那么波浪号所指的目录则根据环境变量“HOME”而定。

通配符代替了你一系列的文件,如“*.c”表示所以后缀为c的文件。一个需要我们注意的是,如果我们的文件名中有通配符,如:“*”,那么可以用转义字符“/”,如“/*”来表示真实的“*”字符,而不是任意长度的字符串。

好吧,还是先来看几个例子吧:

clean:
rm -f *.o

上面这个例子我不不多说了,这是操作系统Shell所支持的通配符。这是在命令中的通配符。

print: *.c
lpr -p $?
touch print

上面这个例子说明了通配符也可以在我们的规则中,目标print依赖于所有的[.c]文件。其中的“$?”是一个自动化变量,我会在后面给你讲述。

objects = *.o

上面这个例子,表示了,通符同样可以用在变量中。并不是说[*.o]会展开,不!objects的值就是“*.o”。Makefile中的变量其实就是C/C++中的宏。如果你要让通配符在变量中展开,也就是让objects的值是所有[.o]的文件名的集合,那么,你可以这样:

objects := $(wildcard *.o)

这种用法由关键字“wildcard”指出,关于Makefile的关键字,我们将在后面讨论。


四、文件搜寻

在一些大的工程中,有大量的源文件,我们通常的做法是把这许多的源文件分类,并存放在不同的目录中。所以,当make需要去找寻文件的依赖关系时,你可以在文件前加上路径,但最好的方法是把一个路径告诉make,让make在自动去找。

Makefile文件中的特殊变量“VPATH”就是完成这个功能的,如果没有指明这个变量,make只会在当前的目录中去找寻依赖文件和目标文件。如果定义了这个变量,那么,make就会在当当前目录找不到的情况下,到所指定的目录中去找寻文件了。

VPATH = src:../headers

上面的的定义指定两个目录,“src”和“../headers”,make会按照这个顺序进行搜索。目录由“冒号”分隔。(当然,当前目录永远是最高优先搜索的地方)

另一个设置文件搜索路径的方法是使用make的“vpath”关键字(注意,它是全小写的),这不是变量,这是一个make的关键字,这和上面提到的那个VPATH变量很类似,但是它更为灵活。它可以指定不同的文件在不同的搜索目录中。这是一个很灵活的功能。它的使用方法有三种:

1、vpath <pattern> <directories>

为符合模式<pattern>的文件指定搜索目录<directories>。

2、vpath <pattern>

清除符合模式<pattern>的文件的搜索目录。

3、vpath

清除所有已被设置好了的文件搜索目录。

vapth使用方法中的<pattern>需要包含“%”字符。“%”的意思是匹配零或若干字符,例如,“%.h”表示所有以“.h”结尾的文件。<pattern>指定了要搜索的文件集,而<directories>则指定了<pattern>的文件集的搜索的目录。例如:

vpath %.h ../headers

该语句表示,要求make在“../headers”目录下搜索所有以“.h”结尾的文件。(如果某文件在当前目录没有找到的话)

我们可以连续地使用vpath语句,以指定不同搜索策略。如果连续的vpath语句中出现了相同的<pattern>,或是被重复了的<pattern>,那么,make会按照vpath语句的先后顺序来执行搜索。如:

vpath %.c foo
vpath % blish
vpath %.c bar

其表示“.c”结尾的文件,先在“foo”目录,然后是“blish”,最后是“bar”目录。

vpath %.c foo:bar
vpath % blish

而上面的语句则表示“.c”结尾的文件,先在“foo”目录,然后是“bar”目录,最后才是“blish”目录。


五、伪目标

最早先的一个例子中,我们提到过一个“clean”的目标,这是一个“伪目标”,

clean:
rm *.o temp

正像我们前面例子中的“clean”一样,即然我们生成了许多文件编译文件,我们也应该提供一个清除它们的“目标”以备完整地重编译而用。 (以“make clean”来使用该目标)

因为,我们并不生成“clean”这个文件。“伪目标”并不是一个文件,只是一个标签,由于“伪目标”不是文件,所以make无法生成它的依赖关系和决定它是否要执行。我们只有通过显示地指明这个“目标”才能让其生效。当然,“伪目标”的取名不能和文件名重名,不然其就失去了“伪目标”的意义了。

当然,为了避免和文件重名的这种情况,我们可以使用一个特殊的标记“.PHONY”来显示地指明一个目标是“伪目标”,向make说明,不管是否有这个文件,这个目标就是“伪目标”。

.PHONY : clean

只要有这个声明,不管是否有“clean”文件,要运行“clean”这个目标,只有“make clean”这样。于是整个过程可以这样写:

.PHONY: clean
clean:
rm *.o temp

伪目标一般没有依赖的文件。但是,我们也可以为伪目标指定所依赖的文件。伪目标同样可以作为“默认目标”,只要将其放在第一个。一个示例就是,如果你的Makefile需要一口气生成若干个可执行文件,但你只想简单地敲一个make完事,并且,所有的目标文件都写在一个Makefile中,那么你可以使用“伪目标”这个特性:

all : prog1 prog2 prog3
.PHONY : all

prog1 : prog1.o utils.o
cc -o prog1 prog1.o utils.o

prog2 : prog2.o
cc -o prog2 prog2.o

prog3 : prog3.o sort.o utils.o
cc -o prog3 prog3.o sort.o utils.o

我们知道,Makefile中的第一个目标会被作为其默认目标。我们声明了一个“all”的伪目标,其依赖于其它三个目标。由于伪目标的特性是,总是被执行的,所以其依赖的那三个目标就总是不如“all”这个目标新。所以,其它三个目标的规则总是会被决议。也就达到了我们一口气生成多个目标的目的。“.PHONY : all”声明了“all”这个目标为“伪目标”。

随便提一句,从上面的例子我们可以看出,目标也可以成为依赖。所以,伪目标同样也可成为依赖。看下面的例子:

.PHONY: cleanall cleanobj cleandiff

cleanall : cleanobj cleandiff
rm program

cleanobj :
rm *.o

cleandiff :
rm *.diff

“make clean”将清除所有要被清除的文件。“cleanobj”和“cleandiff”这两个伪目标有点像“子程序”的意思。我们可以输入“make cleanall”和“make cleanobj”和“make cleandiff”命令来达到清除不同种类文件的目的。

六、多目标

Makefile的规则中的目标可以不止一个,其支持多目标,有可能我们的多个目标同时依赖于一个文件,并且其生成的命令大体类似。于是我们就能把其合并起来。当然,多个目标的生成规则的执行命令是同一个,这可能会可我们带来麻烦,不过好在我们的可以使用一个自动化变量“$@”(关于自动化变量,将在后面讲述),这个变量表示着目前规则中所有的目标的集合,这样说可能很抽象,还是看一个例子吧。

bigoutput littleoutput : text.g
generate text.g -$(subst output,,$@) > $@

上述规则等价于:

bigoutput : text.g
generate text.g -big > bigoutput
littleoutput : text.g
generate text.g -little > littleoutput

其中,-$(subst output,,$@)中的“$”表示执行一个Makefile的函数,函数名为subst,后面的为参数。关于函数,将在后面讲述。这里的这个函数是截取字符串的意思,“$@”表示目标的集合,就像一个数组,“$@”依次取出目标,并执于命令。


七、静态模式

静态模式可以更加容易地定义多目标的规则,可以让我们的规则变得更加的有弹性和灵活。我们还是先来看一下语法:

<targets ...>: <target-pattern>: <prereq-patterns ...>
<commands>
...


targets定义了一系列的目标文件,可以有通配符。是目标的一个集合。

target-parrtern是指明了targets的模式,也就是的目标集模式。

prereq-parrterns是目标的依赖模式,它对target-parrtern形成的模式再进行一次依赖目标的定义。

这样描述这三个东西,可能还是没有说清楚,还是举个例子来说明一下吧。如果我们的<target-parrtern>定义成“%.o”,意思是我们的<target>集合中都是以“.o”结尾的,而如果我们的<prereq-parrterns>定义成“%.c”,意思是对<target-parrtern>所形成的目标集进行二次定义,其计算方法是,取<target-parrtern>模式中的“%”(也就是去掉了[.o]这个结尾),并为其加上[.c]这个结尾,形成的新集合。

所以,我们的“目标模式”或是“依赖模式”中都应该有“%”这个字符,如果你的文件名中有“%”那么你可以使用反斜杠“/”进行转义,来标明真实的“%”字符。

看一个例子:

objects = foo.o bar.o

all: $(objects)

$(objects): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@


上面的例子中,指明了我们的目标从$object中获取,“%.o”表明要所有以“.o”结尾的目标,也就是“foo.o bar.o”,也就是变量$object集合的模式,而依赖模式“%.c”则取模式“%.o”的“%”,也就是“foo bar”,并为其加下“.c”的后缀,于是,我们的依赖目标就是“foo.c bar.c”。而命令中的“$<”和“$@”则是自动化变量,“$<”表示所有的依赖目标集(也就是“foo.c bar.c”),“$@”表示目标集(也就是“foo.o bar.o”)。于是,上面的规则展开后等价于下面的规则:

foo.o : foo.c
$(CC) -c $(CFLAGS) foo.c -o foo.o
bar.o : bar.c
$(CC) -c $(CFLAGS) bar.c -o bar.o

试想,如果我们的“%.o”有几百个,那种我们只要用这种很简单的“静态模式规则”就可以写完一堆规则,实在是太有效率了。“静态模式规则”的用法很灵活,如果用得好,那会一个很强大的功能。再看一个例子:


files = foo.elc bar.o lose.o

$(filter %.o,$(files)): %.o: %.c
$(CC) -c $(CFLAGS) $< -o $@
$(filter %.elc,$(files)): %.elc: %.el
emacs -f batch-byte-compile $<


$(filter %.o,$(files))表示调用Makefile的filter函数,过滤“$filter”集,只要其中模式为“%.o”的内容。其的它内容,我就不用多说了吧。这个例字展示了Makefile中更大的弹性。


八、自动生成依赖性

在Makefile中,我们的依赖关系可能会需要包含一系列的头文件,比如,如果我们的main.c中有一句“#include "defs.h"”,那么我们的依赖关系应该是:

main.o : main.c defs.h

但是,如果是一个比较大型的工程,你必需清楚哪些C文件包含了哪些头文件,并且,你在加入或删除头文件时,也需要小心地修改Makefile,这是一个很没有维护性的工作。为了避免这种繁重而又容易出错的事情,我们可以使用C/C++编译的一个功能。大多数的C/C++编译器都支持一个“-M”的选项,即自动找寻源文件中包含的头文件,并生成一个依赖关系。例如,如果我们执行下面的命令:

cc -M main.c

其输出是:

main.o : main.c defs.h

于是由编译器自动生成的依赖关系,这样一来,你就不必再手动书写若干文件的依赖关系,而由编译器自动生成了。需要提醒一句的是,如果你使用GNU的C/C++编译器,你得用“-MM”参数,不然,“-M”参数会把一些标准库的头文件也包含进来。

gcc -M main.c的输出是:

main.o: main.c defs.h /usr/include/stdio.h /usr/include/features.h /
/usr/include/sys/cdefs.h /usr/include/gnu/stubs.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stddef.h /
/usr/include/bits/types.h /usr/include/bits/pthreadtypes.h /
/usr/include/bits/sched.h /usr/include/libio.h /
/usr/include/_G_config.h /usr/include/wchar.h /
/usr/include/bits/wchar.h /usr/include/gconv.h /
/usr/lib/gcc-lib/i486-suse-linux/2.95.3/include/stdarg.h /
/usr/include/bits/stdio_lim.h


gcc -MM main.c的输出则是:

main.o: main.c defs.h

那么,编译器的这个功能如何与我们的Makefile联系在一起呢。因为这样一来,我们的Makefile也要根据这些源文件重新生成,让Makefile自已依赖于源文件?这个功能并不现实,不过我们可以有其它手段来迂回地实现这一功能。GNU组织建议把编译器为每一个源文件的自动生成的依赖关系放到一个文件中,为每一个“name.c”的文件都生成一个“name.d”的Makefile文件,[.d]文件中就存放对应[.c]文件的依赖关系。

于是,我们可以写出[.c]文件和[.d]文件的依赖关系,并让make自动更新或自成[.d]文件,并把其包含在我们的主Makefile中,这样,我们就可以自动化地生成每个文件的依赖关系了。

这里,我们给出了一个模式规则来产生[.d]文件:

%.d: %.c
@set -e; rm -f $@; /
$(CC) -M $(CPPFLAGS) $< > $@.$$$$; /
sed 's,/($*/)/.o[ :]*,/1.o $@ : ,g' < $@.$$$$ > $@; /
rm -f $@.$$$$


这个规则的意思是,所有的[.d]文件依赖于[.c]文件,“rm -f $@”的意思是删除所有的目标,也就是[.d]文件,第二行的意思是,为每个依赖文件“$<”,也就是[.c]文件生成依赖文件,“$@”表示模式“%.d”文件,如果有一个C文件是name.c,那么“%”就是“name”,“$$$$”意为一个随机编号,第二行生成的文件有可能是“name.d.12345”,第三行使用sed命令做了一个替换,关于sed命令的用法请参看相关的使用文档。第四行就是删除临时文件。

总而言之,这个模式要做的事就是在编译器生成的依赖关系中加入[.d]文件的依赖,即把依赖关系:

main.o : main.c defs.h

转成:

main.o main.d : main.c defs.h

于是,我们的[.d]文件也会自动更新了,并会自动生成了,当然,你还可以在这个[.d]文件中加入的不只是依赖关系,包括生成的命令也可一并加入,让每个[.d]文件都包含一个完赖的规则。一旦我们完成这个工作,接下来,我们就要把这些自动生成的规则放进我们的主Makefile中。我们可以使用Makefile的“include”命令,来引入别的Makefile文件(前面讲过),例如:

sources = foo.c bar.c

include $(sources:.c=.d)

上述语句中的“$(sources:.c=.d)”中的“.c=.d”的意思是做一个替换,把变量$(sources)所有[.c]的字串都替换成[.d],关于这个“替换”的内容,在后面我会有更为详细的讲述。当然,你得注意次序,因为include是按次来载入文件,最先载入的[.d]文件中的目标会成为默认目标。



书写命令
————

每条规则中的命令和操作系统Shell的命令行是一致的。make会一按顺序一条一条的执行命令,每条命令的开头必须以[Tab]键开头,除非,命令是紧跟在依赖规则后面的分号后的。在命令行之间中的空格或是空行会被忽略,但是如果该空格或空行是以Tab键开头的,那么make会认为其是一个空命令。

我们在UNIX下可能会使用不同的Shell,但是make的命令默认是被“/bin/sh”——UNIX的标准Shell解释执行的。除非你特别指定一个其它的Shell。Makefile中,“#”是注释符,很像C/C++中的“//”,其后的本行字符都被注释。

一、显示命令

通常,make会把其要执行的命令行在命令执行前输出到屏幕上。当我们用“@”字符在命令行前,那么,这个命令将不被make显示出来,最具代表性的例子是,我们用这个功能来像屏幕显示一些信息。如:

@echo 正在编译XXX模块......

当make执行时,会输出“正在编译XXX模块......”字串,但不会输出命令,如果没有“@”,那么,make将输出:

echo 正在编译XXX模块......
正在编译XXX模块......

如果make执行时,带入make参数“-n”或“--just-print”,那么其只是显示命令,但不会执行命令,这个功能很有利于我们调试我们的Makefile,看看我们书写的命令是执行起来是什么样子的或是什么顺序的。

而make参数“-s”或“--slient”则是全面禁止命令的显示。



二、命令执行

当依赖目标新于目标时,也就是当规则的目标需要被更新时,make会一条一条的执行其后的命令。需要注意的是,如果你要让上一条命令的结果应用在下一条命令时,你应该使用分号分隔这两条命令。比如你的第一条命令是cd命令,你希望第二条命令得在cd之后的基础上运行,那么你就不能把这两条命令写在两行上,而应该把这两条命令写在一行上,用分号分隔。如:

示例一:
exec:
cd /home/hchen
pwd

示例二:
exec:
cd /home/hchen; pwd

当我们执行“make exec”时,第一个例子中的cd没有作用,pwd会打印出当前的Makefile目录,而第二个例子中,cd就起作用了,pwd会打印出“/home/hchen”。

make一般是使用环境变量SHELL中所定义的系统Shell来执行命令,默认情况下使用UNIX的标准Shell——/bin/sh来执行命令。但在MS-DOS下有点特殊,因为MS-DOS下没有SHELL环境变量,当然你也可以指定。如果你指定了UNIX风格的目录形式,首先,make会在SHELL所指定的路径中找寻命令解释器,如果找不到,其会在当前盘符中的当前目录中寻找,如果再找不到,其会在PATH环境变量中所定义的所有路径中寻找。MS-DOS中,如果你定义的命令解释器没有找到,其会给你的命令解释器加上诸如“.exe”、“.com”、“.bat”、“.sh”等后缀。

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
55 [报告]
发表于 2014-06-24 16:55 |只看该作者
三、命令出错

每当命令运行完后,make会检测每个命令的返回码,如果命令返回成功,那么make会执行下一条命令,当规则中所有的命令成功返回后,这个规则就算是成功完成了。如果一个规则中的某个命令出错了(命令退出码非零),那么make就会终止执行当前规则,这将有可能终止所有规则的执行。

有些时候,命令的出错并不表示就是错误的。例如mkdir命令,我们一定需要建立一个目录,如果目录不存在,那么mkdir就成功执行,万事大吉,如果目录存在,那么就出错了。我们之所以使用mkdir的意思就是一定要有这样的一个目录,于是我们就不希望mkdir出错而终止规则的运行。

为了做到这一点,忽略命令的出错,我们可以在Makefile的命令行前加一个减号“-”(在Tab键之后),标记为不管命令出不出错都认为是成功的。如:

clean:
-rm -f *.o

还有一个全局的办法是,给make加上“-i”或是“--ignore-errors”参数,那么,Makefile中所有命令都会忽略错误。而如果一个规则是以“.IGNORE”作为目标的,那么这个规则中的所有命令将会忽略错误。这些是不同级别的防止命令出错的方法,你可以根据你的不同喜欢设置。

还有一个要提一下的make的参数的是“-k”或是“--keep-going”,这个参数的意思是,如果某规则中的命令出错了,那么就终目该规则的执行,但继续执行其它规则。



四、嵌套执行make

在一些大的工程中,我们会把我们不同模块或是不同功能的源文件放在不同的目录中,我们可以在每个目录中都书写一个该目录的Makefile,这有利于让我们的Makefile变得更加地简洁,而不至于把所有的东西全部写在一个Makefile中,这样会很难维护我们的Makefile,这个技术对于我们模块编译和分段编译有着非常大的好处。

例如,我们有一个子目录叫subdir,这个目录下有个Makefile文件,来指明了这个目录下文件的编译规则。那么我们总控的Makefile可以这样书写:

subsystem:
cd subdir && $(MAKE)

其等价于:

subsystem:
$(MAKE) -C subdir

定义$(MAKE)宏变量的意思是,也许我们的make需要一些参数,所以定义成一个变量比较利于维护。这两个例子的意思都是先进入“subdir”目录,然后执行make命令。

我们把这个Makefile叫做“总控Makefile”,总控Makefile的变量可以传递到下级的Makefile中(如果你显示的声明),但是不会覆盖下层的Makefile中所定义的变量,除非指定了“-e”参数。

如果你要传递变量到下级Makefile中,那么你可以使用这样的声明:

export <variable ...>

如果你不想让某些变量传递到下级Makefile中,那么你可以这样声明:

unexport <variable ...>

如:

示例一:

export variable = value

其等价于:

variable = value
export variable

其等价于:

export variable := value

其等价于:

variable := value
export variable

示例二:

export variable += value

其等价于:

variable += value
export variable

如果你要传递所有的变量,那么,只要一个export就行了。后面什么也不用跟,表示传递所有的变量。

需要注意的是,有两个变量,一个是SHELL,一个是MAKEFLAGS,这两个变量不管你是否export,其总是要传递到下层Makefile中,特别是MAKEFILES变量,其中包含了make的参数信息,如果我们执行“总控Makefile”时有make参数或是在上层Makefile中定义了这个变量,那么MAKEFILES变量将会是这些参数,并会传递到下层Makefile中,这是一个系统级的环境变量。

但是make命令中的有几个参数并不往下传递,它们是“-C”,“-f”,“-h”“-o”和“-W”(有关Makefile参数的细节将在后面说明),如果你不想往下层传递参数,那么,你可以这样来:

subsystem:
cd subdir && $(MAKE) MAKEFLAGS=

如果你定义了环境变量MAKEFLAGS,那么你得确信其中的选项是大家都会用到的,如果其中有“-t”,“-n”,和“-q”参数,那么将会有让你意想不到的结果,或许会让你异常地恐慌。

还有一个在“嵌套执行”中比较有用的参数,“-w”或是“--print-directory”会在make的过程中输出一些信息,让你看到目前的工作目录。比如,如果我们的下级make目录是“/home/hchen/gnu/make”,如果我们使用“make -w”来执行,那么当进入该目录时,我们会看到:

make: Entering directory `/home/hchen/gnu/make'.

而在完成下层make后离开目录时,我们会看到:

make: Leaving directory `/home/hchen/gnu/make'

当你使用“-C”参数来指定make下层Makefile时,“-w”会被自动打开的。如果参数中有“-s”(“--slient”)或是“--no-print-directory”,那么,“-w”总是失效的。



五、定义命令包

如果Makefile中出现一些相同命令序列,那么我们可以为这些相同的命令序列定义一个变量。定义这种命令序列的语法以“define”开始,以“endef”结束,如:

define run-yacc
yacc $(firstword $^)
mv y.tab.c $@
endef

这里,“run-yacc”是这个命令包的名字,其不要和Makefile中的变量重名。在“define”和“endef”中的两行就是命令序列。这个命令包中的第一个命令是运行Yacc程序,因为Yacc程序总是生成“y.tab.c”的文件,所以第二行的命令就是把这个文件改改名字。还是把这个命令包放到一个示例中来看看吧。

foo.c : foo.y
$(run-yacc)

我们可以看见,要使用这个命令包,我们就好像使用变量一样。在这个命令包的使用中,命令包“run-yacc”中的“$^”就是“foo.y”,“$@”就是“foo.c”(有关这种以“$”开头的特殊变量,我们会在后面介绍),make在执行命令包时,命令包中的每个命令会被依次独立执行。

使用变量
————

在Makefile中的定义的变量,就像是C/C++语言中的宏一样,他代表了一个文本字串,在Makefile中执行的时候其会自动原模原样地展开在所使用的地方。其与C/C++所不同的是,你可以在Makefile中改变其值。在Makefile中,变量可以使用在“目标”,“依赖目标”,“命令”或是Makefile的其它部分中。

变量的命名字可以包含字符、数字,下划线(可以是数字开头),但不应该含有“:”、“#”、“=”或是空字符(空格、回车等)。变量是大小写敏感的,“foo”、“Foo”和“FOO”是三个不同的变量名。传统的Makefile的变量名是全大写的命名方式,但我推荐使用大小写搭配的变量名,如:MakeFlags。这样可以避免和系统的变量冲突,而发生意外的事情。

有一些变量是很奇怪字串,如“$<”、“$@”等,这些是自动化变量,我会在后面介绍。

一、变量的基础

变量在声明时需要给予初值,而在使用时,需要给在变量名前加上“$”符号,但最好用小括号“()”或是大括号“{}”把变量给包括起来。如果你要使用真实的“$”字符,那么你需要用“$$”来表示。

变量可以使用在许多地方,如规则中的“目标”、“依赖”、“命令”以及新的变量中。先看一个例子:

objects = program.o foo.o utils.o
program : $(objects)
cc -o program $(objects)

$(objects) : defs.h

变量会在使用它的地方精确地展开,就像C/C++中的宏一样,例如:

foo = c
prog.o : prog.$(foo)
$(foo)$(foo) -$(foo) prog.$(foo)

展开后得到:

prog.o : prog.c
cc -c prog.c

当然,千万不要在你的Makefile中这样干,这里只是举个例子来表明Makefile中的变量在使用处展开的真实样子。可见其就是一个“替代”的原理。

另外,给变量加上括号完全是为了更加安全地使用这个变量,在上面的例子中,如果你不想给变量加上括号,那也可以,但我还是强烈建议你给变量加上括号。


二、变量中的变量

在定义变量的值时,我们可以使用其它变量来构造变量的值,在Makefile中有两种方式来在用变量定义变量的值。

先看第一种方式,也就是简单的使用“=”号,在“=”左侧是变量,右侧是变量的值,右侧变量的值可以定义在文件的任何一处,也就是说,右侧中的变量不一定非要是已定义好的值,其也可以使用后面定义的值。如:

foo = $(bar)
bar = $(ugh)
ugh = Huh?

all:
echo $(foo)

我们执行“make all”将会打出变量$(foo)的值是“Huh?”( $(foo)的值是$(bar),$(bar)的值是$(ugh),$(ugh)的值是“Huh?”)可见,变量是可以使用后面的变量来定义的。

这个功能有好的地方,也有不好的地方,好的地方是,我们可以把变量的真实值推到后面来定义,如:

CFLAGS = $(include_dirs) -O
include_dirs = -Ifoo -Ibar

当“CFLAGS”在命令中被展开时,会是“-Ifoo -Ibar -O”。但这种形式也有不好的地方,那就是递归定义,如:

CFLAGS = $(CFLAGS) -O

或:

A = $(B)
B = $(A)

这会让make陷入无限的变量展开过程中去,当然,我们的make是有能力检测这样的定义,并会报错。还有就是如果在变量中使用函数,那么,这种方式会让我们的make运行时非常慢,更糟糕的是,他会使用得两个make的函数“wildcard”和“shell”发生不可预知的错误。因为你不会知道这两个函数会被调用多少次。

为了避免上面的这种方法,我们可以使用make中的另一种用变量来定义变量的方法。这种方法使用的是“:=”操作符,如:

x := foo
y := $(x) bar
x := later

其等价于:

y := foo bar
x := later

值得一提的是,这种方法,前面的变量不能使用后面的变量,只能使用前面已定义好了的变量。如果是这样:

y := $(x) bar
x := foo

那么,y的值是“bar”,而不是“foo bar”。

上面都是一些比较简单的变量使用了,让我们来看一个复杂的例子,其中包括了make的函数、条件表达式和一个系统变量“MAKELEVEL”的使用:

ifeq (0,${MAKELEVEL})
cur-dir := $(shell pwd)
whoami := $(shell whoami)
host-type := $(shell arch)
MAKE := ${MAKE} host-type=${host-type} whoami=${whoami}
endif

关于条件表达式和函数,我们在后面再说,对于系统变量“MAKELEVEL”,其意思是,如果我们的make有一个嵌套执行的动作(参见前面的“嵌套使用make”),那么,这个变量会记录了我们的当前Makefile的调用层数。

下面再介绍两个定义变量时我们需要知道的,请先看一个例子,如果我们要定义一个变量,其值是一个空格,那么我们可以这样来:

nullstring :=
space := $(nullstring) # end of the line

nullstring是一个Empty变量,其中什么也没有,而我们的space的值是一个空格。因为在操作符的右边是很难描述一个空格的,这里采用的技术很管用,先用一个Empty变量来标明变量的值开始了,而后面采用“#”注释符来表示变量定义的终止,这样,我们可以定义出其值是一个空格的变量。请注意这里关于“#”的使用,注释符“#”的这种特性值得我们注意,如果我们这样定义一个变量:

dir := /foo/bar # directory to put the frobs in

dir这个变量的值是“/foo/bar”,后面还跟了4个空格,如果我们这样使用这样变量来指定别的目录——“$(dir)/file”那么就完蛋了。

还有一个比较有用的操作符是“?=”,先看示例:

FOO ?= bar

其含义是,如果FOO没有被定义过,那么变量FOO的值就是“bar”,如果FOO先前被定义过,那么这条语将什么也不做,其等价于:

ifeq ($(origin FOO), undefined)
FOO = bar
endif


三、变量高级用法

这里介绍两种变量的高级使用方法,第一种是变量值的替换。

我们可以替换变量中的共有的部分,其格式是“$(var:a=b)”或是“${var:a=b}”,其意思是,把变量“var”中所有以“a”字串“结尾”的“a”替换成“b”字串。这里的“结尾”意思是“空格”或是“结束符”。

还是看一个示例吧:

foo := a.o b.o c.o
bar := $(foo:.o=.c)

这个示例中,我们先定义了一个“$(foo)”变量,而第二行的意思是把“$(foo)”中所有以“.o”字串“结尾”全部替换成“.c”,所以我们的“$(bar)”的值就是“a.c b.c c.c”。

另外一种变量替换的技术是以“静态模式”(参见前面章节)定义的,如:

foo := a.o b.o c.o
bar := $(foo:%.o=%.c)

这依赖于被替换字串中的有相同的模式,模式中必须包含一个“%”字符,这个例子同样让$(bar)变量的值为“a.c b.c c.c”。

第二种高级用法是——“把变量的值再当成变量”。先看一个例子:

x = y
y = z
a := $($(x))

在这个例子中,$(x)的值是“y”,所以$($(x))就是$(y),于是$(a)的值就是“z”。(注意,是“x=y”,而不是“x=$(y)”)

我们还可以使用更多的层次:

x = y
y = z
z = u
a := $($($(x)))

这里的$(a)的值是“u”,相关的推导留给读者自己去做吧。

让我们再复杂一点,使用上“在变量定义中使用变量”的第一个方式,来看一个例子:

x = $(y)
y = z
z = Hello
a := $($(x))

这里的$($(x))被替换成了$($(y)),因为$(y)值是“z”,所以,最终结果是:a:=$(z),也就是“Hello”。

再复杂一点,我们再加上函数:

x = variable1
variable2 := Hello
y = $(subst 1,2,$(x))
z = y
a := $($($(z)))

这个例子中,“$($($(z)))”扩展为“$($(y))”,而其再次被扩展为“$($(subst 1,2,$(x)))”。$(x)的值是“variable1”,subst函数把“variable1”中的所有“1”字串替换成“2”字串,于是,“variable1”变成“variable2”,再取其值,所以,最终,$(a)的值就是$(variable2)的值——“Hello”。(喔,好不容易)

在这种方式中,或要可以使用多个变量来组成一个变量的名字,然后再取其值:

first_second = Hello
a = first
b = second
all = $($a_$b)

这里的“$a_$b”组成了“first_second”,于是,$(all)的值就是“Hello”。

再来看看结合第一种技术的例子:

a_objects := a.o b.o c.o
1_objects := 1.o 2.o 3.o

sources := $($(a1)_objects:.o=.c)

这个例子中,如果$(a1)的值是“a”的话,那么,$(sources)的值就是“a.c b.c c.c”;如果$(a1)的值是“1”,那么$(sources)的值是“1.c 2.c 3.c”。

再来看一个这种技术和“函数”与“条件语句”一同使用的例子:

ifdef do_sort
func := sort
else
func := strip
endif

bar := a d b g q c

foo := $($(func) $(bar))

这个示例中,如果定义了“do_sort”,那么:foo := $(sort a d b g q c),于是$(foo)的值就是“a b c d g q”,而如果没有定义“do_sort”,那么:foo := $(sort a d b g q c),调用的就是strip函数。

当然,“把变量的值再当成变量”这种技术,同样可以用在操作符的左边:

dir = foo
$(dir)_sources := $(wildcard $(dir)/*.c)
define $(dir)_print
lpr $($(dir)_sources)
endef

这个例子中定义了三个变量:“dir”,“foo_sources”和“foo_print”。


四、追加变量值

我们可以使用“+=”操作符给变量追加值,如:

objects = main.o foo.o bar.o utils.o
objects += another.o

于是,我们的$(objects)值变成:“main.o foo.o bar.o utils.o another.o”(another.o被追加进去了)

使用“+=”操作符,可以模拟为下面的这种例子:

objects = main.o foo.o bar.o utils.o
objects := $(objects) another.o

所不同的是,用“+=”更为简洁。

如果变量之前没有定义过,那么,“+=”会自动变成“=”,如果前面有变量定义,那么“+=”会继承于前次操作的赋值符。如果前一次的是“:=”,那么“+=”会以“:=”作为其赋值符,如:

variable := value
variable += more

等价于:

variable := value
variable := $(variable) more

但如果是这种情况:

variable = value
variable += more

由于前次的赋值符是“=”,所以“+=”也会以“=”来做为赋值,那么岂不会发生变量的递补归定义,这是很不好的,所以make会自动为我们解决这个问题,我们不必担心这个问题。


五、override 指示符

如果有变量是通常make的命令行参数设置的,那么Makefile中对这个变量的赋值会被忽略。如果你想在Makefile中设置这类参数的值,那么,你可以使用“override”指示符。其语法是:

override <variable> = <value>

override <variable> := <value>

当然,你还可以追加:

override <variable> += <more text>

对于多行的变量定义,我们用define指示符,在define指示符前,也同样可以使用ovveride指示符,如:

override define foo
bar
endef

六、多行变量

还有一种设置变量值的方法是使用define关键字。使用define关键字设置变量的值可以有换行,这有利于定义一系列的命令(前面我们讲过“命令包”的技术就是利用这个关键字)。

define指示符后面跟的是变量的名字,而重起一行定义变量的值,定义是以endef关键字结束。其工作方式和“=”操作符一样。变量的值可以包含函数、命令、文字,或是其它变量。因为命令需要以[Tab]键开头,所以如果你用define定义的命令变量中没有以[Tab]键开头,那么make就不会把其认为是命令。

下面的这个示例展示了define的用法:

define two-lines
echo foo
echo $(bar)
endef

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
56 [报告]
发表于 2014-06-24 16:55 |只看该作者
七、环境变量

make运行时的系统环境变量可以在make开始运行时被载入到Makefile文件中,但是如果Makefile中已定义了这个变量,或是这个变量由make命令行带入,那么系统的环境变量的值将被覆盖。(如果make指定了“-e”参数,那么,系统环境变量将覆盖Makefile中定义的变量)

因此,如果我们在环境变量中设置了“CFLAGS”环境变量,那么我们就可以在所有的Makefile中使用这个变量了。这对于我们使用统一的编译参数有比较大的好处。如果Makefile中定义了CFLAGS,那么则会使用Makefile中的这个变量,如果没有定义则使用系统环境变量的值,一个共性和个性的统一,很像“全局变量”和“局部变量”的特性。

当make嵌套调用时(参见前面的“嵌套调用”章节),上层Makefile中定义的变量会以系统环境变量的方式传递到下层的Makefile中。当然,默认情况下,只有通过命令行设置的变量会被传递。而定义在文件中的变量,如果要向下层Makefile传递,则需要使用exprot关键字来声明。(参见前面章节)

当然,我并不推荐把许多的变量都定义在系统环境中,这样,在我们执行不用的Makefile时,拥有的是同一套系统变量,这可能会带来更多的麻烦。


八、目标变量

前面我们所讲的在Makefile中定义的变量都是“全局变量”,在整个文件,我们都可以访问这些变量。当然,“自动化变量”除外,如“$<”等这种类量的自动化变量就属于“规则型变量”,这种变量的值依赖于规则的目标和依赖目标的定义。

当然,我样同样可以为某个目标设置局部变量,这种变量被称为“Target-specific Variable”,它可以和“全局变量”同名,因为它的作用范围只在这条规则以及连带规则中,所以其值也只在作用范围内有效。而不会影响规则链以外的全局变量的值。

其语法是:

<target ...> : <variable-assignment>

<target ...> : overide <variable-assignment>

<variable-assignment>可以是前面讲过的各种赋值表达式,如“=”、“:=”、“+=”或是“?=”。第二个语法是针对于make命令行带入的变量,或是系统环境变量。

这个特性非常的有用,当我们设置了这样一个变量,这个变量会作用到由这个目标所引发的所有的规则中去。如:

prog : CFLAGS = -g
prog : prog.o foo.o bar.o
$(CC) $(CFLAGS) prog.o foo.o bar.o

prog.o : prog.c
$(CC) $(CFLAGS) prog.c

foo.o : foo.c
$(CC) $(CFLAGS) foo.c

bar.o : bar.c
$(CC) $(CFLAGS) bar.c

在这个示例中,不管全局的$(CFLAGS)的值是什么,在prog目标,以及其所引发的所有规则中(prog.o foo.o bar.o的规则),$(CFLAGS)的值都是“-g”


九、模式变量

在GNU的make中,还支持模式变量(Pattern-specific Variable),通过上面的目标变量中,我们知道,变量可以定义在某个目标上。模式变量的好处就是,我们可以给定一种“模式”,可以把变量定义在符合这种模式的所有目标上。

我们知道,make的“模式”一般是至少含有一个“%”的,所以,我们可以以如下方式给所有以[.o]结尾的目标定义目标变量:

%.o : CFLAGS = -O

同样,模式变量的语法和“目标变量”一样:

<pattern ...> : <variable-assignment>

<pattern ...> : override <variable-assignment>

override同样是针对于系统环境传入的变量,或是make命令行指定的变量。

使用条件判断
——————

使用条件判断,可以让make根据运行时的不同情况选择不同的执行分支。条件表达式可以是比较变量的值,或是比较变量和常量的值。

一、示例

下面的例子,判断$(CC)变量是否“gcc”,如果是的话,则使用GNU函数编译目标。

libs_for_gcc = -lgnu
normal_libs =

foo: $(objects)
ifeq ($(CC),gcc)
$(CC) -o foo $(objects) $(libs_for_gcc)
else
$(CC) -o foo $(objects) $(normal_libs)
endif

可见,在上面示例的这个规则中,目标“foo”可以根据变量“$(CC)”值来选取不同的函数库来编译程序。

我们可以从上面的示例中看到三个关键字:ifeq、else和endif。ifeq的意思表示条件语句的开始,并指定一个条件表达式,表达式包含两个参数,以逗号分隔,表达式以圆括号括起。else表示条件表达式为假的情况。endif表示一个条件语句的结束,任何一个条件表达式都应该以endif结束。

当我们的变量$(CC)值是“gcc”时,目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(libs_for_gcc)

而当我们的变量$(CC)值不是“gcc”时(比如“cc”),目标foo的规则是:

foo: $(objects)
$(CC) -o foo $(objects) $(normal_libs)

当然,我们还可以把上面的那个例子写得更简洁一些:

libs_for_gcc = -lgnu
normal_libs =

ifeq ($(CC),gcc)
libs=$(libs_for_gcc)
else
libs=$(normal_libs)
endif

foo: $(objects)
$(CC) -o foo $(objects) $(libs)


二、语法

条件表达式的语法为:

<conditional-directive>
<text-if-true>
endif

以及:

<conditional-directive>
<text-if-true>
else
<text-if-false>
endif

其中<conditional-directive>表示条件关键字,如“ifeq”。这个关键字有四个。

第一个是我们前面所见过的“ifeq”

ifeq (<arg1>, <arg2> )
ifeq '<arg1>' '<arg2>'
ifeq "<arg1>" "<arg2>"
ifeq "<arg1>" '<arg2>'
ifeq '<arg1>' "<arg2>"

比较参数“arg1”和“arg2”的值是否相同。当然,参数中我们还可以使用make的函数。如:

ifeq ($(strip $(foo)),)
<text-if-empty>
endif

这个示例中使用了“strip”函数,如果这个函数的返回值是空(Empty),那么<text-if-empty>就生效。

第二个条件关键字是“ifneq”。语法是:

ifneq (<arg1>, <arg2> )
ifneq '<arg1>' '<arg2>'
ifneq "<arg1>" "<arg2>"
ifneq "<arg1>" '<arg2>'
ifneq '<arg1>' "<arg2>"

其比较参数“arg1”和“arg2”的值是否相同,如果不同,则为真。和“ifeq”类似。

第三个条件关键字是“ifdef”。语法是:

ifdef <variable-name>

如果变量<variable-name>的值非空,那到表达式为真。否则,表达式为假。当然,<variable-name>同样可以是一个函数的返回值。注意,ifdef只是测试一个变量是否有值,其并不会把变量扩展到当前位置。还是来看两个例子:

示例一:
bar =
foo = $(bar)
ifdef foo
frobozz = yes
else
frobozz = no
endif

示例二:
foo =
ifdef foo
frobozz = yes
else
frobozz = no
endif

第一个例子中,“$(frobozz)”值是“yes”,第二个则是“no”。

第四个条件关键字是“ifndef”。其语法是:

ifndef <variable-name>

这个我就不多说了,和“ifdef”是相反的意思。

在<conditional-directive>这一行上,多余的空格是被允许的,但是不能以[Tab]键做为开始(不然就被认为是命令)。而注释符“#”同样也是安全的。“else”和“endif”也一样,只要不是以[Tab]键开始就行了。

特别注意的是,make是在读取Makefile时就计算条件表达式的值,并根据条件表达式的值来选择语句,所以,你最好不要把自动化变量(如“$@”等)放入条件表达式中,因为自动化变量是在运行时才有的。

而且,为了避免混乱,make不允许把整个条件语句分成两部分放在不同的文件中。



使用函数
————

在Makefile中可以使用函数来处理变量,从而让我们的命令或是规则更为的灵活和具有智能。make所支持的函数也不算很多,不过已经足够我们的操作了。函数调用后,函数的返回值可以当做变量来使用。


一、函数的调用语法

函数调用,很像变量的使用,也是以“$”来标识的,其语法如下:

$(<function> <arguments> )

或是

${<function> <arguments>}

这里,<function>就是函数名,make支持的函数不多。<arguments>是函数的参数,参数间以逗号“,”分隔,而函数名和参数之间以“空格”分隔。函数调用以“$”开头,以圆括号或花括号把函数名和参数括起。感觉很像一个变量,是不是?函数中的参数可以使用变量,为了风格的统一,函数和变量的括号最好一样,如使用“$(subst a,b,$(x))”这样的形式,而不是“$(subst a,b,${x})”的形式。因为统一会更清楚,也会减少一些不必要的麻烦。

还是来看一个示例:

comma:= ,
empty:=
space:= $(empty) $(empty)
foo:= a b c
bar:= $(subst $(space),$(comma),$(foo))

在这个示例中,$(comma)的值是一个逗号。$(space)使用了$(empty)定义了一个空格,$(foo)的值是“a b c”,$(bar)的定义用,调用了函数“subst”,这是一个替换函数,这个函数有三个参数,第一个参数是被替换字串,第二个参数是替换字串,第三个参数是替换操作作用的字串。这个函数也就是把$(foo)中的空格替换成逗号,所以$(bar)的值是“a,b,c”。


二、字符串处理函数

$(subst <from>,<to>,<text> )

名称:字符串替换函数——subst。
功能:把字串<text>中的<from>字符串替换成<to>。
返回:函数返回被替换过后的字符串。

示例:

$(subst ee,EE,feet on the street),

把“feet on the street”中的“ee”替换成“EE”,返回结果是“fEEt on the strEEt”。


$(patsubst <pattern>,<replacement>,<text> )

名称:模式字符串替换函数——patsubst。
功能:查找<text>中的单词(单词以“空格”、“Tab”或“回车”“换行”分隔)是否符合模式<pattern>,如果匹配的话,则以<replacement>替换。这里,<pattern>可以包括通配符“%”,表示任意长度的字串。如果<replacement>中也包含“%”,那么,<replacement>中的这个“%”将是<pattern>中的那个“%”所代表的字串。(可以用“/”来转义,以“/%”来表示真实含义的“%”字符)
返回:函数返回被替换过后的字符串。

示例:

$(patsubst %.c,%.o,x.c.c bar.c)

把字串“x.c.c bar.c”符合模式[%.c]的单词替换成[%.o],返回结果是“x.c.o bar.o”

备注:

这和我们前面“变量章节”说过的相关知识有点相似。如:

“$(var:<pattern>=<replacement> )”
相当于
“$(patsubst <pattern>,<replacement>,$(var))”,

而“$(var: <suffix>=<replacement> )”
则相当于
“$(patsubst %<suffix>,%<replacement>,$(var))”。

例如有:objects = foo.o bar.o baz.o,
那么,“$(objects:.o=.c)”和“$(patsubst %.o,%.c,$(objects))”是一样的。

$(strip <string> )

名称:去空格函数——strip。
功能:去掉<string>字串中开头和结尾的空字符。
返回:返回被去掉空格的字符串值。
示例:

$(strip a b c )

把字串“a b c ”去到开头和结尾的空格,结果是“a b c”。

$(findstring <find>,<in> )

名称:查找字符串函数——findstring。
功能:在字串<in>中查找<find>字串。
返回:如果找到,那么返回<find>,否则返回空字符串。
示例:

$(findstring a,a b c)
$(findstring a,b c)

第一个函数返回“a”字符串,第二个返回“”字符串(空字符串)

$(filter <pattern...>,<text> )

名称:过滤函数——filter。
功能:以<pattern>模式过滤<text>字符串中的单词,保留符合模式<pattern>的单词。可以有多个模式。
返回:返回符合模式<pattern>的字串。
示例:

sources := foo.c bar.c baz.s ugh.h
foo: $(sources)
cc $(filter %.c %.s,$(sources)) -o foo

$(filter %.c %.s,$(sources))返回的值是“foo.c bar.c baz.s”。

$(filter-out <pattern...>,<text> )

名称:反过滤函数——filter-out。
功能:以<pattern>模式过滤<text>字符串中的单词,去除符合模式<pattern>的单词。可以有多个模式。
返回:返回不符合模式<pattern>的字串。
示例:

objects=main1.o foo.o main2.o bar.o
mains=main1.o main2.o

$(filter-out $(mains),$(objects)) 返回值是“foo.o bar.o”。

$(sort <list> )

名称:排序函数——sort。
功能:给字符串<list>中的单词排序(升序)。
返回:返回排序后的字符串。
示例:$(sort foo bar lose)返回“bar foo lose” 。
备注:sort函数会去掉<list>中相同的单词。

$(word <n>,<text> )

名称:取单词函数——word。
功能:取字符串<text>中第<n>个单词。(从一开始)
返回:返回字符串<text>中第<n>个单词。如果<n>比<text>中的单词数要大,那么返回空字符串。
示例:$(word 2, foo bar baz)返回值是“bar”。

$(wordlist <s>,<e>,<text> )

名称:取单词串函数——wordlist。
功能:从字符串<text>中取从<s>开始到<e>的单词串。<s>和<e>是一个数字。
返回:返回字符串<text>中从<s>到<e>的单词字串。如果<s>比<text>中的单词数要大,那么返回空字符串。如果<e>大于<text>的单词数,那么返回从<s>开始,到<text>结束的单词串。
示例: $(wordlist 2, 3, foo bar baz)返回值是“bar baz”。

$(words <text> )

名称:单词个数统计函数——words。
功能:统计<text>中字符串中的单词个数。
返回:返回<text>中的单词数。
示例:$(words, foo bar baz)返回值是“3”。
备注:如果我们要取<text>中最后的一个单词,我们可以这样:$(word $(words <text> ),<text> )。

$(firstword <text> )

名称:首单词函数——firstword。
功能:取字符串<text>中的第一个单词。
返回:返回字符串<text>的第一个单词。
示例:$(firstword foo bar)返回值是“foo”。
备注:这个函数可以用word函数来实现:$(word 1,<text> )。

以上,是所有的字符串操作函数,如果搭配混合使用,可以完成比较复杂的功能。这里,举一个现实中应用的例子。我们知道,make使用“VPATH”变量来指定“依赖文件”的搜索路径。于是,我们可以利用这个搜索路径来指定编译器对头文件的搜索路径参数CFLAGS,如:

override CFLAGS += $(patsubst %,-I%,$(subst :, ,$(VPATH)))

如果我们的“$(VPATH)”值是“src:../headers”,那么“$(patsubst %,-I%,$(subst :, ,$(VPATH)))”将返回“-Isrc -I../headers”,这正是cc或gcc搜索头文件路径的参数。

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
57 [报告]
发表于 2014-06-24 16:56 |只看该作者
三、文件名操作函数

下面我们要介绍的函数主要是处理文件名的。每个函数的参数字符串都会被当做一个或是一系列的文件名来对待。

$(dir <names...> )

名称:取目录函数——dir。
功能:从文件名序列<names>中取出目录部分。目录部分是指最后一个反斜杠(“/”)之前的部分。如果没有反斜杠,那么返回“./”。
返回:返回文件名序列<names>的目录部分。
示例: $(dir src/foo.c hacks)返回值是“src/ ./”。

$(notdir <names...> )

名称:取文件函数——notdir。
功能:从文件名序列<names>中取出非目录部分。非目录部分是指最后一个反斜杠(“/”)之后的部分。
返回:返回文件名序列<names>的非目录部分。
示例: $(notdir src/foo.c hacks)返回值是“foo.c hacks”。

$(suffix <names...> )

名称:取后缀函数——suffix。
功能:从文件名序列<names>中取出各个文件名的后缀。
返回:返回文件名序列<names>的后缀序列,如果文件没有后缀,则返回空字串。
示例:$(suffix src/foo.c src-1.0/bar.c hacks)返回值是“.c .c”。

$(basename <names...> )

名称:取前缀函数——basename。
功能:从文件名序列<names>中取出各个文件名的前缀部分。
返回:返回文件名序列<names>的前缀序列,如果文件没有前缀,则返回空字串。
示例:$(basename src/foo.c src-1.0/bar.c hacks)返回值是“src/foo src-1.0/bar hacks”。

$(addsuffix <suffix>,<names...> )

名称:加后缀函数——addsuffix。
功能:把后缀<suffix>加到<names>中的每个单词后面。
返回:返回加过后缀的文件名序列。
示例:$(addsuffix .c,foo bar)返回值是“foo.c bar.c”。

$(addprefix <prefix>,<names...> )

名称:加前缀函数——addprefix。
功能:把前缀<prefix>加到<names>中的每个单词后面。
返回:返回加过前缀的文件名序列。
示例:$(addprefix src/,foo bar)返回值是“src/foo src/bar”。

$(join <list1>,<list2> )

名称:连接函数——join。
功能:把<list2>中的单词对应地加到<list1>的单词后面。如果<list1>的单词个数要比<list2>的多,那么,<list1>中的多出来的单词将保持原样。如果<list2>的单词个数要比<list1>多,那么,<list2>多出来的单词将被复制到<list2>中。
返回:返回连接过后的字符串。
示例:$(join aaa bbb , 111 222 333)返回值是“aaa111 bbb222 333”。



四、foreach 函数


foreach函数和别的函数非常的不一样。因为这个函数是用来做循环用的,Makefile中的foreach函数几乎是仿照于Unix标准Shell(/bin/sh)中的for语句,或是C-Shell(/bin/csh)中的foreach语句而构建的。它的语法是:



$(foreach <var>,<list>,<text> )



这个函数的意思是,把参数<list>中的单词逐一取出放到参数<var>所指定的变量中,然后再执行<text>所包含的表达式。每一次<text>会返回一个字符串,循环过程中,<text>的所返回的每个字符串会以空格分隔,最后当整个循环结束时,<text>所返回的每个字符串所组成的整个字符串(以空格分隔)将会是foreach函数的返回值。



所以,<var>最好是一个变量名,<list>可以是一个表达式,而<text>中一般会使用<var>这个参数来依次枚举<list>中的单词。举个例子:



names := a b c d

files := $(foreach n,$(names),$(n).o)



上面的例子中,$(name)中的单词会被挨个取出,并存到变量“n”中,“$(n).o”每次根据“$(n)”计算出一个值,这些值以空格分隔,最后作为foreach函数的返回,所以,$(files)的值是“a.o b.o c.o d.o”。



注意,foreach中的<var>参数是一个临时的局部变量,foreach函数执行完后,参数<var>的变量将不在作用,其作用域只在foreach函数当中。





五、if 函数


if函数很像GNU的make所支持的条件语句——ifeq(参见前面所述的章节),if函数的语法是:



$(if <condition>,<then-part> )



或是



$(if <condition>,<then-part>,<else-part> )



可见,if函数可以包含“else”部分,或是不含。即if函数的参数可以是两个,也可以是三个。<condition>参数是if的表达式,如果其返回的为非空字符串,那么这个表达式就相当于返回真,于是,<then-part>会被计算,否则<else-part>会被计算。



而if函数的返回值是,如果<condition>为真(非空字符串),那个<then-part>会是整个函数的返回值,如果<condition>为假(空字符串),那么<else-part>会是整个函数的返回值,此时如果<else-part>没有被定义,那么,整个函数返回空字串。



所以,<then-part>和<else-part>只会有一个被计算。





六、call函数


call函数是唯一一个可以用来创建新的参数化的函数。你可以写一个非常复杂的表达式,这个表达式中,你可以定义许多参数,然后你可以用call函数来向这个表达式传递参数。其语法是:



$(call <expression>,<parm1>,<parm2>,<parm3>...)



当make执行这个函数时,<expression>参数中的变量,如$(1),$(2),$(3)等,会被参数<parm1>,<parm2>,<parm3>依次取代。而<expression>的返回值就是call函数的返回值。例如:

reverse = $(1) $(2)

foo = $(call reverse,a,b)



那么,foo的值就是“a b”。当然,参数的次序是可以自定义的,不一定是顺序的,如:



reverse = $(2) $(1)

foo = $(call reverse,a,b)



此时的foo的值就是“b a”。





七、origin函数
origin函数不像其它的函数,他并不操作变量的值,他只是告诉你你的这个变量是哪里来的?其语法是:



$(origin <variable> )



注意,<variable>是变量的名字,不应该是引用。所以你最好不要在<variable>中使用“$”字符。Origin函数会以其返回值来告诉你这个变量的“出生情况”,下面,是origin函数的返回值:



“undefined”

如果<variable>从来没有定义过,origin函数返回这个值“undefined”。



“default”

如果<variable>是一个默认的定义,比如“CC”这个变量,这种变量我们将在后面讲述。



“environment”

如果<variable>是一个环境变量,并且当Makefile被执行时,“-e”参数没有被打开。



“file”

如果<variable>这个变量被定义在Makefile中。



“command line”

如果<variable>这个变量是被命令行定义的。



“override”

如果<variable>是被override指示符重新定义的。



“automatic”

如果<variable>是一个命令运行中的自动化变量。关于自动化变量将在后面讲述。



这些信息对于我们编写Makefile是非常有用的,例如,假设我们有一个Makefile其包了一个定义文件Make.def,在Make.def中定义了一个变量“bletch”,而我们的环境中也有一个环境变量“bletch”,此时,我们想判断一下,如果变量来源于环境,那么我们就把之重定义了,如果来源于Make.def或是命令行等非环境的,那么我们就不重新定义它。于是,在我们的Makefile中,我们可以这样写:



ifdef bletch

ifeq "$(origin bletch)" "environment"

bletch = barf, gag, etc.

endif

endif



当然,你也许会说,使用override关键字不就可以重新定义环境中的变量了吗?为什么需要使用这样的步骤?是的,我们用override是可以达到这样的效果,可是override过于粗暴,它同时会把从命令行定义的变量也覆盖了,而我们只想重新定义环境传来的,而不想重新定义命令行传来的。





八、shell函数


shell函数也不像其它的函数。顾名思义,它的参数应该就是操作系统Shell的命令。它和反引号“`”是相同的功能。这就是说,shell函数把执行操作系统命令后的输出作为函数返回。于是,我们可以用操作系统命令以及字符串处理命令awk,sed等等命令来生成一个变量,如:



contents := $(shell cat foo)



files := $(shell echo *.c)



注意,这个函数会新生成一个Shell程序来执行命令,所以你要注意其运行性能,如果你的Makefile中有一些比较复杂的规则,并大量使用了这个函数,那么对于你的系统性能是有害的。特别是Makefile的隐晦的规则可能会让你的shell函数执行的次数比你想像的多得多。





九、控制make的函数


make提供了一些函数来控制make的运行。通常,你需要检测一些运行Makefile时的运行时信息,并且根据这些信息来决定,你是让make继续执行,还是停止。



$(error <text ...> )



产生一个致命的错误,<text ...>是错误信息。注意,error函数不会在一被使用就会产生错误信息,所以如果你把其定义在某个变量中,并在后续的脚本中使用这个变量,那么也是可以的。例如:



示例一:

ifdef ERROR_001

$(error error is $(ERROR_001))

endif



示例二:

ERR = $(error found an error!)

.PHONY: err

err: ; $(ERR)



示例一会在变量ERROR_001定义了后执行时产生error调用,而示例二则在目录err被执行时才发生error调用。



$(warning <text ...> )



这个函数很像error函数,只是它并不会让make退出,只是输出一段警告信息,而make继续执行。

make 的运行
——————

一般来说,最简单的就是直接在命令行下输入make命令,make命令会找当前目录的makefile来执行,一切都是自动的。但也有时你也许只想让make重编译某些文件,而不是整个工程,而又有的时候你有几套编译规则,你想在不同的时候使用不同的编译规则,等等。本章节就是讲述如何使用make命令的。

一、make的退出码

make命令执行后有三个退出码:

0 —— 表示成功执行。
1 —— 如果make运行时出现任何错误,其返回1。
2 —— 如果你使用了make的“-q”选项,并且make使得一些目标不需要更新,那么返回2。

Make的相关参数我们会在后续章节中讲述。


二、指定Makefile

前面我们说过,GNU make找寻默认的Makefile的规则是在当前目录下依次找三个文件——“GNUmakefile”、“makefile”和“Makefile”。其按顺序找这三个文件,一旦找到,就开始读取这个文件并执行。

当前,我们也可以给make命令指定一个特殊名字的Makefile。要达到这个功能,我们要使用make的“-f”或是“--file”参数(“--makefile”参数也行)。例如,我们有个makefile的名字是“hchen.mk”,那么,我们可以这样来让make来执行这个文件:

make –f hchen.mk

如果在make的命令行是,你不只一次地使用了“-f”参数,那么,所有指定的makefile将会被连在一起传递给make执行。


三、指定目标

一般来说,make的最终目标是makefile中的第一个目标,而其它目标一般是由这个目标连带出来的。这是make的默认行为。当然,一般来说,你的makefile中的第一个目标是由许多个目标组成,你可以指示make,让其完成你所指定的目标。要达到这一目的很简单,需在make命令后直接跟目标的名字就可以完成(如前面提到的“make clean”形式)

任何在makefile中的目标都可以被指定成终极目标,但是除了以“-”打头,或是包含了“=”的目标,因为有这些字符的目标,会被解析成命令行参数或是变量。甚至没有被我们明确写出来的目标也可以成为make的终极目标,也就是说,只要make可以找到其隐含规则推导规则,那么这个隐含目标同样可以被指定成终极目标。

有一个make的环境变量叫“MAKECMDGOALS”,这个变量中会存放你所指定的终极目标的列表,如果在命令行上,你没有指定目标,那么,这个变量是空值。这个变量可以让你使用在一些比较特殊的情形下。比如下面的例子:

sources = foo.c bar.c
ifneq ( $(MAKECMDGOALS),clean)
include $(sources:.c=.d)
endif

基于上面的这个例子,只要我们输入的命令不是“make clean”,那么makefile会自动包含“foo.d”和“bar.d”这两个makefile。

使用指定终极目标的方法可以很方便地让我们编译我们的程序,例如下面这个例子:

.PHONY: all
all: prog1 prog2 prog3 prog4

从这个例子中,我们可以看到,这个makefile中有四个需要编译的程序——“prog1”, “prog2”, “prog3”和 “prog4”,我们可以使用“make all”命令来编译所有的目标(如果把all置成第一个目标,那么只需执行“make”),我们也可以使用“make prog2”来单独编译目标“prog2”。

即然make可以指定所有makefile中的目标,那么也包括“伪目标”,于是我们可以根据这种性质来让我们的makefile根据指定的不同的目标来完成不同的事。在Unix世界中,软件发布时,特别是GNU这种开源软件的发布时,其makefile都包含了编译、安装、打包等功能。我们可以参照这种规则来书写我们的makefile中的目标。

“all”
这个伪目标是所有目标的目标,其功能一般是编译所有的目标。
“clean”
这个伪目标功能是删除所有被make创建的文件。
“install”
这个伪目标功能是安装已编译好的程序,其实就是把目标执行文件拷贝到指定的目标中去。
“print”
这个伪目标的功能是例出改变过的源文件。
“tar”
这个伪目标功能是把源程序打包备份。也就是一个tar文件。
“dist”
这个伪目标功能是创建一个压缩文件,一般是把tar文件压成Z文件。或是gz文件。
“TAGS”
这个伪目标功能是更新所有的目标,以备完整地重编译使用。
“check”和“test”
这两个伪目标一般用来测试makefile的流程。

当然一个项目的makefile中也不一定要书写这样的目标,这些东西都是GNU的东西,但是我想,GNU搞出这些东西一定有其可取之处(等你的UNIX下的程序文件一多时你就会发现这些功能很有用了),这里只不过是说明了,如果你要书写这种功能,最好使用这种名字命名你的目标,这样规范一些,规范的好处就是——不用解释,大家都明白。而且如果你的makefile中有这些功能,一是很实用,二是可以显得你的makefile很专业(不是那种初学者的作品)。

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
58 [报告]
发表于 2014-06-24 16:56 |只看该作者
四、检查规则

有时候,我们不想让我们的makefile中的规则执行起来,我们只想检查一下我们的命令,或是执行的序列。于是我们可以使用make命令的下述参数:

“-n”
“--just-print”
“--dry-run”
“--recon”
不执行参数,这些参数只是打印命令,不管目标是否更新,把规则和连带规则下的命令打印出来,但不执行,这些参数对于我们调试makefile很有用处。

“-t”
“--touch”
这个参数的意思就是把目标文件的时间更新,但不更改目标文件。也就是说,make假装编译目标,但不是真正的编译目标,只是把目标变成已编译过的状态。

“-q”
“--question”
这个参数的行为是找目标的意思,也就是说,如果目标存在,那么其什么也不会输出,当然也不会执行编译,如果目标不存在,其会打印出一条出错信息。

“-W <file>”
“--what-if=<file>”
“--assume-new=<file>”
“--new-file=<file>”
这个参数需要指定一个文件。一般是是源文件(或依赖文件),Make会根据规则推导来运行依赖于这个文件的命令,一般来说,可以和“-n”参数一同使用,来查看这个依赖文件所发生的规则命令。

另外一个很有意思的用法是结合“-p”和“-v”来输出makefile被执行时的信息(这个将在后面讲述)。


五、make的参数

下面列举了所有GNU make 3.80版的参数定义。其它版本和产商的make大同小异,不过其它产商的make的具体参数还是请参考各自的产品文档。

“-b”
“-m”
这两个参数的作用是忽略和其它版本make的兼容性。

“-B”
“--always-make”
认为所有的目标都需要更新(重编译)。

“-C <dir>”
“--directory=<dir>”
指定读取makefile的目录。如果有多个“-C”参数,make的解释是后面的路径以前面的作为相对路径,并以最后的目录作为被指定目录。如:“make –C ~hchen/test –C prog”等价于“make –C ~hchen/test/prog”。

“—debug[=<options>]”
输出make的调试信息。它有几种不同的级别可供选择,如果没有参数,那就是输出最简单的调试信息。下面是<options>的取值:
a —— 也就是all,输出所有的调试信息。(会非常的多)
b —— 也就是basic,只输出简单的调试信息。即输出不需要重编译的目标。
v —— 也就是verbose,在b选项的级别之上。输出的信息包括哪个makefile被解析,不需要被重编译的依赖文件(或是依赖目标)等。
i —— 也就是implicit,输出所以的隐含规则。
j —— 也就是jobs,输出执行规则中命令的详细信息,如命令的PID、返回码等。
m —— 也就是makefile,输出make读取makefile,更新makefile,执行makefile的信息。

“-d”
相当于“--debug=a”。

“-e”
“--environment-overrides”
指明环境变量的值覆盖makefile中定义的变量的值。

“-f=<file>”
“--file=<file>”
“--makefile=<file>”
指定需要执行的makefile。

“-h”
“--help”
显示帮助信息。

“-i”
“--ignore-errors”
在执行时忽略所有的错误。

“-I <dir>”
“--include-dir=<dir>”
指定一个被包含makefile的搜索目标。可以使用多个“-I”参数来指定多个目录。

“-j [<jobsnum>]”
“--jobs[=<jobsnum>]”
指同时运行命令的个数。如果没有这个参数,make运行命令时能运行多少就运行多少。如果有一个以上的“-j”参数,那么仅最后一个“-j”才是有效的。(注意这个参数在MS-DOS中是无用的)

“-k”
“--keep-going”
出错也不停止运行。如果生成一个目标失败了,那么依赖于其上的目标就不会被执行了。

“-l <load>”
“--load-average[=<load]”
“—max-load[=<load>]”
指定make运行命令的负载。

“-n”
“--just-print”
“--dry-run”
“--recon”
仅输出执行过程中的命令序列,但并不执行。

“-o <file>”
“--old-file=<file>”
“--assume-old=<file>”
不重新生成的指定的<file>,即使这个目标的依赖文件新于它。

“-p”
“--print-data-base”
输出makefile中的所有数据,包括所有的规则和变量。这个参数会让一个简单的makefile都会输出一堆信息。如果你只是想输出信息而不想执行makefile,你可以使用“make -qp”命令。如果你想查看执行makefile前的预设变量和规则,你可以使用“make –p –f /dev/null”。这个参数输出的信息会包含着你的makefile文件的文件名和行号,所以,用这个参数来调试你的makefile会是很有用的,特别是当你的环境变量很复杂的时候。

“-q”
“--question”
不运行命令,也不输出。仅仅是检查所指定的目标是否需要更新。如果是0则说明要更新,如果是2则说明有错误发生。

“-r”
“--no-builtin-rules”
禁止make使用任何隐含规则。

“-R”
“--no-builtin-variabes”
禁止make使用任何作用于变量上的隐含规则。

“-s”
“--silent”
“--quiet”
在命令运行时不输出命令的输出。

“-S”
“--no-keep-going”
“--stop”
取消“-k”选项的作用。因为有些时候,make的选项是从环境变量“MAKEFLAGS”中继承下来的。所以你可以在命令行中使用这个参数来让环境变量中的“-k”选项失效。

“-t”
“--touch”
相当于UNIX的touch命令,只是把目标的修改日期变成最新的,也就是阻止生成目标的命令运行。

“-v”
“--version”
输出make程序的版本、版权等关于make的信息。

“-w”
“--print-directory”
输出运行makefile之前和之后的信息。这个参数对于跟踪嵌套式调用make时很有用。

“--no-print-directory”
禁止“-w”选项。

“-W <file>”
“--what-if=<file>”
“--new-file=<file>”
“--assume-file=<file>”
假定目标<file>需要更新,如果和“-n”选项使用,那么这个参数会输出该目标更新时的运行动作。如果没有“-n”那么就像运行UNIX的“touch”命令一样,使得<file>的修改时间为当前时间。

“--warn-undefined-variables”
只要make发现有未定义的变量,那么就输出警告信息。

隐含规则
————

在我们使用Makefile时,有一些我们会经常使用,而且使用频率非常高的东西,比如,我们编译C/C++的源程序为中间目标文件(Unix下是[.o]文件,Windows下是[.obj]文件)。本章讲述的就是一些在Makefile中的“隐含的”,早先约定了的,不需要我们再写出来的规则。

“隐含规则”也就是一种惯例,make会按照这种“惯例”心照不喧地来运行,那怕我们的Makefile中没有书写这样的规则。例如,把[.c]文件编译成[.o]文件这一规则,你根本就不用写出来,make会自动推导出这种规则,并生成我们需要的[.o]文件。

“隐含规则”会使用一些我们系统变量,我们可以改变这些系统变量的值来定制隐含规则的运行时的参数。如系统变量“CFLAGS”可以控制编译时的编译器参数。

我们还可以通过“模式规则”的方式写下自己的隐含规则。用“后缀规则”来定义隐含规则会有许多的限制。使用“模式规则”会更回得智能和清楚,但“后缀规则”可以用来保证我们Makefile的兼容性。
我们了解了“隐含规则”,可以让其为我们更好的服务,也会让我们知道一些“约定俗成”了的东西,而不至于使得我们在运行Makefile时出现一些我们觉得莫名其妙的东西。当然,任何事物都是矛盾的,水能载舟,亦可覆舟,所以,有时候“隐含规则”也会给我们造成不小的麻烦。只有了解了它,我们才能更好地使用它。


一、使用隐含规则

如果要使用隐含规则生成你需要的目标,你所需要做的就是不要写出这个目标的规则。那么,make会试图去自动推导产生这个目标的规则和命令,如果make可以自动推导生成这个目标的规则和命令,那么这个行为就是隐含规则的自动推导。当然,隐含规则是make事先约定好的一些东西。例如,我们有下面的一个Makefile:

foo : foo.o bar.o
cc –o foo foo.o bar.o $(CFLAGS) $(LDFLAGS)

我们可以注意到,这个Makefile中并没有写下如何生成foo.o和bar.o这两目标的规则和命令。因为make的“隐含规则”功能会自动为我们自动去推导这两个目标的依赖目标和生成命令。

make会在自己的“隐含规则”库中寻找可以用的规则,如果找到,那么就会使用。如果找不到,那么就会报错。在上面的那个例子中,make调用的隐含规则是,把[.o]的目标的依赖文件置成[.c],并使用C的编译命令“cc –c $(CFLAGS) [.c]”来生成[.o]的目标。也就是说,我们完全没有必要写下下面的两条规则:

foo.o : foo.c
cc –c foo.c $(CFLAGS)
bar.o : bar.c
cc –c bar.c $(CFLAGS)

因为,这已经是“约定”好了的事了,make和我们约定好了用C编译器“cc”生成[.o]文件的规则,这就是隐含规则。

当然,如果我们为[.o]文件书写了自己的规则,那么make就不会自动推导并调用隐含规则,它会按照我们写好的规则忠实地执行。

还有,在make的“隐含规则库”中,每一条隐含规则都在库中有其顺序,越靠前的则是越被经常使用的,所以,这会导致我们有些时候即使我们显示地指定了目标依赖,make也不会管。如下面这条规则(没有命令):

foo.o : foo.p

依赖文件“foo.p”(Pascal程序的源文件)有可能变得没有意义。如果目录下存在了“foo.c”文件,那么我们的隐含规则一样会生效,并会通过“foo.c”调用C的编译器生成foo.o文件。因为,在隐含规则中,Pascal的规则出现在C的规则之后,所以,make找到可以生成foo.o的C的规则就不再寻找下一条规则了。如果你确实不希望任何隐含规则推导,那么,你就不要只写出“依赖规则”,而不写命令。


二、隐含规则一览

这里我们将讲述所有预先设置(也就是make内建)的隐含规则,如果我们不明确地写下规则,那么,make就会在这些规则中寻找所需要规则和命令。当然,我们也可以使用make的参数“-r”或“--no-builtin-rules”选项来取消所有的预设置的隐含规则。

当然,即使是我们指定了“-r”参数,某些隐含规则还是会生效,因为有许多的隐含规则都是使用了“后缀规则”来定义的,所以,只要隐含规则中有“后缀列表”(也就一系统定义在目标.SUFFIXES的依赖目标),那么隐含规则就会生效。默认的后缀列表是:.out, .a, .ln, .o, .c, .cc, .C, .p, .f, .F, .r, .y, .l, .s, .S, .mod, .sym, .def, .h, .info, .dvi, .tex, .texinfo, .texi, .txinfo, .w, .ch .web, .sh, .elc, .el。具体的细节,我们会在后面讲述。

还是先来看一看常用的隐含规则吧。

1、编译C程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.c”,并且其生成命令是“$(CC) –c $(CPPFLAGS) $(CFLAGS)”

2、编译C++程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.cc”或是“<n>.C”,并且其生成命令是“$(CXX) –c $(CPPFLAGS) $(CFLAGS)”。(建议使用“.cc”作为C++源文件的后缀,而不是“.C”)

3、编译Pascal程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.p”,并且其生成命令是“$(PC) –c $(PFLAGS)”。

4、编译Fortran/Ratfor程序的隐含规则。
“<n>.o”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”或“<n>.f”,并且其生成命令是:
“.f” “$(FC) –c $(FFLAGS)”
“.F” “$(FC) –c $(FFLAGS) $(CPPFLAGS)”
“.f” “$(FC) –c $(FFLAGS) $(RFLAGS)”

5、预处理Fortran/Ratfor程序的隐含规则。
“<n>.f”的目标的依赖目标会自动推导为“<n>.r”或“<n>.F”。这个规则只是转换Ratfor或有预处理的Fortran程序到一个标准的Fortran程序。其使用的命令是:
“.F” “$(FC) –F $(CPPFLAGS) $(FFLAGS)”
“.r” “$(FC) –F $(FFLAGS) $(RFLAGS)”

6、编译Modula-2程序的隐含规则。
“<n>.sym”的目标的依赖目标会自动推导为“<n>.def”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(DEFFLAGS)”。“<n.o>” 的目标的依赖目标会自动推导为“<n>.mod”,并且其生成命令是:“$(M2C) $(M2FLAGS) $(MODFLAGS)”。

7、汇编和汇编预处理的隐含规则。
“<n>.o” 的目标的依赖目标会自动推导为“<n>.s”,默认使用编译品“as”,并且其生成命令是:“$(AS) $(ASFLAGS)”。“<n>.s” 的目标的依赖目标会自动推导为“<n>.S”,默认使用C预编译器“cpp”,并且其生成命令是:“$(AS) $(ASFLAGS)”。

8、链接Object文件的隐含规则。
“<n>”目标依赖于“<n>.o”,通过运行C的编译器来运行链接程序生成(一般是“ld”),其生成命令是:“$(CC) $(LDFLAGS) <n>.o $(LOADLIBES) $(LDLIBS)”。这个规则对于只有一个源文件的工程有效,同时也对多个Object文件(由不同的源文件生成)的也有效。例如如下规则:

x : y.o z.o

并且“x.c”、“y.c”和“z.c”都存在时,隐含规则将执行如下命令:

cc -c x.c -o x.o
cc -c y.c -o y.o
cc -c z.c -o z.o
cc x.o y.o z.o -o x
rm -f x.o
rm -f y.o
rm -f z.o

如果没有一个源文件(如上例中的x.c)和你的目标名字(如上例中的x)相关联,那么,你最好写出自己的生成规则,不然,隐含规则会报错的。

9、Yacc C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.y”(Yacc生成的文件),其生成命令是:“$(YACC) $(YFALGS)”。(“Yacc”是一个语法分析器,关于其细节请查看相关资料)

10、Lex C程序时的隐含规则。
“<n>.c”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。(关于“Lex”的细节请查看相关资料)

11、Lex Ratfor程序时的隐含规则。
“<n>.r”的依赖文件被自动推导为“n.l”(Lex生成的文件),其生成命令是:“$(LEX) $(LFALGS)”。

12、从C程序、Yacc文件或Lex文件创建Lint库的隐含规则。
“<n>.ln” (lint生成的文件)的依赖文件被自动推导为“n.c”,其生成命令是:“$(LINT) $(LINTFALGS) $(CPPFLAGS) -i”。对于“<n>.y”和“<n>.l”也是同样的规则。


三、隐含规则使用的变量

在隐含规则中的命令中,基本上都是使用了一些预先设置的变量。你可以在你的makefile中改变这些变量的值,或是在make的命令行中传入这些值,或是在你的环境变量中设置这些值,无论怎么样,只要设置了这些特定的变量,那么其就会对隐含规则起作用。当然,你也可以利用make的“-R”或“--no–builtin-variables”参数来取消你所定义的变量对隐含规则的作用。

例如,第一条隐含规则——编译C程序的隐含规则的命令是“$(CC) –c $(CFLAGS) $(CPPFLAGS)”。Make默认的编译命令是“cc”,如果你把变量“$(CC)”重定义成“gcc”,把变量“$(CFLAGS)”重定义成“-g”,那么,隐含规则中的命令全部会以“gcc –c -g $(CPPFLAGS)”的样子来执行了。

我们可以把隐含规则中使用的变量分成两种:一种是命令相关的,如“CC”;一种是参数相的关,如“CFLAGS”。下面是所有隐含规则中会用到的变量:

1、关于命令的变量。

AR
函数库打包程序。默认命令是“ar”。
AS
汇编语言编译程序。默认命令是“as”。
CC
C语言编译程序。默认命令是“cc”。
CXX
C++语言编译程序。默认命令是“g++”。
CO
从 RCS文件中扩展文件程序。默认命令是“co”。
CPP
C程序的预处理器(输出是标准输出设备)。默认命令是“$(CC) –E”。
FC
Fortran 和 Ratfor 的编译器和预处理程序。默认命令是“f77”。
GET
从SCCS文件中扩展文件的程序。默认命令是“get”。
LEX
Lex方法分析器程序(针对于C或Ratfor)。默认命令是“lex”。
PC
Pascal语言编译程序。默认命令是“pc”。
YACC
Yacc文法分析器(针对于C程序)。默认命令是“yacc”。
YACCR
Yacc文法分析器(针对于Ratfor程序)。默认命令是“yacc –r”。
MAKEINFO
转换Texinfo源文件(.texi)到Info文件程序。默认命令是“makeinfo”。
TEX
从TeX源文件创建TeX DVI文件的程序。默认命令是“tex”。
TEXI2DVI
从Texinfo源文件创建军TeX DVI 文件的程序。默认命令是“texi2dvi”。
WEAVE
转换Web到TeX的程序。默认命令是“weave”。
CWEAVE
转换C Web 到 TeX的程序。默认命令是“cweave”。
TANGLE
转换Web到Pascal语言的程序。默认命令是“tangle”。
CTANGLE
转换C Web 到 C。默认命令是“ctangle”。
RM
删除文件命令。默认命令是“rm –f”。

2、关于命令参数的变量

下面的这些变量都是相关上面的命令的参数。如果没有指明其默认值,那么其默认值都是空。

ARFLAGS
函数库打包程序AR命令的参数。默认值是“rv”。
ASFLAGS
汇编语言编译器参数。(当明显地调用“.s”或“.S”文件时)。
CFLAGS
C语言编译器参数。
CXXFLAGS
C++语言编译器参数。
COFLAGS
RCS命令参数。
CPPFLAGS
C预处理器参数。( C 和 Fortran 编译器也会用到)。
FFLAGS
Fortran语言编译器参数。
GFLAGS
SCCS “get”程序参数。
LDFLAGS
链接器参数。(如:“ld”)
LFLAGS
Lex文法分析器参数。
PFLAGS
Pascal语言编译器参数。
RFLAGS
Ratfor 程序的Fortran 编译器参数。
YFLAGS
Yacc文法分析器参数。


四、隐含规则链

有些时候,一个目标可能被一系列的隐含规则所作用。例如,一个[.o]的文件生成,可能会是先被Yacc的[.y]文件先成[.c],然后再被C的编译器生成。我们把这一系列的隐含规则叫做“隐含规则链”。

在上面的例子中,如果文件[.c]存在,那么就直接调用C的编译器的隐含规则,如果没有[.c]文件,但有一个[.y]文件,那么Yacc的隐含规则会被调用,生成[.c]文件,然后,再调用C编译的隐含规则最终由[.c]生成[.o]文件,达到目标。

我们把这种[.c]的文件(或是目标),叫做中间目标。不管怎么样,make会努力自动推导生成目标的一切方法,不管中间目标有多少,其都会执着地把所有的隐含规则和你书写的规则全部合起来分析,努力达到目标,所以,有些时候,可能会让你觉得奇怪,怎么我的目标会这样生成?怎么我的makefile发疯了?

在默认情况下,对于中间目标,它和一般的目标有两个地方所不同:第一个不同是除非中间的目标不存在,才会引发中间规则。第二个不同的是,只要目标成功产生,那么,产生最终目标过程中,所产生的中间目标文件会被以“rm -f”删除。

通常,一个被makefile指定成目标或是依赖目标的文件不能被当作中介。然而,你可以明显地说明一个文件或是目标是中介目标,你可以使用伪目标“.INTERMEDIATE”来强制声明。(如:.INTERMEDIATE : mid )

你也可以阻止make自动删除中间目标,要做到这一点,你可以使用伪目标“.SECONDARY”来强制声明(如:.SECONDARY : sec)。你还可以把你的目标,以模式的方式来指定(如:%.o)成伪目标“.PRECIOUS”的依赖目标,以保存被隐含规则所生成的中间文件。

在“隐含规则链”中,禁止同一个目标出现两次或两次以上,这样一来,就可防止在make自动推导时出现无限递归的情况。

Make会优化一些特殊的隐含规则,而不生成中间文件。如,从文件“foo.c”生成目标程序“foo”,按道理,make会编译生成中间文件“foo.o”,然后链接成“foo”,但在实际情况下,这一动作可以被一条“cc”的命令完成(cc –o foo foo.c),于是优化过的规则就不会生成中间文件。

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
59 [报告]
发表于 2014-06-24 16:58 |只看该作者
java工程fortify失败的最终解决方案;ant、maven相关的fortify检查失败  
fortify java的编译脚本,不论ant、maven都写成如下形式。注意红色字体的地方,这个脚本是终极解决方案。。。

rem 工程名,任意英文名,不要有空格
SET BUILD_ID=my_build
rem 依赖的jar包
SET CLASSPATH=D:\software\apache-maven-3.0\Repo_eUPP\**\*.jar
rem 扫描的工程目录
SET SCAN_DIR=D:\code\eupp V01R01\eUPP-pkg\**\*.java
sourceanalyzer "-b" "%BUILD_ID%" –clean
rem 1.6是指java版本,1.7的话请写1.7
sourceanalyzer "-b" "%BUILD_ID%" "-machine-output" "-cp" "%CLASSPATH%" "-source" "1.6" "%SCAN_DIR%" "-Xmx1024m"

命令打可执行jar包:

        1.将源文件拷贝到d:\jartest;

        2.在cmd命令下进入jartest该目录;

           配置:
                set path=C盘下jdk1.5.0_05\bin所在的目录   --你自己的jdk安装目录
                set classpath=.      --当前路径

           执行命令javac -d . 类.java -----(编译class文件)

        3.删除源文件;

        4.jar -cvf tt.jar ./  -----(此处打包成不可执行jar包  tt为打包后的名字  ./为整个当前目录 指定要打包的文件)

        5.jar -xvf tt.jar     -----解压

        6.删除tt.jar

        7.修改META-INF下的MAMFEST.MF文件,追加一句

           Main-Class: <插入一空格>包路径.类名<回车>     -----此包路径为package后的路径  以.隔开

           (如果有第三方的jar文件 再加上 classpath:lib/a.jar   lib/b.jar  lib目录下面放要第三方jar文件)

        8.jar -cvfM tt.jar *  -----打包

        9.java -jar tt.jar   -----命令运行jar包   也可双击击运行



        我们在打包时,只要不在当前路径下进行打包,对其它路径下的一个目录进行打包,往往含他自己的路径,可以用下面的命令去掉那些路径:

      第一种:jar   cvfm  test.jar   c:\manifest.mf  -C  c:\test   \     注意:后面的 \ 可以换成点

      第二种:jar   cvf     test.jar   -C  c:\test  \   注意:后面的 \ 可以换成点

      第三种:jar   -cvf    test.jar  -C   c:\test  \   注意:后面的 \ 可以换成点



打包可执行jar文件的一些注意事项:
      jar cvfm [目标.jar] [MANIFEST文件名] [应用程序所在目录]

      MANIFEST文件名随便,但jar参数中的"m"不可少,否则在jar文件中你会看到一个只有版本号的MANIFEST.MF文件。

      MANIFEST文件,也叫标明文件,清单文件,用来记录jar文件的相关信息。为了打包可执行jar文件,必须创建带jar文件主类的信息的MANIFEST文件。在任意位置,如:E:\temp,创建名为myManifest的文件,用文本编辑器编辑该文件,加入下行:

       Main-Class: 应用程序主类的路径名+回车

       回车是必须的,否则MANIFEST.MF中只有版本号。主类的路径名如:com.AppMain (假设在E:\temp\中有个com   目 录,com中有个属于com包的AppMain.class)

应用程序所在目录,当然就是com啦,如果我们在e:\temp下打包,则可以输入:

       jar cvfm AppMain.jar myManifest com





#压缩java文件, 打成war包

1.打开要打包的文件夹(cd 目录)
2.配置
    set path=C盘下jdk1.5.0_05\bin所在的目录 --你自己的jdk安装目录
    set classpath=. 当前路径
3.jar cvf tt.war ./    --(./表示当前文件下所有文件,要有   命令格式:java cvf 打包文件名称 要打包的目录 打包文件保存路径)

4.解压自然就是:  jar xvf temp.war



在包涵第三方jar包情况下在eclipse下打jar包:

    在你的项目文件夹下建一个META-INF文件夹里面新建一个

     MANIFEST.MF的文件内容大至如下
       Manifest-Version: 1.0
       Ant-Version: Apache Ant 1.6.2
       Created-By: 1.5.0_06-57 ("Apple Computer, Inc.")
       Main-Class: com.opensymphony.workflow.designer.Launcher
      Class-Path: looks.jar forms.jar syntax.jar jgraph.jar foxtrot.jar osworkflow-2.8.0.jar oscore-2.2.5.jar

     Main-Class就是你要运行的类。
     Class-path:就是你要引入的包
     用eclipse export导出jar文件里,选择
         user existing manifest from workspace
         manifest file:/你的项目名/src/META-INF/MANIFEST.MF
         点击完成。这样应该就可以了

论坛徽章:
3
天秤座
日期:2013-12-27 13:44:58射手座
日期:2014-05-22 16:52:43天蝎座
日期:2014-08-13 16:03:21
60 [报告]
发表于 2014-07-30 16:42 |只看该作者
本帖最后由 compare2000 于 2014-07-30 16:59 编辑

常用Git,Repo命令  
Repo

repo init -u ssh://android.huawei.com/platform/manifest.git -b hw/platform/jellybean/K3V200_D2_4.1.1_r1/U9701L --no-repo-verify --repo-branch=stable

repo forall -c "git xxxx"            //对所有的git仓执行git命令

repo upload                           //提交commit

repo upload .                      //提交当前git仓的commit

repo --trace upload             //提交并打印出过程

repo sync                         //同步代码,拉代码

repo sync "git仓名"             //单同步某个git仓,或单拉某个仓

repo sync .                         //单同步当前的git仓





Git使用指南
Refer to How to version projects with Git



1.创建目录

$ git config --global user.name "Your Name Comes Here"

$ git config --global user.email you@yourdomain.example.com

$ git config --list 查看相关信息

$ git init

如果作为 Server 存在,那么可以忽略工作目录,以纯代码仓库形式存在。

$ git --bare init

可以在~/.gitconfig设置别名

[alias]
st = status
ci = commit -a
co = checkout  

2.文件操作

$ git add .

$ git add file1 file2 file3

$ git add -u

$ git add -p // 为你做的每次修改,Git将为你展示变动的代码,并询问该变动是否应是下一次提交的一部分。回答“y”或者“n”。也有其他选项,比如延迟决定:键入“?”来学习更多。

$ git rm file1 file2 file3

$ git mv file1 file2

3.提交更改

$ git commit -a -m ' '

$ git commit --amend -a -m ' ' // 修改上一次的信息,不作为新的提交

$ git stash // 保存当前草稿,便于切换分支

$ git stash pop // 恢复草稿

$ git stash apply

$ git stash list

$ git stash apply stash@{1}

$ git stash clear

4.撤销更改

$ git reset HEAD file1 // 取消暂存区的文件快照(即恢复成最后一个提交版本),这不会影响工作目录的文件修改。

$ git reset --hard HEAD^ // 将整个项目回溯到以前的某个版本,可以使用 "git reset"。可以选择的参数包括默认的 "--mixed" 和 "--hard",前者不会取消工作目录的修改,而后者则放弃全部的修改。该操作会丢失其后的日志


$ git checkout -- file1 // 使用暂存区快照恢复工作目录文件,工作目录的文件修改被抛弃。

$ git checkout HEAD^ file1 // 直接 "签出" 代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git checkout "@{10 minutes ago}" // 直接 "签出" 10分钟之前代码仓库中的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git checkout "@{5}" // 直接 "签出" 倒数第五次保存的某个文件版本到工作目录,该操作同时会取消暂存区快照。

$ git revert SHA1_HASH // 还原特定哈希值对应的提交。该还原记录作为一个新的提交。

5.查看历史纪录或者当前状态

$ git log

$ git log -p

$ git log -U 显示commit的详细信息

$ git log --stat --summary

$ git log V3..V7 //显示V3之后直至V7的所有历史记录

$ git log V3.. //显示所有V3之后的历史记录。注意<since>..<until>中任何一个被省略都将被默认设置为HEAD。所以如果使用..<until>的话,git log在大部分情况下会输出空的。

$ git log –since=”2 weeks ago” //显示2周前到现在的所有历史记录。具体语法可查询git-ref-parse命令的帮助文件。

$ git log stable..experimental //将显示在experimental分支但不在stable分支的历史记录

$ git log experimental..stable //将显示在stable分支但不在experimental分支的历史记录

$ git log -S'你要找的内容',就可以从全部的历史纪录,瞬間找到你要找的东西


$ git blame FILE // 标注出一个指定的文件里每一行内容的最后修改者,和最后修改时间。

$ git diff // 这个命令只在git add之前使用有效。如果已经add了,那么此命令输出为空

$ git diff –cached // 这个命令在git add之后在git commit之前有效。

$ git diff "@{yesterday}" // 比较当前和昨天的内容

$ git status // 这个命令在git commit之前有效,表示都有哪些文件发生了改动



$ git show 5b888 // 使用git show再加上述的commit名称来显式更详细的commit信息

$ git show master // 显示分支信息

$ git show HEAD // 使用HEAD字段可以代表当前分支的头(也就是最近一次commit)

$ git show HEAD^ //查看HEAD的父母的信息, 可以使用^表示parent

$ git show HEAD^^ //查看HEAD的父母的父母的信息

$ git show HEAD~4 //查看HEAD上溯4代的信息


$ git tag V3 5b888 //以后可以用V3来代替复杂的名称(5b888…)

$ git show V3

$ git branch stable V3 //建立一个基于V3的分支

$ git grep “print” V3 //在V3中搜索所有的包含print的行

$ git grep “print” //在所有的历史记录中搜索包含print的行


6.协作操作


$ git clone git://server/path/to/files // Git deamon

$ git clone your.computer:/path/to/script  or git clone ssh://car.colorado.edu/home/xxx ./xxxxx // SSH
        // SSH

$ git pull

$ git push // 在将代码提交(push)到服务器之前,首先要确认相关更新已经合并(merge)到主分支(master)。还应该先从服务器刷新(pull)最新代码,以确保自己的提交不会和别人最新提交的代码冲突。

如果想在merge前先查看更改:

$ git fetch /home/bob/myrepo master:bobworks //此命令意思是提取出bob修改的代码内容,然后放到我(rocrocket)工作目录下的bobworks分支中。之所以要放到分支中,而不是master中,就是要我先仔仔细细看看bob的开发成果,如果我觉得满意,我再merge到master中,如果不满意,我完全可以直接git branch -D掉。

$ git whatchanged -p master..bobworks //用来查看bob都做了什么

$ git checkout master //切换到master分区

$ git pull . bobworks //如果我检查了bob的工作后很满意,就可以用pull来将bobworks分支合并到我的项目中了

7.分支管理

$ git branch: 查看当前分支

$ git checkout -b/branch experimental: 创建新分支

$ git checkout experimental: 切换到另一分支

$ git merge experimental:合并分支

$ git branch -d experimental:删除分支, 使用的是小写的-d,表示“在分支已经合并到主干后删除分支”。

$ git branch -D experimental:删除分支, 表示“不论如何都删除分支”,-D使用在“分支被证明失败”

8.补丁工作

git format-patch:当你想给一个开源项目(例如Rails)提交一段代码的时候,或者你想给小组成员展示一段你并不想提交的代码,那么你还是需要 patch的,Git的'format-patch'命令良好的支持了这个功能。
第一,利用branch命令 创建一个分支;
第二,修改你的代码;
第三,在该分支上提交你的修改;
第四,使用'git format-patch'命令来生成一个patch文件,例如:'git format-patch master --stdout > ~/Desktop/tmp.patch'就是将工作分支与master主干的不同,存放在'~/Desktop'文件夹下,生成一个叫做 tmp.patch的文件(另一种简单的版本是利用diff命令,例如'git diff ..master > ~/Desktop/tmp.patch'),这样就生成了patch文件。那么别人就可以使用'git apply'命令来应用patch,例如'git apply ~/Desktop/tmp.patch'就是将patch打在当前的工作分支上

9.仓库维护

$ git fsck: 不加–full参数的情况下,这个命令一般会以非常低廉的代价确保仓库在一个不错的健康状态之中。

$ git count-objects: 统计有多少松散的对象,没有 repack 的对象消耗了多少硬盘空间。

$ git gc: 在本地仓库进行 repack,并进行其他日常维护工作。

$ git filter-branch --tree-filter `rm top/secret/file` HEAD //在所有记录中永久删除某个文件

$ git rebase -i HEAD~10 // 后10个提交会出现在你喜爱的$EDITOR。通过删除行来移去提交。通过为行重新排序来为提交重新排序。用“edit”来替换“pick”来标志一个提交可修改。用“squash”来替换“pick”来将一个提交和前一个合并。

10.错误查询

刚刚发现程序里有一个功能出错了,即使你过去经常提交变更,Git还是可以精确的找出问题所在:

$ git bisect start

$ git bisect bad SHA1_OF_BAD_VERSION

$ git bisect good SHA1_OF_GOOD_VERSION

Git从历史记录中检出一个中间的状态,在这个状态上测试功能,如果还是错误的:

$ git bisect bad

如果可以工作了,则把"bad"替换成"good"。 Git会再次帮你找到一个以确定的好版本和坏版本之间的状态,经过一系列的迭代,这种二进制查询会帮你找到导致这个错误的那次提交。一旦完成了问题定位的调查,你可以返回到原始状态,键入:

$ git bisect reset







不需要手工测试每一次改动,执行如下命令可以自动的完成上面的搜索:

$ git bisect run COMMAND

Git使用指定命令(通常是一个一次性的脚本)的返回值来决定一次改动是否是正确的:命令退出时的代码0代表改动是正确的,125代表要跳过对这次改动的检查,1到127之间的其他数值代表改动是错误的。返回负数将会中断整个bisect的检查。
另外推荐git资料:http://gitbook.liuhui998.com/book.pdf

shell中可能经常能看到:>/dev/null 2>&1


分解这个组合:“>/dev/null 2>&1” 为五部分。



1:> 代表重定向到哪里,例如:echo "123" > /home/123.txt

2:/dev/null 代表空设备文件
3:2> 表示stderr标准错误
4:&1, shell 语法,无论文件描述符1在什么地方
5:1 表示stdout标准输出,系统默认值是1,所以">/dev/null"等同于 "1>/dev/null"



因此,>/dev/null 2>&1也可以写成“1> /dev/null 2> &1”




那么本文标题的语句执行过程为:

1>/dev/null :首先表示标准输出重定向到空设备文件,也就是不输出任何信息到终端,说白了就是不显示任何信息。
2>&1 :接着,标准错误输出重定向 到 标准输出,因为之前标准输出已经重定向到了空设备文件,所以标准错误输出也重定向到空设备文件。
说清楚了吗,大家理解下吧!
顺便对比述说下这么用的好处!
   最常用的方式有:
command > file 2>file  与command > file 2>&1
它们 有什么不同的地方吗?

      首先command > file 2>file 的意思是将命令所产生的标准输出信息,和错误的输出信息送到file 中.command  > file 2>file 这样的写法,stdout和stderr都直接送到file中, file会被打开两次,这样stdout和stderr会互相覆盖,这样写相当使用了FD1和FD2两个同时去抢占file 的管道。
      而command >file 2>&1 这条命令就将stdout直接送向file, stderr 继承了FD1管道后,再被送往file,此时,file 只被打开了一次,也只使用了一个管道FD1,它包括了stdout和stderr的内容。

      从IO效率上,前一条命令的效率要比后面一条的命令效率要低,所以在编写shell脚本的时候,较多的时候我们会command > file 2>&1 这样的写法。




思考题:

$ cmd 2>&1 1>/dev/null

结果是怎么样?





---------------------------------

答案:后一种只输出了STDERR到STDOUT,STDOUT的被输入到了/dev/null,这样就只有STDERR显示出来了。



原因是shell处理重定向是从左到右,所以先处理2>&1,处理完了之后,再处理1>/dev/null
grep 查找特定文件的内容  grep -R --include="*.cpp" key dir

上述命令的含义:

在dir目录下递归查找所有.cpp文件中的关键字key

Git stash --- Git 暂存  Git stash应用场景:

在当前branch上开发到任意时刻,有一个突发的问题需要解决,但当前的开发还不能提交,这时,可以先用stash将当前的开发暂存起来,然后回到当前的HEAD,进行突发任务的开发,将当前突发任务开发提交之后,在将暂存的工作拿出来,接着开发。



步骤:



1,$git stash//暂存当前开发,这时候,查看文件状态,是干净的

2,开发

3,提交开发

4,取出暂存

    $git stash pop

      //可能会有conflict,需要resolve

如何识别linux 版本号  动态方式:
#include <linux/utsname.h>
utsname();
   
静态方式:
#if LINUX_VERSION_CODE >= KERNEL_VERSION(3,10,12)
#include <linux/of_gpio.h>
#endif

通过sysrq查看进程和内核运行状态 技巧和方法名称 通过sysrq查看进程和内核运行状态
适用问题的范围 适用于冻屏,内核死锁等问题的定位,解决过的问题:
等级 高级
内容描述

如果系统出现挂起情况或者在诊断一些和内核相关,比较怪异,需要现场定位问题的时候,使用sysrq键是一个比较好的方式。


1. 如何打开和关闭SysRq组合键?

打开这个功能,运行:

adb shell

# echo 1 >> /proc/sys/kernel/sysrq

关闭这个功能:

# echo 0 >> /proc/sys/kernel/sysrq


2. 如何触发一个sysrq事件?

首先手机需要有root权限:

# echo 'w' >> /proc/sysrq-trigger

当我触发一个sysrq事件的时候,结果保存在什么地方?

通过adb shell cat /proc/kmsg > kmsg.txt

在Log中查找关键字sysrq可以看到相关内核信息:


sysrq功能被打开后,有几种sysrq事件可以被触发。不同的内核版本可能会有些不同。但有一些是共用的:

* m - 导出关于内存分配的信息

* w - 导出阻塞线程状态信息(可以定位一些内核驱动或线程死锁等问题)

* p - 到处当前CPU寄存器信息和标志位的信息

* c - 故意让系统崩溃(在使用netdump或者diskdump的时候有用)

* s - 立即同步所有挂载的文件系统

* u - 立即重新挂载所有的文件系统为只读

* b - 立即重新启动系统

* o - 立即关机(如果机器配置并支持此项功能)

详细请参看:

sysrq.txt文件,可以在工程搜索这个文件获取。



扩展及其注意事项

echo 'w' >> /proc/sysrq-trigger 输出结果

<6>[1217, sh] [  152.193682] SysRq : Show Blocked State

<6>[1217, sh] [  152.197344]   task                PC stack   pid father

<6>[1217, sh] [  152.203509] kworker/u:0     D c0778648  6400     5      2 0x00000000

<4>[1217, sh] [  152.210804] [<c0778648>] (__schedule+0x514/0x5f from [<c003be94>] (rr_read+0xa8/0x12

<4>[1217, sh] [  152.219838] [<c003be94>] (rr_read+0xa8/0x12 from [<c003ca28>] (do_read_data+0x18/0xb54)

<4>[1217, sh] [  152.228963] [<c003ca28>] (do_read_data+0x18/0xb54) from [<c0091b5c>] (process_one_work+0x27c/0x484)

<4>[1217, sh] [  152.238913] [<c0091b5c>] (process_one_work+0x27c/0x484) from [<c0091f74>] (worker_thread+0x210/0x3b0)

<4>[1217, sh] [  152.249076] [<c0091f74>] (worker_thread+0x210/0x3b0) from [<c0095e10>] (kthread+0x80/0x8c)

<4>[1217, sh] [  152.258293] [<c0095e10>] (kthread+0x80/0x8c) from [<c000f028>] (kernel_thread_exit+0x0/0x

<6>[1217, sh] [  152.267572] kworker/u:1     D c0778648  5320    45      2 0x00000000

<4>[1217, sh] [  152.274866] [<c0778648>] (__schedule+0x514/0x5f from [<c003be94>] (rr_read+0xa8/0x12

<4>[1217, sh] [  152.283900] [<c003be94>] (rr_read+0xa8/0x12 from [<c003ca28>] (do_read_data+0x18/0xb54)

<4>[1217, sh] [  152.292995] [<c003ca28>] (do_read_data+0x18/0xb54) from [<c0091b5c>] (process_one_work+0x27c/0x484)

<4>[1217, sh] [  152.302975] [<c0091b5c>] (process_one_work+0x27c/0x484) from [<c0091f74>] (worker_thread+0x210/0x3b0)

<4>[1217, sh] [  152.313139] [<c0091f74>] (worker_thread+0x210/0x3b0) from [<c0095e10>] (kthread+0x80/0x8c)

<4>[1217, sh] [  152.322356] [<c0095e10>] (kthread+0x80/0x8c) from [<c000f028>] (kernel_thread_exit+0x0/0x

<6>[1217, sh] [  152.331664] SurfaceFlinger  D c0778648  5712   538      1 0x00000000

<4>[1217, sh] [  152.338928] [<c0778648>] (__schedule+0x514/0x5f from [<c07765fc>] (schedule_timeout+0x28/0x344)

<4>[1217, sh] [  152.348725] [<c07765fc>] (schedule_timeout+0x28/0x344) from [<c0778d24>] (wait_for_common+0xf0/0x16

<4>[1217, sh] [  152.358889] [<c0778d24>] (wait_for_common+0xf0/0x168) from [<c02ea7a8>] (mipi_dsi_cmd_dma_tx+0x22c/0x2ac)

<4>[1217, sh] [  152.369388] [<c02ea7a8>] (mipi_dsi_cmd_dma_tx+0x22c/0x2ac) from [<c02ea910>] (mipi_dsi_cmds_tx+0xe8/0x188)

<4>[1217, sh] [  152.379978] [<c02ea910>] (mipi_dsi_cmds_tx+0xe8/0x188) from [<c02eba38>] (mipi_lcd_register_write+0xec/0x174)

<4>[1217, sh] [  152.390843] [<c02eba38>] (mipi_lcd_register_write+0xec/0x174) from [<c02ebaf8>] (process_mipi_table+0x38/0x7c)

<4>[1217, sh] [  152.401770] [<c02ebaf8>] (process_mipi_table+0x38/0x7c) from [<c02ed9ac>] (mipi_hx8369a_lcd_on+0xa0/0xec)

<4>[1217, sh] [  152.412269] [<c02ed9ac>] (mipi_hx8369a_lcd_on+0xa0/0xec) from [<c02eca54>] (panel_next_on+0x3c/0x48)

<4>[1217, sh] [  152.422340] [<c02eca54>] (panel_next_on+0x3c/0x48) from [<c02e8bf4>] (mipi_dsi_on+0x660/0x800)

<4>[1217, sh] [  152.431893] [<c02e8bf4>] (mipi_dsi_on+0x660/0x800) from [<c02eca54>] (panel_next_on+0x3c/0x48)

<4>[1217, sh] [  152.441446] [<c02eca54>] (panel_next_on+0x3c/0x48) from [<c02cd0cc>] (mdp_on+0x118/0x164)

<4>[1217, sh] [  152.450541] [<c02cd0cc>] (mdp_on+0x118/0x164) from [<c02c71e4>] (msm_fb_blank_sub+0x74/0xd8)

<4>[1217, sh] [  152.459911] [<c02c71e4>] (msm_fb_blank_sub+0x74/0xd8) from [<c02c7cf8>] (msm_fb_open+0xb8/0xfc)

<4>[1217, sh] [  152.469555] [<c02c7cf8>] (msm_fb_open+0xb8/0xfc) from [<c02c1550>] (fb_open+0xa8/0xe8)

<4>[1217, sh] [  152.478406] [<c02c1550>] (fb_open+0xa8/0xe8) from [<c012ce94>] (chrdev_open+0x10c/0x134)

<4>[1217, sh] [  152.487440] [<c012ce94>] (chrdev_open+0x10c/0x134) from [<c0127ab8>] (__dentry_open.isra.12+0x190/0x29c)

<4>[1217, sh] [  152.497848] [<c0127ab8>] (__dentry_open.isra.12+0x190/0x29c) from [<c013599c>] (do_last.isra.29+0x690/0x6c0)

<4>[1217, sh] [  152.508621] [<c013599c>] (do_last.isra.29+0x690/0x6c0) from [<c0135b94>] (path_openat+0xb8/0x35c)

<4>[1217, sh] [  152.518419] [<c0135b94>] (path_openat+0xb8/0x35c) from [<c0135f18>] (do_filp_open+0x2c/0x78)

<4>[1217, sh] [  152.527819] [<c0135f18>] (do_filp_open+0x2c/0x78) from [<c01287dc>] (do_sys_open+0xd8/0x170)

<4>[1217, sh] [  152.537189] [<c01287dc>] (do_sys_open+0xd8/0x170) from [<c000dec0>] (ret_fast_syscall+0x0/0x30)



echo 'm' >> /proc/sysrq-trigger 输出结果



<6>[1443, sh] [   34.173752] SysRq : Show Memory

<4>[1443, sh] [   34.176804] Mem-info:

<4>[1443, sh] [   34.180039] Normal per-cpu:

<4>[1443, sh] [   34.183763] CPU    0: hi:  186, btch:  31 usd:  74

<4>[1443, sh] [   34.189500] HighMem per-cpu:

<4>[1443, sh] [   34.193316] CPU    0: hi:   42, btch:   7 usd:  35

<4>[1443, sh] [   34.199053] active_anon:30965 inactive_anon:58 isolated_anon:0

<4>[1443, sh] [   34.199053]  active_file:2311 inactive_file:39075 isolated_file:0

<4>[1443, sh] [   34.199053]  unevictable:0 dirty:9 writeback:0 unstable:0

<4>[1443, sh] [   34.199053]  free:108103 slab_reclaimable:1861 slab_unreclaimable:2643

<4>[1443, sh] [   34.199053]  mapped:19383 shmem:67 pagetables:1890 bounce:0

<4>[1443, sh] [   34.233175] Normal free:432136kB min:3364kB low:4204kB high:5044kB active_anon:97972kB inactive_anon:192kB active_file:6416kB inactive_file:59080kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:708932kB mlocked:0kB dirty:36kB writeback:0kB mapped:43264kB shmem:192kB slab_reclaimable:7444kB slab_unreclaimable:10572kB kernel_stack:5064kB pagetables:7560kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no

<4>[1443, sh] [   34.273676] lowmem_reserve[]: 0 1013 1013

<4>[1443, sh] [   34.278650] HighMem free:276kB min:128kB low:280kB high:432kB active_anon:25888kB inactive_anon:40kB active_file:2828kB inactive_file:97220kB unevictable:0kB isolated(anon):0kB isolated(file):0kB present:129740kB mlocked:0kB dirty:0kB writeback:0kB mapped:34268kB shmem:76kB slab_reclaimable:0kB slab_unreclaimable:0kB kernel_stack:0kB pagetables:0kB unstable:0kB bounce:0kB writeback_tmp:0kB pages_scanned:0 all_unreclaimable? no

<4>[1443, sh] [   34.317350] lowmem_reserve[]: 0 0 0

<4>[1443, sh] [   34.321776] Normal: 6*4kB 10*8kB 3*16kB 2*32kB 3*64kB 1*128kB 0*256kB 3*512kB 2*1024kB 1*2048kB 104*4096kB = 432152kB

<4>[1443, sh] [   34.333312] HighMem: 5*4kB 0*8kB 0*16kB 0*32kB 0*64kB 0*128kB 1*256kB 0*512kB 0*1024kB 0*2048kB 0*4096kB = 276kB

<4>[1443, sh] [   34.344422] 41449 total pagecache pages

<4>[1443, sh] [   34.349214] 0 pages in swap cache

<4>[1443, sh] [   34.353456] Swap cache stats: add 0, delete 0, find 0/0

<4>[1443, sh] [   34.359621] Free swap  = 0kB

<4>[1443, sh] [   34.363436] Total swap = 0kB

<4>[1443, sh] [   34.383946] 232448 pages of RAM

<4>[1443, sh] [   34.386998] 129615 free pages

<4>[1443, sh] [   34.390904] 12655 reserved pages

<4>[1443, sh] [   34.395086] 3020 slab pages

<4>[1443, sh] [   34.398809] 195010 pages shared

<4>[1443, sh] [   34.402899] 0 pages swap cached
您需要登录后才可以回帖 登录 | 注册

本版积分规则 发表回复

  

北京盛拓优讯信息技术有限公司. 版权所有 京ICP备16024965号-6 北京市公安局海淀分局网监中心备案编号:11010802020122 niuxiaotong@pcpop.com 17352615567
未成年举报专区
中国互联网协会会员  联系我们:huangweiwei@itpub.net
感谢所有关心和支持过ChinaUnix的朋友们 转载本站内容请注明原作者名及出处

清除 Cookies - ChinaUnix - Archiver - WAP - TOP