|
2008年10月27日
linux目錄架構(gòu) / 根目錄 /bin 常用的命令 binary file 的目錄 /boot 存放系統(tǒng)啟動時必須讀取的檔案,包括核心 (kernel) 在內(nèi) /boot/grub/menu.lst GRUB設(shè)置 /boot/vmlinuz 內(nèi)核 /boot/initrd 核心解壓縮所需 RAM Disk /dev 系統(tǒng)周邊設(shè)備 /etc 系統(tǒng)相關(guān)設(shè)定文件 /etc/DIR_COLORS 設(shè)定顏色 /etc/HOSTNAME 設(shè)定用戶的節(jié)點名 /etc/NETWORKING 只有YES標(biāo)明網(wǎng)絡(luò)存在 /etc/host.conf 文件說明用戶的系統(tǒng)如何查詢節(jié)點名 /etc/hosts 設(shè)定用戶自已的IP與名字的對應(yīng)表 /etc/hosts.allow 設(shè)置允許使用inetd的機(jī)器使用 /etc/hosts.deny 設(shè)置不允許使用inetd的機(jī)器使用 /etc/hosts.equiv 設(shè)置遠(yuǎn)端機(jī)不用密碼 /etc/inetd.conf 設(shè)定系統(tǒng)網(wǎng)絡(luò)守護(hù)進(jìn)程inetd的配置 /etc/gateways 設(shè)定路由器 /etc/protocols 設(shè)定系統(tǒng)支持的協(xié)議 /etc/named.boot 設(shè)定本機(jī)為名字服務(wù)器的配置文件 /etc/sysconfig/network-scripts/ifcfg-eth0 設(shè)置IP /etc/resolv.conf 設(shè)置DNS /etc/X11 X Window的配置文件,xorg.conf 或 XF86Config 這兩個 X Server 的設(shè)定檔 /etc/fstab 記錄開機(jī)要mount的文件系統(tǒng) /etc/inittab 設(shè)定系統(tǒng)啟動時init進(jìn)程將把系統(tǒng)設(shè)置成什么樣的runlevel /etc/issue 記錄用戶登錄前顯示的信息 /etc/group 設(shè)定用戶的組名與相關(guān)信息 /etc/passwd 帳號信息 /etc/shadow 密碼信息 /etc/sudoers 可以sudo命令的配置文件 /etc/securetty 設(shè)定哪些終端可以讓root登錄 /etc/login.defs 所有用戶登錄時的缺省配置 /etc/exports 設(shè)定NFS系統(tǒng)用的 /etc/init.d/ 所有服務(wù)的預(yù)設(shè)啟動 script 都是放在這裡的,例如要啟動或者關(guān)閉 /etc/xinetd.d/ 這就是所謂的 super daemon 管理的各項服務(wù)的設(shè)定檔目錄 /etc/modprobe.conf 內(nèi)核模塊額外參數(shù)設(shè)定 /etc/syslog.conf 日志設(shè)置文件 /home 使用者家目錄 /lib 系統(tǒng)會使用到的函數(shù)庫 /lib/modules kernel 的相關(guān)模塊 /var/lib/rpm rpm套件安裝處 /lost+found 系統(tǒng)不正常產(chǎn)生錯誤時,會將一些遺失的片段放置於此目錄下 /mnt 外設(shè)的掛載點 /media 與/mnt類似 /opt 主機(jī)額外安裝的軟件 /proc 虛擬目錄,是內(nèi)存的映射 /proc/version 內(nèi)核版本 /proc/sys/kernel 系統(tǒng)內(nèi)核功能 /root 系統(tǒng)管理員的家目錄 /sbin 系統(tǒng)管理員才能執(zhí)行的指令 /srv 一些服務(wù)啟動之後,這些服務(wù)所需要取用的資料目錄 /tmp 一般使用者或者是正在執(zhí)行的程序暫時放置檔案的地方 /usr 最大的目錄,存許應(yīng)用程序和文件 /usr/X11R6: X-Window目錄 /usr/src: Linux源代碼 /usr/include:系統(tǒng)頭文件 /usr/openwin 存放SUN的OpenWin /usr/man 在線使用手冊 /usr/bin 使用者可執(zhí)行的 binary file 的目錄 /usr/local/bin 使用者可執(zhí)行的 binary file 的目錄 /usr/lib 系統(tǒng)會使用到的函數(shù)庫 /usr/local/lib 系統(tǒng)會使用到的函數(shù)庫 /usr/sbin 系統(tǒng)管理員才能執(zhí)行的指令 /usr/local/sbin 系統(tǒng)管理員才能執(zhí)行的指令 /var 日志文件 /var/log/secure 記錄登入系統(tǒng)存取資料的檔案,例如 pop3, ssh, telnet, ftp 等都會記錄在此檔案中 /var/log/wtmp 記錄登入者的訊息資料, last /var/log/messages 幾乎系統(tǒng)發(fā)生的錯誤訊息 /var/log/boot.log 記錄開機(jī)或者是一些服務(wù)啟動的時候,所顯示的啟動或關(guān)閉訊息 /var/log/maillog 紀(jì)錄郵件存取或往來( sendmail 與 pop3 )的使用者記錄 /var/log/cron 記錄 crontab 這個例行性服務(wù)的內(nèi)容 /var/log/httpd, /var/log/news, /var/log/mysqld.log, /var/log/samba, /var/log/procmail.log: 分別是幾個不同的網(wǎng)路服務(wù)的記錄檔 一些常用的基本命令: uname -a 查看內(nèi)核版本 ls -al 顯示所有文件的屬性 pwd 顯示當(dāng)前路徑 cd - 返回上一次目錄 cd ~ 返回主目錄 date s 設(shè)置時間、日期 cal 顯示日歷 cal 2006 bc 計算器具 man & info 幫助手冊 locale 顯示當(dāng)前字體 locale -a 所有可用字體 /etc/sysconfig/i18n設(shè)置文件 LANG=en 使用英文字體 sync 將數(shù)據(jù)同步寫入硬盤 shutdonw -h now & half & poweroff 關(guān)機(jī) reboot 重啟 startx & init 5 進(jìn)入圖形介面 /work & ?work 向上、下查找文檔內(nèi)容 chgrp 改變檔案群組 chgrp testing install.log chown 改變所屬人 chown root:root install.log chmod 改變屬性 chmod 777 install.log read=4 write=2 execute=1 cp 復(fù)制 cp filename rm 刪除文件 rm -rf filename 強(qiáng)制刪除文件 rmdir 刪除文件夾 mv 移動 mv 123.txt 222.txt 重命名 mkdir 創(chuàng)建文件夾 touch 創(chuàng)建文件 更新當(dāng)前時間 cat 由第一行開始顯示 cat |more 分頁 nl 在內(nèi)容前加行號 more & less 一面一面翻動 head -n filename 顯示第N行內(nèi)容 tail -n filename 顯示后N行內(nèi)容 od 顯示非純文檔 df -h 顯示分區(qū)空間 du 顯示目錄或文件的大小 fdisk 分區(qū)設(shè)置 fdisk -l /dev/hda 顯示硬盤分區(qū)狀態(tài) mkfs 建立各種文件系統(tǒng) mkfs -t ext3 /dev/ram15 fsck 檢查和修復(fù)LINUX檔案 ln 硬鏈接 ln -s 軟件鏈接 whereis 查找命令 locate 查找 find 查找 find / -name "***.*** " which 查看工具 whoami 顯示當(dāng)前用戶 gcc -v 查看GCC版本 chattr +i filename 禁止刪除 chattr -i filename 取消禁止 lsattr 顯示隱藏檔屬性 updatedb 更新資料庫 mke2fs 格式化 mkfs -t ext3 dd if=/etc/passwd of=/tmp/passwd.bak 備份 mount 列出系統(tǒng)所有的分區(qū) mount -t iso9660 /dev/cdrom /mnt/cdrom 掛載光盤 mount -t vfat /dev/fd0 /mnt/floppy 掛載軟盤 mount -t vfat -o iocharset=utf8,umask=000 /dev/hda2 /mnt/hda2 掛載fat32分區(qū) mount -t ntfs -o nls=utf8,umask=000 /dev/hda3 /mnt/hda3 掛載ntfs分區(qū) Linux-NTFS Project: http://linux-ntfs.sourceforge.net/ umount /mnt/hda3 缷載 ifconfig 顯示或設(shè)置網(wǎng)絡(luò)設(shè)備 service network restart 重啟網(wǎng)卡 ifdown eth0 關(guān)閉網(wǎng)卡 ifup eth0 開啟網(wǎng)卡 clear 清屏 history 歷史記錄 !55 執(zhí)行第55個指令 stty 設(shè)置終端 stty -a fdisk /mbr 刪除GRUB at 僅進(jìn)行一次的工作排程 crontab 循環(huán)執(zhí)行的例行性命令 [e]編輯,[l]顯示,[r]刪除任務(wù) & 后臺運(yùn)行程序 tar -zxvf 123.tar.gz & ---------> 后臺運(yùn)行 jobs 觀看后臺暫停的程序 jobs -l fg 將后臺程序調(diào)到前臺 fg n ------> n是數(shù)字,可以指定進(jìn)行那個程序 bg 讓工作在后臺運(yùn)行 kill 結(jié)束進(jìn)程 kill -9 PID [9]強(qiáng)制結(jié)束,[15]正常結(jié)束,[l]列出可用的kill信號 ps aux 查看后臺程序 top 查看后臺程序 top -d 2 每兩秒更新一次 top -d 2 -p10604 觀看某個PID
top -b -n 2 > /tmp/top.txt -----> 將
top 的資訊進(jìn)行 2 次,然後將結(jié)果輸出到 /tmp/top.txt pstree 以樹狀圖顯示程序 [A]以 ASCII 來連接, [u]列出PID, [p]列出帳號 killall 要刪除某個服務(wù) killall -9 httpd free 顯示內(nèi)存狀態(tài) free -m --------> 以M為單位顯示 uptime 顯示目前系統(tǒng)開機(jī)時間 netstat 顯示網(wǎng)絡(luò)狀態(tài) netstat -tulnp------> 找出目前系統(tǒng)上已在監(jiān)聽的網(wǎng)路連線及其 PID dmesg 顯示開機(jī)信息 demsg | more
nice 設(shè)置優(yōu)先權(quán) nice -n -5 vi &
-----> 用 root 給一個 nice 植為 -5 ,用於執(zhí)行 vi renice 調(diào)整已存在優(yōu)先權(quán) runlevel 顯示目前的runlevel depmod 分析可載入模塊的相依性 lsmod 顯示已載入系統(tǒng)的模塊 modinfo 顯示kernel模塊的信息 insmod 載入模塊 modprobe 自動處理可載入模塊 rmmod 刪除模塊 chkconfig 檢查,設(shè)置系統(tǒng)的各種服務(wù) chkconfig --list -----> 列出各項服務(wù)狀態(tài) ntsysv 設(shè)置系統(tǒng)的各種服務(wù) cpio 備份文件 壓縮命令: *.Z compress 程式壓縮的檔案; *.bz2 bzip2 程式壓縮的檔案; *.gz gzip 程式壓縮的檔案; *.tar tar 程式打包的資料,並沒有壓縮過; *.tar.gz tar 程式打包的檔案,其中並且經(jīng)過 gzip 的壓縮 compress filename 壓縮文件 加[-d]解壓 uncompress gzip filename 壓縮 加[-d]解壓 zcat 123.gz 查看壓縮文件內(nèi)容 bzip2 -z filename 壓縮 加[-d]解壓 bzcat filename.bz2 查看壓縮文件內(nèi)容 tar -cvf /home/123.tar /etc 打包,不壓縮 tar -xvf 123.tar 解開包 tar -zxvf /home/123.tar.gz 以gzip解壓 tar -jxvf /home/123.tar.bz2 以bzip2解壓 tar -ztvf /tmp/etc.tar.gz 查看tar內(nèi)容 cpio -covB > [file|device] 份份 cpio -icduv < [file|device] 還原 vi一般用法 一般模式 編輯模式 指令模式 h 左 a,i,r,o,A,I,R,O :w 保存 j 下 進(jìn)入編輯模式 :w! 強(qiáng)制保存 k 上 dd 刪除光標(biāo)當(dāng)前行 :q! 不保存離開 l 右 ndd 刪除n行 :wq! 保存后離開 0 移動到行首 yy 復(fù)制當(dāng)前行 :e! 還原原始檔 $ 移動到行尾 nyy 復(fù)制n行 :w filename 另存為 H 屏幕最上 p,P 粘貼 :set nu 設(shè)置行號
M 屏幕中央 u 撤消
:set nonu 取消行號 L 屏幕最下 [Ctrl]+r 重做上一個動作 ZZ 保存離開 G 檔案最后一行 [ctrl]+z 暫停退出 :set nohlsearch 永久地關(guān)閉高亮顯示
/work 向下搜索
:sp 同時打開兩個文檔 ?work 向上搜索
[Ctrl]+w 兩個文檔設(shè)換
gg 移動到檔案第一行
:nohlsearch 暫時關(guān)閉高亮顯示 認(rèn)識SHELL alias 顯示當(dāng)前所有的命令別名 alias lm= "ls -al " 命令別名 unalias lm 取消命令別名 type 類似which exprot 設(shè)置或顯示環(huán)境變量 exprot PATH= "$PATH ":/sbin 添加/sbin入PATH路徑 echo $PATH 顯示PATH路徑 bash 進(jìn)入子程序 name=yang 設(shè)定變量 unset name 取消變量 echo $name 顯示變量的內(nèi)容 myname= "$name its me " & myname= '$name its me ' 單引號時$name失去變量內(nèi)容 ciw=/etc/sysconfig/network-scripts/ 設(shè)置路徑 env 列出所有環(huán)境變量 echo $RANDOM 顯示隨意產(chǎn)生的數(shù) set 設(shè)置SHELL PS1= '[\u@\h \w \A #\#]\$ ' 提示字元的設(shè)定 [root@linux ~]# read [-pt] variable -----------讀取鍵盤輸入的變量 參數(shù): -p :後面可以接提示字元! -t :後面可以接等待的『秒數(shù)!』 declare 聲明 shell 變量 ulimit -a 顯示所有限制資料 ls /tmp/yang && echo "exist " || echo "not exist " 意思是說,當(dāng) ls /tmp/yang 執(zhí)行後,若正確,就執(zhí)行echo "exist " ,若有問題,就執(zhí)行echo "not exist " echo $PATH | cut -d ': ' -f 5 以:為分隔符,讀取第5段內(nèi)容 export | cut -c 10-20 讀取第10到20個字節(jié)的內(nèi)容 last | grep 'root ' 搜索有root的一行,加[-v]反向搜索 cat /etc/passwd | sort 排序顯示 cat /etc/passwd | wc 顯示『行、字?jǐn)?shù)、字節(jié)數(shù)』 正規(guī)表示法 [root@test root]# grep [-acinv] '搜尋字串 ' filename 參數(shù)說明: -a :將 binary 檔案以 text 檔案的方式搜尋資料 -c :計算找到 '搜尋字串 ' 的次數(shù) -i :忽略大小寫的不同,所以大小寫視為相同 -n :順便輸出行號 -v :反向選擇,亦即顯示出沒有 '搜尋字串 ' 內(nèi)容的那一行! grep -n 'the ' 123.txt 搜索the字符 -----------搜尋特定字串 grep -n 't[ea]st ' 123.txt 搜索test或taste兩個字符---------利用 [] 來搜尋集合字元 grep -n '[^g]oo ' 123.txt 搜索前面不為g的oo-----------向選擇 [^] grep -n '[0-9] ' 123.txt 搜索有0-9的數(shù)字 grep -n '^the ' 123.txt 搜索以the為行首-----------行首搜索^ grep -n '^[^a-zA-Z] ' 123.txt 搜索不以英文字母開頭 grep -n '[a-z]$ ' 123.txt 搜索以a-z結(jié)尾的行---------- 行尾搜索$ grep -n 'g..d ' 123.txt 搜索開頭g結(jié)尾d字符----------任意一個字元 . grep -n 'ooo* ' 123.txt 搜索至少有兩個oo的字符---------重複字元 * sed 文本流編輯器 利用腳本命令來處理文本文件 awd 模式掃描和處理語言 nl 123.txt | sed '2,5d ' 刪除第二到第五行的內(nèi)容 diff 比較文件的差異 cmp 比較兩個文件是否有差異 patch 修補(bǔ)文件 pr 要打印的文件格式化 帳號管理 /etc/passwd 系統(tǒng)帳號信息 /etc/shadow 帳號密碼信息 經(jīng)MD5 32位加密 在密碼欄前面加『 * 』『 ! 』禁止使用某帳號 /etc/group 系統(tǒng)群組信息 /etc/gshadow newgrp 改變登陸組 useradd & adduser 建立新用戶 ---------> useradd -m test 自動建立用戶的登入目錄 useradd -m -g pgroup test ---------> 指定所屬級 /etc/default/useradd 相關(guān)設(shè)定 /etc/login.defs UID/GID 有關(guān)的設(shè)定 passwd 更改密碼 -----------> passwd test usermod 修改用戶帳號 userdel 刪除帳號 -----------> userdel -r test chsh 更換登陸系統(tǒng)時使用的SHELL [-l]顯示可用的SHELL;[-s]修改自己的SHELL chfn 改變finger指令顯示的信息 finger 查找并顯示用戶信息 id 顯示用戶的ID -----------> id test groupadd 添加組 groupmod 與usermod類似 groupdel 刪除組 su test 更改用戶 su - 進(jìn)入root,且使用root的環(huán)境變量 sudo 以其他身份來執(zhí)行指令 visudo 編輯/etc/sudoers 加入一行『 test ALL=(ALL) ALL 』
%wheel ALL = (ALL) ALL
系統(tǒng)里所有wheel群組的用戶都可用sudo %wheel ALL = (ALL) NOPASSWD: ALL wheel群組所有用戶都不用密碼NOPASSWD User_Alias ADMPW = vbird, dmtsai, vbird1, vbird3 加入ADMPW組 ADMPW ALL = NOPASSWD: !/usr/bin/passwd, /usr/bin/passwd [A-Za-z]*, \ !/usr/bin/passwd root 可以更改使用者密碼,但不能更改root密碼 (在指令前面加入 ! 代表不可) PAM (Pluggable Authentication Modules, 嵌入式模組) who & w 看誰在線 last 最近登陸主機(jī)的信息 lastlog 最近登入的時間 讀取 /var/log/lastlog talk 與其他用戶交談 write 發(fā)送信息 write test [ctrl]+d 發(fā)送 mesg 設(shè)置終端機(jī)的寫入權(quán)限 mesg n 禁止接收 mesg y wall 向所有用戶發(fā)送信息 wall this is q test mail 寫mail /etc/default/useradd 家目錄默認(rèn)設(shè)置 quota 顯示磁盤已使用的空間與限制 quota -guvs -----> 秀出目前 root 自己的 quota 限制值 quota -vu 查詢
quotacheck 檢查磁盤的使用空間與限制 quotacheck -avug
-----> 將所有的在 /etc/mtab 內(nèi),含有 quota 支援的 partition 進(jìn)行掃瞄 [-m] 強(qiáng)制掃描 quota一定要是獨立的分區(qū),要有quota.user和quota.group兩件文件,在/etc/fstab添加一句: /dev/hda3 /home ext3 defaults,usrquota,grpquota 1 2 chmod 600 quota* 設(shè)置完成,重啟生效 edquota 編輯用戶或群組的quota [u]用戶,[g]群組,[p]復(fù)制,[t]設(shè)置寬限期限
edquota -a yang edquota -p
yang -u young -----> 復(fù)制 quotaon 開啟磁盤空間限制 quotaon -auvg --------> 啟動所有的具有 quota 的 filesystem quotaoff 關(guān)閉磁盤空間限制 quotaoff -a --------> 關(guān)閉了 quota 的限制 repquota -av 查閱系統(tǒng)內(nèi)所有的具有 quota 的 filesystem 的限值狀態(tài) Quota 從開始準(zhǔn)備 filesystem 的支援到整個設(shè)定結(jié)束的主要的步驟大概是: 1、設(shè)定 partition 的 filesystem 支援 quota 參數(shù): 由於 quota 必須要讓 partition 上面的 filesystem 支援才行,一般來說, 支援度最好的是 ext2/ext3 , 其他的 filesystem 類型鳥哥我是沒有試過啦! 啟動 filesystem 支援 quota 最簡單就是編輯 /etc/fstab , 使得準(zhǔn)備要開放的 quota 磁碟可以支援 quota 囉; 2、建立 quota 記錄檔: 剛剛前面講過,整個 quota 進(jìn)行磁碟限制值記錄的檔案是 aquota.user/aquota.group, 要建立這兩個檔案就必須要先利用 quotacheck 掃瞄才行喔! 3、編輯 quota 限制值資料: 再來就是使用 edquota 來編輯每個使用者或群組的可使用空間囉; 4、重新掃瞄與啟動 quota : 設(shè)定好 quota 之後,建議可以再進(jìn)行一次 quotacheck ,然後再以 quotaon 來啟動吧! 開機(jī)流程簡介 1、載入 BIOS 的硬體資訊,並取得第一個開機(jī)裝置的代號; 2、讀取第一個開機(jī)裝置的 MBR 的 boot Loader (亦即是 lilo, grub, spfdisk 等等) 的開機(jī)資訊; 3、載入 Kernel 作業(yè)系統(tǒng)核心資訊, Kernel 開始解壓縮,並且嘗試驅(qū)動所有硬體裝置; 4、Kernel 執(zhí)行 init 程式並取得 run-level 資訊; 5、init 執(zhí)行 /etc/rc.d/rc.sysinit 檔案; 6、啟動核心的外掛模組 (/etc/modprobe.conf); 7、init 執(zhí)行 run-level 的各個批次檔( Scripts ); 8、init 執(zhí)行 /etc/rc.d/rc.local 檔案; 9、執(zhí)行 /bin/login 程式,並等待使用者登入; 10、登入之後開始以 Shell 控管主機(jī)。 在/etc/rc.d/rc3.d內(nèi),以S開頭的為開機(jī)啟動,以K開頭的為關(guān)閉,接著的數(shù)字代表執(zhí)行順序 GRUB vga設(shè)定 彩度\解析度 640x480 800x600 1024x768 1280x1024 bit
256 769 771 773
775 8 bit 32768 784
787 790 793 15 bit
65536 785 788 791
794 16 bit 16.8M 786
789 792 795 32 bit ./configure 檢查系統(tǒng)信息 ./configure --help | more 幫助信息 make clean 清除之前留下的文件 make 編譯 make install 安裝 rpm -q -----> 查詢是否安裝 rpm -ql ------> 查詢該套件所有的目錄 rpm -qi -----> 查詢套件的說明資料 rpm -qc[d] -----> 設(shè)定檔與說明檔 rpm -ivh ----> 安裝 rpm -V --------> 查看套件有否更動過 rpm -e ------> 刪除 rpm -Uvh -------> 升級安裝 --nodeps -----> 強(qiáng)行安裝 --test -----> 測試安裝
2008年10月22日
Linux環(huán)境進(jìn)程間通信(三):消息隊列
本系列文章中的前兩部分,我們探討管道及信號兩種通信機(jī)制,本文將深入第三部分,介紹系統(tǒng) V 消息隊列及其相應(yīng) API。
消息隊列(也叫做報文隊列)能夠克服早期unix通信機(jī)制的一些缺點。作為早期unix通信機(jī)制之一的信號能夠傳送的信息量有限,后來雖然
POSIX
1003.1b在信號的實時性方面作了拓廣,使得信號在傳遞信息量方面有了相當(dāng)程度的改進(jìn),但是信號這種通信方式更像"即時"的通信方式,它要求接受信號
的進(jìn)程在某個時間范圍內(nèi)對信號做出反應(yīng),因此該信號最多在接受信號進(jìn)程的生命周期內(nèi)才有意義,信號所傳遞的信息是接近于隨進(jìn)程持續(xù)的概念
(process-persistent),見附錄 1;管道及有名管道及有名管道則是典型的隨進(jìn)程持續(xù)IPC,并且,只能傳送無格式的字節(jié)流無疑會給應(yīng)用程序開發(fā)帶來不便,另外,它的緩沖區(qū)大小也受到限制。
消息隊列就是一個消息的鏈表。可以把消息看作一個記錄,具有特定的格式以及特定的優(yōu)先級。對消息隊列有寫權(quán)限的進(jìn)程可以向中按照一定的規(guī)則添加新消息;對消息隊列有讀權(quán)限的進(jìn)程則可以從消息隊列中讀走消息。消息隊列是隨內(nèi)核持續(xù)的(參見附錄 1)。
目前主要有兩種類型的消息隊列:POSIX消息隊列以及系統(tǒng)V消息隊列,系統(tǒng)V消息隊列目前被大量使用。考慮到程序的可移植性,新開發(fā)的應(yīng)用程序應(yīng)盡量使用POSIX消息隊列。
在本系列專題的序(深刻理解Linux進(jìn)程間通信(IPC))中,提到對于消息隊列、信號燈、以及共享內(nèi)存區(qū)來說,有兩個實現(xiàn)版本:POSIX的以
及系統(tǒng)V的。Linux內(nèi)核(內(nèi)核2.4.18)支持POSIX信號燈、POSIX共享內(nèi)存區(qū)以及POSIX消息隊列,但對于主流Linux發(fā)行版本之一
redhad8.0(內(nèi)核2.4.18),還沒有提供對POSIX進(jìn)程間通信API的支持,不過應(yīng)該只是時間上的事。
因此,本文將主要介紹系統(tǒng)V消息隊列及其相應(yīng)API。在沒有聲明的情況下,以下討論中指的都是系統(tǒng)V消息隊列。
一、消息隊列基本概念
- 系統(tǒng)V消息隊列是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個消息隊列時,該消息隊列才會真正被刪除。因此系統(tǒng)中記錄消息隊列的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids msg_ids)位于內(nèi)核中,系統(tǒng)中的所有消息隊列都可以在結(jié)構(gòu)msg_ids中找到訪問入口。
- 消息隊列就是一個消息的鏈表。每個消息隊列都有一個隊列頭,用結(jié)構(gòu)struct msg_queue來描述(參見附錄 2)。隊列頭中包含了該消息隊列的大量信息,包括消息隊列鍵值、用戶ID、組ID、消息隊列中消息數(shù)目等等,甚至記錄了最近對消息隊列讀寫進(jìn)程的ID。讀者可以訪問這些信息,也可以設(shè)置其中的某些信息。
- 下圖說明了內(nèi)核與消息隊列是怎樣建立起聯(lián)系的:
其中:struct ipc_ids msg_ids是內(nèi)核中記錄消息隊列的全局?jǐn)?shù)據(jù)結(jié)構(gòu);struct msg_queue是每個消息隊列的隊列頭。

