網頁

2006年3月27日 星期一

C的組語

GAS與x86(IA32)的AT&T syntax組語寫法
基本介紹
Unix下寫組合語言, 這個需要組合語言的基礎也要有系統的觀念。 使用的工具有nasm或GNU as, nasm用Intel的語法就可以, GNU as跟其它unix上的工具as一樣要按照AT&T的語法規定, 所以如果你是先學Intel的有些語法上需要注意。
AT&T assembly與Intel/Microsoft syntax的不同
order(來源與目的暫存器順序不同)
source在前面destination在後面
Intex Syntax AT&T Syntax
instr dest,source instr source,dest
mov eax,[ecx] movl (%ecx),%eax
register naming(暫存器命名)
AT&T前面要加個% Intel Syntax AT&T Syntax
mov eax,1 movl $1,%eax
mov ebx,0ffh movl $0xff,%ebx
int 80h int $0x80
imme operand(立即定址命名)
AT&T前面要加個$
Intel Syntax AT&T Syntax
mov eax,1 movl $1,%eax
mov ebx,0ffh movl $0xff,%ebx
int 80h int $0x80
memory reference(間接定址)
AT&T用大括號()
Intel Syntax AT&T Syntax
instr foo,segreg:[base+index*scale+disp] instr %segreg:disp(base,index,scale),foo
mov eax,[ebx+20h] movl 0x20(%ebx),%eax
add eax,[ebx+ecx*2h] addl (%ebx,%ecx,0x2),%eax
lea eax,[ebx+ecx] leal (%ebx,%ecx),%eax
sub eax,[ebx+ecx*4h-20h] subl -0x20(%ebx,%ecx,0x4),%eax
opcode naming(指令命名)
必需指定長度 根據系統而不一樣 word有的是32 bits有的是16 bits b : byte
w : word
l : long
Intel Syntax AT&T Syntax
mov al,bl movb %bl,%al
mov ax,bx movw %bx,%ax
mov eax,ebx movl %ebx,%eax
mov eax, dword ptr [ebx] movl (%ebx),%eax

type casting(型別轉換) s (signed)
z (zero)
bl (from byte to long)
bw (from byte to word)
wl (from word to long)
movsbl %al, %edx
long jump, call與ret
Intel Syntax AT&T Syntax
jmp far seg:offsetljmp seg, offset
jmp far INITSEG:GO ljmp $INITSEG, $GO
call far INITSEG:GO lcall $INITSEG, $GO
ret far STACK_ADJUS lret $STACK_ADJUST
C下的inline組語
在C語言中勘進組語的程式碼加個
__asm__("asm code");
__asm__("movl $1,%eax\n\t" // SYS_exit
"xor %ebx,%ebx\n\t"
"int $0x80"
);

注意quote 與\n\t的位置 \n\t是因為gcc其實根據這些與其它c code產生一個.s檔 \n\t只是在.s檔產生newline與tab這在每一行都要除了最後一行不用 另外inline assembly的 通式是 __asm__(asm statements : outputs : inputs : clobber);

通式有兩個重要的概念, 一個是可以和C程式傳變數來溝通,另外 如果我們指定了eax ebx...等register,則這下可完了,如果其他的C code 也正在用eax ebx,則compiler必須先把這些值推進stack才能跑你的asm code, 所以我們可以不特別指定register,讓gcc自動作register運用的最佳化。 其實這是其他mips和其他CPU的作法,別種CPU的register命名沒 eax, ebx.....這麼死與囉唆, $1 $2 $3...就搞定,還可以換來換去很有彈性。 這個通式就是在做這樣的事,請看一個例子 int main (void) {
int operand1, operand2, sum, accumulator;
operand1 = rand (); operand2 = rand ();
__asm__ ("movl %1, %0\n\t"
"addl %2, %0"
: "=r" (sum) /* output operands */
: "r" (operand1), "r" (operand2) /* input operands */
: "0"); /* clobbered operands */
accumulator = sum;
__asm__ ("addl %1, %0\n\t"
"addl %2, %0"
: "=r" (accumulator)
: "0" (accumulator), "g" (operand1), "r" (operand2)
: "0");
return accumulator;
}

