2005-12-10 12:40 scz
最近几年工作重心转向Windows平台,但这份文档仍在维护之中,不会终止。当然, 更新率会越来越低,直至某天工作重心再次转回Unix平台,谁知道呢,:-)
名称 -- Unix编程/应用问答中文版(2005-12-10外发版) 链接 -- http://scz.617.cn/unix/200401170955.txt 维护 -- 小四 [email protected] 主页 -- http://www.nsfocus.com 创建 -- 2001-02-05 13:49 更新 -- 2005-12-07 08:39
感谢 --
感谢C语言的发明者、Unix操作系统的发明者、感谢全世界C程序员创造的Unix共
享传统文化圈,她是如此强大、充满禁忌、而又魅力四射。
感谢deepin,在早期维护过程中的支持、帮助和鼓励。感谢所有NSFOCUS安全研
究小组([email protected])的朋友。
主要支持人员(字母顺序) --
Andrew Gierth
backend
Casper H.S. Dik
deepin
jbtzhm
law
scz
suxm
tt
简介 --
这份文档不是FAQ(Frequently Answered Question),不少问题属于FUQ(Freque-
ntly Unanswered Question)。换句话说,不一定是最常见的编程、应用问答,很可 能其中的答案尚是一个构思,还没有成为现实,又或者根本是个错误的思想火花。但 是,她的确在试图回答一些很有意义的问题,让更多的Unix/C程序员、系统管理员共 享彼此的智慧,那是三十年前无数前辈精英做到过的,也是我们正试图做到的。
Q -- Question
A -- Answer
D -- Discuss
声明 --
一直没有单独出一份完整的,原因很多。如果搁在1995/1996/1997时的CERNET,
这些原因都不成为原因,现在成为原因。不想多说为什么,明白的自然明白,不明白 的当我白痴好了,反正别问我。
文中技术可能涉及未公开的、未文档化的、非规范的编程、应用接口,文档提供
的重在思想,而不保证是正确、高效、唯一的解答。
维护者不对文中任何技术引起的任何灾难性后果负任何法律上、道义上的责任。
文中所附各种源代码,在严格意义上可能存在版权问题,所以事实上这份文档带
有"半地下"性质,使用者务必自己小心卷入此类纠纷。我不能在单份完整文档中附带 可能会带来麻烦的文字、代码,比如Solaris libproc编程接口。但是,在散篇中你 能找到它们。如果你愿意,可以自己将散篇收回到该文档中,这将与我无关。
本份文档的绝大多数内容在"中国教育科研网华南地区网络中心BBS"(bbs.gznet.
edu.cn)的Solaris版发布过了。该版前版主CPU师兄当年的风范促使我开始整理这份 文档,当还昔日指教之情谊。
该份文档"永久拒绝任何商业性质的转载、摘录、引用",同时"允许一切教育性
质的自由转载、摘录、引用,无须提前知会维护者"。我也只是义务维护一下,不对 本文档拥有任何权益。如果不幸潜在拥有而践踏了某种信念,在你看到该声明的同时, 我将自动放弃这种潜在可能拥有的权益。同时意味着一切因本文档带来的麻烦,将由 你个人承担。
既然来自Unix共享传统文化圈,就让它彻底回到Unix共享传统文化圈中去吧。
欢迎一切建设性的Email交流。
目录
-
Unix/C传奇问题 0.0 0.1 Dennis Ritchie 和 Ken Thompson 0.2 W. Richard Stevens 之死 0.3 更多Unix传奇故事 0.4 那些Unix传奇人物长什么样,不会都是三头六臂吧 0.5 "3y3"是如何转换成"eye"的 0.6
-
系统管理配置问题 1.0 如何屏蔽power-button 1.1 如何给SUN工作站增加eeprom硬件口令保护 1.2 如何增加交换空间 1.3 为什么我不能在/home目录下创建子目录 1.4 如何改变一台主机的locale 1.5 Solaris 7自动注销 1.6 一个目录拥有setgid设置,怎么理解 1.7 非Sun Console上有无等价Stop-A的按键 1.8 如何让一个用户只能ftp而无法telnet 1.9 Solaris 8上tftpd的使用 1.10 为什么Sun工作站非要输入boot命令才能启动 1.11 如何让Solaris识别新增加的硬件 1.12 Solaris 9如何在命令行上增加新用户 1.13 如何不让Solaris自动休眠 1.14 Solaris OS Guide for New System Administrators 1.15 粘滞位的意义
-
堆栈相关问题 2.0 理解SIGBUS与SIGSEGV 2.1 如何理解pstack的输出信息 2.2 Solaris的pstack实现源码 2.3 Solaris中如何获取一个C程序的调用栈回溯 2.4 如何编程获取栈底地址 2.5 如何得到一个运行中进程的内存映像 2.6 调试器如何工作的 2.7 x86/Linux上如何处理SIGFPE信号 2.8 GDB调试时没有符号表,如何设置断点
-
-lelf、-lkvm、-lkstat相关问题 3.0 3.1 如何判断可执行文件是否携带了调试信息 3.2 mprotect如何用 3.3 mmap如何用 3.4 getrusage如何用 3.5 setitimer如何用
-
系统资源相关问题 4.0 4.1 主流Unix操作系统上如何编程获取进程的内存、CPU利用状况 4.2 Solaris下如何获知CPU速率 4.3 如何编程获取Solaris系统当前内存大小
-
块设备相关问题 5.0 Solaris/FreeBSD/Linux中如何mount ISO文件 5.1 CDROM设备究竟在哪里 5.2 如何弹出光驱 5.3 如何利用超级块进行恢复工作 5.4 Solaris root口令忘记了 5.5 如何使用fmthard 5.6 如何从光盘恢复Solaris 7的引导扇区 5.7 Solaris支持类似微软autorun.inf文件的功能吗 5.8 如何修改/dev/null的属性 5.9 如何读取Solaris disk label信息 5.10 如何自己制作Solaris启动软盘 5.11 x86/Solaris如何访问FAT32分区 5.12
-
/etc/system可调资源限制 6.1 Solaris下如何限制每个用户可拥有的最大进程数 6.2 如何配置系统使之支持更多的伪终端 6.3 如何增加每个进程可打开文件句柄数 6.4 6.5 做了setuid()这类调用的程序如何产生core dump 6.6 消息队列调整
-
DNS相关问题 7.1 如何进行DNS区传输 7.2 如何获知权威名字服务器 7.3 如何配置DNS的委托解析 7.4 如何获知BIND的版本号 7.5 Solaris/FreeBSD/Linux如何指定域名解析的顺序
-
Solaris编程相关问题 8.0 Solaris多线程编程与errno全局变量 8.1 Solaris内核模块中如何getcwd 8.2 Solaris下如何动态增加系统调用 8.3 如何避免一个套接字进入TIME_WAIT状态 8.4 结构在优化编译中的对齐问题 8.5 kvm编程举例: 如何编程读取shmsys:shminfo_shmmax的值 8.6 如何得到非局部变量列表 8.7 内核可加载模块引用了无法解析的符号 8.8 如何单独获得Solaris编译环境 8.9 如何获取Solaris内核可调参数列表 8.10 如何获取自Unix纪元以来的秒数,如何转换成可理解的表达方式 8.11 如何页边界对齐式分配内存 8.12 Solaris下究竟如何使用setuid/seteuid/setreuid 8.13 compile()和step()怎么用 8.14 Solaris系统中如何检查内存泄露、腐烂 8.15 How to enable microstate accounting in order to use gethrvtime(3C) 8.16 如何让普通用户可以绑定[1, 1023]闭区间上的特权端口 8.17 SPARC/Solaris 7 64-bit kernel mode下dumpadm(1M)手册页 8.18
-
图形界面相关问题 9.1 如何避免进入Solaris的图形界面 9.2 Solaris 7的锁屏 9.3 如何调整键盘重复率 9.4 如何拔掉键盘继续运行Solaris 9.5 Solaris下如何设置显卡分辨率 9.6 Solaris下如何设置显示刷新率 9.7 在PC X Server上使用中文 9.8 如何让Solaris Console保持在字符登录界面,同时可以远程使用PC X Server
-
网卡相关问题 10.0 怎样将第二块网卡名改成hme0 10.1 如何在程序中获取本机MAC地址 10.2 如何在Sun工作站上安装3块网卡 10.3 如何在Solaris x86上安装网卡驱动 10.4 Solaris 单网卡多IP(以太网卡别名) 10.5 如何修改主机名(hostname) 10.6 SPARC/Solaris 2.5/2.6/7/8下如何设置网卡100Mb全双工 10.7 Unix如何对抗ARP欺骗 10.8 SPARC/Solaris 2.6/7/8下如何检查网卡混杂模式 10.9 FreeBSD下ifconfig的man手册 10.10 FreeBSD下arp的man手册 10.11 x86/Solaris如何强制设定网卡速率 10.12 Solaris/FreeBSD/Linux如何确定网卡Capability/Speed 10.13 x86/FreeBSD 4.3-RELEASE下LINK_ADDR(3)手册页 10.14 traceroute是怎么实现的 10.15 SPARC/Solaris 8 snoop(1M)手册页 10.16 x86/FreeBSD TCPDUMP(1)手册页 10.17 Solaris系统中ip_strict_dst_multihoming的确切含义是什么 10.18 Linux下网卡重命名
-
package相关问题 11.0 在SPARC/Solaris 8上手工安装libpcap 11.1 Solaris下如何将二进制软件包安装到指定目标路径下 11.2 Solaris下如何自己定制二进制安装包 11.3 如何恢复/usr/bin/su的缺省安装属性 11.4 如何获知指定包与其他包之间的依赖关系 11.5 如何获得Linux命令的源代码 11.6 Solaris下如何知道某包中有哪些文件 11.7 RedHat下如何检查文件是否被改动过
-
日志相关问题 12.0 Solaris 8如何enable FTP session log 12.1 如何查看/var/adm/utmp、/var/adm/wtmp、/var/adm/lastlog 12.2 logger/syslogd问题 12.3 如何关闭cron的日志 12.4 /var/adm/lastlog文件看上去太大了
-
进程相关问题 13.1 如何根据进程名获得PID 13.2 如何在命令行上访问指定进程P、U两区,如何欺骗Solaris的ps 13.3 getexecname(3C)是怎么实现的 13.4 Solaris 7/8下ps输出中的问号 13.5 如何根据某种原则终止一批进程 13.6 利用libproc库编程举例 13.7 给定一个PID,如何知道它对应一个运行中的进程 13.8 Unix编程中所谓"僵尸进程"指什么 13.9 x86/FreeBSD 4.3-RELEASE的ptrace(2)手册页 13.10 如何知道哪个进程使用了哪个端口 13.11 x86/FreeBSD如何快速获取指定用户拥有的进程数 13.12 如何获取当前进程对应之静态映像文件的绝对路径 13.13 x86/Linux Kernel 2.4.7-10的ptrace(2)手册页 13.14 x86/Linux Kernel 2.4.7-10下如何欺骗ps
-
一些小工具的使用 14.0 14.1 如何在命令行上进行8进制、10进制、16进制之间的转换 14.2 显示文件的三个时间戳(atime、mtime、ctime) 14.3 只在本地文件系统上查找 14.4 join命令 14.5 反汇编
-
32-bit/64-bit相关问题 15.0 15.1 Solaris下如何识别当前内核版本 15.2 如何启动Solaris 32-bit/64-bit内核 15.3 gcc支持64-bit编译吗 15.4 Solaris启动时内核文件找不到了 15.5 64-bit驱动程序无法在8下关联,但在7下工作正常
-
库相关问题 16.0 为什么用高版glibc编译生成的程序不能与低版glibc搭配运行 16.1 在Solaris 7下编写网络程序需要链接哪些库 16.2 SUID设置和LD_LIBRARY_PATH环境变量 16.3 链接过程中库的顺序 16.4 Solaris 2.x下如何构造动态链接库 16.5 如何生成linux下的共享库 16.6 /usr/lib/ld.so.1损坏或丢失 16.7 Solaris下如何使用LD_PRELOAD环境变量 16.8 如何查看系统当前glibc版本 16.9 Solaris 8下如何配置运行时链接环境 16.10 libcrypto.so.0.9.6是什么软件包里的 16.11 共享库的动态加载/卸载 16.12 编译时命令行指定-ldl,ldd观察时却是libdl.so.2,为什么 16.13 如何进行部分静态链接 16.14 如何知道一个运行中的进程加载了哪些动态链接库 16.15 制作so文件时如何指定引出哪些符号
-
文件查看问题 17.0 如何改变vi临时目录 17.1 如何直接查看man文件 17.2 .tex文件怎么读 17.3 Solaris下怎么看.ps文件 17.4 如何将man手册转换成文本文件以便查看
-
补丁相关问题 18.0 18.1 如何根据补丁号从Sun主站下载补丁 18.2 删除旧式补丁备份,释放被占用的磁盘空间 18.3 patchdiag如何使用 18.4 给Solaris 2.6安装推荐补丁集(未完成) 18.5 已知补丁号,如何最快判断系统中是否已经安装该补丁 18.6 如何安装补丁
-
终端相关问题 19.0 如何将stdin、stdout、stderr重定向到/dev/null 19.1 如何使Backspace键做删除操作,而不是显示^H 19.2 telnet时如何关闭本地回显 19.3 如何清空stdin的缓冲 19.4 Linux Console下一按错键就叫,怎么关 19.5 从stdin立即获取按键 19.6 如何屏蔽Ctrl-D 19.7 如何让终端显示从黑方块状态恢复成正常状态
-
shell script问题 20.0 不用临时文件完成字符串替换 20.1 如何获取一个字符串的长度 20.2 读超时自动使用缺省值 20.3 如何删除空行、空白符组成的行 20.4 BASH中如何得到一个字符串的子串 20.5 shell script中如何关闭stdout 20.6 如何将一个文本文件开始的N行删除 20.7 以字符串(非单个字符)为分隔的析取 20.8 使用tr命令加密文件 20.9 有哪些命令用于查找定位 20.10 非递归删除目录树 20.11 如何将大写文件名转换为小写文件名 20.12 shell script中有办法实现段落注释吗 20.13 批量文件字符串替换 20.14 如何显示当前目录下除./与../之外的所有文件、子目录名 20.15 如何使nohup不输出 20.16 如何在csh的命令行提示符中包含当前路径信息 20.17 如果一行含有不以foo前导的bar,则满足需求 20.18 如何得知正在使用什么shell 20.19 如何删除字符串的最后一个字符
-
BSD相关问题 21.0 在x86/FreeBSD 4.5-RELEASE上安装nessus 21.1 如何将/var文件系统mount成mfs并支持cron daemon 21.2 如何将一个512字节的文件写入主引导扇区 21.3 x86/FreeBSD 4.3-RELEASE下FDISK(8)手册页 21.4 x86/FreeBSD 4.3-RELEASE下HEXDUMP(1)手册页 21.5 x86/FreeBSD 4.3-RELEASE下DISKLABEL(8)手册页 21.6 x86/FreeBSD 4.x下不能cp覆盖/kernel 21.7 x86/FreeBSD下如何设置路由 21.8 x86/FreeBSD 4.4-RELEASE下DIFF(1)手册页 21.9 什么是locale 21.10 用cvsup安装vim 21.11 FreeBSD下显示、输入中文 21.12 如何在OpenSSH中限制只允许某些用户登录 21.13 在FreeBSD 4.3-RELEASE上安装libpcap、libnet 21.14 如何使自己的BMP图象成为启动logo 21.15 UDMA ICRC error是什么意思 21.16 Limiting closed port RST response什么意思 21.17 如何获取FreeBSD Kernel Source Code 21.18 /boot/defaults/loader.conf中的技巧 21.19 FreeBSD中sysctl可控内核参数 21.20 x86/FreeBSD 4.3-RELEASE下GETIFADDRS(3)手册页 21.21 FreeBSD下如何访问显存 21.22 FreeBSD下如何为指定用户设定chroot的FTP环境 21.23 如何利用FKLD动态增加一个新协议 21.24 修改/etc/mail/sendmail.cf关闭ident功能 21.25 FreeBSD下如何获取系统负载 21.26 *BSD下如何屏敝远程登录时Copyright显示 21.27 cvsup安装BASH 21.28 配置core dump 21.29 在OpenBSD 3.0上安装Gcc 21.30 在NetBSD 1.5.2上安装BASH 21.31 找不到何处启动了snmpd 21.32 FreeBSD远程root访问
-
Linux Kernel Programming 22.0 22.1 直接访问内存[显存]地址 22.2 /proc可控内核参数
-
Linux相关问题 23.0 以POST方式提交URL请求 23.1 RedHat 7.2远程root访问 23.2 TELNET/FTP连接耗时过长 23.3 Debian/Linux中如何修改本机IP 23.4 如何确认是何种Unix Release 23.5 vi/insert状态下copy/paste时不回车、只换行 23.6 如何产生core dump 23.7 Socket( PF_PACKET, SOCK_RAW, htons( ETH_P_ARP ) )报错 23.8 unknown terminal "vt100"
-
Unix编程相关问题 24.0 如何知道fd是有效文件句柄 24.1 如何使代码段可写 24.2 建议性文件锁与强制性文件锁 24.3 如何编写daemon程序 24.4 将编译、链接过程分开 24.5 进程如何分辨谁在kill()自己 24.6 getopt()、getopt_long()、getopt_long_only()是何用法 24.7 能否在connect()之前得到本地端口号 24.8 如何在GB2312与Unicode之间互相转换 24.9 printf()时想显示文件名及行号
-
AIX相关问题 25.0 如何查看AIX版本号 25.1 如何在AIX命令行上修改IP地址 25.2 如何查看RS/6000物理内存大小 25.3 AIX 4.3.3中"ls a*"不正常 25.4 AIX多线程编程与errno全局变量 25.5 AIX如何进入单用户模式
- Unix/C传奇问题
0.1 Dennis Ritchie 和 Ken Thompson
Q: 我想知道他们,为什么大家不断提到这两个名字?
A: All of Unix Programmers
我们也想知道,:-P
1969年Dennis Ritchie和Ken Thompson在贝尔实验室创造性地发明了Unix操作系统, 为此1983年他们获得了图灵奖。
尽管Ritchie是C程序设计语言的发明者,但是他最喜欢的编程语言是Alef。而 Thompson是一名业余飞行员,曾到莫斯科驾驶过米格-29。
欢迎访问
http://cm.bell-labs.com/who/dmr/ http://cm.bell-labs.com/who/ken/
0.2 W. Richard Stevens 之死
Q: David Johns [email protected]
我是他的崇拜者,用www.google.com搜索他的讣告,但这份讣告没有提及死因,有人 知道吗?
真的仅仅是英年早逝吗?
A: Nithyanandham [email protected]
他死于1999/09/01,家人不想让别人知道死因。讣告位于
http://www.azstarnet.com/clips/richard_stevens.html
A: joe broz [email protected]
似乎是一场攀岩事故,或者滑雪事故,我不确认。
Q: W. 代表什么
A:
William. My parents wanted to name me after my Uncle Bill but also wanted to call me Richard. They figured "William Richard" sounded better than "Richard William".
Q: 做为Guru of the Unix gurus,Stevens一生当中崇拜过什么人吗?
A: http://www.salon.com/tech/feature/2000/09/01/rich_stevens/index.html
Stevens greatly admired and strove to emulate Donald Knuth, who wrote "The Art of Computer Programming," and Brian Kernighan, "The C Programming Language," whose books are as beautifully laid out as they are brilliantly written.
D: knightmare@APUE 2002-04-08 15:54
搞笑片段
我现在越来越崇拜Stevens了,因为昨天我看的电影--反斗神鹰(hot shot)--的导演 是Richard Stevens。
D: Rachel Chalmers [email protected]
这个可与之比拟,不过这个是真的:
His books are so good that they have come to symbolize intelligence. In "Wayne's World II," Garth's girlfriend carries a copy of "Unix Network Programming." Stevens discovered this when he took his 13-year-old son to see the film. His son grabbed his arm and said, "Dad, that's your book!"
"I couldn't believe it," he told programmer Trent Hein. "My book was used to define the ultimate geek, and suddenly my son thinks I'm really cool."
His son was right.
0.5 "3y3"是如何转换成"eye"的
A:
下面是一个基本转换表
a 4 @ 4 4 @ @ 4 4 4 or @ or /-
b |3 b 8 8 B |3 8 8 8 or |3
c C c c k C ( < < (
d |) |) D d D |) D c| |)
e 3 3 3 3 3 3 3 3 3
f |>|-| f ph F F |[ F |= |= or pH
g 6 G g 9 6 6 6 6 9
h |-| |-| H |-| H |{ H |-| |-| or #
i 1 1 i 1 ! | 1 ][ or 1 1 or | or !
j | j j j J | J ] J
k |< |< k |< K |< [< |< |{ or |<
l | 1 1 1 1 | |- 1 or | or [ or | |_ or []_
m |/| |/| M //\ M |V| M |/| |/|
n || || n || N || N || || or //
o 0 o o 0 0 0 0 0 0
p |> p P p P |o P |> |>
q Q q Q q Q O, Q 0 Q
r |2 r R |2 R |)\ R |2 |2
s 5 5 s 5 5 5 $ 5 or Z 5
t 7 + + 7 7 7 7 7 or + + or 7
u || u u u U || U || || or _/
v / v V / V / V / /
w // // W // W |/| W // //
x >< >< X >< X X >< X
y / Y y '/ Y \/ | Y j or J or
/ Y
z Z z Z z 2 -_ Z 5 Z
如果只进行字母到数字的转换,可以简化成
这里有一个转换页面,由于存在一对多的现象,转换结果可能并不完全相符
http://www.planetquake.com/turkey/l33translate.htm
比如"I'm a programmer",将被转换成"1'//\ 4 p|209|24////\3|2"
更多信息参看如下链接
http://www.cwru.edu/orgs/sigmataudelta/submissions/rome-relaxweunderstand.htm
D: dfbb@SMTH
有个geekcode也差不多,http://www.geekcode.com/geek.html
- 系统管理配置问题
1.0 如何屏蔽power-button
Q: 如何屏蔽Sun键盘右上角的power-button
A: Alan Coopersmith [email protected] 2002年7月19日 22:12
-
为了只允许root执行sys-suspend命令shutdown/suspend系统,可以编辑 /etc/default/sys-suspend
-
为了禁止通过power-button激活sys-suspend命令,编辑 /usr/openwin/lib/speckeysd.map
1.1 如何给SUN工作站增加eeprom硬件口令保护
A: scz [email protected]
man -s 1M eeprom了解细节,要求当前是root身份
此时进入交互式设置口令过程,总共输入两次,如果两次口令输入不一致,则本次设 置作废。成功设置之后除了go命令之外的其他ok状态下命令均需要口令,包括boot命 令。
设置成command时,同样进入交互式口令输入过程。此时,除了boot和go命令之外的 其他ok状态下命令均需要口令。注意,如果仅仅输入boot命令,不需要口令,一旦 boot命令后面带了参数,比如boot cdrom -s,同样需要输入口令。
如果设置成none(缺省设置),表示去掉这种口令保护。
如果想改变前面设置的口令,用这条命令,同样是交互式输入过程。
设置口令输入尝试次数。
警告:如果设置了eeprom硬件保护口令而又忘记,会带来很多麻烦,务必小心。
一个可行的设置办法是,安全模式设置到command而不是full,这样至少可以正常启 动系统。于是只要记得root口令或者还有其他机会获得root权限(缓冲区溢出?),就 可以通过设置安全模式为none而挽救回来。
但是如果设置成full模式却忘记了eeprom口令,我想首先应该打电话给SUN的技术支 持。如果出于某种理由你不想这样做,我不确认eeprom是否可以热插拔,先用一个无 口令保护的eeprom启动系统,然后热插拔换上那个有口令保护的eeprom,然后用root 权限抹去eeprom口令。
D: bluesfisher@SMTH
启动时Stop-N可以恢复OBP缺省设置,应该可以把这个密码去掉吧
按住Stop-N,加电,直到键盘灯闪
D: lose@SMTH 2002-03-22 01:45
试了一下Stop-N,不可以。
试了一下小四的办法是可以的,只是有几个小地方需要说一下。没有eeprom时机器是 无法启动的,所以必须要有另一块没有口令的eeprom。第一次为了热插拔方便没有将 新的eeprom插得很紧,启动之后报告IDPROM出错,不过没有关系,系统还是可以启动。 换上eeprom之后,只有console窗口可以运行,其它命令窗口无法运行命令。在 console下修改
reboot机器,一切OK。另外发现,只要你换上eeprom,都可以reboot机器而不需要口 令,重新启动之后再修改也可以,不知道这算不算一个bug。
1.2 如何增加交换空间
A: WT [email protected]
你无法改变分区大小,但是可以增加/删除交换文件,效果类似交换分区。下列命令 在根目录下创建一个500MB的交换文件,名为swapfile
下列命令将使之生效
现在你有了额外的500MB交换空间,为了每次重启后依旧有效,编辑/etc/vfstab文件 增加如下行
/swapfile - - swap - no -
这里"-l"意味着"list",显示所有交换空间。仔细阅读"swap"和"mkfile"的手册页。
1.3 为什么我不能在/home目录下创建子目录
Q:
Solaris 7下,root身份,当我试图在/home目录下创建子目录时,系统拒绝,为什么?
A: mohansundarraj
如果/etc/rc2.d/S74autofs脚本中automount(1M)守护进程已经mount了/home,就是 这种现象,而这还是缺省安装后的情形。可以
然后你就可以用root身份在/home下创建子目录,增加文件了。为了永久取消autofs 特性,可以将/etc/rc2.d/S74autofs脚本改名,并注释掉/etc/auto_home、 /etc/auto_master两个文件中的入口点。
SPARC/Solaris的缺省用户主目录是/export/home,而不是/home。
1.4 如何改变一台主机的locale
Q: 一台SPARC/Solaris 8运行在US locale环境中,现在我们想让它运行在 IE(Ireland) locale环境中,以便可以使用欧洲日期格式,怎么办?
A: Sharad Ramachandran [email protected]
运行sys-unconfig,在此之前请man -s 1M sys-unconfig,:-)
A: chad schrock [email protected]
天啊,为了拍死一只苍蝇,你要引爆原子弹吗?
只需要做如下操作,在你的.cshrc/.profile/.bashrc等启动脚本中设置$LANG环境变 量的值为en_UK,注销,重新登录即可。为了使这个设置全局有效,修改 /etc/default/init文件,LANG=en_UK,重启动。
参看locale(1)和locale(5),了解更多关于locale的信息。运行"locale -a",查看 当前系统所支持的所有locale。
A: Sun Microsystems 2001-06-12
有三种方式改变locale。首先用"locale -a"命令确认系统中已安装的locale
- 从CDE登录屏幕上修改locale
选择 options -> languages -> choose the new locale
注意,如果登录用户的初始化文件中有不同的locale设置,将优先于系统全局locale 设置。
- 临时设置locale(shell相关的)
ksh : LANG= sh : LANG= export LANG csh : setenv LANG bash: export LANG=en_US(zh.GBK)
- vi /etc/default/init
增加如下内容
LANG= LC_ALL=
重启系统。
运行"locale"命令确认改变生效。
如果你希望使用的locale并未安装,参看如下文档安装locale
Solaris 8 : <>
Solaris 7 : <>
Solaris 2.6: <>
D: scz [email protected] 1998-08
SPARC/Solaris 2.5下,为了在vi中正确看到中文需要设置环境变量
sh
LANG=C;export LANG LC_CTYPE=iso_8859_1;export LC_CTYPE
csh
setenv LANG zh
关于设置LANG这个环境变量涉及到/usr/lib/locale下的目录权限。
1.5 Solaris 7自动注销
Q: 怎样设置才能30秒后自动注销
A: shridhara
不幸的是,Solaris对此没有什么好的支持。如果正在使用telnet会话,或许可以考 虑"logout"变量,参看telnet的手册页。一个变通的办法,使用K-Shell,它支持 TMOUT变量,用于指定非活动时限(以秒为单位)。比如,如果一个shell会话3分钟内 不活动,则终止这个shell会话
$ TMOUT=180;export TMOUT
可以在用户的.profile文件中放置该行。缺点是你只能使用ksh。
D: quack
Linux、Solaris 2.6上的bash试了也行。
D: scz [email protected]
vi /etc/default/login
TIMEOUT=180
这里的超时设置针对登录过程,而不是登录成功后的shell会话超时设置。
1.6 一个目录拥有setgid设置,怎么理解
Q: 对一个目录做了setgid设置,可我并没有发现这和正常情况有什么区别
A: John Riddoch [email protected]
在这种目录下创建新文件时将采用setgid设置对应的属组,比如
$ ls -ld b drwxrws--- 2 jr group 512 Mar 14 17:13 b/ $ touch b/a $ ls -l b/a -rw------- 1 jr group 0 Mar 14 17:13 b/a $ id uid=178(jr) gid=10(staff)
jr的缺省组是staff,而现在b/a文件属组是group。
D: 小四 [email protected]
SPARC/Solaris 7下测试
如果目录拥有SGID设置,那么该目录下新创建的文件将继承该目录的属组,而不是创 建者所对应的GID。
[root@ /export/home/scz]> id uid=0(root) gid=1(other) <-- 注意当前用户的属组 [root@ /export/home/scz]> mkdir groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-xr-x root other groupsgid/ [root@ /export/home/scz]> chown scz:users groupsgid [root@ /export/home/scz]> chmod g+s groupsgid [root@ /export/home/scz]> ls -ld groupsgid drwxr-sr-x scz users groupsgid/ <-- 目录拥有SGID设置 [root@ /export/home/scz]> cd groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_0 [root@ /export/home/scz/groupsgid]> ls -l scz_0 -rw-r--r-- root users scz_0 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]> chmod g-s ../groupsgid/ [root@ /export/home/scz/groupsgid]> ls -ld ../groupsgid/ drwxr-xr-x scz users ../groupsgid/ [root@ /export/home/scz/groupsgid]> touch scz_1 [root@ /export/home/scz/groupsgid]> ls -l scz_1 -rw-r--r-- root other scz_1 <-- 注意属组变化 [root@ /export/home/scz/groupsgid]>
1.7 非Sun Console上有无等价Stop-A的按键
A: neomilev
如果是便携机,尝试alt/break 或者 ctrl/break。如果是vt100终端,尝试F11 或者 break
1.8 如何让一个用户只能ftp而无法telnet
A: 小四 [email protected]
修改该用户在/etc/passwd中的shell为/bin/false,在/etc/shells文件中增加 /bin/false,此时,该用户只能ftp,telnet失败。
如果/bin/false不灵,干脆换成/bin/nonexist即可。其实/bin/false不灵只是暂时 某些缓冲机制的结果,重启后必然有效,不重启的话可能要等待一定时间之后才见效 果。
如果将/bin/false换成/usr/bin/passwd,则用户可以远程telnet修改自己的口令, 也可以ftp登录,但无法远程telnet登录获取shell。
1.9 Solaris 8上tftpd的使用
A: Solaris 8上in.tftpd(1M)手册页
维护命令 in.tftpd(1M)
名字
in.tftpd, tftpd - Internet Trivial File Transfer Protocol Server
摘要
in.tftpd [ -s ] [ homedir ]
抽述
tftpd通常通过inetd.conf启动,缺省是注释掉的,需要手工开放。
在响应请求之前,tftpd试图切换自身的当前目录到指定的"homedir",缺省设置
是/tftpboot。
tftp不要求帐号、口令即可访问远程系统。由于缺乏身份认证信息,in.ftpd在
处理get请求时只允许访问全局可读文件。而在处理put请求时,要求server端文
件名已存在且全局可写。
in.tftpd以nobody身份运行。
选项
-s 指定该选项时,tftpd切换自身当前目录到指定"homedir"必须成功,同时
tftpd会以"homedir"为根做chroot操作。
文件
/etc/inetd.conf
tftpd在处理请求失败时会写/var/adm/messages,可用如下命令查看错误信息
tftpd侦听69/udp口。
1.10 为什么Sun工作站非要输入boot命令才能启动
Q: 我有台Sun工作站,每次开机后停在ok状态下,需要手工输入boot命令才能启动, 现在想避免这种效果,怎么办
A: /usr/sbin/eeprom auto-boot?=true /usr/sbin/eeprom auto-boot? <-- 查询
A: dengdai@SMTH
进入OBP状态
ok setenv auto-boot? true ok setenv boot-device disk
反之
ok setenv auto-boot? false
1.11 如何让Solaris识别新增加的硬件
Q: 比如新增加了网卡、硬盘、光驱什么的,如何让Solaris意识到这种增加
A: spp(低音炮) & suxm
有三种办法
a. Stop-A进入OBP状态,输入boot -r b. sync(重复);reboot -- -r c. touch /reconfigure;sync(重复);reboot
参看reboot(1M)、boot(1M)、eeprom(1M)、kernel(1M)、cfgadm(1M)、psradm(1M)手 册页
Q: 我新增加了一块硬盘,不想boot -r而立即生效,怎么办
A: willxu 2001-12-04 16:51
直接将第二块硬盘接上去,然后顺序执行如下命令,不用重新启动机器
modunload -i 0 drvconfig(1M) devlinks(1M) disks(1M)
如果需要重新格式化、分区、创建文件系统,就继续执行
format(1M) newfs(1M)
1.12 Solaris 9如何在命令行上增加新用户
A:
useradd -u -g other -d /export/home/ -s /usr/bin/bash -c -m
1.13 如何不让Solaris自动休眠
Q:
Solaris有时会自动休眠,连电源都关了,然后按Sun键盘右上角的power-button,就 开始恢复到休眠以前的状态。我想屏蔽这个特性。
A: 1997-07
参看power.conf(4)、pmconfig(1M)手册页。最简单的办法是编辑/etc/power.conf, 注释掉autoshutdown那一行,然后执行pmconfig使之立即生效。
1.14 Solaris OS Guide for New System Administrators
A: Rolf Kersten [2004-06-25]
http://www.sun.com/bigadmin/content/solSysadminGuide/ http://www.sun.com/bigadmin/content/solSysadminGuide/solsysadmin.pdf
1.15 粘滞位的意义
A:
Linux stat(2):
当一个目录设置了sticky bit(S_ISVTX)时,意味着该目录下的文件仅当下述条件之 一满足时可被重命名、删除:
- 文件属主
- 目录属主
- root
Solaris chmod(1):
只有root才能对非目录的普通文件设置粘滞位。
Solaris chmod(2):
当一个目录可写并且设置了粘滞位时,该目录下的文件仅当下述条件之一满足时可被 重命名、删除(rename(2)、unlink(2)):
- 文件属主
- 目录属主
- 对该文件拥有写权限的用户
- root
- 堆栈相关问题
2.0 理解SIGBUS与SIGSEGV
Q: SIGSEGV我能理解,但有时碰上SIGBUS,这该如何理解。
A: nkwht@SMTH
nkwht用Google获取这样一些知识。有多种可能导致SIGBUS信号:
-
硬件故障,不用说,程序员最常碰上的肯定不是这种情形。
-
Linux平台上执行malloc(),如果没有足够的RAM,Linux不是让malloc()失败返回, 而是向当前进程分发SIGBUS信号。
注: 对该点执怀疑态度,有机会可自行测试确认当前系统反应。
-
某些架构上访问数据时有对齐的要求,比如只能从4字节边界上读取一个4字节的 数据类型。IA-32架构没有硬性要求对齐,尽管未对齐的访问降低执行效率。另外 一些架构,比如SPARC、m68k,要求对齐访问,否则向当前进程分发SIGBUS信号。
SIGBUS与SIGSEGV信号一样,可以正常捕获。SIGBUS的缺省行为是终止当前进程并产 生core dump。
A: Marc Rochkind [email protected]
SIGBUS与SIGSEGV信号的一般区别如下:
-
SIGBUS(Bus error)意味着指针所对应的地址是有效地址,但总线不能正常使用该 指针。通常是未对齐的数据访问所致。
-
SIGSEGV(Segment fault)意味着指针所对应的地址是无效地址,没有物理内存对 应该地址。
A: scz [email protected] 2002-11-20
参"2.4 如何编程获取栈底地址"中如何捕获SIGBUS与SIGSEGV信号,并利用sigsetjmp、 siglongjmp重获控制权。
测试表明,在x86/Linux、x86/Solaris、SPARC/Solaris平台上,越过栈底的地址访 问导致SIGSEGV信号。在x86/FreeBSD、x86/NetBSD、x86/OpenBSD平台上,越过栈底 的地址访问导致SIGBUS信号,而不是SIGSEGV信号。
下面举例解释一下,什么叫未对齐的数据访问。
/*
- Test: SPARC/Solaris 8 64-bit kernel mode
- gcc -Wall -pipe -g -o bus bus.c */ #include <stdio.h> #include <stdlib.h>
int main ( int argc, char * argv[] ) { unsigned int i = 0x12345678; unsigned short int *q = NULL; unsigned char *p = ( unsigned char * )&i;
*p = 0x00;
q = ( unsigned short int * )( p + 1 );
*q = 0x0000;
return( EXIT_SUCCESS );
$ ./bus 总线错误 (core dumped) $ gdb ./bus core GNU gdb 5.0 #0 0x1084c in main (argc=1, argv=0xffbefc54) at bus.c:16 16 *q = 0x0000; (gdb) disas main Dump of assembler code for function main: 0x10810
: save %sp, -128, %sp 0x10814 <main+4> : st %i0, [ %fp + 0x44 ] 0x10818 <main+8> : st %i1, [ %fp + 0x48 ] 0x1081c <main+12>: sethi %hi(0x12345400), %o1 0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678 0x10824 <main+20>: st %o0, [ %fp + -20 ] 0x10828 <main+24>: clr [ %fp + -24 ] 0x1082c <main+28>: add %fp, -20, %o0 0x10830 <main+32>: st %o0, [ %fp + -28 ] 0x10834 <main+36>: ld [ %fp + -28 ], %o0 0x10838 <main+40>: clrb [ %o0 ] 0x1083c <main+44>: ld [ %fp + -28 ], %o0 0x10840 <main+48>: add %o0, 1, %o1 0x10844 <main+52>: st %o1, [ %fp + -24 ] 0x10848 <main+56>: ld [ %fp + -24 ], %o0 0x1084c <main+60>: clrh [ %o0 ] 0x10850 <main+64>: clr %i0 0x10854 <main+68>: b 0x1085c <main+76> 0x10858 <main+72>: nop 0x1085c <main+76>: ret 0x10860 <main+80>: restore End of assembler dump. (gdb) i r pc pc 0x1084c 67660 (gdb) i r o0 o0 0xffbefbdd -4260899 (gdb) x/3bx 0xffbefbdd 0xffbefbdd: 0x34 0x56 0x78 (gdb)从C语言来说,执行"*q = 0x0000;"时导致SIGBUS了。从汇编指令来说,执行"clrh [%o0]" 时导致SIGBUS了,寄存器%o0值为0xffbefbdd,这个地址未对齐在双字节边界上。
注意,gcc编译时并未指定-O进行优化,但仍然使用clrh,而不是两次clrb。类似 的汇编指令有ldw、lduh等等。有人可能碰上读操作也导致SIGBUS,觉得不可理解, 其实读写导致SIGBUS没有本质区别,比如ldw只能读4字节边界上的地址。
bus.c是显式的未对齐。程序员实际最容易面对的是隐式未对齐,主要来自指针的强 制类型转换。下面举例说明这种情形。
/*
- Test: SPARC/Solaris 8 64-bit kernel mode
- gcc -Wall -pipe -g -o other_bus other_bus.c */ #include <stdio.h> #include <stdlib.h>
int main ( int argc, char * argv[] ) { unsigned int i = 0x12345678; unsigned short int j = 0x0000;
j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) );
return( EXIT_SUCCESS );
$ ./other_bus 总线错误 (core dumped) $ gdb ./other_bus core GNU gdb 5.0 #0 main (argc=1, argv=0xffbefc44) at other_bus.c:13 13 j = *( ( unsigned short int * )( ( ( unsigned char * )&i ) + 1 ) ); (gdb) disas main Dump of assembler code for function main: 0x10810
: save %sp, -120, %sp 0x10814 <main+4> : st %i0, [ %fp + 0x44 ] 0x10818 <main+8> : st %i1, [ %fp + 0x48 ] 0x1081c <main+12>: sethi %hi(0x12345400), %o1 0x10820 <main+16>: or %o1, 0x278, %o0 ! 0x12345678 0x10824 <main+20>: st %o0, [ %fp + -20 ] 0x10828 <main+24>: clrh [ %fp + -22 ] 0x1082c <main+28>: lduh [ %fp + -19 ], %o0 0x10830 <main+32>: sth %o0, [ %fp + -22 ] 0x10834 <main+36>: clr %i0 0x10838 <main+40>: b 0x10840 <main+48> 0x1083c <main+44>: nop 0x10840 <main+48>: ret 0x10844 <main+52>: restore End of assembler dump. (gdb) i r pc pc 0x1082c 67628 (gdb)因此在SPARC架构上编程,一定要留神强制类型转换,务必清楚自己正在干什么,有 没有隐患。
D: yuhuan@SMTH 2004-01-30 11:48
参Linux的mmap(2)手册页
使用映射可能涉及到如下信号
SIGSEGV
试图对只读映射区域进行写操作
SIGBUS
试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,或者以
前有文件内容对应,现在为另一进程截断过的内存区域。
2.1 如何理解pstack的输出信息
Q: 080603a7 main (1, 80479b8, 80479c0) + d53 结尾的d53是什么
A: Roger A. Faulkner [email protected]
在代码段绝对地址0x080603a7处,main()调用了一个函数,0x080603a7正是 main + 0xd53,换句话说,从main()函数开始的0xd53偏移处。
2.3 Solaris中如何获取一个C程序的调用栈回溯
Q: 我想在Solaris 2.6及其后续版本上获取一个C程序的调用栈回溯,类似如下输出
(10) 0x00045e08 integ + 0x408 [./two_brn.e] (11) 0x0006468c trajcem + 0x128 [./two_brn.e] (12) 0x00055490 fly_traj + 0xf58 [./two_brn.e] (13) 0x0004052c top_level + 0x14 [./two_brn.e] (14) 0x000567e4 _start + 0x34 [./two_brn.e]
这样我就可以知道当程序崩溃、死锁的时候代码执行到了何处。在HP-UX和IRIX上 可以利用U_STACK_TRACE()和trace_back_stack_and_print(),Solaris上呢?
Q: 有没有办法显示当前堆栈中的数据(GNU/Linux系统)?我希望自己的异常处理程序 在进程结束前dump整个栈区(stack),以便观察到栈顶是什么函数。对于调试意想 不到的运行时错误而言,这很重要。
Q: Is it possible to unwind the stack on Solaris 8? Is there an API that I could use? I know that with TRU64(Digital UNIX) there are the exception handling routines: except_virtual_unwind() and except_capture_context(). Basically, what I am trying to do is print out the stack on demand, just as dbx or gdb would.
A: Bjorn Reese [email protected]
用/usr/proc/bin/pstack [-F] <pid ...>
参看这个例子代码,http://home1.stofanet.dk/breese/debug/debug.tar.gz
Q: is there a way to access call stack information at run time from within a program? i've been maintaining my own crude stack using FUNCTION and linked lists but can't help but think there's gotta be a better way...
A: Nate Eldredge [email protected]
这依赖于你的系统,如果使用glibc 2.1或更新版本,可以使用backtrace()函数, 参看<execinfo.h>,其他系统可能有不同的技术支持。
注意,你所使用的办法可能是唯一能够保证跨平台使用的
A: Andrew Gabriel [email protected] Consultant Software Engineer
下面是一个backtrace()的应用举例,如果你使用Solaris 2.4及其后续版本,那么这 个例子可以很好的工作。很可能无法工作在64-bit模式下,我没有尝试过,好像 Solaris 7已经提供了一个类似的演示程序。还可以增加某些功能,我没有时间了。
/*
- Produce a stack trace for Solaris systems.
- Copyright (C) 1995-1998 Andrew Gabriel [email protected]
- Parts derived from Usenet postings of Bart Smaalders and Casper Dik.
*/
/* ......................................................................... */
#include <setjmp.h> #include <sys/types.h> #include <sys/reg.h> #include <sys/frame.h> #include <dlfcn.h> #include <errno.h> #include <unistd.h> #include <stdio.h>
#if defined(sparc) || defined(__sparc) #define FLUSHWIN() asm("ta 3"); #define FRAME_PTR_INDEX 1 #define SKIP_FRAMES 0 #endif
#if defined(i386) || defined(__i386) #define FLUSHWIN() #define FRAME_PTR_INDEX 3 #define SKIP_FRAMES 1 #endif
#if defined(ppc) || defined(__ppc) #define FLUSHWIN() #define FRAME_PTR_INDEX 0 #define SKIP_FRAMES 2 #endif
/* ......................................................................... */
static void print_address ( void * pc ) { Dl_info info;
if ( dladdr( pc, &info ) == 0 )
{
/* not found */
fprintf( stderr, "*** %s:0x%x\n", "??", ( unsigned int )pc );
}
else
{
/* found */
fprintf( stderr, "*** %s:%s+0x%x\n", info.dli_fname, info.dli_sname,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
}
return;
} /* end of print_address */
/* ......................................................................... */
static int validaddr ( void * addr ) { static long pagemask = -1; char c;
if ( pagemask == -1 )
{
pagemask = ~( sysconf( _SC_PAGESIZE ) - 1 );
}
addr = ( void * )( ( long )addr & pagemask );
if ( mincore( ( char * )addr, 1, &c ) == -1 && errno == ENOMEM )
{
return 0; /* invalid */
}
else
{
return 1; /* valid */
}
} /* end of validaddr */
/* ......................................................................... */
/*
- this function walks up call stack, calling print_addess
- once for each stack frame, passing the pc as the argument. */
static void print_stack ( void ) { struct frame * sp; jmp_buf env; int i; int * iptr;
FLUSHWIN();
setjmp( env );
iptr = ( int * )env;
sp = ( struct frame * )iptr[ FRAME_PTR_INDEX ];
for ( i = 0; i < SKIP_FRAMES && sp; i++ )
{
if ( !validaddr( sp ) || !validaddr( &sp->fr_savpc ) )
{
fprintf( stderr, "***[stack pointer corrupt]\n" );
return;
}
sp = ( struct frame * )sp->fr_savfp;
}
i = 100; /* looping check */
while ( validaddr( sp ) && validaddr( &sp->fr_savpc ) && sp->fr_savpc && --i )
{
print_address( ( void * )sp->fr_savpc );
sp = ( struct frame * )sp->fr_savfp;
}
} /* end of print_stack */
/* ......................................................................... */
void backtrace( void ) { fprintf( stderr, "***backtrace...\n" ); print_stack(); fprintf( stderr, "***backtrace ends\n" ); }
/* ......................................................................... */
Q: 我正在使用Solaris系统,"uname -a"显示如下
SunOS usunnad01 5.8 Generic_108528-14 sun4u sparc SUNW,UltraAX-i2
假设有如下代码
caller_func ()
{
called_func();
}
called_func ()
{
printf( "called_func() is being called from %s\n", some_magic_func() );
}
我期待着这样的执行输出
"called_func() is being called from caller_func()"
请问如何实现some_magic_func(),C或者汇编语言编程都可以。
D: Paul Pluzhnikov [email protected]
看看mpatrol的源代码,其中有traceback()函数可以给出整个调用栈回溯,而不仅仅 是主调函数。
D: Peter Ammon [email protected]
可以考虑使用宏,这是一个例子
/*
- gcc -Wall -pipe -g -o test test.c */ #include <stdio.h>
#define CALL(x) (printf("Calling %s from %s\n", #x, FUNCTION), x)
int main ( void ) { char buf[64];
while ( CALL( fgets( buf, sizeof( buff ), stdin ) ) != NULL )
{
CALL( puts( buf ) );
}
return( 0 );
}
A: Sun Microsystems 2000-06-13
下面演示如何编程获取当前运行中线程调用栈回溯。惟一要做的就是在应用程序中调 用csprintstack(),记得链接库选项-ldl。
/*
- For SPARC/Solaris 8
- gcc -D__sparc -Wall -pipe -g -o test test.c -ldl
- For x86/Solaris 9
- gcc -D__i386 -Wall -pipe -g -o test test.c -ldl */ #include <stdio.h> #include <stdlib.h> #include <ucontext.h> #include <dlfcn.h> #include <setjmp.h> #include <sys/frame.h> #include <sys/procfs_isa.h>
#if defined(sparc) || defined(__sparc) #define FRAME_PTR_REGISTER REG_SP #endif
#if defined(i386) || defined(__i386) #define FRAME_PTR_REGISTER EBP #endif
struct frame * csgetframeptr ( void ) { ucontext_t u;
( void )getcontext( &u );
return( ( struct frame * )( ( struct frame * )u.uc_mcontext.gregs[FRAME_PTR_REGISTER] )->fr_savfp );
} /* end of csgetframeptr */
void cswalkstack ( struct frame *fp, int ( *operate_func ) ( void *, void * ), void *usrarg ) { void *savpc;
while ( fp && ( savpc = ( void * )fp->fr_savpc )
&& ( *operate_func )( savpc, usrarg ) == 0 )
{
fp = ( void * )fp->fr_savfp;
}
} /* end of cswalkstack */
static int csprintaddress ( void *pc, void *usrarg ) { Dl_info info; char *func; char *lib;
if ( dladdr( pc, &info ) == 0 )
{
func = "??";
lib = "??";
}
else
{
lib = ( char * )info.dli_fname;
func = ( char * )info.dli_sname;
}
fprintf( ( FILE * )usrarg, "%s:%s+0x%x\n", lib, func,
( unsigned int )pc - ( unsigned int )info.dli_saddr );
return( 0 );
} /* end of csprintaddress */
void csprintstack ( FILE f ) { cswalkstack( csgetframeptr(), csprintaddress, ( void * )f ); } / end of csprintstack */
void call_2 ( void ) { csprintstack( stderr ); } /* end of call_2 */
void call_1 ( void ) { call_2(); } /* end of call_1 */
void call_0 ( void ) { call_1(); } /* end of call_0 */
[scz@ /export/home/scz/src]> gcc -D__sparc -Wall -pipe -g -o test test.c -ldl [scz@ /export/home/scz/src]> ./test test:call_2+0xc test:call_1+0x4 test:call_0+0x4 test:main+0x4 test:_start+0x5c [scz@ /export/home/scz/src]>
[scz@ /export/home/scz/src]> gcc -D__i386 -Wall -pipe -g -o test test.c -ldl [scz@ /export/home/scz/src]> ./test /export/home/scz/src/test:call_2+0x13 /export/home/scz/src/test:call_1+0xb /export/home/scz/src/test:call_0+0xb /export/home/scz/src/test:main+0x15 /export/home/scz/src/test:_start+0x5d [scz@ /export/home/scz/src]>
2.4 如何编程获取栈底地址
Q: 虽然很多操作系统的用户进程栈底地址固定,但是我需要写一个可广泛移植C程序 获取这个栈底地址。
A: tt [email protected] 2001-06-02 19:40
假设堆栈(stack)向低地址方向增长,则所谓栈底指堆栈(stack)最高地址
x86/Linux 栈底是0xC0000000 (栈底往低地址的4个字节总是零) SPARC/Solaris 7/8 栈底是0xFFBF0000 (栈底往低地址的4个字节总是零) SPARC/Solaris 2.6 栈底是0xF0000000 (栈底往低地址的4个字节总是零) x86/Solaris 8 栈底是0x08048000 x86/FreeBSD 栈底是0xBFC00000 (栈底往低地址的4个字节总是零) x86/NetBSD 1.5 栈底是0xBFBFE000 x86/OpenBSD 2.8/3.0 栈底是0xDFBFE000 AIX 4.3.3.0 栈底是0x2FF23000
D: jonah
对于NetBSD 1.5,栈底是0xBFC00000。根据源码,最高用户地址是0xBFBFE000,因为 最后4MB(2^22)的最后两页(0x2000字节,一页4096字节)保留用做U区,但是目前不再 使用这块内存。因此,0xBFBFE000才是真正的栈底。
tt在OpenBSD 2.8上测试结果,栈底是0xDFBFE000,注意和NetBSD 1.5相差很大。
A: tt [email protected]
/*
- gcc -Wall -pipe -O3 -o gstack gstack.c
- A simple example to get the current stack bottom address
- warning3 [email protected]
- 2001-06-01
- Modified by scz [email protected]
- 2001-06-02 */
#include <stdio.h> #include <stdlib.h> #include <signal.h> #include <unistd.h> #include <setjmp.h>
/*
- for signal handlers */ typedef void Sigfunc ( int );
static char * get_stack_bottom ( void ); static Sigfunc * PrivateSignal ( int signo, Sigfunc * func ); static void segfault ( int signo ); static Sigfunc * Signal ( int signo, Sigfunc * func );
static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0; static Sigfunc seg_handler; /
- for xxxBSD */ static Sigfunc *bus_handler;
static char * get_stack_bottom ( void ) { /* * for autovar, must be volatile */ volatile char *c;
seg_handler = Signal( SIGSEGV, segfault );
bus_handler = Signal( SIGBUS, segfault );
c = ( char * )&c;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
Signal( SIGSEGV, seg_handler );
Signal( SIGBUS, bus_handler );
return( ( char * )c );
}
/*
* now sigsetjump() is OK
*/
canjump = 1;
while ( 1 )
{
*c = *c;
c++;
}
return( NULL );
} /* end of get_stack_bottom */
static Sigfunc * PrivateSignal ( int signo, Sigfunc * func ) { struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( sigaction( signo, &act, &oact ) < 0 )
{
return( SIG_ERR );
}
return( oact.sa_handler );
} /* end of PrivateSignal */
static void segfault ( int signo ) { if ( 0 == canjump ) { /* * unexpected signal, ignore / return; } canjump = 0; / * jump back to main, don't return / siglongjmp( jmpbuf, signo ); } / end of segfault */
/*
-
for our signal() function */ static Sigfunc * Signal ( int signo, Sigfunc * func ) { Sigfunc * sigfunc;
if ( SIG_ERR == ( sigfunc = PrivateSignal( signo, func ) ) ) { exit( EXIT_FAILURE ); } return( sigfunc ); } /* end of Signal */
int main ( int argc, char * argv[] ) { fprintf ( stderr, "Current stack bottom is 0x%08x\n", ( unsigned int )get_stack_bottom() ); return( EXIT_SUCCESS ); } /* end of main */
D: scz [email protected] 2001-06-03 00:38
W. Richard Stevens在<>中详细 介绍了setjmp/longjmp以及sigsetjmp/siglongjmp函数。
这个程序的原理很简单,不断向栈底方向取值,越过栈底的地址访问会导致SIGSEGV 信号,然后利用长跳转回到主流程报告当前c值,自然对应栈底。
tt测试表明,在x86/FreeBSD中导致SIGBUS信号。据jonah报告,不仅仅是FreeBSD, NetBSD 以及 OpenBSD 系统中上述程序越界访问也导致SIGBUS信号,而不是SIGSEGV 信号。
非局部转移,比如函数间转移的时候考虑使用setjmp/longjmp。但是如果涉及到信号 句柄与主流程之间的转移,就不能使用longjmp了。当捕捉到信号进入信号句柄,此 时当前信号被自动加入进程的信号屏蔽字中,阻止后来产生的这种信号干扰此信号句 柄。如果用longjmp跳出信号句柄,此时进程的信号屏蔽字状态未知,有些系统做了 保存恢复,有些系统没有做,比如x86/Linux Kernel 2.4.7-10的setjmp/longjmp没 有做信号屏蔽字的保存恢复。根据POSIX.1,此时应该使用sigsetjmp/siglongjmp函 数。下面是来自SPARC/Solaris 7的setjmp(3C)
#include <setjmp.h>
int setjmp ( jmp_buf env ); int sigsetjmp ( sigjmp_buf env, int savemask ); void longjmp ( jmp_buf env, int val ); void siglongjmp ( sigjmp_buf env, int val );
如果savemask非0,sigsetjmp在env中保存进程当前信号屏蔽字,相应siglongjmp回 来的时候从env中恢复信号屏蔽字。
数据类型sig_atomic_t由ANSI C定义,在写时不会被中断。它意味着这种变量在具有 虚存的系统上不会跨越页边界,可以用一条机器指令对其存取。这种类型的变量总是 与ANSI类型修饰符volatile一并出现,防止编译器优化带来的不确定状态。
在longjmp/siglongjmp中,全局、静态变量保持不变,声明为volatile的自动变量也 保持不变。
无论是否使用了编译优化开关,为了保证广泛兼容性,都应该在get_stack_bottom() 中声明c为volatile变量。
注意这里,必须使用长跳转,而不能从信号句柄中直接返回。因为导致信号SIGSEGV、 SIGBUS分发的语句始终存在,直接从信号句柄中返回主流程,将回到引发信号的原指 令处,而不是下一条指令(把这种情况理解成异常,而不是中断),于是立即导致下一 次信号分发,出现广义上的死循环,所谓程序僵住。可以简单修改上述程序,不利用 长跳转,简单对一个全局变量做判断决定是否继续循环递增c,程序最终僵住;如果 在信号句柄中输出调试信息,很容易发现这个广义上的无限循环。
D: scz [email protected] 2001-06-03 00:40
在x86/Linux系统中用如下命令可以确定栈区所在
... ... bfffe000-c0000000 rwxp fffff000 00:00 0
在SPARC/Solaris 7中用/usr/proc/bin/pmap命令确定栈区所在
... ... FFBEC000 16K read/write/exec [ stack ]
16KB == 0x4000,0xFFBEC000 + 0x4000 == 0xFFBF0000
与前面tt介绍的
SPARC/Solaris 7/8 栈底是0xffbf0000( 栈底往低地址的4个字节总是零 )
相符合。
此外,在SPARC/Solaris 7下,可以这样验证之
[7015] |0x0000100546f8|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit [8051] |0x000010054700|0x000000000008|OBJT |GLOB |0 |ABS |_userlimit32
physmem 3b72 _userlimit: _userlimit: ffffffff80000000
byteArray [ 8 bytes ] ----> 0000000000000000 00 00 00 00 FF BF 00 00
空间上限
如果编译64-bit应用程序,用户空间上限是_userlimit,也就是0xffffffff80000000
Current stack bottom is at 0xffffffff80000000
对于SPARC/Solaris 2.6 32-bit kernel mode
physmem 3d24 _userlimit: _userlimit: f0000000
Q: 在x86/Linux平台上如何定位栈区(stack)的栈底(高址)与栈顶(低址)位置。
D: "Andrew Gabriel" [email protected]
试试getcontext(2)
A: "Shaun Clowes" [email protected]
检查/proc//stat,其中有两个域对应栈底(非页对齐的)与栈顶。
如果使用getcontext(2),可以通过struct ucontext的uc_mcontext成员获取栈顶位 置,参看/usr/include/sys/ucontext.h。不幸的是此时uc_stack成员未被设置,无 法简单获取栈底位置,至少对于我所检测的版本而言,Redhat 2.4.18-3smp kernel with glibc 2.2.5。
2.5 如何得到一个运行中进程的内存映像
A: Sun Microsystems 1998-03-30
有些时候必须得到一个运行中进程的内存映像而不能停止该进程,Solaris系统了这 样的工具,gcore为运行中进程创建一个core文件。假设我的bash进程号是5347
gcore: core.5347 dumped
core.5347: ELF 32-位 MSB core文件 SPARC 版本 1,来自'bash'
注意,只能获取属主是你自己的进程的内存映像,除非你是root。
2.6 调试器如何工作的
Q: 我想在一个自己编写的程序中单步运行另外一个程序,换句话说,那是一个调试 器,该如何做?
A: Erik de Castro Lopo [email protected]
这是一个操作系统相关的问题。最一般的回答是使用ptrace()系统调用,尽管我 不确认究竟这有多么普遍。Linux man手册上说SVr4、SVID EXT、AT&T、X/OPEN 和BSD 4.3都支持它。
为了使用ptrace(),你的程序应该调用fork(),然后在子进程中做如下调用:
ptrace( PTRACE_TRACEME, 0, 0, 0 );
接下来调用exec()家族的函数执行你最终企图跟踪的程序。
为了单步进入子进程,在父进程中调用:
ptrace( PTRACE_SINGLESTEP, 0, 0, 0 );
还有一些其他函数做恢复/设置寄存器、内存变量一类的工作。
GDB的源代码足以回答这个问题。
2.7 x86/Linux上如何处理SIGFPE信号
Q: 参看如下程序
/*
- gcc -Wall -pipe -O3 -o sigfpe_test_0 sigfpe_test_0.c
- 注意与下面的编译效果进行对比,去掉优化开关-O3
- gcc -Wall -pipe -o sigfpe_test_0 sigfpe_test_0.c */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <setjmp.h>
/*
-
for signal handlers */ typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func ); static void on_fpe ( int signo );
Sigfunc * signal ( int signo, Sigfunc *func ) { struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x / #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; / SVR4, 44BSD / #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); } return( oact.sa_handler ); } / end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo ) { fprintf( stderr, "here is on_fpe\n" ); return; } /* end of on_fpe */
int main ( int argc, char * argv[] ) { unsigned int i;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
有-O3、无-O3,以及有无最后那条fprintf()语句,效果上有差别,自行对比。如果 输出"here is on_fpe",则会发现永不停止。
D: 小四 [email protected] 2001-12-14 18:25
在上述代码中,on_fpe()直接返回了,再次触发除零错,所以无休止输出。事实上在 所有的计算器处理程序中,都会对SIGFPE信号做相应处理,前些日子看yacc/lex的时 候又碰上过。正确的做法是,利用远跳转转移,让开触发除零错的代码。
代码修改如下
/*
- gcc -Wall -pipe -O3 -o sigfpe_test_1 sigfpe_test_1.c
- 注意与下面的编译效果进行对比,去掉优化开关-O3
- gcc -Wall -pipe -o sigfpe_test_1 sigfpe_test_1.c */
#include <stdio.h> #include <stdlib.h> #include <string.h> #include <signal.h> #include <unistd.h> #include <setjmp.h>
/*
-
for signal handlers */ typedef void Sigfunc ( int );
Sigfunc * signal ( int signo, Sigfunc *func );
static Sigfunc * Signal ( int signo, Sigfunc *func ); static void on_fpe ( int signo );
static sigjmp_buf jmpbuf; static volatile sig_atomic_t canjump = 0;
Sigfunc * signal ( int signo, Sigfunc *func ) { struct sigaction act, oact;
act.sa_handler = func;
sigemptyset( &act.sa_mask );
act.sa_flags = 0;
if ( signo == SIGALRM )
{
#ifdef SA_INTERRUPT act.sa_flags |= SA_INTERRUPT; /* SunOS 4.x / #endif } else { #ifdef SA_RESTART act.sa_flags |= SA_RESTART; / SVR4, 44BSD / #endif } if ( sigaction( signo, &act, &oact ) < 0 ) { return( SIG_ERR ); } return( oact.sa_handler ); } / end of signal */
static Sigfunc * Signal ( int signo, Sigfunc *func ) { Sigfunc *sigfunc;
if ( ( sigfunc = signal( signo, func ) ) == SIG_ERR )
{
perror( "signal" );
exit( EXIT_FAILURE );
}
return( sigfunc );
} /* end of Signal */
static void on_fpe ( int signo ) { if ( canjump == 0 ) { return; /* unexpected signal, ignore / } canjump = 0; fprintf( stderr, "here is on_fpe\n" ); siglongjmp( jmpbuf, signo ); / jump back to main, don't return / return; } / end of on_fpe */
int main ( int argc, char * argv[] ) { unsigned int i;
if ( sigsetjmp( jmpbuf, 1 ) != 0 )
{
fprintf( stderr, "c u later\n" );
return( EXIT_SUCCESS );
}
/*
* now sigsetjump() is OK
*/
canjump = 1;
Signal( SIGFPE, on_fpe );
i = 51211314 / 0;
/*
* 另外,增加这行后,再次对比有-O3和无-O3的效果
*
* fprintf( stderr, "i = %#X\n", i );
*/
return( EXIT_SUCCESS );
关于-O3的讨论,对gcc编译器熟悉的朋友请继续,呵,我对Linux下的这此东西,实 在缺乏兴趣。
2.8 GDB调试时没有符号表,如何设置断点
Q: 在OpenBSD 3.0下想用gdb跟踪/usr/bin/skeyaudit(一个setuid-to-root程序), 却因没有符号表无法设置断点
A: tt [email protected]
start address 0x00001020
(gdb) x/50i 0x1020 0x1020: push %ebp <-- 这是start 0x1021: mov %esp,%ebp 0x1023: sub $0xc,%esp 0x1026: push %edi 0x1027: push %esi 0x1028: push %ebx 0x1029: lea 0x4(%ebp),%esi 0x102c: lea 0x4(%esi),%edi 0x102f: mov (%esi),%eax 0x1031: shl $0x2,%eax 0x1034: lea 0x4(%eax,%edi,1),%eax 0x1038: mov %eax,0x33f8 0x103d: mov 0x4(%esi),%ebx 0x1040: test %ebx,%ebx 0x1042: je 0x106a 0x1044: add $0xfffffff8,%esp 0x1047: push $0x2f 0x1049: push %ebx 0x104a: call 0x1774 <-- 这是一个错误处理入口 0x104f: mov %eax,0x3190 0x1054: add $0x10,%esp 0x1057: test %eax,%eax 0x1059: jne 0x1064 0x105b: mov %ebx,0x3190 0x1061: jmp 0x106a 0x1063: nop 0x1064: inc %eax 0x1065: mov %eax,0x3190 0x106a: movl $0x3000,0xfffffffc(%ebp) 0x1071: mov 0xfffffffc(%ebp),%eax 0x1074: mov 0xfffffffc(%ebp),%eax 0x1077: test %eax,%eax 0x1079: je 0x108b 0x107b: add $0xfffffff4,%esp 0x107e: push $0x3000 0x1083: call 0x1150 0x1088: add $0x10,%esp 0x108b: sub $0x10,%esp 0x108e: pushl 0x33f8 <-- 可以先在OpenBSD用gcc编译一个带符号表的程序,找 0x1094: push %edi 出这种汇编指令特征 0x1095: pushl (%esi) 0x1097: call 0x1840 <-- 这就是main 0x109c: push %eax 0x109d: call 0x307c 0x10a2: pop %ecx 0x10a3: pop %eax 0x10a4: push %ecx ---Type to continue, or q to quit---(Ctrl-C)Quit (gdb) x/20i 0x1840 <-- 这里不能用disas,因为没有符号表 0x1840: push %ebp 0x1841: mov %esp,%ebp 0x1843: sub $0x13c,%esp 0x1849: push %edi 0x184a: push %esi 0x184b: push %ebx 0x184c: mov 0x8(%ebp),%ebx 0x184f: mov 0xc(%ebp),%edi 0x1852: call 0x205c 0x1857: movl $0x0,0xfffffee0(%ebp) 0x1861: movl $0x0,0xfffffedc(%ebp) 0x186b: movl $0x0,0xfffffed8(%ebp) 0x1875: movl $0x0,0xfffffed4(%ebp) 0x187f: movl $0xc,0xfffffed0(%ebp) 0x1889: call 0x314c 0x188e: test %eax,%eax 0x1890: je 0x18a4 0x1892: add $0xfffffff8,%esp 0x1895: push $0x1798 0x189a: push $0x1 (gdb) (gdb) b *0x1840 Breakpoint 1 at 0x1840 (gdb) r Starting program: /usr/bin/skeyaudit
Breakpoint 1, 0x1840 in ?? () (gdb) x/20i $pc (gdb) q The program is running. Quit anyway (and kill it)? (y or n) y
- -lelf、-lkvm、-lkstat相关问题
3.1 如何判断可执行文件是否携带了调试信息
Q:
某些时候需要知道编译可执行文件时是否携带了调试信息(比如是否指定了-g编译选 项)。
A: Sun Microsystems 2000-05-15
检查可执行文件中是否包含".stab" elf section,".stab" section用于保存相关调 试信息。
下面这个脚本演示如何判断可执行文件是否携带调试信息
#! /bin/sh
if [ $# -le 0 ] then echo "Usage: $0 " exit 1 fi
if [ ! -f $1 ] then echo "File $1 does not exist" exit 1 fi
/usr/ccs/bin/dump -hv $1 | /bin/egrep -s '.stab$ '
if [ $? -eq 0 ]
then
echo "File '$1' has been built for debug"
exit 0
else
echo "File '$1' has not been built for debug"
exit 1
fi
D: scz@nsfocus
如果对ELF文件格式不熟悉,理解上述代码可能有点困难,参看:
http://www.digibel.org/~tompy/hacking/elf.txt http://www.muppetlabs.com/~breadbox/software/ELF.txt
这是1.1版的ELF文件格式规范。
一般随着".stab"节的出现,还会出现".stabstr"节。似乎本办法只适用于Solaris, 不适用于Linux?在Linux上测试时,要么是无论如何都不出现".stab"节,要么是无 论如何都出现".stab"节,见鬼。我是用"objdump -h"测试的。
3.2 mprotect如何用
A: 小四 [email protected]
下面是SPARC/Solaris 2.6中的mprotect(2)手册页
系统调用 mprotect(2)
名字
mprotect - 设置内存权限
摘要
#include <sys/mman.h>
int mprotect ( void *addr, size_t len, int prot );
描述
mprotect()设置范围[addr, addr + len),一个左闭右开区间。len
将向上舍入到页大小的整数倍。可以利用sysconf(3C)获取页大小。
形参prot指定内存权限。prot的合法取值与mmap()使用的一致,定义
在<sys/mman.h>文件中
PROT_READ /* page can be read */
PROT_WRITE /* page can be written */
PROT_EXEC /* page can be executed */
PROT_NONE /* page can not be accessed */
如果mprotect()失败原因不是EINVAL,则可能出现[addr, addr + len)
中的部分页面内存权限修改成功,而另一部分修改失败。比如从addr2
开始设置权限失败,则[addr, addr2]之间的页面设置权限成功
返回值
成功返回0。否则返回-1,检查errno。
错误码
EACCES The prot argument specifies a protection that
violates the access permission the process
has to the underlying memory object.
EINVAL 形参len小于等于0,或者形参addr不在页边界上
ENOMEM [addr, addr + len)不是进程的合法地址空间,或
者指定了一个或多个尚未映射过的页面
EAGAIN [addr, addr + len)包含了一个或多个页面,这些
页面在内存中被锁定并且是以MAP_PRIVATE方式映射
的,形参prot带有PROT_WRITE设置,系统没有足够
资源用于可能创建的私有页面。如果对以前不可写
而现在可写的(刚设置成可写的)地址范围进行写操
作,就会创建私有页面(写时复制,copy-on-write)。
参看
mmap(2)、plock(3C)、mlock(3C)、mlockall(3C)、sysconf(3C)
sysconfig(_CONFIG_PAGESIZE) = 8192 sysconfig(_CONFIG_PHYS_PAGES) = 16384
由此可知当前系统页尺寸是8192字节。
/*
- gcc -Wall -pipe -g -static -o mtest mtest.c */ #include <stdio.h> #include <stdlib.h> #include <errno.h> #include <sys/mman.h>
int main ( int argc, char * argv[] ) { char *buf; char c;
/*
* 分配一块内存,拥有缺省的rw-保护
*/
buf = ( char * )malloc( 1024 + 8191 );
if ( !buf )
{
perror( "malloc" );
exit( errno );
}
/*
* Align to a multiple of PAGESIZE, assumed to be a power of two
*/
buf = ( char * )( ( ( unsigned int )buf + 8191 ) & ~8191 );
c = buf[77];
buf[77] = c;
printf( "ok\n" );
/*
* Mark the buffer read-only.
*
* 必须保证这里buf位于页边界上,否则mprotect()失败,报告无效参数
*/
if ( mprotect( buf, 1024, PROT_READ ) )
{
perror( "\nmprotect" );
exit( errno );
}
c = buf[77];
/*
* Write error, program dies on SIGSEGV
*/
buf[77] = c;
exit( 0 );
$ ./mtest ok 段错误 (core dumped) <-- 内存保护起作用了 $
3.3 mmap如何用
A: 小四 [email protected]
下面是SPARC/Solaris 2.6中的mmap(2)手册页
系统调用 mmap(2)
名字
mmap - map pages of memory
摘要
#include <sys/mman.h>
void * mmap ( void *addr, size_t len, int prot, int flags,
int fildes, off_t off );
描述
mmap()在进程地址空间和一个虚拟内存对象之间建立一种映射关系
pa = mmap( addr, len, prot, flags, fildes, off );
fildes是一个文件句柄,off是文件内偏移,len指明映射多少字节。
返回值pa是实现相关的一个值。地址范围[pa, pa + len)和文件偏移
范围[off, off + len)之间存在映射关系。
概念上,[pa, pa + len)可以越过文件尾,比如文件是新创建的或者
被截断过。但是不能引用越过文件尾的地址,这会导致分发SIGBUS、
SIGSEGV信号。换句话说,mmap()不能用于扩展文件长度。
在地址范围[pa, pa + len)上,后续的mmap()将替代前面的mmap()。
映射建立后,close(2)文件句柄并不会导致映射关系自动拆除。应该
使用munmap(2)拆除映射关系。
prot形参指明映射页面的内存权限,参看<sys/mman.h>中的定义
PROT_READ Page can be read.
PROT_WRITE Page can be written.
PROT_EXEC Page can be executed.
PROT_NONE Page can not be accessed.
PROT_WRITE 经常实现成rw-,PROT_EXEC则对应r-x。如果PROT_WRITE
未被设置,肯定不可写。
flags形参也定义在<sys/mman.h>文件中
MAP_SHARED Share changes.
MAP_PRIVATE Changes are private.
MAP_FIXED Interpret addr exactly.
MAP_NORESERVE Don't reserve swap space.
如果指定 MAP_SHARED,内存写操作立即为映射过同一文件的其他进
程所察觉。如果指定 MAP_PRIVATE,第一次内存写操作导致写时复制
(copy-on-write),创建一个私有页面,映射过同一文件的其他进程
无法察觉这次及其后续的内存写操作。注意,创建私有页面之前,以
MAP_SHARED方式映射过同一文件的其他进程的写操作将立即为当前进
程所察觉。必须指定 MAP_SHARED 或者 MAP_PRIVATE,但不能同时指
定。fork()后这种设置依旧有效。
译注:指定MAP_SHARED才可能使写操作最终影响对象/文件,指
定MAP_PRIVATE始终无法使写操作最终影响对象/文件,所
做修改局限于内存区域,即使调用msync()也无意义。
指定MAP_SHARED,意味着内存读/写对应对象/文件I/O。
指定MAP_PRIVATE,意味着仅仅是内存读/写。
如果指定 MAP_FIXED,返回值pa等于形参addr,不鼓励指定
MAP_FIXED,这可能导致无法最高效地利用系统资源。
如果指定 MAP_FIXED 的同时,形参addr与前次映射返回的pa相等,
则首先拆除前次映射关系,然后建立新的映射关系。
如果未指定 MAP_FIXED ,分两种情况。如果形参addr不为零,系统
参照形参addr、len自行挑选addr附近的最终映射地址pa。如果形参
addr为零,则系统任意挑选pa,但绝不会在地址0处开始映射,不会
替代任何现存映射,也不会映射到被认为是数据段(data)、堆栈段
(stack)一部分的内存区域上去。
如果不指定 MAP_NORESERVE,以 MAP_PRIVATE 方式进行可写映射时,
系统保留空间用于可能创建的私有页面,发生写操作时保留空间被用
于创建私有页面。指定 MAP_NORESERVE 以 MAP_PRIVATE 方式进行可
写映射,如果发生写操作,系统检查当前可用空间,能创建私有页面
时写操作按计划进行,可用空间不足以创建私有页面时,分发SIGBUS
或者 SIGSEGV 信号到做写操作的进程。MAP_NORESERVE 设置在fork()
后为子进程所继承,但在fork()的瞬间,对于父进程中已经存在的私
有页面(copy-on-write发生过了),在子进程中将为之保留空间,之
后子进程的映射行为和前面描述的相一致。
形参off应该对齐在页边界上,给sysconf(3C)传入形参_SC_PAGESIZE
或者_SC_PAGE_SIZE即可返回页大小。如果指定了 MAP_FIXED ,形参
addr也必须对齐在页边界上。系统自动完成整页映射,形参len将向
上舍入到页大小的整数倍(类似mprotect)。
系统总是用零填充最后一页越过文件尾的内存区域,即使修改了这个
区域,也永远不会将该区域的任何内容写入文件。引用这个区域的地
址导致分发 SIGBUS 或 SIGSEGV 信号。违背磁盘限额设置的时候,
也会分发 SIGBUS 信号。
被映射文件的 st_atime 将被更新成mmap(2)和相应munmap(2)之间的
某个时间。对映射内存区域第一次读/写时,如果这之前文件的
st_atime 未被标记/更新,则这次读/写操作导致文件的 st_atime
被标记/更新。
文件以 MAP_SHARED 方式可写映射,它的 st_ctime 和 st_mtime 将
被更新成内存写操作和任一相关进程对被写内存区域msync(3C)调用(
指定 MS_ASYNC 或者 MS_SYNC )之间的某个时间。如果未发生这样的
msync(3C)调用,st_ctime 和 st_mtime 将被更新成有效内存写操作
(指内存写操作最终影响了文件)之后的某个时间。
如果进程指定 MCL_FUTURE 标志调用过mlockall(3C),这之后mmap()
映射上来的页面均被锁定在内存中(不被交换到磁盘上去)。此时如果
没有足够内存满足锁定要求,mmap()调用失败,errno被设置成
EAGAIN。
返回值
成功则返回最终映射地址pa,失败时返回MAP_FAILED并设置errno
错误码
EACCES 1) 文件句柄fildes非可读打开,无论prot如何指定
2) 文件句柄fildes非可写打开,却准备以
MAP_SHARED 方式进行可写映射(PROT_WRITE)
EAGAIN 映射上来的页面无法锁定在内存中
没有足够内存满足映射中所要求的保留空间
The file to be mapped is already locked using
advisory or mandatory record locking. See
fcntl(2).
EBADF 文件句柄fildes尚未打开
EINVAL 形参addr(指定 MAP_FIXED)或者off未对齐在页边界
上(页大小从sysconf(3C)返回)
形参flags无效,既不是 MAP_PRIVATE 也不是
MAP_SHARED
形参len小于等于0
EMFILE 映射内存区域的数量超过某个实现相关的限制(基于
进程的或者基于整个系统的)
ENODEV 文件句柄fildes描述的对象对于mmap()而言无意义,
比如终端。
ENOMEM 指定了MAP_FIXED,而[addr, addr + len)超出了进
程地址空间
未指定MAP_FIXED,但是没有足够空间完成映射
The composite size of len plus the lengths of
all previous mmappings exceeds RLIMIT_VMEM
(see getrlimit(2)).
ENXIO 映射这个范围[off, off + len),对于对象(文件/
设备)来说非法
EOVERFLOW The file is a regular file and the value of
off plus len exceeds the offset maximum
establish in the open file description asso-
ciated with fildes.
用法
mmap()允许以内存操作方式访问对象,而不是read/write接口。
fildes = open(...)
lseek( fildes, offset, whence )
read( fildes, buf, len )
/* use data in buf */
下面是mmap()方式
fildes = open(...)
address = mmap( ( caddr_t )0, len, ( PROT_READ | PROT_WRITE ),
MAP_PRIVATE, fildes, offset )
/* use data at address */
The mmap() function has an explicit 64-bit equivalent. See
interface64(5).
参看
close(2)、exec(2)、fcntl(2)、fork(2)、getrlimit(2)、mprotect(2)
munmap(2)、shmat(2)、lockf(3C)、mlockall(3C)、msync(3C)、
plock(3C)、sysconf(3C)、interface64(5)
下面写一个完成文件复制功能的小程序,利用mmap(2),而不是标准文件I/O接口。
/*
- gcc -Wall -pipe -O3 -o copy_mmap copy_mmap.c / #include <stdio.h> #include <stdlib.h> #include <string.h> / for memcpy */ #include <strings.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
#define PERMS 0600
int main ( int argc, char * argv[] ) { int src, dst; void *sm, *dm; struct stat statbuf;
if ( argc != 3 )
{
fprintf( stderr, " Usage: %s <source> <target>\n", argv[0] );
exit( EXIT_FAILURE );
}
if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
{
perror( "open source" );
exit( EXIT_FAILURE );
}
/*
* 为了完成复制,必须包含读打开,否则mmap()失败
*/
if ( ( dst = open( argv[2], O_RDWR | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
{
perror( "open target" );
exit( EXIT_FAILURE );
}
if ( fstat( src, &statbuf ) < 0 )
{
perror( "fstat source" );
exit( EXIT_FAILURE );
}
/*
* 参看前面man手册中的说明,mmap()不能用于扩展文件长度。所以这里必须事
* 先扩大目标文件长度,准备一个空架子等待复制。
*/
if ( lseek( dst, statbuf.st_size - 1, SEEK_SET ) < 0 )
{
perror( "lseek target" );
exit( EXIT_FAILURE );
}
if ( write( dst, &statbuf, 1 ) != 1 )
{
perror( "write target" );
exit( EXIT_FAILURE );
}
/*
* 读的时候指定 MAP_PRIVATE 即可
*/
sm = mmap( 0, ( size_t )statbuf.st_size, PROT_READ,
MAP_PRIVATE | MAP_NORESERVE, src, 0 );
if ( MAP_FAILED == sm )
{
perror( "mmap source" );
exit( EXIT_FAILURE );
}
/*
* 这里必须指定 MAP_SHARED 才可能真正改变静态文件
*/
dm = mmap( 0, ( size_t )statbuf.st_size, PROT_WRITE,
MAP_SHARED, dst, 0 );
if ( MAP_FAILED == dm )
{
perror( "mmap target" );
exit( EXIT_FAILURE );
}
memcpy( dm, sm, ( size_t )statbuf.st_size );
/*
* 可以不要这行代码
*
* msync( dm, ( size_t )statbuf.st_size, MS_SYNC );
*/
return( EXIT_SUCCESS );
对照这个使用标准文件I/O接口的实现
/*
- gcc -Wall -pipe -g -static -o copy_io copy_io.c */ #include <stdio.h> #include <stdlib.h> #include <strings.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h>
#define BUFSIZE 8192 /* SPARC/Solaris 2.6上是一页的大小 */ #define PERMS 0600
int main ( int argc, char * argv[] ) { char iobuffer[ BUFSIZE ]; int src, dst, num;
if ( argc != 3 )
{
fprintf( stderr, " Usage: %s <source> <target>\n", argv[0] );
exit( EXIT_FAILURE );
}
if ( ( src = open( argv[1], O_RDONLY ) ) < 0 )
{
perror( "open source" );
exit( EXIT_FAILURE );
}
if ( ( dst = open( argv[2], O_WRONLY | O_CREAT | O_TRUNC, PERMS ) ) < 0 )
{
perror( "open target" );
exit( EXIT_FAILURE );
}
while ( ( num = read( src, iobuffer, BUFSIZE ) ) > 0 )
{
if ( write( dst, iobuffer, num ) != num )
{
perror( "write" );
exit( EXIT_FAILURE );
}
}
close( src );
close( dst );
exit( EXIT_SUCCESS );
mmap()好处是处理大文件时速度明显快于标准文件I/O,无论读写,都少了一次用户 空间与内核空间之间的复制过程。操作内存还便于设计、优化算法。
A: 小四 [email protected]
下面是x86/Linux RedHat 7.0中的mmap(2)手册页
MMAP(2) Linux程序员手册 MMAP(2)
名字
mmap、munmap - 将文件或设备映射到内存中
摘要
#include <unistd.h>
#include <sys/mman.h>
#ifdef _POSIX_MAPPED_FILES
void * mmap ( void *start, size_t length, int prot, int flags,
int fd, off_t offset );
int munmap ( void *start, size_t length );
#endif
描述
length指定映射长度,以字节为单位。offset指明文件或其他设备对象偏移,fd
对应文件句柄。形参start给内核一个建议的起始映射地址,并非强制指定。如
果start为0,表示内核自由选取最终映射地址。mmap()返回值指明了最终映射地
址。形参prot描述了期望的内存权限,必须和打开文件方式相匹配。
PROT_EXEC Pages may be executed.
PROT_READ Pages may be read.
PROT_WRITE Pages may be written.
PROT_NONE Pages may not be accessed.
形参flags可取值如下
MAP_FIXED
start此时为强制指定的映射地址,如果无法在指定地址上完成映射,
mmap()失败返回。如果指定了 MAP_FIXED,start必须对齐在页边界上。
建议不要使用该设置。
MAP_SHARED
此种映射方式下,对内存区域的写操作等价于对文件的写操作,映射过
同一文件的其他进程会立即察觉到这次写操作。磁盘文件可能并非立即
更新,直到msync(2)或munmap(2)被调用。
MAP_PRIVATE
写时复制(copy-on-write)映射方式。此种映射方式下对内存的写操作
不会影响磁盘文件。
必须指定 MAP_SHARED 和 MAP_PRIVATE 中的一个,但不能同时指定。
上述三个flags在POSIX.1b(以前的 POSIX.4)中有描述。Linux自己还支持
MAP_DENYWRITE、MAP_EXECUTABLE、MAP_NORESERVE、MAP_LOCKED、
MAP_GROWSDOWN 以及 MAP_ANON(YMOUS)
形参offset通常应该位于页边界上,可由getpagesize(2)获取页尺寸。但是,这
个形参可以不位于页边界上。
译注:这里和SPARC/Solaris 2.6不同,Solaris要求offset必须对齐在页边
界上,否则返回EINVAL错误码。Linux下有所变化,注意这些差别。
munmap()拆除映射关系。无法引用被拆除映射关系的地址范围。进程终止时映射
自动拆除。关闭文件句柄fd并不会导致映射关系拆除。
返回值
mmap()成功返回一个指针,失败返回MAP_FAILED(-1),并设置errno。
munmap()成功返回0,失败返回-1,并设置errno(可能是EINVAL)
错误码
EBADF 形参fd是个无效文件句柄(同时未指定MAP_ANONYMOUS)
EACCES 指定了MAP_PRIVATE,但是fd打开模式不包括读
指定了MAP_SHARED可写映射(PROT_WRITE),但是fd打开模式不是O_RDWR
EINVAL 形参start、length或者offset有问题,比如太大范围、未对齐在页边界
上等等
ETXTBUSY
指定了MAP_DENYWRITE,但是fd打开模式包括写
EAGAIN 文件被锁定,或者太多内存被锁定
ENOMEM 无足够内存可用
使用映射可能涉及到如下信号
SIGSEGV
试图对只读映射区域进行写操作
SIGBUS 试图访问一块无文件内容对应的内存区域,比如超过文件尾的内存区域,
或者以前有文件内容对应,现在为另一进程截断过的内存区域。
遵从
SVr4、POSIX.1b(以前的POSIX.4)、4.4BSD
Svr4标准中包括额外的错误码 ENXIO 和 ENODEV
参看
getpagesize(2)、msync(2)、shm_open(2)
下面的测试程序在x86/Linux RedHat 7.0上调试通过
/*
- gcc -Wall -pipe -O3 -o procmem_mmap_2 procmem_mmap_2.c / #include <stdio.h> #include <stdlib.h> #include <string.h> / for memcpy / #include <strings.h> #include <sys/mman.h> #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> #include <unistd.h> #include <errno.h> #include <limits.h> / for PAGESIZE */
#ifndef PAGESIZE #define PAGESIZE 4096 #endif
static void outputBinary ( const unsigned char *byteArray, const size_t byteArrayLen ) { size_t offset, k, j, i;
fprintf( stderr, "byteArray [ %u bytes ] ----> \n", byteArrayLen );
if ( byteArrayLen <= 0 )
{
return;
}
i = 0;
offset = 0;
for ( k = byteArrayLen / 16; k > 0; k--, offset += 16 )
{
fprintf( stderr, "%08X ", offset );
for ( j = 0; j < 16; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
fprintf( stderr, " " );
i -= 16;
for ( j = 0; j < 16; j++, i++ )
{
/* if ( isprint( (int)byteArray[i] ) ) */
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
} /* end of for */
k = byteArrayLen - i;
if ( k <= 0 )
{
return;
}
fprintf( stderr, "%08X ", offset );
for ( j = 0 ; j < k; j++, i++ )
{
if ( j == 8 )
{
fprintf( stderr, "-%02X", byteArray[i] );
}
else
{
fprintf( stderr, " %02X", byteArray[i] );
}
}
i -= k;
for ( j = 16 - k; j > 0; j-- )
{
fprintf( stderr, " " );
}
fprintf( stderr, " " );
for ( j = 0; j < k; j++, i++ )
{
if ( ( byteArray[i] >= ' ' ) && ( byteArray[i] <= 255 )
&& ( byteArray[i] != 0x7f ) )
{
fprintf( stderr, "%c", byteArray[i] );
}
else
{
fprintf( stderr, "." );
}
}
fprintf( stderr, "\n" );
return;
} /* end of outputBinary */
int main ( int argc, char * argv[] ) { int fd; void *mem; char buf[ PAGESIZE ]; off_t offset;
if ( ( fd = open( "/proc/self/mem", O_RDONLY ) ) < 0 )
{
perror( "open /proc/self/mem" );
exit( EXIT_FAILURE );
}
fprintf( stderr, "main = %#x\n", ( unsigned int )&main );
outputBinary ( ( const unsigned char * )&main, 32 );
offset = ( off_t )&main + 1;
if ( lseek( fd, offset, SEEK_SET ) < 0 )
{
perror( "lseek /proc/self/mem" );
exit( EXIT_FAILURE );
}
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
{
perror( "read /proc/self/mem" );
exit( EXIT_FAILURE );
}
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
outputBinary ( ( const unsigned char * )buf, 32 );
/*
* 读的时候指定 MAP_PRIVATE 即可
*/
mem = mmap( 0, PAGESIZE, PROT_READ, MAP_PRIVATE | MAP_NORESERVE, fd, offset );
if ( MAP_FAILED == mem )
{
perror( "mmap /proc/self/mem" );
exit( EXIT_FAILURE );
}
fprintf( stderr, "mem = %#x\n", ( unsigned int )mem );
outputBinary ( ( const unsigned char * )mem, 32 );
offset = ( off_t )( ( ( unsigned int )&main & ~( PAGESIZE - 1 ) ) + PAGESIZE );
if ( lseek( fd, offset, SEEK_SET ) < 0 )
{
perror( "lseek /proc/self/mem" );
exit( EXIT_FAILURE );
}
if ( read( fd, buf, PAGESIZE ) != PAGESIZE )
{
perror( "read /proc/self/mem" );
exit( EXIT_FAILURE );
}
fprintf( stderr, "offset = %#x\n", ( unsigned int )offset );
outputBinary ( ( const unsigned char * )buf, 32 );
if ( munmap( mem, PAGESIZE ) < 0 )
{
perror( "munmap /proc/self/mem" );
exit( EXIT_FAILURE );
}
close( fd );
return( EXIT_SUCCESS );
$ ./procmem_mmap_2 main = 0x804886c byteArray [ 32 bytes ] ----> 00000000 55 89 E5 57 56 53 81 EC-24 10 00 00 6A 00 68 81 U