|
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 -----> 测试安装
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手册
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); } |
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()方法
生成具有很多空格的格式化过的输出,这是为了尽可能让人看得明白。它们非常快,而且能够容忍XML文档中的格式错误。例如一个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
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] */
//这样可以很清楚的看到非对齐访问是如何产生错误的 //以及如何消除非对齐访问带来问题 //也可以看到非对齐访问和对齐访问的指令差异导致效率问题
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; };
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了。
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到此数据库寻找所要找的资料。
|