第一個__asm__是說input的東西把operand1(C的變數)放到r,也就是%1, operand2(C的變數)放到r,也就是%2,然後執行assembly code的 movl與addl, 然後結果放到sum(C的變數)=r 也就是%0, 在這邊我們沒有指定eax ebx,只是很單純的%0 %1 %2 r。 %0 %1 %2 分別對應了output r, input r, input r, %0 %1 %2.....會先對應output裡的register,再對應input裡的register, 就是它們出現的順序。 gcc會幫我們最佳化register的使用。 clobbered operands是一堆registers, 我們告訴gcc說這些registers的值已經被暴力的摧毀(被改變了), 你要重新考慮它們的合法性才行, 在這邊0就是%0,gcc會特別照顧一下它所選的%0的值。 有一些規則要說明
r, g, 0這些東西叫constraints(限制的意思),每一個符號有特殊意義 代表這個暫存器必需是什麼型式的(暫存器有通用暫存器,符點運算暫存器, cpu指令有的允許直接對memory做運算,有的不準等等條件的暫存器, r代表通用型暫存器)。
output operand前一定要有個"="表示這個constraint是read-ony, "="叫constraint modifier。
input output operands後一定要跟著相對應的C 變數的參數, 這是給asm的參數。
如果statement裡真要指定register要多加個%變成%%eax
通用constraints I, J, K .... P 是根據不同cpu可以做不同的解釋來表示一個範圍的立即定址整數
Q, R, S .... U
0, 1, 2 .... 相對於assembly statement裡的%0 %1 %2 ....
a, b, c .... f 可以根據不同cpu可以做不同定義的registers
m 表示這是一個memory的operand 例如mov eax, data中的data
p 合法的記憶體位址
r 一般通用型register
g 任何的通用型register, memory operand, 立即定址的整數

constraints在386上有
a eax
b ebx
c ecx
d edx
S esi
D edi
I 表示只有constant value (0 to 31)才行
r 可以是eax ebx ecx edx esi edi
q 可以是eax ebx ecx edx
g eax, ebx, ecx, edx or variable in memory
A eax and edx combined into a 64-bit integer (use long longs)

2006年3月24日 星期五

Kernel Makefile的解釋

==========================================
Makefile 初探
==========================================
Linux的內核配置文件有兩個,一個是隱含的.config文件,嵌入到主Makefile中;另一個是include/linux/autoconf.h,嵌入到各個c源文件中,它們由make configmake menuconfigmake xconfig這些過程創建。幾乎所有的源文件都會通過linux/config.h而嵌入autoconf.h,如果按照通常方法建立文件依賴關係(.depend),只要更新過autoconf.h,就會造成所有源代碼的重新編繹。

為了優化make過程,減少不必要的重新編繹,Linux開發了專用的mkdep工具,用它來取代gcc來生成.depend文件。mkdep在處理源文件時,忽略linux/config.h這樣的頭文件,識別源文件宏指令中具有〞CONFIG_〞特徵的行。例如,如果有〞#ifdef CONFIG_SMP〞這樣的行,它就會在.depend文件中輸出$(wildcard /usr/src/linux/include/config/smp.h)

include/config/下的文件是另一個工具split-includeautoconf.h中生成,它利用autoconf.h中的CONFIG_標記,生成與mkdep相對應的文件。例如,如果autoconf.h中有〞#undef CONFIG_SMP〞這一行,它就生成include/config/smp.h文件,內容為〞#undef CONFIG_SMP〞。這些文件名只在.depend文件中出現,內核源文件是不會嵌入它們的。每配置一次內核,運行split-include一次。split-include會檢查舊的子文件的內容,確定是不是要更新它們。這樣,不管autoconf.h修改日期如何,只要其配置不變,make就不會重新編繹內核。

如果系統的編繹選項發生了變化,Linux也能進行增量編繹。為了做到這一點,make每編繹一個源文件時生成一個flags文件。例如編繹sched.c時,會在相同的目錄下生成隱含的.sched.o.flags文件。它是Makefile的一個片斷,當make進入某個子目錄編繹時,會搜索其中的flags文件,將它們嵌入到Makefile中。這些flags代碼測試當前的編繹選項與原來的是不是相同,如果相同,就將自已對應的目標文件加入FILES_FLAGS_UP_TO_DATE列表,然後,系統從編繹對像表中刪除它們,得到FILES_FLAGS_CHANGED列表,最後,將它們設為目標進行更新。

