|
2008年10月27日
linux目錄架構(gòu) / 根目錄 /bin 常用的命令 binary file 的目錄 /boot 存放系統(tǒng)啟動(dòng)時(shí)必須讀取的檔案,包括核心 (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é)點(diǎn)名 /etc/NETWORKING 只有YES標(biāo)明網(wǎng)絡(luò)存在 /etc/host.conf 文件說明用戶的系統(tǒng)如何查詢節(jié)點(diǎn)名 /etc/hosts 設(shè)定用戶自已的IP與名字的對(duì)應(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 這兩個(gè) X Server 的設(shè)定檔 /etc/fstab 記錄開機(jī)要mount的文件系統(tǒng) /etc/inittab 設(shè)定系統(tǒng)啟動(dòng)時(shí)init進(jìn)程將把系統(tǒng)設(shè)置成什么樣的runlevel /etc/issue 記錄用戶登錄前顯示的信息 /etc/group 設(shè)定用戶的組名與相關(guān)信息 /etc/passwd 帳號(hào)信息 /etc/shadow 密碼信息 /etc/sudoers 可以sudo命令的配置文件 /etc/securetty 設(shè)定哪些終端可以讓root登錄 /etc/login.defs 所有用戶登錄時(shí)的缺省配置 /etc/exports 設(shè)定NFS系統(tǒng)用的 /etc/init.d/ 所有服務(wù)的預(yù)設(shè)啟動(dòng) script 都是放在這裡的,例如要啟動(dòng)或者關(guān)閉 /etc/xinetd.d/ 這就是所謂的 super daemon 管理的各項(xiàng)服務(wù)的設(shè)定檔目錄 /etc/modprobe.conf 內(nèi)核模塊額外參數(shù)設(shè)定 /etc/syslog.conf 日志設(shè)置文件 /home 使用者家目錄 /lib 系統(tǒng)會(huì)使用到的函數(shù)庫 /lib/modules kernel 的相關(guān)模塊 /var/lib/rpm rpm套件安裝處 /lost+found 系統(tǒng)不正常產(chǎn)生錯(cuò)誤時(shí),會(huì)將一些遺失的片段放置於此目錄下 /mnt 外設(shè)的掛載點(diǎn) /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ù)啟動(dòng)之後,這些服務(wù)所需要取用的資料目錄 /tmp 一般使用者或者是正在執(zhí)行的程序暫時(shí)放置檔案的地方 /usr 最大的目錄,存許應(yīng)用程序和文件 /usr/X11R6: X-Window目錄 /usr/src: Linux源代碼 /usr/include:系統(tǒng)頭文件 /usr/openwin 存放SUN的OpenWin /usr/man 在線使用手冊(cè) /usr/bin 使用者可執(zhí)行的 binary file 的目錄 /usr/local/bin 使用者可執(zhí)行的 binary file 的目錄 /usr/lib 系統(tǒng)會(huì)使用到的函數(shù)庫 /usr/local/lib 系統(tǒng)會(huì)使用到的函數(shù)庫 /usr/sbin 系統(tǒng)管理員才能執(zhí)行的指令 /usr/local/sbin 系統(tǒng)管理員才能執(zhí)行的指令 /var 日志文件 /var/log/secure 記錄登入系統(tǒng)存取資料的檔案,例如 pop3, ssh, telnet, ftp 等都會(huì)記錄在此檔案中 /var/log/wtmp 記錄登入者的訊息資料, last /var/log/messages 幾乎系統(tǒng)發(fā)生的錯(cuò)誤訊息 /var/log/boot.log 記錄開機(jī)或者是一些服務(wù)啟動(dòng)的時(shí)候,所顯示的啟動(dòng)或關(guān)閉訊息 /var/log/maillog 紀(jì)錄郵件存取或往來( sendmail 與 pop3 )的使用者記錄 /var/log/cron 記錄 crontab 這個(gè)例行性服務(wù)的內(nèi)容 /var/log/httpd, /var/log/news, /var/log/mysqld.log, /var/log/samba, /var/log/procmail.log: 分別是幾個(gè)不同的網(wǎng)路服務(wù)的記錄檔 一些常用的基本命令: uname -a 查看內(nèi)核版本 ls -al 顯示所有文件的屬性 pwd 顯示當(dāng)前路徑 cd - 返回上一次目錄 cd ~ 返回主目錄 date s 設(shè)置時(shí)間、日期 cal 顯示日歷 cal 2006 bc 計(jì)算器具 man & info 幫助手冊(cè) 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 移動(dòng) mv 123.txt 222.txt 重命名 mkdir 創(chuàng)建文件夾 touch 創(chuàng)建文件 更新當(dāng)前時(shí)間 cat 由第一行開始顯示 cat |more 分頁 nl 在內(nèi)容前加行號(hào) more & less 一面一面翻動(dòng) 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個(gè)指令 stty 設(shè)置終端 stty -a fdisk /mbr 刪除GRUB at 僅進(jìn)行一次的工作排程 crontab 循環(huán)執(zhí)行的例行性命令 [e]編輯,[l]顯示,[r]刪除任務(wù) & 后臺(tái)運(yùn)行程序 tar -zxvf 123.tar.gz & ---------> 后臺(tái)運(yùn)行 jobs 觀看后臺(tái)暫停的程序 jobs -l fg 將后臺(tái)程序調(diào)到前臺(tái) fg n ------> n是數(shù)字,可以指定進(jìn)行那個(gè)程序 bg 讓工作在后臺(tái)運(yùn)行 kill 結(jié)束進(jìn)程 kill -9 PID [9]強(qiáng)制結(jié)束,[15]正常結(jié)束,[l]列出可用的kill信號(hào) ps aux 查看后臺(tái)程序 top 查看后臺(tái)程序 top -d 2 每?jī)擅敫乱淮? top -d 2 -p10604 觀看某個(gè)PID
top -b -n 2 > /tmp/top.txt -----> 將
top 的資訊進(jìn)行 2 次,然後將結(jié)果輸出到 /tmp/top.txt pstree 以樹狀圖顯示程序 [A]以 ASCII 來連接, [u]列出PID, [p]列出帳號(hào) killall 要?jiǎng)h除某個(gè)服務(wù) killall -9 httpd free 顯示內(nèi)存狀態(tài) free -m --------> 以M為單位顯示 uptime 顯示目前系統(tǒng)開機(jī)時(shí)間 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 給一個(gè) nice 植為 -5 ,用於執(zhí)行 vi renice 調(diào)整已存在優(yōu)先權(quán) runlevel 顯示目前的runlevel depmod 分析可載入模塊的相依性 lsmod 顯示已載入系統(tǒng)的模塊 modinfo 顯示kernel模塊的信息 insmod 載入模塊 modprobe 自動(dòng)處理可載入模塊 rmmod 刪除模塊 chkconfig 檢查,設(shè)置系統(tǒng)的各種服務(wù) chkconfig --list -----> 列出各項(xiàng)服務(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 移動(dòng)到行首 yy 復(fù)制當(dāng)前行 :e! 還原原始檔 $ 移動(dòng)到行尾 nyy 復(fù)制n行 :w filename 另存為 H 屏幕最上 p,P 粘貼 :set nu 設(shè)置行號(hào)
M 屏幕中央 u 撤消
:set nonu 取消行號(hào) L 屏幕最下 [Ctrl]+r 重做上一個(gè)動(dòng)作 ZZ 保存離開 G 檔案最后一行 [ctrl]+z 暫停退出 :set nohlsearch 永久地關(guān)閉高亮顯示
/work 向下搜索
:sp 同時(shí)打開兩個(gè)文檔 ?work 向上搜索
[Ctrl]+w 兩個(gè)文檔設(shè)換
gg 移動(dòng)到檔案第一行
:nohlsearch 暫時(shí)關(guān)閉高亮顯示 認(rèn)識(shí)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 ' 單引號(hào)時(shí)$name失去變量?jī)?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個(gè)字節(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 :計(jì)算找到 '搜尋字串 ' 的次數(shù) -i :忽略大小寫的不同,所以大小寫視為相同 -n :順便輸出行號(hào) -v :反向選擇,亦即顯示出沒有 '搜尋字串 ' 內(nèi)容的那一行! grep -n 'the ' 123.txt 搜索the字符 -----------搜尋特定字串 grep -n 't[ea]st ' 123.txt 搜索test或taste兩個(gè)字符---------利用 [] 來搜尋集合字元 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字符----------任意一個(gè)字元 . grep -n 'ooo* ' 123.txt 搜索至少有兩個(gè)oo的字符---------重複字元 * sed 文本流編輯器 利用腳本命令來處理文本文件 awd 模式掃描和處理語言 nl 123.txt | sed '2,5d ' 刪除第二到第五行的內(nèi)容 diff 比較文件的差異 cmp 比較兩個(gè)文件是否有差異 patch 修補(bǔ)文件 pr 要打印的文件格式化 帳號(hào)管理 /etc/passwd 系統(tǒng)帳號(hào)信息 /etc/shadow 帳號(hào)密碼信息 經(jīng)MD5 32位加密 在密碼欄前面加『 * 』『 ! 』禁止使用某帳號(hào) /etc/group 系統(tǒng)群組信息 /etc/gshadow newgrp 改變登陸組 useradd & adduser 建立新用戶 ---------> useradd -m test 自動(dòng)建立用戶的登入目錄 useradd -m -g pgroup test ---------> 指定所屬級(jí) /etc/default/useradd 相關(guān)設(shè)定 /etc/login.defs UID/GID 有關(guān)的設(shè)定 passwd 更改密碼 -----------> passwd test usermod 修改用戶帳號(hào) userdel 刪除帳號(hào) -----------> userdel -r test chsh 更換登陸系統(tǒng)時(shí)使用的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 最近登入的時(shí)間 讀取 /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一定要是獨(dú)立的分區(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 --------> 啟動(dòng)所有的具有 quota 的 filesystem quotaoff 關(guān)閉磁盤空間限制 quotaoff -a --------> 關(guān)閉了 quota 的限制 repquota -av 查閱系統(tǒng)內(nèi)所有的具有 quota 的 filesystem 的限值狀態(tài) Quota 從開始準(zhǔn)備 filesystem 的支援到整個(gè)設(shè)定結(jié)束的主要的步驟大概是: 1、設(shè)定 partition 的 filesystem 支援 quota 參數(shù): 由於 quota 必須要讓 partition 上面的 filesystem 支援才行,一般來說, 支援度最好的是 ext2/ext3 , 其他的 filesystem 類型鳥哥我是沒有試過啦! 啟動(dòng) filesystem 支援 quota 最簡(jiǎn)單就是編輯 /etc/fstab , 使得準(zhǔn)備要開放的 quota 磁碟可以支援 quota 囉; 2、建立 quota 記錄檔: 剛剛前面講過,整個(gè) quota 進(jìn)行磁碟限制值記錄的檔案是 aquota.user/aquota.group, 要建立這兩個(gè)檔案就必須要先利用 quotacheck 掃瞄才行喔! 3、編輯 quota 限制值資料: 再來就是使用 edquota 來編輯每個(gè)使用者或群組的可使用空間囉; 4、重新掃瞄與啟動(dòng) quota : 設(shè)定好 quota 之後,建議可以再進(jìn)行一次 quotacheck ,然後再以 quotaon 來啟動(dòng)吧! 開機(jī)流程簡(jiǎn)介 1、載入 BIOS 的硬體資訊,並取得第一個(gè)開機(jī)裝置的代號(hào); 2、讀取第一個(gè)開機(jī)裝置的 MBR 的 boot Loader (亦即是 lilo, grub, spfdisk 等等) 的開機(jī)資訊; 3、載入 Kernel 作業(yè)系統(tǒng)核心資訊, Kernel 開始解壓縮,並且嘗試驅(qū)動(dòng)所有硬體裝置; 4、Kernel 執(zhí)行 init 程式並取得 run-level 資訊; 5、init 執(zhí)行 /etc/rc.d/rc.sysinit 檔案; 6、啟動(dòng)核心的外掛模組 (/etc/modprobe.conf); 7、init 執(zhí)行 run-level 的各個(gè)批次檔( 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ī)啟動(dòng),以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 ------> 查詢?cè)撎准械哪夸? rpm -qi -----> 查詢套件的說明資料 rpm -qc[d] -----> 設(shè)定檔與說明檔 rpm -ivh ----> 安裝 rpm -V --------> 查看套件有否更動(dòng)過 rpm -e ------> 刪除 rpm -Uvh -------> 升級(jí)安裝 --nodeps -----> 強(qiáng)行安裝 --test -----> 測(cè)試安裝
2008年10月22日
Linux環(huán)境進(jìn)程間通信(三):消息隊(duì)列
本系列文章中的前兩部分,我們探討管道及信號(hào)兩種通信機(jī)制,本文將深入第三部分,介紹系統(tǒng) V 消息隊(duì)列及其相應(yīng) API。
消息隊(duì)列(也叫做報(bào)文隊(duì)列)能夠克服早期unix通信機(jī)制的一些缺點(diǎn)。作為早期unix通信機(jī)制之一的信號(hào)能夠傳送的信息量有限,后來雖然
POSIX
1003.1b在信號(hào)的實(shí)時(shí)性方面作了拓廣,使得信號(hào)在傳遞信息量方面有了相當(dāng)程度的改進(jìn),但是信號(hào)這種通信方式更像"即時(shí)"的通信方式,它要求接受信號(hào)
的進(jìn)程在某個(gè)時(shí)間范圍內(nèi)對(duì)信號(hào)做出反應(yīng),因此該信號(hào)最多在接受信號(hào)進(jìn)程的生命周期內(nèi)才有意義,信號(hào)所傳遞的信息是接近于隨進(jìn)程持續(xù)的概念
(process-persistent),見附錄 1;管道及有名管道及有名管道則是典型的隨進(jìn)程持續(xù)IPC,并且,只能傳送無格式的字節(jié)流無疑會(huì)給應(yīng)用程序開發(fā)帶來不便,另外,它的緩沖區(qū)大小也受到限制。
消息隊(duì)列就是一個(gè)消息的鏈表??梢园严⒖醋饕粋€(gè)記錄,具有特定的格式以及特定的優(yōu)先級(jí)。對(duì)消息隊(duì)列有寫權(quán)限的進(jìn)程可以向中按照一定的規(guī)則添加新消息;對(duì)消息隊(duì)列有讀權(quán)限的進(jìn)程則可以從消息隊(duì)列中讀走消息。消息隊(duì)列是隨內(nèi)核持續(xù)的(參見附錄 1)。
目前主要有兩種類型的消息隊(duì)列:POSIX消息隊(duì)列以及系統(tǒng)V消息隊(duì)列,系統(tǒng)V消息隊(duì)列目前被大量使用??紤]到程序的可移植性,新開發(fā)的應(yīng)用程序應(yīng)盡量使用POSIX消息隊(duì)列。
在本系列專題的序(深刻理解Linux進(jìn)程間通信(IPC))中,提到對(duì)于消息隊(duì)列、信號(hào)燈、以及共享內(nèi)存區(qū)來說,有兩個(gè)實(shí)現(xiàn)版本:POSIX的以
及系統(tǒng)V的。Linux內(nèi)核(內(nèi)核2.4.18)支持POSIX信號(hào)燈、POSIX共享內(nèi)存區(qū)以及POSIX消息隊(duì)列,但對(duì)于主流Linux發(fā)行版本之一
redhad8.0(內(nèi)核2.4.18),還沒有提供對(duì)POSIX進(jìn)程間通信API的支持,不過應(yīng)該只是時(shí)間上的事。
因此,本文將主要介紹系統(tǒng)V消息隊(duì)列及其相應(yīng)API。在沒有聲明的情況下,以下討論中指的都是系統(tǒng)V消息隊(duì)列。
一、消息隊(duì)列基本概念
- 系統(tǒng)V消息隊(duì)列是隨內(nèi)核持續(xù)的,只有在內(nèi)核重起或者顯示刪除一個(gè)消息隊(duì)列時(shí),該消息隊(duì)列才會(huì)真正被刪除。因此系統(tǒng)中記錄消息隊(duì)列的數(shù)據(jù)結(jié)構(gòu)(struct ipc_ids msg_ids)位于內(nèi)核中,系統(tǒng)中的所有消息隊(duì)列都可以在結(jié)構(gòu)msg_ids中找到訪問入口。
- 消息隊(duì)列就是一個(gè)消息的鏈表。每個(gè)消息隊(duì)列都有一個(gè)隊(duì)列頭,用結(jié)構(gòu)struct msg_queue來描述(參見附錄 2)。隊(duì)列頭中包含了該消息隊(duì)列的大量信息,包括消息隊(duì)列鍵值、用戶ID、組ID、消息隊(duì)列中消息數(shù)目等等,甚至記錄了最近對(duì)消息隊(duì)列讀寫進(jìn)程的ID。讀者可以訪問這些信息,也可以設(shè)置其中的某些信息。
- 下圖說明了內(nèi)核與消息隊(duì)列是怎樣建立起聯(lián)系的:
其中:struct ipc_ids msg_ids是內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu);struct msg_queue是每個(gè)消息隊(duì)列的隊(duì)列頭。