從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu) struct ipc_ids msg_ids 可以訪問到每個消息隊列頭的第一個成員:struct
kern_ipc_perm;而每個struct
kern_ipc_perm能夠與具體的消息隊列對應(yīng)起來是因為在該結(jié)構(gòu)中,有一個key_t類型成員key,而key則唯一確定一個消息隊列。
kern_ipc_perm結(jié)構(gòu)如下:
struct kern_ipc_perm{ //內(nèi)核中記錄消息隊列的全局?jǐn)?shù)據(jù)結(jié)構(gòu)msg_ids能夠訪問到該結(jié)構(gòu); key_t key; //該鍵值則唯一對應(yīng)一個消息隊列 uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; mode_t mode; unsigned long seq; }
|
二、操作消息隊列
對消息隊列的操作無非有下面三種類型:
1、 打開或創(chuàng)建消息隊列 消息隊列的內(nèi)核持續(xù)性要求每個消息隊列都在系統(tǒng)范圍內(nèi)對應(yīng)唯一的鍵值,所以,要獲得一個消息隊列的描述字,只需提供該消息隊列的鍵值即可;
注:消息隊列描述字是由在系統(tǒng)范圍內(nèi)唯一的鍵值生成的,而鍵值可以看作對應(yīng)系統(tǒng)內(nèi)的一條路經(jīng)。
2、 讀寫操作
消息讀寫操作非常簡單,對開發(fā)人員來說,每個消息都類似如下的數(shù)據(jù)結(jié)構(gòu):
struct msgbuf{ long mtype; char mtext[1]; };
|
mtype成員代表消息類型,從消息隊列中讀取消息的一個重要依據(jù)就是消息的類型;mtext是消息內(nèi)容,當(dāng)然長度不一定為1。因此,對于發(fā)送消息
來說,首先預(yù)置一個msgbuf緩沖區(qū)并寫入消息類型和內(nèi)容,調(diào)用相應(yīng)的發(fā)送函數(shù)即可;對讀取消息來說,首先分配這樣一個msgbuf緩沖區(qū),然后把消息
讀入該緩沖區(qū)即可。
3、 獲得或設(shè)置消息隊列屬性:
消息隊列的信息基本上都保存在消息隊列頭中,因此,可以分配一個類似于消息隊列頭的結(jié)構(gòu)(struct msqid_ds,見附錄 2),來返回消息隊列的屬性;同樣可以設(shè)置該數(shù)據(jù)結(jié)構(gòu)。
消息隊列API
1、文件名到鍵值
#include <sys/types.h> #include <sys/ipc.h> key_t ftok (char*pathname, char proj);
|
它返回與路徑pathname相對應(yīng)的一個鍵值。該函數(shù)不直接對消息隊列操作,但在調(diào)用ipc(MSGGET,…)或msgget()來獲得消息隊列描述字前,往往要調(diào)用該函數(shù)。典型的調(diào)用代碼是:
key=ftok(path_ptr, 'a'); ipc_id=ipc(MSGGET, (int)key, flags,0,NULL,0); …
|
2、linux為操作系統(tǒng)V進(jìn)程間通信的三種方式(消息隊列、信號燈、共享內(nèi)存區(qū))提供了一個統(tǒng)一的用戶界面:
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
第一個參數(shù)指明對IPC對象的操作方式,對消息隊列而言共有四種操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分別代表向消息
隊列發(fā)送消息、從消息隊列讀取消息、打開或創(chuàng)建消息隊列、控制消息隊列;first參數(shù)代表唯一的IPC對象;下面將介紹四種操作。
- int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
與該操作對應(yīng)的系統(tǒng)V調(diào)用為:int msgget( (key_t)first,second)。
- int ipc(MSGCTL, int first, int second, int third, void *ptr, long fifth)
與該操作對應(yīng)的系統(tǒng)V調(diào)用為:int msgctl( first,second, (struct msqid_ds*) ptr)。
- int ipc(MSGSND, int first, int second, int third, void *ptr, long fifth);
與該操作對應(yīng)的系統(tǒng)V調(diào)用為:int msgsnd( first, (struct msgbuf*)ptr, second, third)。
- int ipc(MSGRCV, int first, int second, int third, void *ptr, long fifth);
與該操作對應(yīng)的系統(tǒng)V調(diào)用為:int msgrcv( first,(struct msgbuf*)ptr, second, fifth,third),
注:本人不主張采用系統(tǒng)調(diào)用ipc(),而更傾向于采用系統(tǒng)V或者POSIX進(jìn)程間通信API。原因如下:
- 雖然該系統(tǒng)調(diào)用提供了統(tǒng)一的用戶界面,但正是由于這個特性,它的參數(shù)幾乎不能給出特定的實際意義(如以first、second來命名參數(shù)),在一定程度上造成開發(fā)不便。
- 正如ipc手冊所說的:ipc()是linux所特有的,編寫程序時應(yīng)注意程序的移植性問題;
- 該系統(tǒng)調(diào)用的實現(xiàn)不過是把系統(tǒng)V IPC函數(shù)進(jìn)行了封裝,沒有任何效率上的優(yōu)勢;
- 系統(tǒng)V在IPC方面的API數(shù)量不多,形式也較簡潔。
3.系統(tǒng)V消息隊列API 系統(tǒng)V消息隊列API共有四個,使用時需要包括幾個頭文件:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>
|
1)int msgget(key_t key, int msgflg)
參數(shù)key是一個鍵值,由ftok獲得;msgflg參數(shù)是一些標(biāo)志位。該調(diào)用返回與健值key相對應(yīng)的消息隊列描述字。
在以下兩種情況下,該調(diào)用將創(chuàng)建一個新的消息隊列:
- 如果沒有消息隊列與健值key相對應(yīng),并且msgflg中包含了IPC_CREAT標(biāo)志位;
- key參數(shù)為IPC_PRIVATE;
參數(shù)msgflg可以為以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或結(jié)果。
調(diào)用返回:成功返回消息隊列描述字,否則返回-1。
注:參數(shù)key設(shè)置成常數(shù)IPC_PRIVATE并不意味著其他進(jìn)程不能訪問該消息隊列,只意味著即將創(chuàng)建新的消息隊列。
2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg); 該系統(tǒng)調(diào)用從msgid代表的消息隊列中讀取一個消息,并把消息存儲在msgp指向的msgbuf結(jié)構(gòu)中。
msqid為消息隊列描述字;消息返回后存儲在msgp指向的地址,msgsz指定msgbuf的mtext成員的長度(即消息內(nèi)容的長度),msgtyp為請求讀取的消息類型;讀消息標(biāo)志msgflg可以為以下幾個常值的或:
- IPC_NOWAIT 如果沒有滿足條件的消息,調(diào)用立即返回,此時,errno=ENOMSG
- IPC_EXCEPT 與msgtyp>0配合使用,返回隊列中第一個類型不為msgtyp的消息
- IPC_NOERROR 如果隊列中滿足條件的消息內(nèi)容大于所請求的msgsz字節(jié),則把該消息截斷,截斷部分將丟失。
msgrcv手冊中詳細(xì)給出了消息類型取不同值時(>0; <0; =0),調(diào)用將返回消息隊列中的哪個消息。
msgrcv()解除阻塞的條件有三個:
- 消息隊列中有了滿足條件的消息;
- msqid代表的消息隊列被刪除;
- 調(diào)用msgrcv()的進(jìn)程被信號中斷;
調(diào)用返回:成功返回讀出消息的實際字節(jié)數(shù),否則返回-1。
3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg); 向msgid代表的消息隊列發(fā)送一個消息,即將發(fā)送的消息存儲在msgp指向的msgbuf結(jié)構(gòu)中,消息的大小由msgze指定。
對發(fā)送消息來說,有意義的msgflg標(biāo)志為IPC_NOWAIT,指明在消息隊列沒有足夠空間容納要發(fā)送的消息時,msgsnd是否等待。造成msgsnd()等待的條件有兩種:
- 當(dāng)前消息的大小與當(dāng)前消息隊列中的字節(jié)數(shù)之和超過了消息隊列的總?cè)萘浚?
- 當(dāng)前消息隊列的消息數(shù)(單位"個")不小于消息隊列的總?cè)萘浚▎挝?字節(jié)數(shù)"),此時,雖然消息隊列中的消息數(shù)目很多,但基本上都只有一個字節(jié)。
msgsnd()解除阻塞的條件有三個:
- 不滿足上述兩個條件,即消息隊列中有容納該消息的空間;
- msqid代表的消息隊列被刪除;
- 調(diào)用msgsnd()的進(jìn)程被信號中斷;
調(diào)用返回:成功返回0,否則返回-1。
4)int msgctl(int msqid, int cmd, struct msqid_ds *buf); 該系統(tǒng)調(diào)用對由msqid標(biāo)識的消息隊列執(zhí)行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
- IPC_STAT:該命令用來獲取消息隊列信息,返回的信息存貯在buf指向的msqid結(jié)構(gòu)中;
- IPC_SET:該命令用來設(shè)置消息隊列的屬性,要設(shè)置的屬性存儲在buf指向的msqid結(jié)構(gòu)中;可設(shè)置屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同時,也影響msg_ctime成員。
- IPC_RMID:刪除msqid標(biāo)識的消息隊列;
調(diào)用返回:成功返回0,否則返回-1。
三、消息隊列的限制 每個消息隊列的容量(所能容納的字節(jié)數(shù))都有限制,該值因系統(tǒng)不同而不同。在后面的應(yīng)用實例中,輸出了redhat 8.0的限制,結(jié)果參見附錄 3。
另一個限制是每個消息隊列所能容納的最大消息數(shù):在redhad 8.0中,該限制是受消息隊列容量制約的:消息個數(shù)要小于消息隊列的容量(字節(jié)數(shù))。
注:上述兩個限制是針對每個消息隊列而言的,系統(tǒng)對消息隊列的限制還有系統(tǒng)范圍內(nèi)的最大消息隊列個數(shù),以及整個系統(tǒng)范圍內(nèi)的最大消息數(shù)。一般來說,實際開發(fā)過程中不會超過這個限制。
四、消息隊列應(yīng)用實例 消息隊列應(yīng)用相對較簡單,下面實例基本上覆蓋了對消息隊列的所有操作,同時,程序輸出結(jié)果有助于加深對前面所講的某些規(guī)則及消息隊列限制的理解。
#include <sys/types.h> #include <sys/msg.h> #include <unistd.h> void msg_stat(int,struct msqid_ds ); main() { int gflags,sflags,rflags; key_t key; int msgid; int reval; struct msgsbuf{ int mtype; char mtext[1]; }msg_sbuf; struct msgmbuf { int mtype; char mtext[10]; }msg_rbuf; struct msqid_ds msg_ginfo,msg_sinfo; char* msgpath="/unix/msgqueue"; key=ftok(msgpath,'a'); gflags=IPC_CREAT|IPC_EXCL; msgid=msgget(key,gflags|00666); if(msgid==-1) { printf("msg create error\n"); return; } //創(chuàng)建一個消息隊列后,輸出消息隊列缺省屬性 msg_stat(msgid,msg_ginfo); sflags=IPC_NOWAIT; msg_sbuf.mtype=10; msg_sbuf.mtext[0]='a'; reval=msgsnd(msgid,&msg_sbuf,sizeof(msg_sbuf.mtext),sflags); if(reval==-1) { printf("message send error\n"); } //發(fā)送一個消息后,輸出消息隊列屬性 msg_stat(msgid,msg_ginfo); rflags=IPC_NOWAIT|MSG_NOERROR; reval=msgrcv(msgid,&msg_rbuf,4,10,rflags); if(reval==-1) printf("read msg error\n"); else printf("read from msg queue %d bytes\n",reval); //從消息隊列中讀出消息后,輸出消息隊列屬性 msg_stat(msgid,msg_ginfo); msg_sinfo.msg_perm.uid=8;//just a try msg_sinfo.msg_perm.gid=8;// msg_sinfo.msg_qbytes=16388; //此處驗證超級用戶可以更改消息隊列的缺省msg_qbytes //注意這里設(shè)置的值大于缺省值 reval=msgctl(msgid,IPC_SET,&msg_sinfo); if(reval==-1) { printf("msg set info error\n"); return; } msg_stat(msgid,msg_ginfo); //驗證設(shè)置消息隊列屬性 reval=msgctl(msgid,IPC_RMID,NULL);//刪除消息隊列 if(reval==-1) { printf("unlink msg queue error\n"); return; } } void msg_stat(int msgid,struct msqid_ds msg_info) { int reval; sleep(1);//只是為了后面輸出時間的方便 reval=msgctl(msgid,IPC_STAT,&msg_info); if(reval==-1) { printf("get msg info error\n"); return; } printf("\n"); printf("current number of bytes on queue is %d\n",msg_info.msg_cbytes); printf("number of messages in queue is %d\n",msg_info.msg_qnum); printf("max number of bytes on queue is %d\n",msg_info.msg_qbytes); //每個消息隊列的容量(字節(jié)數(shù))都有限制MSGMNB,值的大小因系統(tǒng)而異。在創(chuàng)建新的消息隊列時,//msg_qbytes的缺省值就是MSGMNB printf("pid of last msgsnd is %d\n",msg_info.msg_lspid); printf("pid of last msgrcv is %d\n",msg_info.msg_lrpid); printf("last msgsnd time is %s", ctime(&(msg_info.msg_stime))); printf("last msgrcv time is %s", ctime(&(msg_info.msg_rtime))); printf("last change time is %s", ctime(&(msg_info.msg_ctime))); printf("msg uid is %d\n",msg_info.msg_perm.uid); printf("msg gid is %d\n",msg_info.msg_perm.gid); }
|
程序輸出結(jié)果見 附錄 3。
小結(jié): 消息隊列
與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式字節(jié)流,有利于減少開發(fā)人員的工作量;其次,消息具有類型,在實際應(yīng)用中,可作為優(yōu)先級使
用。這兩點是管道以及有名管道所不能比的。同樣,消息隊列可以在幾個進(jìn)程間復(fù)用,而不管這幾個進(jìn)程是否具有親緣關(guān)系,這一點與有名管道很相似;但消息隊列
是隨內(nèi)核持續(xù)的,與有名管道(隨進(jìn)程持續(xù))相比,生命力更強(qiáng),應(yīng)用空間更大。
附錄 1:在參考文獻(xiàn)[1]中,給出了IPC隨進(jìn)程持續(xù)、隨內(nèi)核持續(xù)以及隨文件系統(tǒng)持續(xù)的定義:
- 隨進(jìn)程持續(xù):IPC一直存在到打開IPC對象的最后一個進(jìn)程關(guān)閉該對象為止。如管道和有名管道;
- 隨內(nèi)核持續(xù):IPC一直持續(xù)到內(nèi)核重新自舉或者顯示刪除該對象為止。如消息隊列、信號燈以及共享內(nèi)存等;
- 隨文件系統(tǒng)持續(xù):IPC一直持續(xù)到顯示刪除該對象為止。
附錄 2: 結(jié)構(gòu)msg_queue用來描述消息隊列頭,存在于系統(tǒng)空間:
struct msg_queue { struct kern_ipc_perm q_perm; time_t q_stime; /* last msgsnd time */ time_t q_rtime; /* last msgrcv time */ time_t q_ctime; /* last change time */ unsigned long q_cbytes; /* current number of bytes on queue */ unsigned long q_qnum; /* number of messages in queue */ unsigned long q_qbytes; /* max number of bytes on queue */ pid_t q_lspid; /* pid of last msgsnd */ pid_t q_lrpid; /* last receive pid */ struct list_head q_messages; struct list_head q_receivers; struct list_head q_senders; };
|
結(jié)構(gòu)msqid_ds用來設(shè)置或返回消息隊列的信息,存在于用戶空間;
struct msqid_ds { struct ipc_perm msg_perm; struct msg *msg_first; /* first message on queue,unused */ struct msg *msg_last; /* last message in queue,unused */ __kernel_time_t msg_stime; /* last msgsnd time */ __kernel_time_t msg_rtime; /* last msgrcv time */ __kernel_time_t msg_ctime; /* last change time */ unsigned long msg_lcbytes; /* Reuse junk fields for 32 bit */ unsigned long msg_lqbytes; /* ditto */ unsigned short msg_cbytes; /* current number of bytes on queue */ unsigned short msg_qnum; /* number of messages in queue */ unsigned short msg_qbytes; /* max number of bytes on queue */ __kernel_ipc_pid_t msg_lspid; /* pid of last msgsnd */ __kernel_ipc_pid_t msg_lrpid; /* last receive pid */ };
|
//可以看出上述兩個結(jié)構(gòu)很相似。
附錄 3:消息隊列實例輸出結(jié)果:
current number of bytes on queue is 0 number of messages in queue is 0 max number of bytes on queue is 16384 pid of last msgsnd is 0 pid of last msgrcv is 0 last msgsnd time is Thu Jan 1 08:00:00 1970 last msgrcv time is Thu Jan 1 08:00:00 1970 last change time is Sun Dec 29 18:28:20 2002 msg uid is 0 msg gid is 0 //上面剛剛創(chuàng)建一個新消息隊列時的輸出 current number of bytes on queue is 1 number of messages in queue is 1 max number of bytes on queue is 16384 pid of last msgsnd is 2510 pid of last msgrcv is 0 last msgsnd time is Sun Dec 29 18:28:21 2002 last msgrcv time is Thu Jan 1 08:00:00 1970 last change time is Sun Dec 29 18:28:20 2002 msg uid is 0 msg gid is 0 read from msg queue 1 bytes //實際讀出的字節(jié)數(shù) current number of bytes on queue is 0 number of messages in queue is 0 max number of bytes on queue is 16384 //每個消息隊列最大容量(字節(jié)數(shù)) pid of last msgsnd is 2510 pid of last msgrcv is 2510 last msgsnd time is Sun Dec 29 18:28:21 2002 last msgrcv time is Sun Dec 29 18:28:22 2002 last change time is Sun Dec 29 18:28:20 2002 msg uid is 0 msg gid is 0 current number of bytes on queue is 0 number of messages in queue is 0 max number of bytes on queue is 16388 //可看出超級用戶可修改消息隊列最大容量 pid of last msgsnd is 2510 pid of last msgrcv is 2510 //對操作消息隊列進(jìn)程的跟蹤 last msgsnd time is Sun Dec 29 18:28:21 2002 last msgrcv time is Sun Dec 29 18:28:22 2002 last change time is Sun Dec 29 18:28:23 2002 //msgctl()調(diào)用對msg_ctime有影響 msg uid is 8 msg gid is 8
|
參考文獻(xiàn):
- UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對POSIX以及系統(tǒng)V消息隊列都有闡述,對Linux環(huán)境下的程序開發(fā)有極大的啟發(fā)意義。
- linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V消息隊列相關(guān)的源代碼分析。
- http://www.fanqiang.com/a4/b2/20010508/113315.html,主要闡述linux下對文件的操作,詳細(xì)介紹了對文件的存取權(quán)限位,對IPC對象的存取權(quán)限同樣具有很好的借鑒意義。
- msgget、msgsnd、msgrcv、msgctl手冊
2008年10月20日
http://blog.programfan.com/trackback.asp?id=6922
C語言的標(biāo)準(zhǔn)庫函數(shù)包括一系列日期和時間處理函數(shù),它們都在頭文件中說明。下面列出了這些函數(shù)。在頭文件中定義了三種類型:time_t,struct tm和clock_t。
在中說明的C語言時間函數(shù)
time_t time(time_t *timer);
double difftime(time_t time1,time_t time2);
struct tm *gmtime(const time_t *timer);
struct tm *localtime(const time_t *timer);
char *asctime(const struct tm *timeptr);
char *ctime(const time_t *timer);
size_t strftime(char *s,size_t maxsize,const char *format,const struct tm *timeptr);
time_t mktime(struct tm *timeptr);
clock_t clock(void);
下面是我從網(wǎng)上收集到的時間函數(shù)集
|
asctime(將時間和日期以字符串格式表示)
|
相關(guān)函數(shù)
|
time,ctime,gmtime,localtime
|
表頭文件
|
#include
|
定義函數(shù)
|
char * asctime(const struct tm * timeptr);
|
函數(shù)說明
|
asctime()將參數(shù)timeptr所指的tm結(jié)構(gòu)中的信息轉(zhuǎn)換成真實世界所使用的時間日期表示方法,然后將結(jié)果以字符串形態(tài)返回。此函數(shù)已經(jīng)由時區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r間,字符串格式為:"Wed Jun 30 21:49:08 1993\n"
|
返回值
|
若再調(diào)用相關(guān)的時間日期函數(shù),此字符串可能會被破壞。此函數(shù)與ctime不同處在于傳入的參數(shù)是不同的結(jié)構(gòu)。
|
附加說明
|
返回一字符串表示目前當(dāng)?shù)氐臅r間日期。
|
范例
|
#include main() { time_t timep; time (&timep); printf("%s",asctime(gmtime(&timep))); }
|
執(zhí)行
|
Sat Oct 28 02:10:06 2000
|
|
|
|
ctime(將時間和日期以字符串格式表示)
|
相關(guān)函數(shù)
|
time,asctime,gmtime,localtime
|
表頭文件
|
#include
|
定義函數(shù)
|
char *ctime(const time_t *timep);
|
函數(shù)說明
|
ctime()將參數(shù)timep所指的time_t結(jié)構(gòu)中的信息轉(zhuǎn)換成真實世界所使用的時間日期表示方法,然后將結(jié)果
以字符串形態(tài)返回。此函數(shù)已經(jīng)由時區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r間,字符串格式為"Wed Jun 30 21 :49 :08
1993\n"。若再調(diào)用相關(guān)的時間日期函數(shù),此字符串可能會被破壞。
|
返回值
|
返回一字符串表示目前當(dāng)?shù)氐臅r間日期。
|
范例
|
#include main() { time_t timep; time (&timep); printf("%s",ctime(&timep)); }
|
執(zhí)行
|
Sat Oct 28 10 : 12 : 05 2000
|
|
|
|
gettimeofday(取得目前的時間)
|
相關(guān)函數(shù)
|
time,ctime,ftime,settimeofday
|
表頭文件
|
#include #include
|
定義函數(shù)
|
int gettimeofday ( struct timeval * tv , struct timezone * tz )
|
函數(shù)說明
|
gettimeofday()會把目前的時間有tv所指的結(jié)構(gòu)返回,當(dāng)?shù)貢r區(qū)的信息則放到tz所指的結(jié)構(gòu)中。 timeval結(jié)構(gòu)定義為: struct timeval{ long tv_sec; /*秒*/ long tv_usec; /*微秒*/ }; timezone 結(jié)構(gòu)定義為: struct timezone{ int tz_minuteswest; /*和Greenwich 時間差了多少分鐘*/ int tz_dsttime; /*日光節(jié)約時間的狀態(tài)*/ }; 上述兩個結(jié)構(gòu)都定義在/usr/include/sys/time.h。tz_dsttime 所代表的狀態(tài)如下 DST_NONE /*不使用*/ DST_USA /*美國*/ DST_AUST /*澳洲*/ DST_WET /*西歐*/ DST_MET /*中歐*/ DST_EET /*東歐*/ DST_CAN /*加拿大*/ DST_GB /*大不列顛*/ DST_RUM /*羅馬尼亞*/ DST_TUR /*土耳其*/ DST_AUSTALT /*澳洲(1986年以后)*/
|
返回值
|
成功則返回0,失敗返回-1,錯誤代碼存于errno。附加說明EFAULT指針tv和tz所指的內(nèi)存空間超出存取權(quán)限。
|
范例
|
#include #include main(){ struct timeval tv; struct timezone tz; gettimeofday (&tv , &tz); printf("tv_sec; %d\n", tv,.tv_sec) ; printf("tv_usec; %d\n",tv.tv_usec); printf("tz_minuteswest; %d\n", tz.tz_minuteswest); printf("tz_dsttime, %d\n",tz.tz_dsttime); }
|
執(zhí)行
|
tv_sec: 974857339 tv_usec:136996 tz_minuteswest:-540 tz_dsttime:0
|
|
|
|
gmtime(取得目前時間和日期)
|
相關(guān)函數(shù)
|
time,asctime,ctime,localtime
|
表頭文件
|
#include
|
定義函數(shù)
|
struct tm*gmtime(const time_t*timep);
|
函數(shù)說明
|
gmtime()將參數(shù)timep 所指的time_t 結(jié)構(gòu)中的信息轉(zhuǎn)換成真實世界所使用的時間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。 結(jié)構(gòu)tm的定義為 struct tm { int tm_sec; int tm_min; int tm_hour; int tm_mday; int tm_mon; int tm_year; int tm_wday; int tm_yday; int tm_isdst; }; int tm_sec 代表目前秒數(shù),正常范圍為0-59,但允許至61秒 int tm_min 代表目前分?jǐn)?shù),范圍0-59 int tm_hour 從午夜算起的時數(shù),范圍為0-23 int tm_mday 目前月份的日數(shù),范圍01-31 int tm_mon 代表目前月份,從一月算起,范圍從0-11 int tm_year 從1900 年算起至今的年數(shù) int tm_wday 一星期的日數(shù),從星期一算起,范圍為0-6 int tm_yday 從今年1月1日算起至今的天數(shù),范圍為0-365 int tm_isdst 日光節(jié)約時間的旗標(biāo) 此函數(shù)返回的時間日期未經(jīng)時區(qū)轉(zhuǎn)換,而是UTC時間。
|
返回值
|
返回結(jié)構(gòu)tm代表目前UTC 時間
|
范例
|
#include main(){ char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; time_t timep; struct tm *p; time(&timep); p=gmtime(&timep); printf("%d%d%d",(1900+p->tm_year), (1+p->tm_mon),p->tm_mday); printf("%s%d;%d;%d\n", wday[p->tm_wday], p->tm_hour, p->tm_min, p->tm_sec); }
|
執(zhí)行
|
2000/10/28 Sat 8:15:38
|
|
|
|
localtime(取得當(dāng)?shù)啬壳皶r間和日期)
|
相關(guān)函數(shù)
|
time, asctime, ctime, gmtime
|
表頭文件
|
#include
|
定義函數(shù)
|
struct tm *localtime(const time_t * timep);
|
函數(shù)說明
|
localtime()將參數(shù)timep所指的time_t結(jié)構(gòu)中的信息轉(zhuǎn)換成真實世界所使用的時間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。結(jié)構(gòu)tm的定義請參考gmtime()。此函數(shù)返回的時間日期已經(jīng)轉(zhuǎn)換成當(dāng)?shù)貢r區(qū)。
|
返回值
|
返回結(jié)構(gòu)tm代表目前的當(dāng)?shù)貢r間。
|
范例
|
#include main(){ char *wday[]={"Sun","Mon","Tue","Wed","Thu","Fri","Sat"}; time_t timep; struct tm *p; time(&timep); p=localtime(&timep); /*取得當(dāng)?shù)貢r間*/ printf ("%d%d%d ", (1900+p->tm_year),( l+p->tm_mon), p->tm_mday); printf("%s%d:%d:%d\n", wday[p->tm_wday],p->tm_hour, p->tm_min, p->tm_sec); }
|
執(zhí)行
|
2000/10/28 Sat 11:12:22
|
|
|
|
mktime(將時間結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)換成經(jīng)過的秒數(shù))
|
相關(guān)函數(shù)
|
time,asctime,gmtime,localtime
|
表頭文件
|
#include
|
定義函數(shù)
|
time_t mktime(strcut tm * timeptr);
|
函數(shù)說明
|
mktime()用來將參數(shù)timeptr所指的tm結(jié)構(gòu)數(shù)據(jù)轉(zhuǎn)換成從公元1970年1月1日0時0分0 秒算起至今的UTC時間所經(jīng)過的秒數(shù)。
|
返回值
|
返回經(jīng)過的秒數(shù)。
|
范例
|
/* 用time()取得時間(秒數(shù)),利用localtime() 轉(zhuǎn)換成struct tm 再利用mktine()將struct tm轉(zhuǎn)換成原來的秒數(shù)*/ #include main() { time_t timep; strcut tm *p; time(&timep); printf("time() : %d \n",timep); p=localtime(&timep); timep = mktime(p); printf("time()->localtime()->mktime():%d\n",timep); }
|
執(zhí)行
|
time():974943297 time()->localtime()->mktime():974943297
|
|
|
|
settimeofday(設(shè)置目前時間)
|
相關(guān)函數(shù)
|
time,ctime,ftime,gettimeofday
|
表頭文件
|
#include #include
|
定義函數(shù)
|
int settimeofday ( const struct timeval *tv,const struct timezone *tz);
|
函數(shù)說明
|
settimeofday()會把目前時間設(shè)成由tv所指的結(jié)構(gòu)信息,當(dāng)?shù)貢r區(qū)信息則設(shè)成tz所指的結(jié)構(gòu)。詳細(xì)的說明請參考gettimeofday()。注意,只有root權(quán)限才能使用此函數(shù)修改時間。
|
返回值
|
成功則返回0,失敗返回-1,錯誤代碼存于errno。
|
錯誤代碼
|
EPERM 并非由root權(quán)限調(diào)用settimeofday(),權(quán)限不夠。 EINVAL 時區(qū)或某個數(shù)據(jù)是不正確的,無法正確設(shè)置時間。
|
|
|
|
time(取得目前的時間)
|
相關(guān)函數(shù)
|
ctime,ftime,gettimeofday
|
表頭文件
|
#include
|
定義函數(shù)
|
time_t time(time_t *t);
|
函數(shù)說明
|
此函數(shù)會返回從公元1970年1月1日的UTC時間從0時0分0秒算起到現(xiàn)在所經(jīng)過的秒數(shù)。如果t 并非空指針的話,此函數(shù)也會將返回值存到t指針?biāo)傅膬?nèi)存。
|
返回值
|
成功則返回秒數(shù),失敗則返回((time_t)-1)值,錯誤原因存于errno中。
|
范例
|
#include mian() { int seconds= time((time_t*)NULL); printf("%d\n",seconds); } |
2008年10月14日
http://www.hansencode.cn/2007/06/tinyxml-chinese-doc.html
TinyXML是一個簡單小巧,可以很容易集成到其它程序中的C++ XML解析器。
它能做些什么
簡單地說,TinyXML解析一個XML文檔并由此生成一個可讀可修改可保存的文檔對象模型(DOM)。
XML的意思是“可擴(kuò)展標(biāo)記語言“(eXtensible Markup
Language)。它允許你創(chuàng)建你自己的文檔標(biāo)記。在為瀏覽器標(biāo)記文檔方面HTML做得很好,然而XML允許你定義任何文檔標(biāo)記,比如可以為一個組織者
應(yīng)用程序定義一個描述“to do”列表的文檔。
XML擁有一個結(jié)構(gòu)化并且方便的格式,所有為存儲應(yīng)用程序數(shù)據(jù)而創(chuàng)建的隨機(jī)文件格式都可以用XML代替,而這一切只需要一個解析器。
最全面正確的說明可以在http://www.w3.org/TR/2004/REC-xml-20040204/找到,但坦白地說,它很晦澀難懂。事實上我喜歡http://skew.org/xml/tutorial上關(guān)于XML的介紹。
有不同的方法可以訪問和與XML數(shù)據(jù)進(jìn)行交互。TinyXML使用文檔對象模型(DOM),這意味著XML數(shù)據(jù)被解析成一個可被瀏覽和操作的C++
對象,然后它可以被寫到磁盤或者另一個輸出流中。你也可以把C++對象構(gòu)造成一個XML文檔然后把它寫到磁盤或者另一個輸出流中。
TinyXML被設(shè)計得容易快速上手。它只有兩個頭文件和四個cpp文件。只需要把它們簡單地加到你的項目中就行了。有一個例子文件——xmltest.cpp來引導(dǎo)你該怎么做。
TinyXML以Zlib許可來發(fā)布,所以你可以在開源或者商業(yè)軟件中使用它。許可證更具體的描述在每個源代碼文件的頂部可以找到。
TinyXML在保證正確和恰當(dāng)?shù)腦ML輸出的基礎(chǔ)上嘗試成為一個靈活的解析器。TinyXML可以在任何合理的C++適用系統(tǒng)上編譯。它不依賴于
異常或者運(yùn)行時類型信息,有沒有STL支持都可以編譯。TinyXML完全支持UTF-8編碼和前64k個字符實體(<i>譯注:如果你不明
白這句譯文,可能你需要了解一下Unicode編碼</i>)。
它無法做些什么
TinyXML不解析不使用DTDs(文檔類型定義)或者XSLs(可擴(kuò)展樣式表語言)。有其它解析器(到www.sourceforge.org
搜索一下XML)具有更加全面的特性,但它們也就更大,需要花更長的時間來建立你的項目,有更陡的學(xué)習(xí)曲線,而且經(jīng)常有一個更嚴(yán)格的許可協(xié)議。如果你是用
于瀏覽器或者有更復(fù)雜的XML需要,那么TinyXML不適合你。
下面的DTD語法在TinyXML里是不做解析的:
<!DOCTYPE Archiv [
<!ELEMENT Comment (#PCDATA)>
]>
因為TinyXML把它看成是一個帶著非法嵌入!ELEMENT結(jié)點的!DOCTYPE結(jié)點。或許這在將來會得到支持。
指南
有耐性些,這是一份能很好地指導(dǎo)你怎么開始的指南,它(非常短小精悍)值得你花時間完整地讀上一遍。
代碼狀況
TinyXML是成熟且經(jīng)過測試的代碼,非常健壯。如果你發(fā)現(xiàn)了漏洞,請?zhí)峤宦┒磮蟾娴絪ourcefore網(wǎng)站上 (www.sourceforge.net/projects/tinyxml)。 我們會盡快修正。
有些地方可以讓你得到提高,如果你對TinyXML的工作感興趣的話可以上sourceforge查找一下。
相關(guān)項目
你也許會覺得TinyXML很有用!(簡介由項目提供)
特性
使用STL
TinyXML可以被編譯成使用或不使用STL。如果使用STL,TinyXML會使用std::string類,而且完全支持
std::istream,std::ostream,operator<<和operator>>。許多API方法都有
‘const char*’和’const std::string&’兩個版本。
如果被編譯成不使用STL,則任何STL都不會被包含。所有string類都由TinyXML它自己實現(xiàn)。所有API方法都只提供’const char*’傳入?yún)?shù)。
使用運(yùn)行時定義:
TIXML_USE_STL
來編譯成不同的版本。這可以作為參數(shù)傳給編譯器或者在“tinyxml.h”文件的第一行進(jìn)行設(shè)置。
注意:如果在Linux上編譯測試代碼,設(shè)置環(huán)境變量TINYXML_USE_STL=YES/NO可以控制STL的編譯。而在Windows上,
項目文件提供了STL和非STL兩種目標(biāo)文件。在你的項目中,在tinyxml.h的第一行添加"#define
TIXML_USE_STL"應(yīng)該是最簡單的。
UTF-8
TinyXML支持UTF-8,所以可以處理任何語言的XML文件,而且TinyXML也支持“legacy模式”——一種在支持UTF-8之前使用的編碼方式,可能最好的解釋是“擴(kuò)展的ascii”。
正常情況下,TinyXML會檢測出正確的編碼并使用它,然而,通過設(shè)置頭文件中的TIXML_DEFAULT_ENCODING值,TinyXML可以被強(qiáng)制成總是使用某一種編碼。
除非以下情況發(fā)生,否則TinyXML會默認(rèn)使用Legacy模式:
- 如果文件或者數(shù)據(jù)流以非標(biāo)準(zhǔn)但普遍的"UTF-8引導(dǎo)字節(jié)" (0xef 0xbb 0xbf)開始,TinyXML會以UTF-8的方式來讀取它。
- 如果包含有encoding="UTF-8"的聲明被讀取,那么TinyXML會以UTF-8的方式來讀取它。
- 如果讀取到?jīng)]有指定編碼方式的聲明,那么TinyXML會以UTF-8的方式來讀取它。
- 如果包含有encoding=“其它編碼”的聲明被讀取,那么TinyXML會以Legacy模式來讀取它。在Legacy模式下,TinyXML會像以前那樣工作,雖然已經(jīng)不是很清楚這種模式是如何工作的了,但舊的內(nèi)容還得保持能夠運(yùn)行。
- 除了上面提到的情況,TinyXML會默認(rèn)運(yùn)行在Legacy模式下。
如果編碼設(shè)置錯誤或者檢測到錯誤會發(fā)生什么事呢?TinyXML會嘗試跳過這些看似不正確的編碼,你可能會得到一些奇怪的結(jié)果或者亂碼,你可以強(qiáng)制TinyXML使用正確的編碼模式。
通過使用LoadFile( TIXML_ENCODING_LEGACY )或者LoadFile( filename,
TIXML_ENCODING_LEGACY ),
你可以強(qiáng)制TinyXML使用Legacy模式。你也可以通過設(shè)置TIXML_DEFAULT_ENCODING =
TIXML_ENCODING_LEGACY來強(qiáng)制一直使用Legacy模式。同樣的,你也可以通過相同的方法來強(qiáng)制設(shè)置成
TIXML_ENCODING_UTF8。
對于使用英文XML的英語用戶來說,UTF-8跟low-ASCII是一樣的。你不需要知道UTF-8或者一點也不需要修改你的代碼。你可以把UTF-8當(dāng)作是ASCII的超集。
UTF-8并不是一種雙字節(jié)格式,但它是一種標(biāo)準(zhǔn)的Unicode編碼!TinyXML當(dāng)前不使用或者直接支持wchar,TCHAR,或者微軟的
_UNICODE。"Unicode"這個術(shù)語被普遍地認(rèn)為指的是UTF-16(一種unicode的寬字節(jié)編碼)是不適當(dāng)?shù)模@是混淆的來源。
對于“high-ascii”語言來說——幾乎所有非英語語言,只要XML被編碼成UTF-8,
TinyXML就能夠處理。說起來可能有點微妙,比較舊的程序和操作系統(tǒng)趨向于使用“默認(rèn)”或者“傳統(tǒng)”的編碼方式。許多應(yīng)用程序(和幾乎所有現(xiàn)在的應(yīng)用
程序)都能夠輸出UTF-8,但是那些比較舊或者難處理的(或者干脆不能使用的)系統(tǒng)還是只能以默認(rèn)編碼來輸出文本。
比如說,日本的系統(tǒng)傳統(tǒng)上使用SHIFT-JIS編碼,這種情況下TinyXML就無法讀取了。但是一個好的文本編輯器可以導(dǎo)入SHIFT-JIS的文本然后保存成UTF-8編碼格式的。
Skew.org link上關(guān)于轉(zhuǎn)換編碼的話題做得很好。
測試文件“utf8test.xml”包含了英文、西班牙文、俄文和簡體中文(希望它們都能夠被正確地轉(zhuǎn)化)。“utf8test.gif”文件是
從IE上截取的XML文件快照。請注意如果你的系統(tǒng)上沒有正確的字體(簡體中文或者俄文),那么即使你正確地解析了也看不到與GIF文件上一樣的輸出。同
時要注意在一個西方編碼的控制臺上(至少我的Windows機(jī)器是這樣),Print()或者printf()也無法正確地顯示這個文件,這不關(guān)
TinyXML的事——這只是操作系統(tǒng)的問題。TinyXML沒有丟掉或者損壞數(shù)據(jù),只是控制臺無法顯示UTF-8而已。
實體
TinyXML認(rèn)得預(yù)定義的特殊“字符實體”,即:
& &
< <
> >
" "
' ‘
這些在XML文檔讀取時都會被辨認(rèn)出來,并會被轉(zhuǎn)化成等價的UTF-8字符。比如下面的XML文本:
Far & Away
從TiXmlText 對象查詢出來時會變成"Far & Away"這樣的值,而寫回XML流/文件時會以“&”的方式寫回。老版本的TinyXML“保留”了字符實體,而在新版本中它們會被轉(zhuǎn)化成字符串。
另外,所有字符都可以用它的Unicode編碼數(shù)字來指定, " "和" "都表示不可分的空格字符。
打印
TinyXML有幾種不同的方式來打印輸出,當(dāng)然它們各有各的優(yōu)缺點。
- Print( FILE* ):輸出到一個標(biāo)準(zhǔn)C流中,包括所有的C文件和標(biāo)準(zhǔn)輸出。
- "相當(dāng)漂亮的打印", 但你沒法控制打印選項。
- 輸出數(shù)據(jù)直接寫到FILE對象中,所以TinyXML代碼沒有內(nèi)存負(fù)擔(dān)。
- 被Print()和SaveFile()調(diào)用。
- operator<<:輸出到一個c++流中。
- 與C++ iostreams集成在一起。
- 在"network printing"模式下輸出沒有換行符,這對于網(wǎng)絡(luò)傳輸和C++對象之間的XML交換有好處,但人很難閱讀。
- TiXmlPrinter:輸出到一個std::string或者內(nèi)存緩沖區(qū)中。
- API還不是很簡練。
- 將來會增加打印選項。
- 在將來的版本中可能有些細(xì)微的變化,因為它會被改進(jìn)和擴(kuò)展。
流
設(shè)置了TIXML_USE_STL,TinyXML就能支持C++流(operator <<,>>)和C(FILE*)流。但它們之間有些差異你需要知道:
C風(fēng)格輸出:
- 基于FILE*
- 用Print()和SaveFile()方法
生成具有很多空格的格式化過的輸出,這是為了盡可能讓人看得明白。它們非常快,而且能夠容忍XML文檔中的格式錯誤。例如一個XML文檔包含兩個根元素和兩個聲明仍然能被打印出來。
C風(fēng)格輸入:
- 基于FILE*
- 用Parse()和LoadFile()方法
速度快,容錯性好。當(dāng)你不需要C++流時就可以使用它。
C++風(fēng)格輸出:
- 基于std::ostream
- operator<<
生成壓縮過的輸出,目的是為了便于網(wǎng)絡(luò)傳輸而不是為了可讀性。它可能有些慢(可能不會),這主要跟你系統(tǒng)上ostream類的實現(xiàn)有關(guān)。無法容忍格式錯誤的XML:此文檔只能包含一個根元素。另外根級別的元素?zé)o法以流形式輸出。
C++風(fēng)格輸入:
- 基于std::istream
- operator>>
從流中讀取XML使其可用于網(wǎng)絡(luò)傳輸。通過些小技巧,它知道當(dāng)XML文檔讀取完畢時,流后面的就一定是其它數(shù)據(jù)了。TinyXML總假定當(dāng)它讀取到
根結(jié)點后XML數(shù)據(jù)就結(jié)束了。換句話說,那些具有不止一個根元素的文檔是無法被正確讀取的。另外還要注意由于STL的實現(xiàn)和TinyXML的限
制,operator>>會比Parse慢一些。
空格
對是保留還是壓縮空格這一問題人們還沒達(dá)成共識。舉個例子,假設(shè)‘_’代表一個空格,對于"Hello____world",HTML和某些XML
解析器會解釋成"Hello_world",它們壓縮掉了一些空格。而有些XML解析器卻不會這樣,它們會保留空格,于是就是
“Hello____world”(記住_表示一個空格)。其它的還建議__Hello___world__應(yīng)該變成Hello___world 。
這是一個解決得不能讓我滿意的問題。TinyXML一開始就兩種方式都支持。調(diào)用TiXmlBase::SetCondenseWhiteSpace( bool )來設(shè)置你想要的結(jié)果,默認(rèn)是壓縮掉多余的空格。
如果想要改變默認(rèn)行為,你應(yīng)該在解析任何XML數(shù)據(jù)之前調(diào)用TiXmlBase::SetCondenseWhiteSpace( bool ) ,而且我不建議設(shè)置之后再去改動它。
句柄
想要健壯地讀取一個XML文檔,檢查方法調(diào)用后的返回值是否為null是很重要的。一種安全的檢錯實現(xiàn)可能會產(chǎn)生像這樣的代碼:
TiXmlElement* root = document.FirstChildElement( "Document" );
if ( root )
{
TiXmlElement* element = root->FirstChildElement( "Element" );
if ( element )
{
TiXmlElement* child = element->FirstChildElement( "Child" );
if ( child )
{
TiXmlElement* child2 = child->NextSiblingElement( "Child" );
if ( child2 )
{
// Finally do something useful.
用句柄的話就不會這么冗長了,使用TiXmlHandle類,前面的代碼就會變成這樣:
TiXmlHandle docHandle( &document );
TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
if ( child2 )
{
// do something useful
這處理起來容易多了。 查閱TiXmlHandle可以得到更多的信息。
行列追蹤
對于某些應(yīng)用程序來說,能夠追蹤節(jié)點和屬性在它們源文件中的原始位置是很重要的。另外,知道解析錯誤在源文件中的發(fā)生位置可以節(jié)省大量時間。
TinyXML能夠追蹤所有結(jié)點和屬性在文本文件中的行列原始位置。TiXmlBase::Row() 和
TiXmlBase::Column()
方法返回結(jié)點在源文件中的原始位置。正確的制表符號可以經(jīng)由TiXmlDocument::SetTabSize() 來配置。
使用與安裝
編譯與運(yùn)行xmltest:
提供了一個Linux Makefile和一個Windows Visual C++ .dsw 文件。只需要簡單地編譯和運(yùn)行,它就會在你的磁盤上生成demotest.xml文件并在屏幕上輸出。它還嘗試用不同的方法遍歷DOM并打印出結(jié)點數(shù)。
那個Linux makefile很通用,可以運(yùn)行在很多系統(tǒng)上——它目前已經(jīng)在mingw和MacOSX上測試過。你不需要運(yùn)行 ‘make depend’,因為那些依賴關(guān)系已經(jīng)硬編碼在文件里了。
用于VC6的Windows項目文件
- tinyxml: tinyxml 庫,非STL
- tinyxmlSTL: tinyxml 庫,STL
- tinyXmlTest: 用于測試的應(yīng)用程序,非STL
- tinyXmlTestSTL: 用于測試的應(yīng)用程序,STL
Makefile
在makefile的頂部你可以設(shè)置:
PROFILE,DEBUG,和TINYXML_USE_STL。makefile里有具體描述。
在tinyxml目錄輸入“make clean”然后“make”,就可以生成可執(zhí)行的“xmltest”文件。
在某一應(yīng)用程序中使用:
把tinyxml.cpp,tinyxml.h, tinyxmlerror.cpp, tinyxmlparser.cpp,
tinystr.cpp, 和 tinystr.h
添加到你的項目和makefile中。就這么簡單,它可以在任何合理的C++適用系統(tǒng)上編譯。不需要為TinyXML打開異常或者運(yùn)行時類型信息支持。
TinyXML怎么工作
舉個例子可能是最好的辦法,理解一下:
<?xml version="1.0" standalone=no>
<!– Our to do list data –>
<ToDo>
<Item priority="1"> Go to the <bold>Toy store!</bold></Item>
<Item priority="2"> Do bills</Item>
</ToDo>
它稱不上是一個To Do列表,但它已經(jīng)足夠了。像下面這樣讀取并解析這個文件(叫“demo.xml”)你就能創(chuàng)建一個文檔:
TiXmlDocument doc( "demo.xml" );
doc.LoadFile();
現(xiàn)在它準(zhǔn)備好了,讓我們看看其中的某些行和它們怎么與DOM聯(lián)系起來。
<?xml version="1.0" standalone=no>
第一行是一個聲明,它會轉(zhuǎn)化成TiXmlDeclaration 類,同時也是文檔結(jié)點的第一個子結(jié)點。
這是TinyXML唯一能夠解析的指令/特殊標(biāo)簽。一般來說指令標(biāo)簽會保存在TiXmlUnknown 以保證在它保存回磁盤時不會丟失這些命令。
<!– Our to do list data –>
這是一個注釋,會成為一個TiXmlComment對象。
<ToDo>
"ToDo"標(biāo)簽定義了一個TiXmlElement 對象。它沒有任何屬性,但包含另外的兩個元素。
<Item priority="1">
生成另一個TiXmlElement對象,它是“ToDo”元素的子結(jié)點。此元素有一個名為“priority”和值為“1”的屬性。
Go to the
TiXmlText ,這是一個葉子結(jié)點,它不能再包含其它結(jié)點,是"Item" TiXmlElement的子結(jié)點。
<bold>
另一個TiXmlElement, 這也是“Item”元素的子結(jié)點。
等等
最后,看看整個對象樹:
TiXmlDocument "demo.xml"
TiXmlDeclaration "version=’1.0′" "standalone=no"
TiXmlComment " Our to do list data"
TiXmlElement "ToDo"
TiXmlElement "Item" Attribtutes: priority = 1
TiXmlText "Go to the "
TiXmlElement "bold"
TiXmlText "Toy store!"
TiXmlElement "Item" Attributes: priority=2
TiXmlText "Do bills"
文檔
本文檔由Doxygen使用‘dox’配置文件生成。
許可證
TinyXML基于zlib許可證來發(fā)布:
本軟件按“現(xiàn)狀”提供(即現(xiàn)在你看到的樣子),不做任何明確或隱晦的保證。由使用此軟件所引起的任何損失都決不可能由作者承擔(dān)。
只要遵循下面的限制,就允許任何人把這軟件用于任何目的,包括商業(yè)軟件,也允許修改它并自由地重新發(fā)布:
1. 決不能虛報軟件的來源;你決不能聲稱是你是軟件的第一作者。如果你在某個產(chǎn)品中使用了這個軟件,那么在產(chǎn)品文檔中加入一個致謝辭我們會很感激,但這并非必要。
2. 修改了源版本就應(yīng)該清楚地標(biāo)記出來,決不能虛報說這是原始軟件。
3. 本通告不能從源發(fā)布版本中移除或做修改。
參考書目
萬維網(wǎng)聯(lián)盟是定制XML的權(quán)威標(biāo)準(zhǔn)機(jī)構(gòu),它的網(wǎng)頁上有大量的信息。
權(quán)威指南:http://www.w3.org/TR/2004/REC-xml-20040204/
我還要推薦由OReilly出版由Robert Eckstein撰寫的"XML Pocket Reference"……這本書囊括了入門所需要的一切。
捐助者,聯(lián)系人,還有簡史
非常感謝給我們建議,漏洞報告,意見和鼓勵的所有人。它們很有用,并且使得這個項目變得有趣。特別感謝那些捐助者,是他們讓這個網(wǎng)站頁面生機(jī)勃勃。
有很多人發(fā)來漏洞報告和意見,與其在這里一一列出來不如我們試著把它們寫到“changes.txt”文件中加以贊揚(yáng)。
TinyXML的原作者是Lee Thomason(文檔中還經(jīng)常出現(xiàn)“我”這個詞) 。在Yves Berquin,Andrew Ellerton,和tinyXml社區(qū)的幫助下,Lee查閱修改和發(fā)布新版本。
我們會很感激你的建議,還有我們想知道你是否在使用TinyXML。希望你喜歡它并覺得它很有用。請郵寄問題,評論,漏洞報告給我們,或者你也可登錄網(wǎng)站與我們?nèi)〉寐?lián)系:
www.sourceforge.net/projects/tinyxml
Lee Thomason, Yves Berquin, Andrew Ellerton
2008年10月13日
http://www.yuanma.org/data/2007/0921/article_2859.htm
線程(thread)技術(shù)早在60年代就被提出,但真正應(yīng)用多線程到操作系統(tǒng)中去,是在80年代中期,solaris是這方面的佼佼者。傳統(tǒng)的Unix也支持線程的概念,但是在一個進(jìn)程(process)中只允許有一個線程,這樣多線程就意味著多進(jìn)程。 現(xiàn)在,多線程技術(shù)已經(jīng)被許多操作系統(tǒng)所支持,包括Windows/NT,當(dāng)然,也包括Linux。
為什么有了進(jìn)程的概念后,還要再引入線程呢?使用多線程到底有哪些好處?什么的系統(tǒng)應(yīng)該選用多線程?我們首先必須回答這些問題。
使用多線程的理由之一是和進(jìn)程相比,它是一種非常"節(jié)儉"的多任務(wù)操作方式。我們知道,在Linux系統(tǒng)下,啟動一個新的進(jìn)程必須分配給它獨立的地址空
間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個進(jìn)程中的多個線程,它們彼此之間使用相同的地址
空間,共享大部分?jǐn)?shù)據(jù),啟動一個線程所花費的空間遠(yuǎn)遠(yuǎn)小于啟動一個進(jìn)程所花費的空間,而且,線程間彼此切換所需的時間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時間。
據(jù)統(tǒng)計,總的說來,一個進(jìn)程的開銷大約是一個線程開銷的30倍左右,當(dāng)然,在具體的系統(tǒng)上,這個數(shù)據(jù)可能會有較大的區(qū)別。
使用多線程
的理由之二是線程間方便的通信機(jī)制。對不同進(jìn)程來說,它們具有獨立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)行,這種方式不僅費時,而且很不方
便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他
一些問題,有的變量不能同時被兩個線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災(zāi)難性的打擊,這些正是編寫多線程程序時最
需要注意的地方。
除了以上所說的優(yōu)點外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點:
1) 提高應(yīng)用程序響應(yīng)。這對圖形界面的程序尤其有意義,當(dāng)一個操作耗時很長時,整個系統(tǒng)都會等待這個操作,此時程序不會響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時長的操作(time consuming)置于一個新的線程,可以避免這種尷尬的情況。
2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會保證當(dāng)線程數(shù)不大于CPU數(shù)目時,不同的線程運(yùn)行于不同的CPU上。
3) 改善程序結(jié)構(gòu)。一個既長又復(fù)雜的進(jìn)程可以考慮分為多個線程,成為幾個獨立或半獨立的運(yùn)行部分,這樣的程序會利于理解和修改。
下面我們先來嘗試編寫一個簡單的多線程程序。
簡單的多線程編程
Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時需要
使用庫libpthread.a。順便說一下,Linux下pthread的實現(xiàn)是通過系統(tǒng)調(diào)用clone()來實現(xiàn)的。clone()是Linux所特
有的系統(tǒng)調(diào)用,它的使用方式類似fork,關(guān)于clone()的詳細(xì)情況,有興趣的讀者可以去查看有關(guān)文檔說明。下面我們展示一個最簡單的多線程程序
pthread_create.c。
一個重要的線程創(chuàng)建函數(shù)原型: #include <pthread.h> int pthread_create(pthread_t *restrict tidp,const pthread_attr_t *restrict attr, void *(*start_rtn)(void),void *restrict arg);
返回值:若是成功建立線程返回0,否則返回錯誤的編號 形式參數(shù): pthread_t *restrict tidp 要創(chuàng)建的線程的線程id指針 const pthread_attr_t *restrict attr 創(chuàng)建線程時的線程屬性 void* (start_rtn)(void) 返回值是void類型的指針函數(shù) void *restrict arg start_rtn的行參 例程1: 功能:創(chuàng)建一個簡單的線程 程序名稱:pthread_create.c /******************************************************************************************** ** Name:pthread_create.c ** Used to study the multithread programming in Linux OS ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/
#include <stdio.h> #include <pthread.h>
void *myThread1(void) { int i; for (i=0; i<100; i++) { printf("This is the 1st pthread,created by zieckey.\n"); sleep(1);//Let this thread to sleep 1 second,and then continue to run } }
void *myThread2(void) { int i; for (i=0; i<100; i++) { printf("This is the 2st pthread,created by zieckey.\n"); sleep(1); } }
int main() { int i=0, ret=0; pthread_t id1,id2; ret = pthread_create(&id2, NULL, (void*)myThread1, NULL); if (ret) { printf("Create pthread error!\n"); return 1; } ret = pthread_create(&id2, NULL, (void*)myThread2, NULL); if (ret) { printf("Create pthread error!\n"); return 1; } pthread_join(id1, NULL); pthread_join(id2, NULL); return 0; }
我們編譯此程序: # gcc pthread_create.c -lpthread
因為pthread的庫不是linux系統(tǒng)的庫,所以在進(jìn)行編譯的時候要加上-lpthread,否則編譯不過,會出現(xiàn)下面錯誤 thread_test.c: 在函數(shù) ‘create’ 中: thread_test.c:7: 警告: 在有返回值的函數(shù)中,程序流程到達(dá)函數(shù)尾 /tmp/ccOBJmuD.o: In function `main':thread_test.c:(.text+0x4f):對‘pthread_create’未定義的引用 collect2: ld 返回 1
運(yùn)行,我們得到如下結(jié)果: # ./a.out This is the 1st pthread,created by zieckey. This is the 2st pthread,created by zieckey. This is the 1st pthread,created by zieckey. This is the 2st pthread,created by zieckey. This is the 2st pthread,created by zieckey. This is the 1st pthread,created by zieckey. ....
兩個線程交替執(zhí)行。 此例子介紹了創(chuàng)建線程的方法。 下面例子介紹向線程傳遞參數(shù)。
例程2: 功能:向新的線程傳遞整形值 程序名稱:pthread_int.c /******************************************************************************************** ** Name:pthread_int.c ** Used to study the multithread programming in Linux OS ** Pass a parameter to the thread. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/
#include <stdio.h> #include <pthread.h> #include <unistd.h>
void *create(void *arg) { int *num; num=(int *)arg; printf("create parameter is %d \n",*num); return (void *)0; } int main(int argc ,char *argv[]) { pthread_t tidp; int error; int test=4; int *attr=&test; error=pthread_create(&tidp,NULL,create,(void *)attr);
if(error) { printf("pthread_create is created is not created ... \n"); return -1; } sleep(1); printf("pthread_create is created ...\n"); return 0; }
編譯方法:
gcc -lpthread pthread_int.c -Wall
執(zhí)行結(jié)果:
create parameter is 4 pthread_create is created is created ...
例程總結(jié): 可以看出來,我們在main函數(shù)中傳遞的整行指針,傳遞到我們新建的線程函數(shù)中。 在上面的例子可以看出來我們向新的線程傳入了另一個線程的int數(shù)據(jù),線程之間還可以傳遞字符串或是更復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。 例程3: 程序功能:向新建的線程傳遞字符串 程序名稱:pthread_string.c /******************************************************************************************** ** Name:pthread_string.c ** Used to study the multithread programming in Linux OS ** Pass a ‘char*‘ parameter to the thread. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/ #include <pthread.h> #include <stdio.h> #include <unistd.h>
void *create(void *arg) { char *name; name=(char *)arg; printf("The parameter passed from main function is %s \n",name); return (void *)0; }
int main(int argc, char *argv[]) { char *a="zieckey"; int error; pthread_t tidp;
error=pthread_create(&tidp, NULL, create, (void *)a);
if(error!=0) { printf("pthread is not created.\n"); return -1; } sleep(1); printf("pthread is created... \n"); return 0; }
編譯方法:
gcc -Wall pthread_string.c -lpthread
執(zhí)行結(jié)果: The parameter passed from main function is zieckey pthread is created...
例程總結(jié): 可以看出來main函數(shù)中的字符串傳入了新建的線程中。
例程4: 程序功能:向新建的線程傳遞字符串 程序名稱:pthread_struct.c /******************************************************************************************** ** Name:pthread_struct.c ** Used to study the multithread programming in Linux OS ** Pass a ‘char*‘ parameter to the thread. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/ #include <stdio.h> #include <pthread.h> #include <unistd.h> #include <stdlib.h>
struct menber { int a; char *s; };
void *create(void *arg) { struct menber *temp; temp=(struct menber *)arg; printf("menber->a = %d \n",temp->a); printf("menber->s = %s \n",temp->s); return (void *)0; }
int main(int argc,char *argv[]) { pthread_t tidp; int error; struct menber *b; b=(struct menber *)malloc( sizeof(struct menber) ); b->a = 4; b->s = "zieckey";
error = pthread_create(&tidp, NULL, create, (void *)b);
if( error ) { printf("phread is not created...\n"); return -1; } sleep(1); printf("pthread is created...\n"); return 0; }
編譯方法:
gcc -Wall pthread_struct.c -lpthread
執(zhí)行結(jié)果: menber->a = 4 menber->s = zieckey pthread is created...
例程總結(jié): 可以看出來main函數(shù)中的一個結(jié)構(gòu)體傳入了新建的線程中。 線程包含了標(biāo)識進(jìn)程內(nèi)執(zhí)行環(huán)境必須的信息。他集成了進(jìn)程中的所有信息都是對線程進(jìn)行共享的,包括文本程序、程序的全局內(nèi)存和堆內(nèi)存、棧以及文件描述符。
例程5: 程序目的:驗證新建立的線程可以共享進(jìn)程中的數(shù)據(jù) 程序名稱:pthread_share.c
/******************************************************************************************** ** Name:pthread_share_data.c ** Used to study the multithread programming in Linux OS ** Pass a ‘char*‘ parameter to the thread. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/ #include <stdio.h> #include <pthread.h> #include <unistd.h>
static int a=4; void *create(void *arg) { printf("new pthread ... \n"); printf("a=%d \n",a); return (void *)0; }
int main(int argc,char *argv[]) { pthread_t tidp; int error; a=5;
error=pthread_create(&tidp, NULL, create, NULL);
if(error!=0) { printf("new thread is not create ... \n"); return -1; } sleep(1); printf("new thread is created ... \n"); return 0; } 編譯方法:
gcc -Wall pthread_share_data.c -lpthread
執(zhí)行結(jié)果: new pthread ... a=5 new thread is created ...
例程總結(jié): 可以看出來,我們在主線程更改了我們的全局變量a的值的時候,我們新建立的線程則打印出來了改變的值,可以看出可以訪問線程所在進(jìn)程中的數(shù)據(jù)信息。 2、線程的終止
如果進(jìn)程中任何一個線程中調(diào)用exit,_Exit,或者是_exit,那么整個進(jìn)程就會終止, 與此類似,如果信號的默認(rèn)的動作是終止進(jìn)程,那么,把該信號發(fā)送到線程會終止進(jìn)程。 線程的正常退出的方式: (1) 線程只是從啟動例程中返回,返回值是線程中的退出碼 (2) 線程可以被另一個進(jìn)程進(jìn)行終止 (3) 線程自己調(diào)用pthread_exit函數(shù) 兩個重要的函數(shù)原型:
#include <pthread.h> void pthread_exit(void *rval_ptr); /*rval_ptr 線程退出返回的指針*/
int pthread_join(pthread_t thread,void **rval_ptr); /*成功結(jié)束進(jìn)程為0,否則為錯誤編碼*/
例程6 程序目的:線程正常退出,接受線程退出的返回碼 程序名稱:pthread_exit.c
/******************************************************************************************** ** Name:pthread_exit.c ** Used to study the multithread programming in Linux OS ** A example showing a thread to exit and with a return code. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/
#include <stdio.h> #include <pthread.h> #include <unistd.h>
void *create(void *arg) { printf("new thread is created ... \n"); return (void *)8; }
int main(int argc,char *argv[]) { pthread_t tid; int error; void *temp;
error = pthread_create(&tid, NULL, create, NULL);
if( error ) { printf("thread is not created ... \n"); return -1; } error = pthread_join(tid, &temp);
if( error ) { printf("thread is not exit ... \n"); return -2; } printf("thread is exit code %d \n", (int )temp); return 0; }
編譯方法:
gcc -Wall pthread_exit.c -lpthread
執(zhí)行結(jié)果: new thread is created ... thread is exit code 8
例程總結(jié): 可以看出來,線程退出可以返回線程的int數(shù)值。線程退出不僅僅可以返回線程的int數(shù)值,還可以返回一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
例程7 程序目的:線程結(jié)束返回一個復(fù)雜的數(shù)據(jù)結(jié)構(gòu) 程序名稱:pthread_return_struct.c #include <stdio.h> #include <pthread.h> #include <unistd.h>
struct menber { int a; char *b; }temp={8,"zieckey"}; void *create(void *arg) { printf("new thread ... \n"); return (void *)&temp; }
int main(int argc,char *argv[]) { int error; pthread_t tid; struct menber *c;
error = pthread_create(&tid, NULL, create, NULL); if( error ) { printf("new thread is not created ... \n"); return -1; } printf("main ... \n");
error = pthread_join(tid,(void *)&c);
if( error ) { printf("new thread is not exit ... \n"); return -2; } printf("c->a = %d \n",c->a); printf("c->b = %s \n",c->b); sleep(1); return 0; }
編譯方法:
gcc -Wall pthread_return_struct.c -lpthread
執(zhí)行結(jié)果:
main ... new thread ... c->a = 8 c->b = zieckey
例程總結(jié): 一定要記得返回的數(shù)據(jù)結(jié)構(gòu)要是在這個數(shù)據(jù)要返回的結(jié)構(gòu)沒有釋放的時候應(yīng)用, 如果數(shù)據(jù)結(jié)構(gòu)已經(jīng)發(fā)生變化,那返回的就不會是我們所需要的,而是臟數(shù)據(jù) 3、線程標(biāo)識
函數(shù)原型: #include <pthread.h> pthread_t pthread_self(void);
pid_t getpid(void); getpid()用來取得目前進(jìn)程的進(jìn)程識別碼,函數(shù)說明
例程8 程序目的:實現(xiàn)在新建立的線程中打印該線程的id和進(jìn)程id 程序名稱:pthread_id.c /******************************************************************************************** ** Name:pthread_id.c ** Used to study the multithread programming in Linux OS. ** Showing how to get the thread's tid and the process's pid. ** Author:zeickey ** Date:2006/9/16 ** Copyright (c) 2006,All Rights Reserved! *********************************************************************************************/ #include <stdio.h> #include <pthread.h> #include <unistd.h> /*getpid()*/
void *create(void *arg) { printf("New thread .... \n"); printf("This thread's id is %u \n", (unsigned int)pthread_self()); printf("The process pid is %d \n",getpid()); return (void *)0; }
int main(int argc,char *argv[]) { pthread_t tid; int error;
printf("Main thread is starting ... \n");
error = pthread_create(&tid, NULL, create, NULL);
if(error) { printf("thread is not created ... \n"); return -1; } printf("The main process's pid is %d \n",getpid()); sleep(1); return 0; }
編譯方法:
gcc -Wall -lpthread pthread_id.c
執(zhí)行結(jié)果:
Main thread is starting ... The main process's pid is 3307 New thread .... This thread's id is 3086347152 The process pid is 3307
http://www.yuanma.org/data/2006/0723/article_1213.htm 其實字節(jié)對齊的細(xì)節(jié)和具體編譯器實現(xiàn)相關(guān),但一般而言,滿足三個準(zhǔn)則: 1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2) 結(jié)構(gòu)體每個成員相對于結(jié)構(gòu)體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會在成員之間加上填充字節(jié);例如上面第二個結(jié)構(gòu)體變量的地址空間。
3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會在最末一個成員之后加上填充字節(jié)。例如上面第一個結(jié)構(gòu)體變量。
一.什么是字節(jié)對齊,為什么要對齊?
現(xiàn)代計算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對任何類型的變量的訪問可以從任何地址開始,但實際情況是在訪問特定類型變量的時候經(jīng)常在特
定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個接一個的排放,這就是對齊。
對齊的作用和原因:各個硬件平臺對存儲空間的處理上有很大的不同。一些平臺對某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。比如有些架構(gòu)的CPU在訪問
一個沒有進(jìn)行對齊的變量的時候會發(fā)生錯誤,那么在這種架構(gòu)下編程必須保證字節(jié)對齊.其他平臺可能沒有這種情況,但是最常見的是如果不按照適合其平臺要求對
數(shù)據(jù)存放進(jìn)行對齊,會在存取效率上帶來損失。比如有些平臺每次讀都是從偶地址開始,如果一個int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那
么一個讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個讀周期,并對兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)
據(jù)。顯然在讀取效率上下降很多。
二.字節(jié)對齊對程序的影響:
先讓我們看幾個例子吧(32bit,x86環(huán)境,gcc編譯器): 設(shè)結(jié)構(gòu)體如下定義: struct A { int a; char b; short c; }; struct B { char b; int a; short c; }; 現(xiàn)在已知32位機(jī)器上各種數(shù)據(jù)類型的長度如下: char:1(有符號無符號同) short:2(有符號無符號同) int:4(有符號無符號同) long:4(有符號無符號同) float:4 double:8 那么上面兩個結(jié)構(gòu)大小如何呢? 結(jié)果是: sizeof(strcut A)值為8 sizeof(struct B)的值卻是12
結(jié)構(gòu)體A中包含了4字節(jié)長度的int一個,1字節(jié)長度的char一個和2字節(jié)長度的short型數(shù)據(jù)一個,B也一樣;按理說A,B大小應(yīng)該都是7字節(jié)。 之所以出現(xiàn)上面的結(jié)果是因為編譯器要對數(shù)據(jù)成員在空間上進(jìn)行對齊。上面是按照編譯器的默認(rèn)設(shè)置進(jìn)行對齊的結(jié)果,那么我們是不是可以改變編譯器的這種默認(rèn)對齊設(shè)置呢,當(dāng)然可以.例如: #pragma pack (2) /*指定按2字節(jié)對齊*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定對齊,恢復(fù)缺省對齊*/ sizeof(struct C)值是8。 修改對齊值為1: #pragma pack (1) /*指定按1字節(jié)對齊*/ struct D { char b; int a; short c; }; #pragma pack () /*取消指定對齊,恢復(fù)缺省對齊*/ sizeof(struct D)值為7。 后面我們再講解#pragma pack()的作用.
三.編譯器是按照什么樣的原則進(jìn)行對齊的?
先讓我們看四個重要的基本概念: 1.數(shù)據(jù)類型自身的對齊值: 對于char型數(shù)據(jù),其自身對齊值為1,對于short型為2,對于int,float,double類型,其自身對齊值為4,單位字節(jié)。 2.結(jié)構(gòu)體或者類的自身對齊值:其成員中自身對齊值最大的那個值。 3.指定對齊值:#pragma pack (value)時的指定對齊值value。 4.數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對齊值:自身對齊值和指定對齊值中小的那個值。 有
了這些值,我們就可以很方便的來討論具體數(shù)據(jù)結(jié)構(gòu)的成員和其自身的對齊方式。有效對齊值N是最終用來決定數(shù)據(jù)存放地址方式的值,最重要。有效對齊N,就是
表示“對齊在N上”,也就是說該數(shù)據(jù)的"存放起始地址%N=0".而數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)變量都是按定義的先后順序來排放的。第一個數(shù)據(jù)變量的起始地址就是數(shù)
據(jù)結(jié)構(gòu)的起始地址。結(jié)構(gòu)體的成員變量要對齊排放,結(jié)構(gòu)體本身也要根據(jù)自身的有效對齊值圓整(就是結(jié)構(gòu)體成員變量占用總長度需要是對結(jié)構(gòu)體有效對齊值的整數(shù)
倍,結(jié)合下面例子理解)。這樣就不能理解上面的幾個例子的值了。 例子分析: 分析例子B; struct B { char b; int a; short c; }; 假
設(shè)B從地址空間0x0000開始排放。該例子中沒有定義指定對齊值,在筆者環(huán)境下,該值默認(rèn)為4。第一個成員變量b的自身對齊值是1,比指定或者默認(rèn)指定
對齊值4小,所以其有效對齊值為1,所以其存放地址0x0000符合0x0000%1=0.第二個成員變量a,其自身對齊值為4,所以有效對齊值也為4,
所以只能存放在起始地址為0x0004到0x0007這四個連續(xù)的字節(jié)空間中,復(fù)核0x0004%4=0,且緊靠第一個變量。第三個變量c,自身對齊值為
2,所以有效對齊值也是2,可以存放在0x0008到0x0009這兩個字節(jié)空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的
都是B內(nèi)容。再看數(shù)據(jù)結(jié)構(gòu)B的自身對齊值為其變量中最大對齊值(這里是b)所以就是4,所以結(jié)構(gòu)體的有效對齊值也是4。根據(jù)結(jié)構(gòu)體圓整的要求,
0x0009到0x0000=10字節(jié),(10+2)%4=0。所以0x0000A到0x000B也為結(jié)構(gòu)體B所占用。故B從0x0000到0x000B
共有12個字節(jié),sizeof(struct B)=12;其實如果就這一個就來說它已將滿足字節(jié)對齊了,
因為它的起始地址是0,因此肯定是對齊的,之所以在后面補(bǔ)充2個字節(jié),是因為編譯器為了實現(xiàn)結(jié)構(gòu)數(shù)組的存取效率,試想如果我們定義了一個結(jié)構(gòu)B的數(shù)組,那
么第一個結(jié)構(gòu)起始地址是0沒有問題,但是第二個結(jié)構(gòu)呢?按照數(shù)組的定義,數(shù)組中所有元素都是緊挨著的,如果我們不把結(jié)構(gòu)的大小補(bǔ)充為4的整數(shù)倍,那么下一
個結(jié)構(gòu)的起始地址將是0x0000A,這顯然不能滿足結(jié)構(gòu)的地址對齊了,因此我們要把結(jié)構(gòu)補(bǔ)充成有效對齊大小的整數(shù)倍.其實諸如:對于char型數(shù)據(jù),其
自身對齊值為1,對于short型為2,對于int,float,double類型,其自身對齊值為4,這些已有類型的自身對齊值也是基于數(shù)組考慮的,只
是因為這些類型的長度已知了,所以他們的自身對齊值也就已知了. 同理,分析上面例子C: #pragma pack (2) /*指定按2字節(jié)對齊*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定對齊,恢復(fù)缺省對齊*/ 第
一個變量b的自身對齊值為1,指定對齊值為2,所以,其有效對齊值為1,假設(shè)C從0x0000開始,那么b存放在0x0000,符合0x0000%1=
0;第二個變量,自身對齊值為4,指定對齊值為2,所以有效對齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個連續(xù)
字節(jié)中,符合0x0002%2=0。第三個變量c的自身對齊值為2,所以有效對齊值為2,順序存放 在0x0006、0x0007中,符合
0x0006%2=0。所以從0x0000到0x00007共八字節(jié)存放的是C的變量。又C的自身對齊值為4,所以C的有效對齊值為2。又8%2=0,C
只占用0x0000到0x0007的八個字節(jié)。所以sizeof(struct C)=8.
四.如何修改編譯器的默認(rèn)對齊值?
1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項卡Category的Code Generation選項的Struct Member Alignment中修改,默認(rèn)是8字節(jié)。 2.在編碼時,可以這樣動態(tài)修改:#pragma pack .注意:是pragma而不是progma.
五.針對字節(jié)對齊,我們在編程中如何考慮?
如果在編程的時候要考慮節(jié)約空間的話,那么我們只需要假定結(jié)構(gòu)的首地址是0,然后各個變量按照上面的原則進(jìn)行排列即可,基本的原則就是把結(jié)構(gòu)中的變量按照
類型大小從小到大聲明,盡量減少中間的填補(bǔ)空間.還有一種就是為了以空間換取時間的效率,我們顯示的進(jìn)行填補(bǔ)空間進(jìn)行對齊,比如:有一種使用空間換時間做
法是顯式的插入reserved成員: struct A{ char a; char reserved[3];//使用空間換時間 int b; }
reserved成員對我們的程序沒有什么意義,它只是起到填補(bǔ)空間以達(dá)到字節(jié)對齊的目的,當(dāng)然即使不加這個成員通常編譯器也會給我們自動填補(bǔ)對齊,我們自己加上它只是起到顯式的提醒作用.
六.字節(jié)對齊可能帶來的隱患:
代碼中關(guān)于對齊的隱患,很多是隱式的。比如在強(qiáng)制類型轉(zhuǎn)換的時候。例如: unsigned int i = 0x12345678; unsigned char *p=NULL; unsigned short *p1=NULL;
p=&i; *p=0x00; p1=(unsigned short *)(p+1); *p1=0x0000; 最后兩句代碼,從奇數(shù)邊界去訪問unsignedshort型變量,顯然不符合對齊的規(guī)定。 在x86上,類似的操作只會影響效率,但是在MIPS或者sparc上,可能就是一個error,因為它們要求必須字節(jié)對齊.
七.如何查找與字節(jié)對齊方面的問題:
如果出現(xiàn)對齊或者賦值問題首先查看 1. 編譯器的big little端設(shè)置 2. 看這種體系本身是否支持非對齊訪問 3. 如果支持看設(shè)置了對齊與否,如果沒有則看訪問時需要加某些特殊的修飾來標(biāo)志其特殊訪問操作。
八.相關(guān)文章:轉(zhuǎn)自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
ARM下的對齊處理 from DUI0067D_ADS1_2_CompLib
3.13 type qulifiers
有部分摘自ARM編譯器文檔對齊部分
對齊的使用: 1.__align(num) 這個用于修改最高級別對象的字節(jié)邊界。在匯編中使用LDRD或者STRD時 就要用到此命令__align(8)進(jìn)行修飾限制。來保證數(shù)據(jù)對象是相應(yīng)對齊。 這個修飾對象的命令最大是8個字節(jié)限制,可以讓2字節(jié)的對象進(jìn)行4字節(jié) 對齊,但是不能讓4字節(jié)的對象2字節(jié)對齊。 __align是存儲類修改,他只修飾最高級類型對象不能用于結(jié)構(gòu)或者函數(shù)對象。 2.__packed __packed是進(jìn)行一字節(jié)對齊 1.不能對packed的對象進(jìn)行對齊 2.所有對象的讀寫訪問都進(jìn)行非對齊訪問 3.float及包含float的結(jié)構(gòu)聯(lián)合及未用__packed的對象將不能字節(jié)對齊 4.__packed對局部整形變量無影響 5.強(qiáng)制由unpacked對象向packed對象轉(zhuǎn)化是未定義,整形指針可以合法定 義為packed。 __packed int* p; //__packed int 則沒有意義 6.對齊或非對齊讀寫訪問帶來問題 __packed struct STRUCT_TEST { char a; int b; char c; } ; //定義如下結(jié)構(gòu)此時b的起始地址一定是不對齊的 //在棧中訪問b可能有問題,因為棧上數(shù)據(jù)肯定是對齊訪問[from CL] //將下面變量定義成全局靜態(tài)不在棧上 static char* p; static struct STRUCT_TEST a; void Main() { __packed int* q; //此時定義成__packed來修飾當(dāng)前q指向為非對齊的數(shù)據(jù)地址下面的訪問則可以
p = (char*)&a; q = (int*)(p+1); *q = 0x87654321; /* 得到賦值的匯編指令很清楚 ldr r5,0x20001590 ; = #0x12345678 [0xe1a00005] mov r0,r5 [0xeb0000b0] bl __rt_uwrite4 //在此處調(diào)用一個寫4byte的操作函數(shù) [0xe5c10000] strb r0,[r1,#0] //函數(shù)進(jìn)行4次strb操作然后返回保證了數(shù)據(jù)正確的訪問 [0xe1a02420] mov r2,r0,lsr #8 [0xe5c12001] strb r2,[r1,#1] [0xe1a02820] mov r2,r0,lsr #16 [0xe5c12002] strb r2,[r1,#2] [0xe1a02c20] mov r2,r0,lsr #24 [0xe5c12003] strb r2,[r1,#3] [0xe1a0f00e] mov pc,r14 */
/* 如果q沒有加__packed修飾則匯編出來指令是這樣直接會導(dǎo)致奇地址處訪問失敗 [0xe59f2018] ldr r2,0x20001594 ; = #0x87654321 [0xe5812000] str r2,[r1,#0] */
//這樣可以很清楚的看到非對齊訪問是如何產(chǎn)生錯誤的 //以及如何消除非對齊訪問帶來問題 //也可以看到非對齊訪問和對齊訪問的指令差異導(dǎo)致效率問題
2008年10月9日
http://bbs.chinaunix.net/viewthread.php?tid=1130381
所謂進(jìn)程間通訊,顧名思義,就是在2個(多數(shù)情況下)或多個進(jìn)程間傳遞信息。方法大致如下幾種:
1, 文件(file),匿名管道(anonymous pipe),命名管道(named pipe),信號(signal).
2、 System V IPC 包括消息隊列(message queue),共享內(nèi)存(shared memory),信號量(semaphore)。這種形式的ipc首先在UNIX分支system V中使用,現(xiàn)在多數(shù)unix系統(tǒng)都支持。
文件形式的IPC:
進(jìn)程(process) A寫信息到文件1,進(jìn)程B讀文件1。文件的內(nèi)容,由進(jìn)程自己決定。
匿名管道:
command1 args1 | command2 args2. 最常見的例子:ls –l |more
由于管道操作由shell代替完成,沒有產(chǎn)生有名字的實體,所以稱為匿名管道。
Shell做的事情是調(diào)用pipe(),產(chǎn)生一個管道,然后把command1的輸出連接到管道的出入端,把command2的輸入連接到管道的輸出端。
命名管道
首先,建立一個特殊文件,mkfifo pipe1或者mknod fifo1 p
然后,就當(dāng)作正常文件讀寫pipe1。例如: ls > fifo1 (寫入)。
while read a
do
echo $a
done (讀出)
由于產(chǎn)生有名字的實體,所以被稱為命名管道。
信號:
簡單的用法: kill –USER2
pid,也就是通過kill()系統(tǒng)調(diào)用或者kill命令,發(fā)送信號到別的進(jìn)程。各個進(jìn)程對于信號的處理過程是自己定義的(除了9,也就是KILL是強(qiáng)制
的)。比如自己可以忽略HUP,TERM,INT(按control-C), 等。
消息隊列(message queue)
消息隊列,是一個隊列的結(jié)構(gòu),隊列里面的內(nèi)容由用戶進(jìn)程自己定義。實際上,隊列里面記錄的是指向用戶自定義結(jié)構(gòu)的指針和結(jié)構(gòu)的大小。要使用message
queue,首先要通過系統(tǒng)調(diào)用(msgget)產(chǎn)生一個隊列,然后,進(jìn)程可以用msgsnd發(fā)送消息到這個隊列,消息就是如上所說的結(jié)構(gòu)。別的進(jìn)程用
msgrcv讀取。消息隊列一旦產(chǎn)生,除非明確的刪除(某個有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,產(chǎn)生的隊列會一直保留在系統(tǒng)中。而
且,只要有權(quán)限,就可以對隊列進(jìn)行操作。消息隊列和管道很相似,實際上,管道就是用戶消息為1個字節(jié)的隊列。
ipcs –aq命令可以查看message queue的狀況:
Message Queues:
T ID KEY MODE OWNER GROUP CREATOR CGROUP
CBYTES QNUM QBYTES LSPID LRPID STIME RTIME CTIME
q 256 0x417d0896 --rw------- root daemon root daemon
0 0 16384 97737 210466 14:31:14 14:31:14 9:52:53
其中:
T: 類型, q 表明這是個消息隊列
ID: 用戶自己定義的,在調(diào)用msgget時傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊列建立者的名字和組
CREATOR, CGROUP:隊列建立者和組的ID
CBYTES : 目前queue在隊列里的字節(jié)數(shù)
QNUM, 目前queue在隊列里的消息數(shù)
QBYTES: 隊列中消息最大允許字節(jié)數(shù)
LSPID: 最后發(fā)送者PID
LRPID: 最后接受者PID
STIME: 最后發(fā)送時間
RTIME: 最后接受時間。.
CTIME: 建立或者最后修改的時間
共享內(nèi)存(shared memory)
共享內(nèi)存是一段可以被多個進(jìn)程共享的內(nèi)存段。首先,用shmget系統(tǒng)調(diào)用產(chǎn)生指定大小的共享內(nèi)存段,然后需要訪問此共享內(nèi)存的進(jìn)程調(diào)用shmat系統(tǒng)調(diào)
用,把這個內(nèi)存段附加到自己的地址空間,然后就可以像訪問自己私有的內(nèi)存一樣訪問這個內(nèi)存段了。等到訪問完畢,用shmdt脫離。同message
queue一樣,共享內(nèi)存一旦產(chǎn)生,除非明確的刪除(某個有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,產(chǎn)生的共享內(nèi)存會一直保留在系統(tǒng)中。而
且,只要有權(quán)限,就可以對共享內(nèi)存進(jìn)行操作。共享內(nèi)存的內(nèi)容由進(jìn)程自己定義。為了防止多個進(jìn)程在同一時間寫同樣一段共享內(nèi)存,一般程序會使用信號量來控制
對某一段地址的讀寫。
ipcs –am命令可以查看share memory的狀況:
Shared Memory:
T ID KEY MODE OWNER GROUP CREATOR CGROUP
NATTCH SEGSZ CPID LPID ATIME DTIME CTIME
m 258 0 --rw-r----- oracle dba oracle dba
12 8388608 106303 106329 16:28:54 16:48:36 16:28:49
T: 類型 m 表明這是個共享內(nèi)存
ID: 用戶自己定義的,在調(diào)用shmget時傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊列建立者的名字和組
CREATOR, CGROUP:隊列建立者和組的ID
NATTCH: 有幾個進(jìn)程掛接(attach)在這段共享內(nèi)存上
SEGSZ: 共享內(nèi)存段大小(字節(jié))
CPID: 產(chǎn)生者PID
LPID: 最后掛接(attach)或者脫離(detach)者PID
ATIME: 最后掛接(attach)時間
DTIME: 最后脫離(detach)時間。.
CTIME: 建立或者最后修改的時間
信號量(semaphore)
在操作系統(tǒng)中,有些資源數(shù)量是有限的,在同一時間,只能由有限(一個或幾個)的進(jìn)程使用和訪問。例如磁帶機(jī),同一時間,只能由一個進(jìn)程使用。這樣的資源被
稱為關(guān)鍵(critical)資源。信號量就是用來記錄關(guān)鍵資源的使用情況的。首先,利用系統(tǒng)調(diào)用semget產(chǎn)生一個信號量。當(dāng)需要使用關(guān)鍵資源時,調(diào)
用semop,傳遞的參數(shù)為需要使用的資源的數(shù)量,例如2個,參數(shù)就為+2。如果這個資源有2個或者更多可用,進(jìn)程就獲得了使用權(quán),否則就必須等待,直到
有足夠的資源可用。當(dāng)進(jìn)程使用資源結(jié)束的時候,也用semop釋放關(guān)鍵資源。參數(shù)為需要釋放的數(shù)量,例如2,參數(shù)為-2。同message
queue一樣,共信號量一旦產(chǎn)生,除非明確的刪除(某個有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,信號量會一直保留在系統(tǒng)中。而且,只要
有權(quán)限,就可以對其進(jìn)行操作。
ipcs –as命令可以查看Semaphore的狀況:
Semaphores:
T ID KEY MODE OWNER GROUP CREATOR CGROUP NSEMS OTIME CTIME
s 0 0x696e6974 --ra-r--r-- root system root system 8 9:52:53 9:59:30
T: 類型 s 表明這是個信號量
ID: 用戶自己定義的,在調(diào)用semget時傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊列建立者的名字和組
CREATOR, CGROUP:隊列建立者和組的ID
NSEMS: 本信號量上信號的數(shù)量。
OTIME: 最后一次操作(semop)的時間
CTIM: 建立或者最后修改的時間
http://www.idcnews.net/html/edu/linux/20080407/264092.html
在APUE 14.7節(jié)對消息隊列的講解中,最后一段說“我們得出的結(jié)論是:在新的應(yīng)用程式中不應(yīng)當(dāng)再使用他們。” 雖然在新的應(yīng)用程式中不應(yīng)該再使用消息隊列,我也沒有怎么使用過System V IPC總覺得在UNIX/Linux編程中少了什么,也許學(xué)習(xí)一下System V IPC對我的自信心會有相當(dāng)大的幫助,從此我也敢講我知道如何使用IPC了。
先把各個函數(shù)原形列出。 #include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h> int msgget(key_t key, int msgflag); int msgsnd(int msgid, struct msgbuf *msgp, size_t msgsz, int msgflag); ssize_t msgrcv(int msgid, struct msgbuf *msgp, size_t msgsz, long msgtype, int msgflag); int msgctl(int msgid, int cmd, struct msqid_ds *buf);
msgget()用來創(chuàng)建Message Queue(服務(wù)端)或和一個已建立的Message
Queue連接(客戶端)。key,指定用來生成message
id的關(guān)鍵字,msgflag和open()的flags很相似,可用IPC_CREAT, IPC_EXECL, S_IRUSR等。 在服務(wù)端,可用IPC_PRIVATE(或0)來指定key值,來生成一個新的Message Queue,或使用指定的key值(32位的無符號數(shù)),或使用ftok()來生成一個key。 #include <sys/types.h> #include <sys/ipc.h> key_t ftok(const char *pathname, int proj_id);
在客戶端,能夠直接使用服務(wù)端生成的message
id(通過某些途徑傳送,如文檔,父子進(jìn)程),也能夠用msgget通過和服務(wù)端使用相同的key值來生成相同的message
id,但不能使用IPC_PRIVATE(或0),msgflag也不能使用IPC_CREAT。
Return Value: Sucess return value is the message id(non-negative integer), otherwise -1 return. msgsnd()用來發(fā)送消息。 struct msgbuf { long mtype; char mtext[1]; }; msgsz的計算方法: msgsz = sizeof(msgbuf) - sizeof(long); msgflag有一個標(biāo)志:IPC_NOWAIT。當(dāng)消息隊列已滿(可能是消息總數(shù)達(dá)到了限制值,也可能是隊列中字節(jié)總數(shù)達(dá)到了限制值),立即出錯返回,假如沒有指定,則阻塞。
msgrcv()用來接收消息。msgtype用來指定讀取的消息類型。msgtype == 0, 返回第一個消息; msgtype >
0, 返回消息類型為msgtype的消息;msgtype < 0, 返回隊列中類型值小于msgtype的絕對值的消息集中最小的消息。 msgflag有兩個值:MSG_NOERROR, IPC_NOWAIT。當(dāng)MSG_NOERROR被指定的時候,若消息太長就被截斷,否則返回錯誤;IPC_NOWAIT用于需要讀取的消息不存在時則阻塞。
msgctl用于控制消息隊列。cmd有三種值:IPC_STAT,IPC_SET,IPC_RMID。 IPC_STAT用于取出消息隊列的 msqid_ds結(jié)構(gòu)并保存到buf中。 IPC_SET用來把buf指向的msqid_ds,配置成消息隊列的msqid_ds。只有四個值能夠更改:msg_perm.uid, msg_perm.gid,msg_perm.mode, msg_qbytes。 IPC_RMID用來刪除消息隊列。 struct msqid_ds { struct ipc_perm msg_perm; ulong msg_qbytes; //max of bytes of queue ... }; struct ipc_perm { uid_t uid; //owner's effective user id gid_t gid; //owner's effective group id uid_t cuid; //creator's effective user id gid_t cgid; //creator's effective group id mode_t mode; //access modes ulong seq; //slot usage sequence number key_t key; };
2008年10月8日
http://www.bccn.net/Article/czxt/linux/200511/1037.html
文章摘要: 多線程程序設(shè)計的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點,多線程編程已經(jīng)得到了廣泛的應(yīng)用。本文我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識。
--------------------------------------------------------------------------------
正文: Linux下的多進(jìn)程編程初步
1 引言
對于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一:它執(zhí)行一次卻返回兩個值。fork函數(shù)是Unix系統(tǒng)最杰出的成就
之一,它是七十年代UNIX早期的開發(fā)者經(jīng)過長期在理論和實踐上的艱苦探索后取得的成果,一方面,它使操作系統(tǒng)在進(jìn)程管理上付出了最小的代價,另一方面,
又為程序員提供了一個簡潔明了的多進(jìn)程方法。與DOS和早期的Windows不同,Unix/Linux系統(tǒng)是真正實現(xiàn)多任務(wù)操作的系統(tǒng),可以說,不使用
多進(jìn)程編程,就不能算是真正的Linux環(huán)境下編程。 多線程程序設(shè)計的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點,多線程編程已經(jīng)得到了廣泛的應(yīng)用。 下面,我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識。
2 多進(jìn)程編程
什么是一個進(jìn)程?進(jìn)程這個概念是針對系統(tǒng)而不是針對用戶的,對用戶來說,他面對的概念是程序。當(dāng)用戶敲入命令執(zhí)行一個程序的時候,對系統(tǒng)而言,它將啟動一
個進(jìn)程。但和程序不同的是,在這個進(jìn)程中,系統(tǒng)可能需要再啟動一個或多個進(jìn)程來完成獨立的多個任務(wù)。多進(jìn)程編程的主要內(nèi)容包括進(jìn)程控制和進(jìn)程間通信,在了
解這些之前,我們先要簡單知道進(jìn)程的結(jié)構(gòu)。
2.1 Linux下進(jìn)程的結(jié)構(gòu) Linux下一個進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段"、"堆棧段"和"數(shù)據(jù)段"。其實學(xué)過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。這三個部分也是構(gòu)成一個完整的執(zhí)行序列的必要的部分。
"代碼段",顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個進(jìn)程運(yùn)行相同的一個程序,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程
序的返回地址、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空
間)。這其中有許多細(xì)節(jié)問題,這里限于篇幅就不多介紹了。系統(tǒng)如果同時運(yùn)行數(shù)個相同的程序,它們之間就不能使用同一個堆棧段和數(shù)據(jù)段。
2.2 Linux下的進(jìn)程控制
在傳統(tǒng)的Unix環(huán)境下,有兩個基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork(
)用來創(chuàng)建一個新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個完全拷貝;函數(shù)族exec(
)用來啟動另外的進(jìn)程以取代當(dāng)前運(yùn)行的進(jìn)程。Linux的進(jìn)程控制和傳統(tǒng)的Unix進(jìn)程控制基本一致,只在一些細(xì)節(jié)的地方有些區(qū)別,例如在Linux系統(tǒng)
中調(diào)用vfork和fork完全相同,而在有些版本的Unix系統(tǒng)中,vfork調(diào)用有不同的功能。由于這些差別幾乎不影響我們大多數(shù)的編程,在這里我們
不予考慮。 2.2.1 fork( ) fork在英文中是"分叉"的意思。為什么取這個名字呢?因為一個進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個進(jìn)程,于是進(jìn)程就"分叉"了,所以這個名字取得很形象。下面就看看如何具體使用fork,這段程序演示了使用fork的基本框架:
void main(){ int i; if ( fork() == 0 ) { /* 子進(jìn)程程序 */ for ( i = 1; i <1000; i ++ ) printf("This is child process\n"); } else { /* 父進(jìn)程程序*/ for ( i = 1; i <1000; i ++ ) printf("This is process process\n"); } } 程序運(yùn)行后,你就能看到屏幕上交替出現(xiàn)子進(jìn)程與父進(jìn)程各打印出的一千條信息了。如果程序還在運(yùn)行中,你用ps命令就能看到系統(tǒng)中有兩個它在運(yùn)行了。
那么調(diào)用這個fork函數(shù)時發(fā)生了什么呢?fork函數(shù)啟動一個新的進(jìn)程,前面我們說過,這個進(jìn)程幾乎是當(dāng)前進(jìn)程的一個拷貝:子進(jìn)程和父進(jìn)程使用相同的代
碼段;子進(jìn)程復(fù)制父進(jìn)程的堆棧段和數(shù)據(jù)段。這樣,父進(jìn)程的所有數(shù)據(jù)都可以留給子進(jìn)程,但是,子進(jìn)程一旦開始運(yùn)行,雖然它繼承了父進(jìn)程的一切數(shù)據(jù),但實際上
數(shù)據(jù)卻已經(jīng)分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數(shù)據(jù)了。它們再要交互信息時,只有通過進(jìn)程間通信來實現(xiàn),這將是我們下面的內(nèi)容。
既然它們?nèi)绱讼嘞螅到y(tǒng)如何來區(qū)分它們呢?這是由函數(shù)的返回值來決定的。對于父進(jìn)程,fork函數(shù)返回了子程序的進(jìn)程號,而對于子程序,fork函數(shù)則返
回零。在操作系統(tǒng)中,我們用ps函數(shù)就可以看到不同的進(jìn)程號,對父進(jìn)程而言,它的進(jìn)程號是由比它更低層的系統(tǒng)調(diào)用賦予的,而對于子進(jìn)程而言,它的進(jìn)程號即
是fork函數(shù)對父進(jìn)程的返回值。在程序設(shè)計中,父進(jìn)程和子進(jìn)程都要調(diào)用函數(shù)fork()下面的代碼,而我們就是利用fork()函數(shù)對父子進(jìn)程的不同返
回值用if...else...語句來實現(xiàn)讓父子進(jìn)程完成不同的功能,正如我們上面舉的例子一樣。我們看到,上面例子執(zhí)行時兩條信息是交互無規(guī)則的打印出
來的,這是父子進(jìn)程獨立執(zhí)行的結(jié)果,雖然我們的代碼似乎和串行的代碼沒有什么區(qū)別。
讀者也許會問,如果一個大程序在運(yùn)行中,它的數(shù)據(jù)段和堆棧都很大,一次fork就要復(fù)制一次,那么fork的系統(tǒng)開銷不是很大嗎?其實UNIX自有其解決
的辦法,大家知道,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,每一個頁都是實際物理內(nèi)存的一個映像,象INTEL的CPU,其一頁在通常情況下是
4086字節(jié)大小,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的,fork函數(shù)復(fù)制這兩個段,只是"邏輯"上的,并非"物理"上的,也就是說,實際執(zhí)
行fork時,物理空間上兩個進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個進(jìn)程寫了某個數(shù)據(jù)時,這時兩個進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別的"
頁"從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小。 下面演示一個足以"搞死"Linux的小程序,其源代碼非常簡單: void main() { for( ; ; ) fork(); }
這個程序什么也不做,就是死循環(huán)地fork,其結(jié)果是程序不斷產(chǎn)生進(jìn)程,而這些進(jìn)程又不斷產(chǎn)生新的進(jìn)程,很快,系統(tǒng)的進(jìn)程就滿了,系統(tǒng)就被這么多不斷產(chǎn)生
的進(jìn)程"撐死了"。當(dāng)然只要系統(tǒng)管理員預(yù)先給每個用戶設(shè)置可運(yùn)行的最大進(jìn)程數(shù),這個惡意的程序就完成不了企圖了。 2.2.2 exec( )函數(shù)族
下面我們來看看一個進(jìn)程如何來啟動另一個程序的執(zhí)行。在Linux中要使用exec函數(shù)族。系統(tǒng)調(diào)用execve()對當(dāng)前進(jìn)程進(jìn)行替換,替換者為一個指
定的程序,其參數(shù)包括文件名(filename)、參數(shù)列表(argv)以及環(huán)境變量(envp)。exec函數(shù)族當(dāng)然不止一個,但它們大致相同,在
Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp為例,其它函數(shù)究
竟與execlp有何區(qū)別,請通過manexec命令來了解它們的具體情況。
一個進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧
段,唯一留下的,就是進(jìn)程號,也就是說,對系統(tǒng)而言,還是同一個進(jìn)程,不過已經(jīng)是另一個程序了。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信
息。) 那么如果我的程序想啟動另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,怎么辦呢?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動運(yùn)行其它程序:
char command[256]; void main() { int rtn; /*子進(jìn)程的返回數(shù)值*/ while(1) { /* 從終端讀取要執(zhí)行的命令 */ printf( ">" ); fgets( command, 256, stdin ); command[strlen(command)-1] = 0; if ( fork() == 0 ) { /* 子進(jìn)程執(zhí)行此命令 */ execlp( command, command ); /* 如果exec函數(shù)返回,表明沒有正常執(zhí)行命令,打印錯誤信息*/ perror( command ); exit( errorno ); } else { /* 父進(jìn)程, 等待子進(jìn)程結(jié)束,并打印子進(jìn)程的返回值 */ wait ( &rtn ); printf( " child process return %d\n",. rtn ); } } }
此程序從終端讀入命令并執(zhí)行之,執(zhí)行完成后,父進(jìn)程繼續(xù)等待從終端讀入命令。熟悉DOS和WINDOWS系統(tǒng)調(diào)用的朋友一定知道DOS/WINDOWS也
有exec類函數(shù),其使用方法是類似的,但DOS/WINDOWS還有spawn類函數(shù),因為DOS是單任務(wù)的系統(tǒng),它只能將"父進(jìn)程"駐留在機(jī)器內(nèi)再執(zhí)
行"子進(jìn)程",這就是spawn類的函數(shù)。WIN32已經(jīng)是多任務(wù)的系統(tǒng)了,但還保留了spawn類函數(shù),WIN32中實現(xiàn)spawn函數(shù)的方法同前述
UNIX中的方法差不多,開設(shè)子進(jìn)程后父進(jìn)程等待子進(jìn)程結(jié)束后才繼續(xù)運(yùn)行。UNIX在其一開始就是多任務(wù)的系統(tǒng),所以從核心角度上講不需要spawn類函
數(shù)。
在這一節(jié)里,我們還要講講system()和popen()函數(shù)。system()函數(shù)先調(diào)用fork(),然后再調(diào)用exec()來執(zhí)行用戶的登錄
shell,通過它來查找可執(zhí)行文件的命令并分析參數(shù),最后它么使用wait()函數(shù)族之一來等待子進(jìn)程的結(jié)束。函數(shù)popen()和函數(shù)
system()相似,不同的是它調(diào)用pipe()函數(shù)創(chuàng)建一個管道,通過它來完成程序的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出。這兩個函數(shù)是為那些不太勤快的程序員設(shè)計
的,在效率和安全方面都有相當(dāng)?shù)娜毕荩诳赡艿那闆r下,應(yīng)該盡量避免。
2.3 Linux下的進(jìn)程間通信
詳細(xì)的講述進(jìn)程間通信在這里絕對是不可能的事情,而且筆者很難有信心說自己對這一部分內(nèi)容的認(rèn)識達(dá)到了什么樣的地步,所以在這一節(jié)的開頭首先向大家推薦著
名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX
Environment》,它的中文譯本《UNIX環(huán)境高級編程》已有機(jī)械工業(yè)出版社出版,原文精彩,譯文同樣地道,如果你的確對在Linux下編程有濃
厚的興趣,那么趕緊將這本書擺到你的書桌上或計算機(jī)旁邊來。說這么多實在是難抑心中的景仰之情,言歸正傳,在這一節(jié)里,我們將介紹進(jìn)程間通信最最初步和最
最簡單的一些知識和概念。
首先,進(jìn)程間通信至少可以通過傳送打開文件來實現(xiàn),不同的進(jìn)程通過一個或多個文件來傳遞信息,事實上,在很多應(yīng)用系統(tǒng)里,都使用了這種方法。但一般說來,
進(jìn)程間通信(IPC:InterProcess
Communication)不包括這種似乎比較低級的通信方法。Unix系統(tǒng)中實現(xiàn)進(jìn)程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系
統(tǒng)中進(jìn)行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統(tǒng),幾乎支持所有的Unix下常用的進(jìn)程間通信
方法:管道、消息隊列、共享內(nèi)存、信號量、套接口等等。下面我們將逐一介紹。
2.3.1 管道 管道是進(jìn)程間通信中最古老的方式,它包括無名管道和有名管道兩種,前者用于父進(jìn)程和子進(jìn)程間的通信,后者用于運(yùn)行于同一臺機(jī)器上的任意兩個進(jìn)程間的通信。 無名管道由pipe()函數(shù)創(chuàng)建: #include <unistd.h> int pipe(int filedis[2]); 參數(shù)filedis返回兩個文件描述符:filedes[0]為讀而打開,filedes[1]為寫而打開。filedes[1]的輸出是filedes[0]的輸入。下面的例子示范了如何在父進(jìn)程和子進(jìn)程間實現(xiàn)通信。
#define INPUT 0 #define OUTPUT 1
void main() { int file_descriptors[2]; /*定義子進(jìn)程號 */ pid_t pid; char buf[256]; int returned_count; /*創(chuàng)建無名管道*/ pipe(file_descriptors); /*創(chuàng)建子進(jìn)程*/ if((pid = fork()) == -1) { printf("Error in fork\n"); exit(1); } /*執(zhí)行子進(jìn)程*/ if(pid == 0) { printf("in the spawned (child) process...\n"); /*子進(jìn)程向父進(jìn)程寫數(shù)據(jù),關(guān)閉管道的讀端*/ close(file_descriptors[INPUT]); write(file_descriptors[OUTPUT], "test data", strlen("test data")); exit(0); } else { /*執(zhí)行父進(jìn)程*/ printf("in the spawning (parent) process...\n"); /*父進(jìn)程從管道讀取子進(jìn)程寫的數(shù)據(jù),關(guān)閉管道的寫端*/ close(file_descriptors[OUTPUT]); returned_count = read(file_descriptors[INPUT], buf, sizeof(buf)); printf("%d bytes of data received from spawned process: %s\n", returned_count, buf); } } 在Linux系統(tǒng)下,有名管道可由兩種方式創(chuàng)建:命令行方式mknod系統(tǒng)調(diào)用和函數(shù)mkfifo。下面的兩種途徑都在當(dāng)前目錄下生成了一個名為myfifo的有名管道: 方式一:mkfifo("myfifo","rw"); 方式二:mknod myfifo p 生成了有名管道后,就可以使用一般的文件I/O函數(shù)如open、close、read、write等來對它進(jìn)行操作。下面即是一個簡單的例子,假設(shè)我們已經(jīng)創(chuàng)建了一個名為myfifo的有名管道。 /* 進(jìn)程一:讀有名管道*/ #include <stdio.h> #include <unistd.h> void main() { FILE * in_file; int count = 1; char buf[80]; in_file = fopen("mypipe", "r"); if (in_file == NULL) { printf("Error in fdopen.\n"); exit(1); } while ((count = fread(buf, 1, 80, in_file)) > 0) printf("received from pipe: %s\n", buf); fclose(in_file); } /* 進(jìn)程二:寫有名管道*/ #include <stdio.h> #include <unistd.h> void main() { FILE * out_file; int count = 1; char buf[80]; out_file = fopen("mypipe", "w"); if (out_file == NULL) { printf("Error opening pipe."); exit(1); } sprintf(buf,"this is test data for the named pipe example\n"); fwrite(buf, 1, 80, out_file); fclose(out_file); }
2.3.2 消息隊列 消息隊列用于運(yùn)行于同一臺機(jī)器上的進(jìn)程間通信,它和管道很相似,事實上,它是一種正逐漸被淘汰的通信方式,我們可以用流管道或者套接口的方式來取代它,所以,我們對此方式也不再解釋,也建議讀者忽略這種方式。
2.3.3 共享內(nèi)存
共享內(nèi)存是運(yùn)行在同一臺機(jī)器上的進(jìn)程間通信最快的方式,因為數(shù)據(jù)不需要在不同的進(jìn)程間復(fù)制。通常由一個進(jìn)程創(chuàng)建一塊共享內(nèi)存區(qū),其余進(jìn)程對這塊內(nèi)存區(qū)進(jìn)行
讀寫。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件。前一種方式不給系統(tǒng)帶來額外的開銷,但在現(xiàn)實中并不常用,因為它控制存取的將是
實際的物理內(nèi)存,在Linux系統(tǒng)下,這只有通過限制Linux系統(tǒng)存取的內(nèi)存才可以做到,這當(dāng)然不太實際。常用的方式是通過shmXXX函數(shù)族來實現(xiàn)利
用共享內(nèi)存進(jìn)行存儲的。 首先要用的函數(shù)是shmget,它獲得一個共享存儲標(biāo)識符。 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, int size, int flag);
這個函數(shù)有點類似大家熟悉的malloc函數(shù),系統(tǒng)按照請求分配size大小的內(nèi)存用作共享內(nèi)存。Linux系統(tǒng)內(nèi)核中每個IPC結(jié)構(gòu)都有的一個非負(fù)整數(shù)
的標(biāo)識符,這樣對一個消息隊列發(fā)送消息時只要引用標(biāo)識符就可以了。這個標(biāo)識符是內(nèi)核由IPC結(jié)構(gòu)的關(guān)鍵字得到的,這個關(guān)鍵字,就是上面第一個函數(shù)的
key。數(shù)據(jù)類型key_t是在頭文件sys/types.h中定義的,它是一個長整形的數(shù)據(jù)。在我們后面的章節(jié)中,還會碰到這個關(guān)鍵字。 當(dāng)共享內(nèi)存創(chuàng)建后,其余進(jìn)程可以調(diào)用shmat()將其連接到自身的地址空間中。 void *shmat(int shmid, void *addr, int flag); shmid為shmget函數(shù)返回的共享存儲標(biāo)識符,addr和flag參數(shù)決定了以什么方式來確定連接的地址,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實際地址,進(jìn)程可以對此進(jìn)程進(jìn)行讀寫操作。
使用共享存儲來實現(xiàn)進(jìn)程間通信的注意點是對數(shù)據(jù)存取的同步,必須確保當(dāng)一個進(jìn)程去讀取數(shù)據(jù)時,它所想要的數(shù)據(jù)已經(jīng)寫好了。通常,信號量被要來實現(xiàn)對共享存
儲數(shù)據(jù)存取的同步,另外,可以通過使用shmctl函數(shù)設(shè)置共享存儲內(nèi)存的某些標(biāo)志位如SHM_LOCK、SHM_UNLOCK等來實現(xiàn)。
2.3.4 信號量 信號量又稱為信號燈,它是用來協(xié)調(diào)不同進(jìn)程間的數(shù)據(jù)對象的,而最主要的應(yīng)用是前一節(jié)的共享內(nèi)存方式的進(jìn)程間通信。本質(zhì)上,信號量是一個計數(shù)器,它用來記錄對某個資源(如共享內(nèi)存)的存取狀況。一般說來,為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作: (1) 測試控制該資源的信號量。 (2) 若此信號量的值為正,則允許進(jìn)行使用該資源。進(jìn)程將進(jìn)號量減1。 (3) 若此信號量為0,則該資源目前不可用,進(jìn)程進(jìn)入睡眠狀態(tài),直至信號量值大于0,進(jìn)程被喚醒,轉(zhuǎn)入步驟(1)。 (4) 當(dāng)進(jìn)程不再使用一個信號量控制的資源時,信號量值加1。如果此時有進(jìn)程正在睡眠等待此信號量,則喚醒此進(jìn)程。
維護(hù)信號量狀態(tài)的是Linux內(nèi)核操作系統(tǒng)而不是用戶進(jìn)程。我們可以從頭文件/usr/src/linux/include /linux /sem.h
中看到內(nèi)核用來維護(hù)信號量狀態(tài)的各個結(jié)構(gòu)的定義。信號量是一個數(shù)據(jù)集合,用戶可以單獨使用這一集合的每個元素。要調(diào)用的第一個函數(shù)是semget,用以獲
得一個信號量ID。 #include <sys/types.h> #include <sys/ipc.h> #include <sys/sem.h> int semget(key_t key, int nsems, int flag);
key是前面講過的IPC結(jié)構(gòu)的關(guān)鍵字,它將來決定是創(chuàng)建新的信號量集合,還是引用一個現(xiàn)有的信號量集合。nsems是該集合中的信號量數(shù)。如果是創(chuàng)建新
集合(一般在服務(wù)器中),則必須指定nsems;如果是引用一個現(xiàn)有的信號量集合(一般在客戶機(jī)中)則將nsems指定為0。 semctl函數(shù)用來對信號量進(jìn)行操作。 int semctl(int semid, int semnum, int cmd, union semun arg); 不同的操作是通過cmd參數(shù)來實現(xiàn)的,在頭文件sem.h中定義了7種不同的操作,實際編程時可以參照使用。 semop函數(shù)自動執(zhí)行信號量集合上的操作數(shù)組。 int semop(int semid, struct sembuf semoparray[], size_t nops); semoparray是一個指針,它指向一個信號量操作數(shù)組。nops規(guī)定該數(shù)組中操作的數(shù)量。 下面,我們看一個具體的例子,它創(chuàng)建一個特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個信號量,建立此信號量的索引,修改索引指向的信號量的值,最后我們清除信號量。在下面的代碼中,函數(shù)ftok生成我們上文所說的唯一的IPC關(guān)鍵字。
#include <stdio.h> #include <sys/types.h> #include <sys/sem.h> #include <sys/ipc.h> void main() { key_t unique_key; /* 定義一個IPC關(guān)鍵字*/ int id; struct sembuf lock_it; union semun options; int i;
unique_key = ftok(".", 'a'); /* 生成關(guān)鍵字,字符'a'是一個隨機(jī)種子*/ /* 創(chuàng)建一個新的信號量集合*/ id = semget(unique_key, 1, IPC_CREAT | IPC_EXCL | 0666); printf("semaphore id=%d\n", id); options.val = 1; /*設(shè)置變量值*/ semctl(id, 0, SETVAL, options); /*設(shè)置索引0的信號量*/
/*打印出信號量的值*/ i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i);
/*下面重新設(shè)置信號量*/ lock_it.sem_num = 0; /*設(shè)置哪個信號量*/ lock_it.sem_op = -1; /*定義操作*/ lock_it.sem_flg = IPC_NOWAIT; /*操作方式*/ if (semop(id, &lock_it, 1) == -1) { printf("can not lock semaphore.\n"); exit(1); }
i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i);
/*清除信號量*/ semctl(id, 0, IPC_RMID, 0); }
2.3.5 套接口
套接口(socket)編程是實現(xiàn)Linux系統(tǒng)和其他大多數(shù)操作系統(tǒng)中進(jìn)程間通信的主要方式之一。我們熟知的WWW服務(wù)、FTP服務(wù)、TELNET服務(wù)
等都是基于套接口編程來實現(xiàn)的。除了在異地的計算機(jī)進(jìn)程間以外,套接口同樣適用于本地同一臺計算機(jī)內(nèi)部的進(jìn)程間通信。關(guān)于套接口的經(jīng)典教材同樣是
Richard
Stevens編著的《Unix網(wǎng)絡(luò)編程:聯(lián)網(wǎng)的API和套接字》,清華大學(xué)出版社出版了該書的影印版。它同樣是Linux程序員的必備書籍之一。
關(guān)于這一部分的內(nèi)容,可以參照本文作者的另一篇文章《設(shè)計自己的網(wǎng)絡(luò)螞蟻》,那里由常用的幾個套接口函數(shù)的介紹和示例程序。這一部分或許是Linux進(jìn)程
間通信編程中最須關(guān)注和最吸引人的一部分,畢竟,Internet
正在我們身邊以不可思議的速度發(fā)展著,如果一個程序員在設(shè)計編寫他下一個程序的時候,根本沒有考慮到網(wǎng)絡(luò),考慮到Internet,那么,可以說,他的設(shè)
計很難成功。
3 Linux的進(jìn)程和Win32的進(jìn)程/線程比較 熟悉WIN32編程的人一定知道,WIN32的進(jìn)程管理方式與Linux上有著很大區(qū)別,在UNIX里,只有進(jìn)程的概念,但在WIN32里卻還有一個"線程"的概念,那么Linux和WIN32在這里究竟有著什么區(qū)別呢?
WIN32里的進(jìn)程/線程是繼承自O(shè)S/2的。在WIN32里,"進(jìn)程"是指一個程序,而"線程"是一個"進(jìn)程"里的一個執(zhí)行"線索"。從核心上
講,WIN32的多進(jìn)程與Linux并無多大的區(qū)別,在WIN32里的線程才相當(dāng)于Linux的進(jìn)程,是一個實際正在執(zhí)行的代碼。但是,WIN32里同一
個進(jìn)程里各個線程之間是共享數(shù)據(jù)段的。這才是與Linux的進(jìn)程最大的不同。 下面這段程序顯示了WIN32下一個進(jìn)程如何啟動一個線程。
int g; DWORD WINAPI ChildProcess( LPVOID lpParameter ){ int i; for ( i = 1; i <1000; i ++) { g ++; printf( "This is Child Thread: %d\n", g ); } ExitThread( 0 ); };
void main() { int threadID; int i; g = 0; CreateThread( NULL, 0, ChildProcess, NULL, 0, &threadID ); for ( i = 1; i <1000; i ++) { g ++; printf( "This is Parent Thread: %d\n", g ); } }
在WIN32下,使用CreateThread函數(shù)創(chuàng)建線程,與Linux下創(chuàng)建進(jìn)程不同,WIN32線程不是從創(chuàng)建處開始運(yùn)行的,而是由
CreateThread指定一個函數(shù),線程就從那個函數(shù)處開始運(yùn)行。此程序同前面的UNIX程序一樣,由兩個線程各打印1000條信息。
threadID是子線程的線程號,另外,全局變量g是子線程與父線程共享的,這就是與Linux最大的不同之處。大家可以看出,WIN32的進(jìn)程/線程
要比Linux復(fù)雜,在Linux要實現(xiàn)類似WIN32的線程并不難,只要fork以后,讓子進(jìn)程調(diào)用ThreadProc函數(shù),并且為全局變量開設(shè)共享
數(shù)據(jù)區(qū)就行了,但在WIN32下就無法實現(xiàn)類似fork的功能了。所以現(xiàn)在WIN32下的C語言編譯器所提供的庫函數(shù)雖然已經(jīng)能兼容大多數(shù)
Linux/UNIX的庫函數(shù),但卻仍無法實現(xiàn)fork。
對于多任務(wù)系統(tǒng),共享數(shù)據(jù)區(qū)是必要的,但也是一個容易引起混亂的問題,在WIN32下,一個程序員很容易忘記線程之間的數(shù)據(jù)是共享的這一情況,一個線程修
改過一個變量后,另一個線程卻又修改了它,結(jié)果引起程序出問題。但在Linux下,由于變量本來并不共享,而由程序員來顯式地指定要共享的數(shù)據(jù),使程序變
得更清晰與安全。 至于WIN32的"進(jìn)程"概念,其含義則是"應(yīng)用程序",也就是相當(dāng)于UNIX下的exec了。
2008年10月6日
http://doc.linuxpk.com/201.html
名稱:locate
使用權(quán)限:所有使用者
使用方式: locate [-q] [-d ] [--database=]
locate [-r ] [--regexp=]
locate [-qv] [-o ] [--output=]
locate [-e ] [-f ] ] [-c]
locate [-Vh] [--version] [--help]
說明:
locate 讓使用者可以很快速的搜尋檔案系統(tǒng)內(nèi)是否有指定的檔案。其方法是先建立一個包括系統(tǒng)內(nèi)所有檔案名稱及路徑的數(shù)據(jù)庫,之后當(dāng)尋找時就只需查詢這個數(shù)據(jù)庫,而不必實際深入檔案系統(tǒng)之中了。
在一般的 distribution 之中,數(shù)據(jù)庫的建立都被放在 contab 中自動執(zhí)行。一般使用者在使用時只要用
# locate your_file_name
的型式就可以了。 參數(shù):
-u
-U
建立數(shù)據(jù)庫,-u 會由根目錄開始,-U 則可以指定開始的位置。
-e
將
排除在尋找的范圍之外。
-l
如果 是 1.則啟動安全模式。在安全模式下,使用者不會看到權(quán)限無法看到的檔案。這會始速度減慢,因為 locate 必須至實際的檔案系統(tǒng)中取得檔案的權(quán)限資料。
-f
將特定的檔案系統(tǒng)排除在外,例如我們沒有到理要把 proc 檔案系統(tǒng)中的檔案放在數(shù)據(jù)庫中。
-q
安靜模式,不會顯示任何錯誤訊息。
-n
至多顯示 個輸出。
-r
使用正規(guī)運(yùn)算式 做尋找的條件。
-o
指定數(shù)據(jù)庫存的名稱。
-d
指定數(shù)據(jù)庫的路徑
-h
顯示輔助訊息
-v
顯示更多的訊息
-V
顯示程序的版本訊息 范例:
locate chdrv : 尋找所有叫 chdrv 的檔案
locate -n 100 a.out : 尋找所有叫 a.out 的檔案,但最多只顯示 100 個
locate -u : 建立數(shù)據(jù)庫
locate命令可以在搜尋數(shù)據(jù)庫時快速找到檔案,數(shù)據(jù)庫由updatedb程序來更新,updatedb是由cron
daemon周期性建立的,locate命令在搜尋數(shù)據(jù)庫時比由整個由硬盤資料來搜尋資料來得快,但較差勁的是locate所找到的檔案若是最近才建立或
剛更名的,可能會找不到,在內(nèi)定值中,updatedb每天會跑一次,可以由修改crontab來更新設(shè)定值。(etc/crontab)
locate指定用在搜尋符合條件的檔案,它會去儲存檔案與目錄名稱的數(shù)據(jù)庫內(nèi),尋找合乎范本樣式條件的檔案或目錄錄,可以使用特殊字元(如”*”或
”?”等)來指定范本樣式,如指定范本為kcpa*ner,
locate會找出所有起始字串為kcpa且結(jié)尾為ner的檔案或目錄,如名稱為kcpartner若目錄錄名稱為kcpa_ner則會列出該目錄下包括
子目錄在內(nèi)的所有檔案。
locate指令和find找尋檔案的功能類似,但locate是透過update程序?qū)⒂脖P中的所有檔案和
目錄資料先建立一個索引數(shù)據(jù)庫,在執(zhí)行l(wèi)oacte時直接找該索引,查詢速度會較快,索引數(shù)據(jù)庫一般是由操作系統(tǒng)管理,但也可以直接下達(dá)update強(qiáng)迫
系統(tǒng)立即修改索引數(shù)據(jù)庫。
不過第一次在執(zhí)行update後再使用locate尋找檔案常會失敗,此時就要執(zhí)行slocate
ˉu該命令(也可執(zhí)行updatedb指令,其效果相同)來更新slocate數(shù)據(jù)庫,該命令會在/usr/sbin下產(chǎn)生slocate執(zhí)行檔,再由
locate到此數(shù)據(jù)庫尋找所要找的資料。
|