下一步準備逐步深入的剖析Makefile代碼。

==========================================
Makefile解讀之二: sub-make
==========================================
Linux各級內核源代碼的子目錄下都有Makefile,大多數Makefile要嵌入主目錄下的Rule.makeRule.make將識別各個Makefile中所定義的一些變量。變量obj-y表示需要編繹到內核中的目標文件名集合,定義O_TARGET表示將obj-y連接為一個O_TARGET名稱的目標文件,定義L_TARGET表示將obj-y合併為一個L_TARGET名稱的庫文件。同樣obj-m表示需要編繹成模塊的目標文件名集合。如果還需進行子目錄make,則需要定義subdir-ysubdir-m。在Makefile中,用〞obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o〞和〞subdir-$(CONFIG_EXT2_FS) += ext2〞這種形式自動為obj-yobj-msubdir-ysubdir-m添加文件名。有時,情況沒有這麼單純,還需要使用條件語句個別對待。Makefile中還有其它一些變量,如mod-subdirs定義了subdir-m以外的所有模塊子目錄。

Rules.make是如何使make進入子目錄的呢? 先來看subdir-y是如何處理的,在Rules.make中,先對subdir-y中的每一個文件名加上前綴〞_subdir_〞再進行排序生成subdir-list集合,再以它作為目標集,對其中每一個目標產生一個子make,同時將目標名的前綴去掉得到子目錄名,作為子make的起始目錄參數。subdir-msubdir-y類似,但情況稍微複雜一些。由於subdir-y中可能有模塊定義,因此利用mod-subdirs變量將subdir-y中模塊目錄提取出來,再與subdir-m合成一個大的MOD_SUB_DIRS集合。subdir-m的目標所用的前綴是〞_modsubdir_〞。

一點說明,子目錄中的MakefileRules.make都沒有嵌入.config文件,它是通過主Makefile向下傳遞MAKEFILES變量完成的。MAKEFILESmake自已識別的一個變量,在執行新的Makefile之前,make會首先加載MAKEFILES所指的文件。在主Makefile中它即指向.config


==========================================
Makefile解讀之三: 模塊的版本化處理
==========================================
模塊的版本化是內核與模塊接口之間進行嚴格類型匹配的一種方法。當內核配置了CONFIG_MODVERSIONS之後,make dep操作會在include/linux/modules/目錄下為各級Makefileexport-objs變量所對應的源文件生成擴展名為.ver的文件。

例如對於kernel/ksyms.cmake用以下命令生成對應的ksyms.ver

gcc -E -D__KERNEL__ -D__GENKSYMS__ ksyms.c /sbin/genksyms -k 2.4.1 > ksyms.ver

-D__GENKSYMS__的作用是使ksyms.c中的EXPORT_SYMBOL宏不進行擴展。genksyms命令識別EXPORT_SYMBOL()中的函數名和對應的原型,再根據其原型計算出該函數的版本號。

例如ksyms.c中有一行:
EXPORT_SYMBOL(kmalloc);
kmalloc原型是:
void *kmalloc(size_t, int);
genksyms程序對應的輸出為:
#define __ver_kmalloc 93d4cfe6
#define kmalloc _set_ver(kmalloc)
在內核符號表和模塊中,kmalloc將變成kmalloc_R93d4cfe6

在生成完所有的.ver文件後,make將重建include/linux/modversions.h文件,它包含一系列#include指令行嵌入各個.ver文件。在編繹內核本身export-objs中的文件時,make會增加一個〞-DEXPORT_SYMTAB〞編繹標誌,它使源文件嵌入modversions.h文件,將EXPORT_SYMBOL宏展開中的函數名字符串進行版本名擴展;同時,它也定義_set_ver()宏為一空操作,使代碼中的函數名不受其影響。
在編繹模塊時,make會增加〞-include=linux/modversion.h -DMODVERSIONS〞編繹標誌,使模塊中代碼的函數名得到相應版本擴展。

由於生成.ver文件比較費時,make還為每個.ver創建了一個後綴為.stamp時戳文件。在make dep時,如果其.stamp文件比源文件舊才重新生成.ver文件,否則只是更新.stamp文件時戳。另外,在生成.vermodversions.h文件時,make都會比較新文件和舊文件的內容,保持它們修改時間為最舊。

