網頁

2008年10月9日 星期四

GDB Script file

For Linux Applications

set solib-absolute-prefix /home/dean/rootfs/

set solib-search-path /home/dean/rootfs/lib/

target remote 192.168.38.38:1234

handle SIG32 nostop

For bootloader
Use JTAG (J-link) to debug bootloader ;take a example for u-boot

#
# J-LINK GDB SERVER initialization
#
# This connects to a GDB Server listening
# for commands on localhost at tcp port 2331
target remote 192.168.42.42:2331
# Set JTAG speed to 30 kHz
monitor speed 30
# Set GDBServer to big endian
monitor endian little
# Reset the chip to get to a known state.
monitor reset 1
#monitor WaitHalt 2000
#
# CPU core initialization (to be done by user)
#
# Set the processor mode
monitor reg cpsr = 0xd3
# Set auto JTAG speed
monitor speed auto
# Setup GDB FOR FASTER DOWNLOADS
set remote memory-write-packet-size 1024
set remote memory-write-packet-size fixed
# Load the program executable called "image.elf"
load /home/dean/u-boot-1.2.0/u-boot
#assign PC conter to the start of the program (address 0x01400004) saved in the flash
monitor reg pc = 0x01400004
#add symbol file to this flash start address (0x01400000) ;this context is the useless data
#for identification
add-symbol-file u-boot-1.2.0/u-boot 0x01400000
#set hardware breakpoint
monitor setBP 0x01400058

2008年3月24日 星期一

Compile GDB for arm and U-boot相關資料

  • compile ARM gdb and insight
    Environment
    ubuntu 7.10
    arm linux kernel 2.6.15.7
    ARM gcc-3.4.2 and glibc-2.3.3
  • compile GDB and GDBServer
  • compile GDB
get termcap and build arm termcap library
moidfy Makefile
assign
CC=arm-linux-gcc
LD=arm-linux-ld
AR=arm-linux-ar
make;
copy libtermcap.a into the library of the toolchains and termcap.h into the include path of the toolchain
build GDB
./configure --host= i686-pc-linux-gnu --target=arm-linux ;make
  • compile GDBServer
CC=arm-linux-gcc AR= arm-linux-ar LD=arm-linux-ld ./configure --host= i686-pc-linux-gnu --target=arm-linux;make
  • compile Insight
./configure --host= i686-pc-linux-gnu --target=arm-linux --prefix=/usr/local/insight ;make;make install
  • 燒Kernel的過程
boot# tftp 41000000 uImage
Using faradaynic#0 device
TFTP from server 192.168.42.50; our IP address is 192.168.42.51
Filename 'uImage'.
Load address: 0x41000000
Loading: #################################################################
done
Bytes transferred = 1090003 (10a1d3 hex)
boot# erase 14080000 141fffff

........................ done
Erased 24 sectors

boot# cp.b 41000000 14080000 10a1d3(image size)
  • 環境變數的設定
setenv bootargs mem=36M console=ttyS1
root=/dev/nfs rw nfsroot=192.168.42.50:/home/dean/MyTMP/0.18/aspeed/rootfs ip=192.168.42.45:192.168.42.50:192.168.42.1.255.255.0.0:::eth0:off

格式如下:
ip=ipaddr:tftp_serverip:gatewayip:netmask:::網卡名稱:off

Add netconsole parameters as the below:

The kernel will throw the data of console to the remote pc

Please execute the command in the remote PC and show the console data
netcat -u -l -p 6666 -v
  • u-boot之mkimage的使用方式
[root@localhost tftpboot]#mkimage -A arm -O linux -T kernel -C none -a 0x30000 -e 0x30008000 -d zImage zImage.img

參數的意義:
-A :set architecture
-O :set OS
-T :set image type
-C :set compression type
-a :set load address
-e :set entry point
-n :set image name
-d :use image data
-x :set XIP (execute in place)

VIM的指令

