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

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