FIND函式參數

一、find 命令格式
1find命令的一般形式為;
find pathname -options [-print -exec -ok ...]
2find命令的參數;
pathname: find命令所查找的目錄路徑。例如用.來表示當前目錄,用/來表示系統根目錄。
-printfind命令將匹配的文件輸出到標準輸出。
-execfind命令對匹配的文件執行該參數所給出的shell命令。相應命令的形式為'command' { } \;,注意{ }\;之間的空格。
-ok: 和-exec的作用相同,只不過以一種更為安全的模式來執行該參數所給出的shell命令,在執行每一個命令之前,都會給出提示,讓用戶來確定是否執行。
3find命令選項
-name
按照文件名查找文件。
-perm
按照文件權限來查找文件。
-prune
使用這一選項可以使find命令不在當前指定的目錄中查找,如果同時使用-depth選項,那麼-prune將被find命令忽略。
-user
按照文件屬主來查找文件。
-group
按照文件所屬的組來查找文件。
-mtime -n +n
按照文件的更改時間來查找文件, - n表示文件更改時間距現在n天以內,+ n表示文件更改時間距現在n天以前。find命令還有-atime-ctime 選項,但它們都和-m time選項。
-nogroup
查找無有效所屬組的文件,即該文件所屬的組在/etc/groups中不存在。
-nouser
查找無有效屬主的文件,即該文件的屬主在/etc/passwd中不存在。
-newer file1 ! file2
查找更改時間比文件file1新但比文件file2舊的文件。
-type
查找某一類型的文件,諸如:
b - 塊設備文件。
d - 目錄。
c - 字符設備文件。
p - 管道文件。
l - 符號鏈接文件。
f - 普通文件。
-size n[c] 查找文件長度為n塊的文件,帶有c時表示文件長度以字節計。
-depth:在查找文件時,首先查找當前目錄中的文件,然後再在其子目錄中查找。
-fstype:查找位於某一類型文件系統中的文件,這些文件系統類型通常可以在配置文件/etc/fstab中找到,該配置文件中包含了本系統中有關文件系統的信息。
-mount:在查找文件時不跨越文件系統mount點。
-follow:如果find命令遇到符號鏈接文件,就跟蹤至鏈接所指向的文件。
-cpio:對匹配的文件使用cpio命令,將這些文件備份到磁帶設備中。
另外,下面三個的區別:
-amin n
  查找系統中最後N分鐘訪問的文件
  -atime n
  查找系統中最後n*24小時訪問的文件
  -cmin n
  查找系統中最後N分鐘被改變文件狀態的文件
  -ctime n
  查找系統中最後n*24小時被改變文件狀態的文件
 -mmin n
  查找系統中最後N分鐘被改變文件數據的文件
  -mtime n
  查找系統中最後n*24小時被改變文件數據的文件