從上圖可以看出,全局?jǐn)?shù)據(jù)結(jié)構(gòu) struct ipc_ids msg_ids 可以訪問到每個(gè)消息隊(duì)列頭的第一個(gè)成員:struct
kern_ipc_perm;而每個(gè)struct
kern_ipc_perm能夠與具體的消息隊(duì)列對(duì)應(yīng)起來是因?yàn)樵谠摻Y(jié)構(gòu)中,有一個(gè)key_t類型成員key,而key則唯一確定一個(gè)消息隊(duì)列。
kern_ipc_perm結(jié)構(gòu)如下:
struct kern_ipc_perm{ //內(nèi)核中記錄消息隊(duì)列的全局?jǐn)?shù)據(jù)結(jié)構(gòu)msg_ids能夠訪問到該結(jié)構(gòu); key_t key; //該鍵值則唯一對(duì)應(yīng)一個(gè)消息隊(duì)列 uid_t uid; gid_t gid; uid_t cuid; gid_t cgid; mode_t mode; unsigned long seq; }
|
二、操作消息隊(duì)列
對(duì)消息隊(duì)列的操作無非有下面三種類型:
1、 打開或創(chuàng)建消息隊(duì)列 消息隊(duì)列的內(nèi)核持續(xù)性要求每個(gè)消息隊(duì)列都在系統(tǒng)范圍內(nèi)對(duì)應(yīng)唯一的鍵值,所以,要獲得一個(gè)消息隊(duì)列的描述字,只需提供該消息隊(duì)列的鍵值即可;
注:消息隊(duì)列描述字是由在系統(tǒng)范圍內(nèi)唯一的鍵值生成的,而鍵值可以看作對(duì)應(yīng)系統(tǒng)內(nèi)的一條路經(jīng)。
2、 讀寫操作
消息讀寫操作非常簡(jiǎn)單,對(duì)開發(fā)人員來說,每個(gè)消息都類似如下的數(shù)據(jù)結(jié)構(gòu):
struct msgbuf{ long mtype; char mtext[1]; };
|
mtype成員代表消息類型,從消息隊(duì)列中讀取消息的一個(gè)重要依據(jù)就是消息的類型;mtext是消息內(nèi)容,當(dāng)然長(zhǎng)度不一定為1。因此,對(duì)于發(fā)送消息
來說,首先預(yù)置一個(gè)msgbuf緩沖區(qū)并寫入消息類型和內(nèi)容,調(diào)用相應(yīng)的發(fā)送函數(shù)即可;對(duì)讀取消息來說,首先分配這樣一個(gè)msgbuf緩沖區(qū),然后把消息
讀入該緩沖區(qū)即可。
3、 獲得或設(shè)置消息隊(duì)列屬性:
消息隊(duì)列的信息基本上都保存在消息隊(duì)列頭中,因此,可以分配一個(gè)類似于消息隊(duì)列頭的結(jié)構(gòu)(struct msqid_ds,見附錄 2),來返回消息隊(duì)列的屬性;同樣可以設(shè)置該數(shù)據(jù)結(jié)構(gòu)。
消息隊(duì)列API
1、文件名到鍵值
#include <sys/types.h> #include <sys/ipc.h> key_t ftok (char*pathname, char proj);
|
它返回與路徑pathname相對(duì)應(yīng)的一個(gè)鍵值。該函數(shù)不直接對(duì)消息隊(duì)列操作,但在調(diào)用ipc(MSGGET,…)或msgget()來獲得消息隊(duì)列描述字前,往往要調(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)程間通信的三種方式(消息隊(duì)列、信號(hào)燈、共享內(nèi)存區(qū))提供了一個(gè)統(tǒng)一的用戶界面:
int ipc(unsigned int call, int first, int second, int third, void *ptr, long fifth);
第一個(gè)參數(shù)指明對(duì)IPC對(duì)象的操作方式,對(duì)消息隊(duì)列而言共有四種操作:MSGSND、MSGRCV、MSGGET以及MSGCTL,分別代表向消息
隊(duì)列發(fā)送消息、從消息隊(duì)列讀取消息、打開或創(chuàng)建消息隊(duì)列、控制消息隊(duì)列;first參數(shù)代表唯一的IPC對(duì)象;下面將介紹四種操作。
- int ipc(MSGGET, int first, int second, int third, void *ptr, long fifth);
與該操作對(duì)應(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)
與該操作對(duì)應(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);
與該操作對(duì)應(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);
與該操作對(duì)應(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)一的用戶界面,但正是由于這個(gè)特性,它的參數(shù)幾乎不能給出特定的實(shí)際意義(如以first、second來命名參數(shù)),在一定程度上造成開發(fā)不便。
- 正如ipc手冊(cè)所說的:ipc()是linux所特有的,編寫程序時(shí)應(yīng)注意程序的移植性問題;
- 該系統(tǒng)調(diào)用的實(shí)現(xiàn)不過是把系統(tǒng)V IPC函數(shù)進(jìn)行了封裝,沒有任何效率上的優(yōu)勢(shì);
- 系統(tǒng)V在IPC方面的API數(shù)量不多,形式也較簡(jiǎn)潔。
3.系統(tǒng)V消息隊(duì)列API 系統(tǒng)V消息隊(duì)列API共有四個(gè),使用時(shí)需要包括幾個(gè)頭文件:
#include <sys/types.h> #include <sys/ipc.h> #include <sys/msg.h>
|
1)int msgget(key_t key, int msgflg)
參數(shù)key是一個(gè)鍵值,由ftok獲得;msgflg參數(shù)是一些標(biāo)志位。該調(diào)用返回與健值key相對(duì)應(yīng)的消息隊(duì)列描述字。
在以下兩種情況下,該調(diào)用將創(chuàng)建一個(gè)新的消息隊(duì)列:
- 如果沒有消息隊(duì)列與健值key相對(duì)應(yīng),并且msgflg中包含了IPC_CREAT標(biāo)志位;
- key參數(shù)為IPC_PRIVATE;
參數(shù)msgflg可以為以下:IPC_CREAT、IPC_EXCL、IPC_NOWAIT或三者的或結(jié)果。
調(diào)用返回:成功返回消息隊(duì)列描述字,否則返回-1。
注:參數(shù)key設(shè)置成常數(shù)IPC_PRIVATE并不意味著其他進(jìn)程不能訪問該消息隊(duì)列,只意味著即將創(chuàng)建新的消息隊(duì)列。
2)int msgrcv(int msqid, struct msgbuf *msgp, int msgsz, long msgtyp, int msgflg); 該系統(tǒng)調(diào)用從msgid代表的消息隊(duì)列中讀取一個(gè)消息,并把消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中。
msqid為消息隊(duì)列描述字;消息返回后存儲(chǔ)在msgp指向的地址,msgsz指定msgbuf的mtext成員的長(zhǎng)度(即消息內(nèi)容的長(zhǎng)度),msgtyp為請(qǐng)求讀取的消息類型;讀消息標(biāo)志msgflg可以為以下幾個(gè)常值的或:
- IPC_NOWAIT 如果沒有滿足條件的消息,調(diào)用立即返回,此時(shí),errno=ENOMSG
- IPC_EXCEPT 與msgtyp>0配合使用,返回隊(duì)列中第一個(gè)類型不為msgtyp的消息
- IPC_NOERROR 如果隊(duì)列中滿足條件的消息內(nèi)容大于所請(qǐng)求的msgsz字節(jié),則把該消息截?cái)啵財(cái)嗖糠謱G失。
msgrcv手冊(cè)中詳細(xì)給出了消息類型取不同值時(shí)(>0; <0; =0),調(diào)用將返回消息隊(duì)列中的哪個(gè)消息。
msgrcv()解除阻塞的條件有三個(gè):
- 消息隊(duì)列中有了滿足條件的消息;
- msqid代表的消息隊(duì)列被刪除;
- 調(diào)用msgrcv()的進(jìn)程被信號(hào)中斷;
調(diào)用返回:成功返回讀出消息的實(shí)際字節(jié)數(shù),否則返回-1。
3)int msgsnd(int msqid, struct msgbuf *msgp, int msgsz, int msgflg); 向msgid代表的消息隊(duì)列發(fā)送一個(gè)消息,即將發(fā)送的消息存儲(chǔ)在msgp指向的msgbuf結(jié)構(gòu)中,消息的大小由msgze指定。
對(duì)發(fā)送消息來說,有意義的msgflg標(biāo)志為IPC_NOWAIT,指明在消息隊(duì)列沒有足夠空間容納要發(fā)送的消息時(shí),msgsnd是否等待。造成msgsnd()等待的條件有兩種:
- 當(dāng)前消息的大小與當(dāng)前消息隊(duì)列中的字節(jié)數(shù)之和超過了消息隊(duì)列的總?cè)萘浚?
- 當(dāng)前消息隊(duì)列的消息數(shù)(單位"個(gè)")不小于消息隊(duì)列的總?cè)萘浚▎挝?字節(jié)數(shù)"),此時(shí),雖然消息隊(duì)列中的消息數(shù)目很多,但基本上都只有一個(gè)字節(jié)。
msgsnd()解除阻塞的條件有三個(gè):
- 不滿足上述兩個(gè)條件,即消息隊(duì)列中有容納該消息的空間;
- msqid代表的消息隊(duì)列被刪除;
- 調(diào)用msgsnd()的進(jìn)程被信號(hào)中斷;
調(diào)用返回:成功返回0,否則返回-1。
4)int msgctl(int msqid, int cmd, struct msqid_ds *buf); 該系統(tǒng)調(diào)用對(duì)由msqid標(biāo)識(shí)的消息隊(duì)列執(zhí)行cmd操作,共有三種cmd操作:IPC_STAT、IPC_SET 、IPC_RMID。
- IPC_STAT:該命令用來獲取消息隊(duì)列信息,返回的信息存貯在buf指向的msqid結(jié)構(gòu)中;
- IPC_SET:該命令用來設(shè)置消息隊(duì)列的屬性,要設(shè)置的屬性存儲(chǔ)在buf指向的msqid結(jié)構(gòu)中;可設(shè)置屬性包括:msg_perm.uid、msg_perm.gid、msg_perm.mode以及msg_qbytes,同時(shí),也影響msg_ctime成員。
- IPC_RMID:刪除msqid標(biāo)識(shí)的消息隊(duì)列;
調(diào)用返回:成功返回0,否則返回-1。
三、消息隊(duì)列的限制 每個(gè)消息隊(duì)列的容量(所能容納的字節(jié)數(shù))都有限制,該值因系統(tǒng)不同而不同。在后面的應(yīng)用實(shí)例中,輸出了redhat 8.0的限制,結(jié)果參見附錄 3。
另一個(gè)限制是每個(gè)消息隊(duì)列所能容納的最大消息數(shù):在redhad 8.0中,該限制是受消息隊(duì)列容量制約的:消息個(gè)數(shù)要小于消息隊(duì)列的容量(字節(jié)數(shù))。
注:上述兩個(gè)限制是針對(duì)每個(gè)消息隊(duì)列而言的,系統(tǒng)對(duì)消息隊(duì)列的限制還有系統(tǒng)范圍內(nèi)的最大消息隊(duì)列個(gè)數(shù),以及整個(gè)系統(tǒng)范圍內(nèi)的最大消息數(shù)。一般來說,實(shí)際開發(fā)過程中不會(huì)超過這個(gè)限制。
四、消息隊(duì)列應(yīng)用實(shí)例 消息隊(duì)列應(yīng)用相對(duì)較簡(jiǎn)單,下面實(shí)例基本上覆蓋了對(duì)消息隊(duì)列的所有操作,同時(shí),程序輸出結(jié)果有助于加深對(duì)前面所講的某些規(guī)則及消息隊(duì)列限制的理解。
#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)建一個(gè)消息隊(duì)列后,輸出消息隊(duì)列缺省屬性 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ā)送一個(gè)消息后,輸出消息隊(duì)列屬性 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); //從消息隊(duì)列中讀出消息后,輸出消息隊(duì)列屬性 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; //此處驗(yàn)證超級(jí)用戶可以更改消息隊(duì)列的缺省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); //驗(yàn)證設(shè)置消息隊(duì)列屬性 reval=msgctl(msgid,IPC_RMID,NULL);//刪除消息隊(duì)列 if(reval==-1) { printf("unlink msg queue error\n"); return; } } void msg_stat(int msgid,struct msqid_ds msg_info) { int reval; sleep(1);//只是為了后面輸出時(shí)間的方便 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); //每個(gè)消息隊(duì)列的容量(字節(jié)數(shù))都有限制MSGMNB,值的大小因系統(tǒng)而異。在創(chuàng)建新的消息隊(duì)列時(shí),//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é): 消息隊(duì)列
與管道以及有名管道相比,具有更大的靈活性,首先,它提供有格式字節(jié)流,有利于減少開發(fā)人員的工作量;其次,消息具有類型,在實(shí)際應(yīng)用中,可作為優(yōu)先級(jí)使
用。這兩點(diǎn)是管道以及有名管道所不能比的。同樣,消息隊(duì)列可以在幾個(gè)進(jìn)程間復(fù)用,而不管這幾個(gè)進(jìn)程是否具有親緣關(guān)系,這一點(diǎn)與有名管道很相似;但消息隊(duì)列
是隨內(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對(duì)象的最后一個(gè)進(jìn)程關(guān)閉該對(duì)象為止。如管道和有名管道;
- 隨內(nèi)核持續(xù):IPC一直持續(xù)到內(nèi)核重新自舉或者顯示刪除該對(duì)象為止。如消息隊(duì)列、信號(hào)燈以及共享內(nèi)存等;
- 隨文件系統(tǒng)持續(xù):IPC一直持續(xù)到顯示刪除該對(duì)象為止。
附錄 2: 結(jié)構(gòu)msg_queue用來描述消息隊(duì)列頭,存在于系統(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è)置或返回消息隊(duì)列的信息,存在于用戶空間;
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 */ };
|
//可以看出上述兩個(gè)結(jié)構(gòu)很相似。
附錄 3:消息隊(duì)列實(shí)例輸出結(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)建一個(gè)新消息隊(duì)列時(shí)的輸出 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 //實(shí)際讀出的字節(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 //每個(gè)消息隊(duì)列最大容量(字節(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 //可看出超級(jí)用戶可修改消息隊(duì)列最大容量 pid of last msgsnd is 2510 pid of last msgrcv is 2510 //對(duì)操作消息隊(duì)列進(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)用對(duì)msg_ctime有影響 msg uid is 8 msg gid is 8
|
參考文獻(xiàn):
- UNIX網(wǎng)絡(luò)編程第二卷:進(jìn)程間通信,作者:W.Richard Stevens,譯者:楊繼張,清華大學(xué)出版社。對(duì)POSIX以及系統(tǒng)V消息隊(duì)列都有闡述,對(duì)Linux環(huán)境下的程序開發(fā)有極大的啟發(fā)意義。
- linux內(nèi)核源代碼情景分析(上),毛德操、胡希明著,浙江大學(xué)出版社,給出了系統(tǒng)V消息隊(duì)列相關(guān)的源代碼分析。
- http://www.fanqiang.com/a4/b2/20010508/113315.html,主要闡述linux下對(duì)文件的操作,詳細(xì)介紹了對(duì)文件的存取權(quán)限位,對(duì)IPC對(duì)象的存取權(quán)限同樣具有很好的借鑒意義。
- msgget、msgsnd、msgrcv、msgctl手冊(cè)
2008年10月20日
http://blog.programfan.com/trackback.asp?id=6922
C語言的標(biāo)準(zhǔn)庫函數(shù)包括一系列日期和時(shí)間處理函數(shù),它們都在頭文件中說明。下面列出了這些函數(shù)。在頭文件中定義了三種類型:time_t,struct tm和clock_t。
在中說明的C語言時(shí)間函數(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í)間函數(shù)集
|
asctime(將時(shí)間和日期以字符串格式表示)
|
相關(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)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果以字符串形態(tài)返回。此函數(shù)已經(jīng)由時(shí)區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)間,字符串格式為:"Wed Jun 30 21:49:08 1993\n"
|
返回值
|
若再調(diào)用相關(guān)的時(shí)間日期函數(shù),此字符串可能會(huì)被破壞。此函數(shù)與ctime不同處在于傳入的參數(shù)是不同的結(jié)構(gòu)。
|
附加說明
|
返回一字符串表示目前當(dāng)?shù)氐臅r(shí)間日期。
|
范例
|
#include main() { time_t timep; time (&timep); printf("%s",asctime(gmtime(&timep))); }
|
執(zhí)行
|
Sat Oct 28 02:10:06 2000
|
|
|
|
ctime(將時(shí)間和日期以字符串格式表示)
|
相關(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)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果
以字符串形態(tài)返回。此函數(shù)已經(jīng)由時(shí)區(qū)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)間,字符串格式為"Wed Jun 30 21 :49 :08
1993\n"。若再調(diào)用相關(guān)的時(shí)間日期函數(shù),此字符串可能會(huì)被破壞。
|
返回值
|
返回一字符串表示目前當(dāng)?shù)氐臅r(shí)間日期。
|
范例
|
#include main() { time_t timep; time (&timep); printf("%s",ctime(&timep)); }
|
執(zhí)行
|
Sat Oct 28 10 : 12 : 05 2000
|
|
|
|
gettimeofday(取得目前的時(shí)間)
|
相關(guān)函數(shù)
|
time,ctime,ftime,settimeofday
|
表頭文件
|
#include #include
|
定義函數(shù)
|
int gettimeofday ( struct timeval * tv , struct timezone * tz )
|
函數(shù)說明
|
gettimeofday()會(huì)把目前的時(shí)間有tv所指的結(jié)構(gòu)返回,當(dāng)?shù)貢r(shí)區(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 時(shí)間差了多少分鐘*/ int tz_dsttime; /*日光節(jié)約時(shí)間的狀態(tài)*/ }; 上述兩個(gè)結(jié)構(gòu)都定義在/usr/include/sys/time.h。tz_dsttime 所代表的狀態(tài)如下 DST_NONE /*不使用*/ DST_USA /*美國(guó)*/ DST_AUST /*澳洲*/ DST_WET /*西歐*/ DST_MET /*中歐*/ DST_EET /*東歐*/ DST_CAN /*加拿大*/ DST_GB /*大不列顛*/ DST_RUM /*羅馬尼亞*/ DST_TUR /*土耳其*/ DST_AUSTALT /*澳洲(1986年以后)*/
|
返回值
|
成功則返回0,失敗返回-1,錯(cuò)誤代碼存于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(取得目前時(shí)間和日期)
|
相關(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)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(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í)數(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é)約時(shí)間的旗標(biāo) 此函數(shù)返回的時(shí)間日期未經(jīng)時(shí)區(qū)轉(zhuǎn)換,而是UTC時(shí)間。
|
返回值
|
返回結(jié)構(gòu)tm代表目前UTC 時(shí)間
|
范例
|
#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(shí)間和日期)
|
相關(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)換成真實(shí)世界所使用的時(shí)間日期表示方法,然后將結(jié)果由結(jié)構(gòu)tm返回。結(jié)構(gòu)tm的定義請(qǐng)參考gmtime()。此函數(shù)返回的時(shí)間日期已經(jīng)轉(zhuǎn)換成當(dāng)?shù)貢r(shí)區(qū)。
|
返回值
|
返回結(jié)構(gòu)tm代表目前的當(dāng)?shù)貢r(shí)間。
|
范例
|
#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(shí)間*/ 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(將時(shí)間結(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時(shí)0分0 秒算起至今的UTC時(shí)間所經(jīng)過的秒數(shù)。
|
返回值
|
返回經(jīng)過的秒數(shù)。
|
范例
|
/* 用time()取得時(shí)間(秒數(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è)置目前時(shí)間)
|
相關(guān)函數(shù)
|
time,ctime,ftime,gettimeofday
|
表頭文件
|
#include #include
|
定義函數(shù)
|
int settimeofday ( const struct timeval *tv,const struct timezone *tz);
|
函數(shù)說明
|
settimeofday()會(huì)把目前時(shí)間設(shè)成由tv所指的結(jié)構(gòu)信息,當(dāng)?shù)貢r(shí)區(qū)信息則設(shè)成tz所指的結(jié)構(gòu)。詳細(xì)的說明請(qǐng)參考gettimeofday()。注意,只有root權(quán)限才能使用此函數(shù)修改時(shí)間。
|
返回值
|
成功則返回0,失敗返回-1,錯(cuò)誤代碼存于errno。
|
錯(cuò)誤代碼
|
EPERM 并非由root權(quán)限調(diào)用settimeofday(),權(quán)限不夠。 EINVAL 時(shí)區(qū)或某個(gè)數(shù)據(jù)是不正確的,無法正確設(shè)置時(shí)間。
|
|
|
|
time(取得目前的時(shí)間)
|
相關(guān)函數(shù)
|
ctime,ftime,gettimeofday
|
表頭文件
|
#include
|
定義函數(shù)
|
time_t time(time_t *t);
|
函數(shù)說明
|
此函數(shù)會(huì)返回從公元1970年1月1日的UTC時(shí)間從0時(shí)0分0秒算起到現(xiàn)在所經(jīng)過的秒數(shù)。如果t 并非空指針的話,此函數(shù)也會(huì)將返回值存到t指針?biāo)傅膬?nèi)存。
|
返回值
|
成功則返回秒數(shù),失敗則返回((time_t)-1)值,錯(cuò)誤原因存于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是一個(gè)簡(jiǎn)單小巧,可以很容易集成到其它程序中的C++ XML解析器。
它能做些什么
簡(jiǎn)單地說,TinyXML解析一個(gè)XML文檔并由此生成一個(gè)可讀可修改可保存的文檔對(duì)象模型(DOM)。
XML的意思是“可擴(kuò)展標(biāo)記語言“(eXtensible Markup
Language)。它允許你創(chuàng)建你自己的文檔標(biāo)記。在為瀏覽器標(biāo)記文檔方面HTML做得很好,然而XML允許你定義任何文檔標(biāo)記,比如可以為一個(gè)組織者
應(yīng)用程序定義一個(gè)描述“to do”列表的文檔。
XML擁有一個(gè)結(jié)構(gòu)化并且方便的格式,所有為存儲(chǔ)應(yīng)用程序數(shù)據(jù)而創(chuàng)建的隨機(jī)文件格式都可以用XML代替,而這一切只需要一個(gè)解析器。
最全面正確的說明可以在http://www.w3.org/TR/2004/REC-xml-20040204/找到,但坦白地說,它很晦澀難懂。事實(shí)上我喜歡http://skew.org/xml/tutorial上關(guān)于XML的介紹。
有不同的方法可以訪問和與XML數(shù)據(jù)進(jìn)行交互。TinyXML使用文檔對(duì)象模型(DOM),這意味著XML數(shù)據(jù)被解析成一個(gè)可被瀏覽和操作的C++
對(duì)象,然后它可以被寫到磁盤或者另一個(gè)輸出流中。你也可以把C++對(duì)象構(gòu)造成一個(gè)XML文檔然后把它寫到磁盤或者另一個(gè)輸出流中。
TinyXML被設(shè)計(jì)得容易快速上手。它只有兩個(gè)頭文件和四個(gè)cpp文件。只需要把它們簡(jiǎn)單地加到你的項(xiàng)目中就行了。有一個(gè)例子文件——xmltest.cpp來引導(dǎo)你該怎么做。
TinyXML以Zlib許可來發(fā)布,所以你可以在開源或者商業(yè)軟件中使用它。許可證更具體的描述在每個(gè)源代碼文件的頂部可以找到。
TinyXML在保證正確和恰當(dāng)?shù)腦ML輸出的基礎(chǔ)上嘗試成為一個(gè)靈活的解析器。TinyXML可以在任何合理的C++適用系統(tǒng)上編譯。它不依賴于
異?;蛘哌\(yùn)行時(shí)類型信息,有沒有STL支持都可以編譯。TinyXML完全支持UTF-8編碼和前64k個(gè)字符實(shí)體(<i>譯注:如果你不明
白這句譯文,可能你需要了解一下Unicode編碼</i>)。
它無法做些什么
TinyXML不解析不使用DTDs(文檔類型定義)或者XSLs(可擴(kuò)展樣式表語言)。有其它解析器(到www.sourceforge.org
搜索一下XML)具有更加全面的特性,但它們也就更大,需要花更長(zhǎng)的時(shí)間來建立你的項(xiàng)目,有更陡的學(xué)習(xí)曲線,而且經(jīng)常有一個(gè)更嚴(yán)格的許可協(xié)議。如果你是用
于瀏覽器或者有更復(fù)雜的XML需要,那么TinyXML不適合你。
下面的DTD語法在TinyXML里是不做解析的:
<!DOCTYPE Archiv [
<!ELEMENT Comment (#PCDATA)>
]>
因?yàn)門inyXML把它看成是一個(gè)帶著非法嵌入!ELEMENT結(jié)點(diǎn)的!DOCTYPE結(jié)點(diǎn)?;蛟S這在將來會(huì)得到支持。
指南
有耐性些,這是一份能很好地指導(dǎo)你怎么開始的指南,它(非常短小精悍)值得你花時(shí)間完整地讀上一遍。
代碼狀況
TinyXML是成熟且經(jīng)過測(cè)試的代碼,非常健壯。如果你發(fā)現(xiàn)了漏洞,請(qǐng)?zhí)峤宦┒磮?bào)告到sourcefore網(wǎng)站上 (www.sourceforge.net/projects/tinyxml)。 我們會(huì)盡快修正。
有些地方可以讓你得到提高,如果你對(duì)TinyXML的工作感興趣的話可以上sourceforge查找一下。
相關(guān)項(xiàng)目
你也許會(huì)覺得TinyXML很有用!(簡(jiǎn)介由項(xiàng)目提供)
特性
使用STL
TinyXML可以被編譯成使用或不使用STL。如果使用STL,TinyXML會(huì)使用std::string類,而且完全支持
std::istream,std::ostream,operator<<和operator>>。許多API方法都有
‘const char*’和’const std::string&’兩個(gè)版本。
如果被編譯成不使用STL,則任何STL都不會(huì)被包含。所有string類都由TinyXML它自己實(shí)現(xiàn)。所有API方法都只提供’const char*’傳入?yún)?shù)。
使用運(yùn)行時(shí)定義:
TIXML_USE_STL
來編譯成不同的版本。這可以作為參數(shù)傳給編譯器或者在“tinyxml.h”文件的第一行進(jìn)行設(shè)置。
注意:如果在Linux上編譯測(cè)試代碼,設(shè)置環(huán)境變量TINYXML_USE_STL=YES/NO可以控制STL的編譯。而在Windows上,
項(xiàng)目文件提供了STL和非STL兩種目標(biāo)文件。在你的項(xiàng)目中,在tinyxml.h的第一行添加"#define
TIXML_USE_STL"應(yīng)該是最簡(jiǎn)單的。
UTF-8
TinyXML支持UTF-8,所以可以處理任何語言的XML文件,而且TinyXML也支持“legacy模式”——一種在支持UTF-8之前使用的編碼方式,可能最好的解釋是“擴(kuò)展的ascii”。
正常情況下,TinyXML會(huì)檢測(cè)出正確的編碼并使用它,然而,通過設(shè)置頭文件中的TIXML_DEFAULT_ENCODING值,TinyXML可以被強(qiáng)制成總是使用某一種編碼。
除非以下情況發(fā)生,否則TinyXML會(huì)默認(rèn)使用Legacy模式:
- 如果文件或者數(shù)據(jù)流以非標(biāo)準(zhǔn)但普遍的"UTF-8引導(dǎo)字節(jié)" (0xef 0xbb 0xbf)開始,TinyXML會(huì)以UTF-8的方式來讀取它。
- 如果包含有encoding="UTF-8"的聲明被讀取,那么TinyXML會(huì)以UTF-8的方式來讀取它。
- 如果讀取到?jīng)]有指定編碼方式的聲明,那么TinyXML會(huì)以UTF-8的方式來讀取它。
- 如果包含有encoding=“其它編碼”的聲明被讀取,那么TinyXML會(huì)以Legacy模式來讀取它。在Legacy模式下,TinyXML會(huì)像以前那樣工作,雖然已經(jīng)不是很清楚這種模式是如何工作的了,但舊的內(nèi)容還得保持能夠運(yùn)行。
- 除了上面提到的情況,TinyXML會(huì)默認(rèn)運(yùn)行在Legacy模式下。
如果編碼設(shè)置錯(cuò)誤或者檢測(cè)到錯(cuò)誤會(huì)發(fā)生什么事呢?TinyXML會(huì)嘗試跳過這些看似不正確的編碼,你可能會(huì)得到一些奇怪的結(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。
對(duì)于使用英文XML的英語用戶來說,UTF-8跟low-ASCII是一樣的。你不需要知道UTF-8或者一點(diǎn)也不需要修改你的代碼。你可以把UTF-8當(dāng)作是ASCII的超集。
UTF-8并不是一種雙字節(jié)格式,但它是一種標(biāo)準(zhǔn)的Unicode編碼!TinyXML當(dāng)前不使用或者直接支持wchar,TCHAR,或者微軟的
_UNICODE。"Unicode"這個(gè)術(shù)語被普遍地認(rèn)為指的是UTF-16(一種unicode的寬字節(jié)編碼)是不適當(dāng)?shù)?,這是混淆的來源。
對(duì)于“high-ascii”語言來說——幾乎所有非英語語言,只要XML被編碼成UTF-8,
TinyXML就能夠處理。說起來可能有點(diǎn)微妙,比較舊的程序和操作系統(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就無法讀取了。但是一個(gè)好的文本編輯器可以導(dǎo)入SHIFT-JIS的文本然后保存成UTF-8編碼格式的。
Skew.org link上關(guān)于轉(zhuǎn)換編碼的話題做得很好。
測(cè)試文件“utf8test.xml”包含了英文、西班牙文、俄文和簡(jiǎn)體中文(希望它們都能夠被正確地轉(zhuǎn)化)。“utf8test.gif”文件是
從IE上截取的XML文件快照。請(qǐng)注意如果你的系統(tǒng)上沒有正確的字體(簡(jiǎn)體中文或者俄文),那么即使你正確地解析了也看不到與GIF文件上一樣的輸出。同
時(shí)要注意在一個(gè)西方編碼的控制臺(tái)上(至少我的Windows機(jī)器是這樣),Print()或者printf()也無法正確地顯示這個(gè)文件,這不關(guān)
TinyXML的事——這只是操作系統(tǒng)的問題。TinyXML沒有丟掉或者損壞數(shù)據(jù),只是控制臺(tái)無法顯示UTF-8而已。
實(shí)體
TinyXML認(rèn)得預(yù)定義的特殊“字符實(shí)體”,即:
& &
< <
> >
" "
' ‘
這些在XML文檔讀取時(shí)都會(huì)被辨認(rèn)出來,并會(huì)被轉(zhuǎn)化成等價(jià)的UTF-8字符。比如下面的XML文本:
Far & Away
從TiXmlText 對(duì)象查詢出來時(shí)會(huì)變成"Far & Away"這樣的值,而寫回XML流/文件時(shí)會(huì)以“&”的方式寫回。老版本的TinyXML“保留”了字符實(shí)體,而在新版本中它們會(huì)被轉(zhuǎn)化成字符串。
另外,所有字符都可以用它的Unicode編碼數(shù)字來指定, " "和" "都表示不可分的空格字符。
打印
TinyXML有幾種不同的方式來打印輸出,當(dāng)然它們各有各的優(yōu)缺點(diǎn)。
- Print( FILE* ):輸出到一個(gè)標(biāo)準(zhǔn)C流中,包括所有的C文件和標(biāo)準(zhǔn)輸出。
- "相當(dāng)漂亮的打印", 但你沒法控制打印選項(xiàng)。
- 輸出數(shù)據(jù)直接寫到FILE對(duì)象中,所以TinyXML代碼沒有內(nèi)存負(fù)擔(dān)。
- 被Print()和SaveFile()調(diào)用。
- operator<<:輸出到一個(gè)c++流中。
- 與C++ iostreams集成在一起。
- 在"network printing"模式下輸出沒有換行符,這對(duì)于網(wǎng)絡(luò)傳輸和C++對(duì)象之間的XML交換有好處,但人很難閱讀。
- TiXmlPrinter:輸出到一個(gè)std::string或者內(nèi)存緩沖區(qū)中。
- API還不是很簡(jiǎn)練。
- 將來會(huì)增加打印選項(xiàng)。
- 在將來的版本中可能有些細(xì)微的變化,因?yàn)樗鼤?huì)被改進(jìn)和擴(kuò)展。
流
設(shè)置了TIXML_USE_STL,TinyXML就能支持C++流(operator <<,>>)和C(FILE*)流。但它們之間有些差異你需要知道:
C風(fēng)格輸出:
- 基于FILE*
- 用Print()和SaveFile()方法
生成具有很多空格的格式化過的輸出,這是為了盡可能讓人看得明白。它們非???,而且能夠容忍XML文檔中的格式錯(cuò)誤。例如一個(gè)XML文檔包含兩個(gè)根元素和兩個(gè)聲明仍然能被打印出來。
C風(fēng)格輸入:
- 基于FILE*
- 用Parse()和LoadFile()方法
速度快,容錯(cuò)性好。當(dāng)你不需要C++流時(shí)就可以使用它。
C++風(fēng)格輸出:
- 基于std::ostream
- operator<<
生成壓縮過的輸出,目的是為了便于網(wǎng)絡(luò)傳輸而不是為了可讀性。它可能有些慢(可能不會(huì)),這主要跟你系統(tǒng)上ostream類的實(shí)現(xiàn)有關(guān)。無法容忍格式錯(cuò)誤的XML:此文檔只能包含一個(gè)根元素。另外根級(jí)別的元素?zé)o法以流形式輸出。
C++風(fēng)格輸入:
- 基于std::istream
- operator>>
從流中讀取XML使其可用于網(wǎng)絡(luò)傳輸。通過些小技巧,它知道當(dāng)XML文檔讀取完畢時(shí),流后面的就一定是其它數(shù)據(jù)了。TinyXML總假定當(dāng)它讀取到
根結(jié)點(diǎn)后XML數(shù)據(jù)就結(jié)束了。換句話說,那些具有不止一個(gè)根元素的文檔是無法被正確讀取的。另外還要注意由于STL的實(shí)現(xiàn)和TinyXML的限
制,operator>>會(huì)比Parse慢一些。
空格
對(duì)是保留還是壓縮空格這一問題人們還沒達(dá)成共識(shí)。舉個(gè)例子,假設(shè)‘_’代表一個(gè)空格,對(duì)于"Hello____world",HTML和某些XML
解析器會(huì)解釋成"Hello_world",它們壓縮掉了一些空格。而有些XML解析器卻不會(huì)這樣,它們會(huì)保留空格,于是就是
“Hello____world”(記住_表示一個(gè)空格)。其它的還建議__Hello___world__應(yīng)該變成Hello___world 。
這是一個(gè)解決得不能讓我滿意的問題。TinyXML一開始就兩種方式都支持。調(diào)用TiXmlBase::SetCondenseWhiteSpace( bool )來設(shè)置你想要的結(jié)果,默認(rèn)是壓縮掉多余的空格。
如果想要改變默認(rèn)行為,你應(yīng)該在解析任何XML數(shù)據(jù)之前調(diào)用TiXmlBase::SetCondenseWhiteSpace( bool ) ,而且我不建議設(shè)置之后再去改動(dòng)它。
句柄
想要健壯地讀取一個(gè)XML文檔,檢查方法調(diào)用后的返回值是否為null是很重要的。一種安全的檢錯(cuò)實(shí)現(xiàn)可能會(huì)產(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.
用句柄的話就不會(huì)這么冗長(zhǎng)了,使用TiXmlHandle類,前面的代碼就會(huì)變成這樣:
TiXmlHandle docHandle( &document );
TiXmlElement* child2 = docHandle.FirstChild( "Document" ).FirstChild( "Element" ).Child( "Child", 1 ).ToElement();
if ( child2 )
{
// do something useful
這處理起來容易多了。 查閱TiXmlHandle可以得到更多的信息。
行列追蹤
對(duì)于某些應(yīng)用程序來說,能夠追蹤節(jié)點(diǎn)和屬性在它們?cè)次募械脑嘉恢檬呛苤匾?。另外,知道解析錯(cuò)誤在源文件中的發(fā)生位置可以節(jié)省大量時(shí)間。
TinyXML能夠追蹤所有結(jié)點(diǎn)和屬性在文本文件中的行列原始位置。TiXmlBase::Row() 和
TiXmlBase::Column()
方法返回結(jié)點(diǎn)在源文件中的原始位置。正確的制表符號(hào)可以經(jīng)由TiXmlDocument::SetTabSize() 來配置。
使用與安裝
編譯與運(yùn)行xmltest:
提供了一個(gè)Linux Makefile和一個(gè)Windows Visual C++ .dsw 文件。只需要簡(jiǎn)單地編譯和運(yùn)行,它就會(huì)在你的磁盤上生成demotest.xml文件并在屏幕上輸出。它還嘗試用不同的方法遍歷DOM并打印出結(jié)點(diǎn)數(shù)。
那個(gè)Linux makefile很通用,可以運(yùn)行在很多系統(tǒng)上——它目前已經(jīng)在mingw和MacOSX上測(cè)試過。你不需要運(yùn)行 ‘make depend’,因?yàn)槟切┮蕾囮P(guān)系已經(jīng)硬編碼在文件里了。
用于VC6的Windows項(xiàng)目文件
- tinyxml: tinyxml 庫,非STL
- tinyxmlSTL: tinyxml 庫,STL
- tinyXmlTest: 用于測(cè)試的應(yīng)用程序,非STL
- tinyXmlTestSTL: 用于測(cè)試的應(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
添加到你的項(xiàng)目和makefile中。就這么簡(jiǎn)單,它可以在任何合理的C++適用系統(tǒng)上編譯。不需要為TinyXML打開異?;蛘哌\(yùn)行時(shí)類型信息支持。
TinyXML怎么工作
舉個(gè)例子可能是最好的辦法,理解一下:
<?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>
它稱不上是一個(gè)To Do列表,但它已經(jīng)足夠了。像下面這樣讀取并解析這個(gè)文件(叫“demo.xml”)你就能創(chuàng)建一個(gè)文檔:
TiXmlDocument doc( "demo.xml" );
doc.LoadFile();
現(xiàn)在它準(zhǔn)備好了,讓我們看看其中的某些行和它們?cè)趺磁cDOM聯(lián)系起來。
<?xml version="1.0" standalone=no>
第一行是一個(gè)聲明,它會(huì)轉(zhuǎn)化成TiXmlDeclaration 類,同時(shí)也是文檔結(jié)點(diǎn)的第一個(gè)子結(jié)點(diǎn)。
這是TinyXML唯一能夠解析的指令/特殊標(biāo)簽。一般來說指令標(biāo)簽會(huì)保存在TiXmlUnknown 以保證在它保存回磁盤時(shí)不會(huì)丟失這些命令。
<!– Our to do list data –>
這是一個(gè)注釋,會(huì)成為一個(gè)TiXmlComment對(duì)象。
<ToDo>
"ToDo"標(biāo)簽定義了一個(gè)TiXmlElement 對(duì)象。它沒有任何屬性,但包含另外的兩個(gè)元素。
<Item priority="1">
生成另一個(gè)TiXmlElement對(duì)象,它是“ToDo”元素的子結(jié)點(diǎn)。此元素有一個(gè)名為“priority”和值為“1”的屬性。
Go to the
TiXmlText ,這是一個(gè)葉子結(jié)點(diǎn),它不能再包含其它結(jié)點(diǎn),是"Item" TiXmlElement的子結(jié)點(diǎn)。
<bold>
另一個(gè)TiXmlElement, 這也是“Item”元素的子結(jié)點(diǎn)。
等等
最后,看看整個(gè)對(duì)象樹:
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. 決不能虛報(bào)軟件的來源;你決不能聲稱是你是軟件的第一作者。如果你在某個(gè)產(chǎn)品中使用了這個(gè)軟件,那么在產(chǎn)品文檔中加入一個(gè)致謝辭我們會(huì)很感激,但這并非必要。
2. 修改了源版本就應(yīng)該清楚地標(biāo)記出來,決不能虛報(bà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)系人,還有簡(jiǎn)史
非常感謝給我們建議,漏洞報(bào)告,意見和鼓勵(lì)的所有人。它們很有用,并且使得這個(gè)項(xiàng)目變得有趣。特別感謝那些捐助者,是他們讓這個(gè)網(wǎng)站頁面生機(jī)勃勃。
有很多人發(fā)來漏洞報(bào)告和意見,與其在這里一一列出來不如我們?cè)囍阉鼈儗懙?#8220;changes.txt”文件中加以贊揚(yáng)。
TinyXML的原作者是Lee Thomason(文檔中還經(jīng)常出現(xiàn)“我”這個(gè)詞) 。在Yves Berquin,Andrew Ellerton,和tinyXml社區(qū)的幫助下,Lee查閱修改和發(fā)布新版本。
我們會(huì)很感激你的建議,還有我們想知道你是否在使用TinyXML。希望你喜歡它并覺得它很有用。請(qǐng)郵寄問題,評(píng)論,漏洞報(bào)告給我們,或者你也可登錄網(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也支持線程的概念,但是在一個(gè)進(jìn)程(process)中只允許有一個(gè)線程,這樣多線程就意味著多進(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)下,啟動(dòng)一個(gè)新的進(jìn)程必須分配給它獨(dú)立的地址空
間,建立眾多的數(shù)據(jù)表來維護(hù)它的代碼段、堆棧段和數(shù)據(jù)段,這是一種"昂貴"的多任務(wù)工作方式。而運(yùn)行于一個(gè)進(jìn)程中的多個(gè)線程,它們彼此之間使用相同的地址
空間,共享大部分?jǐn)?shù)據(jù),啟動(dòng)一個(gè)線程所花費(fèi)的空間遠(yuǎn)遠(yuǎn)小于啟動(dòng)一個(gè)進(jìn)程所花費(fèi)的空間,而且,線程間彼此切換所需的時(shí)間也遠(yuǎn)遠(yuǎn)小于進(jìn)程間切換所需要的時(shí)間。
據(jù)統(tǒng)計(jì),總的說來,一個(gè)進(jìn)程的開銷大約是一個(gè)線程開銷的30倍左右,當(dāng)然,在具體的系統(tǒng)上,這個(gè)數(shù)據(jù)可能會(huì)有較大的區(qū)別。
使用多線程
的理由之二是線程間方便的通信機(jī)制。對(duì)不同進(jìn)程來說,它們具有獨(dú)立的數(shù)據(jù)空間,要進(jìn)行數(shù)據(jù)的傳遞只能通過通信的方式進(jìn)行,這種方式不僅費(fèi)時(shí),而且很不方
便。線程則不然,由于同一進(jìn)程下的線程之間共享數(shù)據(jù)空間,所以一個(gè)線程的數(shù)據(jù)可以直接為其它線程所用,這不僅快捷,而且方便。當(dāng)然,數(shù)據(jù)的共享也帶來其他
一些問題,有的變量不能同時(shí)被兩個(gè)線程所修改,有的子程序中聲明為static的數(shù)據(jù)更有可能給多線程程序帶來災(zāi)難性的打擊,這些正是編寫多線程程序時(shí)最
需要注意的地方。
除了以上所說的優(yōu)點(diǎn)外,不和進(jìn)程比較,多線程程序作為一種多任務(wù)、并發(fā)的工作方式,當(dāng)然有以下的優(yōu)點(diǎn):
1) 提高應(yīng)用程序響應(yīng)。這對(duì)圖形界面的程序尤其有意義,當(dāng)一個(gè)操作耗時(shí)很長(zhǎng)時(shí),整個(gè)系統(tǒng)都會(huì)等待這個(gè)操作,此時(shí)程序不會(huì)響應(yīng)鍵盤、鼠標(biāo)、菜單的操作,而使用多線程技術(shù),將耗時(shí)長(zhǎng)的操作(time consuming)置于一個(gè)新的線程,可以避免這種尷尬的情況。
2) 使多CPU系統(tǒng)更加有效。操作系統(tǒng)會(huì)保證當(dāng)線程數(shù)不大于CPU數(shù)目時(shí),不同的線程運(yùn)行于不同的CPU上。
3) 改善程序結(jié)構(gòu)。一個(gè)既長(zhǎng)又復(fù)雜的進(jìn)程可以考慮分為多個(gè)線程,成為幾個(gè)獨(dú)立或半獨(dú)立的運(yùn)行部分,這樣的程序會(huì)利于理解和修改。
下面我們先來嘗試編寫一個(gè)簡(jiǎn)單的多線程程序。
簡(jiǎn)單的多線程編程
Linux系統(tǒng)下的多線程遵循POSIX線程接口,稱為pthread。編寫Linux下的多線程程序,需要使用頭文件pthread.h,連接時(shí)需要
使用庫libpthread.a。順便說一下,Linux下pthread的實(shí)現(xiàn)是通過系統(tǒng)調(diào)用clone()來實(shí)現(xiàn)的。clone()是Linux所特
有的系統(tǒng)調(diào)用,它的使用方式類似fork,關(guān)于clone()的詳細(xì)情況,有興趣的讀者可以去查看有關(guān)文檔說明。下面我們展示一個(gè)最簡(jiǎn)單的多線程程序
pthread_create.c。
一個(gè)重要的線程創(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,否則返回錯(cuò)誤的編號(hào) 形式參數(shù): pthread_t *restrict tidp 要?jiǎng)?chuàng)建的線程的線程id指針 const pthread_attr_t *restrict attr 創(chuàng)建線程時(shí)的線程屬性 void* (start_rtn)(void) 返回值是void類型的指針函數(shù) void *restrict arg start_rtn的行參 例程1: 功能:創(chuàng)建一個(gè)簡(jiǎn)單的線程 程序名稱: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
因?yàn)閜thread的庫不是linux系統(tǒng)的庫,所以在進(jìn)行編譯的時(shí)候要加上-lpthread,否則編譯不過,會(huì)出現(xiàn)下面錯(cuò)誤 thread_test.c: 在函數(shù) ‘create’ 中: thread_test.c:7: 警告: 在有返回值的函數(shù)中,程序流程到達(dá)函數(shù)尾 /tmp/ccOBJmuD.o: In function `main':thread_test.c:(.text+0x4f):對(duì)‘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. ....
兩個(gè)線程交替執(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é): 可以看出來,我們?cè)趍ain函數(shù)中傳遞的整行指針,傳遞到我們新建的線程函數(shù)中。 在上面的例子可以看出來我們向新的線程傳入了另一個(gè)線程的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ù)中的一個(gè)結(jié)構(gòu)體傳入了新建的線程中。 線程包含了標(biāo)識(shí)進(jìn)程內(nèi)執(zhí)行環(huán)境必須的信息。他集成了進(jìn)程中的所有信息都是對(duì)線程進(jìn)行共享的,包括文本程序、程序的全局內(nèi)存和堆內(nèi)存、棧以及文件描述符。
例程5: 程序目的:驗(yàn)證新建立的線程可以共享進(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é): 可以看出來,我們?cè)谥骶€程更改了我們的全局變量a的值的時(shí)候,我們新建立的線程則打印出來了改變的值,可以看出可以訪問線程所在進(jìn)程中的數(shù)據(jù)信息。 2、線程的終止
如果進(jìn)程中任何一個(gè)線程中調(diào)用exit,_Exit,或者是_exit,那么整個(gè)進(jìn)程就會(huì)終止, 與此類似,如果信號(hào)的默認(rèn)的動(dòng)作是終止進(jìn)程,那么,把該信號(hào)發(fā)送到線程會(huì)終止進(jìn)程。 線程的正常退出的方式: (1) 線程只是從啟動(dòng)例程中返回,返回值是線程中的退出碼 (2) 線程可以被另一個(gè)進(jìn)程進(jìn)行終止 (3) 線程自己調(diào)用pthread_exit函數(shù) 兩個(gè)重要的函數(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,否則為錯(cuò)誤編碼*/
例程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ù)值,還可以返回一個(gè)復(fù)雜的數(shù)據(jù)結(jié)構(gòu)。
例程7 程序目的:線程結(jié)束返回一個(gè)復(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)要是在這個(gè)數(shù)據(jù)要返回的結(jié)構(gòu)沒有釋放的時(shí)候應(yīng)用, 如果數(shù)據(jù)結(jié)構(gòu)已經(jīng)發(fā)生變化,那返回的就不會(huì)是我們所需要的,而是臟數(shù)據(jù) 3、線程標(biāo)識(shí)
函數(shù)原型: #include <pthread.h> pthread_t pthread_self(void);
pid_t getpid(void); getpid()用來取得目前進(jìn)程的進(jìn)程識(shí)別碼,函數(shù)說明
例程8 程序目的:實(shí)現(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 其實(shí)字節(jié)對(duì)齊的細(xì)節(jié)和具體編譯器實(shí)現(xiàn)相關(guān),但一般而言,滿足三個(gè)準(zhǔn)則: 1) 結(jié)構(gòu)體變量的首地址能夠被其最寬基本類型成員的大小所整除;
2) 結(jié)構(gòu)體每個(gè)成員相對(duì)于結(jié)構(gòu)體首地址的偏移量都是成員大小的整數(shù)倍,如有需要編譯器會(huì)在成員之間加上填充字節(jié);例如上面第二個(gè)結(jié)構(gòu)體變量的地址空間。
3) 結(jié)構(gòu)體的總大小為結(jié)構(gòu)體最寬基本類型成員大小的整數(shù)倍,如有需要編譯器會(huì)在最末一個(gè)成員之后加上填充字節(jié)。例如上面第一個(gè)結(jié)構(gòu)體變量。
一.什么是字節(jié)對(duì)齊,為什么要對(duì)齊?
現(xiàn)代計(jì)算機(jī)中內(nèi)存空間都是按照byte劃分的,從理論上講似乎對(duì)任何類型的變量的訪問可以從任何地址開始,但實(shí)際情況是在訪問特定類型變量的時(shí)候經(jīng)常在特
定的內(nèi)存地址訪問,這就需要各種類型數(shù)據(jù)按照一定的規(guī)則在空間上排列,而不是順序的一個(gè)接一個(gè)的排放,這就是對(duì)齊。
對(duì)齊的作用和原因:各個(gè)硬件平臺(tái)對(duì)存儲(chǔ)空間的處理上有很大的不同。一些平臺(tái)對(duì)某些特定類型的數(shù)據(jù)只能從某些特定地址開始存取。比如有些架構(gòu)的CPU在訪問
一個(gè)沒有進(jìn)行對(duì)齊的變量的時(shí)候會(huì)發(fā)生錯(cuò)誤,那么在這種架構(gòu)下編程必須保證字節(jié)對(duì)齊.其他平臺(tái)可能沒有這種情況,但是最常見的是如果不按照適合其平臺(tái)要求對(duì)
數(shù)據(jù)存放進(jìn)行對(duì)齊,會(huì)在存取效率上帶來損失。比如有些平臺(tái)每次讀都是從偶地址開始,如果一個(gè)int型(假設(shè)為32位系統(tǒng))如果存放在偶地址開始的地方,那
么一個(gè)讀周期就可以讀出這32bit,而如果存放在奇地址開始的地方,就需要2個(gè)讀周期,并對(duì)兩次讀出的結(jié)果的高低字節(jié)進(jìn)行拼湊才能得到該32bit數(shù)
據(jù)。顯然在讀取效率上下降很多。
二.字節(jié)對(duì)齊對(duì)程序的影響:
先讓我們看幾個(gè)例子吧(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ù)類型的長(zhǎng)度如下: char:1(有符號(hào)無符號(hào)同) short:2(有符號(hào)無符號(hào)同) int:4(有符號(hào)無符號(hào)同) long:4(有符號(hào)無符號(hào)同) float:4 double:8 那么上面兩個(gè)結(jié)構(gòu)大小如何呢? 結(jié)果是: sizeof(strcut A)值為8 sizeof(struct B)的值卻是12
結(jié)構(gòu)體A中包含了4字節(jié)長(zhǎng)度的int一個(gè),1字節(jié)長(zhǎng)度的char一個(gè)和2字節(jié)長(zhǎng)度的short型數(shù)據(jù)一個(gè),B也一樣;按理說A,B大小應(yīng)該都是7字節(jié)。 之所以出現(xiàn)上面的結(jié)果是因?yàn)榫幾g器要對(duì)數(shù)據(jù)成員在空間上進(jìn)行對(duì)齊。上面是按照編譯器的默認(rèn)設(shè)置進(jìn)行對(duì)齊的結(jié)果,那么我們是不是可以改變編譯器的這種默認(rèn)對(duì)齊設(shè)置呢,當(dāng)然可以.例如: #pragma pack (2) /*指定按2字節(jié)對(duì)齊*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/ sizeof(struct C)值是8。 修改對(duì)齊值為1: #pragma pack (1) /*指定按1字節(jié)對(duì)齊*/ struct D { char b; int a; short c; }; #pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/ sizeof(struct D)值為7。 后面我們?cè)僦v解#pragma pack()的作用.
三.編譯器是按照什么樣的原則進(jìn)行對(duì)齊的?
先讓我們看四個(gè)重要的基本概念: 1.數(shù)據(jù)類型自身的對(duì)齊值: 對(duì)于char型數(shù)據(jù),其自身對(duì)齊值為1,對(duì)于short型為2,對(duì)于int,float,double類型,其自身對(duì)齊值為4,單位字節(jié)。 2.結(jié)構(gòu)體或者類的自身對(duì)齊值:其成員中自身對(duì)齊值最大的那個(gè)值。 3.指定對(duì)齊值:#pragma pack (value)時(shí)的指定對(duì)齊值value。 4.數(shù)據(jù)成員、結(jié)構(gòu)體和類的有效對(duì)齊值:自身對(duì)齊值和指定對(duì)齊值中小的那個(gè)值。 有
了這些值,我們就可以很方便的來討論具體數(shù)據(jù)結(jié)構(gòu)的成員和其自身的對(duì)齊方式。有效對(duì)齊值N是最終用來決定數(shù)據(jù)存放地址方式的值,最重要。有效對(duì)齊N,就是
表示“對(duì)齊在N上”,也就是說該數(shù)據(jù)的"存放起始地址%N=0".而數(shù)據(jù)結(jié)構(gòu)中的數(shù)據(jù)變量都是按定義的先后順序來排放的。第一個(gè)數(shù)據(jù)變量的起始地址就是數(shù)
據(jù)結(jié)構(gòu)的起始地址。結(jié)構(gòu)體的成員變量要對(duì)齊排放,結(jié)構(gòu)體本身也要根據(jù)自身的有效對(duì)齊值圓整(就是結(jié)構(gòu)體成員變量占用總長(zhǎng)度需要是對(duì)結(jié)構(gòu)體有效對(duì)齊值的整數(shù)
倍,結(jié)合下面例子理解)。這樣就不能理解上面的幾個(gè)例子的值了。 例子分析: 分析例子B; struct B { char b; int a; short c; }; 假
設(shè)B從地址空間0x0000開始排放。該例子中沒有定義指定對(duì)齊值,在筆者環(huán)境下,該值默認(rèn)為4。第一個(gè)成員變量b的自身對(duì)齊值是1,比指定或者默認(rèn)指定
對(duì)齊值4小,所以其有效對(duì)齊值為1,所以其存放地址0x0000符合0x0000%1=0.第二個(gè)成員變量a,其自身對(duì)齊值為4,所以有效對(duì)齊值也為4,
所以只能存放在起始地址為0x0004到0x0007這四個(gè)連續(xù)的字節(jié)空間中,復(fù)核0x0004%4=0,且緊靠第一個(gè)變量。第三個(gè)變量c,自身對(duì)齊值為
2,所以有效對(duì)齊值也是2,可以存放在0x0008到0x0009這兩個(gè)字節(jié)空間中,符合0x0008%2=0。所以從0x0000到0x0009存放的
都是B內(nèi)容。再看數(shù)據(jù)結(jié)構(gòu)B的自身對(duì)齊值為其變量中最大對(duì)齊值(這里是b)所以就是4,所以結(jié)構(gòu)體的有效對(duì)齊值也是4。根據(jù)結(jié)構(gòu)體圓整的要求,
0x0009到0x0000=10字節(jié),(10+2)%4=0。所以0x0000A到0x000B也為結(jié)構(gòu)體B所占用。故B從0x0000到0x000B
共有12個(gè)字節(jié),sizeof(struct B)=12;其實(shí)如果就這一個(gè)就來說它已將滿足字節(jié)對(duì)齊了,
因?yàn)樗钠鹗嫉刂肥?,因此肯定是對(duì)齊的,之所以在后面補(bǔ)充2個(gè)字節(jié),是因?yàn)榫幾g器為了實(shí)現(xiàn)結(jié)構(gòu)數(shù)組的存取效率,試想如果我們定義了一個(gè)結(jié)構(gòu)B的數(shù)組,那
么第一個(gè)結(jié)構(gòu)起始地址是0沒有問題,但是第二個(gè)結(jié)構(gòu)呢?按照數(shù)組的定義,數(shù)組中所有元素都是緊挨著的,如果我們不把結(jié)構(gòu)的大小補(bǔ)充為4的整數(shù)倍,那么下一
個(gè)結(jié)構(gòu)的起始地址將是0x0000A,這顯然不能滿足結(jié)構(gòu)的地址對(duì)齊了,因此我們要把結(jié)構(gòu)補(bǔ)充成有效對(duì)齊大小的整數(shù)倍.其實(shí)諸如:對(duì)于char型數(shù)據(jù),其
自身對(duì)齊值為1,對(duì)于short型為2,對(duì)于int,float,double類型,其自身對(duì)齊值為4,這些已有類型的自身對(duì)齊值也是基于數(shù)組考慮的,只
是因?yàn)檫@些類型的長(zhǎng)度已知了,所以他們的自身對(duì)齊值也就已知了. 同理,分析上面例子C: #pragma pack (2) /*指定按2字節(jié)對(duì)齊*/ struct C { char b; int a; short c; }; #pragma pack () /*取消指定對(duì)齊,恢復(fù)缺省對(duì)齊*/ 第
一個(gè)變量b的自身對(duì)齊值為1,指定對(duì)齊值為2,所以,其有效對(duì)齊值為1,假設(shè)C從0x0000開始,那么b存放在0x0000,符合0x0000%1=
0;第二個(gè)變量,自身對(duì)齊值為4,指定對(duì)齊值為2,所以有效對(duì)齊值為2,所以順序存放在0x0002、0x0003、0x0004、0x0005四個(gè)連續(xù)
字節(jié)中,符合0x0002%2=0。第三個(gè)變量c的自身對(duì)齊值為2,所以有效對(duì)齊值為2,順序存放 在0x0006、0x0007中,符合
0x0006%2=0。所以從0x0000到0x00007共八字節(jié)存放的是C的變量。又C的自身對(duì)齊值為4,所以C的有效對(duì)齊值為2。又8%2=0,C
只占用0x0000到0x0007的八個(gè)字節(jié)。所以sizeof(struct C)=8.
四.如何修改編譯器的默認(rèn)對(duì)齊值?
1.在VC IDE中,可以這樣修改:[Project]|[Settings],c/c++選項(xiàng)卡Category的Code Generation選項(xiàng)的Struct Member Alignment中修改,默認(rèn)是8字節(jié)。 2.在編碼時(shí),可以這樣動(dòng)態(tài)修改:#pragma pack .注意:是pragma而不是progma.
五.針對(duì)字節(jié)對(duì)齊,我們?cè)诰幊讨腥绾慰紤]?
如果在編程的時(shí)候要考慮節(jié)約空間的話,那么我們只需要假定結(jié)構(gòu)的首地址是0,然后各個(gè)變量按照上面的原則進(jìn)行排列即可,基本的原則就是把結(jié)構(gòu)中的變量按照
類型大小從小到大聲明,盡量減少中間的填補(bǔ)空間.還有一種就是為了以空間換取時(shí)間的效率,我們顯示的進(jìn)行填補(bǔ)空間進(jìn)行對(duì)齊,比如:有一種使用空間換時(shí)間做
法是顯式的插入reserved成員: struct A{ char a; char reserved[3];//使用空間換時(shí)間 int b; }
reserved成員對(duì)我們的程序沒有什么意義,它只是起到填補(bǔ)空間以達(dá)到字節(jié)對(duì)齊的目的,當(dāng)然即使不加這個(gè)成員通常編譯器也會(huì)給我們自動(dòng)填補(bǔ)對(duì)齊,我們自己加上它只是起到顯式的提醒作用.
六.字節(jié)對(duì)齊可能帶來的隱患:
代碼中關(guān)于對(duì)齊的隱患,很多是隱式的。比如在強(qiáng)制類型轉(zhuǎn)換的時(shí)候。例如: 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型變量,顯然不符合對(duì)齊的規(guī)定。 在x86上,類似的操作只會(huì)影響效率,但是在MIPS或者sparc上,可能就是一個(gè)error,因?yàn)樗鼈円蟊仨氉止?jié)對(duì)齊.
七.如何查找與字節(jié)對(duì)齊方面的問題:
如果出現(xiàn)對(duì)齊或者賦值問題首先查看 1. 編譯器的big little端設(shè)置 2. 看這種體系本身是否支持非對(duì)齊訪問 3. 如果支持看設(shè)置了對(duì)齊與否,如果沒有則看訪問時(shí)需要加某些特殊的修飾來標(biāo)志其特殊訪問操作。
八.相關(guān)文章:轉(zhuǎn)自http://blog.csdn.net/goodluckyxl/archive/2005/10/17/506827.aspx
ARM下的對(duì)齊處理 from DUI0067D_ADS1_2_CompLib
3.13 type qulifiers
有部分摘自ARM編譯器文檔對(duì)齊部分
對(duì)齊的使用: 1.__align(num) 這個(gè)用于修改最高級(jí)別對(duì)象的字節(jié)邊界。在匯編中使用LDRD或者STRD時(shí) 就要用到此命令__align(8)進(jìn)行修飾限制。來保證數(shù)據(jù)對(duì)象是相應(yīng)對(duì)齊。 這個(gè)修飾對(duì)象的命令最大是8個(gè)字節(jié)限制,可以讓2字節(jié)的對(duì)象進(jìn)行4字節(jié) 對(duì)齊,但是不能讓4字節(jié)的對(duì)象2字節(jié)對(duì)齊。 __align是存儲(chǔ)類修改,他只修飾最高級(jí)類型對(duì)象不能用于結(jié)構(gòu)或者函數(shù)對(duì)象。 2.__packed __packed是進(jìn)行一字節(jié)對(duì)齊 1.不能對(duì)packed的對(duì)象進(jìn)行對(duì)齊 2.所有對(duì)象的讀寫訪問都進(jìn)行非對(duì)齊訪問 3.float及包含float的結(jié)構(gòu)聯(lián)合及未用__packed的對(duì)象將不能字節(jié)對(duì)齊 4.__packed對(duì)局部整形變量無影響 5.強(qiáng)制由unpacked對(duì)象向packed對(duì)象轉(zhuǎn)化是未定義,整形指針可以合法定 義為packed。 __packed int* p; //__packed int 則沒有意義 6.對(duì)齊或非對(duì)齊讀寫訪問帶來問題 __packed struct STRUCT_TEST { char a; int b; char c; } ; //定義如下結(jié)構(gòu)此時(shí)b的起始地址一定是不對(duì)齊的 //在棧中訪問b可能有問題,因?yàn)闂I蠑?shù)據(jù)肯定是對(duì)齊訪問[from CL] //將下面變量定義成全局靜態(tài)不在棧上 static char* p; static struct STRUCT_TEST a; void Main() { __packed int* q; //此時(shí)定義成__packed來修飾當(dāng)前q指向?yàn)榉菍?duì)齊的數(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)用一個(gè)寫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修飾則匯編出來指令是這樣直接會(huì)導(dǎo)致奇地址處訪問失敗 [0xe59f2018] ldr r2,0x20001594 ; = #0x87654321 [0xe5812000] str r2,[r1,#0] */
//這樣可以很清楚的看到非對(duì)齊訪問是如何產(chǎn)生錯(cuò)誤的 //以及如何消除非對(duì)齊訪問帶來問題 //也可以看到非對(duì)齊訪問和對(duì)齊訪問的指令差異導(dǎo)致效率問題
2008年10月9日
http://bbs.chinaunix.net/viewthread.php?tid=1130381
所謂進(jìn)程間通訊,顧名思義,就是在2個(gè)(多數(shù)情況下)或多個(gè)進(jìn)程間傳遞信息。方法大致如下幾種:
1, 文件(file),匿名管道(anonymous pipe),命名管道(named pipe),信號(hào)(signal).
2、 System V IPC 包括消息隊(duì)列(message queue),共享內(nèi)存(shared memory),信號(hào)量(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)生有名字的實(shí)體,所以稱為匿名管道。
Shell做的事情是調(diào)用pipe(),產(chǎn)生一個(gè)管道,然后把command1的輸出連接到管道的出入端,把command2的輸入連接到管道的輸出端。
命名管道
首先,建立一個(gè)特殊文件,mkfifo pipe1或者mknod fifo1 p
然后,就當(dāng)作正常文件讀寫pipe1。例如: ls > fifo1 (寫入)。
while read a
do
echo $a
done (讀出)
由于產(chǎn)生有名字的實(shí)體,所以被稱為命名管道。
信號(hào):
簡(jiǎn)單的用法: kill –USER2
pid,也就是通過kill()系統(tǒng)調(diào)用或者kill命令,發(fā)送信號(hào)到別的進(jìn)程。各個(gè)進(jìn)程對(duì)于信號(hào)的處理過程是自己定義的(除了9,也就是KILL是強(qiáng)制
的)。比如自己可以忽略HUP,TERM,INT(按control-C), 等。
消息隊(duì)列(message queue)
消息隊(duì)列,是一個(gè)隊(duì)列的結(jié)構(gòu),隊(duì)列里面的內(nèi)容由用戶進(jìn)程自己定義。實(shí)際上,隊(duì)列里面記錄的是指向用戶自定義結(jié)構(gòu)的指針和結(jié)構(gòu)的大小。要使用message
queue,首先要通過系統(tǒng)調(diào)用(msgget)產(chǎn)生一個(gè)隊(duì)列,然后,進(jìn)程可以用msgsnd發(fā)送消息到這個(gè)隊(duì)列,消息就是如上所說的結(jié)構(gòu)。別的進(jìn)程用
msgrcv讀取。消息隊(duì)列一旦產(chǎn)生,除非明確的刪除(某個(gè)有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,產(chǎn)生的隊(duì)列會(huì)一直保留在系統(tǒng)中。而
且,只要有權(quán)限,就可以對(duì)隊(duì)列進(jìn)行操作。消息隊(duì)列和管道很相似,實(shí)際上,管道就是用戶消息為1個(gè)字節(jié)的隊(duì)列。
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 表明這是個(gè)消息隊(duì)列
ID: 用戶自己定義的,在調(diào)用msgget時(shí)傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊(duì)列建立者的名字和組
CREATOR, CGROUP:隊(duì)列建立者和組的ID
CBYTES : 目前queue在隊(duì)列里的字節(jié)數(shù)
QNUM, 目前queue在隊(duì)列里的消息數(shù)
QBYTES: 隊(duì)列中消息最大允許字節(jié)數(shù)
LSPID: 最后發(fā)送者PID
LRPID: 最后接受者PID
STIME: 最后發(fā)送時(shí)間
RTIME: 最后接受時(shí)間。.
CTIME: 建立或者最后修改的時(shí)間
共享內(nèi)存(shared memory)
共享內(nèi)存是一段可以被多個(gè)進(jìn)程共享的內(nèi)存段。首先,用shmget系統(tǒng)調(diào)用產(chǎn)生指定大小的共享內(nèi)存段,然后需要訪問此共享內(nèi)存的進(jìn)程調(diào)用shmat系統(tǒng)調(diào)
用,把這個(gè)內(nèi)存段附加到自己的地址空間,然后就可以像訪問自己私有的內(nèi)存一樣訪問這個(gè)內(nèi)存段了。等到訪問完畢,用shmdt脫離。同message
queue一樣,共享內(nèi)存一旦產(chǎn)生,除非明確的刪除(某個(gè)有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,產(chǎn)生的共享內(nèi)存會(huì)一直保留在系統(tǒng)中。而
且,只要有權(quán)限,就可以對(duì)共享內(nèi)存進(jìn)行操作。共享內(nèi)存的內(nèi)容由進(jìn)程自己定義。為了防止多個(gè)進(jìn)程在同一時(shí)間寫同樣一段共享內(nèi)存,一般程序會(huì)使用信號(hào)量來控制
對(duì)某一段地址的讀寫。
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 表明這是個(gè)共享內(nèi)存
ID: 用戶自己定義的,在調(diào)用shmget時(shí)傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊(duì)列建立者的名字和組
CREATOR, CGROUP:隊(duì)列建立者和組的ID
NATTCH: 有幾個(gè)進(jìn)程掛接(attach)在這段共享內(nèi)存上
SEGSZ: 共享內(nèi)存段大?。ㄗ止?jié))
CPID: 產(chǎn)生者PID
LPID: 最后掛接(attach)或者脫離(detach)者PID
ATIME: 最后掛接(attach)時(shí)間
DTIME: 最后脫離(detach)時(shí)間。.
CTIME: 建立或者最后修改的時(shí)間
信號(hào)量(semaphore)
在操作系統(tǒng)中,有些資源數(shù)量是有限的,在同一時(shí)間,只能由有限(一個(gè)或幾個(gè))的進(jìn)程使用和訪問。例如磁帶機(jī),同一時(shí)間,只能由一個(gè)進(jìn)程使用。這樣的資源被
稱為關(guān)鍵(critical)資源。信號(hào)量就是用來記錄關(guān)鍵資源的使用情況的。首先,利用系統(tǒng)調(diào)用semget產(chǎn)生一個(gè)信號(hào)量。當(dāng)需要使用關(guān)鍵資源時(shí),調(diào)
用semop,傳遞的參數(shù)為需要使用的資源的數(shù)量,例如2個(gè),參數(shù)就為+2。如果這個(gè)資源有2個(gè)或者更多可用,進(jìn)程就獲得了使用權(quán),否則就必須等待,直到
有足夠的資源可用。當(dāng)進(jìn)程使用資源結(jié)束的時(shí)候,也用semop釋放關(guān)鍵資源。參數(shù)為需要釋放的數(shù)量,例如2,參數(shù)為-2。同message
queue一樣,共信號(hào)量一旦產(chǎn)生,除非明確的刪除(某個(gè)有權(quán)限的進(jìn)程或者用ipcrm命令)或者系統(tǒng)重啟。否則,信號(hào)量會(huì)一直保留在系統(tǒng)中。而且,只要
有權(quán)限,就可以對(duì)其進(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 表明這是個(gè)信號(hào)量
ID: 用戶自己定義的,在調(diào)用semget時(shí)傳送的參數(shù)。
Key: 系統(tǒng)返還的全局唯一的ID。
Mode: 權(quán)限,含義和文件權(quán)限基本一致
Owner, group: 隊(duì)列建立者的名字和組
CREATOR, CGROUP:隊(duì)列建立者和組的ID
NSEMS: 本信號(hào)量上信號(hào)的數(shù)量。
OTIME: 最后一次操作(semop)的時(shí)間
CTIM: 建立或者最后修改的時(shí)間
http://www.idcnews.net/html/edu/linux/20080407/264092.html
在APUE 14.7節(jié)對(duì)消息隊(duì)列的講解中,最后一段說“我們得出的結(jié)論是:在新的應(yīng)用程式中不應(yīng)當(dāng)再使用他們。” 雖然在新的應(yīng)用程式中不應(yīng)該再使用消息隊(duì)列,我也沒有怎么使用過System V IPC總覺得在UNIX/Linux編程中少了什么,也許學(xué)習(xí)一下System V IPC對(duì)我的自信心會(huì)有相當(dāng)大的幫助,從此我也敢講我知道如何使用IPC了。
先把各個(gè)函數(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ù)端)或和一個(gè)已建立的Message
Queue連接(客戶端)。key,指定用來生成message
id的關(guān)鍵字,msgflag和open()的flags很相似,可用IPC_CREAT, IPC_EXECL, S_IRUSR等。 在服務(wù)端,可用IPC_PRIVATE(或0)來指定key值,來生成一個(gè)新的Message Queue,或使用指定的key值(32位的無符號(hào)數(shù)),或使用ftok()來生成一個(gè)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的計(jì)算方法: msgsz = sizeof(msgbuf) - sizeof(long); msgflag有一個(gè)標(biāo)志:IPC_NOWAIT。當(dāng)消息隊(duì)列已滿(可能是消息總數(shù)達(dá)到了限制值,也可能是隊(duì)列中字節(jié)總數(shù)達(dá)到了限制值),立即出錯(cuò)返回,假如沒有指定,則阻塞。
msgrcv()用來接收消息。msgtype用來指定讀取的消息類型。msgtype == 0, 返回第一個(gè)消息; msgtype >
0, 返回消息類型為msgtype的消息;msgtype < 0, 返回隊(duì)列中類型值小于msgtype的絕對(duì)值的消息集中最小的消息。 msgflag有兩個(gè)值:MSG_NOERROR, IPC_NOWAIT。當(dāng)MSG_NOERROR被指定的時(shí)候,若消息太長(zhǎng)就被截?cái)?,否則返回錯(cuò)誤;IPC_NOWAIT用于需要讀取的消息不存在時(shí)則阻塞。
msgctl用于控制消息隊(duì)列。cmd有三種值:IPC_STAT,IPC_SET,IPC_RMID。 IPC_STAT用于取出消息隊(duì)列的 msqid_ds結(jié)構(gòu)并保存到buf中。 IPC_SET用來把buf指向的msqid_ds,配置成消息隊(duì)列的msqid_ds。只有四個(gè)值能夠更改:msg_perm.uid, msg_perm.gid,msg_perm.mode, msg_qbytes。 IPC_RMID用來刪除消息隊(duì)列。 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è)計(jì)的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點(diǎn),多線程編程已經(jīng)得到了廣泛的應(yīng)用。本文我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識(shí)。
--------------------------------------------------------------------------------
正文: Linux下的多進(jìn)程編程初步
1 引言
對(duì)于沒有接觸過Unix/Linux操作系統(tǒng)的人來說,fork是最難理解的概念之一:它執(zhí)行一次卻返回兩個(gè)值。fork函數(shù)是Unix系統(tǒng)最杰出的成就
之一,它是七十年代UNIX早期的開發(fā)者經(jīng)過長(zhǎng)期在理論和實(shí)踐上的艱苦探索后取得的成果,一方面,它使操作系統(tǒng)在進(jìn)程管理上付出了最小的代價(jià),另一方面,
又為程序員提供了一個(gè)簡(jiǎn)潔明了的多進(jìn)程方法。與DOS和早期的Windows不同,Unix/Linux系統(tǒng)是真正實(shí)現(xiàn)多任務(wù)操作的系統(tǒng),可以說,不使用
多進(jìn)程編程,就不能算是真正的Linux環(huán)境下編程。 多線程程序設(shè)計(jì)的概念早在六十年代就被提出,但直到八十年代中期,Unix系統(tǒng)中才引入多線程機(jī)制,如今,由于自身的許多優(yōu)點(diǎn),多線程編程已經(jīng)得到了廣泛的應(yīng)用。 下面,我們將介紹在Linux下編寫多進(jìn)程和多線程程序的一些初步知識(shí)。
2 多進(jìn)程編程
什么是一個(gè)進(jìn)程?進(jìn)程這個(gè)概念是針對(duì)系統(tǒng)而不是針對(duì)用戶的,對(duì)用戶來說,他面對(duì)的概念是程序。當(dāng)用戶敲入命令執(zhí)行一個(gè)程序的時(shí)候,對(duì)系統(tǒng)而言,它將啟動(dòng)一
個(gè)進(jìn)程。但和程序不同的是,在這個(gè)進(jìn)程中,系統(tǒng)可能需要再啟動(dòng)一個(gè)或多個(gè)進(jìn)程來完成獨(dú)立的多個(gè)任務(wù)。多進(jìn)程編程的主要內(nèi)容包括進(jìn)程控制和進(jìn)程間通信,在了
解這些之前,我們先要簡(jiǎn)單知道進(jìn)程的結(jié)構(gòu)。
2.1 Linux下進(jìn)程的結(jié)構(gòu) Linux下一個(gè)進(jìn)程在內(nèi)存里有三部分的數(shù)據(jù),就是"代碼段"、"堆棧段"和"數(shù)據(jù)段"。其實(shí)學(xué)過匯編語言的人一定知道,一般的CPU都有上述三種段寄存器,以方便操作系統(tǒng)的運(yùn)行。這三個(gè)部分也是構(gòu)成一個(gè)完整的執(zhí)行序列的必要的部分。
"代碼段",顧名思義,就是存放了程序代碼的數(shù)據(jù),假如機(jī)器中有數(shù)個(gè)進(jìn)程運(yùn)行相同的一個(gè)程序,那么它們就可以使用相同的代碼段。"堆棧段"存放的就是子程
序的返回地址、子程序的參數(shù)以及程序的局部變量。而數(shù)據(jù)段則存放程序的全局變量,常數(shù)以及動(dòng)態(tài)數(shù)據(jù)分配的數(shù)據(jù)空間(比如用malloc之類的函數(shù)取得的空
間)。這其中有許多細(xì)節(jié)問題,這里限于篇幅就不多介紹了。系統(tǒng)如果同時(shí)運(yùn)行數(shù)個(gè)相同的程序,它們之間就不能使用同一個(gè)堆棧段和數(shù)據(jù)段。
2.2 Linux下的進(jìn)程控制
在傳統(tǒng)的Unix環(huán)境下,有兩個(gè)基本的操作用于創(chuàng)建和修改進(jìn)程:函數(shù)fork(
)用來創(chuàng)建一個(gè)新的進(jìn)程,該進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)完全拷貝;函數(shù)族e(cuò)xec(
)用來啟動(dòng)另外的進(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在英文中是"分叉"的意思。為什么取這個(gè)名字呢?因?yàn)橐粋€(gè)進(jìn)程在運(yùn)行中,如果使用了fork,就產(chǎn)生了另一個(gè)進(jìn)程,于是進(jìn)程就"分叉"了,所以這個(gè)名字取得很形象。下面就看看如何具體使用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)中有兩個(gè)它在運(yùn)行了。
那么調(diào)用這個(gè)fork函數(shù)時(shí)發(fā)生了什么呢?fork函數(shù)啟動(dòng)一個(gè)新的進(jìn)程,前面我們說過,這個(gè)進(jìn)程幾乎是當(dāng)前進(jìn)程的一個(gè)拷貝:子進(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í)際上
數(shù)據(jù)卻已經(jīng)分開,相互之間不再有影響了,也就是說,它們之間不再共享任何數(shù)據(jù)了。它們?cè)僖换バ畔r(shí),只有通過進(jìn)程間通信來實(shí)現(xiàn),這將是我們下面的內(nèi)容。
既然它們?nèi)绱讼嘞?,系統(tǒng)如何來區(qū)分它們呢?這是由函數(shù)的返回值來決定的。對(duì)于父進(jìn)程,fork函數(shù)返回了子程序的進(jìn)程號(hào),而對(duì)于子程序,fork函數(shù)則返
回零。在操作系統(tǒng)中,我們用ps函數(shù)就可以看到不同的進(jìn)程號(hào),對(duì)父進(jìn)程而言,它的進(jìn)程號(hào)是由比它更低層的系統(tǒng)調(diào)用賦予的,而對(duì)于子進(jìn)程而言,它的進(jìn)程號(hào)即
是fork函數(shù)對(duì)父進(jìn)程的返回值。在程序設(shè)計(jì)中,父進(jìn)程和子進(jìn)程都要調(diào)用函數(shù)fork()下面的代碼,而我們就是利用fork()函數(shù)對(duì)父子進(jìn)程的不同返
回值用if...else...語句來實(shí)現(xiàn)讓父子進(jìn)程完成不同的功能,正如我們上面舉的例子一樣。我們看到,上面例子執(zhí)行時(shí)兩條信息是交互無規(guī)則的打印出
來的,這是父子進(jìn)程獨(dú)立執(zhí)行的結(jié)果,雖然我們的代碼似乎和串行的代碼沒有什么區(qū)別。
讀者也許會(huì)問,如果一個(gè)大程序在運(yùn)行中,它的數(shù)據(jù)段和堆棧都很大,一次fork就要復(fù)制一次,那么fork的系統(tǒng)開銷不是很大嗎?其實(shí)UNIX自有其解決
的辦法,大家知道,一般CPU都是以"頁"為單位來分配內(nèi)存空間的,每一個(gè)頁都是實(shí)際物理內(nèi)存的一個(gè)映像,象INTEL的CPU,其一頁在通常情況下是
4086字節(jié)大小,而無論是數(shù)據(jù)段還是堆棧段都是由許多"頁"構(gòu)成的,fork函數(shù)復(fù)制這兩個(gè)段,只是"邏輯"上的,并非"物理"上的,也就是說,實(shí)際執(zhí)
行fork時(shí),物理空間上兩個(gè)進(jìn)程的數(shù)據(jù)段和堆棧段都還是共享著的,當(dāng)有一個(gè)進(jìn)程寫了某個(gè)數(shù)據(jù)時(shí),這時(shí)兩個(gè)進(jìn)程之間的數(shù)據(jù)才有了區(qū)別,系統(tǒng)就將有區(qū)別的"
頁"從物理上也分開。系統(tǒng)在空間上的開銷就可以達(dá)到最小。 下面演示一個(gè)足以"搞死"Linux的小程序,其源代碼非常簡(jiǎn)單: void main() { for( ; ; ) fork(); }
這個(gè)程序什么也不做,就是死循環(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ù)先給每個(gè)用戶設(shè)置可運(yùn)行的最大進(jìn)程數(shù),這個(gè)惡意的程序就完成不了企圖了。 2.2.2 exec( )函數(shù)族
下面我們來看看一個(gè)進(jìn)程如何來啟動(dòng)另一個(gè)程序的執(zhí)行。在Linux中要使用exec函數(shù)族。系統(tǒng)調(diào)用execve()對(duì)當(dāng)前進(jìn)程進(jìn)行替換,替換者為一個(gè)指
定的程序,其參數(shù)包括文件名(filename)、參數(shù)列表(argv)以及環(huán)境變量(envp)。exec函數(shù)族當(dāng)然不止一個(gè),但它們大致相同,在
Linux中,它們分別是:execl,execlp,execle,execv,execve和execvp,下面我只以execlp為例,其它函數(shù)究
竟與execlp有何區(qū)別,請(qǐng)通過manexec命令來了解它們的具體情況。
一個(gè)進(jìn)程一旦調(diào)用exec類函數(shù),它本身就"死亡"了,系統(tǒng)把代碼段替換成新的程序的代碼,廢棄原有的數(shù)據(jù)段和堆棧段,并為新程序分配新的數(shù)據(jù)段與堆棧
段,唯一留下的,就是進(jìn)程號(hào),也就是說,對(duì)系統(tǒng)而言,還是同一個(gè)進(jìn)程,不過已經(jīng)是另一個(gè)程序了。(不過exec類函數(shù)中有的還允許繼承環(huán)境變量之類的信
息。) 那么如果我的程序想啟動(dòng)另一程序的執(zhí)行但自己仍想繼續(xù)運(yùn)行的話,怎么辦呢?那就是結(jié)合fork與exec的使用。下面一段代碼顯示如何啟動(dòng)運(yùn)行其它程序:
char command[256]; void main() { int rtn; /*子進(jìn)程的返回?cái)?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í)行命令,打印錯(cuò)誤信息*/ 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ù),因?yàn)镈OS是單任務(wù)的系統(tǒng),它只能將"父進(jìn)程"駐留在機(jī)器內(nèi)再執(zhí)
行"子進(jìn)程",這就是spawn類的函數(shù)。WIN32已經(jīng)是多任務(wù)的系統(tǒng)了,但還保留了spawn類函數(shù),WIN32中實(shí)現(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)建一個(gè)管道,通過它來完成程序的標(biāo)準(zhǔn)輸入和標(biāo)準(zhǔn)輸出。這兩個(gè)函數(shù)是為那些不太勤快的程序員設(shè)計(jì)
的,在效率和安全方面都有相當(dāng)?shù)娜毕?,在可能的情況下,應(yīng)該盡量避免。
2.3 Linux下的進(jìn)程間通信
詳細(xì)的講述進(jìn)程間通信在這里絕對(duì)是不可能的事情,而且筆者很難有信心說自己對(duì)這一部分內(nèi)容的認(rèn)識(shí)達(dá)到了什么樣的地步,所以在這一節(jié)的開頭首先向大家推薦著
名作者Richard Stevens的著名作品:《Advanced Programming in the UNIX
Environment》,它的中文譯本《UNIX環(huán)境高級(jí)編程》已有機(jī)械工業(yè)出版社出版,原文精彩,譯文同樣地道,如果你的確對(duì)在Linux下編程有濃
厚的興趣,那么趕緊將這本書擺到你的書桌上或計(jì)算機(jī)旁邊來。說這么多實(shí)在是難抑心中的景仰之情,言歸正傳,在這一節(jié)里,我們將介紹進(jìn)程間通信最最初步和最
最簡(jiǎn)單的一些知識(shí)和概念。
首先,進(jìn)程間通信至少可以通過傳送打開文件來實(shí)現(xiàn),不同的進(jìn)程通過一個(gè)或多個(gè)文件來傳遞信息,事實(shí)上,在很多應(yīng)用系統(tǒng)里,都使用了這種方法。但一般說來,
進(jìn)程間通信(IPC:InterProcess
Communication)不包括這種似乎比較低級(jí)的通信方法。Unix系統(tǒng)中實(shí)現(xiàn)進(jìn)程間通信的方法很多,而且不幸的是,極少方法能在所有的Unix系
統(tǒng)中進(jìn)行移植(唯一一種是半雙工的管道,這也是最原始的一種通信方式)。而Linux作為一種新興的操作系統(tǒng),幾乎支持所有的Unix下常用的進(jìn)程間通信
方法:管道、消息隊(duì)列、共享內(nèi)存、信號(hào)量、套接口等等。下面我們將逐一介紹。
2.3.1 管道 管道是進(jìn)程間通信中最古老的方式,它包括無名管道和有名管道兩種,前者用于父進(jìn)程和子進(jìn)程間的通信,后者用于運(yùn)行于同一臺(tái)機(jī)器上的任意兩個(gè)進(jìn)程間的通信。 無名管道由pipe()函數(shù)創(chuàng)建: #include <unistd.h> int pipe(int filedis[2]); 參數(shù)filedis返回兩個(gè)文件描述符:filedes[0]為讀而打開,filedes[1]為寫而打開。filedes[1]的輸出是filedes[0]的輸入。下面的例子示范了如何在父進(jìn)程和子進(jìn)程間實(shí)現(xiàn)通信。
#define INPUT 0 #define OUTPUT 1
void main() { int file_descriptors[2]; /*定義子進(jìn)程號(hào) */ 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)前目錄下生成了一個(gè)名為myfifo的有名管道: 方式一:mkfifo("myfifo","rw"); 方式二:mknod myfifo p 生成了有名管道后,就可以使用一般的文件I/O函數(shù)如open、close、read、write等來對(duì)它進(jìn)行操作。下面即是一個(gè)簡(jiǎn)單的例子,假設(shè)我們已經(jīng)創(chuàng)建了一個(gè)名為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 消息隊(duì)列 消息隊(duì)列用于運(yùn)行于同一臺(tái)機(jī)器上的進(jìn)程間通信,它和管道很相似,事實(shí)上,它是一種正逐漸被淘汰的通信方式,我們可以用流管道或者套接口的方式來取代它,所以,我們對(duì)此方式也不再解釋,也建議讀者忽略這種方式。
2.3.3 共享內(nèi)存
共享內(nèi)存是運(yùn)行在同一臺(tái)機(jī)器上的進(jìn)程間通信最快的方式,因?yàn)閿?shù)據(jù)不需要在不同的進(jìn)程間復(fù)制。通常由一個(gè)進(jìn)程創(chuàng)建一塊共享內(nèi)存區(qū),其余進(jìn)程對(duì)這塊內(nèi)存區(qū)進(jìn)行
讀寫。得到共享內(nèi)存有兩種方式:映射/dev/mem設(shè)備和內(nèi)存映像文件。前一種方式不給系統(tǒng)帶來額外的開銷,但在現(xiàn)實(shí)中并不常用,因?yàn)樗刂拼嫒〉膶⑹?
實(shí)際的物理內(nèi)存,在Linux系統(tǒng)下,這只有通過限制Linux系統(tǒng)存取的內(nèi)存才可以做到,這當(dāng)然不太實(shí)際。常用的方式是通過shmXXX函數(shù)族來實(shí)現(xiàn)利
用共享內(nèi)存進(jìn)行存儲(chǔ)的。 首先要用的函數(shù)是shmget,它獲得一個(gè)共享存儲(chǔ)標(biāo)識(shí)符。 #include <sys/types.h> #include <sys/ipc.h> #include <sys/shm.h> int shmget(key_t key, int size, int flag);
這個(gè)函數(shù)有點(diǎn)類似大家熟悉的malloc函數(shù),系統(tǒng)按照請(qǐng)求分配size大小的內(nèi)存用作共享內(nèi)存。Linux系統(tǒng)內(nèi)核中每個(gè)IPC結(jié)構(gòu)都有的一個(gè)非負(fù)整數(shù)
的標(biāo)識(shí)符,這樣對(duì)一個(gè)消息隊(duì)列發(fā)送消息時(shí)只要引用標(biāo)識(shí)符就可以了。這個(gè)標(biāo)識(shí)符是內(nèi)核由IPC結(jié)構(gòu)的關(guān)鍵字得到的,這個(gè)關(guān)鍵字,就是上面第一個(gè)函數(shù)的
key。數(shù)據(jù)類型key_t是在頭文件sys/types.h中定義的,它是一個(gè)長(zhǎng)整形的數(shù)據(jù)。在我們后面的章節(jié)中,還會(huì)碰到這個(gè)關(guān)鍵字。 當(dāng)共享內(nèi)存創(chuàng)建后,其余進(jìn)程可以調(diào)用shmat()將其連接到自身的地址空間中。 void *shmat(int shmid, void *addr, int flag); shmid為shmget函數(shù)返回的共享存儲(chǔ)標(biāo)識(shí)符,addr和flag參數(shù)決定了以什么方式來確定連接的地址,函數(shù)的返回值即是該進(jìn)程數(shù)據(jù)段所連接的實(shí)際地址,進(jìn)程可以對(duì)此進(jìn)程進(jìn)行讀寫操作。
使用共享存儲(chǔ)來實(shí)現(xiàn)進(jìn)程間通信的注意點(diǎn)是對(duì)數(shù)據(jù)存取的同步,必須確保當(dāng)一個(gè)進(jìn)程去讀取數(shù)據(jù)時(shí),它所想要的數(shù)據(jù)已經(jīng)寫好了。通常,信號(hào)量被要來實(shí)現(xiàn)對(duì)共享存
儲(chǔ)數(shù)據(jù)存取的同步,另外,可以通過使用shmctl函數(shù)設(shè)置共享存儲(chǔ)內(nèi)存的某些標(biāo)志位如SHM_LOCK、SHM_UNLOCK等來實(shí)現(xiàn)。
2.3.4 信號(hào)量 信號(hào)量又稱為信號(hào)燈,它是用來協(xié)調(diào)不同進(jìn)程間的數(shù)據(jù)對(duì)象的,而最主要的應(yīng)用是前一節(jié)的共享內(nèi)存方式的進(jìn)程間通信。本質(zhì)上,信號(hào)量是一個(gè)計(jì)數(shù)器,它用來記錄對(duì)某個(gè)資源(如共享內(nèi)存)的存取狀況。一般說來,為了獲得共享資源,進(jìn)程需要執(zhí)行下列操作: (1) 測(cè)試控制該資源的信號(hào)量。 (2) 若此信號(hào)量的值為正,則允許進(jìn)行使用該資源。進(jìn)程將進(jìn)號(hào)量減1。 (3) 若此信號(hào)量為0,則該資源目前不可用,進(jìn)程進(jìn)入睡眠狀態(tài),直至信號(hào)量值大于0,進(jìn)程被喚醒,轉(zhuǎn)入步驟(1)。 (4) 當(dāng)進(jìn)程不再使用一個(gè)信號(hào)量控制的資源時(shí),信號(hào)量值加1。如果此時(shí)有進(jìn)程正在睡眠等待此信號(hào)量,則喚醒此進(jìn)程。
維護(hù)信號(hào)量狀態(tài)的是Linux內(nèi)核操作系統(tǒng)而不是用戶進(jìn)程。我們可以從頭文件/usr/src/linux/include /linux /sem.h
中看到內(nèi)核用來維護(hù)信號(hào)量狀態(tài)的各個(gè)結(jié)構(gòu)的定義。信號(hào)量是一個(gè)數(shù)據(jù)集合,用戶可以單獨(dú)使用這一集合的每個(gè)元素。要調(diào)用的第一個(gè)函數(shù)是semget,用以獲
得一個(gè)信號(hào)量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)建新的信號(hào)量集合,還是引用一個(gè)現(xiàn)有的信號(hào)量集合。nsems是該集合中的信號(hào)量數(shù)。如果是創(chuàng)建新
集合(一般在服務(wù)器中),則必須指定nsems;如果是引用一個(gè)現(xiàn)有的信號(hào)量集合(一般在客戶機(jī)中)則將nsems指定為0。 semctl函數(shù)用來對(duì)信號(hào)量進(jìn)行操作。 int semctl(int semid, int semnum, int cmd, union semun arg); 不同的操作是通過cmd參數(shù)來實(shí)現(xiàn)的,在頭文件sem.h中定義了7種不同的操作,實(shí)際編程時(shí)可以參照使用。 semop函數(shù)自動(dòng)執(zhí)行信號(hào)量集合上的操作數(shù)組。 int semop(int semid, struct sembuf semoparray[], size_t nops); semoparray是一個(gè)指針,它指向一個(gè)信號(hào)量操作數(shù)組。nops規(guī)定該數(shù)組中操作的數(shù)量。 下面,我們看一個(gè)具體的例子,它創(chuàng)建一個(gè)特定的IPC結(jié)構(gòu)的關(guān)鍵字和一個(gè)信號(hào)量,建立此信號(hào)量的索引,修改索引指向的信號(hào)量的值,最后我們清除信號(hào)量。在下面的代碼中,函數(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; /* 定義一個(gè)IPC關(guān)鍵字*/ int id; struct sembuf lock_it; union semun options; int i;
unique_key = ftok(".", 'a'); /* 生成關(guān)鍵字,字符'a'是一個(gè)隨機(jī)種子*/ /* 創(chuàng)建一個(gè)新的信號(hào)量集合*/ 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的信號(hào)量*/
/*打印出信號(hào)量的值*/ i = semctl(id, 0, GETVAL, 0); printf("value of semaphore at index 0 is %d\n", i);
/*下面重新設(shè)置信號(hào)量*/ lock_it.sem_num = 0; /*設(shè)置哪個(gè)信號(hào)量*/ 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);
/*清除信號(hào)量*/ semctl(id, 0, IPC_RMID, 0); }
2.3.5 套接口
套接口(socket)編程是實(shí)現(xiàn)Linux系統(tǒng)和其他大多數(shù)操作系統(tǒng)中進(jìn)程間通信的主要方式之一。我們熟知的WWW服務(wù)、FTP服務(wù)、TELNET服務(wù)
等都是基于套接口編程來實(shí)現(xiàn)的。除了在異地的計(jì)算機(jī)進(jìn)程間以外,套接口同樣適用于本地同一臺(tái)計(jì)算機(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è)計(jì)自己的網(wǎng)絡(luò)螞蟻》,那里由常用的幾個(gè)套接口函數(shù)的介紹和示例程序。這一部分或許是Linux進(jìn)程
間通信編程中最須關(guān)注和最吸引人的一部分,畢竟,Internet
正在我們身邊以不可思議的速度發(fā)展著,如果一個(gè)程序員在設(shè)計(jì)編寫他下一個(gè)程序的時(shí)候,根本沒有考慮到網(wǎng)絡(luò),考慮到Internet,那么,可以說,他的設(shè)
計(jì)很難成功。
3 Linux的進(jìn)程和Win32的進(jìn)程/線程比較 熟悉WIN32編程的人一定知道,WIN32的進(jìn)程管理方式與Linux上有著很大區(qū)別,在UNIX里,只有進(jìn)程的概念,但在WIN32里卻還有一個(gè)"線程"的概念,那么Linux和WIN32在這里究竟有著什么區(qū)別呢?
WIN32里的進(jìn)程/線程是繼承自O(shè)S/2的。在WIN32里,"進(jìn)程"是指一個(gè)程序,而"線程"是一個(gè)"進(jìn)程"里的一個(gè)執(zhí)行"線索"。從核心上
講,WIN32的多進(jìn)程與Linux并無多大的區(qū)別,在WIN32里的線程才相當(dāng)于Linux的進(jìn)程,是一個(gè)實(shí)際正在執(zhí)行的代碼。但是,WIN32里同一
個(gè)進(jìn)程里各個(gè)線程之間是共享數(shù)據(jù)段的。這才是與Linux的進(jìn)程最大的不同。 下面這段程序顯示了WIN32下一個(gè)進(jìn)程如何啟動(dòng)一個(gè)線程。
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指定一個(gè)函數(shù),線程就從那個(gè)函數(shù)處開始運(yùn)行。此程序同前面的UNIX程序一樣,由兩個(gè)線程各打印1000條信息。
threadID是子線程的線程號(hào),另外,全局變量g是子線程與父線程共享的,這就是與Linux最大的不同之處。大家可以看出,WIN32的進(jìn)程/線程
要比Linux復(fù)雜,在Linux要實(shí)現(xiàn)類似WIN32的線程并不難,只要fork以后,讓子進(jìn)程調(diào)用ThreadProc函數(shù),并且為全局變量開設(shè)共享
數(shù)據(jù)區(qū)就行了,但在WIN32下就無法實(shí)現(xiàn)類似fork的功能了。所以現(xiàn)在WIN32下的C語言編譯器所提供的庫函數(shù)雖然已經(jīng)能兼容大多數(shù)
Linux/UNIX的庫函數(shù),但卻仍無法實(shí)現(xiàn)fork。
對(duì)于多任務(wù)系統(tǒng),共享數(shù)據(jù)區(qū)是必要的,但也是一個(gè)容易引起混亂的問題,在WIN32下,一個(gè)程序員很容易忘記線程之間的數(shù)據(jù)是共享的這一情況,一個(gè)線程修
改過一個(gè)變量后,另一個(gè)線程卻又修改了它,結(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)是否有指定的檔案。其方法是先建立一個(gè)包括系統(tǒng)內(nèi)所有檔案名稱及路徑的數(shù)據(jù)庫,之后當(dāng)尋找時(shí)就只需查詢這個(gè)數(shù)據(jù)庫,而不必實(shí)際深入檔案系統(tǒng)之中了。
在一般的 distribution 之中,數(shù)據(jù)庫的建立都被放在 contab 中自動(dòng)執(zhí)行。一般使用者在使用時(shí)只要用
# locate your_file_name
的型式就可以了。 參數(shù):
-u
-U
建立數(shù)據(jù)庫,-u 會(huì)由根目錄開始,-U 則可以指定開始的位置。
-e
將
排除在尋找的范圍之外。
-l
如果 是 1.則啟動(dòng)安全模式。在安全模式下,使用者不會(huì)看到權(quán)限無法看到的檔案。這會(huì)始速度減慢,因?yàn)?locate 必須至實(shí)際的檔案系統(tǒng)中取得檔案的權(quán)限資料。
-f
將特定的檔案系統(tǒng)排除在外,例如我們沒有到理要把 proc 檔案系統(tǒng)中的檔案放在數(shù)據(jù)庫中。
-q
安靜模式,不會(huì)顯示任何錯(cuò)誤訊息。
-n
至多顯示 個(gè)輸出。
-r
使用正規(guī)運(yùn)算式 做尋找的條件。
-o
指定數(shù)據(jù)庫存的名稱。
-d
指定數(shù)據(jù)庫的路徑
-h
顯示輔助訊息
-v
顯示更多的訊息
-V
顯示程序的版本訊息 范例:
locate chdrv : 尋找所有叫 chdrv 的檔案
locate -n 100 a.out : 尋找所有叫 a.out 的檔案,但最多只顯示 100 個(gè)
locate -u : 建立數(shù)據(jù)庫
locate命令可以在搜尋數(shù)據(jù)庫時(shí)快速找到檔案,數(shù)據(jù)庫由updatedb程序來更新,updatedb是由cron
daemon周期性建立的,locate命令在搜尋數(shù)據(jù)庫時(shí)比由整個(gè)由硬盤資料來搜尋資料來得快,但較差勁的是locate所找到的檔案若是最近才建立或
剛更名的,可能會(huì)找不到,在內(nèi)定值中,updatedb每天會(huì)跑一次,可以由修改crontab來更新設(shè)定值。(etc/crontab)
locate指定用在搜尋符合條件的檔案,它會(huì)去儲(chǔ)存檔案與目錄名稱的數(shù)據(jù)庫內(nèi),尋找合乎范本樣式條件的檔案或目錄錄,可以使用特殊字元(如”*”或
”?”等)來指定范本樣式,如指定范本為kcpa*ner,
locate會(huì)找出所有起始字串為kcpa且結(jié)尾為ner的檔案或目錄,如名稱為kcpartner若目錄錄名稱為kcpa_ner則會(huì)列出該目錄下包括
子目錄在內(nèi)的所有檔案。
locate指令和find找尋檔案的功能類似,但locate是透過update程序?qū)⒂脖P中的所有檔案和
目錄資料先建立一個(gè)索引數(shù)據(jù)庫,在執(zhí)行l(wèi)oacte時(shí)直接找該索引,查詢速度會(huì)較快,索引數(shù)據(jù)庫一般是由操作系統(tǒng)管理,但也可以直接下達(dá)update強(qiáng)迫
系統(tǒng)立即修改索引數(shù)據(jù)庫。
不過第一次在執(zhí)行update後再使用locate尋找檔案常會(huì)失敗,此時(shí)就要執(zhí)行slocate
ˉu該命令(也可執(zhí)行updatedb指令,其效果相同)來更新slocate數(shù)據(jù)庫,該命令會(huì)在/usr/sbin下產(chǎn)生slocate執(zhí)行檔,再由
locate到此數(shù)據(jù)庫尋找所要找的資料。
|