[VIM DIFF]

]c 跳到下一個差異點
[c 跳到上一個差異點

dp (diff "put")把當前文件的內容複製到另一個文件
do (diff "get") 把另一個文件的內容複製到當前行中

diffupdate 重新刷新比較結果
wqa (write, then quit all)儲存全部文件,然后離開
set diffopt=context:3 預設是會把不同之處上下各 6 行的顯示出來

zo (folding open)
zc (folding close)


[VIM]
VIM Registers 0 : For Yank operation
最近一次做 yank 動作所存進去的東西

VIM Registers [1-9] : for delete operation
di:顯示Registers 的內容
"Np N=1~9 會把Register的內容貼上
按一下.會依序貼上Register的內容

VIM Registers [a-z A-Z]:
使用者指定才會用到的東西
PS:小寫的[a-z]表示覆蓋 大寫的[A-Z]表示附加
ex:%s/pattern/y a =>%s/pattern/y A

"*p 把OS剪貼簿上的東西來取代選取的區域

[多行註解]
Ctrl+v進入Visual Block模式
選取要註解的那幾行(j向下、k向上)。
按下大寫I(d or x為刪除),會進入編輯模式,輸入註解符號。
然後按下Esc,這時候剛才有選取的那幾行都會加上剛才輸入的字串
















2008年3月1日 星期六

FIND的使用方式

一、find 命令格式
1、find命令的一般形式為;
find pathname -options [-print -exec -ok ...]
2、find命令的參數;
pathname: find命令所查找的目錄路徑。例如用.來表示當前目錄,用/來表示系統根目錄。
-print: find命令將匹配的文件輸出到標準輸出。
-exec: find命令對匹配的文件執行該參數所給出的shell命令。相應命令的形式為'command' { } ;,注意{ }和;之間的空格。
-ok: 和-exec的作用相同,只不過以一種更為安全的模式來執行該參數所給出的shell命令,在執行每一個命令之前,都會給出提示,讓用戶來確定是否執行。
3、find命令選項
-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、使用exec或ok來執行shell命令
使用find時,只要把想要的操作寫在一個文件裡,就可以用exec來配合find查找,很方便的
在有些操作系統中只允許-exec選項執行諸如l s或ls -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中用任何方式刪除文件之前,應當先查看相應的文件,一定要小心!當使用諸如mv或rm命令時,可以使用-exec選項的安全模式。它將在對每個匹配到的文件進行操作之前提示你。
在下面的例子中, find命令在當前目錄中查找所有文件名以.LOG結尾、更改時間在5日以上的文件,並刪除它們,只不過在刪除之前先給出提示。
$ find . -name "*.conf" -mtime +5 -ok rm { } \;
< rm ... ./conf/httpd.conf > ? n
按y鍵刪除文件,按n鍵不刪除。
任何形式的命令都可以在-exec選項中使用。
在下面的例子中我們使用grep命令。find命令首先匹配所有文件名為「 passwd*」的文件,例如passwd、passwd.old、passwd.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 10月 31 01:09 ./fie1
6、find命令將刪除當目錄中訪問時間在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 10月 30 20:14 file6
-rwxrwxrwx 2 sam adm 0 10月 31 01:01 http3.conf
-rwxrwxrwx 2 sam adm 0 10月 31 01:01 httpd.conf
# find . -perm -7 -print xargs chmod o-w
# ls -l
drwxrwxr-x 2 sam adm 4096 10月 30 20:14 file6
-rwxrwxr-x 2 sam adm 0 10月 31 01:01 http3.conf
-rwxrwxr-x 2 sam adm 0 10月 31 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命令配合使用exec和xargs可以使用戶對所匹配到的文件執行幾乎所有的命令。
四、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 10月 31 01:01 http3.conf
-rw-rw-rw- 1 sam adm 34890 10月 31 00:57 httpd1.conf
-rwxrwxr-x 2 sam adm 0 10月 31 01:01 httpd.conf
drw-rw-rw- 2 gem group 4096 10月 26 19:48 sam
-rw-rw-rw- 1 root root 2792 10月 31 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、使用user和nouser選項
按文件屬主查找文件,如在$HOME目錄中查找文件屬主為sam的文件,可以用:
$ find ~ -user sam -print
在/etc目錄下查找文件屬主為uucp的文件:
$ find /etc -user uucp -print

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

例如,希望在/home目錄下查找所有的這類文件,可以用:
$ find /home -nouser -print
6、使用group和nogroup選項
就像user和nouser選項一樣,針對文件所屬於的用戶組, find命令也具有同樣的選項,為了在/apps目錄下查找屬於gem用戶組的文件,可以用:
$ find /apps -group gem -print
要查找沒有有效所屬用戶組的所有文件,可以使用nogroup選項。下面的find命令從文件系統的根目錄處查找這樣的文件
$ find / -nogroup-print
7、按照更改時間或訪問時間等查找文件
如果希望按照更改時間來查找文件,可以使用mtime,atime或ctime選項。如果系統突然沒有可用空間了,很有可能某一個文件的長度在此期間增長迅速,這時就可以用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 10月 31 01:07 fiel
-rw-rw-rw- 1 sam adm 34890 10月 31 00:57 httpd1.conf
-rwxrwxr-x 2 sam adm 0 10月 31 01:01 httpd.conf
drw-rw-rw- 2 gem group 4096 10月 26 19:48 sam
-rw-rw-rw- 1 root root 2792 10月 31 20:19 temp
# find -newer httpd1.conf ! -newer temp -ls
1077669 0 -rwxrwxr-x 2 sam adm 0 10月 31 01:01 ./httpd.conf
1077671 4 -rw-rw-rw- 1 root root 2792 10月 31 20:19 ./temp
1077673 0 -rw-r--r-- 1 sam adm 0 10月 31 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

[Note]Shell Script變數

${FNAME}
顯示變數值的全部。
/home/kenny/tmp/test.1.sh
${FNAME##/*/}
比對變數值開端﹐如果以 /*/ 開頭的話﹐砍掉最長的部份。 test.1.sh
${FNAME#/*/}
比對變數值開端﹐如果以 /*/ 開頭的話﹐砍掉最短的部份。 kenny/tmp/test.1.sh
${FNAME%.*}
比對變數值末端﹐如果以 .* 結尾的話﹐砍掉最短的部份。
/home/kenny/tmp/test.1
${FNAME%%.*}
比對變數值末端﹐如果以 .* 結尾的話﹐砍掉最長的部份。
/home/kenny/tmp/test
${FNAME/sh/bash}
如果在變數值中找到 sh 的話﹐將第一個 sh 換成 bash。
/home/kenny/tmp/test.1.bash
${FNAME//sh/bash}
如果在變數值中找到 sh 的話﹐將全部 sh 換成 bash。
/home/kenny/tmp/test.1.bash

不定參數的用法

printf 的原型:
int printf( const char *format [, argument]... );
在format後面的參數不但不定個數且型態也是不定的。
這些參數是one bye one 的堆在stack裡面。
printf這個函式如何正確的取出這些參數?
秘訣就在 format,函式根據format裡的格式("%d %f...")
依序把堆在stack裡的參數取出。而這取出的動作就是用到
va_arg, va_end, va_start這三個macro再加上va_list。
va_list 事實上是一個 char * 的型態:
typedef char * va_list;
其它的三個macro的例子(不同的平台會有不太一樣的macro)
/* A guess at the proper definitions for other platforms */
#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )

#define va_start(ap,v) ( ap = (va_list)&v + _INTSIZEOF(v) )
#define va_arg(ap,t) ( *(t *)((ap += _INTSIZEOF(t)) - _INTSIZEOF(t)) )
#define va_end(ap) ( ap = (va_list)0 )

這三個macro若看得懂,對了解它們的運作會很有幫助;看不太懂也沒關係
,看一下下面的說明也差不可以了。
首先 va_start 把取出的指標(va_list)指到第一個不定參數。
然後就可以用 va_arg 以指定的型態從va_list取出資料並把va_list
指標移到下一個位置(例如取走了一個int 4 bytes 的資料, va_list便會加 4)
當取完資料後便可使用 va_end 把 va_list 歸零(這個macro應看得懂吧)。
其實對大部份的應用來說 va_end 做不做是沒太大的關係的。

下面是一個不定參數函式的範例(轉載自MSDN):
int average( int first, ... )
{
int count = 0, sum = 0, i = first;
va_list marker;

va_start( marker, first ); /* Initialize variable arguments. */
while( i != -1 )
{
sum += i;
count++;
i = va_arg( marker, int);
}
va_end( marker ); /* Reset variable arguments. */
return( sum ? (sum / count) : 0 );
}

註:_INTSIZEOF 是 int size memory alignment,如果你不知那是什麼
  就把 _INTSIZEOF 看成這樣的定義會比較容易理解:
#define _INTSIZEOF(n) sizeof(n)
為什麼不直接用sizeof(n)就好了,使用 macro 是為了跨平台時能保持運
作正確。若傳了一個2 bytrs的不定參數(如 short),但在一個 32 bits的
平台推入堆疊就會是4個bytes(32 bits)不是2 bytes,所以參數的取出
就要非常的小心。使用_INTSIZEOF 在於保證引數指標移動的正確。


#define _INTSIZEOF(n) ( (sizeof(n) + sizeof(int) - 1) & ~(sizeof(int) - 1) )


令N=sizeof(n)+sizeof (int)-1
X=sizeof()int
若X為2之倍數

_INTSIZEOF(n)可簡化為N&~(X-1)

而原式為N-N%X,以下證明
N&~(X-1)=N-N%X
IF X=1
N&~(1-1)=N&~(0xFFFF..F)=N-N%1

IF X=2
N&~(2-1)=N&~(0xFFFF...FE)=N-N%2

IF X=4
N&~(4-1)=N&~(0xFFFF...FC)=N-N%4

目的為了讓_INTSIZEOF(n)成為X之倍數,也就是sizeof(int)之倍數

Memory Aligment

對一個 16位元的系統來說,意味著,系統有16條data bus,各能存取一個
bit資料。
16位元當然就是2bytes,正如我們2個一數,數數會是2,4,6,8一樣,address
也不會(也不需要)出現奇數位置1,3,5,7...
現在來看看硬體存取一個byte在奇偶數地址的情況:
1.
要取偶數位置E時,會抓到 (E,E+1)一個WORD(因為data bus是16bits),
因為E是在Lo Byte 所以硬體只要把 Hi Bytes 拋棄掉就成了。
2.
要取奇數位置O時,會抓到 (O-1,O)一個WORD(因為data bus
是16bits), 因為O是在Hi Byte 所以硬體得要把 Hi Bytes 搬到 Lo Bytes
才能算完成存取。

看到奇數地址存取會比偶數地址麻煩了嗎?差一點點而已有什麼關係?
好吧!我們再來看看硬體存取一個WORD在奇偶數地址的情況:
3.
要取偶數位置(E,E+1)時,會抓到 (E,E+1)一個WORD,這樣就完成
存取了。
4.
要取奇數位置(O,O+1)時,要先抓到 (O-1,O)取得Lo Byte O,然後
再抓(O,O+1)得到 Hi Byte(O+1),最後合併(O,O+1)才算完成一個奇數
位址WORD的存取。

比較一下3,4,你就不會說只差一點點了吧!?
在32位元和64位元的系統,可以類推。這也是為什麼很多編譯器會預設
把變數位置調整在 2/4/8 倍數上來得到較佳的存取效率。
但這樣做會使像結構的資料大小意外的變化,若是某個結構的大小是很重要
的,你就必需找出編譯器相關的設定或directive(如VC的pack)來控制
aligment。

多維動態陣列

多維動態陣列在 C 中 大概只能用 malloc,但這在一維時尚
不構成問題,但需要多維陣列時怎麼辦呢?這算是程式論壇
最常被問到的問題之一了。
我把它整理了相關的回覆,都只用二維做說明,更多維的陣列
類推即可。
就從 C 談起吧!

動態產生一個[m][n]陣列 Array 的方法


code:------------------------------------------------------------------
int i;
int **Array;
Array= (int **)malloc(m*sizeof(void *));
for (i=0; i<m; i++)
Array=(int *)malloc(n*sizeof(int *));
--------------------------------------------------------------------------

這樣你就有一個 int Array[m][n]; 可以用了
是C 喔!不是 C++
但這不夠好,若你要的是一個較大的mXn陣列,那麼太多的malloc
會使記憶體碎片化(memory fragment)!沒關係,窮則變,變則通!
問題既出在for loop不斷的 memory allocation,就從那兒下手:

code:---------------------------------------------------------------------
int i;
int **Array, *pData;
Array= (int **)malloc(m*sizeof(int *));
pData= (int *)malloc(m*n*sizeof(int));
for (i=0; i<m; i++, pData+=n)
Array[i]=pData;
---------------------------------------------------------------------------

注意到嗎?這次只用了二次的malloc。
當要release memory也只要free Array[0] 和 Array 就成了!
(注意先free Array[0] 再 free Array)
如果嫌兩個alloc/free還是太多,也可簡單併成一個:

code:-----------------------------------------------------------------------
int i;
int **Array, *pData;
Array= (int **)malloc(m*sizeof(int *)+m*n*sizeof(int));
for (i=0,pData= (int *)(Array+m); i<m; i++, pData+=n)
Array[i]=pData;
----------------------------------------------------------------------------

要 free 時只要 free Array 就行了,帥吧?
如果用的是C++,那可用的方法就更多了!

《幼幼班》嘗試錯誤的階段
?> int Array[][] = new int [10][20];//這樣行ㄇ?
當然不行! int Array[][]不是一個指標,而且只能有
一維為不定大小。

《小班》終於會從1數到100了
?> 那...
?> int *Array[] = new int [10][20];//這樣行ㄇ?
有點想法了!但可惜的是 '*' 在 C++的語法是修飾前面的
識別字,所以 int *Array[]的意思是 "Array是一個 int 指標
的一維陣列!"

如果能使那個 '*' 以獨立指標型態去宣告Array,就會變成
"Array是一個指標,指向一維 int 的陣列",而我們知道指標
本身就可以當做一維的陣列,那麼是不是就成了"Array是一個二維
的int陣列"?
對了!這正是我們要的!問題是怎麼讓'*'成為獨立指標型態,
不會去修飾前面的int識別字?答案是使用括號:
int (*Array)[20] = new int [10][20];//這是正解!

但問題又來了, new 運算子可以使用 new int [m][n],但
int (*Array)[20] 的 [20]卻沒辦法以 [n] 來取代,所以就
沒辦法做到不定大小的宣告了。所以這只能算是小班的答案,
要做到不定大小的動態多維宣告,加上 STL 的運用,是不錯
的想法。

《中班》vector 模板的運用
vector<int> *array=new vector<int>[m];
for (int i=0; i<n; i++) array[i].reserve(n);
嘿嘿...不錯吧!?不定大小的二維陣列,而且每個維度還
可以隨時調整大小喔!
不過還是有缺點ㄟ!!那行 for loop 看起來有點礙眼,不能
拿掉嗎?拿掉的話,基本上Array 還是二維陣列,但第二維
並沒有預留空間放東西你可以用push.back等成員函式來增加
空間和存放data,但不能在還沒有空間時使用像
array[5][3]=3; 這種陣列的存取方式。沒更好的方法了嗎?
vector 不是有預留空間大小的建構子嗎?像一維的宣告:
vector<int> *Array= new vector <int>(n);
//這不是預留了 n 個元素的陣列了嗎?
只可惜,這是一維的宣告,若你嘗試做這樣的宣告:
vector<int> *Array= new vector <int>[m](n);
編譯器會給你無情的嘲諷:陣列不能呼叫帶參數的建構子!
這是很令人失望的!為十麼不行?不是邏輯的問題,或許下
一版本的C++會可以這樣宣告吧!但為今之計只能自力救濟
了。

《大班》使用模板在模版中
仔細觀察下面的宣告:
vector<vector<int> > Array(m, vector<int>(n));

Makefile的一些用法

make 內建變數的涵義

Target:Prequeite


$@ Target的檔名

$% 程式庫成員中的檔名元素

$< 第一個prequeite的檔名

$? Timestamp 在Target之後的Prequeite

$^ 所有的Prequeite的檔名 但不包含重複部分

$+ 所有的Prequeite的檔名

$* Target的主檔名

$(@D) $(<D) 指的是Target的檔案路徑

$(@F) $(<F) 指的是Target的檔案名稱


OBJS = foo.o bar.o
CC = gcc
CFLAGS = -Wall -O -g

myprog : $(OBJS)
$(CC) $^ -o $@

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



.clean:   
rm edit $(objects)

更安全的作法PHONY表示clean為一個偽裝目標,也就是說,不管clean這個目標發生什麼問題,都繼續執行 
.PHONY : clean
clean :   
-rm edit $(objects)

patsubst是把第二個參數換成第一個參數中所指定的pattern,而第三個參數則是代表所有要代換的東西
%.o:%.c ${CC} ${CLAGS} -o ${patsubst %.o,%,$@} ${STATIC_LIBS}
${CC} ${CLAGS} -o ${@:.o=.cgi} ${STATIC_LIBS}

通配符自動在規則中進行。但是在變量賦值的和函數的參數中通配符不會擴展,如果在這些情況下需要通配符擴展,
必須使用'wildcard'函數。語法如下:
$(wildcard PATTERN...)
這個在makefile任何地方出現的字符串,會被匹配任何一個文件名格式的以空格隔開的現有文件列表替換。
如果沒有任何文件匹配一個模式,這個模式從'wildcard'的輸出中忽略,注意,這和上述的通配符的處理是不一樣的。
『wildcard'函數的一個功能是找出目錄中所有的'.c'文件:
$(wildcard *.c)
可以通過替換後綴'.c'為'.o'從C文件列表得到目標文件的列表:
$(patsubst %.c,%.o,$(wildcard *.c))
這樣,上節中的makefile改寫為:
objects := $(patsubst %.c,%.o,$(wildcard *.c))
foo : $(objects) cc -o foo $(objects)

這個makefile利用了編譯C程序的隱含規則,所以不需要對編譯寫出顯式的規則。
(':='是'='的一個變體) 注意:'PATTERN'是大小寫敏感的。

介紹Makefile

Linux的內核配置文件有兩個,一個是隱含的.config文件,嵌入到主Makefile中;
另一個是include/linux/autoconf.h,嵌入到各個c源文件中,它們由make config、
make menuconfig、make 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-include從autoconf.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.make,
Rule.make將識別各個Makefile中所定義的一些變量。變量obj-y表示需要編繹到內核中的目標文件名集合,
定義O_TARGET表示將obj-y連接為一個O_TARGET名稱的目標文件,定義L_TARGET表示將obj-y合併為一個L_TARGET名稱的庫文件。
同樣obj-m表示需要編繹成模塊的目標文件名集合。如果還需進行子目錄make,則需要定義subdir-y和subdir-m。
在Makefile中,用」obj-$(CONFIG_BINFMT_ELF) += binfmt_elf.o」和」subdir-$(CONFIG_EXT2_FS) += ext2」
這種形式自動為obj-y、obj-m、subdir-y、subdir-m添加文件名。有時,情況沒有這麼單純,還需要使用條件語句個別對待。
Makefile中還有其它一些變量,如mod-subdirs定義了subdir-m以外的所有模塊子目錄。

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

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


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

例如對於kernel/ksyms.c,make用以下命令生成對應的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文件時戳。另外,在生成.ver和modversions.h文件時,
make都會比較新文件和舊文件的內容,保持它們修改時間為最舊。

介紹#pragma pack()

#pragma是用來設定complier的選項,跟你在complier後加 -Zp -Xk 等等設定
是一樣的。
#pragma pack(n)是用來讓struct的成員對齊記憶體用的,在32bit系統
下基於處理器效率的考量,預設的對齊位置是4 bytes,所以所有的struct成員視為
#pragma pack(4),但有時候我們希望struct裡成員是連績的,尤其是控制硬體相關
的io位置,所以會設為#paragma pack(1),讓struct的成員不要對齊。
看以下例子:
struct pci_conf {
WORD VendorID;
WORD DeviceID;
...
} pci;
如果沒有用#pragma pack(1),VendorID後會空2 bytes不用,以便對到4 bytes
(假設一開始是對齊的),然後才配置DeviceID。
可是若沒留意這樣的問題,
(BYTE*) p = &pci;
預期*(p+0x2)是DeviceID就會出問題。


如果你只有某一塊要特殊的alignement
#pragma pack(push, n)...#pragma pack(pop)

Linux Module的介紹

module
其實是一般的程序。但是它可以被動態載到 kernel
裡成為 kernel的一部分。載到 kernel 裡的 module 它具有跟
kernel 一樣的權力。可以 access 任何 kernel 的 data
structure。你聽過 kdebug 嗎 ? 它是用來 debug kernel
的。它就是先將它本身的一個 module 載到 kernel
裡,而在 user space 的 gdb 就可以經由跟這個 module
溝通,得知 kernel 裡的 data structure
的值,除此之外,還可以經由載到 kernel 的 module
去更改 kernel 裡 data structure。


我們知道,在寫 C 程序的時候,一個程序只能有一個
main。Kernel 本身其實也是一個程序,它本身也有個
main,叫 start_kernel()。當我們把一個 module 載到 kernel
裡的時候,它會跟 kernel 整合在一起,成為 kernel
的一部分。請各位想想,那 module 可以有 main 嗎 ?
答案很明顯的,是
No。理由很簡單。一個程序只能有一個 main。在使用
module 時,有一點要記住的是 module
是處於被動的角色。它是提供某些功能讓別人去使用的。


Kernel 裡有一個變量叫 module_list,每當 user 將一個 module
載到 kernel 裡的時候,這個 module 就會被記錄在
module_list 裡面。當 kernel 要使用到這個 module 提供的
function 時,它就會去 search 這個 list,找到
module,然後再使用其提供的 function 或 variable。每一個
module 都可以 export 一些 function
或變量來讓別人使用。除此之外,module
也可以使用已經載到 kernel 裡的 module 提供的
function。這種情形叫做 module stack。比方說,module A
用到 module B 的東西,那在加載 module A
之前必須要先加載 module B。否則 module A
會無法加載。除了 module 會 export 東西之外,kernel
本身也會 export 一些 function 或 variable。同樣的,module
也可以使用 kernel 所 export
出來的東西。由於大家平時都是撰寫 user space
的程序,所以,當突然去寫 module
的時候,會把平時寫程序用的 function 拿到 module
裡使用。像是 printf
之類的東西。我要告訴各位的是,module 所使用的
function 或 variable,要嘛就是自己寫在 module
裡,要嘛就是別的 module 提供的,再不就是 kernel
所提供的。你不能使用一般 libc 或 glibc所提供的
function。像 printf
之類的東西。這一點可能是各位要多小心的地方。(也許你可以先
link 好,再載到 kernel,我好像試過,但是忘了)


剛才我們說到 kernel 本身會 export 出一些 function 或
variable 來讓 module
使用,但是,我們不是萬能的,我們怎?知道 kernel
有開放那裡東西讓我們使用呢 ? Linux 提供一個
command,叫 ksyms,你只要執行 ksyms -a 就可以知道 kernel
或目前載到 kernel 裡的 module 提供了那些 function 或
variable。底下是我的系統的情形:


c0216ba0 drive_info_R744aa133
c01e4a44 boot_cpu_data_R660bd466
c01e4ac0 EISA_bus_R7413793a
c01e4ac4 MCA_bus_Rf48a2c4c
c010cc34 __verify_write_R203afbeb
. . . . .


在 kernel 裡,有一個 symbol table 是用來記錄 export
出去的 function 或 variable。除此之外,也會記錄著那個
module export 那些 function。上面幾行中,表示 kernel
提供了 drive_info 這個
function/variable。所以,我們可以在 kernel
裡直接使用它,等載到 kernel 裡時,會自動做好 link
的動作。由此,我們可以知道,module
本身其實是還沒做 link 的一些 object
code。一切都要等到 module 被加載 kernel 之後,link
才會完成。各位應該可以看到 drive_info
後面還接著一些奇怪的字符串。_R744aa133,這個字符串是根據目前
kernel 的版本再做些 encode
得出來的結果。為什?額外需要這一個字符串呢 ?


Linux 不知道從那個版本以來,就多了一個 config
的選項,叫做 Set version number in symbols of
module。這是為了避免對系統造成不穩定。我們知道
Linux 的 kernel 更新的很快。在 kernel
更新的過程,有時為了效率起見,會對某些舊有的 data
structure 或 function 做些改變,而且一變可能有的 variable
被拿掉,有的 function 的 prototype
跟原來的都不太一樣。如果這種情形發生的時候,那可能以前
2.0.33 版本的 module 拿到 2.2.1 版本的 kernel
使用,假設原來 module 使用了 2.0.33 kernel 提供的變量叫
A,但是到了 2.2.1 由於某些原因必須把 A 都設成
NULL。那當此 module 用在 2.2.1 kernel
上時,如果它沒去檢查 A
的值就直接使用的話,就會造成系統的錯誤。也許不會整個系統都死掉,但是這個
module 肯定是很難發揮它的功能。為了這個原因,Linux
就在 compile module 時,把 kernel 版本的號碼 encode 到各個
exported function 和 variable 裡。


所以,剛才也許我們不應該講 kernel 提供了
drive_info,而應該說 kernel 提供了 driver_info_R744aa133
來讓我們使用。這樣也許各位會比較明白。也就是說,kernel
認為它提供的 driver_info_R744aa133 這個東西,而不是
driver_info。所以,我們可以發現有的人在加載 module
時,系統都一直告訴你某個 function 無法
resolved。這就是因為 kernel 裡沒有你要的
function,要不然就是你的 module 裡使用的 function 跟
kernel encode 的結果不一樣。所以無法
resolve。解決方式,要嘛就是將 kernel 裡的 set version
選項關掉,要嘛就是將 module compile 成 kernel
有辦法接受的型式。


那有人就會想說,如果 kernel 認定它提供的 function
名字叫做 driver_info_R744aa133
的話,那我們寫程序時,是不是用到這個 funnction
的地方都改成 driver_info_R744aa133 就可以了。答案是
Yes。但是,如果每個 function
都要你這樣寫,你不會覺得很煩嗎 ?
比方說,我們在寫 driver 時,很多人都會用到 printk
這個 function。這是 kernel 所提供的 function。它的功能跟
printf 很像。用法也幾乎都一樣。是 debug
時很好用的東西。如果我們 module 裡用了一百次
printk,那是不是我們也要打一百次的 printk_Rdd132261 呢 ?
當然不是,聰明的人馬上會想到用 #define printk
printk_Rdd132261 就好了嘛。所以囉,Linux
很體貼的幫我們做了這件事。


如果各位的系統有將 set version
的選項打開的話,那大家可以到
/usr/src/linux/include/linux/modules
這個目錄底下。這個目錄底下有所多的
..ver檔案。這些檔案其實就是用來做 #define
用的。我們來看看 ksyms.ver
這個檔案裡,裡面有一行是這樣子的 :


#define printk _set_ver(printk)


set_ver 是一個 macro,就是用來在 printk 後面加上 version
number 的。有興趣的朋友可以自行去觀看這個 macro
的寫法。用了這些 ver 檔,我們就可以在 module
裡直接使用 printk 這樣的名字了。而這些 ver
檔會自動幫我們做好 #define
的動作。可是,我們可以發現這個目錄有很多很多的
ver 檔。有時候,我們怎?知道我們要呼叫的 function
是在那個 ver 檔裡有定義呢 ? Linux
又幫我們做了一件事。/usr/src/linux/include/linux/modversions.h
這個檔案已經將全部的 ver
檔都加進來了。所以在我們的 module 裡只要 include
這個檔,那名字的問題都解決了。但是,在此,我們奉勸各位一件事,不要將
modversions.h 這個檔在 module 裡 include
進來,如果真的要,那也要加上以下數行:


#ifdef MODVERSIONS
#include <linux/modversions.h>
#endif


加入這三行的原因是,避免這個 module 在沒有設定
kernel version 的系統上,將 modversions.h 這個檔案 include
進來。各位可以去試試看,當你把 set version
的選項關掉時,modversions.h 和 modules
這個目錄都會不見。如果沒有上面三行,那 compile
就不會過關。所以一般來講,modversions.h 我們會選擇在
compile 時傳給 gcc 使用。就像下面這個樣子。


gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS main.c -include usr/src/linux/include/linux/modversions.h


在這個 command line 裡,我們看到了
-D__KERNEL__,這是說要定義 __KERNEL__ 這個
constant。很多跟 kernel 有關的 header
file,都必須要定義這個 constant 才能 include
的。所以建議你最好將它定義起來。另外還有一個
-DMODVERSIONS。這個 constant
我剛才忘了講。剛才我們說要解決 fucntion 或 variable
名字 encode 的方式就是要 include
modversions.h,其實除此之外,你還必須定義 MODVERSIONS
這個 constant。再來就是 MODULE 這個
constant。其實,只要是你要寫 module
就一定要定義這個變量。而且你還要 include module.h
這個檔案,因為 _set_ver 就是定義在這裡的。


講到這裡,相信各位應該對 module
有一些認識了,以後遇到 module unresolved
應該不會感到困惑了,應該也有辦法解決了。


剛才講的都是使用別人的 function 上遇到的名字 encode
問題。但是,如果我們自己的 module 想要 export
一些東西讓別的 module 使用呢。很簡單。在 default
上,在你的 module 裡所有的 global variable 和 function
都會被認定為你要 export 出去的。所以,如果你的
module 裡有 10 個 global variable,經由
ksyms,你可以發現這十個 variable 都會被 export
出去。這當然是個很方便的事啦,但是,你知道,有時候我們根本不想把所有的
variable 都 export 出去,萬一有個 module 沒事亂改我們的
variable 怎?辦呢 ?
所以,在很多時候,我們都只會限定幾個必要的東西
export 出去。在 2.2.1 之前的 kernel (不是很確定)
可以利用 register_symtab
來幫我們。但是,現在更新的版本早就出來了。所以,在此,我會介紹
kernel 2.2.1 裡所提供的。kernel 2.2.1 裡提供了一個
macro,叫做 EXPORT_SYMBOL,這是用來幫我們選擇要 export
的 variable 或 function。比方說,我要 export 一個叫 full
的 variable,那我只要在 module 裡寫:


EXPORT_SYMBOL(full);


就會自動將 full export 出去,你馬上就可以從 ksyms
裡發現有 full 這個變量被 export 出去。在使用
EXPORT_SYMBOL 之前,要小心一件事,就是必須在 gcc
裡定義 EXPORT_SYMTAB 這個 constant,否則在 compile
時會發生 parser error。所以,要使用 EXPORT_SYMBOL
的話,那 gcc 應該要下:


gcc -c -D__KERNEL__ -DMODULE -DMODVERSIONS -DEXPORT_SYMTAB main.c -include /usr/src/linux/include/linux/modversions.h


如果我們不想 export 任何的東西,那我們只要在 module
裡下


EXPORT_NO_SYMBOLS;


就可以了。使用 EXPORT_NO_SYMBOLS 用不著定義任何的
constant。其實,如果各位使用過舊版的 register_symbol
的話,一定會覺得新版的方式比較好用。至少我是這樣覺得啦。因為使用
register_symbol 還要先定義出自己的
symbol_table,感覺有點麻煩。


當我們使用 EXPORT_SYMBOL 把一些 function 或 variable export
出來之後,我們使用 ksyma -a 去看一些結果。我們發現
EXPORT_SYMBOL(full) 的確是把 full export出來了 :


c8822200 full [my_module]
c01b8e08 pci_find_slot_R454463b5
. . .


但是,結果怎?跟我們想像中的不太一樣,照理說,應該是
full_Rxxxxxx 之類的東西才對啊,怎?才出現 full 而已呢
? 奇怪,問題在那裡呢 ?


其實,問題就在於我們沒有對本身的 module 所 export
出來的 function 或 variable 的名字做
encode。想想,如果在 module 的開頭。我們加入一行


#define full full_Rxxxxxx


之後,我們再重新 compile module 一次,載到 kernel
之後,就可以發現 ksyms -a 顯示的是


c8822200 full_Rxxxxxx [my_module]
c01b8e08 pci_find_slot_R454463b5
. . . . .


了。那是不是說,我們要去對每一個 export 出來的
variable 和 function 做 define 的動作呢 ?
當然不是囉。記得嗎,前頭我們講去使用 kernel export
的 function 時,由於 include 了一些 .ver
的檔案,以致於我們不用再做 define
的動作。現在,我們也要利用 .ver
的檔案來幫我們,使我們 module export 出來的 function
也可以自動加入 kernel version 的 information。也就是變成
full_Rxxxxxx 之類的東西。


Linux 裡提供了一個 command,叫
genksyms,就是用來幫我們產生這種 .ver
的檔案的。它會從 stdin 裡讀取 source code,然後檢查
source code 裡是否有 export 的 variable 或
function。如果有,它就會自動為每個 export
出來的東西產生一些 define。這些 define
就是我們之前說的。等我們有了這些 define
之後,只要在我們的 module 裡加入這些 define,那 export
出來的 function 或 variable 就會變成上面那個樣子。


假設我們的程序都放在一個叫 main.c
的檔案裡,我們可以使用下列的方式產生這些 define。


gcc -E -D__GENKSYMS__ main.c genksyms -k 2.2.1 > main.ver


gcc 的 -E 參數是指將 preprocessing 的結果 show
出來。也就是說將它 include 的檔案,一些 define
的結果都展開。-D__GENKSYMS__
是一定要的。如果沒有定義這個
constant,你將不會看到任何的結果。用一個管線是因為
genksyms 是從 stdin 讀資料的,所以,經由管線將 gcc
的結果傳給 genksyms。-k 2.2.1 是指目前使用的 kernel
版本是 2.2.1,如果你的 kernel
版本不一樣,必須指定你的 kernel 的版本。產生的
define 將會被放到 main.ver 裡。產生完 main.ver
檔之後,在 main.c 裡將它 include 進來,那一切就 OK
了。有件事要告訴各位的是,使用這個方式產生的
module,其 export 出來的東西會經由 main.ver 的 define
改頭換面。所以如果你要讓別人使用,那你必須將
main.ver 公開,不然,別人就沒辦法使用你 export
出來的東西了。


講了這?多,相信各位應該都已經比較清楚 module 在
kernel 中是怎?樣一回事,也應該知道為什?有時候
module
會無法加載了。除此之外,各位應該還知道如何使自己
module export 出來的東西也具有 kernel version 的
information。


接下來,要跟各位講的就是,如何寫一個 module
了。其實,寫一個 module
很簡單的。如果你瞭解我上面所說的東西。那我再講一次,再用個例子,相信大家就都會了。要寫一個
module,必須要提供兩個 function。這兩個 function 是給
insmod 和 rmmod 使用的。它們分別是 init_module(),以及
cleanup_module()。


int init_module();
void cleanup_module();


相信大家都知道在 Linux 裡可以使用 insmod 這個 command
來將某個 module 加載。比方說,我有一個 module 叫
hello.o,那使用 insmod hello.o 就可以將 hello 這個 module
載到 kernel 裡。觀察 /etc/modules 應該就可以看到 hello
這個 module 的名字。如果要將 hello 這個 module
移除,則只要使用 rmmod hello 就可以了。insmod 在加載
module 之後,就會去呼叫 module 所提供的
init_module()。如果傳回 0 表示成功,那 module
就會被加載。如果失敗,那加載的動作就會失敗。一般來講,我們在
init_module()
做的事都是一些初始化的工作。比方說,你的 module
需要一塊內存,那你就可以在 init_module() 做 kmalloc
的動作。想當然爾。cleanup_module() 就是在 module
要移除的時候做的事。做的事一般來講就是一些善後的工作,比方像把之前
kmalloc 的內存 free 掉。


由於 module 是載到 kernel 使用的,所以,可能別的 module
會使用你的 module,甚至某些 process 也會使用到你的
module,為了避免 module 還有人使用時就被移除,每個
module 都有一個 use count。用來記錄目前有多少個 process
或 module 正在使用這個 module。當 module 的 use count
不等於 0 時,module 是不會被移除掉的。也就是說,當
module 的 use count 不等於 0 時,cleanup_module()
是不會被呼叫的。


在此,我要介紹三個 macro,是跟 module 的 use count
有關的。


MOD_INC_USE_COUNT
MOD_DEC_USE_COUNT
MOD_IN_USE


MOD_INC_USE_COUNT 是用來增加 module 的 use count,而
MOD_DEC_USE_COUNT 是用來減少 module 的 use count。至於
MOD_IN_USE 則是用來檢查目前這個 module
是不是被使用中。也就是檢查 use count 是否為 0。module
的 use count 必須由寫 module 的人自己來
maintain。系統並不會自動為你把 use count
加一或減一。一切都得由自己控制。下面有一個例子,但是,並不會介紹這三個
macro
的使用方法。將來如果有機會,我再來介紹這三個
macro 的用法。


這個例子很簡單。其實只是示範如何使用 init_module()
以及 cleanup_module() 來寫一個 module。當然,這兩個
function 只是構成 module 的基本條件罷了。至於 module
裡要提供的功能則是看各人的需要。


main.c
#define MODULE
#include <linux/module.h>
#include <asm/uaccess.h>
int full;
EXPORT_SYMBOL(full); /* 將 full export 出去 */
int init_module( void )
{
printk( "<5> Module is loadedn" );
return 0;


}


void cleanup_module( void )
{
printk( "<5> Module is unloadedn" );


}


關於 printk 是這樣子的,它是 kernel
所提供的一個打印訊息的 function。kernel 有 export 這個
function。所以你可以自由的使用它。它的用法跟 printf
幾乎一模一樣。唯獨訊息的開頭是
<5>,其實,不見得這三個字符啦。也可以是
<4>,<3>,<7> 等等的東西。這是代表這個訊息的 prioirty
或 level。<5> 表示的是跟 KERNEL 有關的訊息。

main.ver:


利用 genksyms 產生出來的。


gcc -E -D__GENKSYMS__ main.c genksyms -k 2.2.1 > main.ver


接下來,就是要把 main.c compile 成 main.o


gcc -D__KERNEL__ -DMODVERSIONS -DEXPORT_SYMTAB -c -I/usr/src/linux/include/linux -include /usr/src/linux/include/linux/modversions.h -include ./main.ver main.c


好了。main.o 已經成功的 compile 出來了,現在下一個
command,


insmod main.o


檢查看 /proc/modules 裡是否有 main 這個
module。如果有,表示 main 這個 module 已經載到 kernel
了。再下一個指令,看看 full export 出去的結果。


ksyms


結果顯示


Address Symbol Defined by
c40220e0 full_R355b84b2 [main]
c401d04c ne_probe [ne]
c401a04c ei_open [8390]
c401a094 ei_close [8390]
c401a504 ei_interrupt [8390]
c401af1c ethdev_init [8390]
c401af80 NS8390_init [8390]


可以看到 full_R355b84b2,表示,我們已經成功的將 full
的名字加上 kernel version 的 information
了。當我們不需要這個 module 時,我們就可以下一個
command,


rmmod main


這樣 main 就會被移除掉了。再檢查看看 /proc/modules
就可以發現 main 那一行不見了。各位現在可以看一下
/var/log/message 這個檔案,應該可以發現以兩行


Apr 12 14:19:05 host kernel: Module is loaded
Apr 12 14:39:29 host kernel: Module is unloaded


這兩行就是 printk 印出來的。


關於 module 的介紹已經到此告一段落了。其實,使用
module 實在是很簡單的一件事。對於要發展 driver
或是增加 kernel 某些新功能的人來講,用 module
不啻為一個方便的方式。希望這篇文章對各位能有所幫助。