4、使用execok來執行shell命令
使用find時,只要把想要的操作寫在一個文件裡,就可以用exec來配合find查找,很方便的
在有些操作系統中只允許-exec選項執行諸如l sls -l這樣的命令。大多數用戶使用這一選項是為了查找舊文件並刪除它們。建議在真正執行rm命令刪除文件之前,最好先用ls命令看一下,確認它們是所要刪除的文件。
exec選項後面跟隨著所要執行的命令或腳本,然後是一對兒{ },一個空格和一個\,最後是一個分號。為了使用exec選項,必須要同時使用print選項。如果驗證一下find命令,會發現該命令只輸出從當前路徑起的相對路徑及文件名。
例如:為了用ls -l命令列出所匹配到的文件,可以把ls -l命令放在find命令的-exec選項中
# find . -type f -exec ls -l { } \;
-rw-r--r-- 1 root root 34928 2003-02-25 ./conf/httpd.conf
-rw-r--r-- 1 root root 12959 2003-02-25 ./conf/magic
-rw-r--r-- 1 root root 180 2003-02-25 ./conf.d/README
上面的例子中,find命令匹配到了當前目錄下的所有普通文件,並在-exec選項中使用ls -l命令將它們列出。
/logs目錄中查找更改時間在5日以前的文件並刪除它們:
$ find logs -type f -mtime +5 -exec rm { } \;
記住:在shell中用任何方式刪除文件之前,應當先查看相應的文件,一定要小心!當使用諸如mvrm命令時,可以使用-exec選項的安全模式。它將在對每個匹配到的文件進行操作之前提示你。
在下面的例子中, find命令在當前目錄中查找所有文件名以.LOG結尾、更改時間在5日以上的文件,並刪除它們,只不過在刪除之前先給出提示。
$ find . -name "*.conf" -mtime +5 -ok rm { } \;
< rm ... ./conf/httpd.conf > ? n
y鍵刪除文件,按n鍵不刪除。
任何形式的命令都可以在-exec選項中使用。
在下面的例子中我們使用grep命令。find命令首先匹配所有文件名為「 passwd*」的文件,例如passwdpasswd.oldpasswd.bak,然後執行grep命令看看在這些文件中是否存在一個sam用戶。
# find /etc -name "passwd*" -exec grep "sam" { } \;
sam:x:501:501::/usr/sam:/bin/bash
二、find命令的例子;
1、查找當前用戶主目錄下的所有文件:
下面兩種方法都可以使用
$ find $HOME -print
$ find ~ -print
2、讓當前目錄中文件屬主具有讀、寫權限,並且文件所屬組的用戶和其他用戶具有讀權限的文件;
$ find . -type f -perm 644 -exec ls -l { } \;
3、為了查找系統中所有文件長度為0的普通文件,並列出它們的完整路徑;
$ find / -type f -size 0 -exec ls -l { } \;
4、查找/var/logs目錄中更改時間在7日以前的普通文件,並在刪除之前詢問它們;
$ find /var/logs -type f -mtime +7 -ok rm { } \;
5、為了查找系統中所有屬於root組的文件;
$find . -group root -exec ls -l { } \;
-rw-r--r-- 1 root root 595 1031 01:09 ./fie1
6find命令將刪除當目錄中訪問時間在7日以來、含有數字後綴的admin.log文件。
該命令只檢查三位數字,所以相應文件的後綴不要超過999。先建幾個admin.log*的文件 ,才能使用下面這個命令
$ find . -name "admin.log[0-9][0-9][0-9]" -atime -7 -ok
rm { } \;
< rm ... ./admin.log001 > ? n
< rm ... ./admin.log002 > ? n
< rm ... ./admin.log042 > ? n
< rm ... ./admin.log942 > ? n
7、為了查找當前文件系統中的所有目錄並排序;
$ find . -type d sort
8、為了查找系統中所有的rmt磁帶設備;
$ find /dev/rmt -print
三、xargs
xargs - build and execute command lines from standard input
在使用find命令的-exec選項處理匹配到的文件時, find命令將所有匹配到的文件一起傳遞給exec執行。但有些系統對能夠傳遞給exec的命令長度有限制,這樣在find命令運行幾分鐘之後,就會出現溢出錯誤。錯誤信息通常是「參數列太長」或「參數列溢出」。這就是xargs命令的用處所在,特別是與find命令一起使用。
find命令把匹配到的文件傳遞給xargs命令,而xargs命令每次只獲取一部分文件而不是全部,不像-exec選項那樣。這樣它可以先處理最先獲取的一部分文件,然後是下一批,並如此繼續下去。
在有些系統中,使用-exec選項會為處理每一個匹配到的文件而發起一個相應的進程,並非將匹配到的文件全部作為參數一次執行;這樣在有些情況下就會出現進程過多,系統性能下降的問題,因而效率不高;
而使用xargs命令則只有一個進程。另外,在使用xargs命令時,究竟是一次獲取所有的參數,還是分批取得參數,以及每一次獲取參數的數目都會根據該命令的選項及系統內核中相應的可調參數來確定。
來看看xargs命令是如何同find命令一起使用的,並給出一些例子。
下面的例子查找系統中的每一個普通文件,然後使用xargs命令來測試它們分別屬於哪類文件
#find . -type f -print xargs file
./.kde/Autostart/Autorun.desktop: UTF-8 Unicode English text
./.kde/Autostart/.directory: ISO-8859 text在整個系統中查找內存信息轉儲文件(core dump) ,然後把結果保存到/tmp/core.log 文件中:
$ find / -name "core" -print xargs echo "" >/tmp/core.log
上面這個執行太慢,我改成在當前目錄下查找
#find . -name "file*" -print xargs echo "" > /temp/core.log
# cat /temp/core.log
./file6
在當前目錄下查找所有用戶具有讀、寫和執行權限的文件,並收回相應的寫權限:
# ls -l
drwxrwxrwx 2 sam adm 4096 1030 20:14 file6
-rwxrwxrwx 2 sam adm 0 1031 01:01 http3.conf
-rwxrwxrwx 2 sam adm 0 1031 01:01 httpd.conf
# find . -perm -7 -print xargs chmod o-w
# ls -l
drwxrwxr-x 2 sam adm 4096 1030 20:14 file6
-rwxrwxr-x 2 sam adm 0 1031 01:01 http3.conf
-rwxrwxr-x 2 sam adm 0 1031 01:01 httpd.conf
grep命令在所有的普通文件中搜索hostname這個詞:
# find . -type f -print xargs grep "hostname"
./httpd1.conf:# different IP addresses or hostnames and have them handled by the
./httpd1.conf:# VirtualHost: If you want to maintain multiple domains/hostnames
on your
grep命令在當前目錄下的所有普通文件中搜索hostnames這個詞:
# find . -name \* -type f -print xargs grep "hostnames"
./httpd1.conf:# different IP addresses or hostnames and have them handled by the
./httpd1.conf:# VirtualHost: If you want to maintain multiple domains/hostnames
on your
注意,在上面的例子中, \用來取消find命令中的*shell中的特殊含義。

find命令配合使用execxargs可以使用戶對所匹配到的文件執行幾乎所有的命令。
四、find 命令的參數
下面是find一些常用參數的例子,有用到的時候查查就行了,像上面前幾個貼子,都用到了其中的的一些參數,也可以用man或查看論壇裡其它貼子有find的命令手冊
1、使用name選項
文件名選項是find命令最常用的選項,要麼單獨使用該選項,要麼和其他選項一起使用。
可以使用某種文件名模式來匹配文件,記住要用引號將文件名模式引起來。
不管當前路徑是什麼,如果想要在自己的根目錄$HOME中查找文件名符合*.txt的文件,使用~作為 'pathname'參數,波浪號~代表了你的$HOME目錄。
$ find ~ -name "*.txt" -print
想要在當前目錄及子目錄中查找所有的『 *.txt』文件,可以用:

$ find . -name "*.txt" -print
想要的當前目錄及子目錄中查找文件名以一個大寫字母開頭的文件,可以用:
$ find . -name "[A-Z]*" -print
想要在/etc目錄中查找文件名以host開頭的文件,可以用:
$ find /etc -name "host*" -print
想要查找$HOME目錄中的文件,可以用:
$ find ~ -name "*" -print find . -print
要想讓系統高負荷運行,就從根目錄開始查找所有的文件。
$ find / -name "*" -print
如果想在當前目錄查找文件名以兩個小寫字母開頭,跟著是兩個數字,最後是.txt的文件,下面的命令就能夠返回名為ax37.txt的文件:
$find . -name "[a-z][a-z][0--9][0--9].txt" -print
2、用perm選項
按照文件權限模式用-perm選項,按文件權限模式來查找文件的話。最好使用八進制的權限表示法。
如在當前目錄下查找文件權限位為755的文件,即文件屬主可以讀、寫、執行,其他用戶可以讀、執行的文件,可以用:
$ find . -perm 755 -print
還有一種表達方法:在八進制數字前面要加一個橫槓-,表示都匹配,如-007就相當於777-006相當於666
# ls -l
-rwxrwxr-x 2 sam adm 0 1031 01:01 http3.conf
-rw-rw-rw- 1 sam adm 34890 1031 00:57 httpd1.conf
-rwxrwxr-x 2 sam adm 0 1031 01:01 httpd.conf
drw-rw-rw- 2 gem group 4096 1026 19:48 sam
-rw-rw-rw- 1 root root 2792 1031 20:19 temp
# find . -perm 006
# find . -perm -006
./sam
./httpd1.conf
./temp
-perm mode:文件許可正好符合mode
-perm +mode:文件許可部分符合mode
-perm -mode: 文件許可完全符合mode
3、忽略某個目錄
如果在查找文件時希望忽略某個目錄,因為你知道那個目錄中沒有你所要查找的文件,那麼可以使用-prune選項來指出需要忽略的目錄。在使用-prune選項時要當心,因為如果你同時使用了-depth選項,那麼-prune選項就會被find命令忽略。
如果希望在/apps目錄下查找文件,但不希望在/apps/bin目錄下查找,可以用:
$ find /apps -path "/apps/bin" -prune -o -print
4、使用find查找文件的時候怎麼避開某個文件目錄
比如要在/usr/sam目錄下查找不在dir1子目錄之內的所有文件
find /usr/sam -path "/usr/sam/dir1" -prune -o -print
find [-path ..] [expression] 在路徑列表的後面的是表達式
-path "/usr/sam" -prune -o -print -path "/usr/sam" -a -prune -o
-print 的簡寫表達式按順序求值, -a -o 都是短路求值,與 shell && 類似如果 -path "/usr/sam" 為真,則求值 -prune , -prune 返回真,與邏輯表達式為真;否則不求值 -prune,與邏輯表達式為假。如果 -path "/usr/sam" -a -prune 為假,則求值 -print -print返回真,或邏輯表達式為真;否則不求值 -print,或邏輯表達式為真。
這個表達式組合特例可以用偽碼寫為
if -path "/usr/sam" then
-prune
else
-print
避開多個文件夾
find /usr/sam \( -path /usr/sam/dir1 -o -path /usr/sam/file1 \) -prune -o -print
圓括號表示表達式的結合。
\ 表示引用,即指示 shell 不對後面的字符作特殊解釋,而留給 find 命令去解釋其意義。
查找某一確定文件,-name等選項加在-o 之後
#find /usr/sam \(-path /usr/sam/dir1 -o -path /usr/sam/file1 \) -prune -o -name "temp" -print
5、使用usernouser選項
按文件屬主查找文件,如在$HOME目錄中查找文件屬主為sam的文件,可以用:
$ find ~ -user sam -print
/etc目錄下查找文件屬主為uucp的文件:
$ find /etc -user uucp -print

為了查找屬主帳戶已經被刪除的文件,可以使用-nouser選項。這樣就能夠找到那些屬主在/etc/passwd文件中沒有有效帳戶的文件。在使用-nouser選項時,不必給出用戶名; find命令能夠為你完成相應的工作。

例如,希望在/home目錄下查找所有的這類文件,可以用:
$ find /home -nouser -print
6、使用groupnogroup選項
就像usernouser選項一樣,針對文件所屬於的用戶組, find命令也具有同樣的選項,為了在/apps目錄下查找屬於gem用戶組的文件,可以用:
$ find /apps -group gem -print
要查找沒有有效所屬用戶組的所有文件,可以使用nogroup選項。下面的find命令從文件系統的根目錄處查找這樣的文件
$ find / -nogroup-print
7、按照更改時間或訪問時間等查找文件
如果希望按照更改時間來查找文件,可以使用mtime,atimectime選項。如果系統突然沒有可用空間了,很有可能某一個文件的長度在此期間增長迅速,這時就可以用mtime選項來查找這樣的文件。
用減號-來限定更改時間在距今n日以內的文件,而用加號+來限定更改時間在距今n日以前的文件。
希望在系統根目錄下查找更改時間在5日以內的文件,可以用:
$ find / -mtime -5 -print
為了在/var/adm目錄下查找更改時間在3日以前的文件,可以用:
$ find /var/adm -mtime +3 -print
8、查找比某個文件新或舊的文件
如果希望查找更改時間比某個文件新但比另一個文件舊的所有文件,可以使用-newer選項。它的一般形式為:
newest_file_name ! oldest_file_name
其中,!是邏輯非符號。
查找更改時間比文件sam新但比文件temp舊的文件:
例:有兩個文件
-rw-r--r-- 1 sam adm 0 1031 01:07 fiel
-rw-rw-rw- 1 sam adm 34890 1031 00:57 httpd1.conf
-rwxrwxr-x 2 sam adm 0 1031 01:01 httpd.conf
drw-rw-rw- 2 gem group 4096 1026 19:48 sam
-rw-rw-rw- 1 root root 2792 1031 20:19 temp
# find -newer httpd1.conf ! -newer temp -ls
1077669 0 -rwxrwxr-x 2 sam adm 0 1031 01:01 ./httpd.conf
1077671 4 -rw-rw-rw- 1 root root 2792 1031 20:19 ./temp
1077673 0 -rw-r--r-- 1 sam adm 0 1031 01:07 ./fiel
查找更改時間在比temp文件新的文件:
$ find . -newer temp -print
9、使用type選項
/etc目錄下查找所有的目錄,可以用:
$ find /etc -type d -print
在當前目錄下查找除目錄以外的所有類型的文件,可以用:
$ find . ! -type d -print
/etc目錄下查找所有的符號鏈接文件,可以用
$ find /etc -type l -print
10、使用size選項
可以按照文件長度來查找文件,這裡所指的文件長度既可以用塊(block)來計量,也可以用字節來計量。以字節計量文件長度的表達形式為N c;以塊計量文件長度只用數字表示即可。
在按照文件長度查找文件時,一般使用這種以字節表示的文件長度,在查看文件系統的大小,因為這時使用塊來計量更容易轉換。
在當前目錄下查找文件長度大於1 M字節的文件:
$ find . -size +1000000c -print
/home/apache目錄下查找文件長度恰好為100字節的文件:
$ find /home/apache -size 100c -print
在當前目錄下查找長度超過10塊的文件(一塊等於512字節):
$ find . -size +10 -print
11、使用depth選項
在使用find命令時,可能希望先匹配所有的文件,再在子目錄中查找。使用depth選項就可以使find命令這樣做。這樣做的一個原因就是,當在使用find命令向磁帶上備份文件系統時,希望首先備份所有的文件,其次再備份子目錄中的文件。
在下面的例子中, find命令從文件系統的根目錄開始,查找一個名為CON.FILE的文件。
它將首先匹配所有的文件然後再進入子目錄中查找。
$ find / -name "CON.FILE" -depth -print
12、使用mount選項
在當前的文件系統中查找文件(不進入其他文件系統),可以使用find命令的mount選項。
從當前目錄開始查找位於本文件系統中文件名以XC結尾的文件:
$ find . -name "*.XC" -mount -print

2006年3月23日 星期四

Makefile用法(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很專業(不是那種初學者的作品)。
四、檢查規則
有時候,我們不想讓我們的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
相當於UNIXtouch命令,只是把目標的修改日期變成最新的,也就是阻止生成目標的命令運行。
-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用法(call,origin,shell,foreach)

四、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函數很像GNUmake所支持的條件語句——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函數把執行操作系統命令後的輸出作為函數返回。於是,我們可以用操作系統命令以及字符串處理命令awksed等等命令來生成一個變量,如:
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繼續執行。

Makefile用法(字串函式)

字符串處理函數
$(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」,這正是ccgcc搜索頭文件路徑的參數。
三、文件名操作函數
下面我們要介紹的函數主要是處理文件名的。每個函數的參數字符串都會被當做一個或是一系列的文件名來對待。
$(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

Makefile用法(define和if條件句)

六、多行變量
還有一種設置變量值的方法是使用define關鍵字。使用define關鍵字設置變量的值可以有換行,這有利於定義一系列的命令(前面我們講過「命令包」的技術就是利用這個關鍵字)。
define指示符後面跟的是變量的名字,而重起一行定義變量的值,定義是以endef關鍵字結束。其工作方式和「=」操作符一樣。變量的值可以包含函數、命令、文字,或是其它變量。因為命令需要以[Tab]鍵開頭,所以如果你用define定義的命令變量中沒有以[Tab]鍵開頭,那麼make就不會把其認為是命令。
下面的這個示例展示了define的用法:
define two-lines
echo foo
echo $(bar)
endef
七、環境變量
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
九、模式變量
GNUmake中,還支持模式變量(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)」值來選取不同的函數庫來編譯程序。
我們可以從上面的示例中看到三個關鍵字:ifeqelseendififeq的意思表示條件語句的開始,並指定一個條件表達式,表達式包含兩個參數,以逗號分隔,表達式以圓括號括起。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用法(自動生成依賴及多目標)

六、多目標

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))表示調用Makefilefilter函數,過濾「$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
於是由編譯器自動生成的依賴關係,這樣一來,你就不必再手動書寫若干文件的依賴關係,而由編譯器自動生成了。需要提醒一句的是,如果你使用GNUC/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]文件中的目標會成為默認目標